diff options
| author | Josh Kingsley <josh@joshkingsley.me> | 2025-11-23 19:27:57 +0200 |
|---|---|---|
| committer | Josh Kingsley <josh@joshkingsley.me> | 2025-11-23 19:27:57 +0200 |
| commit | 602145c956bb594ca0d0e10601cc4ad1a71cf70d (patch) | |
| tree | d9f9980bd2054cff5819d01379f5c1c55f8eb66d /packages/web/src/doc/index.ts | |
| parent | c2a6efb1b761014a90d90373cad47a14054af40b (diff) | |
feat: integrate web and doc packages
Diffstat (limited to 'packages/web/src/doc/index.ts')
| -rw-r--r-- | packages/web/src/doc/index.ts | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/packages/web/src/doc/index.ts b/packages/web/src/doc/index.ts new file mode 100644 index 0000000..ae221f0 --- /dev/null +++ b/packages/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, + }; +} |
