diff options
| author | Josh Kingsley <josh@joshkingsley.me> | 2025-10-26 19:41:01 +0200 |
|---|---|---|
| committer | Josh Kingsley <josh@joshkingsley.me> | 2025-10-26 19:41:01 +0200 |
| commit | af8cf348feb8e6bb4bda4a277b06a0f41ff890d9 (patch) | |
| tree | ce7df39a6edb7e6df3d3fb0d903972391333a272 | |
| parent | 43ba019bc0d3af502b806169dad5fcbbfc87d2b7 (diff) | |
feat(web): show selected subdivisions
| -rw-r--r-- | web/src/components/grid/drawGrid.ts | 11 | ||||
| -rw-r--r-- | web/src/components/grid/renderGrid.ts | 19 | ||||
| -rw-r--r-- | web/src/components/toolbar/index.ts | 53 | ||||
| -rw-r--r-- | web/src/math/Ratio.ts | 25 | ||||
| -rw-r--r-- | web/src/selection.ts | 52 | ||||
| -rw-r--r-- | web/src/types.ts | 1 |
6 files changed, 143 insertions, 18 deletions
diff --git a/web/src/components/grid/drawGrid.ts b/web/src/components/grid/drawGrid.ts index 5ea17b6..1b94254 100644 --- a/web/src/components/grid/drawGrid.ts +++ b/web/src/components/grid/drawGrid.ts @@ -1,6 +1,6 @@ import { RangeSelection, Selection } from "../../selection"; import { CellRef } from "../../types"; -import { RenderedCell, RenderedGrid } from "./renderGrid"; +import { getRenderedCell, RenderedCell, RenderedGrid } from "./renderGrid"; interface GridColors { bgFill: string; @@ -74,15 +74,6 @@ function strokeGridLines( }); } -function getRenderedCell( - grid: RenderedGrid, - cellRef: CellRef, -): RenderedCell | undefined { - const rowsPerPart = grid.renderedRows.length / grid.parts.length; - const renderedRowIndex = cellRef.partIndex * rowsPerPart + cellRef.rowIndex; - return grid.renderedRows[renderedRowIndex]?.renderedCells[cellRef.cellIndex]; -} - function strokeActiveCell( ctx: CanvasRenderingContext2D, colors: GridColors, diff --git a/web/src/components/grid/renderGrid.ts b/web/src/components/grid/renderGrid.ts index 7ef8813..e6a2c54 100644 --- a/web/src/components/grid/renderGrid.ts +++ b/web/src/components/grid/renderGrid.ts @@ -6,6 +6,8 @@ export interface RenderedCell extends Cell { cellRef: CellRef; renderedRowIndex: number; rect: Rect; + startRatio: Ratio; + endRatio: Ratio; } export interface RenderedRow { @@ -26,6 +28,7 @@ function renderCell( renderedRowIndex: number, topLeftX: number, topLeftY: number, + startRatio: Ratio, ): RenderedCell { const width = cell.widthRatio .divideRatio(grid.baseCellWidthRatio) @@ -34,7 +37,9 @@ function renderCell( const rect = new Rect(topLeftX, topLeftY, width, grid.baseCellSize); - return { ...cell, cellRef, rect, renderedRowIndex }; + const endRatio = startRatio.add(cell.widthRatio); + + return { ...cell, cellRef, rect, renderedRowIndex, startRatio, endRatio }; } function renderRow( @@ -54,6 +59,7 @@ function renderRow( } let topLeftX = 0; + let startRatio = Ratio.fromInteger(0); const renderedCells = row.cells.map((cell, cellIndex) => { const cellRef = { ...rowRef, cellIndex }; @@ -65,9 +71,11 @@ function renderRow( renderedRowIndex, topLeftX, topLeftY, + startRatio, ); topLeftX = renderedCell.rect.bottomRight.x; + startRatio = renderedCell.endRatio; return renderedCell; }); @@ -125,3 +133,12 @@ export default function renderGrid(grid: 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 rowsPerPart = grid.renderedRows.length / grid.parts.length; + const renderedRowIndex = cellRef.partIndex * rowsPerPart + cellRef.rowIndex; + return grid.renderedRows[renderedRowIndex]?.renderedCells[cellRef.cellIndex]; +} diff --git a/web/src/components/toolbar/index.ts b/web/src/components/toolbar/index.ts index d844a69..e672a48 100644 --- a/web/src/components/toolbar/index.ts +++ b/web/src/components/toolbar/index.ts @@ -1,8 +1,59 @@ import h, { CreateElement } from "../../html"; +import { ActiveCellSelection, RangeSelection } from "../../selection"; +import { RenderedGrid } from "../grid/renderGrid"; import "./index.css"; +function getSelectedSubdivisionsCount(): number | undefined { + const selection = window.notive.selection; + + if (!selection) return; + + if (selection instanceof ActiveCellSelection) { + return 1; + } + + if (!(selection instanceof RangeSelection)) return; + + const grid = window.notive.getGrid(selection.gridId); + + if (!grid) return; + + const selectedCells = selection.getSelectedCells(grid); + + return Math.min(...selectedCells.map((cells) => cells.length)); +} + class NotiveToolbarElement extends HTMLElement { + #subdivisionsInputEl: HTMLInputElement = h.input({ + title: "Subdivisions", + placeholder: "-", + disabled: true, + }); + connectedCallback() { + this.#render(); + + window.addEventListener("ntv:selection-changed", () => { + if (window.notive.pendingSelection) { + this.#subdivisionsInputEl.disabled = true; + this.#subdivisionsInputEl.value = ""; + return; + } + + const subdivisionsCount = getSelectedSubdivisionsCount(); + + if (!subdivisionsCount) { + this.#subdivisionsInputEl.disabled = true; + this.#subdivisionsInputEl.value = ""; + return; + } + + this.#subdivisionsInputEl.disabled = false; + this.#subdivisionsInputEl.value = subdivisionsCount.toString(); + }); + } + + #render() { this.append( h.section( h.button({ dataset: { variant: "menu" } }, "File"), @@ -11,7 +62,7 @@ class NotiveToolbarElement extends HTMLElement { ), h.section( h.button({ dataset: { variant: "icon" } }, "-"), - h.input({ type: "text", value: "1" }), + this.#subdivisionsInputEl, h.button({ dataset: { variant: "icon" } }, "+"), ), ); diff --git a/web/src/math/Ratio.ts b/web/src/math/Ratio.ts index 4973ff4..d8e1149 100644 --- a/web/src/math/Ratio.ts +++ b/web/src/math/Ratio.ts @@ -3,15 +3,15 @@ export type RatioData = [numerator: number, denominator: number]; /** Representation of a ratio for performing fractional artithmetic. */ export default class Ratio { - private readonly _numerator: number; - private readonly _denominator: number; + readonly #numerator: number; + readonly #denominator: number; get numerator(): number { - return this._numerator; + return this.#numerator; } get denominator(): number { - return this._denominator; + return this.#denominator; } constructor(numerator: number, denominator: number) { @@ -25,8 +25,8 @@ export default class Ratio { throw new RangeError("Ratio demnominator cannot be zero"); } - this._numerator = numerator; - this._denominator = denominator; + this.#numerator = numerator; + this.#denominator = denominator; } multiplyRatio(other: Ratio): Ratio { @@ -43,6 +43,19 @@ export default class Ratio { ); } + add(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; + } + toNumber(): number { return this.numerator / this.denominator; } diff --git a/web/src/selection.ts b/web/src/selection.ts index 3d18417..294db52 100644 --- a/web/src/selection.ts +++ b/web/src/selection.ts @@ -1,3 +1,8 @@ +import { + getRenderedCell, + RenderedCell, + RenderedGrid, +} from "./components/grid/renderGrid"; import { CellRef, cellRefEquals } from "./types"; export abstract class Selection { @@ -10,6 +15,8 @@ export abstract class Selection { } abstract extend(cellRef: CellRef): Selection; + + abstract getSelectedCells(grid: RenderedGrid): RenderedCell[][]; } export class ActiveCellSelection extends Selection { @@ -23,6 +30,10 @@ export class ActiveCellSelection extends Selection { cellRef, ]); } + + getSelectedCells(grid: RenderedGrid): RenderedCell[][] { + return [[getRenderedCell(grid, this.activeCellRef)!]]; + } } export type CellRange = [CellRef, CellRef]; @@ -49,4 +60,45 @@ export class RangeSelection extends Selection { cellRef, ]); } + + getSelectedCells(grid: RenderedGrid): RenderedCell[][] { + const startCell = getRenderedCell(grid, this.range[0]); + const endCell = getRenderedCell(grid, this.range[1]); + + if (!startCell || !endCell) return []; + + const firstRowIndex = Math.min( + startCell.renderedRowIndex, + endCell.renderedRowIndex, + ); + + const lastRowIndex = Math.max( + startCell.renderedRowIndex, + endCell.renderedRowIndex, + ); + + const startRatio = + startCell.startRatio.compare(endCell.startRatio) <= 0 + ? startCell.startRatio + : endCell.startRatio; + + const endRatio = + startCell.endRatio.compare(endCell.endRatio) >= 0 + ? startCell.endRatio + : endCell.endRatio; + + return grid.renderedRows + .slice(firstRowIndex, lastRowIndex + 1) + .map((row) => { + const firstCellIndex = row.renderedCells.findIndex( + (cell) => cell.startRatio.compare(startRatio) >= 0, + ); + + const lastCellIndex = row.renderedCells.findLastIndex( + (cell) => cell.endRatio.compare(endRatio) <= 0, + ); + + return row.renderedCells.slice(firstCellIndex, lastCellIndex + 1); + }); + } } diff --git a/web/src/types.ts b/web/src/types.ts index 9b7a51a..d932389 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -30,6 +30,7 @@ export interface RowRef { rowIndex: number; } +// TODO Should probably have a gridId export interface CellRef { partIndex: number; rowIndex: number; |
