Updated movement code for performance and accuracy.

This was a learning experience.

The transform controls `rotate`, `scale`, and `translate` have independent CSS names now, so I don't
have to memorize the transformation pattern needed to make them go as expected. On the other hand,
`transition` is still how you describe the speed and easing.  And even though they're not
officially transformations, `transitionend` is how you detect when one has completed.

All of the various different techniques for figuring out where something is on the page with respect
to another element can be terribly confusing. Ultimately, I went with offsets-from-viewport, as that
seems to be the most common way of doing the calculations.

Separating `pointermove` from the animation frames used to update the page is a very nifty technique
that I learned from the Svelte guys. `pointermove` only updates an (x,y) coordinate pair that you
keep in memory; as long as you're "tracking a drag," you just update on every available animation
frame, using the last (x,y) pair that it saved.  This gives you much smoother animations.
This commit is contained in:
Elf M. Sternberg 2025-01-20 16:03:18 -08:00
parent f521b261e6
commit 141a7c0461
44 changed files with 23845 additions and 6336 deletions

29
.editorconfig Normal file
View File

@ -0,0 +1,29 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.json]
indent_size = 2
[*.{html,js,md}]
block_comment_start = /**
block_comment = *
block_comment_end = */

125
.gitignore vendored
View File

@ -1,115 +1,24 @@
# Created by https://www.gitignore.io/api/node ## editors
# Edit at https://www.gitignore.io/?templates=node /.idea
/.vscode
### Node ### ## system files
# Logs .DS_Store
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html) ## npm
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json /node_modules/
/npm-debug.log
# Runtime data ## testing
pids /coverage/
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover ## temp folders
lib-cov /.tmp/
# Coverage directory used by tools like istanbul # build
coverage /_site/
*.lcov /dist/
/out-tsc/
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
dist
# Uncomment the public line if your project uses Gatsby
# https://nextjs.org/blog/next-9-1#public-directory-support
# https://create-react-app.dev/docs/using-the-public-folder/#docsNav
# public
# Storybook build outputs
.out
.storybook-out
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Temporary folders
tmp/
temp/
# End of https://www.gitignore.io/api/node
api/**
storybook-static/
# Wireit's cache
.wireit
storybook-static
custom-elements.json custom-elements.json

1
.husky/pre-commit Normal file
View File

@ -0,0 +1 @@
./node_modules/.bin/lint-staged

8
.storybook/main.js Normal file
View File

@ -0,0 +1,8 @@
const config = {
stories: ['../out-tsc/stories/**/*.stories.{js,md,mdx}'],
framework: {
name: '@web/storybook-framework-web-components',
},
};
export default config;

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

View File

@ -0,0 +1,23 @@
html, body {
overflow: hidden;
padding: 0;
margin: 0;
}
#fridgemagnets {
width: 100vw;
height: 100vh;
}
#fridge {
overflow: hidden;
width: 100vw;
background: url('pingbg.png') repeat;
}
#footer {
background-color: #32cd32;
width: 100vw;
height: 18ex;
}

View File

