summaryrefslogtreecommitdiff
path: root/packages/web/src/doc/index.ts
blob: ae221f090a390ed8c24108babe5f51f492eac234 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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,
  };
}