Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 80 additions & 11 deletions apps/diffshub/components/ReviewUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type ColorMode } from '@pierre/theming';
import { useThemeController } from '@pierre/theming/react';
import {
type ReactNode,
type SetStateAction,
useCallback,
useEffect,
useRef,
Expand All @@ -24,6 +25,10 @@ import {
themeController,
} from '@/components/themeController';
import { preloadAvatars } from '@/lib/annotation';
import {
type DiffsHubDisplayPreferences,
useDiffsHubDisplayPreferences,
} from '@/lib/displayPreferences';
import { removeSavedCommentSidebarEntry } from '@/lib/removeSavedCommentSidebarEntry';
import type { DarkThemeName, LightThemeName } from '@/lib/themeNames';
import type {
Expand Down Expand Up @@ -54,15 +59,23 @@ function ReviewUIInner({ domain, initialUrl, path }: ReviewUIProps) {
useEffect(preloadAvatars, []);

const isWorkerPoolReadyOrDisable = useIsWorkerPoolReadyOrDisabled();
const [diffStyle, setDiffStyle] = useState<'split' | 'unified'>('split');
const [collapseMode, setCollapseMode] = useState<'expanded' | 'collapsed'>(
'expanded'
);
const [fileTreeOverlayOpen, setFileTreeOverlayOpen] = useState(false);
const [overflow, setOverflow] = useState<'wrap' | 'scroll'>('scroll');
const [showBackgrounds, setShowBackgrounds] = useState(true);
const [diffIndicators, setDiffIndicators] = useState<DiffIndicators>('bars');
const [lineNumbers, setLineNumbers] = useState(true);
const [forceUnifiedDiffStyle, setForceUnifiedDiffStyle] = useState(false);
const {
displayPreferences,
displayPreferencesHydrated,
updateDisplayPreferences,
} = useDiffsHubDisplayPreferences();
const {
collapseMode,
diffIndicators,
lineNumbers,
overflow,
showBackgrounds,
} = displayPreferences;
const diffStyle = forceUnifiedDiffStyle
? 'unified'
: displayPreferences.diffStyle;
// All theming state — color mode and the light/dark theme-name picks — lives
// in the single @pierre/theming controller (the same instance the app-wide
// ThemeProvider is bound to). Reading it here means picking Auto/Light/Dark
Expand Down Expand Up @@ -137,6 +150,7 @@ function ReviewUIInner({ domain, initialUrl, path }: ReviewUIProps) {
} = usePatchLoader({
collapseMode,
domain,
enabled: displayPreferencesHydrated,
onLoadStart: handlePatchLoadStart,
path,
viewerRef,
Expand All @@ -145,7 +159,7 @@ function ReviewUIInner({ domain, initialUrl, path }: ReviewUIProps) {
useEffect(() => {
const mediaQuery = window.matchMedia('(max-width: 767px)');
const updateMobileState = (matches: boolean) => {
setDiffStyle(matches ? 'unified' : 'split');
setForceUnifiedDiffStyle(matches);
if (!matches) setFileTreeOverlayOpen(false);
};
const handleChange = (event: MediaQueryListEvent) => {
Expand All @@ -156,6 +170,57 @@ function ReviewUIInner({ domain, initialUrl, path }: ReviewUIProps) {
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
const updateDisplayPreference = useCallback(
<Key extends keyof DiffsHubDisplayPreferences>(
key: Key,
value: SetStateAction<DiffsHubDisplayPreferences[Key]>
) => {
updateDisplayPreferences((previous) => {
const previousValue = previous[key];
const nextValue =
typeof value === 'function'
? (
value as (current: typeof previousValue) => typeof previousValue
)(previousValue)
: value;
return {
...previous,
[key]: nextValue,
};
});
},
[updateDisplayPreferences]
);
const setDiffStyle = useCallback(
(value: SetStateAction<'split' | 'unified'>) => {
updateDisplayPreference('diffStyle', value);
},
[updateDisplayPreference]
);
const setDiffIndicators = useCallback(
(value: SetStateAction<DiffIndicators>) => {
updateDisplayPreference('diffIndicators', value);
},
[updateDisplayPreference]
);
const setLineNumbers = useCallback(
(value: SetStateAction<boolean>) => {
updateDisplayPreference('lineNumbers', value);
},
[updateDisplayPreference]
);
const setOverflow = useCallback(
(value: SetStateAction<'wrap' | 'scroll'>) => {
updateDisplayPreference('overflow', value);
},
[updateDisplayPreference]
);
const setShowBackgrounds = useCallback(
(value: SetStateAction<boolean>) => {
updateDisplayPreference('showBackgrounds', value);
},
[updateDisplayPreference]
);
const handleSelectTreeItem = useCallback((itemId: string) => {
setFileTreeOverlayOpen(false);
const viewer = viewerRef.current;
Expand All @@ -177,9 +242,12 @@ function ReviewUIInner({ domain, initialUrl, path }: ReviewUIProps) {
}, []);
const handleToggleCollapseMode = useCallback(() => {
const next = collapseMode === 'expanded' ? 'collapsed' : 'expanded';
setCollapseMode(next);
updateDisplayPreferences((previous) => ({
...previous,
collapseMode: next,
}));
applyCollapseModeToLoaded(next);
}, [applyCollapseModeToLoaded, collapseMode]);
}, [applyCollapseModeToLoaded, collapseMode, updateDisplayPreferences]);
const handleCommentSaved = useCallback(
(comment: DiffsHubSavedCommentEvent) => {
setCommentSections((prev) =>
Expand Down Expand Up @@ -227,6 +295,7 @@ function ReviewUIInner({ domain, initialUrl, path }: ReviewUIProps) {
// first batch of files against the wrong palette.
const viewerAvailable =
isWorkerPoolReadyOrDisable &&
displayPreferencesHydrated &&
themesHydrated &&
(loadState === 'ready' ||
(loadState === 'streaming' && initialItems.length > 0));
Expand Down
32 changes: 10 additions & 22 deletions apps/diffshub/components/themeController.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { createThemeController, type ThemePersistence } from '@pierre/theming';

import { docsThemeCatalog } from './themeCatalog';
import {
readBrowserStorageKey,
writeBrowserStorageKey,
} from '@/lib/browserStorage';

export { docsThemeCatalog } from './themeCatalog';

Expand All @@ -23,30 +27,14 @@ const MODE_KEY = 'theme';
const LIGHT_THEME_KEY = 'diffshub-light-theme';
const DARK_THEME_KEY = 'diffshub-dark-theme';

function readKey(key: string): string | null {
try {
return globalThis.localStorage?.getItem(key) ?? null;
} catch {
return null;
}
}

function writeKey(key: string, value: string): void {
try {
globalThis.localStorage?.setItem(key, value);
} catch {
// Storage may be unavailable (private mode / denied) — non-fatal.
}
}

// Maps the controller's selection onto the app's three storage keys: mode as a
// plain `light`/`dark`/`system` string under `theme` (what the bootstrap script
// reads), and the theme names under the diffshub-prefixed keys.
const docsPersistence: ThemePersistence = {
load() {
const mode = readKey(MODE_KEY);
const light = readKey(LIGHT_THEME_KEY);
const dark = readKey(DARK_THEME_KEY);
const mode = readBrowserStorageKey(MODE_KEY);
const light = readBrowserStorageKey(LIGHT_THEME_KEY);
const dark = readBrowserStorageKey(DARK_THEME_KEY);
if (mode == null && light == null && dark == null) return null;
const validMode =
mode === 'light' || mode === 'dark' || mode === 'system'
Expand All @@ -59,9 +47,9 @@ const docsPersistence: ThemePersistence = {
};
},
save(selection) {
writeKey(MODE_KEY, selection.mode);
writeKey(LIGHT_THEME_KEY, selection.lightThemeName);
writeKey(DARK_THEME_KEY, selection.darkThemeName);
writeBrowserStorageKey(MODE_KEY, selection.mode);
writeBrowserStorageKey(LIGHT_THEME_KEY, selection.lightThemeName);
writeBrowserStorageKey(DARK_THEME_KEY, selection.darkThemeName);
},
};

Expand Down
7 changes: 7 additions & 0 deletions apps/diffshub/components/usePatchLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const GENERIC_PATCH_LOAD_ERROR_MESSAGE =
interface UsePatchLoaderOptions {
collapseMode: 'expanded' | 'collapsed';
domain?: string;
enabled: boolean;
onLoadStart(): void;
path: string;
viewerRef: RefObject<CodeViewHandle<CommentMetadata> | null>;
Expand All @@ -80,6 +81,7 @@ interface UsePatchLoaderResult {
export function usePatchLoader({
collapseMode,
domain,
enabled,
onLoadStart,
path,
viewerRef,
Expand Down Expand Up @@ -212,6 +214,10 @@ export function usePatchLoader({
);

useEffect(() => {
if (!enabled) {
return;
}

const patchRequestKey =
domain == null || domain === '' ? path : `${domain}${path}`;
const patchSearchParams = new URLSearchParams({ path });
Expand Down Expand Up @@ -488,6 +494,7 @@ export function usePatchLoader({
};
}, [
domain,
enabled,
loadAttempt,
onLoadStart,
path,
Expand Down
16 changes: 16 additions & 0 deletions apps/diffshub/lib/browserStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function readBrowserStorageKey(key: string): string | null {
try {
return globalThis.localStorage?.getItem(key) ?? null;
} catch {
return null;
}
}

export function writeBrowserStorageKey(key: string, value: string): void {
try {
globalThis.localStorage?.setItem(key, value);
} catch {
// Storage may be unavailable (private mode / denied) or full. Callers keep
// their in-memory state, so persistence failure is non-fatal.
}
}
Loading