Simple, but working!, prototype of a WebComponent based clock.

This commit is contained in:
Manuel Friedli 2022-10-05 01:28:29 +02:00
parent 4071e8be63
commit 2ada8acc64
Signed by: manuel
GPG Key ID: 41D08ABA75634DA1
7 changed files with 674 additions and 0 deletions

89
webcomponent/clock.js Normal file
View File

@ -0,0 +1,89 @@
import {Layout as LayoutDE_CH} from './language-de_ch.js';
import {Parser} from "./parser.js";
import {Highlighter} from "./highlighter.js";
import {template} from "./template.js";
class WordClock extends HTMLElement {
_shadowRoot = null;
constructor() {
super();
this._shadowRoot = this.attachShadow({mode: 'open'});
this._shadowRoot.appendChild(template.content.cloneNode(true));
}
static get observedAttributes() {
return ['language', 'mode', 'color', 'state', 'debug'];
}
get language() {
return this.getAttribute('language');
}
get mode() {
return this.getAttribute('mode');
}
get color() {
return this.getAttribute('color');
}
get state() {
return this.getAttribute('state');
}
get debug() {
return this.getAttribute('debug');
}
onclick(event) {
console.log(event);
}
connectedCallback() {
if (!this._isDebug()) {
this.shadowRoot.getElementById('log').style.display = 'none';
}
this._log("connected!")
const layout = new LayoutDE_CH();
const parser = new Parser(layout);
const letters = parser.parse();
const letterarea = this.shadowRoot.getElementById('letterarea');
letters.forEach((line, index, array) => {
line.forEach(letter => {
letterarea.append(letter.toHTMLElement());
});
if (index < array.length - 1) {
letterarea.append(document.createElement('br'));
}
});
this._highlighter = new Highlighter(layout, this.shadowRoot.getElementById('clock'), this.mode);
this._timer = window.setInterval(() => this._tick(), 1000);
}
disconnectedCallback() {
window.clearInterval(this._timer);
}
_tick() {
this._highlighter.display(new Date());
}
_log(...elements) {
if (this._isDebug()) {
for (let e of elements) {
this.shadowRoot.getElementById('log').append(`${e}\n`);
}
}
}
_isDebug() {
return this.debug === 'on' || this.debug === 'true' || this.debug === '1';
}
}
window.customElements.define("word-clock", WordClock);

View File

@ -0,0 +1,86 @@
export class Highlighter {
constructor(layout, clockElement, mode) {
this._layout = layout;
this._clockElement = clockElement;
this._mode = mode === 'seconds' ? 'seconds' : 'time';
this._time = null;
}
display(time) {
if (this._mode === 'seconds') {
const second = this._getSecond(time);
if (this._getCurrentSecond() !== second) {
this._clearAll();
this._highlight(`second${second}`);
}
} else {
const minute = this._getMinute(time);
if (this._getCurrentMinute() !== minute) {
const dotMinute = this._getDotMinute(time);
const hour = this._getHour(time);
this._clearAll();
this._highlight('on');
for (let i = 1; i <= dotMinute; i++) {
this._highlight(`dot${i}`);
}
this._highlight(`minute${minute}`);
this._highlight(`hour${hour}`);
}
}
this._time = time;
}
_getSecond(time) {
if (typeof this._layout.getSeconds === 'function') {
return this._layout.getSeconds(time);
}
return time.getSeconds();
}
_getDotMinute(time) {
if (typeof this._layout.getDotMinute === 'function') {
return this._layout.getDotMinute(time);
}
return time.getMinutes() % 5;
}
_getMinute(time) {
if (typeof this._layout.getMinutes === 'function') {
return this._layout.getMinutes(time);
}
return time.getMinutes();
}
_getHour(time) {
if (typeof this._layout.getHours === 'function') {
return this._layout.getHours(time);
}
const hour = time.getHours();
if (time.getMinutes() >= 25) {
return (hour + 1) % 24;
}
return hour;
}
_clearAll() {
const items = this._clockElement.getElementsByClassName('item');
for (let i = 0; i < items.length; i++) {
items.item(i).classList.remove('active');
}
}
_highlight(itemClass) {
const items = this._clockElement.getElementsByClassName(`item ${itemClass}`);
for (let i = 0; i < items.length; i++) {
items.item(i).classList.add('active');
}
}
_getCurrentSecond() {
return !!this._time && this._time.getSeconds() || -1;
}
_getCurrentMinute() {
return !!this._time && this._time.getMinutes() || -1;
}
}

