Skip to content

Commit 73fe585

Browse files
committed
wip
1 parent d81a678 commit 73fe585

25 files changed

+456
-279
lines changed

webapp/src/App.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,23 +224,27 @@
224224
pointer-events: none;
225225
transition: all 0.3s ease;
226226
}
227+
227228
/* Enhanced Loading States */
228229
.loading-container {
229230
display: flex;
230231
align-items: center;
231232
justify-content: center;
232233
padding: 2rem;
233234
}
235+
234236
.spinner-border.large {
235237
width: 3rem;
236238
height: 3rem;
237239
border-width: 0.3em;
238240
}
241+
239242
.spinner-border.small {
240243
width: 1rem;
241244
height: 1rem;
242245
border-width: 0.2em;
243246
}
247+
244248
/* Loading state with text */
245249
.loading-container .loading-text {
246250
margin-left: 1rem;
@@ -275,6 +279,7 @@
275279
var(--color-gradient-end)
276280
);
277281
}
282+
278283
/* Add loading and error container styles */
279284
.loading-container,
280285
.error-container {
@@ -288,6 +293,7 @@
288293
background: var(--background-color, #fff);
289294
color: var(--text-color, #333);
290295
}
296+
291297
.error-container {
292298
color: var(--error-color, #dc3545);
293299
}

webapp/src/App.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
2-
import {Provider, useSelector} from 'react-redux';
3-
import {store, RootState} from './store';
2+
import {Provider, useDispatch, useSelector} from 'react-redux';
3+
import {RootState, store} from './store';
4+
import {isArchive} from './services/appConfig';
45
import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary';
56
import ErrorFallback from './components/ErrorBoundary/ErrorFallback';
67
import './App.css';
@@ -43,6 +44,20 @@ import 'prismjs/plugins/show-language/prism-show-language';
4344
import 'prismjs/plugins/normalize-whitespace/prism-normalize-whitespace';
4445
// import 'prismjs/plugins/autoloader/prism-autoloader';
4546
import QRCode from 'qrcode-generator';
47+
import {addMessage} from "./store/slices/messageSlice";
48+
import {Message} from './types/messages';
49+
// Add function to extract archived messages
50+
const getArchivedMessages = () => {
51+
if (!isArchive) return null;
52+
try {
53+
const messagesEl = document.getElementById('archived-messages');
54+
if (!messagesEl) return null;
55+
return JSON.parse(messagesEl.textContent || '[]');
56+
} catch (err) {
57+
console.error('Failed to parse archived messages:', err);
58+
return null;
59+
}
60+
};
4661

4762
const APP_VERSION = '1.0.0';
4863
const LOG_PREFIX = '[App]';
@@ -51,13 +66,30 @@ Prism.manual = true;
5166

5267
// Create a separate component for the app content
5368
const AppContent: React.FC = () => {
54-
console.group(`${LOG_PREFIX} Initializing v${APP_VERSION}`);
69+
if (!isArchive) {
70+
console.group(`${LOG_PREFIX} Initializing v${APP_VERSION}`);
71+
}
5572
console.log('Starting component render');
5673
const appConfig = useSelector((state: RootState) => state.config);
74+
const dispatch = useDispatch();
75+
// Load archived messages on mount if in archive mode
76+
React.useEffect(() => {
77+
if (isArchive) {
78+
const archivedMessages = getArchivedMessages();
79+
if (archivedMessages) {
80+
archivedMessages.forEach((msg: Message) => dispatch(addMessage(msg)));
81+
}
82+
}
83+
}, [dispatch]);
5784

5885
const sessionId = websocket.getSessionId();
5986
const isConnected = websocket.isConnected();
6087
React.useEffect(() => {
88+
// Skip websocket setup if loading from archive
89+
if (appConfig.isArchive) {
90+
return;
91+
}
92+
6193
if (appConfig.applicationName) {
6294
document.title = appConfig.applicationName;
6395
console.log(`${LOG_PREFIX} Updated page title to:`, appConfig.applicationName);
@@ -108,7 +140,7 @@ const App: React.FC = () => {
108140
return (
109141
<ErrorBoundary FallbackComponent={ErrorFallback}>
110142
<Provider store={store}>
111-
<AppContent />
143+
<AppContent/>
112144
</Provider>
113145
</ErrorBoundary>
114146
);

webapp/src/components/ChatInterface.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {useEffect, useState} from 'react';
22
import {useDispatch} from 'react-redux';
33
import styled from 'styled-components';
4-
import {fetchAppConfig} from '../services/appConfig';
4+
import {fetchAppConfig, isArchive} from '../services/appConfig';
55
import {useWebSocket} from '../hooks/useWebSocket';
66
import {addMessage} from '../store/slices/messageSlice';
77
import MessageList from './MessageList';
@@ -42,17 +42,20 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
4242
}
4343
};
4444
const [messages, setMessages] = React.useState<Message[]>([]);
45+
const [sessionId] = useState(() => propSessionId || window.location.hash.slice(1) || 'new');
46+
const dispatch = useDispatch();
47+
const ws = useWebSocket(sessionId);
4548
console.log(`${LOG_PREFIX} Rendering with props:`, {
4649
propSessionId,
4750
isConnected,
4851
hashedSessionId: window.location.hash
4952
});
5053

51-
const [sessionId] = useState(() => propSessionId || window.location.hash.slice(1) || 'new');
52-
const dispatch = useDispatch();
53-
const ws = useWebSocket(sessionId);
5454

5555
useEffect(() => {
56+
// Skip effect in archive mode
57+
if (isArchive) return;
58+
5659
let mounted = true;
5760
const loadAppConfig = async () => {
5861
if (!sessionId) return;
@@ -75,6 +78,9 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
7578
}, [sessionId]); // Only depend on sessionId
7679

7780
useEffect(() => {
81+
// Skip effect in archive mode
82+
if (isArchive) return;
83+
7884
debugLog('Setting up message handler', {
7985
sessionId,
8086
isConnected,
@@ -162,7 +168,11 @@ const ChatInterface: React.FC<ChatInterfaceProps> = ({
162168
ws.send(msg);
163169
};
164170

165-
return (
171+
return isArchive ? (
172+
<ChatContainer>
173+
<MessageList/>
174+
</ChatContainer>
175+
) : (
166176
<ChatContainer>
167177
<MessageList/>
168178
<InputArea onSendMessage={handleSendMessage}/>

webapp/src/components/InputArea.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {memo, useCallback, useState} from 'react';
2-
import styled, { css } from 'styled-components';
2+
import styled from 'styled-components';
33
import {useSelector} from 'react-redux';
44
import {RootState} from '../store';
55

@@ -18,6 +18,7 @@ const log = (message: string, data?: unknown) => {
1818
interface InputContainerProps {
1919
$hide?: boolean;
2020
}
21+
2122
const InputContainer = styled.div<InputContainerProps>`
2223
padding: 1.5rem;
2324
background-color: ${(props) => props.theme.colors.surface};

webapp/src/components/Menu/Menu.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import React from 'react';
22
import styled from 'styled-components';
33
import {useDispatch, useSelector} from 'react-redux';
44
import {useModal} from '../../hooks/useModal';
5-
interface MenuContainerProps {
6-
$hidden?: boolean;
7-
}
85
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
96
import {faCog, faHome, faSignInAlt, faSignOutAlt} from '@fortawesome/free-solid-svg-icons';
107
import {ThemeMenu} from "./ThemeMenu";
118
import {WebSocketMenu} from "./WebSocketMenu";
129
import {RootState} from "../../store/index";
1310
import {toggleVerbose} from '../../store/slices/uiSlice';
1411

12+
interface MenuContainerProps {
13+
$hidden?: boolean;
14+
}
15+
1516
const isDevelopment = process.env.NODE_ENV === 'development';
1617

1718
function long64(): string {

webapp/src/components/Menu/ThemeMenu.tsx

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled from 'styled-components';
33
import {useTheme} from '../../hooks/useTheme';
44
import {themes} from '../../themes/themes';
55
import {useDispatch} from 'react-redux';
6-
import {showModal, setModalContent} from '../../store/slices/uiSlice';
6+
import {setModalContent, showModal} from '../../store/slices/uiSlice';
77

88
const LOG_PREFIX = '[ThemeMenu Component]';
99
const logWithPrefix = (message: string, ...args: any[]) => {
@@ -102,9 +102,8 @@ const ThemeList = styled.div`
102102
padding: ${({theme}) => theme.sizing.spacing.xs};
103103
z-index: 10;
104104
min-width: 200px;
105-
box-shadow:
106-
0 4px 16px ${({theme}) => `${theme.colors.primary}20`},
107-
0 0 0 1px ${({theme}) => `${theme.colors.border}40`};
105+
box-shadow: 0 4px 16px ${({theme}) => `${theme.colors.primary}20`},
106+
0 0 0 1px ${({theme}) => `${theme.colors.border}40`};
108107
backdrop-filter: blur(8px);
109108
transform-origin: top;
110109
animation: slideIn 0.2s ease-out;
@@ -115,6 +114,7 @@ const ThemeList = styled.div`
115114
${theme.colors.surface}e8
116115
)`};
117116
/* Add glass effect */
117+
118118
&::before {
119119
content: '';
120120
position: absolute;
@@ -125,6 +125,7 @@ const ThemeList = styled.div`
125125
backdrop-filter: blur(8px);
126126
z-index: -1;
127127
}
128+
128129
@keyframes slideIn {
129130
from {
130131
opacity: 0;
@@ -183,13 +184,13 @@ export const ThemeMenu: React.FC = () => {
183184
return () => {
184185
document.removeEventListener('keydown', handleEscapeKey);
185186
};
186-
}, [isOpen]);
187+
}, [isOpen]);
187188
// Add keyboard shortcut handler
188-
React.useEffect(() => {
189-
const handleKeyboardShortcut = (event: KeyboardEvent) => {
190-
if (event.altKey && event.key.toLowerCase() === 't') {
191-
event.preventDefault();
192-
const themeContent = `
189+
React.useEffect(() => {
190+
const handleKeyboardShortcut = (event: KeyboardEvent) => {
191+
if (event.altKey && event.key.toLowerCase() === 't') {
192+
event.preventDefault();
193+
const themeContent = `
193194
<div>
194195
${Object.keys(themes).map(themeName => `
195196
<button
@@ -201,25 +202,25 @@ React.useEffect(() => {
201202
`).join('')}
202203
</div>
203204
`;
204-
dispatch(showModal('Theme Selection'));
205-
dispatch(setModalContent(themeContent));
206-
logDebug('Theme modal opened via keyboard shortcut (Alt+T)');
207-
}
208-
};
209-
document.addEventListener('keydown', handleKeyboardShortcut);
210-
return () => {
211-
document.removeEventListener('keydown', handleKeyboardShortcut);
212-
};
213-
}, [currentTheme, dispatch]);
214-
React.useEffect(() => {
215-
const handleThemeChangeEvent = (event: CustomEvent<string>) => {
216-
handleThemeChange(event.detail as keyof typeof themes);
217-
};
218-
window.addEventListener('themeChange', handleThemeChangeEvent as EventListener);
219-
return () => {
220-
window.removeEventListener('themeChange', handleThemeChangeEvent as EventListener);
221-
};
222-
}, []);
205+
dispatch(showModal('Theme Selection'));
206+
dispatch(setModalContent(themeContent));
207+
logDebug('Theme modal opened via keyboard shortcut (Alt+T)');
208+
}
209+
};
210+
document.addEventListener('keydown', handleKeyboardShortcut);
211+
return () => {
212+
document.removeEventListener('keydown', handleKeyboardShortcut);
213+
};
214+
}, [currentTheme, dispatch]);
215+
React.useEffect(() => {
216+
const handleThemeChangeEvent = (event: CustomEvent<string>) => {
217+
handleThemeChange(event.detail as keyof typeof themes);
218+
};
219+
window.addEventListener('themeChange', handleThemeChangeEvent as EventListener);
220+
return () => {
221+
window.removeEventListener('themeChange', handleThemeChangeEvent as EventListener);
222+
};
223+
}, []);
223224
React.useEffect(() => {
224225
const handleClickOutside = (event: MouseEvent) => {
225226
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {

0 commit comments

Comments
 (0)