Leandro von Werra
Files pane: stop the phantom conflicts, keep the pane's place, wrap and prettify (#22)
01a64bf unverified
Raw
History Blame
2.63 kB
// What a Files pane remembers when it isn't on screen.
//
// The pane unmounts whenever its tile shows something else, so without this
// every switch away threw out where you were AND anything you had typed — you
// came back to the workspace root with an empty editor. State lives here
// instead, keyed by session, and the pane reads it back on mount.
//
// The in-memory map is what makes a pane switch lossless. localStorage is the
// second line: it also survives a reload of the whole app, which is the other
// way people lose a buffer they never asked to lose.
export interface Draft {
path: string;
text: string;
/** Content tag the buffer was based on — see contentTag() on the server. */
base: string | null;
}
export interface FilesMemory {
root: string;
viewing: string | null;
target: string | null;
sort?: { key: 'name' | 'size' | 'time'; desc: boolean };
draft: Draft | null;
}
const EMPTY: FilesMemory = { root: '', viewing: null, target: null, draft: null };
// A buffer big enough to be worth keeping is still small next to the 5 MB
// localStorage budget; past this the in-memory copy alone carries it, so a pane
// switch is still safe and only a full reload loses it.
const DRAFT_MAX = 256 * 1024;
const mem = new Map<string, FilesMemory>();
const key = (id: string) => `am.files.${id}`;
export function recall(id: string): FilesMemory {
const held = mem.get(id);
if (held) return held;
try {
const raw = localStorage.getItem(key(id));
if (raw) {
const parsed = { ...EMPTY, ...JSON.parse(raw) } as FilesMemory;
mem.set(id, parsed);
return parsed;
}
} catch {
// corrupt or unavailable storage is not worth failing a pane over
}
return EMPTY;
}
export function remember(id: string, patch: Partial<FilesMemory>) {
const next = { ...recall(id), ...patch };
mem.set(id, next);
try {
const forDisk = next.draft && next.draft.text.length > DRAFT_MAX ? { ...next, draft: null } : next;
localStorage.setItem(key(id), JSON.stringify(forDisk));
} catch {
// over quota, or private mode: memory still has it
}
}
export function forget(id: string) {
mem.delete(id);
try { localStorage.removeItem(key(id)); } catch {}
}
// One sticky preference rather than a per-file one: wrapping is about how you
// like to read, not about the file in front of you.
const WRAP = 'am.files.wrap';
export const readWrap = (): boolean => {
try { return localStorage.getItem(WRAP) === '1'; } catch { return false; }
};
export const writeWrap = (on: boolean) => {
try { localStorage.setItem(WRAP, on ? '1' : '0'); } catch {}
};