Modernized!

This commit is contained in:
Elf M. Sternberg 2023-10-07 14:05:54 -07:00
parent fd75bb82e7
commit ff11f79c51
26 changed files with 9668 additions and 9696 deletions

29
.editorconfig Normal file
View File

@ -0,0 +1,29 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.json]
indent_size = 2
[*.{html,js,md}]
block_comment_start = /**
block_comment = *
block_comment_end = */

8
.eslintignore Normal file
View File

@ -0,0 +1,8 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
# don't lint nyc coverage output
coverage
src/locale-codes.ts
storybook-static/

View File

@ -1,58 +1,36 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "eslint-plugin-import", "prettier"],
"root": true,
"env": {
"browser": true,
"es2021": true
},
"extends": ["airbnb", "plugin:@typescript-eslint/recommended", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:lit/recommended",
"plugin:custom-elements/recommended",
"plugin:storybook/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["tsconfig.json"],
"ecmaVersion": 2020,
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "lit", "custom-elements"],
"ignorePatterns": ["demo/**", "stories/**"],
"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"
}
}
"indent": "off",
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double", { "avoidEscape": true }],
"semi": ["error", "always"],
"@typescript-eslint/ban-ts-comment": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
]
}
}

29
.eslintrc.precommit.json Normal file
View File

@ -0,0 +1,29 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:lit/recommended",
"plugin:custom-elements/recommended",
"plugin:storybook/recommended",
"plugin:sonarjs/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "lit", "custom-elements", "sonarjs"],
"rules": {
"indent": "off",
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double", { "avoidEscape": true }],
"semi": ["error", "always"],
"@typescript-eslint/ban-ts-comment": "off",
"sonarjs/cognitive-complexity": ["error", 9],
"sonarjs/no-nested-template-literals": "off"
}
}

30
.gitignore vendored
View File

@ -1,5 +1,25 @@
*#
.#*
*~
node_modules
build
## editors
/.idea
/.vscode
## system files
.DS_Store
## npm
/node_modules/
/npm-debug.log
## testing
/coverage/
## temp folders
/.tmp/
# build
/_site/
/dist/
/out-tsc/
/build-modern/
storybook-static
custom-elements.json

View File

@ -1,6 +0,0 @@
repos:
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v2.4.1" # Use the sha / tag you want to point at
hooks:
- id: prettier
exclude: "index.html"

11
.prettierignore Normal file
View File

@ -0,0 +1,11 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
dist
# don't lint nyc coverage output
coverage
# Import order matters
poly.ts
src/locale-codes.ts
src/locales/
storybook-static/

View File

@ -1,8 +0,0 @@
{
"trailingComma": "es5",
"printWidth": 110,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": false
}

23
.prettierrc.json Normal file
View File

@ -0,0 +1,23 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 100,
"proseWrap": "preserve",
"quoteProps": "consistent",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 4,
"trailingComma": "all",
"useTabs": false,
"vueIndentScriptAndStyle": false,
"plugins": ["@trivago/prettier-plugin-sort-imports"],
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
}

3
.storybook/main.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
stories: ['../dist/stories/**/*.stories.{js,md,mdx}'],
};

8
.storybook/server.mjs Normal file
View File

