Skip to content

Commit 2e55e5b

Browse files
committed
update web ui. use scroll-u for message list container
1 parent 9fa9f5a commit 2e55e5b

104 files changed

Lines changed: 190120 additions & 741 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
-32 KB
Binary file not shown.
-3.94 MB
Binary file not shown.

crates/cherry/src/App.tsx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// src/App.tsx
21
import React, { useState, useEffect } from 'react';
32
import styled from 'styled-components';
43
import { listen } from '@tauri-apps/api/event';
@@ -8,7 +7,6 @@ import WindowControls from './components/WindowControls';
87
import SettingsPage from './components/settings/SettingsPage';
98
import ContactPage from './components/ContactPage';
109
import NotificationManager from './components/NotificationManager';
11-
// import MessageTest from './components/MessageTest';
1210
import LoginForm from './pages/login';
1311
import { User } from './types/types';
1412
import { useWindowSize } from './hooks/useWindowsSize.ts';
@@ -20,10 +18,6 @@ import { useMessageReceiver } from './hooks/useMessageReceiver';
2018
import { ErrorMessage } from './components/UI';
2119
import { useAuthStore } from './store/auth';
2220

23-
// import DebugPanel from './components/DebugPanel';
24-
// import MessageDebug from './components/MessageDebug';
25-
//import ScrollTest from './components/ScrollTest';
26-
2721
// ==================== Styled Components ====================
2822
const AppContainer = styled.div`
2923
display: flex;
@@ -635,18 +629,6 @@ const App: React.FC = () => {
635629

636630
{/* Notification Manager */}
637631
<NotificationManager />
638-
639-
{/* Debug Panel */}
640-
{/* <DebugPanel
641-
isVisible={isDebugVisible}
642-
onToggle={() => setIsDebugVisible(!isDebugVisible)}
643-
/> */}
644-
645-
{/* Message Debug */}
646-
{/* <MessageDebug selectedConversation={selectedConversation} /> */}
647-
648-
{/* Scroll Test */}
649-
{/* <ScrollTest /> */}
650632
</AppContainer>
651633
);
652634
};

crates/cherry/src/components/ConversationContainer.tsx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ const ConversationContainer: React.FC<ConversationContainerProps> = ({
2323
selectedConversationId,
2424
currentUserId,
2525
}) => {
26-
//console.log('ConversationContainer render:', {
27-
// conversationCount: conversations.length,
28-
// selectedConversationId,
29-
// conversationIds: conversations.map(c => c.id)
30-
//});
31-
32-
33-
3426
return (
3527
<Container>
3628
{conversations.map((conversation) => (

crates/cherry/src/components/MessageListSimple.tsx

Lines changed: 45 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef, useState, useCallback } from 'react';
1+
import React, { useEffect, useRef, useState, useCallback, ReactNode } from 'react';
22
import styled from 'styled-components';
33
import { Message, parseMessageContent } from '../types/types';
44
import { appCacheDir } from '@tauri-apps/api/path';
@@ -7,9 +7,7 @@ import { path } from '@tauri-apps/api';
77
import { invoke } from '@tauri-apps/api/core';
88
import QuickEmojiReply from './UI/QuickEmojiReply';
99
import { addReaction, loadMessages, removeReaction } from '../api/api';
10-
import { BidirectionalInfiniteScroll, LoadItemsParams } from './bidirectional-infinite-scroll';
11-
import { DataItem, useBidirectionalData } from '../hooks/use-bidirectional-data';
12-
import { message } from '@tauri-apps/plugin-dialog';
10+
import { ScrollU } from 'scroll-u'
1311

1412
const appCacheDirPath = await appCacheDir();
1513

@@ -232,14 +230,17 @@ const ReactionIcon = styled.button<{ active?: boolean }>`
232230
}
233231
`;
234232

235-
// ==================== Component Implementation ====================
236-
const MessageItem = React.memo<{
233+
interface MessageNodeProps {
237234
message: Message;
238235
currentUserId: string;
239236
onReply: (message: Message) => void;
240237
onReactionClick: (message: Message, emoji: string) => void;
241-
onScrollToMessage: (messageId: number) => void;
242-
}>(({ message, currentUserId, onReply, onReactionClick, onScrollToMessage }) => {
238+
onScrollToMessage: (data_message_id: string) => void;
239+
}
240+
241+
242+
// ==================== Component Implementation ====================
243+
const MessageItem = React.memo<MessageNodeProps>(({ message, currentUserId, onReply, onReactionClick, onScrollToMessage }) => {
243244
const isOwn = message.user_id === currentUserId;
244245
const parsedContent = parseMessageContent(message.content, message.type_);
245246
const formatTime = (timestamp: string) => {
@@ -306,15 +307,14 @@ const MessageItem = React.memo<{
306307

307308
<MessageHeader>
308309
<Username>{message.id}</Username>
309-
{/* <Username>{message.user_id}</Username> */}
310310
<Timestamp>{formatTime(message.timestamp)}</Timestamp>
311311
</MessageHeader>
312312

313313
{/* 新的回复引用显示 */}
314314
{message.isReply && message.replyToMessage && (
315315
<ReplyQuote
316316
$isOwn={isOwn}
317-
onClick={() => onScrollToMessage(message.replyToMessage!.id)}
317+
onClick={() => onScrollToMessage(message.conversation_id + message.replyToMessage!.id)}
318318
>
319319
<ReplyQuoteHeader>
320320
<ReplyQuoteIcon>
@@ -391,71 +391,13 @@ const MessageItem = React.memo<{
391391
);
392392
});
393393

394-
type MessageExt = DataItem<Message>;
395394

396395
const MessageList: React.FC<MessageListProps> = ({ currentUserId, conversationId, setReplyingTo }) => {
397-
398-
const loadItems = useCallback(async (params: LoadItemsParams) => {
399-
const {load_size = 100 } = params
400-
let messageId: number = 0;
401-
let direction: 'forward' | 'backward' = 'backward';
402-
if (params.forward) {
403-
messageId = params.forward.key as number;
404-
direction = 'forward';
405-
} else if (params.backward) {
406-
messageId = params.backward.key as number;
407-
direction = 'backward';
408-
}
409-
410-
//await new Promise((resolve) => setTimeout(resolve, 1000));
411-
412-
const messages = await loadMessages(conversationId, messageId, direction, load_size);
413-
if (messages.length === 0) {
414-
console.info('no more message:', conversationId, messageId, direction);
415-
return {
416-
data: [],
417-
hasNextPage: false,
418-
};
419-
}
420-
// 处理消息数据
421-
const data: MessageExt[] = messages.map(msg => ({
422-
...msg,
423-
getKey(item) {
424-
return item.id;
425-
},
426-
}) as MessageExt);
427-
return {
428-
data,
429-
hasNextPage: true,
430-
}
431-
432-
}, [conversationId]);
433-
434-
435-
// 使用通用数据管理 hook
436-
const {
437-
loading,
438-
items,
439-
hasNextPage,
440-
error,
441-
loadMore,
442-
trimItems,
443-
trimForwardItems,
444-
updateItem,
445-
deleteItem,
446-
} = useBidirectionalData({
447-
loadItems,
448-
initialLoadParams: { backward: { key: 1 << 60 } },
449-
trimThreshold: 25,
450-
enableDeduplication: true,
451-
});
452-
453-
454-
const handleReply = (message: Message) => {
396+
const handleReply = useCallback((message: Message) => {
455397
setReplyingTo(message);
456-
};
398+
}, [setReplyingTo])
457399

458-
const handleScrollToMessage = (messageId: number) => {
400+
const handleScrollToMessage = useCallback((messageId: string) => {
459401
// 滚动到被回复的消息
460402
const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
461403
if (messageElement) {
@@ -466,7 +408,7 @@ const MessageList: React.FC<MessageListProps> = ({ currentUserId, conversationId
466408
messageElement.classList.remove('highlight');
467409
}, 2000);
468410
}
469-
};
411+
}, []);
470412

471413
// reaction 处理
472414
const handleReactionClick = (msg: Message, emoji: string) => {
@@ -485,57 +427,41 @@ const MessageList: React.FC<MessageListProps> = ({ currentUserId, conversationId
485427
}
486428
};
487429

488-
const renderItem = useCallback((item: MessageExt) => {
489-
return (
490-
<MessageItem
491-
key={item.conversation_id + item.id}
492-
message={item}
493-
currentUserId={currentUserId}
494-
onReply={handleReply}
495-
onReactionClick={handleReactionClick}
496-
onScrollToMessage={handleScrollToMessage}
497-
/>
498-
);
499-
}, [currentUserId, handleReply, handleReactionClick, handleScrollToMessage]);
500430

431+
function getMessageProps(node: ReactNode): MessageNodeProps | undefined {
432+
if (node && React.isValidElement(node)) {
433+
return node.props as MessageNodeProps;
434+
}
435+
return undefined;
436+
}
437+
438+
439+
const loadMore = useCallback(async (direction: 'pre' | 'next', node: React.ReactNode) => {
440+
const props = getMessageProps(node)!;
441+
const messageId = props.message.id;
442+
const messages = await loadMessages(conversationId, messageId, direction == 'pre' ? 'backward' : 'forward', 10);
443+
if (messages.length === 0) {
444+
console.info('no more message:', conversationId, messageId, direction);
445+
return [];
446+
}
447+
messages.map(item => {
448+
return (
449+
<MessageItem
450+
key={item.conversation_id + item.id}
451+
message={item}
452+
currentUserId={currentUserId}
453+
onReply={handleReply}
454+
onReactionClick={handleReactionClick}
455+
onScrollToMessage={handleScrollToMessage}
456+
/>
457+
);
458+
});
459+
}, [currentUserId, handleReply, handleReactionClick, handleScrollToMessage]);
501460

502-
const renderError = useCallback(() => {
503-
return (
504-
<div style={{ textAlign: 'center', padding: '1rem', color: 'red' }}>
505-
加载失败,请稍后重试。
506-
</div>
507-
);
508-
}, []);
509461

510462
return (
511-
<BidirectionalInfiniteScroll<Message>
512-
items={items}
513-
loading={loading}
514-
hasNextPage={hasNextPage}
515-
error={error}
516-
onLoadMore={(params) => loadMore(params)}
517-
onTrimItems={(direction, count) => {
518-
if (direction === 'backward') {
519-
trimItems(count);
520-
} else {
521-
trimForwardItems(count);
522-
}
523-
}}
524-
renderItem={renderItem}
525-
renderError={renderError}
526-
trimThreshold={100}
527-
scrollThresholdUp={0.15}
528-
scrollThresholdDown={0.75}
529-
bottomThreshold={100}
530-
enableTrimming={true}
531-
enableBottomLoading={true}
532-
enableScrollPositionPreservation={true}
533-
maxHeight="2000px"
534-
maxWidth="2000px"
535-
className="mt-4"
536-
containerClassName="container"
537-
renderDebugInfo={true}
538-
/>
463+
<ScrollU renderItem={loadMore} />
464+
539465
);
540466
};
541467

0 commit comments

Comments
 (0)