summaryrefslogtreecommitdiff
path: root/web/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/components')
-rw-r--r--web/src/components/app/index.ts47
-rw-r--r--web/src/components/grid/index.ts11
-rw-r--r--web/src/components/grid/selection.ts5
-rw-r--r--web/src/components/toolbar/index.css2
-rw-r--r--web/src/components/toolbar/index.ts62
5 files changed, 114 insertions, 13 deletions
diff --git a/web/src/components/app/index.ts b/web/src/components/app/index.ts
index aa7c738..a2c0c9d 100644
--- a/web/src/components/app/index.ts
+++ b/web/src/components/app/index.ts
@@ -1,5 +1,10 @@
+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";
@@ -15,9 +20,21 @@ export class NotiveAppElement extends NotiveElement {
#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() {
@@ -27,10 +44,36 @@ export class NotiveAppElement extends NotiveElement {
});
}
+ #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(
- ntvToolbar(),
-
+ this.#toolbar,
...this.doc.grids.map((grid) =>
ntvGrid({
grid: renderGrid(grid),
diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts
index 78bb14e..3189409 100644
--- a/web/src/components/grid/index.ts
+++ b/web/src/components/grid/index.ts
@@ -12,7 +12,16 @@ import { extendSelection, GridSelection } from "./selection";
export class NotiveGridElement extends NotiveElement {
#internals: ElementInternals = this.attachInternals();
- grid?: RenderedGrid;
+ #grid?: RenderedGrid;
+
+ get grid(): RenderedGrid | undefined {
+ return this.#grid;
+ }
+
+ set grid(grid: RenderedGrid | undefined) {
+ this.#grid = grid;
+ this.draw();
+ }
#selection?: GridSelection;
diff --git a/web/src/components/grid/selection.ts b/web/src/components/grid/selection.ts
index a24bbf5..517f8ae 100644
--- a/web/src/components/grid/selection.ts
+++ b/web/src/components/grid/selection.ts
@@ -1,4 +1,5 @@
import { CellRef, cellRefEquals } from "../../types";
+import { RenderedGrid } from "./renderGrid";
export type CellRange = [start: CellRef, end: CellRef];
@@ -21,3 +22,7 @@ export function extendSelection(
return { ...selection, range: [selection.activeCellRef, cellRef] };
}
+
+export function getSelectionRange(selection: GridSelection): CellRange {
+ return selection.range ?? [selection.activeCellRef, selection.activeCellRef];
+}
diff --git a/web/src/components/toolbar/index.css b/web/src/components/toolbar/index.css
index e082f7d..653c326 100644
--- a/web/src/components/toolbar/index.css
+++ b/web/src/components/toolbar/index.css
@@ -18,11 +18,13 @@
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] {
diff --git a/web/src/components/toolbar/index.ts b/web/src/components/toolbar/index.ts
index da4b69d..b8a383d 100644
--- a/web/src/components/toolbar/index.ts
+++ b/web/src/components/toolbar/index.ts
@@ -1,24 +1,66 @@
-import NotiveElement, { customElement } from "../../element";
-import h, { fragment } from "../../html";
+import NotiveElement, { customElement, eventHandler } from "../../element";
+import h from "../../html";
+import { minus16Icon, plus16Icon } from "../icons";
import "./index.css";
-@customElement("ntv-toolbar")
-class NotiveToolbarElement extends NotiveElement {
- connectedCallback() {
- this.append(this.#view());
+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,
});
- #view() {
- return fragment(
+ 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: "" } }, "-"),
+ 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: "" } }, "+"),
+ 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")),
);