17
webcomponent/index.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test page</title>
<style>
@font-face {
font-family: 'Clockfont';
src: url('../legacy/resources/uhr.woff') format('woff');
}
</style>
<script type="module" src="clock.js"></script>
</head>
<body>
<word-clock style="display: inline-block; width:800px;height:800px" debug="false"></word-clock>
</body>
</html>

View File

@ -0,0 +1,65 @@
export class Layout {
_es_isch = {1: [1, 2, 4, 5, 6, 7]};
_ab = {4: [1, 2]};
_vor = {3: [9, 10, 11]};
_haubi = {4: [4, 5, 6, 7, 8]};
_fuef = {1: [9, 10, 11]};
_zae = {2: [9, 10, 11]};
_viertu = {2: [1, 2, 3, 4, 5, 6]};
_zwaenzg = {3: [1, 2, 3, 4, 5, 6]};
constructor() {
}
get letters() {
return [
'ESKISCHAFÜF',
'VIERTUBFZÄÄ',
'ZWÄNZGSIVOR',
'ABOHAUBIEGE',
'EISZWÖISDRÜ',
'VIERIFÜFIQT',
'SÄCHSISIBNI',
'ACHTINÜNIEL',
'ZÄNIERBEUFI',
'ZWÖUFINAUHR'
];
}
get permanentlyOn() {
return this._es_isch;
}
get minutes() {
return {
"5,6,7,8,9": [this._fuef, this._ab],
"10,11,12,13,14": [this._zae, this._ab],
"15,16,17,18,19": [this._viertu, this._ab],
"20,21,22,23,24": [this._zwaenzg, this._ab],
"25,26,27,28,29": [this._fuef, this._vor, this._haubi],
"30,31,32,33,34": [this._haubi],
"35,36,37,38,39": [this._fuef, this._ab, this._haubi],
"40,41,42,43,44": [this._zwaenzg, this._vor],
"45,46,47,48,49": [this._viertu, this._vor],
"50,51,52,53,54": [this._zae, this._vor],
"55,56,57,58,59": [this._fuef, this._vor]
};
}
get hours() {
return {
"0,12": {10: [1, 2, 3, 4, 5, 6]},
"1,13": {5: [1, 2, 3]},
"2,14": {5: [4, 5, 6, 7]},
"3,15": {5: [9, 10, 11]},
"4,16": {6: [1, 2, 3, 4, 5]},
"5,17": {6: [6, 7, 8, 9]},
"6,18": {7: [1, 2, 3, 4, 5, 6]},
"7,19": {7: [7, 8, 9, 10, 11]},
"8,20": {8: [1, 2, 3, 4, 5]},
"9,21": {8: [6, 7, 8, 9]},
"10,22": {9: [1, 2, 3, 4]},
"11,23": {9: [8, 9, 10, 11]}
};
}
}

24
webcomponent/letter.js Normal file
View File

@ -0,0 +1,24 @@
export default class Letter {
constructor(value, style) {
this._value = value;
this._style = style;
}
addStyle(style) {
if (!this._style) {
this._style = style;
} else {
this._style += ' ' + style;
}
};
toHTMLElement() {
const element = document.createElement('span');
element.classList.add('item', 'letter');
if (!!this._style) {
element.classList.add(...this._style.split(' '));
}
element.innerHTML = this._value;
return element;
}
}

268
webcomponent/parser.js Normal file
View File

