summaryrefslogtreecommitdiff
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/components/app/index.css3
-rw-r--r--web/src/components/app/index.ts14
-rw-r--r--web/src/components/grid/index.css8
-rw-r--r--web/src/components/grid/index.ts24
-rw-r--r--web/src/components/index.ts2
-rw-r--r--web/src/favicon.icobin0 -> 15406 bytes
-rw-r--r--web/src/global.d.ts9
-rw-r--r--web/src/html.ts26
-rw-r--r--web/src/index.css10
-rw-r--r--web/src/index.html13
-rw-r--r--web/src/index.ts22
-rw-r--r--web/src/math/Coord.ts23
-rw-r--r--web/src/math/Ratio.ts61
-rw-r--r--web/src/math/Rect.ts97
-rw-r--r--web/src/renderGrid.ts64
-rw-r--r--web/src/types.ts36
16 files changed, 412 insertions, 0 deletions
diff --git a/web/src/components/app/index.css b/web/src/components/app/index.css
new file mode 100644
index 0000000..3eeaee9
--- /dev/null
+++ b/web/src/components/app/index.css
@@ -0,0 +1,3 @@
+ntv-app {
+ display: block;
+}
diff --git a/web/src/components/app/index.ts b/web/src/components/app/index.ts
new file mode 100644
index 0000000..5967a46
--- /dev/null
+++ b/web/src/components/app/index.ts
@@ -0,0 +1,14 @@
+import ntvGrid from "../grid";
+import "./index.css";
+
+class NotiveAppElement extends HTMLElement {
+ connectedCallback() {
+ this.append(
+ ...window.notive.doc.grids.map((_grid) => {
+ return ntvGrid();
+ }),
+ );
+ }
+}
+
+customElements.define("ntv-app", NotiveAppElement);
diff --git a/web/src/components/grid/index.css b/web/src/components/grid/index.css
new file mode 100644
index 0000000..296c155
--- /dev/null
+++ b/web/src/components/grid/index.css
@@ -0,0 +1,8 @@
+ntv-grid {
+ display: block;
+}
+
+ntv-grid > canvas {
+ display: block;
+ width: 100%;
+}
diff --git a/web/src/components/grid/index.ts b/web/src/components/grid/index.ts
new file mode 100644
index 0000000..18bf75a
--- /dev/null
+++ b/web/src/components/grid/index.ts
@@ -0,0 +1,24 @@
+import h, { type CreateElement } from "../../html";
+import "./index.css";
+import colors from "open-color";
+
+class NotiveGridElement extends HTMLElement {
+ canvasEl: HTMLCanvasElement = h.canvas();
+
+ connectedCallback() {
+ this.append(this.canvasEl);
+ this.draw();
+ }
+
+ draw() {
+ const ctx = this.canvasEl.getContext("2d");
+ if (!ctx) throw new Error("Unable to get canvas context");
+ ctx.fillStyle = colors.gray[8];
+ ctx.fillRect(0, 0, this.canvasEl.width, this.canvasEl.height);
+ }
+}
+
+customElements.define("ntv-grid", NotiveGridElement);
+
+export default ((...args: any[]): NotiveGridElement =>
+ (h as any)["ntv-grid"](...args)) as CreateElement<NotiveGridElement>;
diff --git a/web/src/components/index.ts b/web/src/components/index.ts
new file mode 100644
index 0000000..8bc14e7
--- /dev/null
+++ b/web/src/components/index.ts
@@ -0,0 +1,2 @@
+import "./app";
+import "./grid";
diff --git a/web/src/favicon.ico b/web/src/favicon.ico
new file mode 100644
index 0000000..c10cfe9
--- /dev/null
+++ b/web/src/favicon.ico
Binary files differ
diff --git a/web/src/global.d.ts b/web/src/global.d.ts
new file mode 100644
index 0000000..3b6d980
--- /dev/null
+++ b/web/src/global.d.ts
@@ -0,0 +1,9 @@
+import type Notive from "./index";
+
+declare global {
+ interface Window {
+ notive: Notive;
+ }
+}
+
+export {};
diff --git a/web/src/html.ts b/web/src/html.ts
new file mode 100644
index 0000000..5bfff21
--- /dev/null
+++ b/web/src/html.ts
@@ -0,0 +1,26 @@
+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:
+ (_, tag: string) =>
+ (...args: any[]) => {
+ const el = document.createElement(tag);
+
+ if (typeof args[0] === "object") {
+ Object.assign(el, args.shift());
+ }
+
+ el.append(...args.flat());
+
+ return el;
+ },
+});
+
+export default h;
diff --git a/web/src/index.css b/web/src/index.css
new file mode 100644
index 0000000..f578562
--- /dev/null
+++ b/web/src/index.css
@@ -0,0 +1,10 @@
+@import "open-color";
+
+body {
+ background: var(--oc-gray-9);
+ color: var(--oc-white);
+ font-weight: normal;
+ font-family:
+ Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro,
+ sans-serif;
+}
diff --git a/web/src/index.html b/web/src/index.html
new file mode 100644
index 0000000..846f5be
--- /dev/null
+++ b/web/src/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <title>Notive</title>
+ <link rel="stylesheet" href="index.css" />
+ <script type="module" src="index.ts"></script>
+ <script type="module" src="components/index.ts"></script>
+ </head>
+ <body>
+ <ntv-app></ntv-app>
+ </body>
+</html>
diff --git a/web/src/index.ts b/web/src/index.ts
new file mode 100644
index 0000000..a32aaf1
--- /dev/null
+++ b/web/src/index.ts
@@ -0,0 +1,22 @@
+import { Doc } from "./types";
+
+function defaultDoc(): Doc {
+ const defaultCells = Array(16).map(() => ({ value: "1" }));
+
+ return {
+ grids: [
+ {
+ baseCellSize: 48,
+ parts: [{ rows: Array(4).map(() => ({ cells: [...defaultCells] })) }],
+ },
+ ],
+ };
+}
+
+export default class Notive {
+ doc: Doc = defaultDoc();
+}
+
+window.notive = new Notive();
+
+window.dispatchEvent(new CustomEvent("ntv:initialized"));
diff --git a/web/src/math/Coord.ts b/web/src/math/Coord.ts
new file mode 100644
index 0000000..db7ee6d
--- /dev/null
+++ b/web/src/math/Coord.ts
@@ -0,0 +1,23 @@
+/** 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/web/src/math/Ratio.ts b/web/src/math/Ratio.ts
new file mode 100644
index 0000000..4973ff4
--- /dev/null
+++ b/web/src/math/Ratio.ts
@@ -0,0 +1,61 @@
+/** Serializable representation of a ratio. */
+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;
+
+ 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");
+ }
+
+ this._numerator = numerator;
+ this._denominator = denominator;
+ }
+
+ 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,
+ );
+ }
+
+ toNumber(): number {
+ return 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]);
+ }
+}
diff --git a/web/src/math/Rect.ts b/web/src/math/Rect.ts
new file mode 100644
index 0000000..e26fbae
--- /dev/null
+++ b/web/src/math/Rect.ts
@@ -0,0 +1,97 @@
+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,
+ );
+ }
+
+ /** 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/web/src/renderGrid.ts b/web/src/renderGrid.ts
new file mode 100644
index 0000000..476876b
--- /dev/null
+++ b/web/src/renderGrid.ts
@@ -0,0 +1,64 @@
+import Rect from "./math/Rect";
+import { Cell, CellRef, Grid, Row, RowRef } from "./types";
+
+export interface RenderedCell extends Cell {
+ cellRef: CellRef;
+ rect: Rect;
+}
+
+export interface RenderedRow {
+ rowRef: RowRef;
+ rect: Rect;
+ renderedCells: RenderedCell[];
+}
+
+export interface RenderedGrid extends Grid {
+ rect: Rect;
+ renderedRows: RenderedRow[];
+}
+
+function renderCell(grid: Grid, row: Row, cell: Cell): RenderedCell {}
+
+function renderRow(grid: Grid, row: Row): RenderedRow {
+ let topLeftX = 0;
+
+ const renderedCells = row.cells.map((cell, cellIndex) => {
+ const renderedCell = renderCell(grid, row, cell);
+ topLeftX = renderedCell.rect.bottomRight.y;
+ return renderedCell;
+ });
+}
+
+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 renderedRow = renderRow(grid, row);
+
+ 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 };
+}
diff --git a/web/src/types.ts b/web/src/types.ts
new file mode 100644
index 0000000..df421d7
--- /dev/null
+++ b/web/src/types.ts
@@ -0,0 +1,36 @@
+import Ratio from "./math/Ratio";
+
+export interface Cell {
+ value?: string;
+}
+
+export interface Row {
+ cells: [Cell, ...Cell[]];
+}
+
+export interface Part {
+ title?: string;
+ rows: [Row, ...Row[]];
+}
+
+export interface Grid {
+ id: string;
+ baseCellSize: number;
+ baseCellWidthRatio: Ratio;
+ parts: [Part, ...Part[]];
+}
+
+export interface Doc {
+ grids: Grid[];
+}
+
+export interface RowRef {
+ partIndex: number;
+ rowIndex: number;
+}
+
+export interface CellRef {
+ partIndex: number;
+ rowIndex: number;
+ cellIndex: number;
+}