@ -0,0 +1,8 @@
import { storybookPlugin } from '@web/dev-server-storybook';
import baseConfig from '../web-dev-server.config.mjs';
export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
...baseConfig,
open: '/',
plugins: [storybookPlugin({ type: 'web-components' }), ...baseConfig.plugins],
});

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 pendor-clock
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,4 +1,5 @@
# Mozilla Public License Version 2.0
Mozilla Public License Version 2.0
==================================
### 1. Definitions
@ -22,9 +23,9 @@ including portions thereof.
**1.5. “Incompatible With Secondary Licenses”**
means
- **(a)** that the initial Contributor has attached the notice described
* **(a)** that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
- **(b)** that the Covered Software was made available under the terms of
* **(b)** that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
@ -46,10 +47,10 @@ all of the rights conveyed by this License.
**1.10. “Modifications”**
means any of the following:
- **(a)** any file in Source Code Form that results from an addition to,
* **(a)** any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
- **(b)** any new file in Source Code Form that contains any Covered
* **(b)** any new file in Source Code Form that contains any Covered
Software.
**1.11. “Patent Claims” of a Contributor**
@ -79,6 +80,7 @@ whether by contract or otherwise, or **(b)** ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
### 2. License Grants and Conditions
#### 2.1. Grants
@ -86,12 +88,12 @@ ownership of such entity.
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
- **(a)** under intellectual property rights (other than patent or trademark)
* **(a)** under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
- **(b)** under Patent Claims of such Contributor to make, use, sell, offer
* **(b)** under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
@ -109,13 +111,13 @@ distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
- **(a)** for any code that a Contributor has removed from Covered Software;
* **(a)** for any code that a Contributor has removed from Covered Software;
or
- **(b)** for infringements caused by: **(i)** Your and any other third party's
* **(b)** for infringements caused by: **(i)** Your and any other third party's
modifications of Covered Software, or **(ii)** the combination of its
Contributions with other software (except as part of its Contributor
Version); or
- **(c)** under Patent Claims infringed by Covered Software in the absence of
* **(c)** under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
@ -146,6 +148,7 @@ equivalents.
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
### 3. Responsibilities
#### 3.1. Distribution of Source Form
@ -162,13 +165,13 @@ Form.
If You distribute Covered Software in Executable Form then:
- **(a)** such Covered Software must also be made available in Source Code
* **(a)** such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
- **(b)** You may distribute such Executable Form under the terms of this
* **(b)** You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
@ -207,6 +210,7 @@ indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
### 4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this
@ -219,6 +223,7 @@ Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
### 5. Termination
**5.1.** The rights granted under this License will terminate automatically
@ -247,6 +252,7 @@ end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
### 6. Disclaimer of Warranty
> Covered Software is provided under this License on an “as is”
@ -279,6 +285,7 @@ prior to termination shall survive termination.
> incidental or consequential damages, so this exclusion and
> limitation may not apply to You.
### 8. Litigation
Any litigation relating to this License may be brought only in the
@ -288,6 +295,7 @@ jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
### 9. Miscellaneous
This License represents the complete agreement concerning the subject
@ -297,6 +305,7 @@ necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
### 10. Versions of the License
#### 10.1. New Versions

View File