@ -0,0 +1,268 @@
import Letter from "./letter.js";
export class Parser {
constructor(layout) {
this._layout = layout;
}
_vorne0 = {
3: [2, 3, 4],
4: [1, 5],
5: [1, 4, 5],
6: [1, 3, 5],
7: [1, 2, 5],
8: [1, 5],
9: [2, 3, 4]
};
_hinten0 = {
3: [8, 9, 10],
4: [7, 11],
5: [7, 10, 11],
6: [7, 9, 11],
7: [7, 8, 11],
8: [7, 11],
9: [8, 9, 10]
};
_vorne1 = {
3: [3],
4: [2, 3],
5: [3],
6: [3],
7: [3],
8: [3],
9: [2, 3, 4]
};
_hinten1 = {
3: [9],
4: [8, 9],
5: [9],
6: [9],
7: [9],
8: [9],
9: [8, 9, 10]
};
_vorne2 = {
3: [2, 3, 4],
4: [1, 5],
5: [5],
6: [4],
7: [3],
8: [2],
9: [1, 2, 3, 4, 5]
};
_hinten2 = {
3: [8, 9, 10],
4: [7, 11],
5: [11],
6: [10],
7: [9],
8: [8],
9: [7, 8, 9, 10, 11]
};
_vorne3 = {
3: [1, 2, 3, 4, 5],
4: [4],
5: [3],
6: [4],
7: [5],
8: [1, 5],
9: [2, 3, 4]
};
_hinten3 = {
3: [7, 8, 9, 10, 11],
4: [10],
5: [9],
6: [10],
7: [11],
8: [7, 11],
9: [8, 9, 10]
};
_vorne4 = {
3: [4],
4: [3, 4],
5: [2, 4],
6: [1, 4],
7: [1, 2, 3, 4, 5],
8: [4],
9: [4]
};
_hinten4 = {
3: [10],
4: [9, 10],
5: [8, 10],
6: [7, 10],
7: [7, 8, 9, 10, 11],
8: [10],
9: [10]
};
_vorne5 = {
3: [1, 2, 3, 4, 5],
4: [1],
5: [1, 2, 3, 4],
6: [5],
7: [5],
8: [1, 5],
9: [2, 3, 4]
};
_hinten5 = {
3: [7, 8, 9, 10, 11],
4: [7],
5: [7, 8, 9, 10],
6: [11],
7: [11],
8: [7, 11],
9: [8, 9, 10]
};
_hinten6 = {
3: [9, 10],
4: [8],
5: [7],
6: [7, 8, 9, 10],
7: [7, 11],
8: [7, 11],
9: [8, 9, 10]
};
_hinten7 = {
3: [7, 8, 9, 10, 11],
4: [11],
5: [10],
6: [9],
7: [8],
8: [8],
9: [8]
};
_hinten8 = {
3: [8, 9, 10],
4: [7, 11],
5: [7, 11],
6: [8, 9, 10],
7: [7, 11],
8: [7, 11],
9: [8, 9, 10]
};
_hinten9 = {
3: [8, 9, 10],
4: [7, 11],
5: [7, 11],
6: [8, 9, 10, 11],
7: [11],
8: [10],
9: [8, 9]
};
_seconds = {
"0": [this._vorne0, this._hinten0],
"1": [this._vorne0, this._hinten1],
"2": [this._vorne0, this._hinten2],
"3": [this._vorne0, this._hinten3],
"4": [this._vorne0, this._hinten4],
"5": [this._vorne0, this._hinten5],
"6": [this._vorne0, this._hinten6],
"7": [this._vorne0, this._hinten7],
"8": [this._vorne0, this._hinten8],
"9": [this._vorne0, this._hinten9],
"10": [this._vorne1, this._hinten0],
"11": [this._vorne1, this._hinten1],
"12": [this._vorne1, this._hinten2],
"13": [this._vorne1, this._hinten3],
"14": [this._vorne1, this._hinten4],
"15": [this._vorne1, this._hinten5],
"16": [this._vorne1, this._hinten6],
"17": [this._vorne1, this._hinten7],
"18": [this._vorne1, this._hinten8],
"19": [this._vorne1, this._hinten9],
"20": [this._vorne2, this._hinten0],
"21": [this._vorne2, this._hinten1],
"22": [this._vorne2, this._hinten2],
"23": [this._vorne2, this._hinten3],
"24": [this._vorne2, this._hinten4],
"25": [this._vorne2, this._hinten5],
"26": [this._vorne2, this._hinten6],
"27": [this._vorne2, this._hinten7],
"28": [this._vorne2, this._hinten8],
"29": [this._vorne2, this._hinten9],
"30": [this._vorne3, this._hinten0],
"31": [this._vorne3, this._hinten1],
"32": [this._vorne3, this._hinten2],
"33": [this._vorne3, this._hinten3],
"34": [this._vorne3, this._hinten4],
"35": [this._vorne3, this._hinten5],
"36": [this._vorne3, this._hinten6],
"37": [this._vorne3, this._hinten7],
"38": [this._vorne3, this._hinten8],
"39": [this._vorne3, this._hinten9],
"40": [this._vorne4, this._hinten0],
"41": [this._vorne4, this._hinten1],
"42": [this._vorne4, this._hinten2],
"43": [this._vorne4, this._hinten3],
"44": [this._vorne4, this._hinten4],
"45": [this._vorne4, this._hinten5],
"46": [this._vorne4, this._hinten6],
"47": [this._vorne4, this._hinten7],
"48": [this._vorne4, this._hinten8],
"49": [this._vorne4, this._hinten9],
"50": [this._vorne5, this._hinten0],
"51": [this._vorne5, this._hinten1],
"52": [this._vorne5, this._hinten2],
"53": [this._vorne5, this._hinten3],
"54": [this._vorne5, this._hinten4],
"55": [this._vorne5, this._hinten5],
"56": [this._vorne5, this._hinten6],
"57": [this._vorne5, this._hinten7],
"58": [this._vorne5, this._hinten8],
"59": [this._vorne5, this._hinten9]
};
_parseArrayOrObject(letters, styleClass, input) {
if (typeof input !== 'undefined' && input !== null) {
if (Array.isArray(input)) {
input.forEach(item => {
this._parseObject(letters, styleClass, item);
});
} else {
this._parseObject(letters, styleClass, input);
}
}
}
_parseObject(letters, styleClass, object) {
if (typeof object !== 'undefined' && object !== null) {
Object.keys(object).forEach(y => {
const highlightLetters = object[y];
highlightLetters.forEach(x => {
letters[y - 1][x - 1].addStyle(styleClass);
});
});
}
}
_parseTimeDefinition(letters, styleClass, definition) {
if (typeof definition !== 'undefined' && definition !== null) {
Object.keys(definition).forEach(listString => {
const array = listString.split(',');
const highlightLetters = definition[listString];
array.forEach(item => this._parseArrayOrObject(letters, styleClass + item, highlightLetters));
});
}
}
parse() {
const letters = [];
this._layout.letters.forEach(string => {
const line = [];
for (let c = 0; c < string.length; c++) {
const character = new Letter(string[c]);
line.push(character);
}
letters.push(line);
});
this._parseArrayOrObject(letters, 'on', this._layout.permanentlyOn);
if (typeof this._layout.seconds !== 'undefined' && this._layout.seconds !== null) {
this._parseTimeDefinition(letters, 'second', this._layout.seconds);
} else {
this._parseTimeDefinition(letters, 'second', this._seconds);
}
this._parseTimeDefinition(letters, 'minute', this._layout.minutes);
this._parseTimeDefinition(letters, 'hour', this._layout.hours);
return letters;
};
}

