diff options
| author | Josh Kingsley <josh@joshkingsley.me> | 2025-11-11 23:35:04 +0200 |
|---|---|---|
| committer | Josh Kingsley <josh@joshkingsley.me> | 2025-11-11 23:35:04 +0200 |
| commit | 5e08e76c30c230aef3a5f1e21d14fa59a84b3c88 (patch) | |
| tree | 5a5564f4abafc8088f9acf811e9aaa27a523eb30 /web/src | |
| parent | 6ea86b1b56aebbae7edeb37b01d7bf5cd145bf60 (diff) | |
feat(web): add op-based doc
Diffstat (limited to 'web/src')
| -rw-r--r-- | web/src/doc/index.test.ts | 15 | ||||
| -rw-r--r-- | web/src/doc/index.ts | 110 |
2 files changed, 125 insertions, 0 deletions
diff --git a/web/src/doc/index.test.ts b/web/src/doc/index.test.ts new file mode 100644 index 0000000..331d0a5 --- /dev/null +++ b/web/src/doc/index.test.ts @@ -0,0 +1,15 @@ +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); + }); + }); +}); diff --git a/web/src/doc/index.ts b/web/src/doc/index.ts new file mode 100644 index 0000000..a58faa6 --- /dev/null +++ b/web/src/doc/index.ts @@ -0,0 +1,110 @@ +export abstract class DocOp { + abstract key: string; +} + +export class CreateGridOp extends DocOp { + gridId: string = crypto.randomUUID(); + rows = 4; + key = this.gridId; + baseCellsPerRow = 16; +} + +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]); + + const maybeTypeOps = opsByType.get(op.constructor); + + if (maybeTypeOps) maybeTypeOps.push(op); + else opsByType.set(op.constructor, [op]); + } + + this.opsByKey = opsByKey; + this.opsByType = opsByType; + } + + static default(): Doc { + return new Doc([new CreateGridOp()]); + } + + get grids(): Grid[] { + const ops = this.opsByType.get(CreateGridOp) ?? []; + return ops.map((op) => new Grid(this, op as CreateGridOp)); + } +} + +export class Grid { + readonly doc: Doc; + readonly id: string; + readonly rows: readonly Row[]; + + constructor(doc: Doc, createOp: CreateGridOp) { + this.doc = doc; + this.id = createOp.gridId; + + const rows: Row[] = []; + + for (let rowIndex = 0; rowIndex < createOp.rows; rowIndex++) { + rows.push(new Row(doc, this.id, rowIndex)); + } + + this.rows = rows; + } +} + +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)); + } + + this.cells = 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; + } +} |