@ -0,0 +1,939 @@
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i7 = decorators.length - 1, decorator; i7 >= 0; i7--)
if (decorator = decorators[i7])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
// node_modules/@lit/reactive-element/css-tag.js
var t = globalThis;
var e = t.ShadowRoot && (void 0 === t.ShadyCSS || t.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype;
var s = Symbol();
var o = /* @__PURE__ */ new WeakMap();
var n = class {
constructor(t5, e5, o6) {
if (this._$cssResult$ = true, o6 !== s) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
this.cssText = t5, this.t = e5;
}
get styleSheet() {
let t5 = this.o;
const s3 = this.t;
if (e && void 0 === t5) {
const e5 = void 0 !== s3 && 1 === s3.length;
e5 && (t5 = o.get(s3)), void 0 === t5 && ((this.o = t5 = new CSSStyleSheet()).replaceSync(this.cssText), e5 && o.set(s3, t5));
}
return t5;
}
toString() {
return this.cssText;
}
};
var r = (t5) => new n("string" == typeof t5 ? t5 : t5 + "", void 0, s);
var i = (t5, ...e5) => {
const o6 = 1 === t5.length ? t5[0] : e5.reduce((e6, s3, o7) => e6 + ((t6) => {
if (true === t6._$cssResult$) return t6.cssText;
if ("number" == typeof t6) return t6;
throw Error("Value passed to 'css' function must be a 'css' function result: " + t6 + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.");
})(s3) + t5[o7 + 1], t5[0]);
return new n(o6, t5, s);
};
var S = (s3, o6) => {
if (e) s3.adoptedStyleSheets = o6.map((t5) => t5 instanceof CSSStyleSheet ? t5 : t5.styleSheet);
else for (const e5 of o6) {
const o7 = document.createElement("style"), n6 = t.litNonce;
void 0 !== n6 && o7.setAttribute("nonce", n6), o7.textContent = e5.cssText, s3.appendChild(o7);
}
};
var c = e ? (t5) => t5 : (t5) => t5 instanceof CSSStyleSheet ? ((t6) => {
let e5 = "";
for (const s3 of t6.cssRules) e5 += s3.cssText;
return r(e5);
})(t5) : t5;
// node_modules/@lit/reactive-element/reactive-element.js
var { is: i2, defineProperty: e2, getOwnPropertyDescriptor: r2, getOwnPropertyNames: h, getOwnPropertySymbols: o2, getPrototypeOf: n2 } = Object;
var a = globalThis;
var c2 = a.trustedTypes;
var l = c2 ? c2.emptyScript : "";
var p = a.reactiveElementPolyfillSupport;
var d = (t5, s3) => t5;
var u = { toAttribute(t5, s3) {
switch (s3) {
case Boolean:
t5 = t5 ? l : null;
break;
case Object:
case Array:
t5 = null == t5 ? t5 : JSON.stringify(t5);
}
return t5;
}, fromAttribute(t5, s3) {
let i7 = t5;
switch (s3) {
case Boolean:
i7 = null !== t5;
break;
case Number:
i7 = null === t5 ? null : Number(t5);
break;
case Object:
case Array:
try {
i7 = JSON.parse(t5);
} catch (t6) {
i7 = null;
}
}
return i7;
} };
var f = (t5, s3) => !i2(t5, s3);
var y = { attribute: true, type: String, converter: u, reflect: false, hasChanged: f };
Symbol.metadata ??= Symbol("metadata"), a.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap();
var b = class extends HTMLElement {
static addInitializer(t5) {
this._$Ei(), (this.l ??= []).push(t5);
}
static get observedAttributes() {
return this.finalize(), this._$Eh && [...this._$Eh.keys()];
}
static createProperty(t5, s3 = y) {
if (s3.state && (s3.attribute = false), this._$Ei(), this.elementProperties.set(t5, s3), !s3.noAccessor) {
const i7 = Symbol(), r6 = this.getPropertyDescriptor(t5, i7, s3);
void 0 !== r6 && e2(this.prototype, t5, r6);
}
}
static getPropertyDescriptor(t5, s3, i7) {
const { get: e5, set: h3 } = r2(this.prototype, t5) ?? { get() {
return this[s3];
}, set(t6) {
this[s3] = t6;
} };
return { get() {
return e5?.call(this);
}, set(s4) {
const r6 = e5?.call(this);
h3.call(this, s4), this.requestUpdate(t5, r6, i7);
}, configurable: true, enumerable: true };
}
static getPropertyOptions(t5) {
return this.elementProperties.get(t5) ?? y;
}
static _$Ei() {
if (this.hasOwnProperty(d("elementProperties"))) return;
const t5 = n2(this);
t5.finalize(), void 0 !== t5.l && (this.l = [...t5.l]), this.elementProperties = new Map(t5.elementProperties);
}
static finalize() {
if (this.hasOwnProperty(d("finalized"))) return;
if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d("properties"))) {
const t6 = this.properties, s3 = [...h(t6), ...o2(t6)];
for (const i7 of s3) this.createProperty(i7, t6[i7]);
}
const t5 = this[Symbol.metadata];
if (null !== t5) {
const s3 = litPropertyMetadata.get(t5);
if (void 0 !== s3) for (const [t6, i7] of s3) this.elementProperties.set(t6, i7);
}
this._$Eh = /* @__PURE__ */ new Map();
for (const [t6, s3] of this.elementProperties) {
const i7 = this._$Eu(t6, s3);
void 0 !== i7 && this._$Eh.set(i7, t6);
}
this.elementStyles = this.finalizeStyles(this.styles);
}
static finalizeStyles(s3) {
const i7 = [];
if (Array.isArray(s3)) {
const e5 = new Set(s3.flat(1 / 0).reverse());
for (const s4 of e5) i7.unshift(c(s4));
} else void 0 !== s3 && i7.push(c(s3));
return i7;
}
static _$Eu(t5, s3) {
const i7 = s3.attribute;
return false === i7 ? void 0 : "string" == typeof i7 ? i7 : "string" == typeof t5 ? t5.toLowerCase() : void 0;
}
constructor() {
super(), this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, this._$Em = null, this._$Ev();
}
_$Ev() {
this._$ES = new Promise((t5) => this.enableUpdating = t5), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t5) => t5(this));
}
addController(t5) {
(this._$EO ??= /* @__PURE__ */ new Set()).add(t5), void 0 !== this.renderRoot && this.isConnected && t5.hostConnected?.();
}
removeController(t5) {
this._$EO?.delete(t5);
}
_$E_() {
const t5 = /* @__PURE__ */ new Map(), s3 = this.constructor.elementProperties;
for (const i7 of s3.keys()) this.hasOwnProperty(i7) && (t5.set(i7, this[i7]), delete this[i7]);
t5.size > 0 && (this._$Ep = t5);
}
createRenderRoot() {
const t5 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);
return S(t5, this.constructor.elementStyles), t5;
}
connectedCallback() {
this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(true), this._$EO?.forEach((t5) => t5.hostConnected?.());
}
enableUpdating(t5) {
}
disconnectedCallback() {
this._$EO?.forEach((t5) => t5.hostDisconnected?.());
}
attributeChangedCallback(t5, s3, i7) {
this._$AK(t5, i7);
}
_$EC(t5, s3) {
const i7 = this.constructor.elementProperties.get(t5), e5 = this.constructor._$Eu(t5, i7);
if (void 0 !== e5 && true === i7.reflect) {
const r6 = (void 0 !== i7.converter?.toAttribute ? i7.converter : u).toAttribute(s3, i7.type);
this._$Em = t5, null == r6 ? this.removeAttribute(e5) : this.setAttribute(e5, r6), this._$Em = null;
}
}
_$AK(t5, s3) {
const i7 = this.constructor, e5 = i7._$Eh.get(t5);
if (void 0 !== e5 && this._$Em !== e5) {
const t6 = i7.getPropertyOptions(e5), r6 = "function" == typeof t6.converter ? { fromAttribute: t6.converter } : void 0 !== t6.converter?.fromAttribute ? t6.converter : u;
this._$Em = e5, this[e5] = r6.fromAttribute(s3, t6.type), this._$Em = null;
}
}
requestUpdate(t5, s3, i7) {
if (void 0 !== t5) {
if (i7 ??= this.constructor.getPropertyOptions(t5), !(i7.hasChanged ?? f)(this[t5], s3)) return;
this.P(t5, s3, i7);
}
false === this.isUpdatePending && (this._$ES = this._$ET());
}
P(t5, s3, i7) {
this._$AL.has(t5) || this._$AL.set(t5, s3), true === i7.reflect && this._$Em !== t5 && (this._$Ej ??= /* @__PURE__ */ new Set()).add(t5);
}
async _$ET() {
this.isUpdatePending = true;
try {
await this._$ES;
} catch (t6) {
Promise.reject(t6);
}
const t5 = this.scheduleUpdate();
return null != t5 && await t5, !this.isUpdatePending;
}
scheduleUpdate() {
return this.performUpdate();
}
performUpdate() {
if (!this.isUpdatePending) return;
if (!this.hasUpdated) {
if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) {
for (const [t7, s4] of this._$Ep) this[t7] = s4;
this._$Ep = void 0;
}
const t6 = this.constructor.elementProperties;
if (t6.size > 0) for (const [s4, i7] of t6) true !== i7.wrapped || this._$AL.has(s4) || void 0 === this[s4] || this.P(s4, this[s4], i7);
}
let t5 = false;
const s3 = this._$AL;
try {
t5 = this.shouldUpdate(s3), t5 ? (this.willUpdate(s3), this._$EO?.forEach((t6) => t6.hostUpdate?.()), this.update(s3)) : this._$EU();
} catch (s4) {
throw t5 = false, this._$EU(), s4;
}
t5 && this._$AE(s3);
}
willUpdate(t5) {
}
_$AE(t5) {
this._$EO?.forEach((t6) => t6.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = true, this.firstUpdated(t5)), this.updated(t5);
}
_$EU() {
this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = false;
}
get updateComplete() {
return this.getUpdateComplete();
}
getUpdateComplete() {
return this._$ES;
}
shouldUpdate(t5) {
return true;
}
update(t5) {
this._$Ej &&= this._$Ej.forEach((t6) => this._$EC(t6, this[t6])), this._$EU();
}
updated(t5) {
}
firstUpdated(t5) {
}
};
b.elementStyles = [], b.shadowRootOptions = { mode: "open" }, b[d("elementProperties")] = /* @__PURE__ */ new Map(), b[d("finalized")] = /* @__PURE__ */ new Map(), p?.({ ReactiveElement: b }), (a.reactiveElementVersions ??= []).push("2.0.4");
// node_modules/lit-html/lit-html.js
var t2 = globalThis;
var i3 = t2.trustedTypes;
var s2 = i3 ? i3.createPolicy("lit-html", { createHTML: (t5) => t5 }) : void 0;
var e3 = "$lit$";
var h2 = `lit$${Math.random().toFixed(9).slice(2)}$`;
var o3 = "?" + h2;
var n3 = `<${o3}>`;
var r3 = document;
var l2 = () => r3.createComment("");
var c3 = (t5) => null === t5 || "object" != typeof t5 && "function" != typeof t5;
var a2 = Array.isArray;
var u2 = (t5) => a2(t5) || "function" == typeof t5?.[Symbol.iterator];
var d2 = "[ \n\f\r]";
var f2 = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g;
var v = /-->/g;
var _ = />/g;
var m = RegExp(`>|${d2}(?:([^\\s"'>=/]+)(${d2}*=${d2}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`, "g");
var p2 = /'/g;
var g = /"/g;
var $ = /^(?:script|style|textarea|title)$/i;
var y2 = (t5) => (i7, ...s3) => ({ _$litType$: t5, strings: i7, values: s3 });
var x = y2(1);
var b2 = y2(2);
var w = y2(3);
var T = Symbol.for("lit-noChange");
var E = Symbol.for("lit-nothing");
var A = /* @__PURE__ */ new WeakMap();
var C = r3.createTreeWalker(r3, 129);
function P(t5, i7) {
if (!a2(t5) || !t5.hasOwnProperty("raw")) throw Error("invalid template strings array");
return void 0 !== s2 ? s2.createHTML(i7) : i7;
}
var V = (t5, i7) => {
const s3 = t5.length - 1, o6 = [];
let r6, l3 = 2 === i7 ? "<svg>" : 3 === i7 ? "<math>" : "", c4 = f2;
for (let i8 = 0; i8 < s3; i8++) {
const s4 = t5[i8];
let a3, u3, d3 = -1, y3 = 0;
for (; y3 < s4.length && (c4.lastIndex = y3, u3 = c4.exec(s4), null !== u3); ) y3 = c4.lastIndex, c4 === f2 ? "!--" === u3[1] ? c4 = v : void 0 !== u3[1] ? c4 = _ : void 0 !== u3[2] ? ($.test(u3[2]) && (r6 = RegExp("</" + u3[2], "g")), c4 = m) : void 0 !== u3[3] && (c4 = m) : c4 === m ? ">" === u3[0] ? (c4 = r6 ?? f2, d3 = -1) : void 0 === u3[1] ? d3 = -2 : (d3 = c4.lastIndex - u3[2].length, a3 = u3[1], c4 = void 0 === u3[3] ? m : '"' === u3[3] ? g : p2) : c4 === g || c4 === p2 ? c4 = m : c4 === v || c4 === _ ? c4 = f2 : (c4 = m, r6 = void 0);
const x2 = c4 === m && t5[i8 + 1].startsWith("/>") ? " " : "";
l3 += c4 === f2 ? s4 + n3 : d3 >= 0 ? (o6.push(a3), s4.slice(0, d3) + e3 + s4.slice(d3) + h2 + x2) : s4 + h2 + (-2 === d3 ? i8 : x2);
}
return [P(t5, l3 + (t5[s3] || "<?>") + (2 === i7 ? "</svg>" : 3 === i7 ? "</math>" : "")), o6];
};
var N = class _N {
constructor({ strings: t5, _$litType$: s3 }, n6) {
let r6;
this.parts = [];
let c4 = 0, a3 = 0;
const u3 = t5.length - 1, d3 = this.parts, [f3, v2] = V(t5, s3);
if (this.el = _N.createElement(f3, n6), C.currentNode = this.el.content, 2 === s3 || 3 === s3) {
const t6 = this.el.content.firstChild;
t6.replaceWith(...t6.childNodes);
}
for (; null !== (r6 = C.nextNode()) && d3.length < u3; ) {
if (1 === r6.nodeType) {
if (r6.hasAttributes()) for (const t6 of r6.getAttributeNames()) if (t6.endsWith(e3)) {
const i7 = v2[a3++], s4 = r6.getAttribute(t6).split(h2), e5 = /([.?@])?(.*)/.exec(i7);
d3.push({ type: 1, index: c4, name: e5[2], strings: s4, ctor: "." === e5[1] ? H : "?" === e5[1] ? I : "@" === e5[1] ? L : k }), r6.removeAttribute(t6);
} else t6.startsWith(h2) && (d3.push({ type: 6, index: c4 }), r6.removeAttribute(t6));
if ($.test(r6.tagName)) {
const t6 = r6.textContent.split(h2), s4 = t6.length - 1;
if (s4 > 0) {
r6.textContent = i3 ? i3.emptyScript : "";
for (let i7 = 0; i7 < s4; i7++) r6.append(t6[i7], l2()), C.nextNode(), d3.push({ type: 2, index: ++c4 });
r6.append(t6[s4], l2());
}
}
} else if (8 === r6.nodeType) if (r6.data === o3) d3.push({ type: 2, index: c4 });
else {
let t6 = -1;
for (; -1 !== (t6 = r6.data.indexOf(h2, t6 + 1)); ) d3.push({ type: 7, index: c4 }), t6 += h2.length - 1;
}
c4++;
}
}
static createElement(t5, i7) {
const s3 = r3.createElement("template");
return s3.innerHTML = t5, s3;
}
};
function S2(t5, i7, s3 = t5, e5) {
if (i7 === T) return i7;
let h3 = void 0 !== e5 ? s3._$Co?.[e5] : s3._$Cl;
const o6 = c3(i7) ? void 0 : i7._$litDirective$;
return h3?.constructor !== o6 && (h3?._$AO?.(false), void 0 === o6 ? h3 = void 0 : (h3 = new o6(t5), h3._$AT(t5, s3, e5)), void 0 !== e5 ? (s3._$Co ??= [])[e5] = h3 : s3._$Cl = h3), void 0 !== h3 && (i7 = S2(t5, h3._$AS(t5, i7.values), h3, e5)), i7;
}
var M = class {
constructor(t5, i7) {
this._$AV = [], this._$AN = void 0, this._$AD = t5, this._$AM = i7;
}
get parentNode() {
return this._$AM.parentNode;
}
get _$AU() {
return this._$AM._$AU;
}
u(t5) {
const { el: { content: i7 }, parts: s3 } = this._$AD, e5 = (t5?.creationScope ?? r3).importNode(i7, true);
C.currentNode = e5;
let h3 = C.nextNode(), o6 = 0, n6 = 0, l3 = s3[0];
for (; void 0 !== l3; ) {
if (o6 === l3.index) {
let i8;
2 === l3.type ? i8 = new R(h3, h3.nextSibling, this, t5) : 1 === l3.type ? i8 = new l3.ctor(h3, l3.name, l3.strings, this, t5) : 6 === l3.type && (i8 = new z(h3, this, t5)), this._$AV.push(i8), l3 = s3[++n6];
}
o6 !== l3?.index && (h3 = C.nextNode(), o6++);
}
return C.currentNode = r3, e5;
}
p(t5) {
let i7 = 0;
for (const s3 of this._$AV) void 0 !== s3 && (void 0 !== s3.strings ? (s3._$AI(t5, s3, i7), i7 += s3.strings.length - 2) : s3._$AI(t5[i7])), i7++;
}
};
var R = class _R {
get _$AU() {
return this._$AM?._$AU ?? this._$Cv;
}
constructor(t5, i7, s3, e5) {
this.type = 2, this._$AH = E, this._$AN = void 0, this._$AA = t5, this._$AB = i7, this._$AM = s3, this.options = e5, this._$Cv = e5?.isConnected ?? true;
}
get parentNode() {
let t5 = this._$AA.parentNode;
const i7 = this._$AM;
return void 0 !== i7 && 11 === t5?.nodeType && (t5 = i7.parentNode), t5;
}
get startNode() {
return this._$AA;
}
get endNode() {
return this._$AB;
}
_$AI(t5, i7 = this) {
t5 = S2(this, t5, i7), c3(t5) ? t5 === E || null == t5 || "" === t5 ? (this._$AH !== E && this._$AR(), this._$AH = E) : t5 !== this._$AH && t5 !== T && this._(t5) : void 0 !== t5._$litType$ ? this.$(t5) : void 0 !== t5.nodeType ? this.T(t5) : u2(t5) ? this.k(t5) : this._(t5);
}
O(t5) {
return this._$AA.parentNode.insertBefore(t5, this._$AB);
}
T(t5) {
this._$AH !== t5 && (this._$AR(), this._$AH = this.O(t5));
}
_(t5) {
this._$AH !== E && c3(this._$AH) ? this._$AA.nextSibling.data = t5 : this.T(r3.createTextNode(t5)), this._$AH = t5;
}
$(t5) {
const { values: i7, _$litType$: s3 } = t5, e5 = "number" == typeof s3 ? this._$AC(t5) : (void 0 === s3.el && (s3.el = N.createElement(P(s3.h, s3.h[0]), this.options)), s3);
if (this._$AH?._$AD === e5) this._$AH.p(i7);
else {
const t6 = new M(e5, this), s4 = t6.u(this.options);
t6.p(i7), this.T(s4), this._$AH = t6;
}
}
_$AC(t5) {
let i7 = A.get(t5.strings);
return void 0 === i7 && A.set(t5.strings, i7 = new N(t5)), i7;
}
k(t5) {
a2(this._$AH) || (this._$AH = [], this._$AR());
const i7 = this._$AH;
let s3, e5 = 0;
for (const h3 of t5) e5 === i7.length ? i7.push(s3 = new _R(this.O(l2()), this.O(l2()), this, this.options)) : s3 = i7[e5], s3._$AI(h3), e5++;
e5 < i7.length && (this._$AR(s3 && s3._$AB.nextSibling, e5), i7.length = e5);
}
_$AR(t5 = this._$AA.nextSibling, i7) {
for (this._$AP?.(false, true, i7); t5 && t5 !== this._$AB; ) {
const i8 = t5.nextSibling;
t5.remove(), t5 = i8;
}
}
setConnected(t5) {
void 0 === this._$AM && (this._$Cv = t5, this._$AP?.(t5));
}
};
var k = class {
get tagName() {
return this.element.tagName;
}
get _$AU() {
return this._$AM._$AU;
}
constructor(t5, i7, s3, e5, h3) {
this.type = 1, this._$AH = E, this._$AN = void 0, this.element = t5, this.name = i7, this._$AM = e5, this.options = h3, s3.length > 2 || "" !== s3[0] || "" !== s3[1] ? (this._$AH = Array(s3.length - 1).fill(new String()), this.strings = s3) : this._$AH = E;
}
_$AI(t5, i7 = this, s3, e5) {
const h3 = this.strings;
let o6 = false;
if (void 0 === h3) t5 = S2(this, t5, i7, 0), o6 = !c3(t5) || t5 !== this._$AH && t5 !== T, o6 && (this._$AH = t5);
else {
const e6 = t5;
let n6, r6;
for (t5 = h3[0], n6 = 0; n6 < h3.length - 1; n6++) r6 = S2(this, e6[s3 + n6], i7, n6), r6 === T && (r6 = this._$AH[n6]), o6 ||= !c3(r6) || r6 !== this._$AH[n6], r6 === E ? t5 = E : t5 !== E && (t5 += (r6 ?? "") + h3[n6 + 1]), this._$AH[n6] = r6;
}
o6 && !e5 && this.j(t5);
}
j(t5) {
t5 === E ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t5 ?? "");
}
};
var H = class extends k {
constructor() {
super(...arguments), this.type = 3;
}
j(t5) {
this.element[this.name] = t5 === E ? void 0 : t5;
}
};
var I = class extends k {
constructor() {
super(...arguments), this.type = 4;
}
j(t5) {
this.element.toggleAttribute(this.name, !!t5 && t5 !== E);
}
};
var L = class extends k {
constructor(t5, i7, s3, e5, h3) {
super(t5, i7, s3, e5, h3), this.type = 5;
}
_$AI(t5, i7 = this) {
if ((t5 = S2(this, t5, i7, 0) ?? E) === T) return;
const s3 = this._$AH, e5 = t5 === E && s3 !== E || t5.capture !== s3.capture || t5.once !== s3.once || t5.passive !== s3.passive, h3 = t5 !== E && (s3 === E || e5);
e5 && this.element.removeEventListener(this.name, this, s3), h3 && this.element.addEventListener(this.name, this, t5), this._$AH = t5;
}
handleEvent(t5) {
"function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t5) : this._$AH.handleEvent(t5);
}
};
var z = class {
constructor(t5, i7, s3) {
this.element = t5, this.type = 6, this._$AN = void 0, this._$AM = i7, this.options = s3;
}
get _$AU() {
return this._$AM._$AU;
}
_$AI(t5) {
S2(this, t5);
}
};
var j = t2.litHtmlPolyfillSupport;
j?.(N, R), (t2.litHtmlVersions ??= []).push("3.2.1");
var B = (t5, i7, s3) => {
const e5 = s3?.renderBefore ?? i7;
let h3 = e5._$litPart$;
if (void 0 === h3) {
const t6 = s3?.renderBefore ?? null;
e5._$litPart$ = h3 = new R(i7.insertBefore(l2(), t6), t6, void 0, s3 ?? {});
}
return h3._$AI(t5), h3;
};
// node_modules/lit-element/lit-element.js
var r4 = class extends b {
constructor() {
super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0;
}
createRenderRoot() {
const t5 = super.createRenderRoot();
return this.renderOptions.renderBefore ??= t5.firstChild, t5;
}
update(t5) {
const s3 = this.render();
this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t5), this._$Do = B(s3, this.renderRoot, this.renderOptions);
}
connectedCallback() {
super.connectedCallback(), this._$Do?.setConnected(true);
}
disconnectedCallback() {
super.disconnectedCallback(), this._$Do?.setConnected(false);
}
render() {
return T;
}
};
r4._$litElement$ = true, r4["finalized"] = true, globalThis.litElementHydrateSupport?.({ LitElement: r4 });
var i4 = globalThis.litElementPolyfillSupport;
i4?.({ LitElement: r4 });
(globalThis.litElementVersions ??= []).push("4.1.1");
// node_modules/@lit/reactive-element/decorators/custom-element.js
var t3 = (t5) => (e5, o6) => {
void 0 !== o6 ? o6.addInitializer(() => {
customElements.define(t5, e5);
}) : customElements.define(t5, e5);
};
// node_modules/@lit/reactive-element/decorators/property.js
var o4 = { attribute: true, type: String, converter: u, reflect: false, hasChanged: f };
var r5 = (t5 = o4, e5, r6) => {
const { kind: n6, metadata: i7 } = r6;
let s3 = globalThis.litPropertyMetadata.get(i7);
if (void 0 === s3 && globalThis.litPropertyMetadata.set(i7, s3 = /* @__PURE__ */ new Map()), s3.set(r6.name, t5), "accessor" === n6) {
const { name: o6 } = r6;
return { set(r7) {
const n7 = e5.get.call(this);
e5.set.call(this, r7), this.requestUpdate(o6, n7, t5);
}, init(e6) {
return void 0 !== e6 && this.P(o6, void 0, t5), e6;
} };
}
if ("setter" === n6) {
const { name: o6 } = r6;
return function(r7) {
const n7 = this[o6];
e5.call(this, r7), this.requestUpdate(o6, n7, t5);
};
}
throw Error("Unsupported decorator location: " + n6);
};
function n4(t5) {
return (e5, o6) => "object" == typeof o6 ? r5(t5, e5, o6) : ((t6, e6, o7) => {
const r6 = e6.hasOwnProperty(o7);
return e6.constructor.createProperty(o7, r6 ? { ...t6, wrapped: true } : t6), r6 ? Object.getOwnPropertyDescriptor(e6, o7) : void 0;
})(t5, e5, o6);
}
// node_modules/lit-html/directive.js
var t4 = { ATTRIBUTE: 1, CHILD: 2, PROPERTY: 3, BOOLEAN_ATTRIBUTE: 4, EVENT: 5, ELEMENT: 6 };
var e4 = (t5) => (...e5) => ({ _$litDirective$: t5, values: e5 });
var i5 = class {
constructor(t5) {
}
get _$AU() {
return this._$AM._$AU;
}
_$AT(t5, e5, i7) {
this._$Ct = t5, this._$AM = e5, this._$Ci = i7;
}
_$AS(t5, e5) {
return this.update(t5, e5);
}
update(t5, e5) {
return this.render(...e5);
}
};
// node_modules/lit-html/directives/style-map.js
var n5 = "important";
var i6 = " !" + n5;
var o5 = e4(class extends i5 {
constructor(t5) {
if (super(t5), t5.type !== t4.ATTRIBUTE || "style" !== t5.name || t5.strings?.length > 2) throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.");
}
render(t5) {
return Object.keys(t5).reduce((e5, r6) => {
const s3 = t5[r6];
return null == s3 ? e5 : e5 + `${r6 = r6.includes("-") ? r6 : r6.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g, "-$&").toLowerCase()}:${s3};`;
}, "");
}
update(e5, [r6]) {
const { style: s3 } = e5.element;
if (void 0 === this.ft) return this.ft = new Set(Object.keys(r6)), this.render(r6);
for (const t5 of this.ft) null == r6[t5] && (this.ft.delete(t5), t5.includes("-") ? s3.removeProperty(t5) : s3[t5] = null);
for (const t5 in r6) {
const e6 = r6[t5];
if (null != e6) {
this.ft.add(t5);
const r7 = "string" == typeof e6 && e6.endsWith(i6);
t5.includes("-") || r7 ? s3.setProperty(t5, r7 ? e6.slice(0, -11) : e6, r7 ? n5 : "") : s3[t5] = e6;
}
}
return T;
}
});
// src/lit-events.ts
var LitDragStart = class _LitDragStart extends Event {
// container: HTMLElement;
constructor(source) {
super(_LitDragStart.eventName, { bubbles: true, composed: true });
this.offsetX = 0;
this.offsetY = 0;
this.offsetX = source.translateX;
this.offsetY = source.translateY;
this.node = source.host;
}
static {
this.eventName = "lit-drag-start";
}
};
var LitDragging = class _LitDragging extends Event {
// container: HTMLElement;
constructor(source) {
super(_LitDragging.eventName, { bubbles: true, composed: true });
this.offsetX = 0;
this.offsetY = 0;
this.offsetX = source.translateX;
this.offsetY = source.translateY;
this.node = source.host;
}
static {
this.eventName = "lit-dragging";
}
};
var LitDragEnd = class _LitDragEnd extends Event {
// container: HTMLElement;
constructor(source) {
super(_LitDragEnd.eventName, { bubbles: true, composed: true });
this.offsetX = 0;
this.offsetY = 0;
this.offsetX = source.initialX;
this.offsetY = source.initialY;
this.node = source.host;
}
static {
this.eventName = "lit-drag-end";
}
};
// src/lit-draggable.ts
var defaultOptions = {
preventSelect: true
};
var LitDraggable = class {
constructor(host, options = defaultOptions) {
this.translateX = 0;
this.translateY = 0;
this.initialX = 0;
this.initialY = 0;
this.handleOffsetX = 0;
this.handleOffsetY = 0;
this.dragging = false;
this.pointers = /* @__PURE__ */ new Set();
(this.host = host).addController(this);
this.options = options;
this.dragStart = this.dragStart.bind(this);
this.drag = this.drag.bind(this);
this.dragEnd = this.dragEnd.bind(this);
}
// TODO: Make this work with a handle, which will be a `ref` object.
hostConnected() {
console.log(this.host);
this.host.addEventListener("pointerdown", this.dragStart);
}
hostDisconnected() {
this.host.removeEventListener("pointerdown", this.dragStart);
}
dragStart(ev) {
if (ev.button === 2) {
return;
}
document.addEventListener("pointerup", this.dragEnd);
document.addEventListener("pointermove", this.drag);
const event_target = ev.composedPath()[0];
if (!(event_target === this.host || this.host.shadowRoot?.contains(event_target))) {
console.log("HUH?");
return;
}
this.pointers.add(ev.pointerId);
this.dragging = true;
if (this.options.preventSelect) {
this.currentSelect = document.body.style.userSelect;
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));
}
drag(ev) {
if (!this.dragging || !this.pointers.has(ev.pointerId)) {
return;
}
this.host.dataset.litDragging = "true";
ev.preventDefault();
this.translateX = ev.clientX - this.initialX;
this.translateY = ev.clientY - this.initialY;
this.host.dispatchEvent(new LitDragging(this));
this.host.style.setProperty("transform", `translate3d(${+this.translateX}px, ${+this.translateY}px, 0)`);
}
dragEnd(ev) {
document.removeEventListener("pointerup", this.dragEnd);
document.removeEventListener("pointermove", this.drag);
if (!this.dragging || !this.pointers.has(ev.pointerId)) {
return;
}
this.pointers.delete(ev.pointerId);
this.dragging = false;
delete this.host.dataset.litDrag;
delete this.host.dataset.litDragging;
if (this.currentSelect) {
document.body.style.userSelect = this.currentSelect;
this.currentSelect = void 0;
}
this.initialX = ev.clientX - this.handleOffsetX;
this.initialY = ev.clientY - this.handleOffsetY;
this.host.dispatchEvent(new LitDragEnd(this));
this.host.style.removeProperty("transform");
}
};
// src/fridge-tile.ts
var FridgeTile = class extends r4 {
constructor() {
super();
this.word = "";
this.rotation = Math.random() * 30 - 15;
this.dragHandle = new LitDraggable(this);
this.onDragEnd = this.onDragEnd.bind(this);
this.addEventListener("lit-drag-end", this.onDragEnd);
}
static get styles() {
return i`
:host {
display: block;
position: absolute;
}
:host([data-dragging="idle"]) {
cursor: grab;
}
:host([data-dragging="dragging"]) {
cursor: grabbing;
}
.word {
font-family: Georgia, Palatino, "Palatino Linotype", Times, "Times New Roman", serif;
box-shadow: 0 0 0.375rem 0.125rem #aaa;
text-align: center;
user-select: none;
cursor: pointer;
color: #444;
font-size: 0.9375rem;
padding: 0.1875rem 0.25rem 0.25rem 0.25rem;
position: relative;
background: white;
z-index: 100;
}
.word.dragging {
font-size: 1.1875rem;
}
`;
}
onDragEnd(ev) {
this.style.setProperty("top", `${+ev.offsetY}px`);
this.style.setProperty("left", `${+ev.offsetX}px`);
}
render() {
const styles = {
width: `${this.word.length}ch`,
transform: `rotate(${this.rotation}deg)`
};
return x`<div part="word" style="${o5(styles)}" class="word">${this.word}</div>`;
}
};
__decorateClass([
n4({ type: String })
], FridgeTile.prototype, "word", 2);
FridgeTile = __decorateClass([
t3("fridge-tile")
], FridgeTile);
// src/fridge-magnets.ts
var FridgeMagnets = class extends r4 {
static get styles() {
return i`
:host {
display: block;
}
#fridgemagnets {
width: 100%;
height: 100%;
display: grid;
grid-template-rows: 100fr 18ex;
}
#fridge {
overflow: hidden;
position: relative;
width: 100%;
background: url("./dist/pingbg.png") repeat;
}
#footer {
background-color: #32cd32;
width: 100%;
height: 18ex;
}
`;
}
render() {
return x` <div id="fridgemagnets">
<div id="fridge">
<fridge-tile word="Magnificent"></fridge-tile>
</div>
<div id="footer">Footer</div>
</div>`;
}
};
FridgeMagnets = __decorateClass([
t3("fridge-magnets")
], FridgeMagnets);
/*! Bundled license information:
@lit/reactive-element/css-tag.js:
(**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/reactive-element.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-html/lit-html.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-element/lit-element.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-html/is-server.js:
(**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/custom-element.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
@lit/reactive-element/decorators/property.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-html/directive.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-html/directives/style-map.js:
(**
* @license
* Copyright 2018 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
*/
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

