/** * The model of the centre's open documents — the same one VS Code has (owner remark 2). * A tab is pinned by a double click or by a meaningful interaction with the content; what counts * as meaningful here and why — `Document.tsx`. The functions are pure: the model is checked by a * test, not by a frame (documents.test.ts). */ export interface Documents { /** The pinned tabs, in the order they were opened. */ pinned: string[]; /** The preview tab. There is exactly one of it, or none. */ preview: string | null; /** The selected tab; an empty string means "nothing is open". */ active: string; } export const noDocuments: Documents = { pinned: [], preview: null, active: '' }; /** The preview is always the rightmost one: pinning does not move the row. */ export const openedIds = ({ pinned, preview }: Documents): string[] => preview === null ? pinned : [...pinned, preview]; /** A single click. An already pinned tab is simply selected — the preview does not touch it. */ export function previewDocument(documents: Documents, id: string): Documents { if (documents.pinned.includes(id)) return { ...documents, active: id }; return { ...documents, preview: id, active: id }; } /** A double click or a meaningful interaction: the preview becomes a permanent tab. */ export function pinDocument(documents: Documents, id: string): Documents { if (documents.pinned.includes(id)) return { ...documents, active: id }; return { pinned: [...documents.pinned, id], preview: documents.preview === id ? null : documents.preview, active: id, }; } export function selectDocument(documents: Documents, id: string): Documents { return { ...documents, active: id }; } /** * Closing. The selection moves to the neighbouring tab on the RIGHT, and if there is none — on the * left: this way the place in the row is not lost, and closing several in a row does not throw the * reader out into an empty centre. */ export function closeDocument(documents: Documents, id: string): Documents { const order = openedIds(documents); const index = order.indexOf(id); const next: Documents = { pinned: documents.pinned.filter((other) => other !== id), preview: documents.preview === id ? null : documents.preview, active: documents.active, }; if (documents.active !== id) return next; const rest = openedIds(next); next.active = rest[Math.min(index, rest.length - 1)] ?? ''; return next; } /** * What is open while the reader has not touched the tabs: one pinned and one preview — otherwise * both forms of a tab are not visible on a static frame, and the VS Code model tells them apart by * italics. The second and the third chapters are taken: in the default fixture it is they that * have text, and the second of them also has notes. This is a prop of the SHOWCASE and will go * away together with it, once a real library appears. */ export function firstLook(ids: string[]): Documents { const [, pinned, preview] = ids; if (pinned === undefined) return noDocuments; return { pinned: [pinned], preview: preview ?? null, active: pinned }; }