Persist the studio session across reloads
The working photo and every knob the workspace holds now survive a reload, for guests as much as for signed-in users: - engine/session.ts: the knobs go to localStorage (rc.studio.v1) as small JSON; the photo goes to IndexedDB, because a 12MP JPEG does not fit in localStorage. Both fail soft (private mode, quota) — the studio still works, it just forgets. - App.tsx: state seeds from the stored snapshot synchronously, so the first paint already holds the user's settings; the boot effect pulls the photo back and adopts it with keepGeo, so the restored params are not clobbered by the photo's own EXIF. Recipes a guest creates with SAVE RECIPE stay session-only, as asked — they are still gone on reload (create-test asserts it).
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import type { CropRatio, FrameId, GPSInfo, Recipe } from '../../shared/types';
|
||||
|
||||
// Studio session: the working photo plus every knob the workspace holds, so a
|
||||
// reload resumes exactly where the user left off — guest or signed in (the
|
||||
// login cookie already outlives the tab; this covers the rest).
|
||||
//
|
||||
// ponytail: one session per browser, not per account — two accounts in the same
|
||||
// browser share the working photo. Key it by user id if that ever matters.
|
||||
const STATE_KEY = 'rc.studio.v1';
|
||||
const DB_NAME = 'recipescam-studio';
|
||||
const STORE = 'session';
|
||||
const PHOTO_KEY = 'photo';
|
||||
|
||||
export interface StudioState {
|
||||
recipe?: Recipe;
|
||||
simId?: string | null;
|
||||
frameId?: FrameId;
|
||||
crop?: Exclude<CropRatio, 'free'>;
|
||||
rotation?: 0 | 90 | 180 | 270;
|
||||
gps?: GPSInfo | null;
|
||||
gpsManual?: { lat: string; lng: string };
|
||||
place?: string;
|
||||
useGeotag?: boolean;
|
||||
gpsStyle?: { color: string; showName: boolean; showTime: boolean };
|
||||
markText?: string;
|
||||
markOn?: boolean;
|
||||
markStyle?: { color: string; size: number };
|
||||
}
|
||||
|
||||
export function loadState(): StudioState {
|
||||
try {
|
||||
const raw = localStorage.getItem(STATE_KEY);
|
||||
return raw ? (JSON.parse(raw) as StudioState) : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function saveState(state: StudioState): void {
|
||||
try {
|
||||
localStorage.setItem(STATE_KEY, JSON.stringify(state));
|
||||
} catch {
|
||||
// Quota or private mode: the studio still works, it just forgets.
|
||||
}
|
||||
}
|
||||
|
||||
// --- the photo: IndexedDB, because a 12MP JPEG does not fit in localStorage ---
|
||||
function openDb(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open(DB_NAME, 1);
|
||||
req.onupgradeneeded = () => req.result.createObjectStore(STORE);
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function idb<T>(mode: IDBTransactionMode, run: (store: IDBObjectStore) => IDBRequest): Promise<T> {
|
||||
const db = await openDb();
|
||||
try {
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
const req = run(db.transaction(STORE, mode).objectStore(STORE));
|
||||
req.onsuccess = () => resolve(req.result as T);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function savePhoto(name: string, bytes: Uint8Array): Promise<void> {
|
||||
try {
|
||||
await idb('readwrite', (s) => s.put({ name, bytes }, PHOTO_KEY));
|
||||
} catch {
|
||||
// Private mode / no IndexedDB: the photo just will not survive a reload.
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadPhoto(): Promise<{ name: string; bytes: Uint8Array } | null> {
|
||||
try {
|
||||
const row = await idb<{ name: string; bytes: Uint8Array } | undefined>('readonly', (s) => s.get(PHOTO_KEY));
|
||||
return row && row.bytes instanceof Uint8Array ? { name: row.name, bytes: row.bytes } : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user