View File

@ -0,0 +1,23 @@
html, body {
overflow: hidden;
padding: 0;
margin: 0;
}
#fridgemagnets {
width: 100vw;
height: 100vh;
}
#fridge {
overflow: hidden;
width: 100vw;
background: url('pingbg.png') repeat;
}
#footer {
background-color: #32cd32;
width: 100vw;
height: 18ex;
}

View File

@ -0,0 +1 @@
{"fullyTracked":true,"platform":"linux","arch":"x64","nodeVersion":"v21.7.3","command":"${NODE_RUNNER} build.mjs","extraArgs":[],"clean":true,"files":{"/home/elf/projects/fridgemagnets/package-lock.json":"d409c4caa75a369b0756c4a20afd229a1674727732ed0cb941fefa7c8654e8fd","/home/elf/projects/fridgemagnets/src/fridge-magnets.ts":"77c7985b22e6f2e312cc93d0f29874879f5f4588b379466b7b341c0cf6fe5672","/home/elf/projects/fridgemagnets/src/fridge-tile.ts":"a5d4d0c88404be2ef2b4a344c5ea9158c4f61550e4d0cb34385743499288bb8f","/home/elf/projects/fridgemagnets/src/index.ts":"af58cf7ef5b2c7248a9da8ed4a62f738d3a956d2e381763ce509ec73d76c253e","/home/elf/projects/fridgemagnets/src/lit-draggable.ts":"e8e573b657e0150a90cbba225c2f76c1c381f236c734527e99fcf7162a739b4c","/home/elf/projects/fridgemagnets/src/lit-events.ts":"68f685e47fa10d7f1c5945179904b58910c6ea4f89fdd599291f7fa3b2e0942e","/home/elf/projects/fridgemagnets/src/sat.ts":"ffed6dde0015ee7921976eec091d4d2310f7721b5424450e15eeef639cd96d51","/home/elf/projects/fridgemagnets/src/styles.css":"9f8ce850cf0cc61e861a6be7f1258cb96420c2f3e9de650e8cd9b112e846bd12","/home/elf/projects/fridgemagnets/src/types.ts":"c53748e2e8ce078819195c0e840f61acb20283082ff28e2923c20e8a8c8f1d0b","/home/elf/projects/fridgemagnets/src/wordlist.ts":"933b256ab705c637723c783dee2dd51a27cd98400a8216a655ac26a8629cfe5c","/home/elf/projects/package-lock.json":"4a146336c9879a2419c2630a53f3ce56e1cc9a1d1917481a2887d1f87d6d6b20"},"output":["dist/**","!.git/","!.hg/","!.svn/","!.wireit/","!.yarn/","!CVS/","!node_modules/"],"dependencies":{},"env":{"NODE_RUNNER":"node"}}

