Updated all NPM versioning and switched to lit-html.

This commit is contained in:
Elf M. Sternberg 2023-02-14 17:09:01 -08:00
parent 6c9ac44eac
commit 0ba271be28
9 changed files with 16424 additions and 12024 deletions

View File

@ -1,58 +1,58 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "eslint-plugin-import", "prettier"],
"root": true,
"env": {
"browser": true,
"es2021": true
},
"extends": ["plugin:@typescript-eslint/recommended", "prettier"],
"parserOptions": {
"project": ["tsconfig.json"],
"ecmaVersion": 2020,
"sourceType": "module"
},
"rules": {
"no-await-in-loop": "off",
"no-use-before-define": "off",
"no-nested-ternary": "off",
"@typescript-eslint/no-use-before-define": ["error"],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unused-vars": "off",
"import/extensions": "off",
"import/prefer-default-export": "off",
"@typescript-eslint/ban-types": "warn",
"@typescript-eslint/no-use-before-define": "warn",
"arrow-body-style": "warn",
"camelcase": "warn",
"dot-notation": "warn",
"eqeqeq": "warn",
"import/first": "warn",
"import/newline-after-import": "warn",
"import/no-extraneous-dependencies": "warn",
"import/order": "warn",
"no-else-return": "warn",
"no-param-reassign": "warn",
"no-return-assign": "warn",
"no-sequences": "warn",
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["warn"],
"no-underscore-dangle": "warn",
"no-unneeded-ternary": "warn",
"object-shorthand": "warn",
"prefer-arrow-callback": "warn",
"prefer-const": "warn",
"prefer-destructuring": "warn",
"prefer-template": "warn"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".ts"]
},
"typescript": {
"project": "./tsconfig.json"
}
}
}
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier", "jest", "unused-imports"],
"extends": ["@open-wc", "prettier"],
"rules": {
"@typescript-eslint/ban-ts-comment": "warn",
"@typescript-eslint/ban-types": "warn",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-shadow": ["warn"],
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-use-before-define": ["error", { "functions": false }],
"arrow-body-style": "warn",
"camelcase": "off",
"default-param-last": "off",
"dot-notation": "warn",
"eqeqeq": "warn",
"import/extensions": "off",
"import/first": "warn",
"import/newline-after-import": "warn",
"import/no-cycle": "warn",
"import/no-extraneous-dependencies": "warn",
"import/order": "warn",
"import/prefer-default-export": "warn",
"no-else-return": "warn",
"no-nested-ternary": "off",
"no-param-reassign": "warn",
"no-return-assign": "warn",
"no-sequences": "warn",
"no-shadow": "off",
"no-underscore-dangle": "off",
"no-unneeded-ternary": "warn",
"no-unused-vars": "off",
"no-use-before-define": "off",
"object-shorthand": "warn",
"one-var": ["error", "never"],
"one-var-declaration-per-line": ["error", "initializations"],
"prefer-arrow-callback": "warn",
"prefer-const": "warn",
"prefer-destructuring": "warn",
"prefer-template": "warn",
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
],
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
]
}
}

View File

@ -1,8 +1,10 @@
{
"trailingComma": "es5",
"printWidth": 110,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": false
"useTabs": false,
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSameLine": false,
"arrowParens": "avoid"
}

View File

