Compare commits

...

2 Commits

Author SHA1 Message Date
Elf M. Sternberg 97dde3bdfe Removing cruft. 2025-01-20 16:03:38 -08:00
Elf M. Sternberg 141a7c0461 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.
2025-01-20 16:03:18 -08:00
31 changed files with 21770 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
# Edit at https://www.gitignore.io/?templates=node
## editors
/.idea
/.vscode
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
## system files
.DS_Store
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
## npm
/node_modules/
/npm-debug.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
## testing
/coverage/
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
## temp folders
/.tmp/
# Coverage directory used by tools like istanbul
coverage
*.lcov
# 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
# build
/_site/
/dist/
/out-tsc/
storybook-static
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;

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

(image error) 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>
<html lang="en-GB">
<head>
<meta charset="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400..900&display=swap" rel="stylesheet" />
<script src="./dist/index.js" type="text/javascript"></script>
<link href="./dist/styles.css" rel="stylesheet" />
</head>
<body>
<fridge-magnets id="fridgemagnets">
</fridge-magnets>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="Description" content="Put your description here.">
<base href="/">
<style>
html,
body {
margin: 0;
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>

26336
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,168 +1,94 @@
{
"name": "elf-boilerplate",
"version": "0.0.0",
"dependencies": {
"@lit/localize": "^0.12.2",
"@neodrag/vanilla": "^2.0.5",
"lit": "^3.2.0"
},
"devDependencies": {
"@types/eslint__js": "^8.42.3",
"@types/node": "^22.5.5",
"@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^8.6.0",
"chokidar": "^4.0.1",
"esbuild": "^0.24.0",
"eslint": "^9.11.0",
"eslint-plugin-lit": "^1.15.0",
"eslint-plugin-wc": "^2.1.1",
"glob": "^10.4.5",
"http-server": "^14.1.1",
"knip": "^5.30.4",
"lit-analyzer": "^2.0.3",
"lockfile-lint": "^4.14.0",
"prettier": "^3.3.3",
"rimraf": "^5.0.10",
"syncpack": "^13.0.0",
"typescript": "^5.6.2",
"typescript-eslint": "^8.6.0",
"wireit": "^0.14.9"
},
"engines": {
"node": ">=20"
},
"license": "MIT",
"optionalDependencies": {
"@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": [
"dist/**"
],
"env": {
"NODE_RUNNER": {
"external": true,
"default": "node"
}
}
},
"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": {
"command": "prettier ."
},
"lint": {
"dependencies": [
"lint:types",
"lint:components",
"lint:lit",
"lint:lockfile",
"lint:eslint",
"prettier"
]
},
"lint:imports": {
"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"
}
}
"name": "fridge-magnets",
"description": "Webcomponent fridge-magnets following open-wc recommendations",
"license": "MIT",
"author": "fridge-magnets",
"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": {
"lit": "^3.1.4"
},
"devDependencies": {
"@custom-elements-manifest/analyzer": "^0.10.3",
"@open-wc/eslint-config": "^12.0.3",
"@open-wc/testing": "^4.0.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^15.2.3",
"@storybook/addon-a11y": "^7.6.20",
"@storybook/addon-essentials": "^7.6.20",
"@storybook/addon-links": "^7.6.20",
"@storybook/web-components": "^7.6.20",
"@types/mocha": "^10.0.7",
"@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.16.0",
"@web/dev-server": "^0.4.6",
"@web/rollup-plugin-html": "^2.3.0",
"@web/rollup-plugin-import-meta-assets": "^2.2.1",
"@web/storybook-builder": "^0.1.16",
"@web/storybook-framework-web-components": "^0.1.2",
"@web/test-runner": "^0.18.2",
"babel-plugin-template-html-minifier": "^4.1.0",
"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"
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"extends": [
"@open-wc",
"prettier"
],
"plugins": [
"@typescript-eslint"
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error"
],
"import/no-unresolved": "off",
"import/extensions": [
"error",
"always",
{
"ignorePackages": true
}
]
}
}
},
"prettier": {
"singleQuote": true,
"arrowParens": "avoid"
},
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
]
},
"customElements": "custom-elements.json"
}

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 {
static readonly eventName = "fridge-pointer-location";

View File

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

View File

@ -3,29 +3,18 @@ import { customElement } from "lit/decorators/custom-element.js";
import { property } from "lit/decorators/property.js";
import { styleMap } from "lit/directives/style-map.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")
export class FridgeTile extends LitElement {
@property({ type: String })
word = "";
dragHandle: LitDraggable;
transform = {
rotate: Math.random() * 30 - 15,
scale: 1.0,
};
handle: Ref<HTMLDivElement> = createRef();
static get styles() {
return css`
:host {
display: block;
position: absolute;
}
: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>`;
}
}
//
// 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 {
static get styles() {
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 {
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": {
"paths": {
"@goauthentik/admin/*": ["./src/admin/*"],
"@goauthentik/common/*": ["./src/common/*"],
"@goauthentik/components/*": ["./src/components/*"],
"@goauthentik/docs/*": ["../website/docs/*"],
"@goauthentik/elements/*": ["./src/elements/*"],
"@goauthentik/flow/*": ["./src/flow/*"],
"@goauthentik/locales/*": ["./src/locales/*"],
"@goauthentik/polyfill/*": ["./src/polyfill/*"],
"@goauthentik/standalone/*": ["./src/standalone/*"],
"@goauthentik/user/*": ["./src/user/*"]
}
}
"compilerOptions": {
"target": "es2021",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmitOnError": true,
"lib": ["es2021", "dom", "DOM.Iterable"],
"strict": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"importHelpers": true,
"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
});