summaryrefslogtreecommitdiff
path: root/packages/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/web/src')
-rw-r--r--packages/web/src/components/app/index.css12
-rw-r--r--packages/web/src/components/app/index.ts93
-rw-r--r--packages/web/src/components/grid/cellAtCoord.ts40
-rw-r--r--packages/web/src/components/grid/drawGrid.ts91
-rw-r--r--packages/web/src/components/grid/drawSelection.ts97
-rw-r--r--packages/web/src/components/grid/excursion.ts8
-rw-r--r--packages/web/src/components/grid/index.css49
-rw-r--r--packages/web/src/components/grid/index.ts276
-rw-r--r--packages/web/src/components/grid/renderGrid.ts144
-rw-r--r--packages/web/src/components/grid/selection.ts28
-rw-r--r--packages/web/src/components/icons/index.ts19
-rw-r--r--packages/web/src/components/icons/svgs/minus16.svg3
-rw-r--r--packages/web/src/components/icons/svgs/plus16.svg3
-rw-r--r--packages/web/src/components/index.ts3
-rw-r--r--packages/web/src/components/toolbar/index.css48
-rw-r--r--packages/web/src/components/toolbar/index.ts70
-rw-r--r--packages/web/src/defaultDoc.ts42
-rw-r--r--packages/web/src/doc/index.test.ts16
-rw-r--r--packages/web/src/doc/index.ts125
-rw-r--r--packages/web/src/element.ts45
-rw-r--r--packages/web/src/favicon.icobin15406 -> 0 bytes
-rw-r--r--packages/web/src/grid.test.ts45
-rw-r--r--packages/web/src/grid.ts109
-rw-r--r--packages/web/src/html.ts50
-rw-r--r--packages/web/src/index.css10
-rw-r--r--packages/web/src/index.html11
-rw-r--r--packages/web/src/index.ts8
-rw-r--r--packages/web/src/math/Coord.ts23
-rw-r--r--packages/web/src/math/Ratio.test.ts27
-rw-r--r--packages/web/src/math/Ratio.ts105
-rw-r--r--packages/web/src/math/Rect.ts104
-rw-r--r--packages/web/src/math/index.ts3
-rw-r--r--packages/web/src/types.ts55
33 files changed, 0 insertions, 1762 deletions
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<NotiveGridElement>("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<NotiveGridElement>(
- `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 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
- <path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
-</svg>
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 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
- <path d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" />
-</svg>
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();
diff --git a/packages/web/src/defaultDoc.ts b/packages/web/src/defaultDoc.ts
deleted file mode 100644
index 0a3fbfb..0000000
--- a/packages/web/src/defaultDoc.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import Ratio from "./math/Ratio";
-import { Cell, Doc } from "./types";
-
-export default function defaultDoc(): Doc {
- const defaultCells: Cell[] = Array.from({ length: 16 }, () => ({
- widthRatio: new Ratio(1, 16),
- }));
-
- return {
- grids: [
- {
- id: globalThis.crypto.randomUUID(),
- baseCellSize: 42,
- baseCellWidthRatio: new Ratio(1, 16),
- parts: [
- {
- rows: Array.from({ length: 4 }, () => ({
- cells: [...defaultCells],
- })),
- },
- ],
- },
- {
- id: globalThis.crypto.randomUUID(),
- baseCellSize: 42,
- baseCellWidthRatio: new Ratio(1, 16),
- parts: [
- {
- rows: Array.from({ length: 2 }, () => ({
- cells: [...defaultCells],
- })),
- },
- {
- rows: Array.from({ length: 2 }, () => ({
- cells: [...defaultCells],
- })),
- },
- ],
- },
- ],
- };
-}
diff --git a/packages/web/src/doc/index.test.ts b/packages/web/src/doc/index.test.ts
deleted file mode 100644
index 5f61398..0000000
--- a/packages/web/src/doc/index.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { expect, test } from "vitest";
-import { apply, defaultDoc, realizeGrids, subdivide } from ".";
-
-test(realizeGrids, () => {
- const doc = defaultDoc();
- const grids = realizeGrids(doc);
-
- expect(grids.length).toBe(1);
- expect(grids[0].rows.length).toBe(4);
- expect(grids[0].rows[0].cells.length).toBe(16);
-
- const doc2 = apply(doc, subdivide(grids[0].id, 0, 0, 3, 3));
- const grids2 = realizeGrids(doc2);
-
- expect(grids2[0].rows[0].cells.length).toBe(15);
-});
diff --git a/packages/web/src/doc/index.ts b/packages/web/src/doc/index.ts
deleted file mode 100644
index ae221f0..0000000
--- a/packages/web/src/doc/index.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-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,
- };
-}
diff --git a/packages/web/src/element.ts b/packages/web/src/element.ts
deleted file mode 100644
index 6299d2f..0000000
--- a/packages/web/src/element.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { createElement, type CreateElement } from "./html";
-
-export default class NotiveElement extends HTMLElement {
- static makeFactory<T extends NotiveElement>(this: {
- new (): T;
- }): CreateElement<T>;
-
- static makeFactory(): any {
- throw new Error(
- "Missing makeFactory implementation. Did you forget to use @customElement?",
- );
- }
-}
-
-export function customElement(tagName: string) {
- return function (_value: unknown, context: ClassDecoratorContext) {
- context.addInitializer(function () {
- window.customElements.define(tagName, this as typeof NotiveElement);
- (this as typeof NotiveElement).makeFactory = () =>
- ((...args: any[]) => createElement(tagName, ...args)) as CreateElement<any>;
- });
- };
-}
-
-export function eventHandler(eventName: string) {
- return function (_value: unknown, context: ClassFieldDecoratorContext) {
- const privateKey = Symbol(context.name.toString());
-
- context.addInitializer(function () {
- Object.defineProperty(this, context.name, {
- get() {
- return this[privateKey];
- },
- set(handler) {
- const oldHandler = this[privateKey];
- if (oldHandler) this.removeEventListener(eventName, oldHandler);
- this[privateKey] = handler;
- if (handler) this.addEventListener(eventName, handler);
- },
- enumerable: true,
- configurable: true,
- });
- });
- };
-}
diff --git a/packages/web/src/favicon.ico b/packages/web/src/favicon.ico
deleted file mode 100644
index c10cfe9..0000000
--- a/packages/web/src/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/packages/web/src/grid.test.ts b/packages/web/src/grid.test.ts
deleted file mode 100644
index 50c0626..0000000
--- a/packages/web/src/grid.test.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { expect, test } from "vitest";
-import defaultDoc from "./defaultDoc";
-import renderGrid from "./components/grid/renderGrid";
-import { changeSelectedSubdivisions } from "./grid";
-import { GridSelection } from "./components/grid/selection";
-
-test("foo", () => {
- const doc = defaultDoc();
- const grid = doc.grids[1];
-
- const selection: GridSelection = {
- activeCellRef: { partIndex: 0, rowIndex: 0, cellIndex: 0 },
- range: [
- { partIndex: 0, rowIndex: 0, cellIndex: 0 },
- { partIndex: 0, rowIndex: 0, cellIndex: 3 },
- ],
- };
-
- const newGrid = changeSelectedSubdivisions(grid, selection, 3);
- const renderedGrid = renderGrid(newGrid);
-
- expect(
- renderedGrid.renderedRows.map((row) => row.renderedCells.length),
- ).toStrictEqual([15, 16, 16, 16]);
-
- expect(
- newGrid.parts[0].rows[0].cells.map((cell) => cell.widthRatio.toData()),
- ).toStrictEqual([
- [1, 12],
- [1, 12],
- [1, 12],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- [1, 16],
- ]);
-});
diff --git a/packages/web/src/grid.ts b/packages/web/src/grid.ts
deleted file mode 100644
index e849803..0000000
--- a/packages/web/src/grid.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { produce } from "immer";
-import renderGrid, { getRenderedCell } from "./components/grid/renderGrid";
-import { getSelectionRange, GridSelection } from "./components/grid/selection";
-import Ratio from "./math/Ratio";
-import { Cell, Grid, renderedRowIndexToRef } from "./types";
-
-export function getSelectedSubdivisionsCount(
- grid: Grid,
- selection: GridSelection,
-): number | undefined {
- const renderedGrid = renderGrid(grid);
-
- const [startCellRef, endCellRef] = getSelectionRange(selection);
- const startCell = getRenderedCell(renderedGrid, startCellRef);
- const endCell = getRenderedCell(renderedGrid, endCellRef);
-
- if (!startCell || !endCell) throw new Error("Invalid cell refs");
-
- const startRenderedRowIndex = Math.min(
- startCell.renderedRowIndex,
- endCell.renderedRowIndex,
- );
-
- const endRenderedRowIndex = Math.max(
- startCell.renderedRowIndex,
- endCell.renderedRowIndex,
- );
-
- const startRatio = Ratio.min(startCell.startRatio, endCell.startRatio);
- const endRatio = Ratio.max(startCell.endRatio, endCell.endRatio);
-
- return Math.min(
- ...renderedGrid.renderedRows
- .slice(startRenderedRowIndex, endRenderedRowIndex + 1)
- .map((row) => {
- const startCellIndex = row.renderedCells.findIndex((cell) =>
- cell.startRatio.equals(startRatio),
- );
-
- const endCellIndex = row.renderedCells.findLastIndex((cell) =>
- cell.endRatio.equals(endRatio),
- );
-
- return endCellIndex - startCellIndex + 1;
- }),
- );
-}
-
-export function changeSelectedSubdivisions(
- grid: Grid,
- selection: GridSelection,
- subdivisions: number,
-): Grid {
- const renderedGrid = renderGrid(grid);
- const [startCellRef, endCellRef] = getSelectionRange(selection);
- const startCell = getRenderedCell(renderedGrid, startCellRef);
- const endCell = getRenderedCell(renderedGrid, endCellRef);
- if (!startCell || !endCell) throw new Error("Invalid cell refs");
-
- const startRenderedRowIndex = Math.min(
- startCell.renderedRowIndex,
- endCell.renderedRowIndex,
- );
-
- const endRenderedRowIndex = Math.max(
- startCell.renderedRowIndex,
- endCell.renderedRowIndex,
- );
-
- const startRatio = Ratio.min(startCell.startRatio, endCell.startRatio);
- const endRatio = Ratio.max(startCell.endRatio, endCell.endRatio);
- const selectedWidthRatio = endRatio.subtract(startRatio);
- const widthRatio = selectedWidthRatio.divideRatio(
- Ratio.fromInteger(subdivisions),
- );
-
- return produce(grid, (draft) => {
- for (
- let renderedRowIndex = startRenderedRowIndex;
- renderedRowIndex <= endRenderedRowIndex;
- renderedRowIndex++
- ) {
- const renderedRow = renderedGrid.renderedRows[renderedRowIndex];
-
- const startCellIndex = renderedRow.renderedCells.findIndex((cell) =>
- cell.startRatio.equals(startRatio),
- );
-
- const endCellIndex = renderedRow.renderedCells.findLastIndex((cell) =>
- cell.endRatio.equals(endRatio),
- );
-
- const { partIndex, rowIndex } = renderedRowIndexToRef(
- grid,
- renderedRowIndex,
- );
-
- const row = draft.parts[partIndex].rows[rowIndex];
- const previousCells = row.cells.slice(0, startCellIndex);
- const nextCells = row.cells.slice(endCellIndex + 1);
-
- const newCells: Cell[] = Array.from({ length: subdivisions }, () => ({
- widthRatio,
- }));
-
- row.cells = [...previousCells, ...newCells, ...nextCells];
- }
- });
-}
diff --git a/packages/web/src/html.ts b/packages/web/src/html.ts
deleted file mode 100644
index 3fccda3..0000000
--- a/packages/web/src/html.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-export function createElement<T extends HTMLElement>(
- tagName: string,
- ...children: (Node | string)[]
-): T;
-
-export function createElement<T extends HTMLElement>(
- tagName: string,
- attrs: Partial<T>,
- ...children: (Node | string)[]
-): T;
-
-export function createElement(tagName: string, ...args: any[]) {
- const el = document.createElement(tagName);
-
- if (args[0]?.constructor === Object) {
- const { dataset, style, ...attrs } = args.shift();
- Object.assign(el, attrs);
- if (dataset) Object.assign(el.dataset, dataset);
- if (style) Object.assign(el.style, style);
- }
-
- el.append(...args.flat());
-
- return el;
-}
-
-export type CreateElement<T extends HTMLElement> = {
- (...children: (Node | string)[]): T;
- (attrs: Partial<T>, ...children: (Node | string)[]): T;
-};
-
-type ElementCreator = {
- [K in keyof HTMLElementTagNameMap]: CreateElement<HTMLElementTagNameMap[K]>;
-};
-
-const h = new Proxy({} as ElementCreator, {
- get:
- (_, tagName: string) =>
- (...args: any[]) => {
- return createElement(tagName, ...args);
- },
-});
-
-export default h;
-
-export function fragment(...children: (Node | string)[]): DocumentFragment {
- const fragment = document.createDocumentFragment();
- fragment.append(...children);
- return fragment;
-}
diff --git a/packages/web/src/index.css b/packages/web/src/index.css
deleted file mode 100644
index f100378..0000000
--- a/packages/web/src/index.css
+++ /dev/null
@@ -1,10 +0,0 @@
-@import "tailwindcss";
-
-body {
- background: var(--color-neutral-800);
- user-select: none;
-}
-
-*:focus-visible {
- outline: 2px solid var(--color-green-400);
-}
diff --git a/packages/web/src/index.html b/packages/web/src/index.html
deleted file mode 100644
index 9f8bcbf..0000000
--- a/packages/web/src/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!doctype html>
-<html lang="en">
- <head>
- <meta charset="utf-8" />
- <title>Notive</title>
- <link rel="stylesheet" href="index.css" />
- <link rel="icon" href="favicon.ico" />
- <script type="module" src="index.ts"></script>
- </head>
- <body></body>
-</html>
diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts
deleted file mode 100644
index 857e76a..0000000
--- a/packages/web/src/index.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import ntvApp from "./components/app";
-import { State } from "@notive/doc";
-
-const state = new State();
-state.create_grid();
-console.log(state.to_json());
-
-document.body.append(ntvApp());
diff --git a/packages/web/src/math/Coord.ts b/packages/web/src/math/Coord.ts
deleted file mode 100644
index db7ee6d..0000000
--- a/packages/web/src/math/Coord.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/** A coord on a grid whose origin is in the top left. */
-export default class Coord {
- private readonly _x: number;
- private readonly _y: number;
-
- constructor(x: number, y: number) {
- this._x = x;
- this._y = y;
- }
-
- get x(): number {
- return this._x;
- }
-
- get y(): number {
- return this._y;
- }
-
- /** Get the squared distance of this point from the origin. */
- squaredDistanceFromOrigin(): number {
- return this._x * this._x + this._y * this._y;
- }
-}
diff --git a/packages/web/src/math/Ratio.test.ts b/packages/web/src/math/Ratio.test.ts
deleted file mode 100644
index da6fef2..0000000
--- a/packages/web/src/math/Ratio.test.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { describe, expect, test } from "vitest";
-import Ratio from "./Ratio";
-
-describe(Ratio, () => {
- describe(Ratio.prototype.add, () => {
- test("returns fractions in simplest form", () => {
- const a = Ratio.fromInteger(0);
- const b = new Ratio(1, 4);
-
- const c = a.add(b);
- expect(c.numerator).toBe(1);
- expect(c.denominator).toBe(4);
-
- const d = c.add(b);
- expect(d.numerator).toBe(1);
- expect(d.denominator).toBe(2);
-
- const e = d.add(b);
- expect(e.numerator).toBe(3);
- expect(e.denominator).toBe(4);
-
- const f = e.add(b);
- expect(f.numerator).toBe(1);
- expect(f.denominator).toBe(1);
- });
- });
-});
diff --git a/packages/web/src/math/Ratio.ts b/packages/web/src/math/Ratio.ts
deleted file mode 100644
index e2a1fbf..0000000
--- a/packages/web/src/math/Ratio.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { gcd } from ".";
-
-/** Serializable representation of a ratio. */
-export type RatioData = [numerator: number, denominator: number];
-
-/** Representation of a ratio for performing fractional artithmetic. */
-export default class Ratio {
- readonly #numerator: number;
- readonly #denominator: number;
-
- get numerator(): number {
- return this.#numerator;
- }
-
- get denominator(): number {
- return this.#denominator;
- }
-
- constructor(numerator: number, denominator: number) {
- if (!Number.isInteger(numerator) || !Number.isInteger(denominator)) {
- throw new TypeError(
- `Ratio must have integer parts: ${numerator} / ${denominator}`,
- );
- }
-
- if (denominator === 0) {
- throw new RangeError("Ratio demnominator cannot be zero");
- }
-
- const divisor = gcd(numerator, denominator);
-
- this.#numerator = numerator / divisor;
- this.#denominator = denominator / divisor;
- }
-
- multiplyRatio(other: Ratio): Ratio {
- return new Ratio(
- this.numerator * other.numerator,
- this.denominator * other.denominator,
- );
- }
-
- divideRatio(other: Ratio): Ratio {
- return new Ratio(
- this.numerator * other.denominator,
- this.denominator * other.numerator,
- );
- }
-
- add(other: Ratio): Ratio {
- return new Ratio(
- this.numerator * other.denominator + other.numerator * this.denominator,
- this.denominator * other.denominator,
- );
- }
-
- subtract(other: Ratio): Ratio {
- return new Ratio(
- this.numerator * other.denominator - other.numerator * this.denominator,
- this.denominator * other.denominator,
- );
- }
-
- compare(other: Ratio): number {
- const left = this.numerator * other.denominator;
- const right = other.numerator * this.denominator;
- return left < right ? -1 : left > right ? 1 : 0;
- }
-
- equals(other: Ratio): boolean {
- return this.compare(other) === 0;
- }
-
- toNumber(): number {
- return this.numerator / this.denominator;
- }
-
- toString(): string {
- return `${this.numerator}/${this.denominator}`;
- }
-
- [Symbol.for("nodejs.util.inspect.custom")](): string {
- return `Ratio { ${this.numerator}/${this.denominator} }`;
- }
-
- static fromInteger(n: number): Ratio {
- return new Ratio(n, 1);
- }
-
- toData(): RatioData {
- return [this.numerator, this.denominator];
- }
-
- static fromData(ratio: RatioData): Ratio {
- return new Ratio(ratio[0], ratio[1]);
- }
-
- static min(...ratios: Ratio[]): Ratio {
- return ratios.reduce((a, b) => (a.compare(b) <= 0 ? a : b));
- }
-
- static max(...ratios: Ratio[]): Ratio {
- return ratios.reduce((a, b) => (a.compare(b) >= 0 ? a : b));
- }
-}
diff --git a/packages/web/src/math/Rect.ts b/packages/web/src/math/Rect.ts
deleted file mode 100644
index f52a2f7..0000000
--- a/packages/web/src/math/Rect.ts
+++ /dev/null
@@ -1,104 +0,0 @@
-import Coord from "./Coord";
-
-/** A rectangle on a grid whose origin is in the top left. */
-export default class Rect {
- private readonly _topLeft: Coord;
- private readonly _width: number;
- private readonly _height: number;
-
- constructor(
- topLeftX: number,
- topLeftY: number,
- width: number,
- height: number,
- ) {
- this._topLeft = new Coord(topLeftX, topLeftY);
- this._width = width;
- this._height = height;
- }
-
- /** Width of this rectangle. */
- get width(): number {
- return this._width;
- }
-
- /** Height of this rectangle. */
- get height(): number {
- return this._height;
- }
-
- /** Coord of the top-left point of this rectangle. */
- get topLeft(): Coord {
- return this._topLeft;
- }
-
- /** Coord of the bottom-right point of this rectangle. */
- get bottomRight(): Coord {
- return new Coord(
- this._topLeft.x + this._width,
- this._topLeft.y + this._height,
- );
- }
-
- get center(): Coord {
- return new Coord(
- this.topLeft.x + (this.bottomRight.x - this.topLeft.x) / 2,
- this.topLeft.y + (this.bottomRight.y - this.topLeft.y) / 2,
- );
- }
-
- /** Determine if this rectangle contains the point at `coord`. */
- containsCoord(coord: Coord): boolean {
- return (
- this.topLeft.x <= coord.x &&
- coord.x <= this.bottomRight.x &&
- this.topLeft.y <= coord.y &&
- coord.y <= this.bottomRight.y
- );
- }
-
- verticallyContainsCoord(coord: Coord): boolean {
- return this.topLeft.y <= coord.y && coord.y <= this.bottomRight.y;
- }
-
- horizontallyContainsCoord(coord: Coord): boolean {
- return this.topLeft.x <= coord.x && coord.x <= this.bottomRight.x;
- }
-
- extend(other: Rect): Rect {
- const topLeftX = Math.min(
- this.topLeft.x,
- this.bottomRight.x,
- other.topLeft.x,
- other.bottomRight.x,
- );
-
- const topLeftY = Math.min(
- this.topLeft.y,
- this.bottomRight.y,
- other.topLeft.y,
- other.bottomRight.y,
- );
-
- const bottomRightX = Math.max(
- this.topLeft.x,
- this.bottomRight.x,
- other.topLeft.x,
- other.bottomRight.x,
- );
-
- const bottomRightY = Math.max(
- this.topLeft.y,
- this.bottomRight.y,
- other.topLeft.y,
- other.bottomRight.y,
- );
-
- return new Rect(
- topLeftX,
- topLeftY,
- bottomRightX - topLeftX,
- bottomRightY - topLeftY,
- );
- }
-}
diff --git a/packages/web/src/math/index.ts b/packages/web/src/math/index.ts
deleted file mode 100644
index 70dbb67..0000000
--- a/packages/web/src/math/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export function gcd(a: number, b: number): number {
- return b === 0 ? Math.abs(a) : gcd(b, a % b);
-}
diff --git a/packages/web/src/types.ts b/packages/web/src/types.ts
deleted file mode 100644
index dc26c89..0000000
--- a/packages/web/src/types.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Immutable } from "immer";
-import Ratio from "./math/Ratio";
-
-export type Cell = Immutable<{
- value?: string;
- widthRatio: Ratio;
-}>;
-
-export interface Row {
- cells: Cell[];
-}
-
-export interface Part {
- title?: string;
- rows: Row[];
-}
-
-export interface Grid {
- id: string;
- baseCellSize: number;
- baseCellWidthRatio: Ratio;
- parts: Part[];
-}
-
-export interface Doc {
- grids: Grid[];
-}
-
-export interface RowRef {
- partIndex: number;
- rowIndex: number;
-}
-
-export interface CellRef {
- partIndex: number;
- rowIndex: number;
- cellIndex: number;
-}
-
-export function cellRefEquals(a: CellRef, b: CellRef): boolean {
- return (
- a.partIndex === b.partIndex &&
- a.rowIndex === b.rowIndex &&
- a.cellIndex === b.cellIndex
- );
-}
-
-export function renderedRowIndexToRef(
- grid: Grid,
- renderedRowIndex: number,
-): RowRef {
- const partIndex = renderedRowIndex % grid.parts.length;
- const rowIndex = Math.floor(renderedRowIndex / grid.parts.length);
- return { partIndex, rowIndex };
-}