Compare commits

..

No commits in common. "master" and "d43e474251833c58c8946ab7231cf84dddc12fa8" have entirely different histories.

14 changed files with 12656 additions and 17206 deletions

View File

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

View File

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

View File

@ -1,62 +1,30 @@
# Dominoclock # Dominoclock
It's nostalgia week at Pendorwright Labs, where toy programs are the This is code that's been around since 1996 or so, and is one of the
order of the day. DominoClock is a simulation (or a re-implementation) three first Javascript programs I ever wrote. It's just the "time of
of a nifty electromechanical watch I saw back about 25 years ago; it was day" counter for the fictional world that's the setting of my
a watch with a domino face, and the points on it elevated as the minutes long-running [space opera
slipped by. It was kinda-sorta meant for the blind, so re-implementing series](https://www.pendorwright.com/journals/). It has its own
it as a web application is a bit silly. It only has a resolution of calendar, and unlike Star Trek, I had in mind what the "star dates"
every five minutes, so implementing the seconds hand is also a bit would mean early on.
silly, but it lets you see that the clock is working as planned.
# Motivation # Motivation
In the `orginal/` folder, you'll find the Java (!) program I wrote back This is a slightly modernized version, just to see what it would be
in 1996. This is literally my first and, if memory serves me correctly, like to write this in 2021. The answer is that not much has changed;
only Java applet I ever wrote. It's written in Java so old it may even the code runs just fine, although `getYear()` has been deprecated,
be before Java 1.0 was released. Sun used to send a sales team to nerdy replaced by `.geUTCFullYear()`. The syntax of 2021 Javascript is a lot
offices and, while the main guy talked to the managers, the sales nicer than 1996, although there is a limit to how much density one can
engineer would slip the devs a CD or two with labels like "Java 0.9 achieve when it's a lot of fiddly calculations around converting
beta" and "Sun Proprietary - Not for general release" handwritten on human-readable dates into Pendorian-readable ones.
them. By the time management got around to asking the nerds, "Have you
heard about this Java thing?" we'd already been playing with it for
months.
I just wanted to see it again. What this project _really_ involves is preserving the basic elements
of prettier, eslint, vitejs, and typescript that I routinely use these
I also wanted to practice a bit more with web components. I think days as the basis of my Javascript work. Most of the configuration
they're more important than React, and I have a strong preference for files are short, as you'd expect from a vanilla javascript project
the way they integrate with the browser ecosystem rather than fight with a single source file and no framework, but they do include things
against it the way React does. like sourcemap inclusion, minification, and using rollup to generate
proper EcmaScript-6.
# Running It
```
$ npm install
$ npm run dev
```
It'll be on port 3000.
It's written using the original colors I chose back in 1996, so it's
more than a bit ugly. This version, though, has rounded corners and
dots, so there's that. It does respond to a variety of CSS variables, so
you can change the size and colors. I *really* should make it resize
the faces in response to the overall size of the container allocated to
the `<domino-clock />` component, but that's a future task.
# DEMO
There is a demo: [The Domino
Clock.](https://elfsternberg.com/projects/dominoclock/)
# TODO
- Add a few more CSS variables.
- Make it respond to the size of the container, rather than forcing its
size.
- Make the faces aria-compliant. `aria-valuenow` seems like the safest
bet.
# License # License

View File

@ -1,180 +0,0 @@
{
"schemaVersion": "1.0.0",
"readme": "",
"modules": [
{
"kind": "javascript-module",
"path": "src/DominoClock.ts",
"declarations": [
{
"kind": "class",
"description": "",
"name": "DominoClock",
"members": [
{
"kind": "field",
"name": "faces",
"type": {
"text": "HTMLCollection"
}
},
{
"kind": "field",
"name": "timer",
"type": {
"text": "number"
},
"default": "0"
},
{
"kind": "method",
"name": "paint"
}
],
"superclass": {
"name": "LitElement",
"package": "lit"
},
"customElement": true
}
],
"exports": [
{
"kind": "js",
"name": "DominoClock",
"declaration": {
"name": "DominoClock",
"module": "src/DominoClock.ts"
}
},
{
"kind": "js",
"name": "default",
"declaration": {
"name": "DominoClock",
"module": "src/DominoClock.ts"
}
}
]
},
{
"kind": "javascript-module",
"path": "src/DominoFace.ts",
"declarations": [
{
"kind": "class",
"description": "",
"name": "DominoClockface",
"members": [
{
"kind": "field",
"name": "name",
"type": {
"text": "string"
},
"default": "\"hour\""
},
{
"kind": "field",
"name": "time",
"type": {
"text": "string"
},
"default": "\"12\""
},
{
"kind": "field",
"name": "dots",
"type": {
"text": "HTMLCollection"
}
},
{
"kind": "method",
"name": "calculateDots"
}
],
"superclass": {
"name": "LitElement",
"package": "lit"
},
"customElement": true
}
],
"exports": [
{
"kind": "js",
"name": "DominoClockface",
"declaration": {
"name": "DominoClockface",
"module": "src/DominoFace.ts"
}
},
{
"kind": "js",
"name": "default",
"declaration": {
"name": "DominoClockface",
"module": "src/DominoFace.ts"
}
}
]
},
{
"kind": "javascript-module",
"path": "src/index.ts",
"declarations": [],
"exports": [
{
"kind": "js",
"name": "DominoClock",
"declaration": {
"name": "DominoClock",
"module": "src/index.ts"
}
},
{
"kind": "js",
"name": "default",
"declaration": {
"name": "DominoClock",
"module": "src/index.ts"
}
}
]
},
{
"kind": "javascript-module",
"path": "build/assets/index.c09ad8c7.js",
"declarations": [
{
"kind": "variable",
"name": "n3"
},
{
"kind": "variable",
"name": "e3",
"default": "t2"
}
],
"exports": [
{
"kind": "custom-element-definition",
"name": "e3",
"declaration": {
"name": "n3",
"module": "build/assets/index.c09ad8c7.js"
}
},
{
"kind": "custom-element-definition",
"name": "e3",
"declaration": {
"name": "n4",
"module": "build/assets/index.c09ad8c7.js"
}
}
]
}
]
}

View File

@ -7,52 +7,10 @@
<meta http-equiv="X-UI-Compatible" content="ie-edge" /> <meta http-equiv="X-UI-Compatible" content="ie-edge" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<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=Mulish&display=swap" rel="stylesheet" />
<style>
body {
font-family: "Mulish", sans-serif;
font-size: 16px;
line-height: 1.36;
letter-spacing: 0.012em;
padding: 1rem;
}
p {
color: #282828;
max-width: 45ch;
}
</style>
</head> </head>
<body> <body>
<h1>The Domino Clock</h1> <h2>Domino Clock:</h2>
<p>
Sometime around 1996, when I was working for CompuServe, I saw a lovely demonstration of an
electromechanical watch that used a sort of domino face to tell the time. I liked it so much that
when I was looking for a toy project for my first Java Applet, I decided to implement the
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
<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
resolution of only five minutes, but it allows you to watch the clock in operation and learn what
each symbol means: an empty face is 0 (12) and all four pips visible is 11 (or 55
minutes/seconds).
</p>
<domino-clock></domino-clock> <domino-clock></domino-clock>
<p> <script type="module" src="/src/index.ts"></script>
The source code, both for this project and for the Java original,
<a href="https://git.elfsternberg.com/elf/dominoclock"
>are available at my personal Git repository</a
>
at <a href="https://elfsternberg.com">ElfSternberg.com</a>.
</p>
<script type="module" src="./src/index.ts"></script>
</body> </body>
</html> </html>

View File

@ -1,149 +0,0 @@
//Draw a clock based upon the Domino display.
import java.applet.Applet;
import java.util.*;
import java.lang.*;
import java.awt.*;
public class DominoClock extends Applet implements Runnable {
int StartX, StartY, SquareD, DotRad;
Color bgColor, dominoColor, dotColor;
Thread clock = null;
String background;
public void init() {
try {
background = getParameter( "bgcolor" );
} catch (NullPointerException e) {};
if (( background == null ) ||
( background.charAt(0) != '#' ) ||
( background.length() != 7 )) {
Integer rgbValue = new Integer(0);
bgColor = new Color(rgbValue.intValue());
} else {
Integer rgbValue = new Integer(0);
rgbValue = Integer.valueOf( background.substring(1,7), 16 );
bgColor = new Color(rgbValue.intValue());
}
try {
background = getParameter( "dominocolor" );
} catch (NullPointerException e) {};
if (( background == null ) ||
( background.charAt(0) != '#' ) ||
( background.length() != 7 )) {
Integer rgbValue = new Integer(14331680);
dominoColor = new Color(rgbValue.intValue());
} else {
Integer rgbValue = new Integer(14331680);
rgbValue = Integer.valueOf( background.substring(1,7), 16 );
dominoColor = new Color(rgbValue.intValue());
}
try {
background = getParameter( "dotcolor" );
} catch (NullPointerException e) {};
if (( background == null ) ||
( background.charAt(0) != '#' ) ||
( background.length() != 7 )) {
Integer rgbValue = new Integer(3100495);
dotColor = new Color(rgbValue.intValue());
} else {
Integer rgbValue = new Integer(3100495);
rgbValue = Integer.valueOf( background.substring(1,7), 16 );
dotColor = new Color(rgbValue.intValue());
}
Dimension D = size();
if (D.height > (3 * D.width)) {
SquareD = D.width;
StartX = 0;
StartY = 0; // (D.height / 3) - SquareD;
}
else {
SquareD = (D.height / 3);
StartY = 0;
StartX = 0; //.width - (SquareD / 3);
}
DotRad = (int) (Math.sqrt(2 * SquareD * SquareD) / 6);
}
public void paint(Graphics g) {
int Hour, Min, Sec;
Date D = new Date();
Hour = D.getHours();
if (Hour > 12) Hour = Hour - 12;
Min = D.getMinutes();
Sec = D.getSeconds();
Tock(StartX, StartY, Hour, g);
Tock(StartX, StartY + SquareD, (Min / 5), g);
Tock(StartX, StartY + (2 * SquareD), (Sec / 5), g);
}
public void update(Graphics g) {
paint(g); // Overridden to prevent flicker.
}
public void start() {
clock = new Thread(this);
clock.start();
}
public void stop() {
clock.stop();
}
public void run() {
while(true) {
try {
Thread.sleep(1000);
}
catch(InterruptedException e) {}
repaint();
}
}
private void Tock(int StartX, int StartY, int time, Graphics g) {
g.setColor(dominoColor);
g.fillRect(StartX, StartY, SquareD, SquareD);
g.setColor(dotColor);
if ((time == 1) || (time == 5) || (time == 8) || (time == 9) || (time == 11))
g.fillOval(StartX + DotRad, StartY + DotRad, DotRad, DotRad);
if ((time == 2) || (time == 5) || (time == 6) || (time == 10) || (time == 11))
g.fillOval(StartX + SquareD - (2 * DotRad), StartY + DotRad, DotRad, DotRad);
if ((time == 3) || (time == 6) || (time == 7) || (time == 9) || (time == 11))
g.fillOval(StartX + SquareD - (2 * DotRad), StartY + SquareD - (2 * DotRad), DotRad, DotRad);
if ((time == 4) || (time == 7) || (time == 8) || (time == 10) || (time == 11))
g.fillOval(StartX + DotRad, StartY + SquareD - (2 * DotRad), DotRad, DotRad);
g.drawLine(StartX, StartY + SquareD - 1, StartX + SquareD, StartY + SquareD - 1);
}
public static void main(String args[]) {
Frame f1 = new Frame("Domino Clock");
DominoClock s1 = new DominoClock();
f1.add("Center", s1);
f1.resize(300, 300);
f1.show();
s1.init();
s1.start();
}
}

12733
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,33 @@
{ {
"name": "DominoClock", "name": "pendordate",
"version": "1.0.0", "version": "1.0.0",
"description": "The Domino Clock", "description": "The Pendor Clock, updated for 2021",
"main": "build/index.js", "main": "build/index.js",
"type": "module",
"scripts": { "scripts": {
"analyze": "cem analyze --litelement", "build": "vite build",
"build": "vite build --base='./'", "lint": "eslint",
"lint": "eslint --ignore-path .gitignore && prettier --check --ignore-path .gitignore src/**/*.ts",
"fix": "eslint --fix --ignore-path .gitignore && prettier --write --ignore-path .gitignore src/**/*.ts",
"dev": "vite -m development", "dev": "vite -m development",
"test": "jest" "test": "jest"
}, },
"author": "Elf M. Sternberg <elf.sternberg@gmail.com>", "author": "Elf M. Sternberg <elf.sternberg@gmail.com>",
"license": "MPL-2.0", "license": "MPL-2.0",
"dependencies": {
"lit": "^2.0.2"
},
"devDependencies": { "devDependencies": {
"@custom-elements-manifest/analyzer": "^0.4.17",
"@open-wc/eslint-config": "^9.2.1",
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.48.0", "@typescript-eslint/parser": "^5.4.0",
"eslint": "^8.3.0", "eslint": "^8.2.0",
"eslint-config-airbnb": "^19.0.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0",
"eslint-plugin-import": "^2.25.3", "eslint-plugin-import": "^2.25.3",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"typescript": "^4.5.2", "rimraf": "^3.0.2",
"typescript": "^4.4.4",
"vite": "^2.6.14", "vite": "^2.6.14",
"vite-plugin-compression": "^0.3.5", "vite-plugin-compression": "^0.3.5",
"vite-plugin-ejs": "^1.4.3", "vite-plugin-ejs": "^1.4.3",
"vite-plugin-eslint": "^1.3.0" "vite-plugin-eslint": "^1.3.0"
}, }
"customElements": "custom-elements.json"
} }

View File

@ -1,58 +0,0 @@
import { LitElement, css, html } from "lit";
import { customElement, queryAll } from "lit/decorators.js";
import PulseController from "./PulseController.ts";
import "./DominoFace.ts";
function times() {
const now = new Date();
const rawHours = now.getHours();
return {
hour: rawHours > 12 ? rawHours - 12 : rawHours,
minute: Math.floor(now.getUTCMinutes() / 5),
second: Math.floor(now.getUTCSeconds() / 5),
};
}
@customElement("domino-clock")
export class DominoClock extends LitElement {
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));
}
`;
@queryAll("domino-face") faces: HTMLCollection;
timer: PulseController;
constructor() {
super();
this.timer = new PulseController(this, 250, this.paint);
}
paint() {
const update = times();
Array.from(this.faces).forEach(face => {
const name = face.getAttribute("name");
face.setAttribute("time", `${update[name]}`);
});
}
render() {
const update = times();
return html` <div class="dominoclock">
<domino-face name="hour" time=${update.hour}></domino-face>
<domino-face name="minute" time=${update.minute}></domino-face>
<domino-face name="second" time=${update.second}></domino-face>
</div>`;
}
}
export default DominoClock;

View File

@ -1,87 +0,0 @@
import { LitElement, css, html } from "lit";
import { customElement, property, queryAll } from "lit/decorators.js";
type Times = number[];
type Coordinates = Times;
const MATRIX: Coordinates[] = [
[1, 5, 8, 9, 11],
[2, 5, 6, 10, 11],
[3, 6, 7, 9, 11],
[4, 7, 8, 10, 11],
];
// An element that takes a single attribute, "time," which is a number between 1
// and 12; that number is scanned in the Coordinates matrix above to determine
// which of four dots should be illuminated, reflecting 12 patterns that can be
// memorized to represent the time. After rendering, if the attribute changes,
// the active/hidden dots are recalculated.
@customElement("domino-face")
export class DominoClockface extends LitElement {
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.6s ease;
width: var(--dominoclock-dot-size, var(--dominoclock-default-dot-size, 1rem));
height: var(--dominoclock-dot-size, var(--dominoclock-default-dot-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;
}
`;
@property({ type: String }) name = "hour";
@property({ type: String }) time = "12";
@queryAll(".dot") dots: HTMLCollection;
calculateDots() {
const time = parseInt(this.time, 10);
return [0, 1, 2, 3].map(i => MATRIX[i].includes(time));
}
render() {
const points = this.calculateDots();
return html`
<div class="face">
${points.map(pos => html` <div class="dot${pos ? " active" : ""}"><div></div></div> `)}
</div>
`;
}
}
export default DominoClockface;

