diff options
Diffstat (limited to 'web/src')
| -rw-r--r-- | web/src/components/app/index.ts | 4 | ||||
| -rw-r--r-- | web/src/components/grid/drawGrid.ts | 30 | ||||
| -rw-r--r-- | web/src/components/grid/index.css | 2 | ||||
| -rw-r--r-- | web/src/components/grid/index.ts | 26 | ||||
| -rw-r--r-- | web/src/components/grid/renderGrid.ts | 127 | ||||
| -rw-r--r-- | web/src/index.css | 1 | ||||
| -rw-r--r-- | web/src/index.ts | 24 | ||||
| -rw-r--r-- | web/src/renderGrid.ts | 64 | ||||
| -rw-r--r-- | web/src/types.ts | 7 |
9 files changed, 207 insertions, 78 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 }; +} diff --git a/web/src/index.css b/web/src/index.css index f578562..ba2a6a7 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,6 +1,7 @@ @import "open-color"; body { + margin: 0; background: var(--oc-gray-9); color: var(--oc-white); font-weight: normal; diff --git a/web/src/index.ts b/web/src/index.ts index a32aaf1..fbbf37c 100644 --- a/web/src/index.ts +++ b/web/src/index.ts @@ -1,13 +1,24 @@ -import { Doc } from "./types"; +import Ratio from "./math/Ratio"; +import { Cell, Doc, Grid } from "./types"; function defaultDoc(): Doc { - const defaultCells = Array(16).map(() => ({ value: "1" })); + const defaultCells: Cell[] = Array.from({ length: 16 }, () => ({ + widthRatio: new Ratio(1, 16), + })); return { grids: [ { + id: window.crypto.randomUUID(), baseCellSize: 48, - parts: [{ rows: Array(4).map(() => ({ cells: [...defaultCells] })) }], + baseCellWidthRatio: new Ratio(1, 16), + parts: [ + { + rows: Array.from({ length: 4 }, () => ({ + cells: [...defaultCells], + })), + }, + ], }, ], }; @@ -15,8 +26,11 @@ function defaultDoc(): Doc { export default class Notive { doc: Doc = defaultDoc(); + gridsById = Object.fromEntries(this.doc.grids.map((grid) => [grid.id, grid])); + + getGrid(id: string): Grid | undefined { + return this.gridsById[id]; + } } window.notive = new Notive(); - -window.dispatchEvent(new CustomEvent("ntv:initialized")); diff --git a/web/src/renderGrid.ts b/web/src/renderGrid.ts deleted file mode 100644 index 476876b..0000000 --- a/web/src/renderGrid.ts +++ /dev/null @@ -1,64 +0,0 @@ -import Rect from "./math/Rect"; -import { Cell, CellRef, Grid, Row, RowRef } from "./types"; - -export interface RenderedCell extends Cell { - cellRef: CellRef; - rect: Rect; -} - -export interface RenderedRow { - rowRef: RowRef; - rect: Rect; - renderedCells: RenderedCell[]; -} - -export interface RenderedGrid extends Grid { - rect: Rect; - renderedRows: RenderedRow[]; -} - -function renderCell(grid: Grid, row: Row, cell: Cell): RenderedCell {} - -function renderRow(grid: Grid, row: Row): RenderedRow { - let topLeftX = 0; - - const renderedCells = row.cells.map((cell, cellIndex) => { - const renderedCell = renderCell(grid, row, cell); - topLeftX = renderedCell.rect.bottomRight.y; - return renderedCell; - }); -} - -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 renderedRow = renderRow(grid, row); - - 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 }; -} diff --git a/web/src/types.ts b/web/src/types.ts index df421d7..008d1ee 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -2,22 +2,23 @@ import Ratio from "./math/Ratio"; export interface Cell { value?: string; + widthRatio: Ratio; } export interface Row { - cells: [Cell, ...Cell[]]; + cells: Cell[]; } export interface Part { title?: string; - rows: [Row, ...Row[]]; + rows: Row[]; } export interface Grid { id: string; baseCellSize: number; baseCellWidthRatio: Ratio; - parts: [Part, ...Part[]]; + parts: Part[]; } export interface Doc { |
