summaryrefslogtreecommitdiff
path: root/web/src/components
diff options
context:
space:
mode:
authorJosh Kingsley <josh@joshkingsley.me>2025-10-25 22:05:04 +0300
committerJosh Kingsley <josh@joshkingsley.me>2025-10-25 22:10:04 +0300
commit1b8d05bf83d7bd9ab425852f519ea81bdc379444 (patch)
treea2555ce10f3c607c6809d020ba4d31fa3c05c7fb /web/src/components
parent5404a95c15e176d25728bf1a319ddb9828b23625 (diff)
feat(web): render and draw grid
Diffstat (limited to 'web/src/components')
-rw-r--r--web/src/components/app/index.ts4
-rw-r--r--web/src/components/grid/drawGrid.ts30
-rw-r--r--web/src/components/grid/index.css2
-rw-r--r--web/src/components/grid/index.ts26
-rw-r--r--web/src/components/grid/renderGrid.ts127
5 files changed, 183 insertions, 6 deletions
diff --git a/web/src/components/app/index.ts b/web/src/components/app/index.ts
index 5967a46..2782e22 100644
--- a/web/src/components/app/index.ts
+++ b/web/src/components/app/index.ts
@@ -4,8 +4,8 @@ import "./index.css";
class NotiveAppElement extends HTMLElement {
connectedCallback() {
this.append(
- ...window.notive.doc.grids.map((_grid) => {
- return ntvGrid();
+ ...window.notive.doc.grids.map((grid) => {
+ return ntvGrid({ gridId: grid.id });
}),
);
}
diff --git a/web/src/components/grid/drawGrid.ts b/web/src/components/grid/drawGrid.ts
new file mode 100644
index 0000000..6284693
--- /dev/null
+++ b/web/src/components/grid/drawGrid.ts
@@ -0,0 +1,30 @@
+import { RenderedGrid } from "./renderGrid";
+import colors from "open-color";
+
+export default function drawGrid(
+ ctx: CanvasRenderingContext2D,
+ grid: RenderedGrid,
+) {
+ ctx.clearRect(0, 0, grid.rect.width, grid.rect.height);
+
+ ctx.fillStyle = colors.gray[8];
+ ctx.fillRect(0, 0, grid.rect.width, grid.rect.height);
+
+ ctx.strokeStyle = colors.gray[7];
+
+ grid.renderedRows.forEach((row, renderedRowIndex) => {
+ const isLastRow = renderedRowIndex === grid.renderedRows.length - 1;
+
+ row.renderedCells.forEach((cell, cellIndex) => {
+ const { topLeft, width, height } = cell.rect;
+ const isLastCell = cellIndex === row.renderedCells.length - 1;
+
+ ctx.strokeRect(
+ topLeft.x + 0.5,
+ topLeft.y + 0.5,
+ isLastCell ? width - 1 : width,
+ isLastRow ? height - 1 : height,
+ );
+ });
+ });
+}
diff --git a/web/src/components/grid/index.css b/web/src/components/grid/index.css
index 296c155..0fad720 100644
--- a/web/src/components/grid/index.css
+++ b/web/src/components/grid/index.css
@@ -1,8 +1,8 @@
ntv-grid {
display: block;
+ padding: 1.5rem;
}
ntv-grid > canvas {
display: block;
- width: 100%;
}
diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts
index 18bf75a..829a511 100644
--- a/web/src/components/grid/index.ts
+++ b/web/src/components/grid/index.ts
@@ -1,11 +1,27 @@
import h, { type CreateElement } from "../../html";
+import renderGrid from "./renderGrid";
+import drawGrid from "./drawGrid";
import "./index.css";
-import colors from "open-color";
class NotiveGridElement extends HTMLElement {
+ #gridId!: string;
+
+ get gridId() {
+ return this.#gridId;
+ }
+
+ set gridId(val: string) {
+ this.#gridId = val;
+ this.setAttribute("grid-id", val);
+ }
+
canvasEl: HTMLCanvasElement = h.canvas();
connectedCallback() {
+ if (!this.gridId) {
+ throw new Error("ntv-grid requries gridId attribute");
+ }
+
this.append(this.canvasEl);
this.draw();
}
@@ -13,8 +29,12 @@ class NotiveGridElement extends HTMLElement {
draw() {
const ctx = this.canvasEl.getContext("2d");
if (!ctx) throw new Error("Unable to get canvas context");
- ctx.fillStyle = colors.gray[8];
- ctx.fillRect(0, 0, this.canvasEl.width, this.canvasEl.height);
+ const grid = window.notive.getGrid(this.gridId);
+ if (!grid) return;
+ const renderedGrid = renderGrid(grid);
+ this.canvasEl.setAttribute("width", renderedGrid.rect.width + "px");
+ this.canvasEl.setAttribute("height", renderedGrid.rect.height + "px");
+ drawGrid(ctx, renderedGrid);
}
}
diff --git a/web/src/components/grid/renderGrid.ts b/web/src/components/grid/renderGrid.ts
new file mode 100644
index 0000000..5666f66
--- /dev/null
+++ b/web/src/components/grid/renderGrid.ts
@@ -0,0 +1,127 @@
+import Ratio from "../../math/Ratio";
+import Rect from "../../math/Rect";
+import { Cell, CellRef, Grid, Row, RowRef } from "./types";
+
+export interface RenderedCell extends Cell {
+ cellRef: CellRef;
+ renderedRowIndex: number;
+ rect: Rect;
+}
+
+export interface RenderedRow {
+ rowRef: RowRef;
+ rect: Rect;
+ renderedCells: RenderedCell[];
+}
+
+export interface RenderedGrid extends Grid {
+ rect: Rect;
+ renderedRows: RenderedRow[];
+}
+
+function renderCell(
+ grid: Grid,
+ cell: Cell,
+ cellRef: CellRef,
+ renderedRowIndex: number,
+ topLeftX: number,
+ topLeftY: number,
+): RenderedCell {
+ const width = cell.widthRatio
+ .divideRatio(grid.baseCellWidthRatio)
+ .multiplyRatio(Ratio.fromInteger(grid.baseCellSize))
+ .toNumber();
+
+ const rect = new Rect(topLeftX, topLeftY, width, grid.baseCellSize);
+
+ return { ...cell, cellRef, rect, renderedRowIndex };
+}
+
+function renderRow(
+ grid: Grid,
+ row: Row,
+ rowRef: RowRef,
+ renderedRowIndex: number,
+ topLeftY: number,
+): RenderedRow {
+ if (row.cells.length === 0) {
+ return {
+ ...row,
+ rowRef,
+ rect: new Rect(0, topLeftY, 0, 0),
+ renderedCells: [],
+ };
+ }
+
+ let topLeftX = 0;
+
+ const renderedCells = row.cells.map((cell, cellIndex) => {
+ const cellRef = { ...rowRef, cellIndex };
+
+ const renderedCell = renderCell(
+ grid,
+ cell,
+ cellRef,
+ renderedRowIndex,
+ topLeftX,
+ topLeftY,
+ );
+
+ topLeftX = renderedCell.rect.bottomRight.x;
+
+ return renderedCell;
+ });
+
+ const { topLeft } = renderedCells[0].rect;
+ const { bottomRight } = renderedCells.at(-1)!.rect;
+
+ const rect = new Rect(
+ topLeft.x,
+ topLeft.y,
+ bottomRight.x - topLeft.x,
+ bottomRight.y - topLeft.y,
+ );
+
+ return { ...row, renderedCells, rect, rowRef };
+}
+
+function renderRows(grid: Grid): RenderedRow[] {
+ const renderedRows: RenderedRow[] = [];
+
+ let partIndex = 0;
+ let rowIndex = 0;
+ let topLeftY = 0;
+ let renderedRowIndex = 0;
+
+ while (true) {
+ if (!grid.parts[partIndex]?.rows[rowIndex]) break;
+
+ const row = grid.parts[partIndex].rows[rowIndex];
+ const rowRef = { partIndex, rowIndex };
+ const renderedRow = renderRow(
+ grid,
+ row,
+ rowRef,
+ renderedRowIndex,
+ topLeftY,
+ );
+
+ topLeftY = renderedRow.rect.bottomRight.y;
+ renderedRows.push(renderedRow);
+
+ if (!grid.parts[++partIndex]) {
+ partIndex = 0;
+ rowIndex++;
+ }
+
+ renderedRowIndex++;
+ }
+
+ return renderedRows;
+}
+
+export default function renderGrid(grid: Grid) {
+ const renderedRows = renderRows(grid);
+ const rect = renderedRows[0].rect.extend(renderedRows.at(-1)!.rect);
+ return { ...grid, rect, renderedRows };
+}