mirror of https://github.com/buster-so/buster.git
update mountained status
This commit is contained in:
parent
f7a7ab7e7a
commit
f41f16c108
|
@ -19,56 +19,200 @@ import { sizeToPixels, easeInOutCubic, createAutoSaveId } from './helpers';
|
||||||
import { useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn } from '@/hooks';
|
||||||
import { useMount } from '@/hooks/useMount';
|
import { useMount } from '@/hooks/useMount';
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// INTERFACES AND TYPES
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the AppSplitter component
|
||||||
|
*/
|
||||||
interface IAppSplitterProps {
|
interface IAppSplitterProps {
|
||||||
|
/** Content to display in the left panel */
|
||||||
leftChildren: React.ReactNode;
|
leftChildren: React.ReactNode;
|
||||||
|
|
||||||
|
/** Content to display in the right panel */
|
||||||
rightChildren: React.ReactNode;
|
rightChildren: React.ReactNode;
|
||||||
|
|
||||||
|
/** Unique identifier for auto-saving layout to localStorage */
|
||||||
autoSaveId: string;
|
autoSaveId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default layout configuration as [left, right] sizes
|
||||||
|
* Can be numbers (pixels), percentages (strings like "50%"), or "auto"
|
||||||
|
*/
|
||||||
defaultLayout: (string | number)[];
|
defaultLayout: (string | number)[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum size for the left panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
leftPanelMinSize?: number | string;
|
leftPanelMinSize?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum size for the right panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
rightPanelMinSize?: number | string;
|
rightPanelMinSize?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size for the left panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* If not specified, defaults to container size
|
||||||
|
*/
|
||||||
leftPanelMaxSize?: number | string;
|
leftPanelMaxSize?: number | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum size for the right panel
|
||||||
|
* Can be a number (pixels) or string (percentage)
|
||||||
|
* If not specified, defaults to container size
|
||||||
|
*/
|
||||||
rightPanelMaxSize?: number | string;
|
rightPanelMaxSize?: number | string;
|
||||||
|
|
||||||
|
/** Additional CSS classes for the container */
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the splitter can be resized by dragging
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
allowResize?: boolean;
|
allowResize?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split direction
|
||||||
|
* @default 'vertical'
|
||||||
|
*/
|
||||||
split?: 'vertical' | 'horizontal';
|
split?: 'vertical' | 'horizontal';
|
||||||
|
|
||||||
|
/** Additional CSS classes for the splitter element */
|
||||||
splitterClassName?: string;
|
splitterClassName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which side to preserve when resizing
|
||||||
|
* 'left' - left panel maintains its size, right panel adjusts
|
||||||
|
* 'right' - right panel maintains its size, left panel adjusts
|
||||||
|
*/
|
||||||
preserveSide: 'left' | 'right';
|
preserveSide: 'left' | 'right';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to hide the right panel completely
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
rightHidden?: boolean;
|
rightHidden?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to hide the left panel completely
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
leftHidden?: boolean;
|
leftHidden?: boolean;
|
||||||
|
|
||||||
|
/** Inline styles for the container */
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to hide the splitter handle
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
hideSplitter?: boolean;
|
hideSplitter?: boolean;
|
||||||
|
|
||||||
|
/** Additional CSS classes for the left panel */
|
||||||
leftPanelClassName?: string;
|
leftPanelClassName?: string;
|
||||||
|
|
||||||
|
/** Additional CSS classes for the right panel */
|
||||||
rightPanelClassName?: string;
|
rightPanelClassName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to clear saved layout from localStorage on initialization
|
||||||
|
* Can be a boolean or a function that returns a boolean based on preserved side value and container width
|
||||||
|
*/
|
||||||
bustStorageOnInit?: boolean | ((preservedSideValue: number | null, refWidth: number) => boolean);
|
bustStorageOnInit?: boolean | ((preservedSideValue: number | null, refWidth: number) => boolean);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to render the left panel content
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
renderLeftPanel?: boolean;
|
renderLeftPanel?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to render the right panel content
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
renderRightPanel?: boolean;
|
renderRightPanel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref interface for controlling the AppSplitter imperatively
|
||||||
|
*/
|
||||||
export interface AppSplitterRef {
|
export interface AppSplitterRef {
|
||||||
|
/**
|
||||||
|
* Animate a panel to a specific width
|
||||||
|
* @param width - Target width (pixels or percentage)
|
||||||
|
* @param side - Which side to animate
|
||||||
|
* @param duration - Animation duration in milliseconds
|
||||||
|
*/
|
||||||
animateWidth: (
|
animateWidth: (
|
||||||
width: string | number,
|
width: string | number,
|
||||||
side: 'left' | 'right',
|
side: 'left' | 'right',
|
||||||
duration?: number
|
duration?: number
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the split sizes programmatically
|
||||||
|
* @param sizes - [left, right] sizes as pixels or percentages
|
||||||
|
*/
|
||||||
setSplitSizes: (sizes: [string | number, string | number]) => void;
|
setSplitSizes: (sizes: [string | number, string | number]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a side is closed (hidden or 0px)
|
||||||
|
* @param side - Which side to check
|
||||||
|
*/
|
||||||
isSideClosed: (side: 'left' | 'right') => boolean;
|
isSideClosed: (side: 'left' | 'right') => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current sizes in pixels
|
||||||
|
* @returns [leftSize, rightSize] in pixels
|
||||||
|
*/
|
||||||
getSizesInPixels: () => [number, number];
|
getSizesInPixels: () => [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consolidated state interface for better organization
|
/**
|
||||||
|
* Internal state interface for the splitter
|
||||||
|
*/
|
||||||
interface SplitterState {
|
interface SplitterState {
|
||||||
|
/** Current container size in pixels */
|
||||||
containerSize: number;
|
containerSize: number;
|
||||||
|
/** Whether the user is currently dragging the splitter */
|
||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
|
/** Whether an animation is currently in progress */
|
||||||
isAnimating: boolean;
|
isAnimating: boolean;
|
||||||
|
/** Whether the current size was set by an animation */
|
||||||
sizeSetByAnimation: boolean;
|
sizeSetByAnimation: boolean;
|
||||||
|
/** Whether the user has interacted with the splitter */
|
||||||
hasUserInteracted: boolean;
|
hasUserInteracted: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// MAIN COMPONENT
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppSplitter - A resizable split panel component with localStorage persistence
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Horizontal or vertical splitting
|
||||||
|
* - Drag to resize with constraints
|
||||||
|
* - Auto-save layout to localStorage
|
||||||
|
* - Smooth animations
|
||||||
|
* - Responsive container resizing
|
||||||
|
* - Panel hiding/showing
|
||||||
|
* - Imperative API via ref
|
||||||
|
*/
|
||||||
const AppSplitterWrapper = forwardRef<AppSplitterRef, IAppSplitterProps>(
|
const AppSplitterWrapper = forwardRef<AppSplitterRef, IAppSplitterProps>(
|
||||||
({ autoSaveId, style, className, split = 'vertical', ...props }, componentRef) => {
|
({ autoSaveId, style, className, split = 'vertical', ...props }, componentRef) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const isVertical = split === 'vertical';
|
const isVertical = split === 'vertical';
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(!props.bustStorageOnInit);
|
||||||
const splitterAutoSaveId = createAutoSaveId(autoSaveId);
|
const splitterAutoSaveId = createAutoSaveId(autoSaveId);
|
||||||
|
|
||||||
useMount(() => {
|
useMount(() => {
|
||||||
|
@ -97,11 +241,13 @@ const AppSplitterWrapper = forwardRef<AppSplitterRef, IAppSplitterProps>(
|
||||||
|
|
||||||
AppSplitterWrapper.displayName = 'AppSplitterWrapper';
|
AppSplitterWrapper.displayName = 'AppSplitterWrapper';
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// CORE IMPLEMENTATION
|
||||||
|
// ================================
|
||||||
|
|
||||||
const AppSplitterBase = forwardRef<
|
const AppSplitterBase = forwardRef<
|
||||||
AppSplitterRef,
|
AppSplitterRef,
|
||||||
Omit<IAppSplitterProps, 'autoSaveId' | 'style' | 'className'> & {
|
Omit<IAppSplitterProps, 'autoSaveId' | 'style' | 'className'> & {
|
||||||
// savedLayout: number | null;
|
|
||||||
// setSavedLayout: (layout: number | null) => void;
|
|
||||||
isVertical: boolean;
|
isVertical: boolean;
|
||||||
containerRef: React.RefObject<HTMLDivElement>;
|
containerRef: React.RefObject<HTMLDivElement>;
|
||||||
splitterAutoSaveId: string;
|
splitterAutoSaveId: string;
|
||||||
|
@ -134,25 +280,14 @@ const AppSplitterBase = forwardRef<
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
|
// ================================
|
||||||
|
// REFS AND STATE
|
||||||
|
// ================================
|
||||||
|
|
||||||
const startPosRef = useRef(0);
|
const startPosRef = useRef(0);
|
||||||
const startSizeRef = useRef(0);
|
const startSizeRef = useRef(0);
|
||||||
const animationRef = useRef<number | null>(null);
|
const animationRef = useRef<number | null>(null);
|
||||||
|
|
||||||
const bustStorageOnInitSplitter = (preservedSideValue: number | null) => {
|
|
||||||
const refWidth = containerRef.current?.offsetWidth;
|
|
||||||
// Don't bust storage if container hasn't been sized yet
|
|
||||||
if (!refWidth || refWidth === 0) return false;
|
|
||||||
return typeof bustStorageOnInit === 'function'
|
|
||||||
? bustStorageOnInit(preservedSideValue, refWidth)
|
|
||||||
: !!bustStorageOnInit;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load saved layout from localStorage
|
|
||||||
const [savedLayout, setSavedLayout] = useLocalStorageState<number | null>(splitterAutoSaveId, {
|
|
||||||
defaultValue: null,
|
|
||||||
bustStorageOnInit: bustStorageOnInitSplitter
|
|
||||||
});
|
|
||||||
|
|
||||||
// Consolidated state management
|
// Consolidated state management
|
||||||
const [state, setState] = useState<SplitterState>({
|
const [state, setState] = useState<SplitterState>({
|
||||||
containerSize: containerRef.current?.offsetWidth ?? 0,
|
containerSize: containerRef.current?.offsetWidth ?? 0,
|
||||||
|
@ -162,6 +297,45 @@ const AppSplitterBase = forwardRef<
|
||||||
hasUserInteracted: false
|
hasUserInteracted: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// STORAGE MANAGEMENT
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
const bustStorageOnInitSplitter = (preservedSideValue: number | null) => {
|
||||||
|
const refWidth = containerRef.current?.offsetWidth;
|
||||||
|
// Don't bust storage if container hasn't been sized yet
|
||||||
|
if (!refWidth || refWidth === 0) {
|
||||||
|
console.warn('AppSplitter: container not sized yet');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof bustStorageOnInit === 'function'
|
||||||
|
? bustStorageOnInit(preservedSideValue, refWidth)
|
||||||
|
: !!bustStorageOnInit;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultValue = () => {
|
||||||
|
const [leftValue, rightValue] = defaultLayout;
|
||||||
|
if (preserveSide === 'left' && leftValue === 'auto') {
|
||||||
|
return containerRef.current?.offsetWidth ?? 0;
|
||||||
|
}
|
||||||
|
if (preserveSide === 'right' && rightValue === 'auto') {
|
||||||
|
return containerRef.current?.offsetWidth ?? 0;
|
||||||
|
}
|
||||||
|
const preserveValue = preserveSide === 'left' ? leftValue : rightValue;
|
||||||
|
return sizeToPixels(preserveValue, containerRef.current?.offsetWidth ?? 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load saved layout from localStorage
|
||||||
|
const [savedLayout, setSavedLayout] = useLocalStorageState<number | null>(splitterAutoSaveId, {
|
||||||
|
defaultValue,
|
||||||
|
bustStorageOnInit: bustStorageOnInitSplitter
|
||||||
|
});
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// SIZE CALCULATION LOGIC
|
||||||
|
// ================================
|
||||||
|
|
||||||
// Calculate initial size based on default layout
|
// Calculate initial size based on default layout
|
||||||
const calculateInitialSize = useMemoizedFn((containerSize: number): number => {
|
const calculateInitialSize = useMemoizedFn((containerSize: number): number => {
|
||||||
if (containerSize === 0) return 0;
|
if (containerSize === 0) return 0;
|
||||||
|
@ -282,11 +456,9 @@ const AppSplitterBase = forwardRef<
|
||||||
}
|
}
|
||||||
}, [state, savedLayout, leftHidden, rightHidden, preserveSide, applyConstraints]);
|
}, [state, savedLayout, leftHidden, rightHidden, preserveSide, applyConstraints]);
|
||||||
|
|
||||||
// Determine if splitter should be hidden
|
// ================================
|
||||||
const shouldHideSplitter =
|
// CONTAINER RESIZE HANDLING
|
||||||
hideSplitterProp || (leftHidden && rightHidden) || leftSize === 0 || rightSize === 0;
|
// ================================
|
||||||
|
|
||||||
const showSplitter = !leftHidden && !rightHidden;
|
|
||||||
|
|
||||||
// Update container size and handle initialization
|
// Update container size and handle initialization
|
||||||
const updateContainerSize = useMemoizedFn(() => {
|
const updateContainerSize = useMemoizedFn(() => {
|
||||||
|
@ -346,6 +518,10 @@ const AppSplitterBase = forwardRef<
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// ANIMATION LOGIC
|
||||||
|
// ================================
|
||||||
|
|
||||||
// Animation function
|
// Animation function
|
||||||
const animateWidth = useMemoizedFn(
|
const animateWidth = useMemoizedFn(
|
||||||
async (
|
async (
|
||||||
|
@ -405,6 +581,10 @@ const AppSplitterBase = forwardRef<
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// IMPERATIVE API METHODS
|
||||||
|
// ================================
|
||||||
|
|
||||||
// Set split sizes function
|
// Set split sizes function
|
||||||
const setSplitSizes = useMemoizedFn((sizes: [string | number, string | number]) => {
|
const setSplitSizes = useMemoizedFn((sizes: [string | number, string | number]) => {
|
||||||
if (!state.containerSize) return;
|
if (!state.containerSize) return;
|
||||||
|
@ -457,7 +637,10 @@ const AppSplitterBase = forwardRef<
|
||||||
return [leftSize, rightSize];
|
return [leftSize, rightSize];
|
||||||
}, [leftSize, rightSize]);
|
}, [leftSize, rightSize]);
|
||||||
|
|
||||||
// Mouse event handlers
|
// ================================
|
||||||
|
// MOUSE EVENT HANDLERS
|
||||||
|
// ================================
|
||||||
|
|
||||||
const handleMouseDown = useMemoizedFn((e: React.MouseEvent) => {
|
const handleMouseDown = useMemoizedFn((e: React.MouseEvent) => {
|
||||||
if (!allowResize) return;
|
if (!allowResize) return;
|
||||||
|
|
||||||
|
@ -495,13 +678,14 @@ const AppSplitterBase = forwardRef<
|
||||||
setState((prev) => ({ ...prev, isDragging: false }));
|
setState((prev) => ({ ...prev, isDragging: false }));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use useLayoutEffect for initial measurement to ensure DOM is ready
|
// ================================
|
||||||
useLayoutEffect(() => {
|
// EFFECTS AND LIFECYCLE
|
||||||
updateContainerSize();
|
// ================================
|
||||||
}, [updateContainerSize]);
|
|
||||||
|
|
||||||
// Use useEffect for ongoing resize monitoring
|
// Container resize monitoring
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
updateContainerSize();
|
||||||
|
|
||||||
// If container is still 0 after layout, try again with animation frame
|
// If container is still 0 after layout, try again with animation frame
|
||||||
if (containerRef.current?.offsetWidth === 0) {
|
if (containerRef.current?.offsetWidth === 0) {
|
||||||
requestAnimationFrame(updateContainerSize);
|
requestAnimationFrame(updateContainerSize);
|
||||||
|
@ -520,6 +704,7 @@ const AppSplitterBase = forwardRef<
|
||||||
};
|
};
|
||||||
}, [updateContainerSize]);
|
}, [updateContainerSize]);
|
||||||
|
|
||||||
|
// Mouse event handling during drag
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.isDragging) {
|
if (state.isDragging) {
|
||||||
document.addEventListener('mousemove', handleMouseMove);
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
@ -536,7 +721,7 @@ const AppSplitterBase = forwardRef<
|
||||||
}
|
}
|
||||||
}, [state.isDragging, handleMouseMove, handleMouseUp, isVertical]);
|
}, [state.isDragging, handleMouseMove, handleMouseUp, isVertical]);
|
||||||
|
|
||||||
// Expose methods via ref
|
// Expose imperative API via ref
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -548,6 +733,16 @@ const AppSplitterBase = forwardRef<
|
||||||
[animateWidth, setSplitSizes, isSideClosed, getSizesInPixels]
|
[animateWidth, setSplitSizes, isSideClosed, getSizesInPixels]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// RENDER LOGIC
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
// Determine if splitter should be hidden
|
||||||
|
const shouldHideSplitter =
|
||||||
|
hideSplitterProp || (leftHidden && rightHidden) || leftSize === 0 || rightSize === 0;
|
||||||
|
|
||||||
|
const showSplitter = !leftHidden && !rightHidden;
|
||||||
|
|
||||||
const sizes = useMemo<[string | number, string | number]>(
|
const sizes = useMemo<[string | number, string | number]>(
|
||||||
() => [`${leftSize}px`, `${rightSize}px`],
|
() => [`${leftSize}px`, `${rightSize}px`],
|
||||||
[leftSize, rightSize]
|
[leftSize, rightSize]
|
||||||
|
|
|
@ -27,7 +27,7 @@ interface Options<T> {
|
||||||
export function useLocalStorageState<T>(
|
export function useLocalStorageState<T>(
|
||||||
key: string,
|
key: string,
|
||||||
options?: Options<T>
|
options?: Options<T>
|
||||||
): [T | undefined, (value?: SetState<T>) => void, () => T | undefined] {
|
): [T | undefined, (value?: SetState<T>) => void] {
|
||||||
const {
|
const {
|
||||||
defaultValue,
|
defaultValue,
|
||||||
serializer = JSON.stringify,
|
serializer = JSON.stringify,
|
||||||
|
@ -37,27 +37,22 @@ export function useLocalStorageState<T>(
|
||||||
expirationTime = DEFAULT_EXPIRATION_TIME
|
expirationTime = DEFAULT_EXPIRATION_TIME
|
||||||
} = options || {};
|
} = options || {};
|
||||||
|
|
||||||
const executeBustStorage = useMemoizedFn(() => {
|
|
||||||
console.log(
|
|
||||||
'***executeBustStorage',
|
|
||||||
key,
|
|
||||||
typeof defaultValue === 'function' ? (defaultValue as () => T)() : defaultValue
|
|
||||||
);
|
|
||||||
if (!isServer) window.localStorage.removeItem(key);
|
|
||||||
return typeof defaultValue === 'function' ? (defaultValue as () => T)() : defaultValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get initial value from localStorage or use default
|
// Get initial value from localStorage or use default
|
||||||
const getInitialValue = useMemoizedFn((): T | undefined => {
|
const getInitialValue = useMemoizedFn((): T | undefined => {
|
||||||
|
const gonnaBustTheStorage = () => {
|
||||||
|
if (!isServer) window.localStorage.removeItem(key);
|
||||||
|
return typeof defaultValue === 'function' ? (defaultValue as () => T)() : defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
// If bustStorageOnInit is true, ignore localStorage and use default value
|
// If bustStorageOnInit is true, ignore localStorage and use default value
|
||||||
if (bustStorageOnInit === true) {
|
if (bustStorageOnInit === true) {
|
||||||
return executeBustStorage();
|
return gonnaBustTheStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const item = window.localStorage.getItem(key);
|
const item = window.localStorage.getItem(key);
|
||||||
if (item === null) {
|
if (item === null) {
|
||||||
return executeBustStorage();
|
return gonnaBustTheStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the stored data which includes value and timestamp
|
// Parse the stored data which includes value and timestamp
|
||||||
|
@ -71,7 +66,7 @@ export function useLocalStorageState<T>(
|
||||||
!('timestamp' in storageData)
|
!('timestamp' in storageData)
|
||||||
) {
|
) {
|
||||||
// If the data doesn't have the expected structure (legacy data), treat as expired
|
// If the data doesn't have the expected structure (legacy data), treat as expired
|
||||||
return executeBustStorage();
|
return gonnaBustTheStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the data has expired
|
// Check if the data has expired
|
||||||
|
@ -80,20 +75,20 @@ export function useLocalStorageState<T>(
|
||||||
|
|
||||||
if (timeDifference > expirationTime) {
|
if (timeDifference > expirationTime) {
|
||||||
// Data has expired, remove it and return default value
|
// Data has expired, remove it and return default value
|
||||||
return executeBustStorage();
|
return gonnaBustTheStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data is still valid, deserialize and return the value
|
// Data is still valid, deserialize and return the value
|
||||||
const deserializedValue = deserializer(JSON.stringify(storageData.value));
|
const deserializedValue = deserializer(JSON.stringify(storageData.value));
|
||||||
|
|
||||||
if (typeof bustStorageOnInit === 'function' && bustStorageOnInit(deserializedValue)) {
|
if (typeof bustStorageOnInit === 'function' && bustStorageOnInit(deserializedValue)) {
|
||||||
return executeBustStorage();
|
return gonnaBustTheStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
return deserializedValue;
|
return deserializedValue;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onError?.(error);
|
onError?.(error);
|
||||||
return executeBustStorage();
|
return gonnaBustTheStorage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -138,5 +133,5 @@ export function useLocalStorageState<T>(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return [state, setStoredState, executeBustStorage];
|
return [state, setStoredState];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,7 @@ import { AppPageLayout } from '@/components/ui/layouts';
|
||||||
import { ChatContent } from './ChatContent';
|
import { ChatContent } from './ChatContent';
|
||||||
import { ChatHeader } from './ChatHeader';
|
import { ChatHeader } from './ChatHeader';
|
||||||
|
|
||||||
export const ChatContainer = React.memo(({ mounted }: { mounted?: boolean }) => {
|
export const ChatContainer = React.memo(() => {
|
||||||
console.log('ChatContainer', mounted);
|
|
||||||
return (
|
return (
|
||||||
<AppPageLayout
|
<AppPageLayout
|
||||||
headerSizeVariant="default"
|
headerSizeVariant="default"
|
||||||
|
|
|
@ -41,17 +41,11 @@ export const ChatLayout: React.FC<ChatSplitterProps> = ({ children }) => {
|
||||||
const mounted = true;
|
const mounted = true;
|
||||||
|
|
||||||
const bustStorageOnInit = (preservedSideValue: number | null, containerSize: number) => {
|
const bustStorageOnInit = (preservedSideValue: number | null, containerSize: number) => {
|
||||||
console.log(
|
console.log('bustStorageOnInit', autoSaveId, preservedSideValue, {
|
||||||
selectedLayout === 'chat-only' || selectedLayout === 'file-only' || !!secondaryFileView,
|
selectedLayout,
|
||||||
'bustStorageOnInit',
|
secondaryFileView,
|
||||||
autoSaveId,
|
containerSize
|
||||||
preservedSideValue,
|
});
|
||||||
{
|
|
||||||
selectedLayout,
|
|
||||||
secondaryFileView,
|
|
||||||
containerSize
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return selectedLayout === 'chat-only' || selectedLayout === 'file-only' || !!secondaryFileView;
|
return selectedLayout === 'chat-only' || selectedLayout === 'file-only' || !!secondaryFileView;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,7 +54,7 @@ export const ChatLayout: React.FC<ChatSplitterProps> = ({ children }) => {
|
||||||
<ChatContextProvider>
|
<ChatContextProvider>
|
||||||
<AppSplitter
|
<AppSplitter
|
||||||
ref={appSplitterRef}
|
ref={appSplitterRef}
|
||||||
leftChildren={<div>HUH?</div>}
|
leftChildren={useMemo(() => mounted && <ChatContainer />, [mounted])}
|
||||||
rightChildren={useMemo(
|
rightChildren={useMemo(
|
||||||
() => mounted && <FileContainer>{children}</FileContainer>,
|
() => mounted && <FileContainer>{children}</FileContainer>,
|
||||||
[children, mounted]
|
[children, mounted]
|
||||||
|
|
Loading…
Reference in New Issue