textmachine/frontend/src/showcase/documents.ts

72 lines
3.1 KiB
TypeScript

/**
* 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 };
}