Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
acharneski committed Nov 30, 2024
1 parent 0696461 commit 06f7aab
Show file tree
Hide file tree
Showing 12 changed files with 1,317 additions and 26 deletions.
4 changes: 4 additions & 0 deletions webapp/chat-app/src/components/InputArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const InputArea = memo(function InputArea({onSendMessage}: InputAreaProps) {
const [message, setMessage] = useState('');
const config = useSelector((state: RootState) => state.config);
const [isSubmitting, setIsSubmitting] = useState(false);
const textAreaRef = React.useRef<HTMLTextAreaElement>(null);

const handleSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -115,6 +116,8 @@ const InputArea = memo(function InputArea({onSendMessage}: InputAreaProps) {

React.useEffect(() => {
log('Component mounted', {configState: config});
// Focus the textarea when component mounts
textAreaRef.current?.focus();
return () => {
log('Component unmounting');
};
Expand All @@ -125,6 +128,7 @@ const InputArea = memo(function InputArea({onSendMessage}: InputAreaProps) {
<InputContainer>
<StyledForm onSubmit={handleSubmit}>
<TextArea
ref={textAreaRef}
value={message}
onChange={handleMessageChange}
onKeyPress={handleKeyPress}
Expand Down
52 changes: 48 additions & 4 deletions webapp/chat-app/src/store/slices/uiSlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
import {createSlice, PayloadAction} from '@reduxjs/toolkit';
import {ThemeName} from '../../themes/themes';
// Helper function to safely interact with localStorage
const safeStorage = {
setItem(key: string, value: string) {
try {
localStorage.setItem(key, value);
return true;
} catch (error: unknown) {
console.warn('[UI Slice] Failed to save to localStorage:', {
key,
error,
storageUsed: this.getUsedSpace()
});
// Try to clear old items if storage is full
if (error instanceof Error && error.name === 'QuotaExceededError') {
this.clearOldItems();
try {
localStorage.setItem(key, value);
return true;
} catch (retryError: unknown) {
console.error('[UI Slice] Still failed after clearing storage:', retryError);
}
}
return false;
}
},
getUsedSpace() {
let total = 0;
for (const key in localStorage) {
if (Object.prototype.hasOwnProperty.call(localStorage, key)) {
total += localStorage[key].length + key.length;
}
}
return (total * 2) / 1024 / 1024; // Approximate MB used
},
clearOldItems() {
const themeKey = 'theme';
// Keep theme but clear other items
const currentTheme = localStorage.getItem(themeKey);
localStorage.clear();
if (currentTheme) {
localStorage.setItem(themeKey, currentTheme);
}
}
};

interface UiState {
theme: ThemeName;
Expand All @@ -14,7 +58,7 @@ const initialState: UiState = {
theme: 'main',
modalOpen: false,
modalType: null,
verboseMode: localStorage.getItem('verboseMode') === 'true',
verboseMode: safeStorage.setItem('verboseMode', 'false') ? false : false,
activeTab: 'chat', // Set default tab
lastUpdate: Date.now()
};
Expand All @@ -38,7 +82,7 @@ const uiSlice = createSlice({
setTheme: (state, action: PayloadAction<ThemeName>) => {
logStateChange('Setting theme', action.payload, {theme: state.theme});
state.theme = action.payload;
localStorage.setItem('theme', action.payload);
safeStorage.setItem('theme', action.payload);
},
setDarkMode: (state, action: PayloadAction<boolean>) => {
const newTheme = action.payload ? 'night' : 'main';
Expand All @@ -47,7 +91,7 @@ const uiSlice = createSlice({
newTheme
}, {currentTheme: state.theme});
state.theme = newTheme;
localStorage.setItem('theme', newTheme);
safeStorage.setItem('theme', newTheme);
},
showModal: (state, action: PayloadAction<string>) => {
logStateChange('Showing modal', {
Expand All @@ -74,7 +118,7 @@ const uiSlice = createSlice({
}, {
previousState: state.verboseMode
});
localStorage.setItem('verboseMode', newVerboseState.toString());
safeStorage.setItem('verboseMode', newVerboseState.toString());
state.verboseMode = !state.verboseMode;
}
},
Expand Down
10 changes: 10 additions & 0 deletions webapp/chat-app/src/themes/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import {useSelector} from 'react-redux';
import {RootState} from '../store';
import {logThemeChange, themes, ThemeName} from './themes';
import Prism from 'prismjs';
import {safeStorage} from '../utils/storage';

interface ThemeProviderProps {
children: React.ReactNode;
}

const LOG_PREFIX = '[ThemeProvider]';
// Fallback theme in case of storage errors
const FALLBACK_THEME: ThemeName = 'main';

// Define Prism themes mapping to our theme names
const prismThemes: Record<ThemeName, string> = {
main: 'prism',
Expand All @@ -35,6 +39,12 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({children}) => {
const previousTheme = useRef(currentTheme);

useEffect(() => {
// Validate theme before applying
if (!themes[currentTheme]) {
console.warn(`${LOG_PREFIX} Invalid theme "${currentTheme}", falling back to ${FALLBACK_THEME}`);
return;
}

// Create a style element for dynamic theme transitions
const styleEl = document.createElement('style');
document.head.appendChild(styleEl);
Expand Down
75 changes: 75 additions & 0 deletions webapp/chat-app/src/utils/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Utility for safely interacting with localStorage
export const safeStorage = {
setItem(key: string, value: string) {
try {
localStorage.setItem(key, value);
return true;
} catch (error: unknown) {
console.warn('[Storage] Failed to save to localStorage:', {
key,
error,
storageUsed: this.getUsedSpace()
});
// Try to clear old items if storage is full
if (error instanceof Error && error.name === 'QuotaExceededError') {
this.clearOldItems();
try {
localStorage.setItem(key, value);
return true;
} catch (retryError: unknown) {
console.error('[Storage] Still failed after clearing storage:', retryError);
}
}
return false;
}
},

getItem(key: string, defaultValue: string = '') {
try {
const value = localStorage.getItem(key);
return value !== null ? value : defaultValue;
} catch (error: unknown) {
console.warn('[Storage] Failed to read from localStorage:', {
key,
error
});
return defaultValue;
}
},

getUsedSpace() {
try {
let total = 0;
for (let key in localStorage) {
if (Object.prototype.hasOwnProperty.call(localStorage, key)) {
total += localStorage[key].length + key.length;
}
}
return (total * 2) / 1024 / 1024; // Approximate MB used
} catch (error: unknown) {
console.error('[Storage] Failed to calculate storage usage:', error);
return 0;
}
},

clearOldItems() {
try {
const themeKey = 'theme';
const verboseModeKey = 'verboseMode';
// Keep important settings but clear other items
const currentTheme = this.getItem(themeKey);
const verboseMode = this.getItem(verboseModeKey);

localStorage.clear();

if (currentTheme) {
this.setItem(themeKey, currentTheme);
}
if (verboseMode) {
this.setItem(verboseModeKey, verboseMode);
}
} catch (error) {
console.error('[Storage] Failed to clear storage:', error);
}
}
};
6 changes: 3 additions & 3 deletions webui/src/main/resources/application/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files": {
"main.css": "/static/css/main.351373b1.css",
"main.js": "/static/js/main.a499650c.js",
"main.js": "/static/js/main.853522c4.js",
"static/css/684.baca662c.chunk.css": "/static/css/684.baca662c.chunk.css",
"static/js/684.4c3de71b.chunk.js": "/static/js/684.4c3de71b.chunk.js",
"static/css/662.745fbc88.chunk.css": "/static/css/662.745fbc88.chunk.css",
Expand Down Expand Up @@ -66,7 +66,7 @@
"static/js/194.4acfb22a.chunk.js": "/static/js/194.4acfb22a.chunk.js",
"index.html": "/index.html",
"main.351373b1.css.map": "/static/css/main.351373b1.css.map",
"main.a499650c.js.map": "/static/js/main.a499650c.js.map",
"main.853522c4.js.map": "/static/js/main.853522c4.js.map",
"684.baca662c.chunk.css.map": "/static/css/684.baca662c.chunk.css.map",
"684.4c3de71b.chunk.js.map": "/static/js/684.4c3de71b.chunk.js.map",
"662.745fbc88.chunk.css.map": "/static/css/662.745fbc88.chunk.css.map",
Expand Down Expand Up @@ -132,6 +132,6 @@
},
"entrypoints": [
"static/css/main.351373b1.css",
"static/js/main.a499650c.js"
"static/js/main.853522c4.js"
]
}
20 changes: 1 addition & 19 deletions webui/src/main/resources/application/index.html
Original file line number Diff line number Diff line change
@@ -1,19 +1 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<link rel="icon" href="favicon.ico"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="theme-color" content="#000000"/>
<meta name="description" content="Web site created using create-react-app"/>
<link rel="apple-touch-icon" href="logo192.png"/>
<link rel="manifest" href="manifest.json"/>
<title>React App</title>
<script defer="defer" src="static/js/main.a499650c.js"></script>
<link href="static/css/main.351373b1.css" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.853522c4.js"></script><link href="/static/css/main.351373b1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
475 changes: 475 additions & 0 deletions webui/src/main/resources/application/static/js/main.49738660.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*!
* Wait for document loaded before starting the execution
*/

/*!
* Font Awesome Free 6.7.1 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2024 Fonticons, Inc.
*/

/*! @license DOMPurify 3.1.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.6/LICENSE */

/*! Check if previously processed */

/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */

/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* use-sync-external-store-shim.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* use-sync-external-store-shim/with-selector.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* Prism: Lightweight, robust, elegant syntax highlighting
*
* @license MIT <https://opensource.org/licenses/MIT>
* @author Lea Verou <https://lea.verou.me>
* @namespace
* @public
*/

/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/** @license React v17.0.2
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

Large diffs are not rendered by default.

475 changes: 475 additions & 0 deletions webui/src/main/resources/application/static/js/main.853522c4.js

Large diffs are not rendered by default.

Loading

0 comments on commit 06f7aab

Please sign in to comment.