@ -15,7 +15,8 @@
font-family: "Mulish", sans-serif;
font-size: 16px;
line-height: 1.36;
letter-spacing: 0.012em;
letter-spacing: 0.012em;
padding: 1rem;
}
p {
color: #282828;
@ -32,10 +33,11 @@
algorithm of the watch.
</p>
<p>
25 years later, I found the source code buried somewhere in an old CD. I decided to try an
re-implement the logic in Javascript, and to experiment with a hand-written
<a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Component</a>. This is
the result.
25 years later, I found the source code buried somewhere in an old
CD. I decided to try an re-implement the logic in Javascript, and to
experiment with a
<a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components">Web Component</a>,
using the <a href="https://lit.dev">lit-html library</a>. This is the result.
</p>
<p>
It might seem a bit silly to have implemented the seconds face when the minutes face has a

27895
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,36 @@
{
"name": "DominoClock",
"version": "1.0.0",
"description": "The Domino Clock",
"main": "build/index.js",
"scripts": {
"build": "vite build --base='./'",
"lint": "eslint",
"dev": "vite -m development",
"test": "jest"
},
"author": "Elf M. Sternberg <elf.sternberg@gmail.com>",
"license": "MPL-2.0",
"devDependencies": {
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint": "^8.2.0",
"eslint-config-airbnb": "^19.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.3.1",
"prettier": "^2.4.1",
"rimraf": "^3.0.2",
"typescript": "^4.4.4",
"vite": "^2.6.14",
"vite-plugin-compression": "^0.3.5",
"vite-plugin-ejs": "^1.4.3",
"vite-plugin-eslint": "^1.3.0"
}
"name": "DominoClock",
"version": "1.0.0",
"description": "The Domino Clock",
"main": "build/index.js",
"scripts": {
"build": "vite build --base='./'",
"lint": "eslint",
"dev": "vite -m development",
"test": "jest"
},
"author": "Elf M. Sternberg <elf.sternberg@gmail.com>",
"license": "MPL-2.0",
"dependencies": {
"lit": "^2.0.2"
},
"devDependencies": {
"@open-wc/eslint-config": "^9.2.1",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"eslint": "^8.3.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"jest": "^27.3.1",
"prettier": "^2.4.1",
"typescript": "^4.5.2",
"vite": "^2.6.14",
"vite-plugin-compression": "^0.3.5",
"vite-plugin-ejs": "^1.4.3",
"vite-plugin-eslint": "^1.3.0"
}
}

77
src/DominoClock.ts Normal file
View File

@ -0,0 +1,77 @@
import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import "./DominoFace.ts";
type Times = number[];
type Cats = Times;
const MATRIX: Cats[] = [
[1, 5, 8, 9, 11],
[2, 5, 6, 10, 11],
[3, 6, 7, 9, 11],
[4, 7, 8, 10, 11],
];
const tock = (time: number) => [0, 1, 2, 3].map(i => MATRIX[i].includes(time));
@customElement("domino-clock")
export class DominoClock extends LitElement {
timer: number;
@state() hour = [false, false, false, false];
@state() minute = [false, false, false, false];
@state() second = [false, false, false, false];
constructor() {
super();
this.timer = 0;
this.paint = this.paint.bind(this);
}
paint() {
const now = new Date();
const rawHours = now.getHours();
this.hour = tock(rawHours > 12 ? rawHours - 12 : rawHours);
this.minute = tock(Math.floor(now.getUTCMinutes() / 5));
this.second = tock(Math.floor(now.getUTCSeconds() / 5));
this.timer = window.setTimeout(this.paint, 250);
}
connectedCallback() {
if (super.connectedCallback) {
super.connectedCallback();
}
this.timer = window.setTimeout(this.paint, 250);
}
disconnectedCallback() {
window.clearTimeout(this.timer);
if (super.disconnectedCallback) {
super.disconnectedCallback();
}
}
static styles = css`
:host {
--dominoclock-default-direction: row;
--dominoclock-default-face-gap: 0.25rem;
}
.dominoclock {
display: flex;
flex-direction: var(--dominoclock-direction, var(--dominoclock-default-direction, row));
gap: var(--dominoclock-face-gap, var(--dominoclock-default-face-gap, 0.25rem));
}
`;
render() {
return html` <div class="dominoclock">
<domino-face .time=${this.hour}></domino-face>
<domino-face .time=${this.minute}></domino-face>
<domino-face .time=${this.second}></domino-face>
</div>`;
}
}
export default DominoClock;

71
src/DominoFace.ts Normal file
View File

@ -0,0 +1,71 @@
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
type Dots = [boolean, boolean, boolean, boolean];
function timeChanged(oldvalue: Dots, newvalue: Dots): boolean {
if (!newvalue) {
return true;
}
return !oldvalue.reduce((acc, t, idx) => acc && t === newvalue[idx], true);
}
@customElement("domino-face")
export class DominoClockface extends LitElement {
@property({ type: Array, hasChanged: timeChanged }) time = [false, false, false, false];
static styles = css`
:host {
--dominoclock-default-dot-size: 1rem;
--dominoclock-default-dot-color: #2f4f4f;
--dominoclock-default-face-size: 5rem;
--dominoclock-default-background-color: #daaf20;
--dominoclock-default-border-radius: 0.5rem;
}
.face {
position: relative;
width: var(--dominoclock-face-size, var(--dominoclock-default-face-size, 5rem));
height: var(--dominoclock-face-size, var(--dominoclock-default-face-size, 5rem));
background-color: var(
--dominoclock-background-color,
var(--dominoclock-default-background-color, #daaf20)
);
border: 1px solid #282828;
border-radius: var(--dominoclock-border-radius, var(--dominoclock-default-border-radius, 0.5rem));
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
.dot {
width: 100%;
height: 100%;
display: grid;
place-items: center center;
}
.dot > div {
transition: opacity 0.3s ease;
width: var(--dominoclock-date-size, var(--dominoclock-default-date-size, 1rem));
height: var(--dominoclock-date-size, var(--dominoclock-default-date-size, 1rem));
background-color: var(--dominoclock-dot-color, var(--dominoclock-default-dot-color, #2f4f4f));
border-radius: calc(var(--dominoclock-dot-size, var(--dominoclock-default-dot-size, 1rem)) / 2);
opacity: 0;
}
.dot.active > div {
opacity: 1;
}
`;
render() {
return html`<div class="face">
${this.time.map(pos =>
pos ? html`<div class="dot active"><div></div></div>` : html`<div class="dot"><div></div></div>`
)}
</div>`;
}
}
export default DominoClockface;

View File

@ -1,142 +1,4 @@
type Times = number[];
type Cats = [string, Times];
const MATRIX: Cats[] = [
["o0", [1, 5, 8, 9, 11]],
["o1", [2, 5, 6, 10, 11]],
["o2", [3, 6, 7, 9, 11]],
["o3", [4, 7, 8, 10, 11]],
];
import { DominoClock } from "./DominoClock.ts";
const TEMPLATE = `
<style>
.dominoclock {
display: flex;
flex-direction: var(--dominoclock-direction, row);
}
.face {
position: relative;
width: var(--dominoclock-size, 5rem);
height: var(--dominoclock-size, 5rem);
background-color: var(--dominoclock-background, #daaf20);
border: 1px solid #282828;
border-radius: var(--domino-clock-borderradius, 0.5rem);
}
.face:not(:last-child) {
margin-right: 0.25rem;
}
.dot {
position: absolute;
width: var(--dominoclock-dot-size, 1rem);
height: var(--dominoclock-dot-size, 1rem);
background-color: var(--dominoclock-dot-color, #2f4f4f);
transition: opacity 0.3s ease;
border-radius: calc(var(--dominoclock-dot-size, 1rem) / 2);
opacity: 0;
}
.o0 {
top: var(--dominoclock-dot-size, 1rem);
left: var(--dominoclock-dot-size, 1rem);
}
.o1 {
top: var(--dominoclock-dot-size, 1rem);
left: calc(3 * var(--dominoclock-dot-size, 1rem));
}
.o2 {
top: calc(3 * var(--dominoclock-dot-size, 1rem));
left: calc(3 * var(--dominoclock-dot-size, 1rem));
}
.o3 {
top: calc(3 * var(--dominoclock-dot-size, 1rem));
left: var(--dominoclock-dot-size, 1rem);
}
</style>
<div class="dominoclock">
<div class="face" id="hours">
<div class="dot o0" ></div>
<div class="dot o1" ></div>
<div class="dot o2" ></div>
<div class="dot o3" ></div>
</div>
<div class="face" id="minutes">
<div class="dot o0" ></div>
<div class="dot o1" ></div>
<div class="dot o2" ></div>
<div class="dot o3" ></div>
</div>
<div class="face" id="seconds">
<div class="dot o0" ></div>
<div class="dot o1" ></div>
<div class="dot o2" ></div>
<div class="dot o3" ></div>
</div>
</div>
`;
type Face = "hours" | "minutes" | "seconds";
const isFace = (v: unknown): v is Face => {
if (typeof v === "string" && ["hours", "minutes", "seconds"].includes(v)) {
return true;
}
throw new Error(`Expected a Face, got ${v}`);
};
class DominoClock extends HTMLElement {
elements: { [K in Face]: HTMLDivElement };
timer: number;
constructor() {
super();
this.timer = 0;
this.innerHTML = TEMPLATE;
this.elements = Object.fromEntries(
Array.from(this.getElementsByTagName("div"))
.filter(element => element.className === "face")
.map(element => [isFace(element.id) && element.id, element])
);
this.paint = this.paint.bind(this);
}
connectedCallback() {
this.timer = window.setTimeout(this.paint, 250);
}
disconnectedCallback() {
window.clearTimeout(this.timer);
}
tock(board: Face, time: number) {
const dots = Array.from(this.elements[board].getElementsByTagName("div"));
dots.forEach(element => {
element.style.opacity = "0";
});
dots.forEach(element => {
const row = MATRIX.find(i => Array.from(element.classList).includes(i[0]));
if (!row) {
console.log(`Didn't find a row for ${element.className}?`);
return;
}
if (row[1].includes(time)) {
element.style.opacity = "1";
}
});
}
paint() {
const now = new Date();
const rawHours = now.getHours();
const hours = rawHours > 12 ? rawHours - 12 : rawHours;
this.tock("hours", hours);
this.tock("minutes", Math.floor(now.getUTCMinutes() / 5));
this.tock("seconds", Math.floor(now.getUTCSeconds() / 5));
window.clearTimeout(this.timer);
this.timer = window.setTimeout(this.paint, 250);
}
}
customElements.define("domino-clock", DominoClock);
export { DominoClock };
export default DominoClock;

View File

@ -1,30 +1,32 @@
{
"compilerOptions": {
"sourceMap": false,
"noImplicitAny": false,
"module": "esnext",
"target": "es2019",
"lib": ["es2019", "dom", "dom.iterable"],
"removeComments": true,
"allowSyntheticDefaultImports": true,
"declaration": true,
"allowJs": true,
"strict": true,
"baseUrl": "./",
"esModuleInterop": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"downlevelIteration": true,
"noUnusedLocals": true /* Report errors on unused locals. */,
"experimentalDecorators": true,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
"noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["./src/**/*"]
"compilerOptions": {
"target": "es2018",
"module": "esnext",
"moduleResolution": "node",
"noEmitOnError": true,
"lib": ["es2017", "dom"],
"strict": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": false,
"importHelpers": true,
"outDir": "dist",
"sourceMap": true,
"inlineSources": true,
"baseUrl": "./",
"rootDir": "./",
"declaration": true,
"incremental": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["./src/**/*"]
}