Skip to content

Commit 1da4f9e

Browse files
authored
fix(web): persist browser preview viewport (#4899)
1 parent 9757b25 commit 1da4f9e

2 files changed

Lines changed: 62 additions & 1 deletion

File tree

apps/web/src/components/DesignBrowserPanel.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,11 @@ export function DesignBrowserPanel({
834834
const [isLoading, setIsLoading] = useState(false);
835835
const [webviewNode, setWebviewNode] = useState<WebviewElement | null>(null);
836836
const [drawOverlayOpen, setDrawOverlayOpen] = useState(false);
837-
const [viewport, setViewport] = useState<BrowserViewportId>('desktop');
837+
const [viewport, setViewportState] = useState<BrowserViewportId>(() => loadBrowserViewport(projectId));
838+
const setViewport = useCallback((nextViewport: BrowserViewportId) => {
839+
saveBrowserViewport(projectId, nextViewport);
840+
setViewportState(nextViewport);
841+
}, [projectId]);
838842
const [activeTool, setActiveTool] = useState<BrowserTool | null>(null);
839843
const [activeCommentTarget, setActiveCommentTarget] = useState<BrowserElementSnapshot | null>(null);
840844
const [activePreviewCommentId, setActivePreviewCommentId] = useState<string | null>(null);
@@ -924,6 +928,7 @@ export function DesignBrowserPanel({
924928
}, []);
925929

926930
useEffect(() => {
931+
setViewportState(loadBrowserViewport(projectId));
927932
setHistory(loadHistory(projectId));
928933
const nextInitialState = initialBrowserState(initialUrl, initialTitle);
929934
setLoadUrl(nextInitialState.url);
@@ -3323,6 +3328,33 @@ function historyStorageKey(projectId: string): string {
33233328
return `od:design-browser:${projectId}:history:v1`;
33243329
}
33253330

3331+
function viewportStorageKey(projectId: string): string {
3332+
return `od:design-browser:${projectId}:viewport:v1`;
3333+
}
3334+
3335+
function isBrowserViewportId(value: unknown): value is BrowserViewportId {
3336+
return value === 'desktop' || value === 'tablet' || value === 'mobile';
3337+
}
3338+
3339+
export function loadBrowserViewport(projectId: string): BrowserViewportId {
3340+
if (typeof window === 'undefined') return 'desktop';
3341+
try {
3342+
const stored = window.localStorage.getItem(viewportStorageKey(projectId));
3343+
return isBrowserViewportId(stored) ? stored : 'desktop';
3344+
} catch {
3345+
return 'desktop';
3346+
}
3347+
}
3348+
3349+
export function saveBrowserViewport(projectId: string, viewport: BrowserViewportId) {
3350+
if (typeof window === 'undefined') return;
3351+
try {
3352+
window.localStorage.setItem(viewportStorageKey(projectId), viewport);
3353+
} catch {
3354+
// Ignore storage quota and private-mode failures.
3355+
}
3356+
}
3357+
33263358
export function isHistoryEntry(value: unknown): value is BrowserHistoryEntry {
33273359
if (typeof value !== 'object' || value == null || Array.isArray(value)) return false;
33283360
const record = value as Record<string, unknown>;

apps/web/tests/components/DesignBrowserPanel.test.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ import {
1919
isHistoryEntry,
2020
isHistoryUrl,
2121
labelFromUrl,
22+
loadBrowserViewport,
2223
loadHistory,
2324
normalizeBrowserAddress,
2425
pageBriefMarkdown,
2526
referenceIconUrl,
2627
sameUrl,
28+
saveBrowserViewport,
2729
saveHistory,
2830
} from '../../src/components/DesignBrowserPanel';
2931
import {
@@ -438,6 +440,33 @@ describe('loadHistory / saveHistory round-trip', () => {
438440
});
439441
});
440442

443+
describe('loadBrowserViewport / saveBrowserViewport round-trip', () => {
444+
const projectId = 'proj-viewport';
445+
446+
beforeEach(() => {
447+
window.localStorage.clear();
448+
});
449+
450+
afterEach(() => {
451+
window.localStorage.clear();
452+
});
453+
454+
it('returns desktop when no browser viewport is stored', () => {
455+
expect(loadBrowserViewport(projectId)).toBe('desktop');
456+
});
457+
458+
it('round-trips the selected browser viewport', () => {
459+
saveBrowserViewport(projectId, 'mobile');
460+
expect(window.localStorage.getItem(`od:design-browser:${projectId}:viewport:v1`)).toBe('mobile');
461+
expect(loadBrowserViewport(projectId)).toBe('mobile');
462+
});
463+
464+
it('ignores malformed stored browser viewport values', () => {
465+
window.localStorage.setItem(`od:design-browser:${projectId}:viewport:v1`, 'watch');
466+
expect(loadBrowserViewport(projectId)).toBe('desktop');
467+
});
468+
});
469+
441470
describe('REFERENCE_GROUPS catalogue', () => {
442471
it('exposes every documented designer reference category', () => {
443472
const ids = REFERENCE_GROUPS.map((group) => group.id);

0 commit comments

Comments
 (0)