diff --git a/README.md b/README.md deleted file mode 100644 index e4152c4..0000000 --- a/README.md +++ /dev/null @@ -1,20 +0,0 @@ -This repository contains the boilerplate package which I use to start most of my smaller Typescript -projects. It is "primitive" in that it doesn't have hot module reloading or VSCode integration; -instead, it runs the build process using a watcher that enables fast iteration via the command line. -This boilerplate is focused on web components, primarily those build with [Lit](https://lit.dev). - -## Dependencies - -Aside from the NodeJS dependencies, this script uses -[codespell](https://github.com/codespell-project/codespell), which is a Python-based spell checker -for comments and documentation. - -## Decisions: - -- TSC is still the best analyzer of Typescript's types. -- Scripting ESBuild gives you enormous power. -- Many of the commands have the `${NODE_RUNNER}` prefix. If you set `export NODE_RUNNER=bun`, you - can get a huge speedup in building and linting. -- Knip makes sure you're not importing anything you're not using. -- As this is a Lit-focused project, both Lit-Analyzer and WC-Analyzer are included -- xo diff --git a/alphamod.png b/alphamod.png deleted file mode 100644 index b573ec4..0000000 Binary files a/alphamod.png and /dev/null differ diff --git a/src/fridge-tile.ts b/src/fridge-tile.ts index 855bec5..56435b5 100644 --- a/src/fridge-tile.ts +++ b/src/fridge-tile.ts @@ -2,21 +2,10 @@ import { LitElement, html, css } from "lit"; import { customElement } from "lit/decorators/custom-element.js"; import { property } from "lit/decorators/property.js"; import { styleMap } from "lit/directives/style-map.js"; -import { LitDragEvent, LitDraggable } from "./lit-draggable.js"; - -export function bound(_target: unknown, key: string, descriptor: PropertyDescriptor): PropertyDescriptor { - if (typeof descriptor?.value !== "function") { - throw new Error("Only methods can be @bound."); - } - return { - configurable: true, - get() { - const method = descriptor.value.bind(this); - Object.defineProperty(this, key, { value: method, configurable: true, writable: true }); - return method; - }, - }; -} +import { ref, createRef, Ref } from "lit/directives/ref.js"; +import { LitDraggable } from "./lit-draggable.js"; +import { LitDragEvent } from "./types.js"; +import { LitDragStart, LitDragEnd } from "./lit-events.js"; @customElement("fridge-tile") export class FridgeTile extends LitElement { @@ -25,6 +14,13 @@ export class FridgeTile extends LitElement { dragHandle: LitDraggable; + transform = { + rotate: Math.random() * 30 - 15, + scale: 1.0, + }; + + handle: Ref = createRef(); + static get styles() { return css` :host { @@ -52,6 +48,9 @@ export class FridgeTile extends LitElement { position: relative; background: white; z-index: 100; + transition-property: transform; + transition-duration: 200ms; + transition-timing-function: ease-in-out; //other options are ease } .word.dragging { @@ -64,17 +63,35 @@ export class FridgeTile extends LitElement { super(); this.dragHandle = new LitDraggable(this); this.onDragEnd = this.onDragEnd.bind(this); - this.addEventListener("lit-drag-end", this.onDragEnd); + this.onDragStart = this.onDragStart.bind(this); + this.addEventListener(LitDragEnd.eventName, this.onDragEnd); + this.addEventListener(LitDragStart.eventName, this.onDragStart); } onDragEnd(ev: LitDragEvent) { + this.transform = { scale: 1.0, rotate: Math.random() * 30 - 15 }; + this.handle.value!.style.setProperty( + "transform", + `scale(${+this.transform.scale}) rotate(${+this.transform.rotate}deg)` + ); this.style.setProperty("top", `${+ev.offsetY}px`); this.style.setProperty("left", `${+ev.offsetX}px`); } + onDragStart(_ev: LitDragEvent) { + this.transform = { scale: 1.3, rotate: Math.random() * 30 - 15 }; + this.handle.value!.style.setProperty( + "transform", + `scale(${+this.transform.scale}) rotate(${+this.transform.rotate}deg)` + ); + } + render() { - const styles = styleMap({ width: `${this.word.length}ch` }); - return html`
${this.word}
`; + const styles = { + width: `${this.word.length}ch`, + transform: `rotate(${this.transform.rotate}deg)`, + }; + return html`
${this.word}
`; } } diff --git a/src/lit-draggable.ts b/src/lit-draggable.ts index bb181d3..e8ae049 100644 --- a/src/lit-draggable.ts +++ b/src/lit-draggable.ts @@ -1,4 +1,5 @@ import { ReactiveControllerHost, ReactiveController } from "lit"; +import { LitDragStart, LitDragging, LitDragEnd } from "./lit-events.js"; export type DragBoundaries = { top: number; @@ -15,37 +16,6 @@ export type DragAxes = "x" | "y" | "both" | "none"; export type DragBounds = HTMLElement | Partial | "parent" | "body" | (string & Record); -export interface LitDragEvent extends Event { - offsetX: number; - offsetY: number; - node: HTMLElement; -} - -function makeLitDragEvent(name: string): LitDragEvent { - class _LitDragEvent extends Event implements LitDragEvent { - static readonly eventName = name; - offsetX: number = 0; - offsetY: number = 0; - node: HTMLElement; - // container: HTMLElement; - constructor(source: LitDraggable) { - super(_LitDragEvent.eventName, { bubbles: true, composed: true }); - this.offsetX = source.translateX; - this.offsetY = source.translateY; - this.node = source.host; - // this.container = source.container; - } - } - - return _LitDragEvent as unknown as LitDragEvent; -} - -export const LitDragStart = makeLitDragEvent("lit-drag-start"); - -export const LitDragging = makeLitDragEvent("lit-dragging"); - -export const LitDragEnd: LitDragEvent = makeLitDragEvent("lit-drag-end"); - const defaultOptions: DragOptions = { preventSelect: true, }; @@ -61,6 +31,9 @@ export class LitDraggable implements ReactiveController { initialX = 0; initialY = 0; + handleOffsetX = 0; + handleOffsetY = 0; + dragging = false; currentSelect?: string; @@ -107,8 +80,12 @@ export class LitDraggable implements ReactiveController { document.body.style.userSelect = "none"; } + const hostRect = this.host.getBoundingClientRect(); + this.initialX = ev.clientX; this.initialY = ev.clientY; + this.handleOffsetX = this.initialX - hostRect.left; + this.handleOffsetY = this.initialY - hostRect.top; this.host.dataset.litDrag = "true"; this.host.dispatchEvent(new LitDragStart(this)); } @@ -142,8 +119,9 @@ export class LitDraggable implements ReactiveController { document.body.style.userSelect = this.currentSelect; this.currentSelect = undefined; } - this.initialX = this.translateX; - this.initialY = this.translateY; + + this.initialX = ev.clientX - this.handleOffsetX; + this.initialY = ev.clientY - this.handleOffsetY; this.host.dispatchEvent(new LitDragEnd(this)); this.host.style.removeProperty("transform"); } diff --git a/src/lit-events.ts b/src/lit-events.ts new file mode 100644 index 0000000..36e2bc3 --- /dev/null +++ b/src/lit-events.ts @@ -0,0 +1,54 @@ +import { LitDraggable, LitDragEvent } from "./types"; + +export class LitDragStart extends Event implements LitDragEvent { + static readonly eventName = "lit-drag-start"; + offsetX: number = 0; + offsetY: number = 0; + node: HTMLElement; + // container: HTMLElement; + constructor(source: LitDraggable) { + super(LitDragStart.eventName, { bubbles: true, composed: true }); + this.offsetX = source.translateX; + this.offsetY = source.translateY; + this.node = source.host; + // this.container = source.container; + } +} + +export class LitDragging extends Event implements LitDragEvent { + static readonly eventName = "lit-dragging"; + offsetX: number = 0; + offsetY: number = 0; + node: HTMLElement; + // container: HTMLElement; + constructor(source: LitDraggable) { + super(LitDragging.eventName, { bubbles: true, composed: true }); + this.offsetX = source.translateX; + this.offsetY = source.translateY; + this.node = source.host; + // this.container = source.container; + } +} + +export class LitDragEnd extends Event implements LitDragEvent { + static readonly eventName = "lit-drag-end"; + offsetX: number = 0; + offsetY: number = 0; + node: HTMLElement; + // container: HTMLElement; + constructor(source: LitDraggable) { + super(LitDragEnd.eventName, { bubbles: true, composed: true }); + this.offsetX = source.initialX; + this.offsetY = source.initialY; + this.node = source.host; + // this.container = source.container; + } +} + +declare global { + interface GlobalEventHandlersEventMap { + [LitDragStart.eventName]: LitDragStart; + [LitDragging.eventName]: LitDragging; + [LitDragEnd.eventName]: LitDragEnd; + } +} diff --git a/src/sat.ts b/src/sat.ts new file mode 100644 index 0000000..9d6b03a --- /dev/null +++ b/src/sat.ts @@ -0,0 +1,143 @@ +// Copyright (c) 2012 Elf M. Sternberg +// +// Much of the code here I would never have understood if it hadn't +// been for the patient work of Caleb Helbling +// (http://www.propulsionjs.com/), as well as the Wikipedia pages for +// the Separating Axis Theorem. It took me a week to wrap my head +// around these ideas. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +class Vector { + x: number; + y: number; + + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + + add: (v2: Vector) { + return new Vector(this.x + v2.x, this.y + v2.y); + } + + + + add: function (v1, v2) { + return { + x: v1.x + v2.x, + y: v1.y + v2.y, + }; + }, + scalar: function (v, s) { + return { + x: v.x * s, + y: v.y * s, + }; + }, + dot: function (v1, v2) { + return v1.x * v2.x + v1.y * v2.y; + }, + magnitude2: function (v) { + var x, y; + x = v.x; + y = v.y; + return x * x + y * y; + }, + magnitude: function (v) { + return Math.sqrt(Math.vector.magnitude2(v)); + }, + normalize: function (v) { + var mag; + mag = Math.vector.magnitude(v); + return { + x: v.x / mag, + y: v.y / mag, + }; + }, + leftNormal: function (v) { + return { + x: -v.y, + y: v.x, + }; + }, +}; + + this.colliding = function (shape1, shape2) { + var axes, axes1, axes2, axis, genAxes, genProjection, j, len, proj1, proj2; + genAxes = function (shape) { + var axis, i, j, ref, results; + if (shape.length < 3) { + throw "Cannot handle non-polygons"; + } + axis = function (shape, pi) { + var edge, p1, p2; + p1 = shape[pi]; + p2 = shape[pi === shape.length - 1 ? 0 : pi + 1]; + edge = { + x: p1.x - p2.x, + y: p1.y - p2.y, + }; + return Math.vector.normalize(Math.vector.leftNormal(edge)); + }; + results = []; + for (i = j = 0, ref = shape.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { + results.push(axis(shape, i)); + } + return results; + }; + genProjection = function (shape, axis) { + var i, j, max, min, p, ref; + min = Math.vector.dot(axis, shape[0]); + max = min; + for (i = j = 1, ref = shape.length; 1 <= ref ? j < ref : j > ref; i = 1 <= ref ? ++j : --j) { + p = Math.vector.dot(axis, shape[i]); + if (p < min) { + min = p; + } + if (p > max) { + max = p; + } + } + return { + min: min, + max: max, + }; + }; + axes1 = genAxes(shape1); + axes2 = genAxes(shape2); + axes = axes1.concat(axes2); + for (j = 0, len = axes.length; j < len; j++) { + axis = axes[j]; + proj1 = genProjection(shape1, axis); + proj2 = genProjection(shape2, axis); + if ( + !( + (proj1.min >= proj2.min && proj1.min <= proj2.max) || + (proj1.max >= proj2.min && proj1.max <= proj2.max) || + (proj2.min >= proj1.min && proj2.min <= proj1.max) || + (proj2.max >= proj1.min && proj2.max <= proj1.max) + ) + ) { + return false; + } + } + return true; + }; +}).call(this); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a053696 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,13 @@ +export interface LitDraggable { + translateX: number; + translateY: number; + initialX: number; + initialY: number; + host: HTMLElement; +} + +export interface LitDragEvent extends Event { + offsetX: number; + offsetY: number; + node: HTMLElement; +}