update types

This commit is contained in:
Orion Reed 2024-12-02 17:53:03 -05:00
parent b1e20bfd3f
commit 7969bdb706
14 changed files with 175 additions and 92 deletions

View File

@ -4,8 +4,9 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --base=/folk-canvas",
"preview": "vite build && vite preview"
"build": "tsc --noEmit && vite build --base=/folk-canvas",
"preview": "tsc --noEmit && vite build && vite preview",
"types": "tsc --noEmit"
},
"dependencies": {
"@babel/parser": "^7.26.2",

107
src/common/Vector.ts Normal file
View File

@ -0,0 +1,107 @@
import type { Point } from './types.ts';
export class Vector {
/**
* Creates a zero vector (0,0)
* @returns {Point} A point representing a zero vector
*/
static zero(): Point {
return { x: 0, y: 0 };
}
/**
* Subtracts vector b from vector a
* @param {Point} a - The first vector
* @param {Point} b - The vector to subtract
* @returns {Point} The resulting vector
*/
static sub(a: Point, b: Point): Point {
return { x: a.x - b.x, y: a.y - b.y };
}
/**
* Adds two vectors together
* @param {Point} a - The first vector
* @param {Point} b - The second vector
* @returns {Point} The sum of the two vectors
*/
static add(a: Point, b: Point): Point {
return { x: a.x + b.x, y: a.y + b.y };
}
/**
* Multiplies two vectors component-wise
* @param {Point} a - The first vector
* @param {Point} b - The second vector
* @returns {Point} The component-wise product of the two vectors
*/
static mult(a: Point, b: Point): Point {
return { x: a.x * b.x, y: a.y * b.y };
}
/**
* Scales a vector by a scalar value
* @param {Point} v - The vector to scale
* @param {number} scaleFactor - The scaling factor
* @returns {Point} The scaled vector
*/
static scale(v: Point, scaleFactor: number): Point {
return { x: v.x * scaleFactor, y: v.y * scaleFactor };
}
/**
* Calculates the magnitude (length) of a vector
* @param {Point} v - The vector
* @returns {number} The magnitude of the vector
*/
static mag(v: Point): number {
return Math.hypot(v.x, v.y);
}
/**
* Returns a normalized (unit) vector in the same direction
* @param {Point} v - The vector to normalize
* @returns {Point} The normalized vector
*/
static normalized(v: Point): Point {
const magnitude = Vector.mag(v);
return magnitude === 0 ? Vector.zero() : { x: v.x / magnitude, y: v.y / magnitude };
}
/**
* Calculates the Euclidean distance between two points
* @param {Point} a - The first point
* @param {Point} b - The second point
* @returns {number} The distance between the points
*/
static distance(a: Point, b: Point): number {
return Math.hypot(a.x - b.x, a.y - b.y);
}
/**
* Calculates the squared distance between two points
* Useful for performance when comparing distances
* @param {Point} a - The first point
* @param {Point} b - The second point
* @returns {number} The squared distance between the points
*/
static distanceSquared(a: Point, b: Point): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return dx * dx + dy * dy;
}
/**
* Linearly interpolates between two points
* @param {Point} a - The starting point
* @param {Point} b - The ending point
* @param {number} t - The interpolation parameter (0-1)
* @returns {Point} The interpolated point
*/
static lerp(a: Point, b: Point, t: number): Point {
return {
x: a.x + (b.x - a.x) * t,
y: a.y + (b.y - a.y) * t,
};
}
}

View File

@ -1,17 +0,0 @@
export type Vector2 = { x: number; y: number };
export class Vector {
static zero: () => Vector2 = () => ({ x: 0, y: 0 });
static sub: (a: Vector2, b: Vector2) => Vector2 = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
static add: (a: Vector2, b: Vector2) => Vector2 = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
static mult: (a: Vector2, b: Vector2) => Vector2 = (a, b) => ({ x: a.x * b.x, y: a.y * b.y });
static scale: (v: Vector2, scaleFactor: number) => Vector2 = (v, scaleFactor) => ({
x: v.x * scaleFactor,
y: v.y * scaleFactor,
});
static mag: (v: Vector2) => number = (v) => Math.sqrt(v.x * v.x + v.y * v.y);
static normalized: (v: Vector2) => Vector2 = (v) => {
const mag = Vector.mag(v);
return mag === 0 ? Vector.zero() : { x: v.x / mag, y: v.y / mag };
};
}

1
src/common/types.ts Normal file
View File

