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": { "env": {
"browser": true, "browser": true,
"es2021": 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": { "parserOptions": {
"project": ["tsconfig.json"], "ecmaVersion": 12,
"ecmaVersion": 2020,
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["@typescript-eslint", "lit", "custom-elements"],
"ignorePatterns": ["demo/**", "stories/**"],
"rules": { "rules": {
"no-await-in-loop": "off", "indent": "off",
"no-use-before-define": "off", "linebreak-style": ["error", "unix"],
"no-nested-ternary": "off", "quotes": ["error", "double", { "avoidEscape": true }],
"@typescript-eslint/no-use-before-define": ["error"], "semi": ["error", "always"],
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-vars": "off", "no-unused-vars": "off",
"import/extensions": "off", "@typescript-eslint/no-unused-vars": [
"import/prefer-default-export": "off", "error",
"@typescript-eslint/ban-types": "warn", {
"@typescript-eslint/no-use-before-define": "warn", "argsIgnorePattern": "^_",
"arrow-body-style": "warn", "varsIgnorePattern": "^_",
"camelcase": "warn", "caughtErrorsIgnorePattern": "^_"
"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"
} }
} ]
} }
} }

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 @@
*# ## editors
.#* /.idea
*~ /.vscode
node_modules
build ## 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,83 +1,85 @@
# Mozilla Public License Version 2.0 Mozilla Public License Version 2.0
==================================
### 1. Definitions ### 1. Definitions
**1.1. “Contributor”** **1.1. “Contributor”**
means each individual or legal entity that creates, contributes to means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software. the creation of, or owns Covered Software.
**1.2. “Contributor Version”** **1.2. “Contributor Version”**
means the combination of the Contributions of others (if any) used means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution. by a Contributor and that particular Contributor's Contribution.
**1.3. “Contribution”** **1.3. “Contribution”**
means Covered Software of a particular Contributor. means Covered Software of a particular Contributor.
**1.4. “Covered Software”** **1.4. “Covered Software”**
means Source Code Form to which the initial Contributor has attached means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case Form, and Modifications of such Source Code Form, in each case
including portions thereof. including portions thereof.
**1.5. “Incompatible With Secondary Licenses”** **1.5. “Incompatible With Secondary Licenses”**
means 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 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 version 1.1 or earlier of the License, but not also under the
terms of a Secondary License. terms of a Secondary License.
**1.6. “Executable Form”** **1.6. “Executable Form”**
means any form of the work other than Source Code Form. means any form of the work other than Source Code Form.
**1.7. “Larger Work”** **1.7. “Larger Work”**
means a work that combines Covered Software with other material, in means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software. a separate file or files, that is not Covered Software.
**1.8. “License”** **1.8. “License”**
means this document. means this document.
**1.9. “Licensable”** **1.9. “Licensable”**
means having the right to grant, to the maximum extent possible, means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License. all of the rights conveyed by this License.
**1.10. “Modifications”** **1.10. “Modifications”**
means any of the following: 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 deletion from, or modification of the contents of Covered
Software; or 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. Software.
**1.11. “Patent Claims” of a Contributor** **1.11. “Patent Claims” of a Contributor**
means any patent claim(s), including without limitation, method, means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its made, import, or transfer of either its Contributions or its
Contributor Version. Contributor Version.
**1.12. “Secondary License”** **1.12. “Secondary License”**
means either the GNU General Public License, Version 2.0, the GNU means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those Public License, Version 3.0, or any later versions of those
licenses. licenses.
**1.13. “Source Code Form”** **1.13. “Source Code Form”**
means the form of the work preferred for making modifications. means the form of the work preferred for making modifications.
**1.14. “You” (or “Your”)** **1.14. “You” (or “Your”)**
means an individual or a legal entity exercising rights under this means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that License. For legal entities, “You” includes any entity that
controls, is controlled by, or is under common control with You. For controls, is controlled by, or is under common control with You. For
purposes of this definition, “control” means **(a)** the power, direct purposes of this definition, “control” means **(a)** the power, direct
or indirect, to cause the direction or management of such entity, or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or **(b)** ownership of more than whether by contract or otherwise, or **(b)** ownership of more than
fifty percent (50%) of the outstanding shares or beneficial fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity. ownership of such entity.
### 2. License Grants and Conditions ### 2. License Grants and Conditions
@ -86,12 +88,12 @@ ownership of such entity.
Each Contributor hereby grants You a world-wide, royalty-free, Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license: 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, Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and 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 for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version. 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 Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor: 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 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 modifications of Covered Software, or **(ii)** the combination of its
Contributions with other software (except as part of its Contributor Contributions with other software (except as part of its Contributor
Version); or 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. its Contributions.
This License does not grant any rights in the trademarks, service marks, 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 Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1. in Section 2.1.
### 3. Responsibilities ### 3. Responsibilities
#### 3.1. Distribution of Source Form #### 3.1. Distribution of Source Form
@ -162,13 +165,13 @@ Form.
If You distribute Covered Software in Executable Form then: 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 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 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 Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and 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, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License. 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 disclaimers of warranty and limitations of liability specific to any
jurisdiction. jurisdiction.
### 4. Inability to Comply Due to Statute or Regulation ### 4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this 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 or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it. recipient of ordinary skill to be able to understand it.
### 5. Termination ### 5. Termination
**5.1.** The rights granted under this License will terminate automatically **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 have been validly granted by You or Your distributors under this License
prior to termination shall survive termination. prior to termination shall survive termination.
### 6. Disclaimer of Warranty ### 6. Disclaimer of Warranty
> Covered Software is provided under this License on an “as is” > 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 > incidental or consequential damages, so this exclusion and
> limitation may not apply to You. > limitation may not apply to You.
### 8. Litigation ### 8. Litigation
Any litigation relating to this License may be brought only in the 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 Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims. cross-claims or counter-claims.
### 9. Miscellaneous ### 9. Miscellaneous
This License represents the complete agreement concerning the subject 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 that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor. shall not be used to construe this License against a Contributor.
### 10. Versions of the License ### 10. Versions of the License
#### 10.1. New Versions #### 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 Way back at the dawn of the Internet, I started writing (and sometimes still write) a long-running
three first Javascript programs I ever wrote. It's just the "time of adult space opera serial called [The Journal Entries](https://pendorwright.com/journals). One of the
day" counter for the fictional world that's the setting of my conceits of the series in that the distant world of Pendor was built and terraformed to have a
long-running [space opera 30-hour day, but its years and Terra's years are of exactly the same length. A 30-hour day has a
series](https://www.pendorwright.com/journals/). It has its own different calendar, and one of the very first [Java
calendar, and unlike Star Trek, I had in mind what the "star dates" applets](https://en.wikipedia.org/wiki/Java_applet) (remember those?) I wrote was a clock that
would mean early on. 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 ``` JavaScript
like to write this in 2021. The answer is that not much has changed; background-color: var(--pendorclock-background-color, #000030);
the code runs just fine, although `getYear()` has been deprecated, color: var(--pendorclock-color, #ffffff);
replaced by `.geUTCFullYear()`. The syntax of 2021 Javascript is a lot font-size: var(--pendorclock-font-size, --default-font-size);
nicer than 1996, although there is a limit to how much density one can line-height: var(--pendorclock-line-height, 1.35);
achieve when it's a lot of fiddly calculations around converting font-weight: var(--pendorclock-font-weight, 700);
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
``` ```
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>

18369
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,95 @@
{ {
"name": "pendorclock", "name": "pendor-clock",
"version": "1.0.0", "description": "Webcomponent pendor-clock following open-wc recommendations",
"description": "The Pendor Clock, updated for 2021", "license": "MIT",
"main": "build/index.js", "author": "pendor-clock",
"scripts": { "version": "0.0.0",
"build": "vite build", "type": "module",
"lint": "eslint", "main": "dist/src/index.js",
"dev": "vite -m development", "module": "dist/src/index.js",
"test": "jest" "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": { "devDependencies": {
"@types/jest": "^27.0.2", "@custom-elements-manifest/analyzer": "^0.4.17",
"@typescript-eslint/eslint-plugin": "^5.4.0", "@open-wc/eslint-config": "^9.2.1",
"@typescript-eslint/parser": "^5.4.0", "@rollup/plugin-node-resolve": "^15.2.2",
"eslint": "^8.2.0", "@trivago/prettier-plugin-sort-imports": "^4.2.0",
"eslint-config-airbnb": "^19.0.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-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.5.0", "eslint-plugin-custom-elements": "^0.0.8",
"eslint-plugin-import": "^2.25.3", "eslint-plugin-sonarjs": "^0.21.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-storybook": "^0.6.14",
"jest": "^27.3.1", "husky": "^4.3.8",
"lint-staged": "^10.5.4",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"rimraf": "^3.0.2", "rollup": "^2.79.1",
"typescript": "^4.4.4", "rollup-plugin-copy": "^3.5.0",
"vite": "^2.6.14", "rollup-plugin-minify-html-literals": "^1.2.6",
"vite-plugin-compression": "^0.3.5", "rollup-plugin-terser": "^7.0.2",
"vite-plugin-ejs": "^1.4.3", "tslib": "^2.3.1",
"vite-plugin-eslint": "^1.3.0" "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]; export { PendorClock } from "./PendorClock.js";
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);

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

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
});