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,
};
}
|