Compare commits
11 Commits
d43e474251
...
master
Author | SHA1 | Date |
---|---|---|
|
c849032741 | |
|
55e425dd85 | |
|
26bf5bc883 | |
|
566bc9d9f4 | |
|
6f10ef9e9e | |
|
0f23358f1d | |
|
0ba271be28 | |
|
6c9ac44eac | |
|
3b2a7f22f4 | |
|
cf560f576d | |
|
52c3ec642d |
|
@ -1,58 +1,58 @@
|
||||||
{
|
{
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"plugins": ["@typescript-eslint", "eslint-plugin-import", "prettier"],
|
"plugins": ["@typescript-eslint", "prettier", "jest", "unused-imports"],
|
||||||
"root": true,
|
"extends": ["@open-wc", "prettier", "eslint:recommended"],
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": ["airbnb", "plugin:@typescript-eslint/recommended", "prettier"],
|
|
||||||
"parserOptions": {
|
|
||||||
"project": ["tsconfig.json"],
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-await-in-loop": "off",
|
"@typescript-eslint/ban-ts-comment": "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/no-unused-vars": "off",
|
|
||||||
"import/extensions": "off",
|
|
||||||
"import/prefer-default-export": "off",
|
|
||||||
"@typescript-eslint/ban-types": "warn",
|
"@typescript-eslint/ban-types": "warn",
|
||||||
"@typescript-eslint/no-use-before-define": "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",
|
"arrow-body-style": "warn",
|
||||||
"camelcase": "warn",
|
"camelcase": "off",
|
||||||
|
"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",
|
||||||
"@typescript-eslint/no-shadow": ["warn"],
|
"no-underscore-dangle": "off",
|
||||||
"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": [
|
||||||
"settings": {
|
"error",
|
||||||
"import/resolver": {
|
{
|
||||||
"node": {
|
"ignoreDeclarationSort": true
|
||||||
"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": "^_"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
.prettierrc
10
.prettierrc
|
@ -1,8 +1,10 @@
|
||||||
{
|
{
|
||||||
"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"
|
||||||
}
|
}
|
||||||
|
|
74
README.md
74
README.md
|
@ -1,30 +1,62 @@
|
||||||
# Dominoclock
|
# Dominoclock
|
||||||
|
|
||||||
This is code that's been around since 1996 or so, and is one of the
|
It's nostalgia week at Pendorwright Labs, where toy programs are the
|
||||||
three first Javascript programs I ever wrote. It's just the "time of
|
order of the day. DominoClock is a simulation (or a re-implementation)
|
||||||
day" counter for the fictional world that's the setting of my
|
of a nifty electromechanical watch I saw back about 25 years ago; it was
|
||||||
long-running [space opera
|
a watch with a domino face, and the points on it elevated as the minutes
|
||||||
series](https://www.pendorwright.com/journals/). It has its own
|
slipped by. It was kinda-sorta meant for the blind, so re-implementing
|
||||||
calendar, and unlike Star Trek, I had in mind what the "star dates"
|
it as a web application is a bit silly. It only has a resolution of
|
||||||
would mean early on.
|
every five minutes, so implementing the seconds hand is also a bit
|
||||||
|
silly, but it lets you see that the clock is working as planned.
|
||||||
|
|
||||||
# Motivation
|
# Motivation
|
||||||
|
|
||||||
This is a slightly modernized version, just to see what it would be
|
In the `orginal/` folder, you'll find the Java (!) program I wrote back
|
||||||
like to write this in 2021. The answer is that not much has changed;
|
in 1996. This is literally my first and, if memory serves me correctly,
|
||||||
the code runs just fine, although `getYear()` has been deprecated,
|
only Java applet I ever wrote. It's written in Java so old it may even
|
||||||
replaced by `.geUTCFullYear()`. The syntax of 2021 Javascript is a lot
|
be before Java 1.0 was released. Sun used to send a sales team to nerdy
|
||||||
nicer than 1996, although there is a limit to how much density one can
|
offices and, while the main guy talked to the managers, the sales
|
||||||
achieve when it's a lot of fiddly calculations around converting
|
engineer would slip the devs a CD or two with labels like "Java 0.9
|
||||||
human-readable dates into Pendorian-readable ones.
|
beta" and "Sun Proprietary - Not for general release" handwritten on
|
||||||
|
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.
|
||||||
|
|
||||||
What this project _really_ involves is preserving the basic elements
|
I just wanted to see it again.
|
||||||
of prettier, eslint, vitejs, and typescript that I routinely use these
|
|
||||||
days as the basis of my Javascript work. Most of the configuration
|
I also wanted to practice a bit more with web components. I think
|
||||||
files are short, as you'd expect from a vanilla javascript project
|
they're more important than React, and I have a strong preference for
|
||||||
with a single source file and no framework, but they do include things
|
the way they integrate with the browser ecosystem rather than fight
|
||||||
like sourcemap inclusion, minification, and using rollup to generate
|
against it the way React does.
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
46
index.html
46
index.html
|
@ -7,10 +7,52 @@
|
||||||
<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>
|
||||||
<h2>Domino Clock:</h2>
|
<h1>The Domino Clock</h1>
|
||||||
|
<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>
|
||||||
<script type="module" src="/src/index.ts"></script>
|
<p>
|
||||||
|
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>
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
//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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
|
@ -1,33 +1,41 @@
|
||||||
{
|
{
|
||||||
"name": "pendordate",
|
"name": "DominoClock",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "The Pendor Clock, updated for 2021",
|
"description": "The Domino Clock",
|
||||||
"main": "build/index.js",
|
"main": "build/index.js",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"analyze": "cem analyze --litelement",
|
||||||
"lint": "eslint",
|
"build": "vite build --base='./'",
|
||||||
|
"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.4.0",
|
"@typescript-eslint/eslint-plugin": "^5.48.0",
|
||||||
"@typescript-eslint/parser": "^5.4.0",
|
"@typescript-eslint/parser": "^5.48.0",
|
||||||
"eslint": "^8.2.0",
|
"eslint": "^8.3.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",
|
||||||
"rimraf": "^3.0.2",
|
"typescript": "^4.5.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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
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;
|
|
@ -0,0 +1,87 @@
|
||||||
|
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;
|
|
@ -0,0 +1,46 @@
|
||||||
|
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;
|
132
src/index.ts
132
src/index.ts
|
@ -1,130 +1,4 @@
|
||||||
type Times = number[];
|
import { DominoClock } from "./DominoClock.ts";
|
||||||
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;
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": false,
|
"target": "es2018",
|
||||||
"noImplicitAny": false,
|
|
||||||
"module": "esnext",
|
"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",
|
"moduleResolution": "node",
|
||||||
"downlevelIteration": true,
|
"noEmitOnError": true,
|
||||||
"noUnusedLocals": true /* Report errors on unused locals. */,
|
"lib": ["es2017", "dom"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
"emitDecoratorMetadata": false,
|
||||||
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
"importHelpers": true,
|
||||||
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
"outDir": "dist",
|
||||||
"noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */,
|
"sourceMap": true,
|
||||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
"inlineSources": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"rootDir": "./",
|
||||||
|
"declaration": true,
|
||||||
|
"incremental": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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,
|
||||||
|
|
Loading…
Reference in New Issue