Импорт чатов с телеграмм, асинхронная загрузка сообщений
This commit is contained in:
@@ -48,6 +48,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
typingUsers,
|
||||
pinnedMessages,
|
||||
isLoadingMessages,
|
||||
hasMoreMessages,
|
||||
loadMessages,
|
||||
setActiveChat,
|
||||
} = useChatStore();
|
||||
|
||||
@@ -113,6 +115,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
}
|
||||
const firstUnreadId = activeChat === sessionUnreadRef.current.chatId ? sessionUnreadRef.current.msgId : null;
|
||||
const lastObservedMessageIdRef = useRef<string | null>(null);
|
||||
const prevScrollHeightRef = useRef<number>(0);
|
||||
|
||||
// Load muted state
|
||||
useEffect(() => {
|
||||
@@ -199,7 +202,16 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
}
|
||||
setScrollReady(true);
|
||||
}
|
||||
}, [activeChat, isLoadingMessages]);
|
||||
}, [activeChat, isLoadingMessages && chatMessages.length === 0]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (prevScrollHeightRef.current > 0 && messagesContainerRef.current) {
|
||||
const container = messagesContainerRef.current;
|
||||
const diff = container.scrollHeight - prevScrollHeightRef.current;
|
||||
container.scrollTop += diff;
|
||||
prevScrollHeightRef.current = 0;
|
||||
}
|
||||
}, [chatMessages.length]);
|
||||
|
||||
// Scroll on new message arrivals
|
||||
useEffect(() => {
|
||||
@@ -316,6 +328,12 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
|
||||
const handleScroll = () => {
|
||||
checkScrollPosition();
|
||||
|
||||
const container = messagesContainerRef.current;
|
||||
if (container && container.scrollTop < 100 && activeChat && hasMoreMessages[activeChat] && !isLoadingMessages) {
|
||||
prevScrollHeightRef.current = container.scrollHeight;
|
||||
useChatStore.getState().loadMessages(activeChat, false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -851,7 +869,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
onScroll={handleScroll}
|
||||
className={`flex-1 overflow-y-auto px-6 pt-6 pb-2 relative z-10 ${!scrollReady && !isLoadingMessages && chatMessages.length > 0 ? 'invisible' : ''}`}
|
||||
>
|
||||
{isLoadingMessages ? (
|
||||
{isLoadingMessages && chatMessages.length === 0 ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-6 h-6 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
@@ -861,6 +879,11 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1 max-w-3xl mx-auto">
|
||||
{isLoadingMessages && (
|
||||
<div className="flex justify-center py-4">
|
||||
<div className="w-5 h-5 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
{chatMessages.map((msg, i) => {
|
||||
const prevMsg = i > 0 ? chatMessages[i - 1] : null;
|
||||
const showAvatar = !prevMsg || prevMsg.senderId !== msg.senderId;
|
||||
|
||||
@@ -431,7 +431,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
<div className="px-5 py-3 border-t border-border mt-2">
|
||||
<h4 className="text-xs text-zinc-500 uppercase tracking-wide mb-3">Хранилище и данные</h4>
|
||||
<button
|
||||
onClick={() => { setShowImportModal(true); }}
|
||||
onClick={() => { loadFriends(); setShowImportModal(true); }}
|
||||
className="w-full flex items-center gap-4 px-3 py-3 rounded-xl bg-surface-tertiary/50 hover:bg-surface-hover transition-colors"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-lg bg-blue-500/20 flex items-center justify-center">
|
||||
|
||||
@@ -189,13 +189,11 @@ export default function TelegramImportModal({ isOpen, onClose, friends }: Telegr
|
||||
>
|
||||
<option value="">-- Выберите пользователя --</option>
|
||||
<option value={user?.id}>Это я ({user?.displayName || user?.username})</option>
|
||||
<optgroup label="Мои контакты">
|
||||
{friends.map(f => (
|
||||
<option key={f.id} value={f.id}>
|
||||
Контакт: {f.displayName || f.username}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
{friends.map(f => (
|
||||
<option key={f.id} value={f.id}>
|
||||
Контакт: {f.displayName || f.username}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -15,13 +15,14 @@ interface ChatState {
|
||||
isLoadingMessages: boolean;
|
||||
searchQuery: string;
|
||||
drafts: Record<string, string>;
|
||||
hasMoreMessages: Record<string, boolean>;
|
||||
|
||||
setActiveChat: (chatId: string | null) => void;
|
||||
setSearchQuery: (query: string) => void;
|
||||
setDraft: (chatId: string, text: string) => void;
|
||||
getDraft: (chatId: string) => string;
|
||||
loadChats: () => Promise<void>;
|
||||
loadMessages: (chatId: string) => Promise<void>;
|
||||
loadMessages: (chatId: string, reset?: boolean) => Promise<void>;
|
||||
addMessage: (message: Message) => void;
|
||||
updateMessage: (message: Message) => void;
|
||||
removeMessage: (messageId: string, chatId: string) => void;
|
||||
@@ -56,6 +57,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
isLoadingMessages: false,
|
||||
searchQuery: '',
|
||||
drafts: JSON.parse(localStorage.getItem('knot_drafts') || '{}'),
|
||||
hasMoreMessages: {},
|
||||
|
||||
setActiveChat: (chatId) => set((state) => ({
|
||||
activeChat: chatId,
|
||||
@@ -111,13 +113,21 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
loadMessages: async (chatId) => {
|
||||
loadMessages: async (chatId, reset = false) => {
|
||||
try {
|
||||
const state = get();
|
||||
if (!reset && state.messages[chatId] && state.hasMoreMessages[chatId] === false) return;
|
||||
if (state.isLoadingMessages) return;
|
||||
|
||||
set({ isLoadingMessages: true });
|
||||
const fetched = await api.getMessages(chatId);
|
||||
const currentMessages = state.messages[chatId] || [];
|
||||
const cursor = !reset && currentMessages.length > 0 ? currentMessages[0].createdAt : undefined;
|
||||
|
||||
const fetched = await api.getMessages(chatId, cursor);
|
||||
|
||||
set((state) => {
|
||||
// Merge fetched messages with any that arrived via socket during the fetch
|
||||
const existing = state.messages[chatId] || [];
|
||||
// Merge fetched messages with any that arrived via socket
|
||||
const existing = reset ? [] : (state.messages[chatId] || []);
|
||||
const fetchedIds = new Set(fetched.map(m => m.id));
|
||||
const socketOnly = existing.filter(m => !fetchedIds.has(m.id));
|
||||
const merged = [...fetched, ...socketOnly].sort(
|
||||
@@ -125,6 +135,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
);
|
||||
return {
|
||||
messages: { ...state.messages, [chatId]: merged },
|
||||
hasMoreMessages: { ...state.hasMoreMessages, [chatId]: fetched.length === 100 },
|
||||
isLoadingMessages: false,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user