0
.wireit/6275696c64/lock Normal file
View File

View File

@ -0,0 +1 @@
{"/home/elf/projects/fridgemagnets/dist/index.js":{"t":"f","m":1732067209462.9365,"s":36632},"/home/elf/projects/fridgemagnets/dist/index.js.map":{"t":"f","m":1732067209462.9365,"s":264336},"/home/elf/projects/fridgemagnets/dist/pingbg.png":{"t":"f","m":1732067209364.9382,"s":360700},"/home/elf/projects/fridgemagnets/dist/styles.css":{"t":"f","m":1732067209360.9382,"s":306}}

0
.wireit/64656d6f/lock Normal file
View File

0
.wireit/7761746368/lock Normal file
View File

30
README.md Normal file
View File

@ -0,0 +1,30 @@
<p align="center">
<img width="200" src="https://open-wc.org/hero.png"></img>
</p>
## Open-wc Starter App
[![Built with open-wc recommendations](https://img.shields.io/badge/built%20with-open--wc-blue.svg)](https://github.com/open-wc)
## Quickstart
To get started:
```sh
npm init @open-wc
# requires node 10 & npm 6 or higher
```
## Scripts
- `start` runs your app for development, reloading on file changes
- `start:build` runs your app after it has been built using the build command
- `build` builds your app and outputs it in your `dist` directory
- `test` runs your test suite with Web Test Runner
- `lint` runs the linter for your project
## Tooling configs
For most of the tools, the configuration is in the `package.json` to reduce the amount of files in your project.
If you customize the configuration a lot, you can consider moving them to individual files.

29
assets/open-wc-logo.svg Normal file
View File

@ -0,0 +1,29 @@
<svg
width="244px"
height="244px"
viewBox="0 0 244 244"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
<stop stop-color="#9B00FF" offset="0%"></stop>
<stop stop-color="#0077FF" offset="100%"></stop>
</linearGradient>
</defs>
<g
id="Page-1"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
>
<path
d="M205.639259,176.936244 C207.430887,174.217233 209.093339,171.405629 210.617884,168.510161 M215.112174,158.724316 C216.385153,155.50304 217.495621,152.199852 218.433474,148.824851 M220.655293,138.874185 C221.231935,135.482212 221.637704,132.03207 221.863435,128.532919 M222,118.131039 C221.860539,114.466419 221.523806,110.85231 221.000113,107.299021 M218.885321,96.8583653 C218.001583,93.4468963 216.942225,90.1061026 215.717466,86.8461994 M211.549484,77.3039459 C209.957339,74.1238901 208.200597,71.0404957 206.290425,68.0649233 M200.180513,59.5598295 C181.848457,36.6639805 153.655709,22 122.036748,22 C66.7879774,22 22,66.771525 22,122 C22,177.228475 66.7879774,222 122.036748,222 C152.914668,222 180.52509,208.015313 198.875424,186.036326"
id="Shape"
stroke="url(#linearGradient-1)"
stroke-width="42.0804674"
></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

133
build.mjs
View File

@ -1,133 +0,0 @@
import { execFileSync } from "child_process";
import * as chokidar from "chokidar";
import esbuild from "esbuild";
import fs from "fs";
import { globSync } from "glob";
import path from "path";
import { cwd } from "process";
import process from "process";
import { fileURLToPath } from "url";
// Hack replaces what was lost when using Bun / later Node versions
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const isProdBuild = process.env.NODE_ENV === "production";
const definitions = {
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"process.env.CWD": JSON.stringify(cwd()),
};
// If you have assets in your src folder that won't be built/bundled, put them into "otherFiles" to
// copy them. All this is a replacement for rollup-copy-plugin, which I used to use.
const otherFiles = [
["./src/*.css", "."],
["./src/*.png", "."]
];
const isFile = (filePath) => fs.statSync(filePath).isFile();
function nameCopyTarget(src, dest, strip) {
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
return [src, target];
}
function copyOthers() {
for (const [source, rawdest, strip] of otherFiles) {
const matchedPaths = globSync(source);
const dest = path.join("dist", rawdest);
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
for (const [src, dest] of copyTargets) {
if (isFile(src)) {
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.copyFileSync(src, dest);
}
}
}
}
// This starts the definitions used for esbuild: Targets, arguments, the function for running a
// build, and the options for building: building or watching. If you're building more than one app,
// order them by the approximately largest project to smallest to build even faster.
const apps = [
["index.ts", "."]
];
const baseArgs = {
bundle: true,
write: true,
sourcemap: true,
minify: isProdBuild,
splitting: true,
treeShaking: true,
external: ["*.woff", "*.woff2"],
tsconfig: "./tsconfig.json",
loader: { ".css": "text", ".md": "text" },
define: definitions,
format: "esm",
};
async function buildOneSource(source, dest) {
const DIST = path.join(__dirname, "./dist", dest);
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
try {
const start = Date.now();
copyOthers();
await esbuild.build({
...baseArgs,
entryPoints: [`./src/${source}`],
entryNames: '[dir]/[name]',
outdir: DIST,
});
const end = Date.now();
console.log(
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${
Date.now() - start
}ms`,
);
} catch (exc) {
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
}
}
async function buildAll(apps) {
await Promise.allSettled(apps.map(([source, dest]) => buildOneSource(source, dest)));
}
let timeoutId = null;
function debouncedBuild() {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
console.log("\x1bc");
buildAll(apps);
}, 250);
}
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
console.log(`Build:
options:
-w, --watch: Build all ${apps.length} applications
-h, --help: This help message
`);
process.exit(0);
}
if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
console.log("Watching ./src for changes");
chokidar.watch("./src").on("all", (event, path) => {
if (!["add", "change", "unlink"].includes(event)) {
return;
}
if (!/(\.css|\.ts|\.js)$/.test(path)) {
return;
}
debouncedBuild();
});
} else {
await buildAll(apps);
}

View File

@ -1,84 +0,0 @@
import eslint from "@eslint/js";
import tsparser from "@typescript-eslint/parser";
import litconf from "eslint-plugin-lit";
import wcconf from "eslint-plugin-wc";
import globals from "globals";
import tseslint from "typescript-eslint";
export default [
// You would not believe how much this change has frustrated users: ["if an ignores key is used
// without any other keys in the configuration object, then the patterns act as global
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
{
ignores: [
"dist/",
// don't lint the cache
".wireit/",
// let packages have their own configurations
"packages/",
// don't ever lint node_modules
"node_modules/",
".storybook/*",
// don't lint build output (make sure it's set to your correct build folder name)
// don't lint nyc coverage output
"coverage/",
"src/locale-codes.ts",
"storybook-static/",
"src/locales/",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
},
files: ["src/**"],
rules: {
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules: {
"no-unused-vars": "off",
// We WANT our scripts to output to the console!
"no-console": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
];

View File

@ -1,15 +1,28 @@
<!DOCTYPE html> <!doctype html>
<html lang="en-GB"> <html lang="en">
<head>
<meta charset="utf-8" /> <head>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <meta charset="utf-8">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400..900&display=swap" rel="stylesheet" /> <meta name="Description" content="Put your description here.">
<script src="./dist/index.js" type="text/javascript"></script> <base href="/">
<link href="./dist/styles.css" rel="stylesheet" />
</head> <style>
<body> html,
<fridge-magnets id="fridgemagnets"> body {
</fridge-magnets> margin: 0;
</body> padding: 0;
font-family: sans-serif;
background-color: #ededed;
}
</style>
<title>fridge-magnets</title>
</head>
<body>
<fridge-magnets></fridge-magnets>
<script type="module" src="./out-tsc/src/fridge-magnets.js"></script>
</body>
</html> </html>

19434
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,168 +1,94 @@
{ {
"name": "elf-boilerplate", "name": "fridge-magnets",
"description": "Webcomponent fridge-magnets following open-wc recommendations",
"license": "MIT",
"author": "fridge-magnets",
"version": "0.0.0", "version": "0.0.0",
"type": "module",
"scripts": {
"lint": "eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore",
"format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore",
"prepare": "husky",
"test": "tsc && wtr --coverage",
"test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\"",
"storybook": "tsc && npm run analyze -- --exclude dist && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"storybook dev -p 8080\"",
"storybook:build": "tsc && npm run analyze -- --exclude dist && storybook build",
"build": "rimraf dist && tsc && rollup -c rollup.config.js && npm run analyze -- --exclude dist",
"start:build": "web-dev-server --root-dir dist --app-index index.html --open",
"analyze": "cem analyze --litelement",
"start": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"web-dev-server\""
},
"dependencies": { "dependencies": {
"@lit/localize": "^0.12.2", "lit": "^3.1.4"
"@neodrag/vanilla": "^2.0.5",
"lit": "^3.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/eslint__js": "^8.42.3", "@custom-elements-manifest/analyzer": "^0.10.3",
"@types/node": "^22.5.5", "@open-wc/eslint-config": "^12.0.3",
"@typescript-eslint/eslint-plugin": "^8.6.0", "@open-wc/testing": "^4.0.0",
"@typescript-eslint/parser": "^8.6.0", "@rollup/plugin-babel": "^6.0.4",
"chokidar": "^4.0.1", "@rollup/plugin-node-resolve": "^15.2.3",
"esbuild": "^0.24.0", "@storybook/addon-a11y": "^7.6.20",
"eslint": "^9.11.0", "@storybook/addon-essentials": "^7.6.20",
"eslint-plugin-lit": "^1.15.0", "@storybook/addon-links": "^7.6.20",
"eslint-plugin-wc": "^2.1.1", "@storybook/web-components": "^7.6.20",
"glob": "^10.4.5", "@types/mocha": "^10.0.7",
"http-server": "^14.1.1", "@typescript-eslint/eslint-plugin": "^7.16.0",
"knip": "^5.30.4", "@typescript-eslint/parser": "^7.16.0",
"lit-analyzer": "^2.0.3", "@web/dev-server": "^0.4.6",
"lockfile-lint": "^4.14.0", "@web/rollup-plugin-html": "^2.3.0",
"prettier": "^3.3.3", "@web/rollup-plugin-import-meta-assets": "^2.2.1",
"rimraf": "^5.0.10", "@web/storybook-builder": "^0.1.16",
"syncpack": "^13.0.0", "@web/storybook-framework-web-components": "^0.1.2",
"typescript": "^5.6.2", "@web/test-runner": "^0.18.2",
"typescript-eslint": "^8.6.0", "babel-plugin-template-html-minifier": "^4.1.0",
"wireit": "^0.14.9" "concurrently": "^8.2.2",
"deepmerge": "^4.3.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.7",
"prettier": "^3.3.2",
"rimraf": "^5.0.9",
"rollup": "^4.18.1",
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-workbox": "^8.1.0",
"storybook": "^7.6.20",
"tslib": "^2.6.3",
"typescript": "^5.5.3"
}, },
"engines": { "eslintConfig": {
"node": ">=20" "parser": "@typescript-eslint/parser",
}, "extends": [
"license": "MIT", "@open-wc",
"optionalDependencies": { "prettier"
"@esbuild/darwin-arm64": "^0.23.0",
"@esbuild/linux-amd64": "^0.18.11",
"@esbuild/linux-arm64": "^0.23.0"
},
"scripts": {
"build": "wireit",
"clean": "wireit",
"demo": "wireit",
"fix": "wireit",
"format": "wireit",
"lint": "wireit",
"realclean": "wireit",
"watch": "wireit"
},
"type": "module",
"wireit": {
"build": {
"command": "${NODE_RUNNER} build.mjs",
"files": [
"./src/**/*.{css,ts,js}"
], ],
"output": [ "plugins": [
"dist/**" "@typescript-eslint"
], ],
"env": { "rules": {
"NODE_RUNNER": { "no-unused-vars": "off",
"external": true, "@typescript-eslint/no-unused-vars": [
"default": "node" "error"
],
"import/no-unresolved": "off",
"import/extensions": [
"error",
"always",
{
"ignorePackages": true
} }
}
},
"clean": {
"command": "rimraf ./dist"
},
"demo": {
"command": "${NODE_RUNNER} ./node_modules/.bin/http-server . --port ${HTTP_DEMO_PORT}",
"server": true,
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
},
"HTTP_DEMO_PORT": {
"external": true,
"default": "8000"
}
}
},
"fix": {
"command": "prettier --write .",
"dependencies": [
"fix:eslint",
"fix:package"
] ]
},
"format": {
"command": "prettier --write ."
},
"fix:eslint": {
"command": "${NODE_RUNNER ./scripts/eslint.mjs --fix",
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"lint:components": {
"command": "lit-analyzer src"
},
"lint:eslint": {
"command": "${NODE_RUNNER} ./scripts/eslint.mjs --precommit",
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"lint:lockfile": {
"_comment": "Ensure every entry has a resolved hash",
"shell": true,
"command": "[ -z \"$(jq -r '.packages | to_entries[] | select((.key | startswith(\"node_modules\")) and (.value | has(\"resolved\") | not)) | .key' < package-lock.json)\" ]",
"dependencies": [
"lint:lockfile:base"
]
},
"lint:lockfile:base": {
"command": "lockfile-lint --path ./package-lock.json --allowed-hosts npm --validate-https --validate-integrity"
},
"lint:spelling": {
"command": "${NODE_RUNNER} scripts/check-spelling.mjs",
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
} }
}, },
"prettier": { "prettier": {
"command": "prettier ." "singleQuote": true,
"arrowParens": "avoid"
}, },
"lint": { "lint-staged": {
"dependencies": [ "*.ts": [
"lint:types", "eslint --fix",
"lint:components", "prettier --write"
"lint:lit",
"lint:lockfile",
"lint:eslint",
"prettier"
] ]
}, },
"lint:imports": { "customElements": "custom-elements.json"
"command": "knip --config scripts/knip.config.ts"
},
"lint:lit": {
"command": "lit-analyzer src"
},
"lint:types": {
"command": "tsc --noEmit -p ."
},
"watch": {
"command": "${NODE_RUNNER} build.mjs --watch",
"server": true,
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
}
}
} }

71
rollup.config.js Normal file
View File

@ -0,0 +1,71 @@
import nodeResolve from '@rollup/plugin-node-resolve';
import babel from '@rollup/plugin-babel';
import { rollupPluginHTML as html } from '@web/rollup-plugin-html';
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';
import esbuild from 'rollup-plugin-esbuild';
import { generateSW } from 'rollup-plugin-workbox';
import path from 'path';
export default {
input: 'index.html',
output: {
entryFileNames: '[hash].js',
chunkFileNames: '[hash].js',
assetFileNames: '[hash][extname]',
format: 'es',
dir: 'dist',
},
preserveEntrySignatures: false,
plugins: [
/** Enable using HTML as rollup entrypoint */
html({
minify: true,
injectServiceWorker: true,
serviceWorkerPath: 'dist/sw.js',
}),
/** Resolve bare module imports */
nodeResolve(),
/** Minify JS, compile JS to a lower language target */
esbuild({
minify: true,
target: ['chrome64', 'firefox67', 'safari11.1'],
}),
/** Bundle assets references via import.meta.url */
importMetaAssets(),
/** Minify html and css tagged template literals */
babel({
plugins: [
[
'babel-plugin-template-html-minifier',
{
modules: { lit: ['html', { name: 'css', encapsulation: 'style' }] },
failOnError: false,
strictCSS: true,
htmlMinifier: {
collapseWhitespace: true,
conservativeCollapse: true,
removeComments: true,
caseSensitive: true,
minifyCSS: true,
},
},
],
],
}),
/** Create and inject a service worker */
generateSW({
globIgnores: ['polyfills/*.js', 'nomodule-*.js'],
navigateFallback: '/index.html',
// where to output the generated sw
swDest: path.join('dist', 'sw.js'),
// directory to match patterns against to be precached
globDirectory: path.join('dist'),
// cache any html js and css by default
globPatterns: ['**/*.{html,js,css,webmanifest}'],
skipWaiting: true,
clientsClaim: true,
runtimeCaching: [{ urlPattern: 'polyfills/*.js', handler: 'CacheFirst' }],
}),
],
};

View File

@ -1,56 +0,0 @@
import { execFileSync } from "child_process";
import { ESLint } from "eslint";
import fs from "fs";
import path from "path";
import process from "process";
import { fileURLToPath } from "url";
function changedFiles() {
const gitStatus = execFileSync("git", ["diff", "--name-only", "HEAD"], { encoding: "utf8" });
const gitUntracked = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
encoding: "utf8",
});
const changed = gitStatus
.split("\n")
.filter((line) => line.trim().substring(0, 4) === "web/")
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
.map((line) => line.substring(4))
.filter((line) => fs.existsSync(line));
const untracked = gitUntracked
.split("\n")
.filter((line) => /\.(m|c)?(t|j)s$/.test(line))
.filter((line) => fs.existsSync(line));
const sourceFiles = [...changed, ...untracked].filter((line) => /^src\//.test(line));
const scriptFiles = [...changed, ...untracked].filter(
(line) => /^scripts\//.test(line) || !/^src\//.test(line),
);
return [...sourceFiles, ...scriptFiles];
}
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const projectRoot = path.join(__dirname, "..");
process.chdir(projectRoot);
const hasFlag = (flags) => process.argv.length > 1 && flags.includes(process.argv[2]);
const [configFile, files] = hasFlag(["-n", "--nightmare"])
? [path.join(__dirname, "eslint.nightmare.mjs"), changedFiles()]
: hasFlag(["-p", "--precommit"])
? [path.join(__dirname, "eslint.precommit.mjs"), changedFiles()]
: [path.join(projectRoot, "eslint.config.mjs"), ["."]];
const eslint = new ESLint({
overrideConfigFile: configFile,
warnIgnored: false,
});
const results = await eslint.lintFiles(files);
const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results);
const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
console.log(resultText);
process.exit(errors > 1 ? 1 : 0);

View File

@ -1,199 +0,0 @@
import eslint from "@eslint/js";
import tsparser from "@typescript-eslint/parser";
import litconf from "eslint-plugin-lit";
import wcconf from "eslint-plugin-wc";
import globals from "globals";
import tseslint from "typescript-eslint";
const MAX_DEPTH = 4;
const MAX_NESTED_CALLBACKS = 4;
const MAX_PARAMS = 5;
const rules = {
"accessor-pairs": "error",
"array-callback-return": "error",
"block-scoped-var": "error",
"consistent-return": "error",
"consistent-this": ["error", "that"],
"curly": ["error", "all"],
"dot-notation": [
"error",
{
allowKeywords: true,
},
],
"eqeqeq": "error",
"func-names": "error",
"guard-for-in": "error",
"max-depth": ["error", MAX_DEPTH],
"max-nested-callbacks": ["error", MAX_NESTED_CALLBACKS],
"max-params": ["error", MAX_PARAMS],
"new-cap": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-div-regex": "error",
"no-dupe-args": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-else-return": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-function": "error",
"no-labels": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-ex-assign": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-boolean-cast": "error",
"no-extra-label": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-implied-eval": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-inner-declarations": ["error", "functions"],
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-iterator": "error",
"no-invalid-this": "error",
"no-label-var": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": ["error", { ignore: [0, 1, -1] }],
"no-multi-str": "error",
"no-negated-condition": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-param-reassign": "error",
"no-proto": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-restricted-syntax": ["error", "WithStatement"],
"no-script-url": "error",
"no-self-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-undef-init": "error",
"no-unexpected-multiline": "error",
"no-useless-constructor": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unreachable": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-use-before-define": "error",
"no-useless-call": "error",
"no-dupe-class-members": "error",
"no-var": "error",
"no-void": "error",
"no-with": "error",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"radix": "error",
"require-yield": "error",
"strict": ["error", "global"],
"use-isnan": "error",
"valid-typeof": "error",
"vars-on-top": "error",
"yoda": ["error", "never"],
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
};
export default [
// You would not believe how much this change has frustrated users: ["if an ignores key is used
// without any other keys in the configuration object, then the patterns act as global
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
{
ignores: [
"dist/",
".wireit/",
// don't ever lint node_modules
"node_modules/",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.browser,
},
},
files: ["src/**"],
rules,
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules,
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
...globals.jest,
},
},
files: ["src/**/*.test.ts"],
rules,
},
];

View File

@ -1,73 +0,0 @@
import eslint from "@eslint/js";
import tsparser from "@typescript-eslint/parser";
import litconf from "eslint-plugin-lit";
import wcconf from "eslint-plugin-wc";
import globals from "globals";
import tseslint from "typescript-eslint";
export default [
// You would not believe how much this change has frustrated users: ["if an ignores key is used
// without any other keys in the configuration object, then the patterns act as global
// ignores"](https://eslint.org/docs/latest/use/configure/ignore)
{
ignores: [
"dist/",
".wireit/",
// don't ever lint node_modules
"node_modules/",
],
},
eslint.configs.recommended,
wcconf.configs["flat/recommended"],
litconf.configs["flat/recommended"],
...tseslint.configs.recommended,
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
},
files: ["src/**"],
rules: {
"no-unused-vars": "off",
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
{
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 12,
sourceType: "module",
},
globals: {
...globals.nodeBuiltin,
},
},
files: ["scripts/*.mjs", "*.ts", "*.mjs"],
rules: {
"no-unused-vars": "off",
"no-console": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
},
],
},
},
];

View File

@ -1,29 +0,0 @@
import { type KnipConfig } from "knip";
const config: KnipConfig = {
"entry": [
"./src/index.ts",
],
"project": ["src/**/*.ts", "src/**/*.js", "./scripts/*.mjs"],
// "ignore": ["src/**/*.test.ts", "src/**/*.stories.ts"],
// Prevent Knip from complaining about web components, which export their classes but also
// export their registration, and we don't always use both.
"ignoreExportsUsedInFile": true,
"typescript": {
config: ["tsconfig.json"],
},
"wireit": {
config: ["package.json"],
},
"eslint": {
entry: [
"eslint.config.mjs",
"scripts/eslint.precommit.mjs",
"scripts/eslint.nightmare.mjs",
"scripts/eslint.mjs",
],
config: ["package.json"],
}
};
export default config;

View File

@ -1,10 +0,0 @@
module.exports =
twitter:
consumer_key: "YOUR TWITTER CONSUMER KEY"
consumer_private_key: "YOUR TWITTER SECRET KEY"
access_token_key: "YOUR TWITTER ONE TIME ACCESS KEY"
access_token_secret: "YOUR TWITTER ONE TIME ACCESS SECRET"
tracker:
database: 'fridgemagnets'
username: 'fridgemagnets'
password: 'some witty password here'

View File

@ -1,151 +0,0 @@
express = require 'express'
mysql = require('db-mysql')
OAuth = require('oauth').OAuth
util = require('util')
config = require('./config')
fs = require('fs')
wordlist = JSON.parse(fs.readFileSync('./wordlist.js', 'utf-8'))
wordstream = (i.w for i in wordlist).join(' ')
body_to_haiku = (lines) ->
ret = (for words in lines
line = words[0].w
for word in words[1...words.length]
line += if word.s == 1 then word.w else ' ' + word.w
line).join(" / ")
console.log(ret)
ret
class AddressTracker
constructor: (database, username, password) ->
@db = new mysql.Database({
"hostname": "localhost"
"user": username
"password": password
"database": database})
@db.on('ready', () -> @connection = this)
@db.on('error', () -> console.log(arguments))
connect: (cb) ->
atrack = @
@db.connect () ->
atrack.connection = this
cb.apply(this, arguments)
validate: (ip_address, message, cb) ->
yesterday = new Date((new Date()).valueOf() - 1000 * 86400)
connection = @connection
connection.query().
select('*').
from('tweets').
where('address = ? and entered > ?', [ip_address, yesterday]).
execute (err, rows, cols) ->
return cb(err, null) if (err)
return cb("You've used up your allotted number of tweets today", null) if rows.length > 10
connection.query().
select('*').
from('tweets').
where('tweet = ?', [body_to_haiku(message.message)]).
execute (err, rows, cols) ->
return cb(err, null) if (err)
return cb("You've already sent that poem!", null) if rows.length > 0
connection.query().
insert('tweets', ['address', 'tweet', 'entered'], [ip_address, body_to_haiku(message.message), (new Date())]).
execute (err, result) ->
return cb(err, null) if err
cb(null, result)
class TwitterPoster
constructor: ->
@oauth = new OAuth(
"https://api.twitter.com/oauth/request_token",
"https://api.twitter.com/oauth/access_token",
config.twitter.consumer_key,
config.twitter.consumer_private_key,
"1.0",
null,
"HMAC-SHA1"
)
post: (message, callback) ->
@oauth.post(
"http://api.twitter.com/1/statuses/update.json",
config.twitter.access_token_key,
config.twitter.access_token_secret,
{"status": body_to_haiku(message.message) },
"application/json",
(error, data, response2) ->
if error
console.log(error) if error
callback(error, null)
return
callback(null, data)
)
app = module.exports = express.createServer()
# Configuration
app.configure ->
app.use express.bodyParser()
app.use express.methodOverride()
app.use express.logger()
app.use app.router
app.configure 'development', ->
app.use express.errorHandler
dumpExceptions: true
showStack: true
app.configure 'production', ->
app.use express.errorHandler()
all_good_words = (lines) ->
for words in lines
for word in words
if not (new RegExp('\\b' + word + '\\b')).test(wordstream)
return false
return true
address_tracker = new AddressTracker(config.tracker.database, config.tracker.username, config.tracker.password)
twitter_poster = new TwitterPoster()
# Our single route
app.post '/poems/', (req, res) ->
if not req.body? or not req.body.message?
res.send({error: true, code: -1, message: "We did not receive a poem."})
return
if not all_good_words(req.body.message)
res.send({error: true, code: -1, message: "ERROR -5: HACKSTOP."})
return
address_tracker.validate req.headers['x-forwarded-for'], req.body, (err, result) ->
if err != null
console.log(err)
res.send({error: true, code: 1, message: err})
return
twitter_poster.post req.body, (err, result) ->
if err != null
console.log(err)
res.send({error: true, code: 2, message: err})
return
res.send({error: false, message: result})
address_tracker.connect () ->
app.listen 8012
console.log "Express server listening on port %d in %s mode", app.address().port, app.settings.env

View File

@ -1,15 +0,0 @@
{
"name": "FridgemagnetServer",
"description": "A Twitter Forwarding Serverour CLI formatting friend.",
"version": "0.0.1",
"author": "Elf M. Sternberg <elf@pendorwright.com>",
"dependencies": {
"coffee-script": "1.2.0",
"oauth": "0.9.6",
"express": "2.5.8",
"forever": "0.8.5",
"db-mysql": "0.7.6"
},
"main": "./magnet_server",
"engines": { "node": ">= 0.6.2" }
}

View File

@ -1,7 +0,0 @@
BEGIN;
CREATE TABLE tweets (
address CHAR(42) NOT NULL,
tweet TEXT NOT NULL,
entered DATETIME NOT NULL
);
COMMIT;

View File

@ -1,4 +1,4 @@
import type { Position } from "./types"; import type { Position } from "./types.ts";
export class PointerLocationRequest extends Event { export class PointerLocationRequest extends Event {
static readonly eventName = "fridge-pointer-location"; static readonly eventName = "fridge-pointer-location";

View File

@ -42,11 +42,18 @@ export class FridgeBoard extends LitElement {
fridge-tile { fridge-tile {
position: absolute; position: absolute;
transition:
scale 200ms ease-out,
rotate 200ms ease-out;
will-change: scale rotate translate;
} }
.ready-to-slide { .ready-to-slide {
transition: transform 1500ms cubic-bezier(0.4, 0, 0.2, 1); transition:
will-change: transform; scale 1500ms ease-out,
rotate 1500ms ease-out,
translate 1500ms cubic-bezier(0.4, 0, 0.2, 1);
will-change: scale rotate translate;
} }
`; `;
} }
@ -84,7 +91,7 @@ export class FridgeBoard extends LitElement {
const isPointInBox = (point: Point) => const isPointInBox = (point: Point) =>
point[0] >= box.xl && point[0] <= box.xr && point[1] >= box.yt && point[1] <= box.yb; point[0] >= box.xl && point[0] <= box.xr && point[1] >= box.yt && point[1] <= box.yb;
if (pointsAlreadyPositioned.find((p) => isPointInBox(p)) === undefined) { if (pointsAlreadyPositioned.find(p => isPointInBox(p)) === undefined) {
tile.style.top = `${box.yt}px`; tile.style.top = `${box.yt}px`;
tile.style.left = `${box.xl}px`; tile.style.left = `${box.xl}px`;
return true; return true;
@ -106,19 +113,20 @@ export class FridgeBoard extends LitElement {
onPointerDown(ev: PointerEvent) { onPointerDown(ev: PointerEvent) {
const node = ev.target; const node = ev.target;
if (!(isHTMLElement(node) && node.tagName.toLowerCase() === "fridge-tile")) { if (!(isHTMLElement(node) && node.tagName.toLowerCase() === "fridge-tile")) {
return; return;
} }
// The position of the board with respect to the viewport; // The position of the board relative to the viewport;
const { left: fridgeLeft, top: fridgeTop } = this.getBoundingClientRect(); const { left: fridgeLeft, top: fridgeTop } = this.getBoundingClientRect();
// The position of the node with respect to the viewport: // The position of the node relative to the viewport:
const { left: nodeLeft, top: nodeTop } = node.getBoundingClientRect(); const { left: nodeLeft, top: nodeTop } = node.getBoundingClientRect();
// Where the pointer was when the event started with respect to the viewport; // The position of the pointer when the event started relative to the viewport;
const { x: pointerStartX, y: pointerStartY } = pointerLocation(); const { clientX: pointerStartX, clientY: pointerStartY } = ev;
let pointerX = pointerStartX;
let pointerY = pointerStartY;
// Starting position of the *node* with respect to the board; // Starting position of the *node* with respect to the board;
const tileStart = { const tileStart = {
@ -131,33 +139,45 @@ export class FridgeBoard extends LitElement {
let tracking = true; let tracking = true;
const pointer = (ev: PointerEvent) => {
const { clientX, clientY } = ev;
pointerX = clientX;
pointerY = clientY;
};
node.style.setProperty("scale", "1.3");
node.style.setProperty("rotate", `${Math.random() * 30 - 15}deg`);
const stop = () => { const stop = () => {
console.log("Stop called?");
controller.abort();
tracking = false; tracking = false;
const cursorPosition = pointerLocation(); controller.abort();
let newX = tileStart.x - (cursorPosition.x - pointerStartX); let delX = pointerX - pointerStartX;
let newY = tileStart.y - (cursorPosition.y - pointerStartY); let delY = pointerY - pointerStartY;
node.style.setProperty("transform", ""); node.style.removeProperty("translate");
node.style.setProperty("top", `${newY}px`); node.style.setProperty("top", `${tileStart.y + delY}px`);
node.style.setProperty("left", `${newX}px`); node.style.setProperty("left", `${tileStart.x + delX}px`);
requestAnimationFrame(() => {
node.style.setProperty("scale", "1.0");
node.style.setProperty("rotate", `${Math.random() * 30 - 15}deg`);
});
}; };
let animationFrame: number = -1; let animationFrame: number = -1;
const move = () => { const move = () => {
const cursorPosition = pointerLocation();
let delX = cursorPosition.x - pointerStartX;
let delY = cursorPosition.y - pointerStartY;
node.style.setProperty("transform", `translate3d(${+delX}px, ${+delY}px, 0)`);
if (tracking) { if (tracking) {
let delX = pointerX - pointerStartX;
let delY = pointerY - pointerStartY;
node.style.setProperty("translate", `${+delX}px ${+delY}px 0`);
animationFrame = requestAnimationFrame(move); animationFrame = requestAnimationFrame(move);
} else { return;
cancelAnimationFrame(animationFrame);
} }
cancelAnimationFrame(animationFrame);
return;
}; };
window.addEventListener("pointermove", pointer, { signal });
window.addEventListener("pointerup", stop, { signal }); window.addEventListener("pointerup", stop, { signal });
window.addEventListener("pointercancel", stop, { signal }); window.addEventListener("pointercancel", stop, { signal });
animationFrame = requestAnimationFrame(move); animationFrame = requestAnimationFrame(move);
@ -165,7 +185,7 @@ export class FridgeBoard extends LitElement {
render() { render() {
return html`<div id="fridge" @pointerdown=${this.onPointerDown}> return html`<div id="fridge" @pointerdown=${this.onPointerDown}>
${words.map((word) => html`<fridge-tile style="opacity: 0" word=${word.w}></fridge-tile>`)} ${words.map(word => html`<fridge-tile style="opacity: 0" word=${word.w}></fridge-tile>`)}
</div>`; </div>`;
} }
@ -178,30 +198,39 @@ export class FridgeBoard extends LitElement {
return Math.random() < 0.5 ? newDelta + size : newDelta * -1; return Math.random() < 0.5 ? newDelta + size : newDelta * -1;
}; };
requestAnimationFrame(() => const lastTile = this.tiles[this.tiles.length - 1];
this.tiles.forEach((tile) => {
const slideEnd = (ev: TransitionEvent) => {
if (ev.propertyName === "translate") {
this.tiles.forEach(tile => {
tile.classList.remove("ready-to-slide");
});
lastTile.removeEventListener("transitionend", slideEnd);
}
};
requestAnimationFrame(() => {
this.tiles.forEach(tile => {
const outerX = fd(boardWidth); const outerX = fd(boardWidth);
const outerY = fd(boardHeight); const outerY = fd(boardHeight);
const [tileX, tileY] = tile.relativePosition; const [tileX, tileY] = tile.relativePosition;
tile.style.transform = `translate(${outerX - tileX}px, ${outerY - tileY}px) rotate(${Math.random() * 30 - 15}deg) scale(1.5)`; tile.style.setProperty("translate", `${outerX - tileX}px ${outerY - tileY}px 0`);
tile.style.opacity = "100%"; tile.style.setProperty("scale", "1.5");
}) tile.style.setProperty("rotate", `${Math.random() * 720}deg`);
); tile.style.setProperty("opacity", "100%");
});
lastTile.addEventListener("transitionend", slideEnd);
requestAnimationFrame(() => requestAnimationFrame(() =>
this.tiles.forEach((tile) => { this.tiles.forEach(tile => {
tile.classList.add("ready-to-slide"); tile.classList.add("ready-to-slide");
tile.style.transform = `translate(0, 0) rotate(${Math.random() * 30}deg) scale(1.0)`; tile.style.setProperty("translate", "0 0 0");
tile.style.setProperty("rotate", `${Math.random() * 30 - 15}deg`);
tile.style.setProperty("scale", "1.0");
}) })
); );
});
setTimeout(
() =>
this.tiles.forEach((tile) => {
tile.classList.remove("ready-to-slide");
}),
1525
);
} }
} }

View File

@ -3,29 +3,18 @@ import { customElement } from "lit/decorators/custom-element.js";
import { property } from "lit/decorators/property.js"; import { property } from "lit/decorators/property.js";
import { styleMap } from "lit/directives/style-map.js"; import { styleMap } from "lit/directives/style-map.js";
import { ref, createRef, Ref } from "lit/directives/ref.js"; 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") @customElement("fridge-tile")
export class FridgeTile extends LitElement { export class FridgeTile extends LitElement {
@property({ type: String }) @property({ type: String })
word = ""; word = "";
dragHandle: LitDraggable;
transform = {
rotate: Math.random() * 30 - 15,
scale: 1.0,
};
handle: Ref<HTMLDivElement> = createRef(); handle: Ref<HTMLDivElement> = createRef();
static get styles() { static get styles() {
return css` return css`
:host { :host {
display: block; display: block;
position: absolute;
} }
:host([data-dragging="idle"]) { :host([data-dragging="idle"]) {
@ -100,78 +89,3 @@ export class FridgeTile extends LitElement {
return html`<div part="word ${ref(this.handle)} " style="${styleMap(styles)}" class="word">${this.word}</div>`; return html`<div part="word ${ref(this.handle)} " style="${styleMap(styles)}" class="word">${this.word}</div>`;
} }
} }
//
// base_style:
// 'font-size': "15px"
//
// drag_style:
// 'font-size': "19px"
//
// visible: false
//
// # Initial tilt.
// rotation: (Math.random() * 30) - 15
//
// constructor: (@word, @board, @master) ->
// super()
// @el = $('<div class="word">' + @word.w + '</div>')
// @el.css @base_style
// @board.append(@el)
// @rotation = (Math.random() * 30) - 15
//
// @el.draggable
// helper: "original"
// refreshPositions: false
// revertDuration: 1
//
// start: (event) =>
// mod = (Math.random() * 16) - 8
// @rotation = if Math.abs(@rotation + mod) > 15 then @rotation - mod else @rotation + mod
// style = clone(@drag_style)
// style.rotate = @rotation
// @el.animate(style, 200, () => @new_width = @el.width())
// true
//
// stop: (event) =>
// # Drop the thing dead center, at least on the x-axis,
// # and animate its return to the new font size.
// mod = (Math.random() * 16) - 8
// @rotation = if Math.abs(@rotation + mod) > 15 then @rotation - mod else @rotation + mod
// style = clone(@base_style)
// style.rotate = @rotation
// style['left'] = parseInt(@el.position().left + (0.5 * (@new_width - @width())))
// @el.animate style, 200, 'easeOutQuad', () =>
// @reset_dims()
// explode_hearts(@board, @)
// @master.poemed(@)
// true
//
// fadeOut: -> $.Deferred((d) => @el.fadeOut('fast', (() => @unset_dims(); @visible = false; d.resolve()))).promise()
//
// # Shape for deteriming poemed collision
// fuzzyshape: -> shape @left() - WIDTH_FUZZ, @top() - HEIGHT_FUZZ, @width() + (2 * WIDTH_FUZZ), @height() + (2 * HEIGHT_FUZZ)
//
// get_new_pos: ->
// bh = => parseInt(Math.random() * (@board.height() - @height()) * 0.985)
// bw = => parseInt(Math.random() * (@board.width() - @width()) * 0.98)
// [top, left] = [bh(), bw()]
// [top, left] = [bh(), bw()] until @master.unoccupied(left, top, @width(), @height())
// [top, left]
//
// flyIn: ->
// fd = (mod) ->
// m = parseInt(40 * Math.random())
// if (Math.random() < 0.5) then mod + m else -1 * m
// @el.css
// left: fd(@board.width())
// top: fd(@board.height())
// dfd = $.Deferred()
// x = Math.random()
// [top, left] = @get_new_pos()
// @el.fadeIn().animate {top: top, left: left, rotate: @rotation}, 1500, 'easeOutQuint', () =>
// @visible = true
// dfd.resolve()
// dfd.promise()
//
//

View File

@ -1,3 +1,5 @@
import { html, css, LitElement } from "lit";
export class FixedFooter extends LitElement { export class FixedFooter extends LitElement {
static get styles() { static get styles() {
return css``; return css``;

View File

@ -1,4 +1,4 @@
import { LitDraggable, LitDragEvent } from "./types"; import type { LitDraggable, LitDragEvent } from "./types.ts";
export class LitDragStart extends Event implements LitDragEvent { export class LitDragStart extends Event implements LitDragEvent {
static readonly eventName = "lit-drag-start"; static readonly eventName = "lit-drag-start";

View File

@ -0,0 +1,30 @@
import { html, TemplateResult } from 'lit';
import '../src/fridge-magnets.js';
export default {
title: 'FridgeMagnets',
component: 'fridge-magnets',
argTypes: {
backgroundColor: { control: 'color' },
},
};
interface Story<T> {
(args: T): TemplateResult;
args?: Partial<T>;
argTypes?: Record<string, unknown>;
}
interface ArgTypes {
header?: string;
backgroundColor?: string;
}
const Template: Story<ArgTypes> = ({ header, backgroundColor = 'white' }: ArgTypes) => html`
<fridge-magnets style="--fridge-magnets-background-color: ${backgroundColor}" .header=${header}></fridge-magnets>
`;
export const App = Template.bind({});
App.args = {
header: 'My app',
};

View File

@ -0,0 +1,22 @@
import { html } from 'lit';
import { fixture, expect } from '@open-wc/testing';
import type { FridgeMagnets } from '../src/fridge-magnets.js';
import '../src/fridge-magnets.js';
describe('FridgeMagnets', () => {
let element: FridgeMagnets;
beforeEach(async () => {
element = await fixture(html`<fridge-magnets></fridge-magnets>`);
});
it('renders a h1', () => {
const h1 = element.shadowRoot!.querySelector('h1')!;
expect(h1).to.exist;
expect(h1.textContent).to.equal('My app');
});
it('passes the a11y audit', async () => {
await expect(element).shadowDom.to.be.accessible();
});
});

View File

@ -1,61 +0,0 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"esModuleInterop": true,
"paths": {
"@goauthentik/docs/*": ["../website/docs/*"]
},
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"sourceMap": true,
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"lib": [
"ES5",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020",
"ESNext",
"DOM",
"DOM.Iterable",
"WebWorker"
],
"noUnusedLocals": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"useDefineForClassFields": false,
"alwaysStrict": true,
"noImplicitAny": true,
"plugins": [
{
"name": "ts-lit-plugin",
"strict": true,
"rules": {
"no-unknown-tag-name": "off",
"no-missing-import": "off",
"no-incompatible-type-binding": "off",
"no-unknown-property": "off",
"no-unknown-attribute": "off"
}
},
{
"name": "@genesiscommunitysuccess/custom-elements-lsp",
"designSystemPrefix": "ak-",
"parser": {
"timeout": 2000
}
}
]
}
}

View File

@ -1,17 +1,21 @@
{ {
"extends": "./tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"paths": { "target": "es2021",
"@goauthentik/admin/*": ["./src/admin/*"], "module": "NodeNext",
"@goauthentik/common/*": ["./src/common/*"], "moduleResolution": "NodeNext",
"@goauthentik/components/*": ["./src/components/*"], "noEmitOnError": true,
"@goauthentik/docs/*": ["../website/docs/*"], "lib": ["es2021", "dom", "DOM.Iterable"],
"@goauthentik/elements/*": ["./src/elements/*"], "strict": true,
"@goauthentik/flow/*": ["./src/flow/*"], "esModuleInterop": false,
"@goauthentik/locales/*": ["./src/locales/*"], "allowSyntheticDefaultImports": true,
"@goauthentik/polyfill/*": ["./src/polyfill/*"], "experimentalDecorators": true,
"@goauthentik/standalone/*": ["./src/standalone/*"], "importHelpers": true,
"@goauthentik/user/*": ["./src/user/*"] "outDir": "out-tsc",
} "sourceMap": true,
} "inlineSources": true,
"rootDir": "./",
"incremental": true,
"skipLibCheck": true
},
"include": ["**/*.ts"]
} }

26
web-dev-server.config.js Normal file
View File

@ -0,0 +1,26 @@
// import { hmrPlugin, presets } from '@open-wc/dev-server-hmr';
/** Use Hot Module replacement by adding --hmr to the start command */
const hmr = process.argv.includes('--hmr');
export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
open: '/',
watch: !hmr,
/** Resolve bare module imports */
nodeResolve: {
exportConditions: ['browser', 'development'],
},
/** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
// esbuildTarget: 'auto'
/** Set appIndex to enable SPA routing */
appIndex: './index.html',
plugins: [
/** Use Hot Module Replacement by uncommenting. Requires @open-wc/dev-server-hmr plugin */
// hmr && hmrPlugin({ exclude: ['**/*/node_modules/**/*'], presets: [presets.litElement] }),
],
// See documentation for all available options
});

41
web-test-runner.config.js Normal file
View File

@ -0,0 +1,41 @@
// import { playwrightLauncher } from '@web/test-runner-playwright';
const filteredLogs = ['Running in dev mode', 'Lit is in dev mode'];
export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
/** Test files to run */
files: 'out-tsc/test/**/*.test.js',
/** Resolve bare module imports */
nodeResolve: {
exportConditions: ['browser', 'development'],
},
/** Filter out lit dev mode logs */
filterBrowserLogs(log) {
for (const arg of log.args) {
if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) {
return false;
}
}
return true;
},
/** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
// esbuildTarget: 'auto',
/** Amount of browsers to run concurrently */
// concurrentBrowsers: 2,
/** Amount of test files per browser to test concurrently */
// concurrency: 1,
/** Browsers to run tests on */
// browsers: [
// playwrightLauncher({ product: 'chromium' }),
// playwrightLauncher({ product: 'firefox' }),
// playwrightLauncher({ product: 'webkit' }),
// ],
// See documentation for all available options
});