72 lines
3.1 KiB
TypeScript
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 };
|
|
}
|