fix
This commit is contained in:
@@ -28,7 +28,6 @@ const DEFAULT_FOLDER_NAME = 'Documents';
|
|||||||
const ROW_KEY_SEPARATOR = ':';
|
const ROW_KEY_SEPARATOR = ':';
|
||||||
const DOCUMENT_ROW_PREFIX = 'document';
|
const DOCUMENT_ROW_PREFIX = 'document';
|
||||||
const FOLDER_ROW_PREFIX = 'folder';
|
const FOLDER_ROW_PREFIX = 'folder';
|
||||||
const THEME_STORAGE_KEY = 'papercrate_theme_settings';
|
|
||||||
|
|
||||||
const resolveApiPath = (path = '') => path;
|
const resolveApiPath = (path = '') => path;
|
||||||
|
|
||||||
@@ -199,66 +198,6 @@ const AppLayout = () => {
|
|||||||
});
|
});
|
||||||
const lastNonDeskViewRef = useRef(documentsViewMode === 'desk' ? 'list' : documentsViewMode);
|
const lastNonDeskViewRef = useRef(documentsViewMode === 'desk' ? 'list' : documentsViewMode);
|
||||||
|
|
||||||
const loadThemeSettings = () => {
|
|
||||||
const defaults = { neutralHue: 260, mode: 'system' };
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
if (typeof document !== 'undefined') {
|
|
||||||
const current = document.documentElement.style.getPropertyValue('--neutral-hue');
|
|
||||||
const parsed = Number.parseInt(current, 10);
|
|
||||||
return {
|
|
||||||
neutralHue: Number.isNaN(parsed) ? defaults.neutralHue : parsed,
|
|
||||||
mode: defaults.mode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = document.documentElement;
|
|
||||||
const loadFromRoot = () => {
|
|
||||||
const current = root.style.getPropertyValue('--neutral-hue');
|
|
||||||
const parsed = Number.parseInt(current, 10);
|
|
||||||
return Number.isNaN(parsed) ? defaults.neutralHue : parsed;
|
|
||||||
};
|
|
||||||
|
|
||||||
let neutralHueValue = loadFromRoot();
|
|
||||||
let modeValue = defaults.mode;
|
|
||||||
|
|
||||||
const composite = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
||||||
if (composite) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(composite);
|
|
||||||
const storedHue = Number.parseInt(parsed?.neutralHue, 10);
|
|
||||||
if (!Number.isNaN(storedHue)) {
|
|
||||||
neutralHueValue = storedHue;
|
|
||||||
}
|
|
||||||
const storedMode = parsed?.mode;
|
|
||||||
if (storedMode === 'light' || storedMode === 'dark' || storedMode === 'system') {
|
|
||||||
modeValue = storedMode;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[theme] failed to parse stored theme settings', error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const legacyHue = window.localStorage.getItem('papercrate_neutral_hue');
|
|
||||||
if (legacyHue) {
|
|
||||||
const parsedLegacyHue = Number.parseInt(legacyHue, 10);
|
|
||||||
if (!Number.isNaN(parsedLegacyHue)) {
|
|
||||||
neutralHueValue = parsedLegacyHue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const legacyMode = window.localStorage.getItem('papercrate_theme_mode');
|
|
||||||
if (legacyMode === 'light' || legacyMode === 'dark' || legacyMode === 'system') {
|
|
||||||
modeValue = legacyMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { neutralHue: neutralHueValue, mode: modeValue };
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialThemeSettings = loadThemeSettings();
|
|
||||||
const [neutralHue, setNeutralHue] = useState(initialThemeSettings.neutralHue);
|
|
||||||
const [themeMode, setThemeMode] = useState(initialThemeSettings.mode);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (documentsViewMode !== 'desk') {
|
if (documentsViewMode !== 'desk') {
|
||||||
lastNonDeskViewRef.current = documentsViewMode;
|
lastNonDeskViewRef.current = documentsViewMode;
|
||||||
@@ -363,56 +302,6 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
}, [token, appStatus, refreshAccessToken]);
|
}, [token, appStatus, refreshAccessToken]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof document !== 'undefined') {
|
|
||||||
document.documentElement.style.setProperty('--neutral-hue', `${neutralHue}deg`);
|
|
||||||
}
|
|
||||||
}, [neutralHue]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof document !== 'undefined') {
|
|
||||||
const root = document.documentElement;
|
|
||||||
if (themeMode === 'system') {
|
|
||||||
root.removeAttribute('data-theme');
|
|
||||||
} else {
|
|
||||||
root.setAttribute('data-theme', themeMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [themeMode]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const payload = JSON.stringify({ neutralHue, mode: themeMode });
|
|
||||||
window.localStorage.setItem(THEME_STORAGE_KEY, payload);
|
|
||||||
window.localStorage.removeItem('papercrate_neutral_hue');
|
|
||||||
window.localStorage.removeItem('papercrate_theme_mode');
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[theme] failed to persist theme settings', error);
|
|
||||||
}
|
|
||||||
}, [neutralHue, themeMode]);
|
|
||||||
|
|
||||||
const handleNeutralHueChange = useCallback((value) => {
|
|
||||||
if (value === '') {
|
|
||||||
setNeutralHue(180);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const parsed = Number(value);
|
|
||||||
if (Number.isNaN(parsed)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setNeutralHue(parsed);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleThemeModeChange = useCallback((mode) => {
|
|
||||||
if (mode !== 'light' && mode !== 'dark' && mode !== 'system') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setThemeMode(mode);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const clearFilters = useCallback(() => {
|
const clearFilters = useCallback(() => {
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
setActiveTagFilters([]);
|
setActiveTagFilters([]);
|
||||||
@@ -5084,10 +4973,6 @@ const AppLayout = () => {
|
|||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
onCreateFolder: handlePromptCreateFolder,
|
onCreateFolder: handlePromptCreateFolder,
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
onNeutralHueChange: handleNeutralHueChange,
|
|
||||||
neutralHue,
|
|
||||||
themeMode,
|
|
||||||
onThemeModeChange: handleThemeModeChange,
|
|
||||||
tags,
|
tags,
|
||||||
activeTagIds: activeTagFilters,
|
activeTagIds: activeTagFilters,
|
||||||
onToggleTagFilter: toggleTagFilter,
|
onToggleTagFilter: toggleTagFilter,
|
||||||
@@ -5146,10 +5031,6 @@ const AppLayout = () => {
|
|||||||
handleCorrespondentCreate,
|
handleCorrespondentCreate,
|
||||||
handlePromptCreateFolder,
|
handlePromptCreateFolder,
|
||||||
creatingFolder,
|
creatingFolder,
|
||||||
handleNeutralHueChange,
|
|
||||||
neutralHue,
|
|
||||||
themeMode,
|
|
||||||
handleThemeModeChange,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -157,10 +157,6 @@ const Sidebar = ({
|
|||||||
draggedFolderId,
|
draggedFolderId,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
creatingFolder = false,
|
creatingFolder = false,
|
||||||
onNeutralHueChange,
|
|
||||||
neutralHue,
|
|
||||||
themeMode = 'system',
|
|
||||||
onThemeModeChange,
|
|
||||||
tags = [],
|
tags = [],
|
||||||
activeTagIds = [],
|
activeTagIds = [],
|
||||||
onToggleTagFilter,
|
onToggleTagFilter,
|
||||||
@@ -184,7 +180,15 @@ const Sidebar = ({
|
|||||||
onSelectTenant,
|
onSelectTenant,
|
||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
}) => {
|
}) => {
|
||||||
const { setCollapsed } = useSidebarContext();
|
const {
|
||||||
|
setCollapsed,
|
||||||
|
neutralHue,
|
||||||
|
setNeutralHue,
|
||||||
|
resetNeutralHue,
|
||||||
|
themeMode,
|
||||||
|
cycleThemeMode,
|
||||||
|
themeModes,
|
||||||
|
} = useSidebarContext();
|
||||||
const handleCollapse = useCallback(() => {
|
const handleCollapse = useCallback(() => {
|
||||||
setCollapsed(true);
|
setCollapsed(true);
|
||||||
}, [setCollapsed]);
|
}, [setCollapsed]);
|
||||||
@@ -246,15 +250,31 @@ const Sidebar = ({
|
|||||||
}, [creatingFolder, onCreateFolder]);
|
}, [creatingFolder, onCreateFolder]);
|
||||||
|
|
||||||
const handleNeutralHueReset = useCallback(() => {
|
const handleNeutralHueReset = useCallback(() => {
|
||||||
onNeutralHueChange?.('');
|
resetNeutralHue();
|
||||||
}, [onNeutralHueChange]);
|
}, [resetNeutralHue]);
|
||||||
|
|
||||||
const themeModeIndex = THEME_MODES.indexOf(themeMode);
|
const handleNeutralHueChange = useCallback(
|
||||||
|
(value) => {
|
||||||
|
if (value === '') {
|
||||||
|
resetNeutralHue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsed = Number(value);
|
||||||
|
if (Number.isNaN(parsed)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setNeutralHue(parsed);
|
||||||
|
},
|
||||||
|
[resetNeutralHue, setNeutralHue],
|
||||||
|
);
|
||||||
|
|
||||||
|
const themeModeList = themeModes && themeModes.length ? themeModes : THEME_MODES;
|
||||||
|
const themeModeIndex = themeModeList.indexOf(themeMode);
|
||||||
const safeThemeModeIndex = themeModeIndex === -1 ? 0 : themeModeIndex;
|
const safeThemeModeIndex = themeModeIndex === -1 ? 0 : themeModeIndex;
|
||||||
const resolvedThemeMode = THEME_MODES[safeThemeModeIndex];
|
const resolvedThemeMode = themeModeList[safeThemeModeIndex] || THEME_MODES[0];
|
||||||
const nextThemeMode = THEME_MODES[(safeThemeModeIndex + 1) % THEME_MODES.length];
|
const nextThemeMode = themeModeList[(safeThemeModeIndex + 1) % themeModeList.length];
|
||||||
const themeModeLabel = THEME_MODE_LABELS[resolvedThemeMode];
|
const themeModeLabel = THEME_MODE_LABELS[resolvedThemeMode] || THEME_MODE_LABELS.system;
|
||||||
const nextThemeLabel = THEME_MODE_LABELS[nextThemeMode];
|
const nextThemeLabel = THEME_MODE_LABELS[nextThemeMode] || THEME_MODE_LABELS.system;
|
||||||
const themeModeIcon = resolvedThemeMode === 'dark'
|
const themeModeIcon = resolvedThemeMode === 'dark'
|
||||||
? <MoonIcon size={16} />
|
? <MoonIcon size={16} />
|
||||||
: resolvedThemeMode === 'light'
|
: resolvedThemeMode === 'light'
|
||||||
@@ -262,14 +282,8 @@ const Sidebar = ({
|
|||||||
: <DesktopIcon size={16} />;
|
: <DesktopIcon size={16} />;
|
||||||
|
|
||||||
const handleThemeModeToggle = useCallback(() => {
|
const handleThemeModeToggle = useCallback(() => {
|
||||||
if (!onThemeModeChange) {
|
cycleThemeMode();
|
||||||
return;
|
}, [cycleThemeMode]);
|
||||||
}
|
|
||||||
const currentIndex = THEME_MODES.indexOf(themeMode);
|
|
||||||
const safeIndex = currentIndex === -1 ? 0 : currentIndex;
|
|
||||||
const nextMode = THEME_MODES[(safeIndex + 1) % THEME_MODES.length];
|
|
||||||
onThemeModeChange(nextMode);
|
|
||||||
}, [onThemeModeChange, themeMode]);
|
|
||||||
|
|
||||||
const handleSearchInputChange = useCallback(
|
const handleSearchInputChange = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
@@ -415,7 +429,7 @@ const Sidebar = ({
|
|||||||
max="360"
|
max="360"
|
||||||
step="1"
|
step="1"
|
||||||
value={neutralHue}
|
value={neutralHue}
|
||||||
onChange={(event) => onNeutralHueChange?.(event.target.value)}
|
onChange={(event) => handleNeutralHueChange(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<span className="sidebar-slider__value">{neutralHue}°</span>
|
<span className="sidebar-slider__value">{neutralHue}°</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -1,11 +1,161 @@
|
|||||||
import React, { createContext, useContext, useMemo, useState, useCallback } from 'react';
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
const SidebarContext = createContext(null);
|
const SidebarContext = createContext(null);
|
||||||
|
|
||||||
|
const THEME_STORAGE_KEY = 'papercrate_theme_settings';
|
||||||
|
const DEFAULT_NEUTRAL_HUE = 180;
|
||||||
|
const DEFAULT_THEME_MODE = 'system';
|
||||||
|
const THEME_MODES = ['system', 'light', 'dark'];
|
||||||
|
|
||||||
|
const loadInitialThemeSettings = () => {
|
||||||
|
const defaults = { neutralHue: DEFAULT_NEUTRAL_HUE, mode: DEFAULT_THEME_MODE };
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
const current = document.documentElement.style.getPropertyValue('--neutral-hue');
|
||||||
|
const parsed = Number.parseInt(current, 10);
|
||||||
|
return {
|
||||||
|
neutralHue: Number.isNaN(parsed) ? defaults.neutralHue : parsed,
|
||||||
|
mode: defaults.mode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.documentElement;
|
||||||
|
const loadFromRoot = () => {
|
||||||
|
const current = root.style.getPropertyValue('--neutral-hue');
|
||||||
|
const parsed = Number.parseInt(current, 10);
|
||||||
|
return Number.isNaN(parsed) ? defaults.neutralHue : parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
let neutralHueValue = loadFromRoot();
|
||||||
|
let modeValue = defaults.mode;
|
||||||
|
|
||||||
|
const composite = window.localStorage.getItem(THEME_STORAGE_KEY);
|
||||||
|
if (composite) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(composite);
|
||||||
|
const storedHue = Number.parseInt(parsed?.neutralHue, 10);
|
||||||
|
if (!Number.isNaN(storedHue)) {
|
||||||
|
neutralHueValue = storedHue;
|
||||||
|
}
|
||||||
|
const storedMode = parsed?.mode;
|
||||||
|
if (THEME_MODES.includes(storedMode)) {
|
||||||
|
modeValue = storedMode;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[theme] failed to parse stored theme settings', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const legacyHue = window.localStorage.getItem('papercrate_neutral_hue');
|
||||||
|
if (legacyHue) {
|
||||||
|
const parsedLegacyHue = Number.parseInt(legacyHue, 10);
|
||||||
|
if (!Number.isNaN(parsedLegacyHue)) {
|
||||||
|
neutralHueValue = parsedLegacyHue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const legacyMode = window.localStorage.getItem('papercrate_theme_mode');
|
||||||
|
if (THEME_MODES.includes(legacyMode)) {
|
||||||
|
modeValue = legacyMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { neutralHue: neutralHueValue, mode: modeValue };
|
||||||
|
};
|
||||||
|
|
||||||
export const SidebarProvider = ({ initialCollapsed = false, children }) => {
|
export const SidebarProvider = ({ initialCollapsed = false, children }) => {
|
||||||
const [collapsed, setCollapsed] = useState(initialCollapsed);
|
const [collapsed, setCollapsed] = useState(initialCollapsed);
|
||||||
|
const initialTheme = useMemo(() => loadInitialThemeSettings(), []);
|
||||||
|
const [neutralHue, setNeutralHueState] = useState(initialTheme.neutralHue);
|
||||||
|
const [themeMode, setThemeModeState] = useState(initialTheme.mode);
|
||||||
|
|
||||||
const contextValue = useMemo(() => ({ collapsed, setCollapsed }), [collapsed]);
|
useEffect(() => {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.documentElement.style.setProperty('--neutral-hue', `${neutralHue}deg`);
|
||||||
|
}, [neutralHue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const root = document.documentElement;
|
||||||
|
if (themeMode === 'system') {
|
||||||
|
root.removeAttribute('data-theme');
|
||||||
|
} else {
|
||||||
|
root.setAttribute('data-theme', themeMode);
|
||||||
|
}
|
||||||
|
}, [themeMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const payload = JSON.stringify({ neutralHue, mode: themeMode });
|
||||||
|
window.localStorage.setItem(THEME_STORAGE_KEY, payload);
|
||||||
|
window.localStorage.removeItem('papercrate_neutral_hue');
|
||||||
|
window.localStorage.removeItem('papercrate_theme_mode');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('[theme] failed to persist theme settings', error);
|
||||||
|
}
|
||||||
|
}, [neutralHue, themeMode]);
|
||||||
|
|
||||||
|
const setNeutralHue = useCallback((value) => {
|
||||||
|
setNeutralHueState((prev) => {
|
||||||
|
if (value === '' || value === null || typeof value === 'undefined') {
|
||||||
|
return DEFAULT_NEUTRAL_HUE;
|
||||||
|
}
|
||||||
|
const parsed = Number(value);
|
||||||
|
if (Number.isNaN(parsed)) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
const clamped = Math.min(Math.max(Math.round(parsed), 0), 360);
|
||||||
|
return clamped;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resetNeutralHue = useCallback(() => {
|
||||||
|
setNeutralHueState(DEFAULT_NEUTRAL_HUE);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setThemeMode = useCallback((mode) => {
|
||||||
|
if (!THEME_MODES.includes(mode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setThemeModeState(mode);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const cycleThemeMode = useCallback(() => {
|
||||||
|
const index = THEME_MODES.indexOf(themeMode);
|
||||||
|
const nextIndex = index === -1 ? 0 : (index + 1) % THEME_MODES.length;
|
||||||
|
setThemeModeState(THEME_MODES[nextIndex]);
|
||||||
|
}, [themeMode]);
|
||||||
|
|
||||||
|
const contextValue = useMemo(
|
||||||
|
() => ({
|
||||||
|
collapsed,
|
||||||
|
setCollapsed,
|
||||||
|
neutralHue,
|
||||||
|
setNeutralHue,
|
||||||
|
resetNeutralHue,
|
||||||
|
themeMode,
|
||||||
|
setThemeMode,
|
||||||
|
cycleThemeMode,
|
||||||
|
themeModes: THEME_MODES,
|
||||||
|
defaultNeutralHue: DEFAULT_NEUTRAL_HUE,
|
||||||
|
}),
|
||||||
|
[collapsed, neutralHue, setNeutralHue, resetNeutralHue, themeMode, setThemeMode, cycleThemeMode],
|
||||||
|
);
|
||||||
|
|
||||||
return <SidebarContext.Provider value={contextValue}>{children}</SidebarContext.Provider>;
|
return <SidebarContext.Provider value={contextValue}>{children}</SidebarContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user