summaryrefslogtreecommitdiff
path: root/apps/web/src/doc
diff options
context:
space:
mode:
authorJosh Kingsley <josh@joshkingsley.me>2025-11-24 15:46:22 +0200
committerJosh Kingsley <josh@joshkingsley.me>2025-11-24 15:46:22 +0200
commitd724cc0bf6ff6d351319e6fb00f5184a04e16ac0 (patch)
treecb43253479df5db8f4844e17e68a48ea5a212df4 /apps/web/src/doc
parent7c966e105cd9f65853de1aba0ecce63aa56aca0b (diff)
chore: improve dev tasks
Diffstat (limited to 'apps/web/src/doc')
-rw-r--r--apps/web/src/doc/index.test.ts16
-rw-r--r--apps/web/src/doc/index.ts125
2 files changed, 141 insertions, 0 deletions
diff --git a/apps/web/src/doc/index.test.ts b/apps/web/src/doc/index.test.ts
new file mode 100644
index 0000000..5f61398
--- /dev/null
+++ b/apps/web/src/doc/index.test.ts
@@ -0,0 +1,16 @@
+import { expect, test } from "vitest";
+import { apply, defaultDoc, realizeGrids, subdivide } from ".";
+
+test(realizeGrids, () => {
+ const doc = defaultDoc();
+ const grids = realizeGrids(doc);
+
+ expect(grids.length).toBe(1);
+ expect(grids[0].rows.length).toBe(4);
+ expect(grids[0].rows[0].cells.length).toBe(16);
+
+ const doc2 = apply(doc, subdivide(grids[0].id, 0, 0, 3, 3));
+ const grids2 = realizeGrids(doc2);
+
+ expect(grids2[0].rows[0].cells.length).toBe(15);
+});
diff --git a/apps/web/src/doc/index.ts b/apps/web/src/doc/index.ts
new file mode 100644
index 0000000..ae221f0
--- /dev/null
+++ b/apps/web/src/doc/index.ts
@@ -0,0 +1,125 @@
+import { Immutable, produce } from "immer";
+
+export type Doc = Immutable<{ ops: Op[] }>;
+
+export type Op = CreateGrid | Subdivide;
+
+export type CreateGrid = Immutable<{
+ type: "createGrid";
+ gridId: string;
+ rows: number;
+ baseCellsPerRow: number;
+}>;
+
+export function createGrid(): CreateGrid {
+ return {
+ type: "createGrid",
+ gridId: crypto.randomUUID(),
+ rows: 4,
+ baseCellsPerRow: 16,
+ };
+}
+
+export type Subdivide = Immutable<{
+ type: "subdivide";
+ gridId: string;
+ rowIndex: number;
+ startCellIndex: number;
+ endCellIndex: number;
+ subdivisions: number;
+}>;
+
+export function subdivide(
+ gridId: string,
+ rowIndex: number,
+ startCellIndex: number,
+ endCellIndex: number,
+ subdivisions: number,
+): Subdivide {
+ return {
+ type: "subdivide",
+ gridId,
+ rowIndex,
+ startCellIndex,
+ endCellIndex,
+ subdivisions,
+ };
+}
+
+export function defaultDoc(): Doc {
+ const ops = [createGrid()];
+ return { ops };
+}
+
+export function apply(doc: Doc, ...ops: Op[]): Doc {
+ return produce(doc, (doc) => {
+ doc.ops.push(...ops);
+ });
+}
+
+export type DocIndex = Immutable<{
+ opsByType: Map<Op["type"], Op[]>;
+}>;
+
+export function indexDoc(doc: Doc): DocIndex {
+ const opsByType = new Map();
+
+ for (const op of doc.ops) {
+ opsByType.set(op.type, [...(opsByType.get(op.type) ?? []), op]);
+ }
+
+ return { opsByType };
+}
+
+export function getOpsByType<T extends Op["type"]>(
+ index: DocIndex,
+ type: T,
+): Extract<Op, { type: T }>[] {
+ return (index.opsByType.get(type) ?? []) as Extract<Op, { type: T }>[];
+}
+
+export type Grid = Immutable<{ id: string; rows: Row[] }>;
+
+export type Row = Immutable<{ index: number; cells: Cell[] }>;
+
+export type Cell = Immutable<{}>;
+
+export function realizeGrids(doc: Doc): Grid[] {
+ const index = indexDoc(doc);
+ const createGridOps = getOpsByType(index, "createGrid");
+ return createGridOps.map((op) => realizeGrid(doc, index, op));
+}
+
+function realizeGrid(doc: Doc, index: DocIndex, createOp: CreateGrid): Grid {
+ const rows = [];
+
+ for (let rowIndex = 0; rowIndex < createOp.rows; rowIndex++) {
+ let cells: Cell[] = [];
+
+ for (let cellIndex = 0; cellIndex < createOp.baseCellsPerRow; cellIndex++) {
+ cells.push({ index: cellIndex });
+ }
+
+ const subdivideOps = doc.ops.filter(
+ (op) =>
+ op.type === "subdivide" &&
+ op.gridId === createOp.gridId &&
+ op.rowIndex === rowIndex,
+ ) as Subdivide[];
+
+ subdivideOps.forEach((op) => {
+ cells = [
+ ...cells.slice(0, op.startCellIndex),
+ ...Array.from({ length: op.subdivisions }, () => ({})),
+ ...cells.slice(op.endCellIndex + 1),
+ ];
+ });
+
+ rows.push({ index: rowIndex, cells });
+ }
+
+ return {
+ id: createOp.gridId,
+ rows,
+ };
+}