View File

@ -1,46 +0,0 @@
import type { ReactiveController, ReactiveControllerHost } from "lit";
type Pulse = () => void;
export class PulseController implements ReactiveController {
host: ReactiveControllerHost;
interval: number = 0;
timer: number = 0;
// eslint-disable-next-line
_onPulse: Pulse = () => undefined;
constructor(host: ReactiveControllerHost, interval: number = 1000, pulse?: Pulse) {
this.host = host;
this.host.addController(this);
if (pulse) {
this._onPulse = pulse;
}
this.handle = this.handle.bind(this);
this.interval = interval;
}
hostConnected() {
this.timer = window.setTimeout(this.handle, this.interval);
}
hostDisconnected() {
if (this.timer !== 0) {
window.clearTimout(this.timer);
}
this.timer = 0;
}
set onPulse(cb: Pulse) {
this._onPulse = cb;
}
handle() {
this._onPulse.call(this.host);
this.timer = window.setTimeout(this.handle, this.interval);
}
}
export default PulseController;

View File

@ -1,4 +1,130 @@
import { DominoClock } from "./DominoClock.ts"; 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]]];
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)
}
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,29 +1,27 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es2018", "sourceMap": false,
"noImplicitAny": false,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "target": "es2019",
"noEmitOnError": true, "lib": ["es2019", "dom", "dom.iterable"],
"lib": ["es2017", "dom"], "removeComments": true,
"strict": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": false,
"importHelpers": true,
"outDir": "dist",
"sourceMap": true,
"inlineSources": true,
"baseUrl": "./",
"rootDir": "./",
"declaration": true, "declaration": true,
"incremental": true, "allowJs": true,
"noUnusedLocals": true, "strict": true,
"noUnusedParameters": true, "baseUrl": "./",
"noImplicitReturns": true, "esModuleInterop": true,
"noFallthroughCasesInSwitch": true, "resolveJsonModule": true,
"noUncheckedIndexedAccess": true, "moduleResolution": "node",
"forceConsistentCasingInFileNames": true, "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": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }

View File

@ -13,7 +13,6 @@ const config = (mode) => ({
build: { build: {
outDir: "build", outDir: "build",
assetDir: "./assets",
sourcemap: mode === "development", sourcemap: mode === "development",
minify: !mode === "development", minify: !mode === "development",
brotliSize: false, brotliSize: false,