@ -0,0 +1 @@
export type Point = { x: number; y: number };

View File

@ -1,35 +1,21 @@
// Adopted from: https://github.com/pshihn/bezier-points/blob/master/src/index.ts
export type Point = [number, number];
export interface Vertex {
x: number;
y: number;
}
// distance between 2 points
function distance(p1: Point, p2: Point): number {
return Math.sqrt(distanceSq(p1, p2));
}
// distance between 2 points squared
function distanceSq(p1: Point, p2: Point): number {
return Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2);
}
import type { Point } from './types.ts';
import { Vector } from './Vector.ts';
// Distance squared from a point p to the line segment vw
function distanceToSegmentSq(p: Point, v: Point, w: Point): number {
const l2 = distanceSq(v, w);
const l2 = Vector.distanceSquared(v, w);
if (l2 === 0) {
return distanceSq(p, v);
return Vector.distanceSquared(p, v);
}
let t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
t = Math.max(0, Math.min(1, t));
return distanceSq(p, lerp(v, w, t));
return Vector.distanceSquared(p, Vector.lerp(v, w, t));
}
function lerp(a: Point, b: Point, t: number): Point {
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
}
// Adapted from https://seant23.wordpress.com/2010/11/12/offset-bezier-curves/
@ -39,13 +25,13 @@ function flatness(points: readonly Point[], offset: number): number {
const p3 = points[offset + 2];
const p4 = points[offset + 3];
let ux = 3 * p2[0] - 2 * p1[0] - p4[0];
let ux = 3 * p2.x - 2 * p1.x - p4.x;
ux *= ux;
let uy = 3 * p2[1] - 2 * p1[1] - p4[1];
let uy = 3 * p2.y - 2 * p1.y - p4.y;
uy *= uy;
let vx = 3 * p3[0] - 2 * p4[0] - p1[0];
let vx = 3 * p3.x - 2 * p4.x - p1.x;
vx *= vx;
let vy = 3 * p3[1] - 2 * p4[1] - p1[1];
let vy = 3 * p3.y - 2 * p4.y - p1.y;
vy *= vy;
if (ux < vx) {
@ -69,7 +55,7 @@ function getPointsOnBezierCurveWithSplitting(
if (flatness(points, offset) < tolerance) {
const p0 = points[offset + 0];
if (outPoints.length) {
const d = distance(outPoints[outPoints.length - 1], p0);
const d = Vector.distance(outPoints[outPoints.length - 1], p0);
if (d > 1) {
outPoints.push(p0);
}
@ -176,7 +162,7 @@ export function getSvgPathFromStroke(stroke: number[][]): string {
return d.join(' ');
}
export function verticesToPolygon(vertices: Vertex[]): string {
export function verticesToPolygon(vertices: Point[]): string {
if (vertices.length === 0) return '';
return `polygon(${vertices.map((vertex) => `${vertex.x}px ${vertex.y}px`).join(', ')})`;
@ -184,7 +170,7 @@ export function verticesToPolygon(vertices: Vertex[]): string {
const vertexRegex = /(?<x>-?([0-9]*[.])?[0-9]+),\s*(?<y>-?([0-9]*[.])?[0-9]+)/;
export function parseVertex(str: string): Vertex | null {
export function parseVertex(str: string): Point | null {
const results = vertexRegex.exec(str);
if (results === null) return null;

View File

@ -73,10 +73,10 @@ export class FolkConnection extends AbstractArrow {
) as Arrow;
const points = pointsOnBezierCurves([
[sx, sy],
[cx, cy],
[ex, ey],
[ex, ey],
{ x: sx, y: sy },
{ x: cx, y: cy },
{ x: ex, y: ey },
{ x: ex, y: ey },
]);
const stroke = getStroke(points, this.#options);

View File

@ -1,6 +1,6 @@
import { FolkSet } from './folk-set';
import { Vertex, verticesToPolygon } from './common/utils';
import { verticesToPolygon } from './common/utils';
import type { Point } from './common/types';
declare global {
interface HTMLElementTagNameMap {
'folk-hull': FolkHull;
@ -10,9 +10,9 @@ declare global {
export class FolkHull extends FolkSet {
static tagName = 'folk-hull';
#hull: Vertex[] = [];
#hull: Point[] = [];
get hull(): ReadonlyArray<Vertex> {
get hull(): ReadonlyArray<Point> {
return this.#hull;
}
@ -50,7 +50,7 @@ export class FolkHull extends FolkSet {
* If not, see <http://www.gnu.org/licenses/>.
*/
function comparePoints(a: Vertex, b: Vertex): number {
function comparePoints(a: Point, b: Point): number {
if (a.x < b.x) return -1;
if (a.x > b.x) return 1;
if (a.y < b.y) return -1;
@ -58,8 +58,8 @@ function comparePoints(a: Vertex, b: Vertex): number {
return 0;
}
export function makeHull(rects: DOMRectReadOnly[]): Vertex[] {
const points: Vertex[] = rects
export function makeHull(rects: DOMRectReadOnly[]): Point[] {
const points: Point[] = rects
.flatMap((rect) => [
{ x: rect.left, y: rect.top },
{ x: rect.right, y: rect.top },
@ -74,12 +74,12 @@ export function makeHull(rects: DOMRectReadOnly[]): Vertex[] {
// as per the mathematical convention, instead of "down" as per the computer
// graphics convention. This doesn't affect the correctness of the result.
const upperHull: Array<Vertex> = [];
const upperHull: Array<Point> = [];
for (let i = 0; i < points.length; i++) {
const p: Vertex = points[i];
const p: Point = points[i];
while (upperHull.length >= 2) {
const q: Vertex = upperHull[upperHull.length - 1];
const r: Vertex = upperHull[upperHull.length - 2];
const q: Point = upperHull[upperHull.length - 1];
const r: Point = upperHull[upperHull.length - 2];
if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.pop();
else break;
}
@ -87,12 +87,12 @@ export function makeHull(rects: DOMRectReadOnly[]): Vertex[] {
}
upperHull.pop();
const lowerHull: Array<Vertex> = [];
const lowerHull: Array<Point> = [];
for (let i = points.length - 1; i >= 0; i--) {
const p: Vertex = points[i];
const p: Point = points[i];
while (lowerHull.length >= 2) {
const q: Vertex = lowerHull[lowerHull.length - 1];
const r: Vertex = lowerHull[lowerHull.length - 2];
const q: Point = lowerHull[lowerHull.length - 1];
const r: Point = lowerHull[lowerHull.length - 2];
if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.pop();
else break;
}

View File

@ -24,9 +24,11 @@ export class FolkLLM extends HTMLElement {
this.#update(new Set(['systemPrompt', 'prompt']));
}
#session;
#session: any;
#isModelReady = window?.ai.languageModel.capabilities().then((capabilities) => capabilities.available === 'readily');
#isModelReady = window?.ai.languageModel
.capabilities()
.then((capabilities: any) => capabilities.available === 'readily');
#systemPrompt: Prompt = this.getAttribute('system-prompt') || '';
get systemPrompt() {

View File

@ -1,18 +1,18 @@
// This is a rewrite of https://github.com/guerrillacontra/html5-es6-physics-rope
import { Vector, type Vector2 } from './common/Vector2.ts';
import { Vector } from './common/Vector.ts';
import type { Point } from './common/types.ts';
import { AbstractArrow } from './abstract-arrow.ts';
import { Vertex } from './common/utils.ts';
const lerp = (first: number, second: number, percentage: number) => first + (second - first) * percentage;
// Each rope part is one of these uses a high precision variant of StörmerVerlet integration to keep the simulation consistent otherwise it would "explode"!
interface RopePoint {
pos: Vertex;
pos: Point;
distanceToNextPoint: number;
isFixed: boolean;
oldPos: Vertex;
velocity: Vertex;
oldPos: Point;
velocity: Point;
mass: number;
damping: number;
prev: RopePoint | null;
@ -162,7 +162,7 @@ export class FolkRope extends AbstractArrow {
}
}
#generatePoints(start: Vertex, end: Vertex) {
#generatePoints(start: Point, end: Point) {
const delta = Vector.sub(end, start);
const len = Vector.mag(delta);
const resolution = 5;
@ -202,7 +202,7 @@ export class FolkRope extends AbstractArrow {
return points;
}
#integratePoint(point: RopePoint, gravity: Vector2) {
#integratePoint(point: RopePoint, gravity: Point) {
if (!point.isFixed) {
point.velocity = Vector.sub(point.pos, point.oldPos);
point.oldPos = { ...point.pos };

View File

@ -1,6 +1,6 @@
import { css, html } from './common/tags';
import { ResizeObserverManager } from './common/resize-observer';
import type { Vector2 } from './common/Vector2';
import type { Point } from './common/types';
const resizeObserver = new ResizeObserverManager();
@ -11,13 +11,13 @@ type RotatedDOMRect = DOMRect & {
rotation: number;
// Returns the center point in worldspace coordinates
center(): Vector2;
center(): Point;
// Returns the four corners in worldspace coordinates, in clockwise order
corners(): [Vector2, Vector2, Vector2, Vector2];
corners(): [Point, Point, Point, Point];
// Returns all the vertices in worldspace coordinates
vertices(): Vector2[];
vertices(): Point[];
};
export type MoveEventDetail = { movementX: number; movementY: number };
@ -311,18 +311,18 @@ export class FolkShape extends HTMLElement {
bottom: y + height,
rotation,
center(): Vector2 {
center(): Point {
return {
x: this.x + this.width / 2,
y: this.y + this.height / 2,
};
},
vertices(): Vector2[] {
vertices(): Point[] {
// TODO: Implement
return [];
},
corners(): [Vector2, Vector2, Vector2, Vector2] {
corners(): [Point, Point, Point, Point] {
const center = this.center();
const cos = Math.cos(radians);
const sin = Math.sin(radians);
@ -331,7 +331,7 @@ export class FolkShape extends HTMLElement {
const halfHeight = this.height / 2;
// Helper to rotate a point around the center
const rotatePoint = (dx: number, dy: number): Vector2 => ({
const rotatePoint = (dx: number, dy: number): Point => ({
x: center.x + dx * cos - dy * sin,
y: center.y + dx * sin + dy * cos,
});

View File

@ -160,7 +160,7 @@ export class FolkSpreadsheet extends HTMLElement {
#shadow = this.attachShadow({ mode: 'open' });
#textarea;
#textarea: HTMLTextAreaElement | null = null;
#editedCell: FolkSpreadSheetCell | null = null;
@ -315,6 +315,7 @@ export class FolkSpreadsheet extends HTMLElement {
}
#focusTextarea(cell: FolkSpreadSheetCell) {
if (this.#textarea === null) return;
this.#editedCell = cell;
const gridColumn = getColumnIndex(cell.column) + 2;
const gridRow = cell.row + 1;
@ -327,6 +328,7 @@ export class FolkSpreadsheet extends HTMLElement {
#resetTextarea() {
if (this.#editedCell === null) return;
if (this.#textarea === null) return;
this.#textarea.style.setProperty('--text-column', '0');
this.#textarea.style.setProperty('--text-row', '0');
this.#editedCell.expression = this.#textarea.value;

View File

@ -12,7 +12,7 @@ export class FolkTimer extends HTMLElement {
}
#timeMs = 0;
#timeoutId = -1;
#timeoutId: NodeJS.Timeout | -1 = -1;
#intervalMs = 100;

View File

@ -19,7 +19,7 @@ export class FolkWeather extends HTMLElement {
static observedAttributes = ['coordinates'];
#coordinates = [0, 0] as const;
#coordinates: readonly [number, number] = [0, 0];
#results: Weather | null = null;
get coordinates() {
@ -30,9 +30,10 @@ export class FolkWeather extends HTMLElement {
this.setAttribute('coordinates', coordinates.join(', '));
}
attributeChangedCallback(name, oldValue, newValue) {
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
if (name === 'coordinates') {
this.#coordinates = newValue.split(',').map((str) => Number(str)) || [0, 0];
const [lat = 0, long = 0] = newValue.split(',').map((str) => Number(str));
this.#coordinates = [lat, long] as const;
this.fetchWeather(this.#coordinates);
}
}

View File

@ -1,6 +1,6 @@
import { AbstractArrow } from './abstract-arrow.js';
import { Vertex, verticesToPolygon } from './common/utils.js';
import { verticesToPolygon } from './common/utils.js';
import type { Point } from './common/types.js';
export class FolkXanadu extends AbstractArrow {
static tagName = 'folk-xanadu';
@ -47,7 +47,7 @@ export class FolkXanadu extends AbstractArrow {
}
// The order that vertices are returned is significant
function computeInlineVertices(rects: DOMRect[]): Vertex[] {
function computeInlineVertices(rects: DOMRect[]): Point[] {
rects = rects.map((rect) =>
DOMRectReadOnly.fromRect({
height: Math.round(rect.height),
@ -68,7 +68,7 @@ function computeInlineVertices(rects: DOMRect[]): Vertex[] {
];
}
const vertices: Vertex[] = [];
const vertices: Point[] = [];
if (rects[1].left < rects[0].left) {
vertices.push({ x: rects[1].left, y: rects[1].top }, { x: rects[0].left, y: rects[0].bottom });