From d724cc0bf6ff6d351319e6fb00f5184a04e16ac0 Mon Sep 17 00:00:00 2001 From: Josh Kingsley Date: Mon, 24 Nov 2025 15:46:22 +0200 Subject: chore: improve dev tasks --- packages/web/src/components/app/index.css | 12 - packages/web/src/components/app/index.ts | 93 ------- packages/web/src/components/grid/cellAtCoord.ts | 40 --- packages/web/src/components/grid/drawGrid.ts | 91 ------- packages/web/src/components/grid/drawSelection.ts | 97 -------- packages/web/src/components/grid/excursion.ts | 8 - packages/web/src/components/grid/index.css | 49 ---- packages/web/src/components/grid/index.ts | 276 --------------------- packages/web/src/components/grid/renderGrid.ts | 144 ----------- packages/web/src/components/grid/selection.ts | 28 --- packages/web/src/components/icons/index.ts | 19 -- packages/web/src/components/icons/svgs/minus16.svg | 3 - packages/web/src/components/icons/svgs/plus16.svg | 3 - packages/web/src/components/index.ts | 3 - packages/web/src/components/toolbar/index.css | 48 ---- packages/web/src/components/toolbar/index.ts | 70 ------ 16 files changed, 984 deletions(-) delete mode 100644 packages/web/src/components/app/index.css delete mode 100644 packages/web/src/components/app/index.ts delete mode 100644 packages/web/src/components/grid/cellAtCoord.ts delete mode 100644 packages/web/src/components/grid/drawGrid.ts delete mode 100644 packages/web/src/components/grid/drawSelection.ts delete mode 100644 packages/web/src/components/grid/excursion.ts delete mode 100644 packages/web/src/components/grid/index.css delete mode 100644 packages/web/src/components/grid/index.ts delete mode 100644 packages/web/src/components/grid/renderGrid.ts delete mode 100644 packages/web/src/components/grid/selection.ts delete mode 100644 packages/web/src/components/icons/index.ts delete mode 100644 packages/web/src/components/icons/svgs/minus16.svg delete mode 100644 packages/web/src/components/icons/svgs/plus16.svg delete mode 100644 packages/web/src/components/index.ts delete mode 100644 packages/web/src/components/toolbar/index.css delete mode 100644 packages/web/src/components/toolbar/index.ts (limited to 'packages/web/src/components') diff --git a/packages/web/src/components/app/index.css b/packages/web/src/components/app/index.css deleted file mode 100644 index aaf2ced..0000000 --- a/packages/web/src/components/app/index.css +++ /dev/null @@ -1,12 +0,0 @@ -ntv-app { - display: block; - padding: 1.5rem; -} - -ntv-app > ntv-toolbar { - margin-bottom: 1.5rem; -} - -ntv-app > ntv-grid + ntv-grid { - margin-top: 1.5rem; -} diff --git a/packages/web/src/components/app/index.ts b/packages/web/src/components/app/index.ts deleted file mode 100644 index a2c0c9d..0000000 --- a/packages/web/src/components/app/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { produce } from "immer"; -import defaultDoc from "../../defaultDoc"; -import NotiveElement, { customElement } from "../../element"; -import { - changeSelectedSubdivisions, - getSelectedSubdivisionsCount, -} from "../../grid"; -import { Doc } from "../../types"; -import ntvGrid, { NotiveGridElement } from "../grid"; -import renderGrid from "../grid/renderGrid"; -import { GridSelection } from "../grid/selection"; -import ntvToolbar from "../toolbar"; -import "./index.css"; - -@customElement("ntv-app") -export class NotiveAppElement extends NotiveElement { - doc: Doc = defaultDoc(); - - #selectedGridId?: string; - #selection?: GridSelection; - - setSelection(gridId: string, selection: GridSelection) { - const grid = this.doc.grids.find((grid) => grid.id === gridId); - if (!grid) throw new Error("Invalid grid ID"); - - this.#selectedGridId = gridId; - this.#selection = selection; - this.#updateGridSelections(); - - this.#toolbar.subdivisions = getSelectedSubdivisionsCount(grid, selection); - } - - clearSelection() { - this.#selectedGridId = undefined; - this.#selection = undefined; - this.#updateGridSelections(); - this.#toolbar.subdivisions = undefined; - } - - #updateGridSelections() { - this.querySelectorAll("ntv-grid").forEach((grid) => { - grid.selection = - this.#selectedGridId === grid.grid?.id ? this.#selection : undefined; - }); - } - - #toolbar = ntvToolbar({ - onsubdivisionschange: ({ subdivisions }) => { - if (!subdivisions) return; - - const gridId = this.#selectedGridId; - const selection = this.#selection; - - if (!gridId || !selection) return; - - const gridIndex = this.doc.grids.findIndex((grid) => grid.id === gridId); - - this.doc = produce(this.doc, (doc) => { - doc.grids[gridIndex] = changeSelectedSubdivisions( - this.doc.grids[gridIndex], - selection, - subdivisions, - ); - }); - - this.querySelector( - `ntv-grid[data-grid-id="${gridId}"]`, - )!.grid = renderGrid(this.doc.grids[gridIndex]); - - this.clearSelection(); - }, - }); - - connectedCallback() { - this.append( - this.#toolbar, - ...this.doc.grids.map((grid) => - ntvGrid({ - grid: renderGrid(grid), - dataset: { gridId: grid.id }, - ongridselectionchange: (event) => { - this.setSelection(grid.id, event.selection); - }, - oncellchange: (event) => { - console.log(event); - }, - }), - ), - ); - } -} - -export default NotiveAppElement.makeFactory(); diff --git a/packages/web/src/components/grid/cellAtCoord.ts b/packages/web/src/components/grid/cellAtCoord.ts deleted file mode 100644 index dd594a4..0000000 --- a/packages/web/src/components/grid/cellAtCoord.ts +++ /dev/null @@ -1,40 +0,0 @@ -import Coord from "../../math/Coord"; -import { CellRef } from "../../types"; -import { RenderedGrid, RenderedRow } from "./renderGrid"; - -function rowAtCoord(grid: RenderedGrid, coord: Coord): RenderedRow | undefined { - if (coord.y <= grid.rect.topLeft.y) { - return grid.renderedRows[0]; - } - - if (coord.y >= grid.rect.bottomRight.y) { - return grid.renderedRows.at(-1); - } - - return grid.renderedRows.find((row) => - row.rect.verticallyContainsCoord(coord), - ); -} - -export default function cellAtCoord( - grid: RenderedGrid, - x: number, - y: number, -): CellRef | undefined { - const coord = new Coord(x, y); - const row = rowAtCoord(grid, coord); - - if (!row) return; - - if (x <= row.rect.topLeft.x) { - return row.renderedCells[0]?.cellRef; - } - - if (x >= row.rect.bottomRight.x) { - return row.renderedCells.at(-1)?.cellRef; - } - - return row.renderedCells.find((cell) => - cell.rect.horizontallyContainsCoord(coord), - )?.cellRef; -} diff --git a/packages/web/src/components/grid/drawGrid.ts b/packages/web/src/components/grid/drawGrid.ts deleted file mode 100644 index da83c8e..0000000 --- a/packages/web/src/components/grid/drawGrid.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { RangeSelection, Selection } from "../../selection"; -import { CellRef } from "../../types"; -import { getRenderedCell, RenderedCell, RenderedGrid } from "./renderGrid"; - -export interface GridStyles { - bgFill: string; - borderStroke: string; - cellStroke: string; - cellValueFont: string; - cellValueLineHeight: string; -} - -function excursion(ctx: CanvasRenderingContext2D, f: () => void) { - ctx.save(); - f(); - ctx.restore(); -} - -function fillBackground( - ctx: CanvasRenderingContext2D, - styles: GridStyles, - grid: RenderedGrid, -) { - ctx.clearRect(0, 0, grid.rect.width, grid.rect.height); - ctx.fillStyle = styles.bgFill; - ctx.fillRect(0, 0, grid.rect.width, grid.rect.height); -} - -function strokeGrid( - ctx: CanvasRenderingContext2D, - styles: GridStyles, - grid: RenderedGrid, -) { - ctx.strokeStyle = styles.borderStroke; - ctx.strokeRect(0.5, 0.5, grid.rect.width - 1, grid.rect.height - 1); -} - -function strokeGridLines( - ctx: CanvasRenderingContext2D, - styles: GridStyles, - grid: RenderedGrid, -) { - ctx.strokeStyle = styles.cellStroke; - - 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, - ); - }); - }); -} - -function drawCellValues( - ctx: CanvasRenderingContext2D, - styles: GridStyles, - grid: RenderedGrid, -) { - grid.renderedRows.forEach((row) => - row.renderedCells.forEach((cell) => { - if (!cell.value) return; - ctx.fillStyle = "white"; - ctx.textAlign = "center"; - ctx.font = styles.cellValueFont; - ctx.fillText( - cell.value, - cell.rect.center.x, - cell.rect.center.y + parseInt(styles.cellValueLineHeight) / 4, - ); - }), - ); -} - -export default function drawGrid( - ctx: CanvasRenderingContext2D, - styles: GridStyles, - grid: RenderedGrid, -) { - excursion(ctx, () => fillBackground(ctx, styles, grid)); - excursion(ctx, () => strokeGridLines(ctx, styles, grid)); - excursion(ctx, () => strokeGrid(ctx, styles, grid)); - excursion(ctx, () => drawCellValues(ctx, styles, grid)); -} diff --git a/packages/web/src/components/grid/drawSelection.ts b/packages/web/src/components/grid/drawSelection.ts deleted file mode 100644 index 1b8c2ed..0000000 --- a/packages/web/src/components/grid/drawSelection.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { CellRef } from "../../types"; -import excursion from "./excursion"; -import { getRenderedCell, RenderedCell, RenderedGrid } from "./renderGrid"; -import { GridSelection } from "./selection"; - -export interface SelectionStyles { - activeCellStroke: string; - selectionRangeFill: string; - selectionRangeStroke: string; -} - -function strokeActiveCell( - ctx: CanvasRenderingContext2D, - styles: SelectionStyles, - grid: RenderedGrid, - cell: RenderedCell, -) { - excursion(ctx, () => { - const isLastCell = cell.rect.bottomRight.x === grid.rect.bottomRight.x; - const isLastRow = cell.rect.bottomRight.y === grid.rect.bottomRight.y; - - ctx.strokeStyle = styles.activeCellStroke; - ctx.lineWidth = 2; - - ctx.strokeRect( - cell.rect.topLeft.x + 1, - cell.rect.topLeft.y + 1, - isLastCell ? cell.rect.width - 2 : cell.rect.width - 1, - isLastRow ? cell.rect.height - 2 : cell.rect.height - 1, - ); - }); -} - -function drawCellRange( - ctx: CanvasRenderingContext2D, - styles: SelectionStyles, - grid: RenderedGrid, - start: CellRef, - end: CellRef, - { stroke }: { stroke: boolean }, -) { - excursion(ctx, () => { - const startCell = getRenderedCell(grid, start); - const endCell = getRenderedCell(grid, end); - - if (!startCell || !endCell) return; - - const rect = startCell.rect.extend(endCell.rect); - - const isRightEdge = rect.bottomRight.x === grid.rect.bottomRight.x; - const isBottomEdge = rect.bottomRight.y === grid.rect.bottomRight.y; - - ctx.fillStyle = styles.selectionRangeFill; - - ctx.fillRect( - rect.topLeft.x + 1, - rect.topLeft.y + 1, - isRightEdge ? rect.width - 2 : rect.width - 1, - isBottomEdge ? rect.height - 2 : rect.height - 1, - ); - - if (!stroke) return; - - ctx.strokeStyle = styles.selectionRangeStroke; - - ctx.strokeRect( - rect.topLeft.x + 0.5, - rect.topLeft.y + 0.5, - isRightEdge ? rect.width - 1 : rect.width, - isBottomEdge ? rect.height - 1 : rect.height, - ); - }); -} - -export default function drawSelection( - ctx: CanvasRenderingContext2D, - styles: SelectionStyles, - grid: RenderedGrid, - selection: GridSelection | undefined, - { pending }: { pending: boolean }, -) { - ctx.clearRect(0, 0, grid.rect.width, grid.rect.height); - - if (!selection) return; - - const activeCell = getRenderedCell(grid, selection.activeCellRef); - - if (!activeCell) return; - - if (selection.range) { - drawCellRange(ctx, styles, grid, selection.range[0], selection.range[1], { - stroke: !pending, - }); - } - - strokeActiveCell(ctx, styles, grid, activeCell); -} diff --git a/packages/web/src/components/grid/excursion.ts b/packages/web/src/components/grid/excursion.ts deleted file mode 100644 index 7752df1..0000000 --- a/packages/web/src/components/grid/excursion.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default function excursion( - ctx: CanvasRenderingContext2D, - f: () => void, -) { - ctx.save(); - f(); - ctx.restore(); -} diff --git a/packages/web/src/components/grid/index.css b/packages/web/src/components/grid/index.css deleted file mode 100644 index c29f55d..0000000 --- a/packages/web/src/components/grid/index.css +++ /dev/null @@ -1,49 +0,0 @@ -@layer components { - ntv-grid { - display: block; - position: relative; - - --grid-bg-fill: var(--color-neutral-900); - --grid-border-stroke: var(--color-neutral-700); - --grid-cell-stroke: var(--color-neutral-800); - --grid-active-cell-stroke: var(--color-green-400); - --grid-selection-range-fill: color-mix( - in oklab, - var(--color-green-400) 10%, - transparent - ); - --grid-selection-range-stroke: var(--color-green-400); - font-size: 14px; - } - - ntv-grid > canvas { - display: block; - } - - ntv-grid > canvas[data-selection] { - position: absolute; - top: 0; - left: 0; - pointer-events: none; - } - - :has(ntv-grid:state(selecting)) - > ntv-grid:not(:state(selecting)) - > canvas[data-selection] { - display: none; - } - - ntv-grid input[data-edit] { - position: absolute; - vertical-align: baseline; - background: var(--color-neutral-800); - padding-right: 1px; - padding-bottom: 1px; - color: white; - text-align: center; - } - - ntv-grid input[data-edit]:focus-visible { - outline: none; - } -} diff --git a/packages/web/src/components/grid/index.ts b/packages/web/src/components/grid/index.ts deleted file mode 100644 index 3189409..0000000 --- a/packages/web/src/components/grid/index.ts +++ /dev/null @@ -1,276 +0,0 @@ -import NotiveElement, { customElement, eventHandler } from "../../element"; -import h from "../../html"; -import { CellRef } from "../../types"; -import cellAtCoord from "./cellAtCoord"; -import drawGrid, { GridStyles } from "./drawGrid"; -import drawSelection, { SelectionStyles } from "./drawSelection"; -import "./index.css"; -import { getRenderedCell, RenderedGrid } from "./renderGrid"; -import { extendSelection, GridSelection } from "./selection"; - -@customElement("ntv-grid") -export class NotiveGridElement extends NotiveElement { - #internals: ElementInternals = this.attachInternals(); - - #grid?: RenderedGrid; - - get grid(): RenderedGrid | undefined { - return this.#grid; - } - - set grid(grid: RenderedGrid | undefined) { - this.#grid = grid; - this.draw(); - } - - #selection?: GridSelection; - - get selection() { - return this.#selection; - } - - set selection(selection: GridSelection | undefined) { - this.#selection = selection; - this.drawSelection(); - } - - @eventHandler("ntv:grid:selectionchange") - ongridselectionchange?: (event: GridSelectionChangeEvent) => any; - - @eventHandler("ntv:grid:cellchange") - oncellchange?: (event: GridCellChangeEvent) => any; - - canvas: HTMLCanvasElement = h.canvas({ - onmousedown: (event) => { - if (event.button !== 0) return; - if (!this.grid) return; - const cellRef = this.#mouseEventCellRef(event); - if (!cellRef) return; - this.startSelecting(cellRef); - }, - ondblclick: (event) => { - if (!this.grid) return; - const cellRef = this.#mouseEventCellRef(event); - if (!cellRef) return; - this.startEditing(cellRef); - }, - }); - - selectionCanvas: HTMLCanvasElement = h.canvas({ - dataset: { selection: "true" }, - }); - - connectedCallback() { - this.append(this.canvas, this.selectionCanvas); - this.draw(); - this.drawSelection(); - } - - draw() { - if (!this.grid) return; - - const ctx = this.canvas.getContext("2d"); - - if (!ctx) throw new Error("Unable to get canvas context"); - - this.canvas.setAttribute("width", this.grid.rect.width + "px"); - this.canvas.setAttribute("height", this.grid.rect.height + "px"); - - drawGrid(ctx, this.getGridStyles(), this.grid); - } - - drawSelection() { - if (!this.grid) return; - - const ctx = this.selectionCanvas.getContext("2d"); - - if (!ctx) throw new Error("Unable to get canvas context"); - - this.selectionCanvas.setAttribute("width", this.grid.rect.width + "px"); - this.selectionCanvas.setAttribute("height", this.grid.rect.height + "px"); - - drawSelection( - ctx, - this.getSelectionStyles(), - this.grid, - this.#pendingSelection ?? this.selection, - { - pending: !!this.#pendingSelection, - }, - ); - } - - getGridStyles(): GridStyles { - const style = window.getComputedStyle(this); - const val = (k: string) => style.getPropertyValue(k); - - return { - bgFill: val("--grid-bg-fill"), - borderStroke: val("--grid-border-stroke"), - cellStroke: val("--grid-cell-stroke"), - cellValueFont: val("font"), - cellValueLineHeight: val("line-height"), - }; - } - - getSelectionStyles(): SelectionStyles { - const style = window.getComputedStyle(this); - const val = (k: string) => style.getPropertyValue(k); - - return { - activeCellStroke: val("--grid-active-cell-stroke"), - selectionRangeFill: val("--grid-selection-range-fill"), - selectionRangeStroke: val("--grid-selection-range-stroke"), - }; - } - - #pendingSelection?: GridSelection; - #selectionAbortController?: AbortController; - - startSelecting(cellRef: CellRef) { - if (!this.grid || this.#pendingSelection) return; - - this.#internals.states.add("selecting"); - - this.#selectionAbortController = new AbortController(); - const { signal } = this.#selectionAbortController; - - window.addEventListener( - "mousemove", - (event) => { - const cellRef = this.#mouseEventCellRef(event); - if (!cellRef) return; - this.#pendingSelection = extendSelection( - this.#pendingSelection, - cellRef, - ); - this.drawSelection(); - }, - { signal }, - ); - - window.addEventListener("mouseup", () => this.#finishSelecting(), { - signal, - }); - - window.addEventListener( - "keydown", - (event) => { - event.preventDefault(); - if (event.key === "Escape") { - this.#pendingSelection = undefined; - this.#finishSelecting(); - } - }, - { signal }, - ); - - this.#pendingSelection = extendSelection(undefined, cellRef); - this.drawSelection(); - } - - #finishSelecting() { - this.#selectionAbortController?.abort(); - this.#selectionAbortController = undefined; - this.#internals.states.delete("selecting"); - if (this.#pendingSelection) { - this.dispatchEvent(new GridSelectionChangeEvent(this.#pendingSelection)); - } - this.#pendingSelection = undefined; - this.drawSelection(); - } - - #mouseEventCellRef( - this: NotiveGridElement, - event: MouseEvent, - ): CellRef | undefined { - if (!this.grid) return; - const clientRect = this.canvas.getBoundingClientRect(); - const x = event.x - clientRect.x; - const y = event.y - clientRect.y; - return cellAtCoord(this.grid, x, y); - } - - #editingCellRef?: CellRef; - - #editInput: HTMLInputElement = h.input({ - dataset: { edit: "true" }, - onblur: () => this.#finishEditing(), - onkeydown: (event) => { - switch (event.key) { - case "Enter": - this.#finishEditing(); - break; - - case "Escape": - this.#cancelEditing(); - break; - } - }, - }); - - startEditing(cellRef: CellRef) { - if (!this.grid) return; - - const cell = getRenderedCell(this.grid, cellRef); - - if (!cell) return; - - this.#editingCellRef = cellRef; - - this.append(this.#editInput); - - this.#editInput.value = cell.value || ""; - - Object.assign(this.#editInput.style, { - left: cell.rect.topLeft.x + 2 + "px", - top: cell.rect.topLeft.y + 2 + "px", - width: cell.rect.width - 3 + "px", - height: cell.rect.height - 3 + "px", - }); - - this.#editInput.focus(); - } - - #cancelEditing() { - this.#editInput.remove(); - } - - #finishEditing() { - this.#editInput.remove(); - - if (!this.grid || !this.#editingCellRef) return; - - this.dispatchEvent( - new GridCellChangeEvent(this.#editingCellRef, this.#editInput.value), - ); - } -} - -export default NotiveGridElement.makeFactory(); - -export class GridSelectionChangeEvent extends Event { - static readonly TYPE = "ntv:grid:selectionchange"; - - constructor(public selection: GridSelection) { - super(GridSelectionChangeEvent.TYPE); - } -} - -export class GridCellChangeEvent extends Event { - static readonly TYPE = "ntv:grid:cellchange"; - - constructor( - public cellRef: CellRef, - public value: string | undefined, - ) { - super(GridCellChangeEvent.TYPE); - } -} - -declare global { - interface HTMLElementEventMap { - [GridSelectionChangeEvent.TYPE]: GridSelectionChangeEvent; - [GridCellChangeEvent.TYPE]: GridCellChangeEvent; - } -} diff --git a/packages/web/src/components/grid/renderGrid.ts b/packages/web/src/components/grid/renderGrid.ts deleted file mode 100644 index 89938ec..0000000 --- a/packages/web/src/components/grid/renderGrid.ts +++ /dev/null @@ -1,144 +0,0 @@ -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; - startRatio: Ratio; - endRatio: Ratio; -} - -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, - startRatio: Ratio, -): RenderedCell { - const width = cell.widthRatio - .divideRatio(grid.baseCellWidthRatio) - .multiplyRatio(Ratio.fromInteger(grid.baseCellSize)) - .toNumber(); - - const rect = new Rect(topLeftX, topLeftY, width, grid.baseCellSize); - - const endRatio = startRatio.add(cell.widthRatio); - - return { ...cell, cellRef, rect, renderedRowIndex, startRatio, endRatio }; -} - -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; - let startRatio = Ratio.fromInteger(0); - - const renderedCells = row.cells.map((cell, cellIndex) => { - const cellRef = { ...rowRef, cellIndex }; - - const renderedCell = renderCell( - grid, - cell, - cellRef, - renderedRowIndex, - topLeftX, - topLeftY, - startRatio, - ); - - topLeftX = renderedCell.rect.bottomRight.x; - startRatio = renderedCell.endRatio; - - 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 }; -} - -export function getRenderedCell( - grid: RenderedGrid, - cellRef: CellRef, -): RenderedCell | undefined { - const renderedRowIndex = - cellRef.rowIndex * grid.parts.length + cellRef.partIndex; - return grid.renderedRows[renderedRowIndex]?.renderedCells[cellRef.cellIndex]; -} diff --git a/packages/web/src/components/grid/selection.ts b/packages/web/src/components/grid/selection.ts deleted file mode 100644 index 517f8ae..0000000 --- a/packages/web/src/components/grid/selection.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CellRef, cellRefEquals } from "../../types"; -import { RenderedGrid } from "./renderGrid"; - -export type CellRange = [start: CellRef, end: CellRef]; - -export interface GridSelection { - activeCellRef: CellRef; - range?: CellRange; -} - -export function extendSelection( - selection: GridSelection | undefined, - cellRef: CellRef, -): GridSelection { - if (!selection || cellRefEquals(selection.activeCellRef, cellRef)) { - return { activeCellRef: cellRef }; - } - - if (selection.range) { - return { ...selection, range: [selection.range[0], cellRef] }; - } - - return { ...selection, range: [selection.activeCellRef, cellRef] }; -} - -export function getSelectionRange(selection: GridSelection): CellRange { - return selection.range ?? [selection.activeCellRef, selection.activeCellRef]; -} diff --git a/packages/web/src/components/icons/index.ts b/packages/web/src/components/icons/index.ts deleted file mode 100644 index 5731026..0000000 --- a/packages/web/src/components/icons/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import plus16 from "./svgs/plus16.svg?raw"; -import minus16 from "./svgs/minus16.svg?raw"; - -function makeIconFactory(source: string) { - return (attrs?: object): SVGElement => { - const parser = new DOMParser(); - const doc = parser.parseFromString(source, "image/svg+xml"); - const svg = doc.documentElement as unknown as SVGElement; - - if (attrs) { - Object.entries(attrs).forEach(([k, v]) => svg.setAttribute(k, v)); - } - - return svg; - }; -} - -export const plus16Icon = makeIconFactory(plus16); -export const minus16Icon = makeIconFactory(minus16); diff --git a/packages/web/src/components/icons/svgs/minus16.svg b/packages/web/src/components/icons/svgs/minus16.svg deleted file mode 100644 index d77dcfc..0000000 --- a/packages/web/src/components/icons/svgs/minus16.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/web/src/components/icons/svgs/plus16.svg b/packages/web/src/components/icons/svgs/plus16.svg deleted file mode 100644 index 1d7b023..0000000 --- a/packages/web/src/components/icons/svgs/plus16.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/web/src/components/index.ts b/packages/web/src/components/index.ts deleted file mode 100644 index b7f6f55..0000000 --- a/packages/web/src/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import "./app"; -import "./grid"; -import "./toolbar"; diff --git a/packages/web/src/components/toolbar/index.css b/packages/web/src/components/toolbar/index.css deleted file mode 100644 index 653c326..0000000 --- a/packages/web/src/components/toolbar/index.css +++ /dev/null @@ -1,48 +0,0 @@ -@layer components { - ntv-toolbar { - display: flex; - border-radius: 99999px; - background: var(--color-neutral-900); - width: min-content; - } - - ntv-toolbar > section { - display: flex; - gap: 0.25rem; - padding: 0.325rem; - } - - ntv-toolbar button { - border-radius: 99999px; - background: var(--color-neutral-800); - padding: 0 0.5rem; - height: 1.25rem; - color: white; - font-weight: 600; - font-size: 0.75rem; - } - - ntv-toolbar button:hover { - background: var(--color-green-400); - color: var(--color-neutral-900); - } - - ntv-toolbar button[data-icon] { - display: flex; - justify-content: center; - align-items: center; - aspect-ratio: 1; - height: 1.25rem; - } - - ntv-toolbar input { - border: 1px solid var(--color-neutral-700); - border-radius: 4px; - background: var(--color-neutral-900); - width: 2.5rem; - height: 1.25rem; - color: white; - font-size: 0.75rem; - text-align: center; - } -} diff --git a/packages/web/src/components/toolbar/index.ts b/packages/web/src/components/toolbar/index.ts deleted file mode 100644 index b8a383d..0000000 --- a/packages/web/src/components/toolbar/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import NotiveElement, { customElement, eventHandler } from "../../element"; -import h from "../../html"; -import { minus16Icon, plus16Icon } from "../icons"; -import "./index.css"; - -export class SubdivisionsChangeEvent extends Event { - static readonly TYPE = "ntv:toolbar:subdivisionschange"; - - constructor(public subdivisions: number | undefined) { - super(SubdivisionsChangeEvent.TYPE); - } -} - -@customElement("ntv-toolbar") -class NotiveToolbarElement extends NotiveElement { - #subdivisionsInputEl: HTMLInputElement = h.input({ - title: "Subdivisions", - disabled: true, - }); - - get subdivisions(): number | undefined { - if (this.#subdivisionsInputEl.value === "") return; - return parseInt(this.#subdivisionsInputEl.value); - } - - set subdivisions(n: number | undefined) { - const m = n && Math.max(n, 1); - this.#subdivisionsInputEl.value = m === undefined ? "" : m.toString(); - } - - @eventHandler(SubdivisionsChangeEvent.TYPE) - onsubdivisionschange?: (event: SubdivisionsChangeEvent) => any; - - connectedCallback() { - this.append( - h.section( - h.button( - { - dataset: { icon: "" }, - onclick: () => { - if (!this.subdivisions) return; - this.subdivisions = this.subdivisions - 1; - this.dispatchEvent( - new SubdivisionsChangeEvent(this.subdivisions), - ); - }, - }, - h.span(minus16Icon()), - ), - this.#subdivisionsInputEl, - h.button( - { - dataset: { icon: "" }, - onclick: () => { - if (!this.subdivisions) return; - this.subdivisions = this.subdivisions + 1; - this.dispatchEvent( - new SubdivisionsChangeEvent(this.subdivisions), - ); - }, - }, - h.span(plus16Icon()), - ), - ), - h.section(h.button("Play")), - ); - } -} - -export default NotiveToolbarElement.makeFactory(); -- cgit v1.2.3