From ca7edbcc6b201df7b5070cde205e66f576948475 Mon Sep 17 00:00:00 2001 From: Josh Kingsley Date: Sun, 26 Oct 2025 21:04:24 +0200 Subject: feat(web): add subdivideSelection method --- web/src/components/grid/index.ts | 2 +- web/src/components/toolbar/index.ts | 39 +++++++++++++++++++++++++++++++++--- web/src/index.ts | 33 +++++++++++++++++++++++++----- web/src/selection.ts | 19 ++++++++++++++++++ web/src/types.ts | 40 +++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 9 deletions(-) diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts index fbb9c60..5301afb 100644 --- a/web/src/components/grid/index.ts +++ b/web/src/components/grid/index.ts @@ -27,7 +27,7 @@ class NotiveGridElement extends HTMLElement { throw new Error("ntv-grid requries gridId attribute"); } - window.addEventListener("ntv:selection-changed", () => this.draw()); + window.addEventListener("ntv:selectionchange", () => this.draw()); this.canvasEl.addEventListener( "mousedown", diff --git a/web/src/components/toolbar/index.ts b/web/src/components/toolbar/index.ts index e672a48..666114b 100644 --- a/web/src/components/toolbar/index.ts +++ b/web/src/components/toolbar/index.ts @@ -33,7 +33,7 @@ class NotiveToolbarElement extends HTMLElement { connectedCallback() { this.#render(); - window.addEventListener("ntv:selection-changed", () => { + window.addEventListener("ntv:selectionchange", () => { if (window.notive.pendingSelection) { this.#subdivisionsInputEl.disabled = true; this.#subdivisionsInputEl.value = ""; @@ -51,6 +51,12 @@ class NotiveToolbarElement extends HTMLElement { this.#subdivisionsInputEl.disabled = false; this.#subdivisionsInputEl.value = subdivisionsCount.toString(); }); + + this.#subdivisionsInputEl.addEventListener("change", () => { + window.notive.subdivideSelection( + parseInt(this.#subdivisionsInputEl.value), + ); + }); } #render() { @@ -61,9 +67,36 @@ class NotiveToolbarElement extends HTMLElement { h.button({ dataset: { variant: "menu" } }, "Format"), ), h.section( - h.button({ dataset: { variant: "icon" } }, "-"), + h.button( + { + dataset: { variant: "icon" }, + onclick: () => { + const subdivisions = Math.max( + 1, + parseInt(this.#subdivisionsInputEl.value) - 1, + ); + + this.#subdivisionsInputEl.value = subdivisions.toString(); + window.notive.subdivideSelection(subdivisions); + }, + }, + "-", + ), this.#subdivisionsInputEl, - h.button({ dataset: { variant: "icon" } }, "+"), + h.button( + { + dataset: { variant: "icon" }, + + onclick: () => { + const subdivisions = + parseInt(this.#subdivisionsInputEl.value) + 1; + + this.#subdivisionsInputEl.value = subdivisions.toString(); + window.notive.subdivideSelection(subdivisions); + }, + }, + "+", + ), ), ); } diff --git a/web/src/index.ts b/web/src/index.ts index 6ab61c9..a6f0ac7 100644 --- a/web/src/index.ts +++ b/web/src/index.ts @@ -1,8 +1,10 @@ import Ratio from "./math/Ratio"; -import { Cell, CellRef, Doc, Grid } from "./types"; +import { Cell, CellRef, Doc, Grid, mapRowsInRange } from "./types"; import { ActiveCellSelection, Selection } from "./selection"; -import renderGrid, { RenderedGrid } from "./components/grid/renderGrid"; -import cellAtCoord from "./components/grid/cellAtCoord"; +import renderGrid, { + getRenderedCell, + RenderedGrid, +} from "./components/grid/renderGrid"; function defaultDoc(): Doc { const defaultCells: Cell[] = Array.from({ length: 16 }, () => ({ @@ -29,7 +31,12 @@ function defaultDoc(): Doc { baseCellWidthRatio: new Ratio(1, 16), parts: [ { - rows: Array.from({ length: 4 }, () => ({ + rows: Array.from({ length: 2 }, () => ({ + cells: [...defaultCells], + })), + }, + { + rows: Array.from({ length: 2 }, () => ({ cells: [...defaultCells], })), }, @@ -92,7 +99,23 @@ export default class Notive { } #dispatchSelectionChanged() { - window.dispatchEvent(new CustomEvent("ntv:selection-changed")); + window.dispatchEvent(new CustomEvent("ntv:selectionchange")); + } + + subdivideSelection(subdivisions: number) { + const selection = this.selection; + + if (!selection) return; + + const newDoc = mapRowsInRange( + this.doc, + selection.gridId, + selection.startCellRef(), + selection.endCellRef(), + (row, rowRef) => { + return row; + }, + ); } } diff --git a/web/src/selection.ts b/web/src/selection.ts index 294db52..abaf8b5 100644 --- a/web/src/selection.ts +++ b/web/src/selection.ts @@ -17,6 +17,9 @@ export abstract class Selection { abstract extend(cellRef: CellRef): Selection; abstract getSelectedCells(grid: RenderedGrid): RenderedCell[][]; + + abstract startCellRef(): CellRef; + abstract endCellRef(): CellRef; } export class ActiveCellSelection extends Selection { @@ -34,6 +37,14 @@ export class ActiveCellSelection extends Selection { getSelectedCells(grid: RenderedGrid): RenderedCell[][] { return [[getRenderedCell(grid, this.activeCellRef)!]]; } + + startCellRef(): CellRef { + return this.activeCellRef; + } + + endCellRef(): CellRef { + return this.activeCellRef; + } } export type CellRange = [CellRef, CellRef]; @@ -101,4 +112,12 @@ export class RangeSelection extends Selection { return row.renderedCells.slice(firstCellIndex, lastCellIndex + 1); }); } + + startCellRef(): CellRef { + return this.range[0]; + } + + endCellRef(): CellRef { + return this.range[1]; + } } diff --git a/web/src/types.ts b/web/src/types.ts index d932389..8b1b650 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -44,3 +44,43 @@ export function cellRefEquals(a: CellRef, b: CellRef): boolean { a.cellIndex === b.cellIndex ); } + +export function mapRowsInRange( + doc: Doc, + gridId: string, + startRef: CellRef, + endRef: CellRef, + mapFn: (row: Row, ref: RowRef) => Row, +): Doc { + const firstPartIndex = Math.min(startRef.partIndex, endRef.partIndex); + const lastPartIndex = Math.max(startRef.partIndex, endRef.partIndex); + const firstRowIndex = Math.min(startRef.rowIndex, endRef.rowIndex); + const lastRowIndex = Math.max(startRef.rowIndex, endRef.rowIndex); + + return { + ...doc, + grids: doc.grids.map((grid) => { + if (grid.id !== gridId) return grid; + + return { + ...grid, + parts: grid.parts.map((part, partIndex) => { + if (partIndex < firstPartIndex || partIndex > lastPartIndex) { + return part; + } + + return { + ...part, + rows: part.rows.map((row, rowIndex) => { + if (rowIndex < firstRowIndex || rowIndex > lastRowIndex) { + return row; + } + + return mapFn(row, { partIndex, rowIndex }); + }), + }; + }), + }; + }), + }; +} -- cgit v1.2.3