@ -1,43 +1,34 @@
# The Pendor Clock
# \<pendor-clock>
This is code that's been around since 1996 or so, and is one of the
three first Javascript programs I ever wrote. It's just the "time of
day" counter for the fictional world that's the setting of my
long-running [space opera
series](https://www.pendorwright.com/journals/). It has its own
calendar, and unlike Star Trek, I had in mind what the "star dates"
would mean early on.
Way back at the dawn of the Internet, I started writing (and sometimes still write) a long-running
adult space opera serial called [The Journal Entries](https://pendorwright.com/journals). One of the
conceits of the series in that the distant world of Pendor was built and terraformed to have a
30-hour day, but its years and Terra's years are of exactly the same length. A 30-hour day has a
different calendar, and one of the very first [Java
applets](https://en.wikipedia.org/wiki/Java_applet) (remember those?) I wrote was a clock that
showed the time of day on Pendor.
# Motivation
This is the Pendorclock, but modernized into a custom Web Component, capable of running in any
browser without your needing to do anything at all to make it work correctly. It will load its own
font (Google Audiowide) and run anywhere you want. The font is not currently customizable without a
rebuild, but the following CSS Custom Attributes are exposed and you're free to change any of them:
This is a slightly modernized version, just to see what it would be
like to write this in 2021. The answer is that not much has changed;
the code runs just fine, although `getYear()` has been deprecated,
replaced by `.geUTCFullYear()`. The syntax of 2021 Javascript is a lot
nicer than 1996, although there is a limit to how much density one can
achieve when it's a lot of fiddly calculations around converting
human-readable dates into Pendorian-readable ones.
What this project _really_ involves is preserving the basic elements
of prettier, eslint, vitejs, and typescript that I routinely use these
days as the basis of my Javascript work. Most of the configuration
files are short, as you'd expect from a vanilla javascript project
with a single source file and no framework, but they do include things
like sourcemap inclusion, minification, and using rollup to generate
proper EcmaScript-6.
# Running it
Really? Okay:
```
$ npm install
$ npm run dev
``` JavaScript
background-color: var(--pendorclock-background-color, #000030);
color: var(--pendorclock-color, #ffffff);
font-size: var(--pendorclock-font-size, --default-font-size);
line-height: var(--pendorclock-line-height, 1.35);
font-weight: var(--pendorclock-font-weight, 700);
```
The demo will be on port 3000 by default.
This is mostly my set-up these days, complete with the hard-core `lint:hard`, Codespell, and
automatic Prettier.
## LICENSE
The Pendorwright Clock is Copyright [Elf M. Sternberg](https://elfsternberg.com) (c) 2023, and
licensed with the Mozilla Public License vers. 2.0. A copy of the license file is included in the
root folder.
# License
This code is released under the Mozilla 2.0 Public License. A copy of
the License File is included in this folder.

26
demo/index.html Normal file
View File

@ -0,0 +1,26 @@
<!doctype html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<style>
body {
background: #fafafa;
}
</style>
</head>
<body>
<div id="demo"></div>
<script type="module">
import { html, render } from 'lit';
import '../dist/src/pendor-clock.js';
render(
html`
<pendor-clock>
</pendor-clock>
`,
document.querySelector('#demo')
);
</script>
</body>
</html>

View File

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pendordate Testbed</title>
<meta http-equiv="X-UI-Compatible" content="ie-edge" />
<meta property="og:type" content="website" />
<meta name="theme-color" content="#000000" />
</head>
<body>
<p>The Date on Pendor is: <span id="pendordate" /></p>
<script type="module" src="/src/index.ts"></script>
</body>
</html>

18325
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,95 @@
{
"name": "pendorclock",
"version": "1.0.0",
"description": "The Pendor Clock, updated for 2021",
"main": "build/index.js",
"scripts": {
"build": "vite build",
"lint": "eslint",
"dev": "vite -m development",
"test": "jest"
"name": "pendor-clock",
"description": "Webcomponent pendor-clock following open-wc recommendations",
"license": "MIT",
"author": "pendor-clock",
"version": "0.0.0",
"type": "module",
"main": "dist/src/index.js",
"module": "dist/src/index.js",
"exports": {
".": "./dist/src/index.js",
"./pendor-clock.js": "./dist/src/pendor-clock.js"
},
"scripts": {
"analyze": "cem analyze --litelement",
"start": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wds\"",
"build": "tsc && npm run analyze -- --exclude dist",
"prepublish": "tsc && npm run analyze -- --exclude dist",
"bundle": "npm run build && rollup -c rollup.conf.js && rm -fr build-modern/node_modules",
"lint": "eslint --ext .ts,.html ./src --ignore-path .gitignore && prettier \"**/*.ts\" --config .prettierrc.json --check --ignore-path .gitignore",
"lint:hard": "eslint --ext .ts,.html --config ./.eslintrc.precommit.json ./src/ --ignore-path .gitignore && prettier --config .prettierrc.json \"**/*.ts\" --check --ignore-path .gitignore",
"format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --config .prettierrc.json --write --ignore-path .gitignore",
"lint:spelling": "codespell -D - ./src -s",
"storybook": "tsc && npm run analyze -- --exclude dist && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wds -c .storybook/server.mjs\"",
"storybook:build": "tsc && npm run analyze -- --exclude dist && build-storybook"
},
"dependencies": {
"lit": "^2.0.2"
},
"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",
"@custom-elements-manifest/analyzer": "^0.4.17",
"@open-wc/eslint-config": "^9.2.1",
"@rollup/plugin-node-resolve": "^15.2.2",
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@web/dev-server": "^0.1.34",
"@web/dev-server-storybook": "^0.5.4",
"concurrently": "^5.3.0",
"eslint": "^8.31.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",
"eslint-plugin-custom-elements": "^0.0.8",
"eslint-plugin-sonarjs": "^0.21.0",
"eslint-plugin-storybook": "^0.6.14",
"husky": "^4.3.8",
"lint-staged": "^10.5.4",
"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"
"rollup": "^2.79.1",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-minify-html-literals": "^1.2.6",
"rollup-plugin-terser": "^7.0.2",
"tslib": "^2.3.1",
"typescript": "^4.5.2"
},
"customElements": "custom-elements.json",
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"extends": [
"@open-wc",
"prettier"
],
"plugins": [
"@typescript-eslint"
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error"
],
"import/no-unresolved": "off",
"import/extensions": [
"error",
"always",
{
"ignorePackages": true
}
]
}
},
"prettier": {
"singleQuote": true,
"arrowParens": "avoid"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
]
}
}

32
rollup.conf.js Normal file
View File

@ -0,0 +1,32 @@
import resolve from "@rollup/plugin-node-resolve";
import { terser } from "rollup-plugin-terser";
import minifyHTML from "rollup-plugin-minify-html-literals";
import copy from "rollup-plugin-copy";
// Static assets will vary depending on the application
const copyConfig = {
targets: [
{ src: "node_modules/@webcomponents", dest: "build-modern/node_modules" },
{ src: "images", dest: "build-modern" },
{ src: "data", dest: "build-modern" },
{ src: "index.html", dest: "build-modern" },
],
};
// The main JavaScript bundle for modern browsers that support
// JavaScript modules and other ES2015+ features.
const config = {
input: "dist/src/pendor-clock.js",
output: {
dir: "build-modern/",
format: "es",
},
plugins: [minifyHTML(), copy(copyConfig), resolve()],
preserveEntrySignatures: false,
};
if (process.env.NODE_ENV !== "development") {
config.plugins.push(terser());
}
export default config;

157
src/PendorClock.ts Normal file
View File

@ -0,0 +1,157 @@
import { LitElement, ReactiveController, ReactiveControllerHost, css, html, render } from "lit";
const terranMonthIntervals = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
const pendorMonthIntervals = [0, 1, 25, 49, 73, 97, 121, 145, 146, 147, 171, 195, 211, 243, 267, 291, 292];
const pendorMonthNames = [
"Yestar",
"Narrin",
"Nenim",
"Sulim",
"Virta",
"Lothess",
"Narnya",
"Attendes",
"Loende",
"Cerim",
"Urim",
"Yavar",
"Narquel",
"Hiss",
"Ring",
"Mettare",
];
const pendorWeekdayNames = ["Seren", "Anar", "Noren", "Aldea", "Erwer", "Elenya"];
const prefix = (n: number) => `${n < 10 ? "0" : ""}${n.toFixed(0)}`;
export class ClockController implements ReactiveController {
host: ReactiveControllerHost;
value = new Date();
timeout: number;
// Node and the DOM do not agree on the type. Grrr.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _timerID?: any;
constructor(host: ReactiveControllerHost, timeout = 1000) {
(this.host = host).addController(this);
this.timeout = timeout;
}
hostConnected() {
this._timerID = setInterval(() => {
this.value = new Date();
this.host.requestUpdate();
}, this.timeout);
}
hostDisconnected() {
clearInterval(this._timerID);
this._timerID = undefined;
}
}
const styles = css`
*,
*::before,
*::after {
all: unset;
display: revert;
box-sizing: border-box;
}
:host {
padding-top: 0;
letter-spacing: 1px;
--default-font-size: calc(clamp(0.63rem, calc(0.5rem + 0.63vw), 0.9rem));
font-family: Bitwise, Audiowide, Tahoma, Arial, Helvetica, sans-serif;
flex: 0 1 auto;
text-align: left;
}
div#clock {
padding: 0.175rem 0.375rem 0.175rem 0.375rem;
background-color: var(--pendorclock-background-color, #000030);
color: var(--pendorclock-color, #ffffff);
font-size: var(--pendorclock-font-size, --default-font-size);
line-height: var(--pendorclock-line-height, 1.35);
font-weight: var(--pendorclock-font-weight, 700);
min-width: 20ch;
max-width: 35ch;
text-align: center;
}
`;
const fontStyle = css`
@font-face {
font-family: "Audiowide";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/audiowide/v20/l7gdbjpo0cum0ckerWCdlg_O.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
`;
export class PendorClock extends LitElement {
static get styles() {
return styles;
}
clock: ClockController;
constructor() {
super();
this.clock = new ClockController(this, 250);
}
connectedCallback() {
super.connectedCallback();
if (document.getElementById("pendor-font-block")) {
return;
}
const head = document.head || document.getElementsByTagName("head")[0];
const style = html`<style id="pendor-font-block" type="text/css">
${fontStyle}
</style>`;
render(style, head);
}
tick(now: Date) {
let hours = terranMonthIntervals[now.getMonth()] + now.getDate();
if (now.getMonth() > 2 && now.getFullYear() % 4 == 0) {
hours++;
}
// DST Calculation, and wildly wrong, but WTF
hours = hours * 24 + now.getHours() - 16;
const year = now.getFullYear() + 16;
const dayOfYear = hours / 30;
hours = hours % 30;
let seconds = (now.getSeconds() + now.getMinutes() * 60) / 2.25;
const minutes = seconds / 40;
seconds = seconds % 40;
const timePart = `${hours.toFixed(0)}:${prefix(minutes)}:${prefix(seconds)}`;
const nextMonth = pendorMonthIntervals.findIndex(i => i >= dayOfYear);
if (nextMonth === undefined || pendorMonthIntervals[nextMonth - 1] === undefined) {
return undefined;
}
const dayOfMonth = dayOfYear - pendorMonthIntervals[nextMonth - 1];
const dayOfWeek = (Math.ceil(dayOfMonth) - 1) % 6;
return `${pendorWeekdayNames[dayOfWeek]}, ${pendorMonthNames[nextMonth - 1]} ${dayOfMonth.toFixed(
0
)}, 00${year.toFixed(0)}, ${timePart}`;
}
render() {
return html`<div id="clock">${this.tick(this.clock.value)}</div>`;
}
}

View File

@ -1,60 +1 @@
const aiMonths = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
const aiPendor = [0, 1, 25, 49, 73, 97, 121, 145, 146, 147, 171, 195, 211, 243, 267, 291, 292];
const asWNames = ["Seren", "Anar", "Noren", "Aldea", "Erwer", "Elenya"];
const asMNames = [
"Yestar",
"Narrin",
"Nenim",
"Sulim",
"Virta",
"Lothess",
"Narnya",
"Attendes",
"Loende",
"Cerim",
"Urim",
"Yavar",
"Narquel",
"Hiss",
"Ring",
"Mettare",
];
const p = (n: number): string => {
const t = n.toFixed(0);
return n < 10 ? `0${t}` : `${t}`;
};
const pendorClock = () => {
const theDiv: HTMLSpanElement = document.getElementById("pendordate")!;
let timer = 0;
const update = () => {
const now = new Date();
const days =
aiMonths[now.getMonth()]! +
now.getDate() +
(now.getMonth() > 2 && now.getUTCFullYear() % 4 === 0 ? 1 : 0);
const tHours = days * 24 + now.getHours() - 16;
const tSeconds = (now.getSeconds() + now.getMinutes() * 60) / 2.25;
const [doy, hours, year, minutes, seconds] = [
tHours / 30,
tHours % 30,
now.getUTCFullYear() - 1884,
tSeconds / 40,
tSeconds % 40,
];
const mIndex = aiPendor.findIndex((k) => k > doy);
const dayOfMonth = doy - aiPendor[mIndex - 1]!;
const dayOfWeek = (Math.ceil(dayOfMonth) - 1) % 6;
theDiv.innerHTML = `${asWNames[dayOfWeek]!}, ${asMNames[mIndex - 1]!} ${dayOfMonth.toFixed(
0
)}, 00${year.toFixed(0)}, ${p(hours)}:${p(minutes)}:${p(seconds)}`;
window.clearTimeout(timer);
timer = window.setTimeout(update, 1250);
};
timer = window.setTimeout(update, 125);
};
window.addEventListener("load", pendorClock);
export { PendorClock } from "./PendorClock.js";

5
src/pendor-clock.ts Normal file
View File

@ -0,0 +1,5 @@
import { PendorClock } from "./PendorClock.js";
if (!window.customElements.get("pendor-clock")) {
window.customElements.define("pendor-clock", PendorClock);
}

61
stories/index.stories.ts Normal file
View File

@ -0,0 +1,61 @@
import { TemplateResult, html } from "lit";
import "../src/pendor-clock.js";
export default {
title: "PendorClock",
component: "pendor-clock",
argTypes: {
title: { control: "text" },
counter: { control: "number" },
textColor: { control: "color" },
},
};
interface Story<T> {
(args: T): TemplateResult;
args?: Partial<T>;
argTypes?: Record<string, unknown>;
}
interface ArgTypes {
title?: string;
counter?: number;
textColor?: string;
slot?: TemplateResult;
}
const Template: Story<ArgTypes> = ({
title = "Hello world",
counter = 5,
textColor,
slot,
}: ArgTypes) => html`
<pendor-clock
style="--pendor-clock-text-color: ${textColor || "black"}"
.title=${title}
.counter=${counter}
>
${slot}
</pendor-clock>
`;
export const Regular = Template.bind({});
export const CustomTitle = Template.bind({});
CustomTitle.args = {
title: "My title",
};
export const CustomCounter = Template.bind({});
CustomCounter.args = {
counter: 123456,
};
export const SlottedContent = Template.bind({});
SlottedContent.args = {
slot: html`<p>Slotted content</p>`,
};
SlottedContent.argTypes = {
slot: { table: { disable: true } },
};

View File

@ -1,30 +1,21 @@
{
"compilerOptions": {
"sourceMap": false,
"noImplicitAny": false,
"target": "es2018",
"module": "esnext",
"target": "es6",
"lib": ["es6", "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. */,
"noEmitOnError": true,
"lib": ["es2017", "dom"],
"strict": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"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/*"]
}
"importHelpers": true,
"outDir": "dist",
"sourceMap": true,
"inlineSources": true,
"rootDir": "./",
"declaration": true,
"incremental": true
},
"include": ["./src/**/*"]
"include": ["**/*.ts"]
}

View File

@ -1,34 +0,0 @@
import path from "path";
import viteCompression from "vite-plugin-compression";
import eslintPlugin from "vite-plugin-eslint";
const config = (mode) => ({
plugins: [
viteCompression({ filter: /\.(js|css|map)$/, algorithm: "gzip", ext: ".gz" }),
viteCompression({ filter: /\.(js|css|map)$/, algorithm: "brotliCompress", ext: ".br" }),
eslintPlugin({ cache: true }),
],
sourcemap: mode === "development",
build: {
outDir: "build",
sourcemap: mode === "development",
minify: !mode === "development",
brotliSize: false,
emptyOutDir: true,
},
optimizeDeps: {
allowNodeBuiltins: false,
},
server: {
proxy: {
// Allows us to run the proxy server independent of the content, and still
// get full-service.
},
},
});
export default config;

27
web-dev-server.config.mjs Normal file
View File

@ -0,0 +1,27 @@
// import { hmrPlugin, presets } from '@open-wc/dev-server-hmr';
/** Use Hot Module replacement by adding --hmr to the start command */
const hmr = process.argv.includes('--hmr');
export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
open: '/demo/',
/** Use regular watch mode if HMR is not enabled. */
watch: !hmr,
/** Resolve bare module imports */
nodeResolve: {
exportConditions: ['browser', 'development'],
},
/** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
// esbuildTarget: 'auto'
/** Set appIndex to enable SPA routing */
// appIndex: 'demo/index.html',
plugins: [
/** Use Hot Module Replacement by uncommenting. Requires @open-wc/dev-server-hmr plugin */
// hmr && hmrPlugin({ exclude: ['**/*/node_modules/**/*'], presets: [presets.litElement] }),
],
// See documentation for all available options
});