125
webcomponent/template.js Normal file
View File

@ -0,0 +1,125 @@
export const template = document.createElement('template');
template.innerHTML = `
<style>
/* BASE */
@font-face {
font-family: 'Clockfont';
src: url('../legacy/resources/uhr.woff') format('woff');
}
* {
font-family: 'Clockfont', sans-serif;
}
#clock {
position: relative;
height: 100%;
margin: 0;
transition: background-color 0.5s;
width: 100%;
}
#reflection {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: radial-gradient(225em 45em at 160% 0, rgba(255, 255, 255, 0.4) 0, rgba(255, 255, 255, 0.05) 40%, rgba(255, 255, 255, 0) 40%) no-repeat scroll;
display: block;
margin: 0.15em;
}
#letterarea {
display: block;
position: absolute;
top: 12%;
bottom: 12%;
left: 12%;
right: 12%;
overflow: hidden;
font-size: 200%;
}
.item {
transition: box-shadow 0.5s, text-shadow 0.5s, border-color 0.5s, color 0.5s;
}
.dot {
position: absolute;
display: block;
height: 0;
width: 0;
border: 0.2em solid;
border-radius: 1em;
}
.dot.active {
border-color: #eee;
box-shadow: 0 0 0.2em #eee;
}
.dot1 {
top: 3.75%;
left: 3.75%;
}
.dot2 {
top: 3.75%;
right: 3.75%;
}
.dot3 {
bottom: 3.75%;
right: 3.75%;
}
.dot4 {
bottom: 3.75%;
left: 3.75%;
}
.letter {
height: 10%;
width: 9.0909%;
padding: 0;
margin: 0;
display: inline-block;
text-align: center;
line-height: 160%;
}
.letter.active {
color: #eee;
text-shadow: 0 0 0.2em #eee;
}
/* BLACK */
#clock.black {
background-color: #111;
}
.black .onoffswitch-inner:before {
background-color: #111;
}
#clock.black .dot:not(.active) {
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 0.1em rgba(255, 255, 255, 0.1);
}
#clock.black .letter:not(.active) {
color: rgba(255, 255, 255, 0.1);
text-shadow: 0 0 0.1em rgba(255, 255, 255, 0.1);
}
</style>
<div id="clock" class="black">
<span class="item dot dot1"></span>
<span class="item dot dot2"></span>
<span class="item dot dot3"></span>
<span class="item dot dot4"></span>
<div id="letterarea"></div>
<div id="reflection"></div>
</div>
<pre id="log"></pre>
`;