summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/src/doc/index.test.ts29
-rw-r--r--web/src/doc/index.ts189
2 files changed, 117 insertions, 101 deletions
diff --git a/web/src/doc/index.test.ts b/web/src/doc/index.test.ts
index 331d0a5..5f61398 100644
--- a/web/src/doc/index.test.ts
+++ b/web/src/doc/index.test.ts
@@ -1,15 +1,16 @@
-import { describe, expect, test } from "vitest";
-import Doc from ".";
-
-describe(Doc, () => {
- describe(Doc.default, () => {
- const doc = Doc.default();
-
- test("produces valid grid data", () => {
- expect(doc.grids.length).toBe(1);
- expect(doc.grids[0].doc).toBe(doc);
- expect(doc.grids[0].rows.length).toBe(4);
- expect(doc.grids[0].rows[0].cells.length).toBe(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/web/src/doc/index.ts b/web/src/doc/index.ts
index a58faa6..ae221f0 100644
--- a/web/src/doc/index.ts
+++ b/web/src/doc/index.ts
@@ -1,110 +1,125 @@
-export abstract class DocOp {
- abstract key: string;
+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 class CreateGridOp extends DocOp {
- gridId: string = crypto.randomUUID();
- rows = 4;
- key = this.gridId;
- 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 default class Doc {
- readonly ops: DocOp[];
-
- readonly opsByKey: Map<string, readonly DocOp[]>;
- readonly opsByType: Map<Function, readonly DocOp[]>;
-
- constructor(ops: DocOp[]) {
- this.ops = ops;
-
- const opsByKey = new Map();
- const opsByType = new Map();
-
- for (const op of ops) {
- const maybeKeyOps = opsByKey.get(op.key);
-
- if (maybeKeyOps) maybeKeyOps.push(op);
- else opsByKey.set(op.key, [op]);
+export function defaultDoc(): Doc {
+ const ops = [createGrid()];
+ return { ops };
+}
- const maybeTypeOps = opsByType.get(op.constructor);
+export function apply(doc: Doc, ...ops: Op[]): Doc {
+ return produce(doc, (doc) => {
+ doc.ops.push(...ops);
+ });
+}
- if (maybeTypeOps) maybeTypeOps.push(op);
- else opsByType.set(op.constructor, [op]);
- }
+export type DocIndex = Immutable<{
+ opsByType: Map<Op["type"], Op[]>;
+}>;
- this.opsByKey = opsByKey;
- this.opsByType = opsByType;
- }
+export function indexDoc(doc: Doc): DocIndex {
+ const opsByType = new Map();
- static default(): Doc {
- return new Doc([new CreateGridOp()]);
+ for (const op of doc.ops) {
+ opsByType.set(op.type, [...(opsByType.get(op.type) ?? []), op]);
}
- get grids(): Grid[] {
- const ops = this.opsByType.get(CreateGridOp) ?? [];
- return ops.map((op) => new Grid(this, op as CreateGridOp));
- }
+ return { opsByType };
}
-export class Grid {
- readonly doc: Doc;
- readonly id: string;
- readonly rows: readonly Row[];
+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 }>[];
+}
- constructor(doc: Doc, createOp: CreateGridOp) {
- this.doc = doc;
- this.id = createOp.gridId;
+export type Grid = Immutable<{ id: string; rows: Row[] }>;
- const rows: Row[] = [];
+export type Row = Immutable<{ index: number; cells: Cell[] }>;
- for (let rowIndex = 0; rowIndex < createOp.rows; rowIndex++) {
- rows.push(new Row(doc, this.id, rowIndex));
- }
+export type Cell = Immutable<{}>;
- this.rows = rows;
- }
+export function realizeGrids(doc: Doc): Grid[] {
+ const index = indexDoc(doc);
+ const createGridOps = getOpsByType(index, "createGrid");
+ return createGridOps.map((op) => realizeGrid(doc, index, op));
}
-export class Row {
- readonly doc: Doc;
- readonly gridId: string;
- readonly index: number;
- readonly cells: readonly Cell[];
-
- constructor(doc: Doc, gridId: string, index: number) {
- this.doc = doc;
- this.gridId = gridId;
- this.index = index;
-
- const createGridOp = doc.opsByType
- .get(CreateGridOp)
- ?.find((op) => op.key === gridId)! as CreateGridOp;
-
- const cells: Cell[] = [];
-
- for (
- let cellIndex = 0;
- cellIndex < createGridOp.baseCellsPerRow;
- cellIndex++
- ) {
- cells.push(new Cell(doc, gridId, index, cellIndex));
+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 });
}
- this.cells = cells;
+ 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 });
}
-}
-export class Cell {
- readonly doc: Doc;
- readonly gridId: string;
- readonly rowIndex: number;
- readonly index: number;
-
- constructor(doc: Doc, gridId: string, rowIndex: number, index: number) {
- this.doc = doc;
- this.gridId = gridId;
- this.rowIndex = rowIndex;
- this.index = index;
- }
+ return {
+ id: createOp.gridId,
+ rows,
+ };
}