diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4742c5a..d3063f5 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,34 +1,22 @@ module.exports = { - "env": { - "browser": true, - "es2021": true + "env": { + "browser": true, + "es2021": true, + }, + "extends": "eslint:recommended", + "overrides": [ + { + "env": { + "node": true, + }, + "files": [".eslintrc.{js,cjs}"], + "parserOptions": { + "sourceType": "script", + }, }, - "extends": "eslint:recommended", - "overrides": [ - { - "env": { - "node": true - }, - "files": [ - ".eslintrc.{js,cjs}" - ], - "parserOptions": { - "sourceType": "script" - } - } - ], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": { - "indent": [ - "error", - 4 - ], - "semi": [ - "error", - "always" - ] - } + ], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + }, }; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9c6b791 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +out +dist +pnpm-lock.yaml +LICENSE.md +tsconfig.json +tsconfig.*.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..d9c2460 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,6 @@ +singleQuote: false +semi: true +printWidth: 100 +trailingComma: all +quoteProps: preserve +tabWidth: 2 diff --git a/README.md b/README.md index 9bf116a..98559df 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,6 @@ Room 233's student response system ## Build -```npm run build``` +`npm run build` -Serve the files that are generated in ```/dist``` +Serve the files that are generated in `/dist` diff --git a/index.html b/index.html index 1799754..02670a8 100644 --- a/index.html +++ b/index.html @@ -1,230 +1,304 @@ - + - - - - - - - + + + + + + Virtual Clicker - - - - + + + + - +
-

000

- -
- -
- - -
+

000

+ +
+ +
+ +
- -
-
-
- -

-
-
-
- - - - - - - - - - -
- -
-
-
-
- -
-
- +
+ +
+
+
+ +

+
+
+
+ + + + + + + + + +
+ +
+
+
+
+
-
- - - - - +
+
- +
+
+ + + + + +
+
- - - - + + + +
-
+
-

-
-
- - - - -
+

+
+
+ + + + +
-

- - - - -
- +

+ + + + +
+ +
+
+ + +
+
-
- - - -
- - -

Where do you sit?

-

Screen

-
-
+
+ + +

Where do you sit?

+

Screen

+
-
-
-

000

-

Question

-
-

Answer

-
-
-
- - -
- - -
-
-
-

000

-

Question

-
-

Answer

-
-
-
-
- - - - - - - -
- -

Use Shift + R to reset theme if you - can't - get back here

-
- - -
-
+
+
+
+

000

+

Question

+
+

Answer

+
+
+
+
+
-
-

Ctrl + Enter Submit click

-

Alt + 1-9 Insert nth symbol

-

Ctrl + / Open Keyboard Shortcuts

-

Ctrl + , Open Settings

-

Ctrl + . Open History

-

Shift + R Reset theme

+
+ +
-
- - +
+
+

000

+

Question

+
+

Answer

+
+
+
+
+ + + + + + + +
+ +

+ Use Shift + R to reset + the theme if you can't get back here +

+
+ + +
+
+
+

Ctrl + Enter Submit click

+

Alt + 1-9 Insert nth symbol

+

Ctrl + / Open Keyboard Shortcuts

+

Ctrl + , Open Settings

+

Ctrl + . Open History

+

Shift + R Reset theme

+
+
+ + +
-

VSCHSD administration enforces a setting which clears site settings when you close your - browser

-

Fix this by adding an exception for this site:

-
    -
  1. In a new tab, open edge://settings/clearBrowsingDataOnClose
  2. -
  3. Click "Add" under "Cookies and other site data"
  4. -
  5. Add
  6. -
+

+ VSCHSD administration enforces a setting which clears site settings when you close your + browser +

+

Fix this by adding an exception for this site:

+
    +
  1. + In a new tab, open + edge://settings/clearBrowsingDataOnClose +
  2. +
  3. Click "Add" under "Cookies and other site data"
  4. +
  5. + Add +
  6. +
- - - \ No newline at end of file + + diff --git a/package-lock.json b/package-lock.json index ea48d40..a605957 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,20 @@ { "name": "virtual-clicker", - "version": "3.8.2", + "version": "3.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "virtual-clicker", - "version": "3.8.2", + "version": "3.14.0", "dependencies": { "bootstrap-icons": "^1.11.3", - "mathlive": "^0.98.6" + "mathlive": "^0.98.6", + "vite-plugin-package-version": "^1.1.0" }, "devDependencies": { "eslint": "^8.51.0", + "prettier": "^3.2.5", "vite": "^4.4.0", "vite-plugin-webfont-dl": "^3.7.6" } @@ -46,7 +48,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -62,7 +63,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -78,7 +78,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -94,7 +93,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -110,7 +108,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -126,7 +123,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -142,7 +138,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -158,7 +153,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -174,7 +168,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -190,7 +183,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -206,7 +198,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -222,7 +213,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -238,7 +228,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -254,7 +243,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -270,7 +258,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -286,7 +273,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -302,7 +288,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -318,7 +303,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -334,7 +318,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -350,7 +333,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -366,7 +348,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -382,7 +363,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -484,7 +464,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -500,7 +479,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "optional": true, "peer": true, "engines": { @@ -511,7 +489,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "optional": true, "peer": true, "engines": { @@ -522,7 +499,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -534,7 +510,6 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true, "optional": true, "peer": true }, @@ -542,7 +517,6 @@ "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -589,7 +563,7 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -704,7 +678,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "optional": true, "peer": true }, @@ -848,7 +821,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, "optional": true, "peer": true, "bin": { @@ -874,7 +846,6 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -1194,7 +1165,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -1408,7 +1378,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.21.7.tgz", "integrity": "sha512-xITZyh5sLFwRPYUSw15T00Rm7gcQ1qOPuQwNOcvHsTm6nLWTQ723w7zl42wrC5t+xtdg6FPmnXHml1nZxxvp1w==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -1440,7 +1409,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -1461,7 +1429,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -1482,7 +1449,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -1503,7 +1469,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1524,7 +1489,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1545,7 +1509,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1566,7 +1529,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1587,7 +1549,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1608,7 +1569,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -1702,7 +1662,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, "funding": [ { "type": "github", @@ -1820,14 +1779,12 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1860,6 +1817,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -1918,7 +1890,6 @@ "version": "3.28.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", - "dev": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -1978,7 +1949,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -1987,7 +1958,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1996,7 +1966,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -2044,7 +2013,6 @@ "version": "5.16.4", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.4.tgz", "integrity": "sha512-5yEGuZ3DZradbogeYQ1NaGz7rXVBDWujWlx1PT8efXO6Txn+eWbfKqB2bTDVmFXmePFkoLU6XI8UektMIEA0ug==", - "dev": true, "optional": true, "peer": true, "dependencies": { @@ -2064,7 +2032,6 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, "optional": true, "peer": true }, @@ -2108,10 +2075,9 @@ } }, "node_modules/vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", - "dev": true, + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -2162,6 +2128,14 @@ } } }, + "node_modules/vite-plugin-package-version": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-package-version/-/vite-plugin-package-version-1.1.0.tgz", + "integrity": "sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==", + "peerDependencies": { + "vite": ">=2.0.0-beta.69" + } + }, "node_modules/vite-plugin-webfont-dl": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/vite-plugin-webfont-dl/-/vite-plugin-webfont-dl-3.7.6.tgz", @@ -2231,154 +2205,132 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "dev": true, "optional": true }, "@esbuild/android-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "dev": true, "optional": true }, "@esbuild/android-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "dev": true, "optional": true }, "@esbuild/darwin-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", - "dev": true, "optional": true }, "@esbuild/darwin-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "dev": true, "optional": true }, "@esbuild/freebsd-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "dev": true, "optional": true }, "@esbuild/linux-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "dev": true, "optional": true }, "@esbuild/linux-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "dev": true, "optional": true }, "@esbuild/linux-ia32": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "dev": true, "optional": true }, "@esbuild/linux-loong64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "dev": true, "optional": true }, "@esbuild/linux-mips64el": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "dev": true, "optional": true }, "@esbuild/linux-ppc64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "dev": true, "optional": true }, "@esbuild/linux-riscv64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "dev": true, "optional": true }, "@esbuild/linux-s390x": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "dev": true, "optional": true }, "@esbuild/linux-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "dev": true, "optional": true }, "@esbuild/netbsd-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "dev": true, "optional": true }, "@esbuild/openbsd-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "dev": true, "optional": true }, "@esbuild/sunos-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "dev": true, "optional": true }, "@esbuild/win32-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "dev": true, "optional": true }, "@esbuild/win32-ia32": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "dev": true, "optional": true }, "@esbuild/win32-x64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "dev": true, "optional": true }, "@eslint-community/eslint-utils": { @@ -2446,7 +2398,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, "optional": true, "peer": true, "requires": { @@ -2459,7 +2410,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, "optional": true, "peer": true }, @@ -2467,7 +2417,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "optional": true, "peer": true }, @@ -2475,7 +2424,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, "optional": true, "peer": true, "requires": { @@ -2487,7 +2435,6 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true, "optional": true, "peer": true }, @@ -2495,7 +2442,6 @@ "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "dev": true, "optional": true, "peer": true, "requires": { @@ -2533,7 +2479,7 @@ "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true + "devOptional": true }, "acorn-jsx": { "version": "5.3.2", @@ -2617,7 +2563,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "optional": true, "peer": true }, @@ -2722,7 +2667,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, "optional": true, "peer": true }, @@ -2739,7 +2683,6 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, "requires": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", @@ -2974,7 +2917,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "optional": true }, "glob": { @@ -3136,7 +3078,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.21.7.tgz", "integrity": "sha512-xITZyh5sLFwRPYUSw15T00Rm7gcQ1qOPuQwNOcvHsTm6nLWTQ723w7zl42wrC5t+xtdg6FPmnXHml1nZxxvp1w==", - "dev": true, "optional": true, "peer": true, "requires": { @@ -3156,7 +3097,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.21.7.tgz", "integrity": "sha512-tt7hIsFio9jZofTVHtCACz6rB6c9RyABMXfA9A/VcKOjS3sq+koX/QkRJWY06utwOImbJIXBC5hbg9t3RkPUAQ==", - "dev": true, "optional": true, "peer": true }, @@ -3164,7 +3104,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.21.7.tgz", "integrity": "sha512-F4gS4bf7eWekfPT+TxJNm/pF+QRgZiTrTkQH6cw4/UWfdeZISfuhD5El2dm16giFnY0K5ylIwO+ZusgYNkGSXA==", - "dev": true, "optional": true, "peer": true }, @@ -3172,7 +3111,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.21.7.tgz", "integrity": "sha512-RMfNzJWXCSfPnL55fcLWEAadcY6QUFT0S8NceNKYzp1KiCZtkJIy6RQ5SaVxPzRqd3iMsahUf5sfnG8N1UQSNQ==", - "dev": true, "optional": true, "peer": true }, @@ -3180,7 +3118,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.21.7.tgz", "integrity": "sha512-biSRUDZNx7vubWP1jArw/qqfZKPGpkV/qzunasZzxmqijbZ43sW9faDQYxWNcxPWljJJdF/qs6qcurYFovWtrQ==", - "dev": true, "optional": true, "peer": true }, @@ -3188,7 +3125,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.21.7.tgz", "integrity": "sha512-PENY8QekqL9TG3AY/A7rkUBb5ymefGxea7Oe7+x7Hbw4Bz4Hpj5cec5OoMypMqFbURPmpi0fTWx4vSWUPzpDcA==", - "dev": true, "optional": true, "peer": true }, @@ -3196,7 +3132,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.21.7.tgz", "integrity": "sha512-pfOipKvA/0X1OjRaZt3870vnV9UGBSjayIqHh0fGx/+aRz3O0MVFHE/60P2UWXpM3YGJEw/hMWtNkrFwqOge8A==", - "dev": true, "optional": true, "peer": true }, @@ -3204,7 +3139,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.21.7.tgz", "integrity": "sha512-dgcsis4TAA7s0ia4f31QHX+G4PWPwxk+wJaEQLaV0NdJs09O5hHoA8DpLEr8nrvc/tsRTyVNBP1rDtgzySjpXg==", - "dev": true, "optional": true, "peer": true }, @@ -3212,7 +3146,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.21.7.tgz", "integrity": "sha512-A+9dXpxld3p4Cd6fxev2eqEvaauYtrgNpXV3t7ioCJy30Oj9nYiNGwiGusM+4MJVcEpUPGUGiuAqY4sWilRDwA==", - "dev": true, "optional": true, "peer": true }, @@ -3220,7 +3153,6 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.21.7.tgz", "integrity": "sha512-07/8vogEq+C/mF99pdMhh/f19/xreq8N9Ca6AWeVHZIdODyF/pt6KdKSCWDZWIn+3CUxI8gCJWuUWyOc3xymvw==", - "dev": true, "optional": true, "peer": true }, @@ -3280,8 +3212,7 @@ "nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" }, "natural-compare": { "version": "1.4.0", @@ -3360,14 +3291,12 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, "requires": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -3380,6 +3309,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3414,7 +3349,6 @@ "version": "3.28.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", - "dev": true, "requires": { "fsevents": "~2.3.2" } @@ -3447,19 +3381,17 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "devOptional": true }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "optional": true, "peer": true, "requires": { @@ -3495,7 +3427,6 @@ "version": "5.16.4", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.4.tgz", "integrity": "sha512-5yEGuZ3DZradbogeYQ1NaGz7rXVBDWujWlx1PT8efXO6Txn+eWbfKqB2bTDVmFXmePFkoLU6XI8UektMIEA0ug==", - "dev": true, "optional": true, "peer": true, "requires": { @@ -3509,7 +3440,6 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, "optional": true, "peer": true } @@ -3546,10 +3476,9 @@ } }, "vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", - "dev": true, + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "requires": { "esbuild": "^0.18.10", "fsevents": "~2.3.2", @@ -3557,6 +3486,12 @@ "rollup": "^3.27.1" } }, + "vite-plugin-package-version": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-package-version/-/vite-plugin-package-version-1.1.0.tgz", + "integrity": "sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==", + "requires": {} + }, "vite-plugin-webfont-dl": { "version": "3.7.6", "resolved": "https://registry.npmjs.org/vite-plugin-webfont-dl/-/vite-plugin-webfont-dl-3.7.6.tgz", diff --git a/package.json b/package.json index 1e1ed95..2b7951a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "virtual-clicker", "private": true, - "version": "3.12.0", + "version": "3.14.0", "type": "module", "scripts": { "dev": "vite --host", @@ -18,11 +18,13 @@ "homepage": "https://github.com/khui0/virtual-clicker#readme", "devDependencies": { "eslint": "^8.51.0", + "prettier": "^3.2.5", "vite": "^4.4.0", "vite-plugin-webfont-dl": "^3.7.6" }, "dependencies": { "bootstrap-icons": "^1.11.3", - "mathlive": "^0.98.6" + "mathlive": "^0.98.6", + "vite-plugin-package-version": "^1.1.0" } -} \ No newline at end of file +} diff --git a/src/clicker/clicker.js b/src/clicker/clicker.js index c2f59ff..08d154a 100644 --- a/src/clicker/clicker.js +++ b/src/clicker/clicker.js @@ -4,7 +4,8 @@ import storage from "/src/modules/storage.js"; import { autocomplete } from "/src/symbols/symbols.js"; import { unixToTimeString } from "/src/modules/time.js"; import { getPeriod } from "/src/periods/periods"; -import { convertLatexToAsciiMath } from "mathlive"; +import { convertLatexToAsciiMath, convertLatexToMarkup, renderMathInElement } from "mathlive"; +``; const questionInput = document.getElementById("question-input"); const answerInput = document.getElementById("answer-input"); @@ -17,178 +18,179 @@ let historyIndex = 0; // Initialization { - // Get URL parameters - const params = new URLSearchParams(window.location.search); - const code = params.get("code"); - // Test for valid seat code - const regex = /^[1-9][1-6][1-5]$/; - if (regex.test(code)) { - // Update seat code - storage.set("code", code); - updateCode(); - } - // Show seat code modal if no saved code exists - if (storage.get("code")) { - updateCode(); - } - else { - ui.view("settings/code"); - } - // Show clear data fix guide - if (storage.get("created")) { - document.querySelector(`[data-modal-view="clear-data-fix"]`).remove(); - } - else { - storage.set("created", Date.now()); - } - // Focus question input - questionInput.focus(); - // Set default answer mode - answerMode("input"); - // Populate seat code finder grid - for (let col = 1; col <= 5; col++) { - for (let row = 6; row > 0; row--) { - const period = document.getElementById("period-input").value; - const code = period + row.toString() + col.toString(); - const button = new ui.Element("button", "", { - click: () => { - document.getElementById("code-input").value = code; - ui.view("settings/code"); - }, - }).element; - document.getElementById("seat-grid").append(button); - ui.addTooltip(button, code); - } + // Get URL parameters + const params = new URLSearchParams(window.location.search); + const code = params.get("code"); + // Test for valid seat code + const regex = /^[1-9][1-6][1-5]$/; + if (regex.test(code)) { + // Update seat code + storage.set("code", code); + updateCode(); + } + // Show seat code modal if no saved code exists + if (storage.get("code")) { + updateCode(); + } else { + ui.view("settings/code"); + } + // Show clear data fix guide + if (storage.get("created")) { + document.querySelector(`[data-modal-view="clear-data-fix"]`).remove(); + } else { + storage.set("created", Date.now()); + } + // Focus question input + questionInput.focus(); + // Set default answer mode + answerMode("input"); + // Populate seat code finder grid + for (let col = 1; col <= 5; col++) { + for (let row = 6; row > 0; row--) { + const period = document.getElementById("period-input").value; + const code = period + row.toString() + col.toString(); + const button = new ui.Element("button", "", { + click: () => { + document.getElementById("code-input").value = code; + ui.view("settings/code"); + }, + }).element; + document.getElementById("seat-grid").append(button); + ui.addTooltip(button, code); } - // Update history feed - updateHistory(); - // Focus answer input - document.getElementById("answer-suggestion").addEventListener("click", () => answerInput.focus()); + } + // Update history feed + updateHistory(); + // Focus answer input + document.getElementById("answer-suggestion").addEventListener("click", () => answerInput.focus()); } // Submit click document.getElementById("submit-button").addEventListener("click", () => { - const mode = ui.getButtonSelectValue(document.getElementById("answer-mode-selector")); - const question = questionInput.value?.trim(); - const answer = multipleChoice || (() => { - if (mode === "input") { - return answerInput.value?.trim(); - } else if (mode === "math") { - return convertLatexToAsciiMath(mf.value); - } + const mode = ui.getButtonSelectValue(document.getElementById("answer-mode-selector")); + const question = questionInput.value?.trim(); + const answer = + multipleChoice || + (() => { + if (mode === "input") { + return answerInput.value?.trim(); + } else if (mode === "math") { + return convertLatexToAsciiMath(mf.value); + } })(); - if (storage.get("code")) { - if (question && answer) { - // Check if code matches current period - const matchesCurrentPeriod = parseInt(storage.get("code").slice(0, 1)) === getPeriod() + 1 || true; - if (!matchesCurrentPeriod) { - ui.prompt("Are you sure you want to submit?", "Your seat code isn't for this period!", [ - { - text: "Cancel", - close: true, - }, - { - text: "Submit Anyways", - close: true, - onclick: submit, - } - ]); - } else { - submit(); - } - } - if (!answer) { - if (mode === "input") { - answerInput.classList.add("attention"); - answerInput.focus(); - } else if (mode === "math") { - mf.classList.add("attention"); - mf.focus(); - } - } - if (!question) { - questionInput.classList.add("attention"); - questionInput.focus(); - } + if (storage.get("code")) { + if (question && answer) { + // Check if code matches current period + const matchesCurrentPeriod = + parseInt(storage.get("code").slice(0, 1)) === getPeriod() + 1 || true; + if (!matchesCurrentPeriod) { + ui.prompt("Are you sure you want to submit?", "Your seat code isn't for this period!", [ + { + text: "Cancel", + close: true, + }, + { + text: "Submit Anyways", + close: true, + onclick: submit, + }, + ]); + } else { + submit(); + } } - else { - ui.view("settings/code"); + if (!answer) { + if (mode === "input") { + answerInput.classList.add("attention"); + answerInput.focus(); + } else if (mode === "math") { + mf.classList.add("attention"); + mf.focus(); + } } - function submit() { - submitClick(storage.get("code"), question, answer); - if (mode === "math" && !multipleChoice) { - storeClick(storage.get("code"), question, mf.value, "latex"); - } else { - storeClick(storage.get("code"), question, answer, "text"); - } - resetInputs(); - // Show submit confirmation - ui.modeless(``, "Submitted!"); + if (!question) { + questionInput.classList.add("attention"); + questionInput.focus(); + } + } else { + ui.view("settings/code"); + } + function submit() { + submitClick(storage.get("code"), question, answer); + if (mode === "math" && !multipleChoice) { + storeClick(storage.get("code"), question, mf.value, "latex"); + } else { + storeClick(storage.get("code"), question, answer, "text"); } + resetInputs(); + // Show submit confirmation + ui.modeless(``, "Submitted!"); + } }); // Remove attention ring when user types in either input -questionInput.addEventListener("input", e => { - e.target.classList.remove("attention"); +questionInput.addEventListener("input", (e) => { + e.target.classList.remove("attention"); }); -answerInput.addEventListener("input", e => { - e.target.classList.remove("attention"); +answerInput.addEventListener("input", (e) => { + e.target.classList.remove("attention"); }); -mf.addEventListener("input", e => { - e.target.classList.remove("attention"); +mf.addEventListener("input", (e) => { + e.target.classList.remove("attention"); }); // Prevent MathLive default behavior -mf.addEventListener("keydown", e => { - if (e.ctrlKey && e.key == "Enter") { - e.preventDefault(); - } +mf.addEventListener("keydown", (e) => { + if (e.ctrlKey && e.key == "Enter") { + e.preventDefault(); + } }); // Reset inputs to default state function resetInputs() { - const mode = ui.getButtonSelectValue(document.getElementById("answer-mode-selector")); - // Reset answer inputs - questionInput.value = ""; - answerInput.value = ""; - mf.value = ""; - // Switch input mode (exit multiple choice) - answerMode(mode); - multipleChoice = null; - autocomplete.update(); - // Focus input element - questionInput.focus(); + const mode = ui.getButtonSelectValue(document.getElementById("answer-mode-selector")); + // Reset answer inputs + questionInput.value = ""; + answerInput.value = ""; + mf.value = ""; + // Switch input mode (exit multiple choice) + answerMode(mode); + multipleChoice = null; + autocomplete.update(); + // Focus input element + questionInput.focus(); } // Submit to Google Forms function submitClick(code, question, answer) { - const fields = { - "entry.1896388126": code, - "entry.1232458460": question, - "entry.1065046570": answer - }; - const params = new URLSearchParams(fields).toString(); - const url = "https://docs.google.com/forms/d/e/1FAIpQLSfwDCxVqO2GuB4jhk9iAl7lzoA2TsRlX6hz052XkEHbLrbryg/formResponse?"; - fetch(url + params, { - method: "POST", - mode: "no-cors", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - } - }); + const fields = { + "entry.1896388126": code, + "entry.1232458460": question, + "entry.1065046570": answer, + }; + const params = new URLSearchParams(fields).toString(); + const url = + "https://docs.google.com/forms/d/e/1FAIpQLSfwDCxVqO2GuB4jhk9iAl7lzoA2TsRlX6hz052XkEHbLrbryg/formResponse?"; + fetch(url + params, { + method: "POST", + mode: "no-cors", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + }); } // Limit seat code input to integers -document.getElementById("code-input").addEventListener("input", e => { - e.target.value = parseInt(e.target.value) || ""; +document.getElementById("code-input").addEventListener("input", (e) => { + e.target.value = parseInt(e.target.value) || ""; }); // Save seat code on enter -document.getElementById("code-input").addEventListener("keydown", e => { - if (e.key == "Enter") { - e.preventDefault(); - saveCode(); - } +document.getElementById("code-input").addEventListener("keydown", (e) => { + if (e.key == "Enter") { + e.preventDefault(); + saveCode(); + } }); // Save seat code button @@ -196,263 +198,278 @@ document.getElementById("save-code-button").addEventListener("click", saveCode); // Save seat code function saveCode() { - const input = document.getElementById("code-input").value; - // Tests for valid seat code - const regex = /^[1-9][1-6][1-5]$/; - if (regex.test(input)) { - storage.set("code", input); - updateCode(); - // Close all modals - ui.view(""); - // Update URL parameters with seat code - const params = new URLSearchParams(window.location.search); - params.set("code", input); - history.replaceState({}, "", "?" + params.toString()); - } - else { - ui.alert("Error", "Seat code isn't possible"); - } + const input = document.getElementById("code-input").value; + // Tests for valid seat code + const regex = /^[1-9][1-6][1-5]$/; + if (regex.test(input)) { + storage.set("code", input); + updateCode(); + // Close all modals + ui.view(""); + // Update URL parameters with seat code + const params = new URLSearchParams(window.location.search); + params.set("code", input); + history.replaceState({}, "", "?" + params.toString()); + } else { + ui.alert("Error", "Seat code isn't possible"); + } } // Update elements with new seat code function updateCode() { - if (storage.get("code")) { - document.getElementById("code-input").value = storage.get("code"); - document.querySelectorAll("span.code").forEach(element => { - element.innerHTML = storage.get("code"); - }); - document.title = `Virtual Clicker (${storage.get("code")})`; - } + if (storage.get("code")) { + document.getElementById("code-input").value = storage.get("code"); + document.querySelectorAll("span.code").forEach((element) => { + element.innerHTML = storage.get("code"); + }); + document.title = `Virtual Clicker (${storage.get("code")})`; + } } // Show multiple choice card -document.querySelectorAll("[data-multiple-choice]").forEach(button => { - const descriptions = { - "a": ["Agree", "True", "Yes"], - "b": ["Disagree", "False", "No"], - "c": ["Both", "Always"], - "d": ["Neither", "Never"], - "e": ["Sometimes", "Cannot be determined"], - }; - button.addEventListener("click", e => { - const choice = e.target.getAttribute("data-multiple-choice"); - // Set content of multiple choice card - const content = document.querySelector(`[data-answer-mode="choice"]>div`); - content.innerHTML = `

Choice ${choice.toUpperCase()}

+document.querySelectorAll("[data-multiple-choice]").forEach((button) => { + const descriptions = { + "a": ["Agree", "True", "Yes"], + "b": ["Disagree", "False", "No"], + "c": ["Both", "Always"], + "d": ["Neither", "Never"], + "e": ["Sometimes", "Cannot be determined"], + }; + button.addEventListener("click", (e) => { + const choice = e.target.getAttribute("data-multiple-choice"); + // Set content of multiple choice card + const content = document.querySelector(`[data-answer-mode="choice"]>div`); + content.innerHTML = `

Choice ${choice.toUpperCase()}

Equivalent to submitting

${descriptions[choice].join(", ")}

`; - // Show multiple choice card - answerMode("choice"); - multipleChoice = `CHOICE ${choice.toUpperCase()}`; - }); + // Show multiple choice card + answerMode("choice"); + multipleChoice = `CHOICE ${choice.toUpperCase()}`; + }); }); // Hide multiple choice card document.getElementById("remove-choice-button").addEventListener("click", () => { - answerMode(ui.getButtonSelectValue(document.getElementById("answer-mode-selector"))); - multipleChoice = null; + answerMode(ui.getButtonSelectValue(document.getElementById("answer-mode-selector"))); + multipleChoice = null; }); // Set answer mode function answerMode(mode) { - const current = document.querySelector(`[data-answer-mode="${currentAnswerMode}"]`); - const fromHeight = current?.getBoundingClientRect().height; - - if (currentAnswerMode == mode) return; - document.querySelectorAll("[data-answer-mode]").forEach(item => { - if (item.getAttribute("data-answer-mode") == mode) { - item.style.removeProperty("display"); - } - else { - item.style.display = "none"; + const current = document.querySelector(`[data-answer-mode="${currentAnswerMode}"]`); + const fromHeight = current?.getBoundingClientRect().height; + + if (currentAnswerMode == mode) return; + document.querySelectorAll("[data-answer-mode]").forEach((item) => { + if (item.getAttribute("data-answer-mode") == mode) { + item.style.removeProperty("display"); + } else { + item.style.display = "none"; + } + }); + + // Animate container + const container = document.getElementById("answer-container"); + const target = document.querySelector(`[data-answer-mode="${mode}"]`); + const toHeight = target.getBoundingClientRect().height; + ui.animate( + container, + fromHeight + ? { + height: fromHeight + "px", } - }); + : undefined, + { + height: toHeight + "px", + }, + 500, + false, + ); - // Animate container - const container = document.getElementById("answer-container"); - const target = document.querySelector(`[data-answer-mode="${mode}"]`); - const toHeight = target.getBoundingClientRect().height; - ui.animate(container, fromHeight ? { - height: fromHeight + "px", - } : undefined, { - height: toHeight + "px", - }, 500, false); - - currentAnswerMode = mode; + currentAnswerMode = mode; } // Store click to storage and history function storeClick(code, question, answer, type) { - const history = storage.get("history") || []; - const timestamp = Date.now(); - history.push({ - "code": code, - "question": question, - "answer": answer, - "timestamp": timestamp, - "type": type || "text", - }); - storage.set("history", history); - updateHistory(); + const history = storage.get("history") || []; + const timestamp = Date.now(); + history.push({ + "code": code, + "question": question, + "answer": answer, + "timestamp": timestamp, + "type": type || "text", + }); + storage.set("history", history); + updateHistory(); } document.getElementById("history-first").addEventListener("click", () => { - historyIndex = getHistoryDates().length - 1; - updateHistory(); + historyIndex = getHistoryDates().length - 1; + updateHistory(); }); document.getElementById("history-backward").addEventListener("click", () => { - historyIndex++; - updateHistory(); + historyIndex++; + updateHistory(); }); document.getElementById("history-forward").addEventListener("click", () => { - historyIndex--; - updateHistory(); + historyIndex--; + updateHistory(); }); document.getElementById("history-last").addEventListener("click", () => { - historyIndex = 0; - updateHistory(); + historyIndex = 0; + updateHistory(); }); // Count number of unique days function getHistoryDates() { - const data = (storage.get("history") || []).map(entry => { - const day = new Date(entry.timestamp).toISOString().split("T")[0]; - return { ...entry, day: day }; - }); - const unique = data.map(entry => entry.day).filter((value, i, array) => { - return array.indexOf(value) === i; - }).reverse(); - return unique; + const data = (storage.get("history") || []).map((entry) => { + const day = new Date(entry.timestamp).toISOString().split("T")[0]; + return { ...entry, day: day }; + }); + const unique = data + .map((entry) => entry.day) + .filter((value, i, array) => { + return array.indexOf(value) === i; + }) + .reverse(); + return unique; } // Filter history by date function filterHistory() { - const data = (storage.get("history") || []).map(entry => { - const day = new Date(entry.timestamp).toISOString().split("T")[0]; - return { ...entry, day: day }; - }); - return data.filter(entry => entry.day === getHistoryDates()[historyIndex]); + const data = (storage.get("history") || []).map((entry) => { + const day = new Date(entry.timestamp).toISOString().split("T")[0]; + return { ...entry, day: day }; + }); + return data.filter((entry) => entry.day === getHistoryDates()[historyIndex]); } // Update history feed function updateHistory() { - const history = filterHistory(); - const date = history[0] && new Intl.DateTimeFormat("en-US", { - weekday: "long", - year: "numeric", - month: "long", - day: "numeric", + const history = filterHistory(); + const date = + history[0] && + new Intl.DateTimeFormat("en-US", { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", }).format(new Date(history[0]?.day)); - // Update history navigation - document.getElementById("history-first").disabled = historyIndex === getHistoryDates().length - 1; - document.getElementById("history-backward").disabled = historyIndex === getHistoryDates().length - 1; - document.getElementById("history-forward").disabled = historyIndex === 0; - document.getElementById("history-last").disabled = historyIndex === 0; - document.getElementById("history-date").textContent = date; - - const feed = document.getElementById("history-feed"); - if (history.length != 0) { - feed.innerHTML = ""; - history.forEach(item => { - const button = document.createElement("button"); - button.innerHTML = `

${item.question}. ${unixToTimeString(item.timestamp)} (${item.code})

\n

${item.answer}

`; - feed.prepend(button); - // Resubmit click - button.addEventListener("click", () => { - questionInput.value = item.question; - const latex = item.type === "latex"; - ui.view(""); - if (latex) { - answerMode("math"); - ui.setButtonSelectValue(document.getElementById("answer-mode-selector"), "math"); - mf.value = item.answer; - } else { - const choice = item.answer.match(/^CHOICE ([A-E])$/); - if (!choice) { - answerInput.value = item.answer; - answerMode("input"); - } - else { - document.querySelector(`[data-multiple-choice="${choice[1].toLowerCase()}"]`).click(); - } - questionInput.focus(); - autocomplete.update(); - } - }); - }); - } - else { - feed.innerHTML = "

Submitted clicks will show up here!

"; - } + // Update history navigation + document.getElementById("history-first").disabled = historyIndex === getHistoryDates().length - 1; + document.getElementById("history-backward").disabled = + historyIndex === getHistoryDates().length - 1; + document.getElementById("history-forward").disabled = historyIndex === 0; + document.getElementById("history-last").disabled = historyIndex === 0; + document.getElementById("history-date").textContent = date; + + const feed = document.getElementById("history-feed"); + if (history.length != 0) { + feed.innerHTML = ""; + history.forEach((item) => { + const button = document.createElement("button"); + const latex = item.type === "latex"; + if (!latex) { + button.innerHTML = `

${item.question}. ${unixToTimeString(item.timestamp)} (${item.code})

\n

${item.answer}

`; + } else { + button.innerHTML = `

${item.question}. ${unixToTimeString(item.timestamp)} (${item.code})

\n${convertLatexToMarkup(item.answer)}\n

(Equation may not display properly)

`; + } + feed.prepend(button); + renderMathInElement(button); + // Resubmit click + button.addEventListener("click", () => { + questionInput.value = item.question; + ui.view(""); + if (latex) { + answerMode("math"); + ui.setButtonSelectValue(document.getElementById("answer-mode-selector"), "math"); + mf.value = item.answer; + } else { + const choice = item.answer.match(/^CHOICE ([A-E])$/); + if (!choice) { + answerInput.value = item.answer; + answerMode("input"); + } else { + document.querySelector(`[data-multiple-choice="${choice[1].toLowerCase()}"]`).click(); + } + questionInput.focus(); + autocomplete.update(); + } + }); + }); + } else { + feed.innerHTML = "

Submitted clicks will show up here!

"; + } } // Reset modals const resets = { - "history": () => { - ui.prompt("Clear history?", "This action cannot be reversed!", [ - { - text: "Cancel", - close: true, - }, - { - text: "Clear", - close: true, - onclick: () => { - storage.delete("history"); - window.location.reload(); - }, - }, - ]); - }, - "all": () => { - ui.prompt("Reset all settings?", "This action cannot be reversed!", [ - { - text: "Cancel", - close: true, - }, - { - text: "Reset", - close: true, - onclick: () => { - storage.obliterate(); - window.location.reload(); - }, - }, - ]); - }, + "history": () => { + ui.prompt("Clear history?", "This action cannot be reversed!", [ + { + text: "Cancel", + close: true, + }, + { + text: "Clear", + close: true, + onclick: () => { + storage.delete("history"); + window.location.reload(); + }, + }, + ]); + }, + "all": () => { + ui.prompt("Reset all settings?", "This action cannot be reversed!", [ + { + text: "Cancel", + close: true, + }, + { + text: "Reset", + close: true, + onclick: () => { + storage.obliterate(); + window.location.reload(); + }, + }, + ]); + }, }; // Show reset modal -document.querySelectorAll("[data-reset]").forEach(button => { - button.addEventListener("click", e => { - resets[e.target.getAttribute("data-reset")](); - }); +document.querySelectorAll("[data-reset]").forEach((button) => { + button.addEventListener("click", (e) => { + resets[e.target.getAttribute("data-reset")](); + }); }); // Disable developer mode button if (storage.get("developer")) { - document.querySelector(`[data-modal-page="reset"]`).append( - new ui.Element("button", "Disable Developer Mode", { - "click": () => { - storage.delete("developer"); - }, - }).element - ); + document.querySelector(`[data-modal-page="reset"]`).append( + new ui.Element("button", "Disable Developer Mode", { + "click": () => { + storage.delete("developer"); + }, + }).element, + ); } const answerLabel = document.querySelector(`label[for="answer-input"]`); // Select answer mode -document.getElementById("answer-mode-selector").addEventListener("input", e => { - const mode = e.detail; - answerMode(mode); - if (mode === "input") { - answerLabel.setAttribute("for", "answer-input"); - } else if (mode === "math") { - answerLabel.setAttribute("for", "math-input"); - } -}); \ No newline at end of file +document.getElementById("answer-mode-selector").addEventListener("input", (e) => { + const mode = e.detail; + answerMode(mode); + if (mode === "input") { + answerLabel.setAttribute("for", "answer-input"); + } else if (mode === "math") { + answerLabel.setAttribute("for", "math-input"); + } +}); diff --git a/src/design.css b/src/design.css index eb92709..6ef76b0 100644 --- a/src/design.css +++ b/src/design.css @@ -1,64 +1,66 @@ * { - color: inherit; - background-color: inherit; + color: inherit; + background-color: inherit; } :root { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #fafafa; - --surface-color: #e7e7e7; - --accent-color: #424242; - --accent-text-color: #ffffff; - --error-color: #fa8796; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #fafafa; + --surface-color: #e7e7e7; + --accent-color: #424242; + --accent-text-color: #ffffff; + --error-color: #fa8796; } :focus-visible { - outline: none; - box-shadow: 0 0 0 0.125rem var(--accent-color) inset; - transition: box-shadow 100ms ease; + outline: none; + box-shadow: 0 0 0 0.125rem var(--accent-color) inset; + transition: box-shadow 100ms ease; } ::placeholder { - color: var(--text-color); - opacity: 0.5; + color: var(--text-color); + opacity: 0.5; } ::-webkit-scrollbar { - width: 1rem; + width: 1rem; } ::-webkit-scrollbar-track { - background-color: transparent; + background-color: transparent; } ::-webkit-scrollbar-thumb { - border-radius: 0.5rem; - border: 0.25rem solid transparent; - background-color: var(--surface-color); - background-clip: content-box; + border-radius: 0.5rem; + border: 0.25rem solid transparent; + background-color: var(--surface-color); + background-clip: content-box; } ::-webkit-scrollbar-thumb:hover { - background-color: var(--accent-color); + background-color: var(--accent-color); } body { - min-height: 100%; - padding: 1rem; - color: var(--text-color); - background-color: var(--background-color); - font: 16px "Figtree", sans-serif; - line-height: 1.5; + min-height: 100%; + padding: 1rem; + color: var(--text-color); + background-color: var(--background-color); + font: + 16px "Figtree", + sans-serif; + line-height: 1.5; } body.enable-transitions { - --easing: cubic-bezier(0.32, 0, 0.67, 0); + --easing: cubic-bezier(0.32, 0, 0.67, 0); } h1 { - font-size: 1.75rem; - line-height: 1.2; + font-size: 1.75rem; + line-height: 1.2; } h2, @@ -66,268 +68,269 @@ h3, h4, h5, h6 { - font-size: 1.5rem; - line-height: 1.2; + font-size: 1.5rem; + line-height: 1.2; } button { - height: 2.25em; - padding: 0 0.75em; - border: none; - border-radius: 0.5rem; - background-color: var(--surface-color); - font: inherit; - transition: 100ms color var(--easing), 100ms background-color var(--easing); + height: 2.25em; + padding: 0 0.75em; + border: none; + border-radius: 0.5rem; + background-color: var(--surface-color); + font: inherit; + transition: + 100ms color var(--easing), + 100ms background-color var(--easing); } button.icon { - width: 2.25em; - height: 2.25em; - padding: 0; - border-radius: 50%; + width: 2.25em; + height: 2.25em; + padding: 0; + border-radius: 50%; } button:disabled { - opacity: 0.5; - pointer-events: none; + opacity: 0.5; + pointer-events: none; } a.icon { - font-size: 1.5em; + font-size: 1.5em; } .pill { - border-radius: 100vh; + border-radius: 100vh; } i.bi { - background-color: transparent; + background-color: transparent; } input, select { - height: 2.25em; - padding: 0 0.5em; - border: none; - border-radius: 0.5rem; - background-color: var(--surface-color); + height: 2.25em; + padding: 0 0.5em; + border: none; + border-radius: 0.5rem; + background-color: var(--surface-color); } textarea { - padding: 0.375em 0.5em; - border: none; - border-radius: 0.5rem; - background-color: var(--surface-color); - resize: none; + padding: 0.375em 0.5em; + border: none; + border-radius: 0.5rem; + background-color: var(--surface-color); + resize: none; } ul, ol { - margin: 0; - padding: 0; - padding-left: 2rem; + margin: 0; + padding: 0; + padding-left: 2rem; } li { - word-wrap: break-word; + word-wrap: break-word; } kbd { - height: 1.5em; - padding: 0 0.25rem; - border-radius: 0.25rem; - background-color: var(--surface-color); - font: 14px "Figtree", sans-serif; + height: 1.5em; + padding: 0 0.25rem; + border-radius: 0.25rem; + background-color: var(--surface-color); + font: + 14px "Figtree", + sans-serif; } code { - height: 1.5em; - padding: 0 0.25rem; - border-radius: 0.25rem; - background-color: var(--surface-color); + height: 1.5em; + padding: 0 0.25rem; + border-radius: 0.25rem; + background-color: var(--surface-color); } button[data-theme] { - height: 3rem; - color: var(--text-color); - background-color: var(--surface-color); - text-transform: capitalize; + height: 3rem; + color: var(--text-color); + background-color: var(--surface-color); + text-transform: capitalize; } .attention { - box-shadow: 0 0 0 0.125rem var(--error-color) inset; - transition: box-shadow 100ms ease; + box-shadow: 0 0 0 0.125rem var(--error-color) inset; + transition: box-shadow 100ms ease; } .virtual-clicker-logo { - color: var(--logo-color); + color: var(--logo-color); } #code-input { - font-size: 2.5em; - text-align: center; - height: 1.5em; - background-color: transparent; + font-size: 2.5em; + text-align: center; + height: 1.5em; + background-color: transparent; } @media (hover: hover) { - button:hover { - color: var(--accent-text-color); - background-color: var(--accent-color); - } + button:hover { + color: var(--accent-text-color); + background-color: var(--accent-color); + } - label:hover+button { - background-color: var(--surface-color); - } + label:hover + button { + background-color: var(--surface-color); + } - button[data-theme]:hover { - color: var(--accent-text-color); - background-color: var(--accent-color); - } + button[data-theme]:hover { + color: var(--accent-text-color); + background-color: var(--accent-color); + } } [data-gg-chat-anchor], [data-gg-privacy-banner-anchor] { - display: none !important; + display: none !important; } #theme-preview, #editor-preview { - display: flex; - flex-direction: column; - gap: 0.25rem; - width: min(100%, 300px); - margin: 1rem; - align-self: center; - padding: 1rem; - border-radius: 1rem; - background-color: var(--background-color); - transition: 200ms var(--easing); + display: flex; + flex-direction: column; + gap: 0.25rem; + width: min(100%, 300px); + margin: 1rem; + align-self: center; + padding: 1rem; + border-radius: 1rem; + background-color: var(--background-color); + transition: 200ms var(--easing); } .text-placeholder { - color: transparent; - background-color: var(--text-color); - width: fit-content; - height: 0.8em; - -webkit-user-select: none; - user-select: none; - border-radius: 0.25rem; - transition: 200ms var(--easing); + color: transparent; + background-color: var(--text-color); + width: fit-content; + height: 0.8em; + -webkit-user-select: none; + user-select: none; + border-radius: 0.25rem; + transition: 200ms var(--easing); } .control-placeholder { - height: 2.25em; - padding: 0 0.5em; - border: none; - border-radius: 0.5rem; - background-color: var(--surface-color); - transition: 200ms var(--easing); + height: 2.25em; + padding: 0 0.5em; + border: none; + border-radius: 0.5rem; + background-color: var(--surface-color); + transition: 200ms var(--easing); } .control-placeholder.pill { - border-radius: 100vh; -} - -#theme-selector, -#theme-selector>option { - text-transform: capitalize; + border-radius: 100vh; } .hint { - align-self: center; - text-align: center; - font-size: 0.8em; - text-transform: uppercase; - line-height: 1; + align-self: center; + text-align: center; + font-size: 0.8em; + text-transform: uppercase; + line-height: 1; } math-field { - --caret-color: var(--text-color); - --contains-highlight-background-color: var(--background-color); - --text-font-family: "Figtree", sans-serif; + --caret-color: var(--text-color); + --contains-highlight-background-color: var(--background-color); + --text-font-family: "Figtree", sans-serif; - padding: 0.375em 0.5em; - border: none; - border-radius: 0.5rem; - background-color: var(--surface-color); - font-size: 1.25em; - cursor: text; + padding: 0.375em 0.5em; + border: none; + border-radius: 0.5rem; + background-color: var(--surface-color); + font-size: 1.25em; + cursor: text; } math-field:focus { - outline: none; - box-shadow: 0 0 0 0.125rem var(--accent-color) inset; - transition: box-shadow 100ms ease; + outline: none; + box-shadow: 0 0 0 0.125rem var(--accent-color) inset; + transition: box-shadow 100ms ease; } math-field.attention { - box-shadow: 0 0 0 0.125rem var(--error-color) inset; - transition: box-shadow 100ms ease; + box-shadow: 0 0 0 0.125rem var(--error-color) inset; + transition: box-shadow 100ms ease; } math-field::part(virtual-keyboard-toggle) { - color: var(--text-color); - background-color: var(--surface-color); - transition: 100ms color var(--easing), 100ms background-color var(--easing); + color: var(--text-color); + background-color: var(--surface-color); + transition: + 100ms color var(--easing), + 100ms background-color var(--easing); } math-field::part(virtual-keyboard-toggle):hover { - color: var(--accent-text-color); - background-color: var(--accent-color); + color: var(--accent-text-color); + background-color: var(--accent-color); } math-field::part(menu-toggle) { - display: none; + display: none; } math-field::part(content) { - padding: 0; + padding: 0; } div.ML__keyboard { - background-color: transparent; + background-color: transparent; } [data-button-select] { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 0.125rem; - padding: 0.25rem; - background-color: var(--surface-color); - border-radius: 0.5rem; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.125rem; + padding: 0.25rem; + background-color: var(--surface-color); + border-radius: 0.5rem; } -[data-button-select]>button { - height: 1.75em; - border-radius: 0.25rem; +[data-button-select] > button { + height: 1.75em; + border-radius: 0.25rem; } -[data-button-select]>button[aria-selected="true"] { - color: var(--accent-text-color); - background-color: var(--accent-color); +[data-button-select] > button[aria-selected="true"] { + color: var(--accent-text-color); + background-color: var(--accent-color); } #answer-input { - background-color: transparent; + background-color: transparent; } #answer-input:focus { - box-shadow: none; + box-shadow: none; } #answer-textarea-container:has(#answer-input:focus) { - outline: none; - box-shadow: 0 0 0 0.125rem var(--accent-color) inset; - transition: box-shadow 100ms ease; + outline: none; + box-shadow: 0 0 0 0.125rem var(--accent-color) inset; + transition: box-shadow 100ms ease; } #answer-input.attention { - box-shadow: none; + box-shadow: none; } #answer-textarea-container:has(#answer-input.attention) { - outline: none; - box-shadow: 0 0 0 0.125rem var(--error-color) inset; - transition: box-shadow 100ms ease; -} \ No newline at end of file + outline: none; + box-shadow: 0 0 0 0.125rem var(--error-color) inset; + transition: box-shadow 100ms ease; +} diff --git a/src/festive/festive.css b/src/festive/festive.css deleted file mode 100644 index b3097c4..0000000 --- a/src/festive/festive.css +++ /dev/null @@ -1,53 +0,0 @@ -[data-theme="festive-2023"] { - color-scheme: light; - --text-color: #242424; - --background-color: #f1e2cc; - --surface-color: #dbc8ab; - --accent-color: #f0b73c; - --accent-text-color: #242424; - --error-color: #e451da; - - --red: #f75e5e; - --green: #42b970; -} - -[data-theme="festive-2023"] button:nth-child(odd):not(button[data-theme]) { - --surface-color: var(--green); -} - -[data-theme="festive-2023"] button:nth-child(even):not(button[data-theme]) { - --surface-color: var(--red); -} - -[data-theme="festive-2023"] :is(button, input, textarea, select, dialog, [data-answer-mode="choice"]) { - border: 0.125rem solid #242424; -} - -[data-theme="festive-2023"] #submit-button { - height: 2.25rem; - background: linear-gradient(-45deg, white 25%, var(--red) 25% 50%, white 50% 75%, var(--red) 75%); - background-size: 2rem 2rem; - font: bold 16px "Segoe Script", "Figtree", sans-serif; -} - -[data-theme="festive-2023"] #submit-button:is(:hover, :focus) { - color: var(--text-color); - animation: scroll 500ms linear infinite forwards; -} - -@keyframes scroll { - from { - background-position: 0 0; - } - - to { - background-position: 2rem 0; - } -} - -img.snowflake { - position: absolute; - background-color: transparent; - width: 50px; - pointer-events: none; -} \ No newline at end of file diff --git a/src/keybinds/keybinds.js b/src/keybinds/keybinds.js index 21a6f5d..0d6f74d 100644 --- a/src/keybinds/keybinds.js +++ b/src/keybinds/keybinds.js @@ -4,31 +4,32 @@ import * as themes from "/src/themes/themes.js"; import { insertFromIndex } from "/src/symbols/symbols.js"; -document.addEventListener("keydown", e => { - const anyDialogOpen = Array.from(document.querySelectorAll("dialog")).some(dialog => dialog.open); - const isTyping = document.activeElement.matches("input, textarea"); - if (e.ctrlKey) { - if (e.key == "Enter" && !anyDialogOpen) { - document.getElementById("submit-button").click(); - } - if (e.key == "," && !anyDialogOpen) { - ui.view("settings"); - } - if (e.key == "." && !anyDialogOpen) { - ui.view("history"); - } - if (e.key == "/" && !anyDialogOpen) { - ui.view("settings/keybinds"); - } +document.addEventListener("keydown", (e) => { + const anyDialogOpen = Array.from(document.querySelectorAll("dialog")).some( + (dialog) => dialog.open, + ); + const isTyping = document.activeElement.matches("input, textarea"); + if (e.ctrlKey) { + if (e.key == "Enter" && !anyDialogOpen) { + document.getElementById("submit-button").click(); } - else if (e.altKey) { - if (/[1-9]/.test(e.key)) { - e.preventDefault(); - insertFromIndex(parseInt(e.key) - 1); - } - } else if (e.shiftKey) { - if (e.key == "R" && !anyDialogOpen && !isTyping) { - themes.resetTheme(); - } + if (e.key == "," && !anyDialogOpen) { + ui.view("settings"); } -}); \ No newline at end of file + if (e.key == "." && !anyDialogOpen) { + ui.view("history"); + } + if (e.key == "/" && !anyDialogOpen) { + ui.view("settings/keybinds"); + } + } else if (e.altKey) { + if (/[1-9]/.test(e.key)) { + e.preventDefault(); + insertFromIndex(parseInt(e.key) - 1); + } + } else if (e.shiftKey) { + if (e.key == "R" && !anyDialogOpen && !isTyping) { + themes.resetTheme(); + } + } +}); diff --git a/src/layout.css b/src/layout.css index 5851008..27174c7 100644 --- a/src/layout.css +++ b/src/layout.css @@ -1,236 +1,243 @@ -body>* { - flex-shrink: 0; +body > * { + flex-shrink: 0; } div.spacer { - height: 1.5rem; + height: 1.5rem; } .row { - display: flex; - flex-direction: row; - gap: 0.25rem; - flex-wrap: wrap; + display: flex; + flex-direction: row; + gap: 0.25rem; + flex-wrap: wrap; } .full-row { - display: flex; - flex-direction: row; - gap: 0.25rem; + display: flex; + flex-direction: row; + gap: 0.25rem; } -.full-row>* { - flex: 1; +.full-row > * { + flex: 1; } .col { - display: flex; - flex-direction: column; - gap: 0.25rem; + display: flex; + flex-direction: column; + gap: 0.25rem; } -:is(.space, .extra-space)+.space { - margin-top: 0.25rem; +:is(.space, .extra-space) + .space { + margin-top: 0.25rem; } -:is(.space, .extra-space)+.extra-space { - margin-top: 1.5rem; +:is(.space, .extra-space) + .extra-space { + margin-top: 1.5rem; } .center { - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; } .button-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(2.5rem, 1fr)); - gap: 0.25rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(2.5rem, 1fr)); + gap: 0.25rem; } #header { - align-self: center; - display: flex; - flex-direction: column; - align-items: center; + align-self: center; + display: flex; + flex-direction: column; + align-items: center; } -#header>h1 { - font-size: 1.5rem; +#header > h1 { + font-size: 1.5rem; } #clicker { - flex: 1; - align-self: center; - display: flex; - flex-direction: column; - justify-content: center; - width: min(100%, 800px); + flex: 1; + align-self: center; + display: flex; + flex-direction: column; + justify-content: center; + width: min(100%, 800px); } #question-container { - display: flex; - flex-direction: row; - gap: 0.25rem; - flex-wrap: wrap; - justify-content: center; + display: flex; + flex-direction: row; + gap: 0.25rem; + flex-wrap: wrap; + justify-content: center; } #question-input { - flex: 1; + flex: 1; } #answer-container { - overflow: hidden; - border-radius: 0.5rem; + overflow: hidden; + border-radius: 0.5rem; } [data-answer-mode="input"] { - display: flex; - flex-direction: column-reverse; - gap: 0.25rem; + display: flex; + flex-direction: column-reverse; + gap: 0.25rem; } #answer-textarea-container { - display: flex; - flex-direction: column; - background-color: var(--surface-color); - border-radius: 0.5rem; + display: flex; + flex-direction: column; + background-color: var(--surface-color); + border-radius: 0.5rem; } #answer-input { - padding-bottom: 0; + padding-bottom: 0; } #answer-suggestion { - height: 2em; - line-height: 2; - padding: 0 0.5rem; - background-color: transparent; - -webkit-user-select: none; - user-select: none; - cursor: text; + height: 2em; + line-height: 2; + padding: 0 0.5rem; + background-color: transparent; + -webkit-user-select: none; + user-select: none; + cursor: text; } -#answer-suggestion>kbd { - color: var(--accent-text-color); - background-color: var(--accent-color); +#answer-suggestion > kbd { + color: var(--accent-text-color); + background-color: var(--accent-color); } [data-answer-mode="choice"] { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; - padding: 0.375em 0.5em; - border: none; - border-radius: 0.5rem; - background-color: var(--surface-color); + padding: 0.375em 0.5em; + border: none; + border-radius: 0.5rem; + background-color: var(--surface-color); } [data-answer-mode="math"] { - display: flex; - flex-direction: column; - gap: 0.25rem; + display: flex; + flex-direction: column; + gap: 0.25rem; } -[data-answer-mode="math"]>math-field { - width: 100%; +[data-answer-mode="math"] > math-field { + width: 100%; } #insert-row { - flex: 1; - overflow: hidden; - height: 2.25em; + flex: 1; + overflow: hidden; + height: 2.25em; } #controls-container { - display: flex; - flex-direction: row; - background-color: var(--surface-color); - width: fit-content; - overflow: hidden; - align-self: center; + display: flex; + flex-direction: row; + background-color: var(--surface-color); + width: fit-content; + overflow: hidden; + align-self: center; } #symbols-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 0.125rem; - background-color: var(--surface-color); - border-radius: 0.5rem; - overflow: hidden; + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 0.125rem; + background-color: var(--surface-color); + border-radius: 0.5rem; + overflow: hidden; } -#symbols-grid>button { - border-radius: 0; - font-size: 1.125em; - background-color: var(--background-color); +#symbols-grid > button { + border-radius: 0; + font-size: 1.125em; + background-color: var(--background-color); } -#symbols-grid>button:hover { - background-color: var(--accent-color); +#symbols-grid > button:hover { + background-color: var(--accent-color); } -#symbols-grid>div { - background-color: var(--background-color); +#symbols-grid > div { + background-color: var(--background-color); } -#theme-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); - gap: 0.25rem; +#theme-selector { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 0.25rem; + max-height: 9.5rem; + overflow: auto; +} + +#theme-selector > button { + height: 3em; + line-height: 1.2; } #seat-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 0.25rem 1.5rem; + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 0.25rem 1.5rem; } #history-date { - font-size: 1.25rem; + font-size: 1.25rem; } #history-date:empty { - display: none; + display: none; } -#history-date:empty~#history-navigation { - display: none; +#history-date:empty ~ #history-navigation { + display: none; } #history-feed { - overflow-y: auto; - height: 400px; + overflow-y: auto; + height: 400px; } -#history-feed>button { - height: fit-content; - padding: 0.375em 0.75em; - text-align: left; +#history-feed > button { + height: fit-content; + padding: 0.375em 0.75em; + text-align: left; } -#history-feed>button>p { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; +#history-feed > button > p { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } label { - display: flex; - flex-direction: column; - background-color: transparent; + display: flex; + flex-direction: column; + background-color: transparent; } #theme-editor { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0.25rem; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.25rem; } -#theme-editor>:nth-of-type(1) { - grid-column: 1 / 3; -} \ No newline at end of file +#theme-editor > :nth-of-type(1) { + grid-column: 1 / 3; +} diff --git a/src/main.js b/src/main.js index 5f93271..65c330f 100644 --- a/src/main.js +++ b/src/main.js @@ -11,31 +11,34 @@ import "/src/symbols/symbols.js"; import "/src/themes/themes.js"; import "/src/keybinds/keybinds.js"; -import "/src/festive/festive.js"; - import storage from "/src/modules/storage.js"; +const version = import.meta.env.PACKAGE_VERSION; + updateVersionString(); function updateVersionString() { - document.querySelectorAll("span.version").forEach(element => { - const DEVELOPER_MODE = storage.get("developer"); - element.innerHTML = __APP_VERSION__ + (DEVELOPER_MODE ? " dev" : ""); - }); + document.querySelectorAll("span.version").forEach((element) => { + const DEVELOPER_MODE = storage.get("developer"); + element.innerHTML = + version + + (version.startsWith("3.14") ? " π" : "") + + (DEVELOPER_MODE ? " dev" : ""); + }); } -document.querySelectorAll("span.hostname").forEach(element => { - element.innerHTML = window.location.hostname; +document.querySelectorAll("span.hostname").forEach((element) => { + element.innerHTML = window.location.hostname; }); let developerTimeout; let developerClicks = 0; document.getElementById("version-string").addEventListener("click", () => { - developerClicks++; - clearTimeout(developerTimeout); - developerTimeout = setTimeout(() => { - developerClicks = 0; - }, 1000); - if (developerClicks == 10) { - storage.set("developer", true); - updateVersionString(); - } -}); \ No newline at end of file + developerClicks++; + clearTimeout(developerTimeout); + developerTimeout = setTimeout(() => { + developerClicks = 0; + }, 1000); + if (developerClicks == 10) { + storage.set("developer", true); + updateVersionString(); + } +}); diff --git a/src/modules/mathlive.js b/src/modules/mathlive.js index a212054..cdcf61b 100644 --- a/src/modules/mathlive.js +++ b/src/modules/mathlive.js @@ -1,4 +1,4 @@ import "/node_modules/mathlive/dist/mathlive-fonts.css"; import { MathfieldElement } from "mathlive"; -MathfieldElement.soundsDirectory = null; \ No newline at end of file +MathfieldElement.soundsDirectory = null; diff --git a/src/modules/storage.js b/src/modules/storage.js index 3b8a776..96ae170 100644 --- a/src/modules/storage.js +++ b/src/modules/storage.js @@ -1,33 +1,33 @@ class Storage { - constructor(id) { - this.id = id; - } + constructor(id) { + this.id = id; + } - set(key, value) { - let temp = this.object; - temp[key] = value; - localStorage.setItem(this.id, JSON.stringify(temp)); - return this; - } + set(key, value) { + let temp = this.object; + temp[key] = value; + localStorage.setItem(this.id, JSON.stringify(temp)); + return this; + } - get(key) { - return this.object[key]; - } + get(key) { + return this.object[key]; + } - delete(key) { - let temp = this.object; - delete temp[key]; - localStorage.setItem(this.id, JSON.stringify(temp)); - return this; - } + delete(key) { + let temp = this.object; + delete temp[key]; + localStorage.setItem(this.id, JSON.stringify(temp)); + return this; + } - obliterate() { - localStorage.removeItem(this.id); - } + obliterate() { + localStorage.removeItem(this.id); + } - get object() { - return JSON.parse(localStorage.getItem(this.id)) || {}; - } + get object() { + return JSON.parse(localStorage.getItem(this.id)) || {}; + } } -export default new Storage("virtual-clicker-2"); \ No newline at end of file +export default new Storage("virtual-clicker-2"); diff --git a/src/modules/time.js b/src/modules/time.js index 984438c..c13f554 100644 --- a/src/modules/time.js +++ b/src/modules/time.js @@ -1,41 +1,39 @@ export function unixToString(timestamp) { - let date = new Date(timestamp); - if (timestamp) { - let month = date.getMonth() + 1; - let day = date.getDate(); - let hours = date.getHours(); - let minutes = date.getMinutes().toString().padStart(2, "0"); - let period; - if (hours >= 12) { - hours %= 12; - period = "PM"; - } - else { - period = "AM"; - } - if (hours == 0) { - hours = 12; - } - return `${month}/${day} ${hours}:${minutes} ${period}`; + let date = new Date(timestamp); + if (timestamp) { + let month = date.getMonth() + 1; + let day = date.getDate(); + let hours = date.getHours(); + let minutes = date.getMinutes().toString().padStart(2, "0"); + let period; + if (hours >= 12) { + hours %= 12; + period = "PM"; + } else { + period = "AM"; } + if (hours == 0) { + hours = 12; + } + return `${month}/${day} ${hours}:${minutes} ${period}`; + } } export function unixToTimeString(timestamp) { - let date = new Date(timestamp); - if (timestamp) { - let hours = date.getHours(); - let minutes = date.getMinutes().toString().padStart(2, "0"); - let period; - if (hours >= 12) { - hours %= 12; - period = "PM"; - } - else { - period = "AM"; - } - if (hours == 0) { - hours = 12; - } - return `${hours}:${minutes} ${period}`; + let date = new Date(timestamp); + if (timestamp) { + let hours = date.getHours(); + let minutes = date.getMinutes().toString().padStart(2, "0"); + let period; + if (hours >= 12) { + hours %= 12; + period = "PM"; + } else { + period = "AM"; + } + if (hours == 0) { + hours = 12; } -} \ No newline at end of file + return `${hours}:${minutes} ${period}`; + } +} diff --git a/src/modules/ui.css b/src/modules/ui.css index 780c4d9..ed11265 100644 --- a/src/modules/ui.css +++ b/src/modules/ui.css @@ -1,110 +1,114 @@ dialog[open] { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } dialog { - gap: 0.25rem; - width: min(100%, 450px); - padding: 1rem; - border: none; - border-radius: 1rem; - box-shadow: 0 0 2rem rgba(0, 0, 0, 0.2); + gap: 0.25rem; + width: min(100%, 450px); + padding: 1rem; + border: none; + border-radius: 1rem; + box-shadow: 0 0 2rem rgba(0, 0, 0, 0.2); } dialog::backdrop { - display: none; + display: none; +} + +dialog:modal { + max-width: calc(100vw - 2rem); + max-height: calc(100vh - 2rem); } div[data-modal-page] { - display: flex; - flex-direction: column; - gap: 0.25rem; + display: flex; + flex-direction: column; + gap: 0.25rem; } div[data-modal-menu] { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - gap: 0.25rem; - + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 0.25rem; } div[data-modal-buttons] { - display: flex; - flex-direction: row; - gap: 0.25rem; + display: flex; + flex-direction: row; + gap: 0.25rem; } div[data-modal-actions] { - display: flex; - flex-direction: row; - gap: 0.25rem; + display: flex; + flex-direction: row; + gap: 0.25rem; + margin-top: 0.25rem; } -div[data-modal-actions]>button { - flex: 1; - margin-top: 0.5rem; +div[data-modal-actions] > button { + flex: 1; } -dialog button[data-modal-view]:not(.icon) { - text-align: unset; +dialog > button[data-modal-view]:not(.icon) { + text-align: unset; } div.modeless { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 999; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 999; - display: flex; - flex-direction: column; - align-items: center; - padding: 1.5rem; - border-radius: 1rem; - box-shadow: 0 0 2rem rgba(0, 0, 0, 0.2); - pointer-events: none; + display: flex; + flex-direction: column; + align-items: center; + padding: 1.5rem; + border-radius: 1rem; + box-shadow: 0 0 2rem rgba(0, 0, 0, 0.2); + pointer-events: none; } -div.modeless>h2 { - font-size: 3em; - font-weight: normal; +div.modeless > h2 { + font-size: 3em; + font-weight: normal; } p.tooltip { - pointer-events: none; - padding: 0.25em 0.5em; - border-radius: 0.5rem; - color: var(--accent-text-color); - background-color: var(--accent-color); + pointer-events: none; + padding: 0.25em 0.5em; + border-radius: 0.5rem; + color: var(--accent-text-color); + background-color: var(--accent-color); } div[data-color-input] { - position: relative; + position: relative; } -div[data-color-input]>input { - padding-left: 2.25em; - width: 100%; +div[data-color-input] > input { + padding-left: 2.25em; + width: 100%; } -div[data-color-input]>input[type="color"] { - position: absolute; - pointer-events: none; - opacity: 0; +div[data-color-input] > input[type="color"] { + position: absolute; + pointer-events: none; + opacity: 0; } -div[data-color-input]>div[data-color-preview] { - position: absolute; - width: 1.75em; - height: 1.75em; - top: 0.25em; - left: 0.25em; - border-radius: 0.25em; +div[data-color-input] > div[data-color-preview] { + position: absolute; + width: 1.75em; + height: 1.75em; + top: 0.25em; + left: 0.25em; + border-radius: 0.25em; } -div[data-color-input]>div[data-color-preview]:hover { - cursor: pointer; -} \ No newline at end of file +div[data-color-input] > div[data-color-preview]:hover { + cursor: pointer; +} diff --git a/src/modules/ui.js b/src/modules/ui.js index 7e86eab..c3dec6f 100644 --- a/src/modules/ui.js +++ b/src/modules/ui.js @@ -1,403 +1,437 @@ import "./ui.css"; export function alert(title, text, callback, blur) { - return modal({ - title, - body: new Element("p", text).element.outerHTML, - buttons: [{ - text: "Close", - close: true, - onclick: callback, - }], - blur, - }); + return modal({ + title, + body: new Element("p", text).element.outerHTML, + buttons: [ + { + text: "Close", + close: true, + onclick: callback, + }, + ], + blur, + }); } export function prompt(title, text, buttons, blur) { - return modal({ - title, - body: new Element("p", text).element.outerHTML, - buttons, - blur, - }); + return modal({ + title, + body: new Element("p", text).element.outerHTML, + buttons, + blur, + }); } export function modal(options) { - // Create dialog element - const dialog = document.createElement("dialog"); - dialog.innerHTML = options.body; - // Append dialog element to DOM - document.body.append(dialog); - // Show modal - show(dialog, options.title, null, options.buttons, options.blur); - // Remove dialog element on close - dialog.addEventListener("close", () => { - dialog.remove(); - }); - return dialog; + // Create dialog element + const dialog = document.createElement("dialog"); + dialog.innerHTML = options.body; + // Append dialog element to DOM + document.body.append(dialog); + // Show modal + show(dialog, options.title, null, options.buttons, options.blur); + // Remove dialog element on close + dialog.addEventListener("close", () => { + dialog.remove(); + }); + return dialog; } export function show(dialog, title, buttons, actions, blur, effects = true) { - // Create modal menu bar - const menu = dialog.querySelector("[data-modal-menu]") || (() => { - const div = document.createElement("div"); - div.setAttribute("data-modal-menu", ""); - // Create title element - const titleEl = new Element("h2", title, null, null, { - "data-modal-title": true, - }).element; - div.append(titleEl); - // Add menu bar to dialog - dialog.prepend(div); - return div; + // Create modal menu bar + const menu = + dialog.querySelector("[data-modal-menu]") || + (() => { + const div = document.createElement("div"); + div.setAttribute("data-modal-menu", ""); + // Create title element + const titleEl = new Element("h2", title, null, null, { + "data-modal-title": true, + }).element; + div.append(titleEl); + // Add menu bar to dialog + dialog.prepend(div); + return div; })(); - // Update title - menu.querySelector("[data-modal-title]").textContent = title; - - // Remove existing buttons - menu.querySelector("[data-modal-buttons]")?.remove(); - // Create modal buttons - if (buttons?.length > 0) { - const container = document.createElement("div"); - container.setAttribute("data-modal-buttons", ""); - menu.append(container); - // Populate buttons - buttons.forEach(button => { - container.append( - new Element("button", button.text, { - click: () => { - button.close && close(); - button.onclick && button.onclick(); - }, - }, button.class).element - ); - }); - } - - // Remove existing actions - menu.querySelector("[data-modal-actions]")?.remove(); - // Create modal buttons - if (actions?.length > 0) { - const container = document.createElement("div"); - container.setAttribute("data-modal-actions", ""); - dialog.append(container); - // Populate buttons - actions.forEach(button => { - container.append( - new Element("button", button.text, { - click: () => { - button.close && close(); - button.onclick && button.onclick(); - }, - }, button.class).element - ); - }); - } + // Update title + menu.querySelector("[data-modal-title]").textContent = title; + + // Remove existing buttons + menu.querySelector("[data-modal-buttons]")?.remove(); + // Create modal buttons + if (buttons?.length > 0) { + const container = document.createElement("div"); + container.setAttribute("data-modal-buttons", ""); + menu.append(container); + // Populate buttons + buttons.forEach((button) => { + container.append( + new Element( + "button", + button.text, + { + click: () => { + button.close && close(); + button.onclick && button.onclick(); + }, + }, + button.class, + ).element, + ); + }); + } + + // Remove existing actions + menu.querySelector("[data-modal-actions]")?.remove(); + // Create modal buttons + if (actions?.length > 0) { + const container = document.createElement("div"); + container.setAttribute("data-modal-actions", ""); + dialog.append(container); + // Populate buttons + actions.forEach((button) => { + container.append( + new Element( + "button", + button.text, + { + click: () => { + button.close && close(); + button.onclick && button.onclick(); + }, + }, + button.class, + ).element, + ); + }); + } - dialog.showModal(); + dialog.showModal(); - effects && animate(dialog, { + effects && + animate( + dialog, + { scale: "0.9", opacity: "0", - }, { + }, + { scale: "1", opacity: "1", + }, + 250, + ); + if (effects) { + setTimeout(() => { + dialog.setAttribute("data-open", ""); }, 250); - if (effects) { - setTimeout(() => { - dialog.setAttribute("data-open", ""); - }, 250); - } else { - dialog.setAttribute("data-open", ""); - } + } else { + dialog.setAttribute("data-open", ""); + } - dialog.addEventListener("cancel", e => { - e.preventDefault(); - close(); - }); + dialog.addEventListener("cancel", (e) => { + e.preventDefault(); + close(); + }); - dialog.addEventListener("triggerclose", close, { once: true }); + dialog.addEventListener("triggerclose", close, { once: true }); - function close() { - dialog.removeAttribute("data-open"); - if (effects) { - animate(dialog, undefined, { - scale: "0.9", - opacity: "0", - }, 250); - setTimeout(() => { - dialog.close(); - }, 250); - } else { - dialog.close(); - } + function close() { + dialog.removeAttribute("data-open"); + if (effects) { + animate( + dialog, + undefined, + { + scale: "0.9", + opacity: "0", + }, + 250, + ); + setTimeout(() => { + dialog.close(); + }, 250); + } else { + dialog.close(); } + } - blur && menu.querySelectorAll("[data-modal-buttons]>button").forEach(button => button.blur()); + blur && menu.querySelectorAll("[data-modal-buttons]>button").forEach((button) => button.blur()); } export function view(path) { - if (!path) { - const event = new Event("triggerclose"); - document.querySelector("dialog[open]").dispatchEvent(event); - return; - } - const pages = path.split("/"); - const target = document.querySelector(`[data-modal-page="${pages[pages.length - 1]}"]`); - const title = target.getAttribute("data-page-title") || path; - for (let i = 0; i < pages.length; i++) { - const query = pages.slice(0, i + 1).map(item => `[data-modal-page="${item}"]`).join(">"); - document.querySelectorAll(`${query}>:not([data-modal-menu], [data-modal-title], [data-modal-buttons], .tooltip)`).forEach(element => { - const page = element.getAttribute("data-modal-page"); - if (page == pages[i + 1]) { - element.style.removeProperty("display"); - } - else { - element.style.display = "none"; - } - }); - } - const previous = pages.slice(0, pages.length - 1).join("/"); - const buttons = [ - { - text: ``, - class: "icon", - close: true, - }, - ]; - if (previous) { - buttons.unshift({ - text: ``, - class: "icon", - close: false, - onclick: () => { - const dialog = document.querySelector(`[data-modal-page="${pages[0]}"]`); - dialog.removeAttribute("data-open"); - view(previous); - }, - }); - } - show( - document.querySelector(`[data-modal-page="${pages[0]}"]`), - title, - buttons, - ); - const event = new Event("view"); - target.dispatchEvent(event); + if (!path) { + const event = new Event("triggerclose"); + document.querySelector("dialog[open]").dispatchEvent(event); + return; + } + const pages = path.split("/"); + const target = document.querySelector(`[data-modal-page="${pages[pages.length - 1]}"]`); + const title = target.getAttribute("data-page-title") || path; + for (let i = 0; i < pages.length; i++) { + const query = pages + .slice(0, i + 1) + .map((item) => `[data-modal-page="${item}"]`) + .join(">"); + document + .querySelectorAll( + `${query}>:not([data-modal-menu], [data-modal-title], [data-modal-buttons], .tooltip)`, + ) + .forEach((element) => { + const page = element.getAttribute("data-modal-page"); + if (page == pages[i + 1]) { + element.style.removeProperty("display"); + } else { + element.style.display = "none"; + } + }); + } + const previous = pages.slice(0, pages.length - 1).join("/"); + const buttons = [ + { + text: ``, + class: "icon", + close: true, + }, + ]; + if (previous) { + buttons.unshift({ + text: ``, + class: "icon", + close: false, + onclick: () => { + const dialog = document.querySelector(`[data-modal-page="${pages[0]}"]`); + dialog.removeAttribute("data-open"); + view(previous); + }, + }); + } + show(document.querySelector(`[data-modal-page="${pages[0]}"]`), title, buttons); + const event = new Event("view"); + target.dispatchEvent(event); } export function modeless(icon, message) { - document.querySelector("div.modeless")?.remove(); - const element = document.createElement("div"); - const keyframes = [ - { opacity: 0 }, - { opacity: 1 }, - ]; - element.className = "modeless"; - element.append( - new Element("h2", icon).element, - new Element("p", message).element, - ); + document.querySelector("div.modeless")?.remove(); + const element = document.createElement("div"); + const keyframes = [{ opacity: 0 }, { opacity: 1 }]; + element.className = "modeless"; + element.append(new Element("h2", icon).element, new Element("p", message).element); + element.animate(keyframes, { + duration: 100, + fill: "forwards", + }); + setTimeout(() => { element.animate(keyframes, { - duration: 100, - fill: "forwards", + duration: 100, + direction: "reverse", + fill: "forwards", }); setTimeout(() => { - element.animate(keyframes, { - duration: 100, - direction: "reverse", - fill: "forwards", - }); - setTimeout(() => { - element.remove(); - }, 100); - }, 2400); - document.body.append(element); + element.remove(); + }, 100); + }, 2400); + document.body.append(element); } export function addTooltip(element, text) { - const tooltip = document.createElement("p"); - tooltip.textContent = text; - tooltip.style.opacity = "0"; - tooltip.style.position = "absolute"; - tooltip.style.left = "0px"; - tooltip.style.top = "0px"; - tooltip.classList.add("tooltip"); - - const parent = element.closest("dialog") || document.body; - parent.append(tooltip); - - element.addEventListener("pointerenter", () => { - tooltip.style.left = element.offsetLeft + (element.offsetWidth / 2) + "px"; - tooltip.style.top = element.offsetTop + "px"; - animate(tooltip, { - translate: "-50% -90%", - opacity: "0", - }, { - translate: "-50% calc(-100% - 0.5rem)", - opacity: "1", - }, 250); - }); - - element.addEventListener("pointerleave", () => { - animate(tooltip, undefined, { - translate: "-50% -90%", - opacity: "0", - }, 250); - setTimeout(() => { - tooltip.style.left = "0px"; - tooltip.style.top = "0px"; - }, 250); - }); + const tooltip = document.createElement("p"); + tooltip.textContent = text; + tooltip.style.opacity = "0"; + tooltip.style.position = "absolute"; + tooltip.style.left = "0px"; + tooltip.style.top = "0px"; + tooltip.classList.add("tooltip"); + + const parent = element.closest("dialog") || document.body; + parent.append(tooltip); + + element.addEventListener("pointerenter", () => { + tooltip.style.left = element.offsetLeft + element.offsetWidth / 2 + "px"; + tooltip.style.top = element.offsetTop + "px"; + animate( + tooltip, + { + translate: "-50% -90%", + opacity: "0", + }, + { + translate: "-50% calc(-100% - 0.5rem)", + opacity: "1", + }, + 250, + ); + }); + + element.addEventListener("pointerleave", () => { + animate( + tooltip, + undefined, + { + translate: "-50% -90%", + opacity: "0", + }, + 250, + ); + setTimeout(() => { + tooltip.style.left = "0px"; + tooltip.style.top = "0px"; + }, 250); + }); } // From kennyhui.dev export function animate(element, from, to, duration, assign = true) { - const animation = element.animate([ - from && from, - to && to, - ], { - duration, - easing: "cubic-bezier(0.65, 0, 0.35, 1)", - fill: "forwards", - }); - setTimeout(() => { - animation.cancel(); - assign && Object.assign(element.style, to); - }, duration); + const animation = element.animate([from && from, to && to], { + duration, + easing: "cubic-bezier(0.65, 0, 0.35, 1)", + fill: "forwards", + }); + setTimeout(() => { + animation.cancel(); + assign && Object.assign(element.style, to); + }, duration); } export class Element { - constructor(tag, text, events, className, attributes) { - this.tag = tag; - this.text = text; - this.events = events; - this.className = className; - this.attributes = attributes; - } - - get element() { - const element = document.createElement(this.tag); - element.innerHTML = this.text; - this.className && (element.className = this.className); - this.events && Object.keys(this.events).forEach(type => { - const listener = this.events[type]; - element.addEventListener(type, listener); - }); - this.attributes && Object.keys(this.attributes).forEach(attribute => { - const value = this.attributes[attribute]; - element.setAttribute(attribute, value); - }); - return element; - } + constructor(tag, text, events, className, attributes) { + this.tag = tag; + this.text = text; + this.events = events; + this.className = className; + this.attributes = attributes; + } + + get element() { + const element = document.createElement(this.tag); + element.innerHTML = this.text; + this.className && (element.className = this.className); + this.events && + Object.keys(this.events).forEach((type) => { + const listener = this.events[type]; + element.addEventListener(type, listener); + }); + this.attributes && + Object.keys(this.attributes).forEach((attribute) => { + const value = this.attributes[attribute]; + element.setAttribute(attribute, value); + }); + return element; + } } // Click outside modal (() => { - document.addEventListener("pointerdown", e => { - const dialog = document.querySelector("dialog[open]"); - if (dialog?.hasAttribute("data-open") && !dialog?.contains(e.target)) { - document.addEventListener("pointerup", () => { - const event = new Event("triggerclose"); - dialog.dispatchEvent(event); - }, { once: true }); - } - }); + document.addEventListener("pointerdown", (e) => { + const dialog = document.querySelector("dialog[open]"); + if (dialog?.hasAttribute("data-open") && !dialog?.contains(e.target)) { + document.addEventListener( + "pointerup", + () => { + const event = new Event("triggerclose"); + dialog.dispatchEvent(event); + }, + { once: true }, + ); + } + }); })(); -document.querySelectorAll("[data-modal-view]").forEach(element => { - element.addEventListener("click", () => { - const path = element.getAttribute("data-modal-view"); - view(path); - }); +document.querySelectorAll("[data-modal-view]").forEach((element) => { + element.addEventListener("click", () => { + const path = element.getAttribute("data-modal-view"); + view(path); + }); }); -document.querySelectorAll("[data-color-input]").forEach(element => { - const name = element.getAttribute("data-color-input"); - // Create child elements - const colorPicker = document.createElement("input"); - colorPicker.type = "color"; - colorPicker.name = name; - colorPicker.tabIndex = "-1"; - const colorPreview = document.createElement("div"); - colorPreview.setAttribute("data-color-preview", ""); - colorPreview.tabIndex = "0"; - colorPreview.role = "button"; - const colorCode = document.createElement("input"); - colorCode.type = "text"; - - colorPicker.addEventListener("input", update); - colorCode.addEventListener("blur", validate); - colorCode.addEventListener("keydown", e => { - if (e.key == "Enter") { - validate(); - } - }); - colorPreview.addEventListener("click", () => { - colorPicker.focus(); - }); - colorPreview.addEventListener("keydown", e => { - if (e.key == " " || e.key == "Enter") { - e.preventDefault(); - colorPicker.focus(); - } - }); - colorPicker.addEventListener("update", update); +document.querySelectorAll("[data-color-input]").forEach((element) => { + const name = element.getAttribute("data-color-input"); + // Create child elements + const colorPicker = document.createElement("input"); + colorPicker.type = "color"; + colorPicker.name = name; + colorPicker.tabIndex = "-1"; + const colorPreview = document.createElement("div"); + colorPreview.setAttribute("data-color-preview", ""); + colorPreview.tabIndex = "0"; + colorPreview.role = "button"; + const colorCode = document.createElement("input"); + colorCode.type = "text"; + + colorPicker.addEventListener("input", update); + colorCode.addEventListener("blur", validate); + colorCode.addEventListener("keydown", (e) => { + if (e.key == "Enter") { + validate(); + } + }); + colorPreview.addEventListener("click", () => { + colorPicker.focus(); + }); + colorPreview.addEventListener("keydown", (e) => { + if (e.key == " " || e.key == "Enter") { + e.preventDefault(); + colorPicker.focus(); + } + }); + colorPicker.addEventListener("update", update); - element.append(colorPicker, colorPreview, colorCode); - update(); + element.append(colorPicker, colorPreview, colorCode); + update(); - function validate() { - const value = colorCode.value; - const valid = /^#[0-9a-fA-F]{6}$/.test(value); - if (valid) { - colorPicker.value = value; - } else { - colorCode.value = colorPicker.value; - } - update(); + function validate() { + const value = colorCode.value; + const valid = /^#[0-9a-fA-F]{6}$/.test(value); + if (valid) { + colorPicker.value = value; + } else { + colorCode.value = colorPicker.value; } + update(); + } - function update() { - colorCode.value = colorPicker.value; - colorPreview.style.backgroundColor = colorPicker.value; - } + function update() { + colorCode.value = colorPicker.value; + colorPreview.style.backgroundColor = colorPicker.value; + } }); -document.querySelectorAll("[data-button-select]").forEach(element => { - element.querySelectorAll("button").forEach(button => { - if (!button.hasAttribute("aria-selected")) { - button.setAttribute("aria-selected", false); - } - button.addEventListener("click", () => { - // Deselect all selected elements - element.querySelectorAll(`button[aria-selected="true"]`).forEach(el => { - el.setAttribute("aria-selected", false); - }); - // Select target element - button.setAttribute("aria-selected", true); - // Dispatch event - const value = button.getAttribute("data-value"); - const event = new CustomEvent("input", { detail: value }); - element.dispatchEvent(event); - }); +document.querySelectorAll("[data-button-select]").forEach((element) => { + element.querySelectorAll("button").forEach((button) => { + if (!button.hasAttribute("aria-selected")) { + button.setAttribute("aria-selected", false); + } + button.addEventListener("click", () => { + // Deselect all selected elements + element.querySelectorAll(`button[aria-selected="true"]`).forEach((el) => { + el.setAttribute("aria-selected", false); + }); + // Select target element + button.setAttribute("aria-selected", true); + // Dispatch event + const value = button.getAttribute("data-value"); + const event = new CustomEvent("input", { detail: value }); + element.dispatchEvent(event); }); + }); }); export function getButtonSelectValue(element) { - if (element.hasAttribute("data-button-select")) { - return element.querySelector(`button[aria-selected="true"]`).getAttribute("data-value"); - } + if (element.hasAttribute("data-button-select")) { + return element.querySelector(`button[aria-selected="true"]`).getAttribute("data-value"); + } } export function setButtonSelectValue(element, value) { - if (element.hasAttribute("data-button-select")) { - // Deselect all selected elements - element.querySelectorAll(`button[aria-selected="true"]`).forEach(el => { - el.setAttribute("aria-selected", false); - }); - // Select target element - element.querySelector(`button[data-value="${value}"]`).setAttribute("aria-selected", true); - } -} \ No newline at end of file + if (element.hasAttribute("data-button-select")) { + // Deselect all selected elements + element.querySelectorAll(`button[aria-selected="true"]`).forEach((el) => { + el.setAttribute("aria-selected", false); + }); + // Select target element + element.querySelector(`button[data-value="${value}"]`).setAttribute("aria-selected", true); + } +} diff --git a/src/periods/periods.js b/src/periods/periods.js index 0ddfc2f..eac4d1d 100644 --- a/src/periods/periods.js +++ b/src/periods/periods.js @@ -1,25 +1,22 @@ import schedule from "./schedule.json"; -const timestamps = schedule.map(period => { - return [ - timeToMs(period[0]), - timeToMs(period[1]), - ]; +const timestamps = schedule.map((period) => { + return [timeToMs(period[0]), timeToMs(period[1])]; }); // Returns zero-indexed period export function getPeriod(date) { - date = date || Date.now(); - const period = timestamps.findIndex(periods => { - return date >= periods[0] && date < periods[1]; - }); - return period; + date = date || Date.now(); + const period = timestamps.findIndex((periods) => { + return date >= periods[0] && date < periods[1]; + }); + return period; } // Converts hh:mm to milliseconds export function timeToMs(time) { - const now = new Date(); - const hours = time.split(":")[0]; - const minutes = time.split(":")[1]; - return new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes).getTime(); -} \ No newline at end of file + const now = new Date(); + const hours = time.split(":")[0]; + const minutes = time.split(":")[1]; + return new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes).getTime(); +} diff --git a/src/periods/schedule.json b/src/periods/schedule.json index faf4bbd..42ffa1c 100644 --- a/src/periods/schedule.json +++ b/src/periods/schedule.json @@ -1,38 +1,11 @@ [ - [ - "07:50", - "08:38" - ], - [ - "08:42", - "09:23" - ], - [ - "09:27", - "10:09" - ], - [ - "10:13", - "10:55" - ], - [ - "10:59", - "11:41" - ], - [ - "11:45", - "12:27" - ], - [ - "12:31", - "13:13" - ], - [ - "13:17", - "13:58" - ], - [ - "14:02", - "14:43" - ] -] \ No newline at end of file + ["07:50", "08:38"], + ["08:42", "09:23"], + ["09:27", "10:09"], + ["10:13", "10:55"], + ["10:59", "11:41"], + ["11:45", "12:27"], + ["12:31", "13:13"], + ["13:17", "13:58"], + ["14:02", "14:43"] +] diff --git a/src/reset.css b/src/reset.css index fb55459..634aefc 100644 --- a/src/reset.css +++ b/src/reset.css @@ -1,12 +1,12 @@ *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; } html { - height: 100%; - -webkit-text-size-adjust: 100%; + height: 100%; + -webkit-text-size-adjust: 100%; } body, @@ -25,7 +25,7 @@ figure, blockquote, dl, dd { - margin: 0; + margin: 0; } img, @@ -33,13 +33,13 @@ picture, video, canvas, svg { - display: block; - max-width: 100%; + display: block; + max-width: 100%; } input, textarea, select, button { - font: inherit; -} \ No newline at end of file + font: inherit; +} diff --git a/src/symbols/symbols.js b/src/symbols/symbols.js index 93efd2a..bb1595a 100644 --- a/src/symbols/symbols.js +++ b/src/symbols/symbols.js @@ -2,130 +2,129 @@ import * as ui from "/src/modules/ui.js"; import symbols from "./symbols.json"; class Autocomplete { - #start = 0; - #end = 0; - #query = ""; + #start = 0; + #end = 0; + #query = ""; - constructor(input, suggestion) { - this.input = input; - this.suggestion = suggestion; + constructor(input, suggestion) { + this.input = input; + this.suggestion = suggestion; - this.input.addEventListener("keydown", e => { - if (e.key == "Tab" && this.matches.length != 0) { - e.preventDefault(); - e.target.setRangeText(symbols[this.matches[0]], this.#start, this.#end, "end"); - this.#start = e.target.selectionEnd; - this.#query = e.target.value.substring(this.#start, this.#end + 1); - this.#updateSuggestion(); - } - if (e.key == "Escape") { - this.#start = e.target.selectionEnd; - this.#query = e.target.value.substring(this.#start, this.#end + 1); - this.#updateSuggestion(); - } - if (e.key == " ") { - this.#start = e.target.selectionEnd; - this.#start++; - this.#end++; - } - if (this.#start > this.#end) { - this.#start = this.#end; - } - }); + this.input.addEventListener("keydown", (e) => { + if (e.key == "Tab" && this.matches.length != 0) { + e.preventDefault(); + e.target.setRangeText(symbols[this.matches[0]], this.#start, this.#end, "end"); + this.#start = e.target.selectionEnd; + this.#query = e.target.value.substring(this.#start, this.#end + 1); + this.#updateSuggestion(); + } + if (e.key == "Escape") { + this.#start = e.target.selectionEnd; + this.#query = e.target.value.substring(this.#start, this.#end + 1); + this.#updateSuggestion(); + } + if (e.key == " ") { + this.#start = e.target.selectionEnd; + this.#start++; + this.#end++; + } + if (this.#start > this.#end) { + this.#start = this.#end; + } + }); - this.input.addEventListener("input", () => { - this.update(); - }); - } + this.input.addEventListener("input", () => { + this.update(); + }); + } - update() { - this.#end = this.input.selectionEnd; - this.#query = this.input.value.substring(this.#start, this.#end + 1); - if (this.matches.length == 0) { - this.#start = this.#end; - } - this.#updateSuggestion(); - // console.log(this.#start, this.#end, this.#query, this.input.selectionEnd, this.matches); + update() { + this.#end = this.input.selectionEnd; + this.#query = this.input.value.substring(this.#start, this.#end + 1); + if (this.matches.length == 0) { + this.#start = this.#end; } + this.#updateSuggestion(); + // console.log(this.#start, this.#end, this.#query, this.input.selectionEnd, this.matches); + } - get matches() { - if (this.#query?.trim()) { - if (this.#query in symbols) { - return [this.#query]; - } - else { - return Object.keys(symbols).filter(string => string.startsWith(this.#query)); - } - } - else { - return []; - } + get matches() { + if (this.#query?.trim()) { + if (this.#query in symbols) { + return [this.#query]; + } else { + return Object.keys(symbols).filter((string) => string.startsWith(this.#query)); + } + } else { + return []; } + } - #updateSuggestion() { - if (this.matches.length != 0) { - this.suggestion.innerHTML = `Tab to insert ${symbols[this.matches[0]]} Esc to cancel`; - } - else if (this.#query?.trim()) { - this.suggestion.innerHTML = ""; - } - else { - this.suggestion.innerHTML = ""; - } + #updateSuggestion() { + if (this.matches.length != 0) { + this.suggestion.innerHTML = `Tab to insert ${symbols[this.matches[0]]} Esc to cancel`; + } else if (this.#query?.trim()) { + this.suggestion.innerHTML = ""; + } else { + this.suggestion.innerHTML = ""; } + } } const uniqueSymbols = [...new Set(Object.values(symbols))]; const answerInput = document.getElementById("answer-input"); -export const autocomplete = new Autocomplete(answerInput, document.getElementById("answer-suggestion")); +export const autocomplete = new Autocomplete( + answerInput, + document.getElementById("answer-suggestion"), +); // Insert symbol by index -document.querySelectorAll("[data-insert-symbol]").forEach(button => { - const index = button.getAttribute("data-insert-symbol"); - const symbol = Object.values(symbols)[index]; - button.innerHTML = symbol; - button.addEventListener("click", () => { - insert(symbol); - }); +document.querySelectorAll("[data-insert-symbol]").forEach((button) => { + const index = button.getAttribute("data-insert-symbol"); + const symbol = Object.values(symbols)[index]; + button.innerHTML = symbol; + button.addEventListener("click", () => { + insert(symbol); + }); }); // Loop through unique symbols and append them to DOM -uniqueSymbols.forEach(symbol => { - const button = new ui.Element("button", symbol, { - click: () => { - // Close the modal - ui.view(""); - // Insert symbol - insert(symbol); - }, - }).element; - // Show symbol name - const keys = []; - Object.entries(symbols).forEach(([key, value]) => { - if (value == symbol) { - keys.push(key); - } - }); - button.title = keys.join(", "); - document.querySelector("#symbols-grid").append(button); - ui.addTooltip(button, keys.join(", ")); +uniqueSymbols.forEach((symbol) => { + const button = new ui.Element("button", symbol, { + click: () => { + // Close the modal + ui.view(""); + // Insert symbol + insert(symbol); + }, + }).element; + // Show symbol name + const keys = []; + Object.entries(symbols).forEach(([key, value]) => { + if (value == symbol) { + keys.push(key); + } + }); + button.title = keys.join(", "); + document.querySelector("#symbols-grid").append(button); + ui.addTooltip(button, keys.join(", ")); }); // Fill missing space -const emptySpaces = 6 - uniqueSymbols.length % 6; +const emptySpaces = 6 - (uniqueSymbols.length % 6); for (let i = 0; i < emptySpaces; i++) { - document.querySelector("#symbols-grid").append(document.createElement("div")); + document.querySelector("#symbols-grid").append(document.createElement("div")); } // Insert symbol at cursor position function insert(symbol) { - answerInput.setRangeText(symbol, answerInput.selectionStart, answerInput.selectionEnd, "end"); - answerInput.focus(); - autocomplete.update(); + answerInput.setRangeText(symbol, answerInput.selectionStart, answerInput.selectionEnd, "end"); + answerInput.focus(); + autocomplete.update(); } // Insert symbol from index export function insertFromIndex(index) { - insert(uniqueSymbols[index]); -} \ No newline at end of file + insert(uniqueSymbols[index]); +} diff --git a/src/symbols/symbols.json b/src/symbols/symbols.json index e56391f..43c7358 100644 --- a/src/symbols/symbols.json +++ b/src/symbols/symbols.json @@ -1,34 +1,34 @@ { - "pm": "±", - "sqrt": "√", - "inf": "∞", - "pi": "π", - "sum": "Σ", - "theta": "θ", - "cbrt": "∛", - "fourthrt": "∜", - "int": "∫", - "rightarrow": "→", - "or": "∨", - "and": "∧", - "subset": "⊂", - "subseteq": "⊆", - "neq": "≠", - "in": "∈", - "all": "∀", - "some": "∃", - "del": "∇", - "delta": "∆", - "angle": "∢", - "parallel": "∥", - "perpendicular": "⟂", - "intersect": "∩", - "union": "∪", - "therefore": "∴", - "composed": "∘", - "degrees": "°", - "infinity": "∞", - "radical": "√", - "root": "√", - "approx": "≈" -} \ No newline at end of file + "pm": "±", + "sqrt": "√", + "inf": "∞", + "pi": "π", + "sum": "Σ", + "theta": "θ", + "cbrt": "∛", + "fourthrt": "∜", + "int": "∫", + "rightarrow": "→", + "or": "∨", + "and": "∧", + "subset": "⊂", + "subseteq": "⊆", + "neq": "≠", + "in": "∈", + "all": "∀", + "some": "∃", + "del": "∇", + "delta": "∆", + "angle": "∢", + "parallel": "∥", + "perpendicular": "⟂", + "intersect": "∩", + "union": "∪", + "therefore": "∴", + "composed": "∘", + "degrees": "°", + "infinity": "∞", + "radical": "√", + "root": "√", + "approx": "≈" +} diff --git a/src/themes/butterfly/background.png b/src/themes/butterfly/background.png new file mode 100644 index 0000000..f76d568 Binary files /dev/null and b/src/themes/butterfly/background.png differ diff --git a/src/themes/butterfly/butterfly.css b/src/themes/butterfly/butterfly.css new file mode 100644 index 0000000..51d21dc --- /dev/null +++ b/src/themes/butterfly/butterfly.css @@ -0,0 +1,94 @@ +@font-face { + font-family: "ultinoid"; + src: url("./ultinoid.woff"); +} + +@font-face { + font-family: "typerwriter"; + src: url("./typerwriter.woff2"); +} + +[data-theme="butterfly"] { + color-scheme: light; + --text-color: #000000; + --background-color: #9d87da; + --surface-color: #ffffff; + --accent-color: #000000; + --accent-text-color: #ffffff; + --error-color: #cb2b25; +} + +#virtual-clicker-butterfly { + display: none; +} + +body[data-theme="butterfly"] { + background-image: url("./background.png"); + background-position: center; + background-size: cover; + font-family: "typerwriter"; + font-weight: normal; +} + +body[data-theme="butterfly"] #virtual-clicker-butterfly { + display: unset; +} + +body[data-theme="butterfly"] #header > .virtual-clicker-logo { + display: none; +} + +body[data-theme="butterfly"] :is(#header, #clicker) { + background-color: transparent; +} + +body[data-theme="butterfly"] #answer-mode-selector { + background-color: var(--surface-color); +} + +body[data-theme="butterfly"] :is(#header, #clicker > h1, #clicker > label) { + color: #ffffff; +} + +body[data-theme="butterfly"] #header h1 { + font-family: "ultinoid"; + font-size: 2.5rem; + font-weight: normal; +} + +body[data-theme="butterfly"] #clicker > h1 { + animation: glow 1.5s linear infinite alternate; +} + +body[data-theme="butterfly"] dialog { + color: #ffffff; +} + +body[data-theme="butterfly"] :is(button, kbd, code, input, select, textarea) { + color: #000000; + font-family: inherit; +} + +body[data-theme="butterfly"] #code-input { + color: unset; +} + +body[data-theme="butterfly"] button:hover { + color: #ffffff; +} + +img.star { + pointer-events: none; + position: fixed; + background-color: transparent; + top: -30px; +} + +@keyframes glow { + from { + text-shadow: none; + } + from { + text-shadow: 0 0 5px white; + } +} diff --git a/src/themes/butterfly/butterfly.js b/src/themes/butterfly/butterfly.js new file mode 100644 index 0000000..a060d9b --- /dev/null +++ b/src/themes/butterfly/butterfly.js @@ -0,0 +1,40 @@ +import "./butterfly.css"; +import star0 from "./stars/star0.png"; +import star1 from "./stars/star1.png"; +import star2 from "./stars/star2.png"; +import star3 from "./stars/star3.png"; +import star4 from "./stars/star4.png"; + +const stars = [star0, star1, star2, star3, star4]; + +setInterval(() => { + if (document.body.getAttribute("data-theme") !== "butterfly") return; + const w = [22, 25, 30][Math.floor(Math.random() * 3)]; + const x = Math.random() * (window.innerWidth - w); + const starIndex = Math.floor(Math.random() * stars.length); + const duration = 10000 * (window.innerHeight / 1000); + + const star = document.createElement("img"); + star.className = "star"; + star.src = stars[starIndex]; + star.style.width = `${w}px`; + star.style.left = `${x}px`; + document.body.append(star); + star.animate( + [ + { + transform: "translateY(0)", + }, + { + transform: `translateY(${window.innerHeight + 30}px)`, + }, + ], + { + duration: duration, + easing: "linear", + }, + ); + setTimeout(() => { + star.remove(); + }, duration); +}, 700); diff --git a/src/themes/butterfly/stars/star0.png b/src/themes/butterfly/stars/star0.png new file mode 100644 index 0000000..6d4719c Binary files /dev/null and b/src/themes/butterfly/stars/star0.png differ diff --git a/src/themes/butterfly/stars/star1.png b/src/themes/butterfly/stars/star1.png new file mode 100644 index 0000000..184bf44 Binary files /dev/null and b/src/themes/butterfly/stars/star1.png differ diff --git a/src/themes/butterfly/stars/star2.png b/src/themes/butterfly/stars/star2.png new file mode 100644 index 0000000..0a7aab9 Binary files /dev/null and b/src/themes/butterfly/stars/star2.png differ diff --git a/src/themes/butterfly/stars/star3.png b/src/themes/butterfly/stars/star3.png new file mode 100644 index 0000000..eb59f7f Binary files /dev/null and b/src/themes/butterfly/stars/star3.png differ diff --git a/src/themes/butterfly/stars/star4.png b/src/themes/butterfly/stars/star4.png new file mode 100644 index 0000000..dc763cc Binary files /dev/null and b/src/themes/butterfly/stars/star4.png differ diff --git a/src/themes/butterfly/typerwriter.woff2 b/src/themes/butterfly/typerwriter.woff2 new file mode 100644 index 0000000..7786e29 Binary files /dev/null and b/src/themes/butterfly/typerwriter.woff2 differ diff --git a/src/themes/butterfly/ultinoid.woff b/src/themes/butterfly/ultinoid.woff new file mode 100644 index 0000000..55725d0 Binary files /dev/null and b/src/themes/butterfly/ultinoid.woff differ diff --git a/src/themes/festive/festive.css b/src/themes/festive/festive.css new file mode 100644 index 0000000..53677d3 --- /dev/null +++ b/src/themes/festive/festive.css @@ -0,0 +1,76 @@ +[data-theme="festive-2023"] { + color-scheme: light; + --text-color: #242424; + --background-color: #f1e2cc; + --surface-color: #dbc8ab; + --accent-color: #f0b73c; + --accent-text-color: #242424; + --error-color: #e451da; + + --red: #f75e5e; + --green: #42b970; +} + +[data-theme="festive-2023"] button:nth-child(odd):not(button[data-theme]) { + --surface-color: var(--green); +} + +[data-theme="festive-2023"] button:nth-child(even):not(button[data-theme]) { + --surface-color: var(--red); +} + +[data-theme="festive-2023"] + :is( + button, + input, + textarea, + select, + dialog, + [data-answer-mode="choice"], + #answer-textarea-container, + math-field + ) { + border: 0.125rem solid #242424; +} + +[data-theme="festive-2023"] #submit-button { + height: 2.25rem; + background: linear-gradient(-45deg, white 25%, var(--red) 25% 50%, white 50% 75%, var(--red) 75%); + background-size: 2rem 2rem; + font: + bold 16px "Segoe Script", + "Figtree", + sans-serif; +} + +[data-theme="festive-2023"] #submit-button:is(:hover, :focus) { + color: var(--text-color); + animation: scroll 500ms linear infinite forwards; +} + +[data-theme="festive-2023"] #answer-input { + border: none; +} + +[data-theme="festive-2023"] #answer-textarea-container:has(#answer-input:focus) { + outline: none; + box-shadow: 0 0 0 0.125rem var(--accent-color) inset; + transition: box-shadow 100ms ease; +} + +@keyframes scroll { + from { + background-position: 0 0; + } + + to { + background-position: 2rem 0; + } +} + +img.snowflake { + position: absolute; + background-color: transparent; + width: 50px; + pointer-events: none; +} diff --git a/src/festive/festive.js b/src/themes/festive/festive.js similarity index 100% rename from src/festive/festive.js rename to src/themes/festive/festive.js diff --git a/src/festive/snowflake.png b/src/themes/festive/snowflake.png similarity index 100% rename from src/festive/snowflake.png rename to src/themes/festive/snowflake.png diff --git a/src/themes/themes.css b/src/themes/themes.css index 977c693..32ada5e 100644 --- a/src/themes/themes.css +++ b/src/themes/themes.css @@ -1,229 +1,229 @@ [data-theme="classic"] { - color-scheme: dark; - --text-color: #ffffff; - --background-color: #303946; - --surface-color: #424f61; - --accent-color: #059fff; - --accent-text-color: #ffffff; - --error-color: #f84747; + color-scheme: dark; + --text-color: #ffffff; + --background-color: #303946; + --surface-color: #424f61; + --accent-color: #059fff; + --accent-text-color: #ffffff; + --error-color: #f84747; } [data-theme="abyss"] { - color-scheme: dark; - --text-color: #ffffff; - --background-color: #000000; - --surface-color: #1f1f22; - --accent-color: #059fff; - --accent-text-color: #ffffff; - --error-color: #f84747; + color-scheme: dark; + --text-color: #ffffff; + --background-color: #000000; + --surface-color: #1f1f22; + --accent-color: #059fff; + --accent-text-color: #ffffff; + --error-color: #f84747; } [data-theme="graphite"] { - color-scheme: dark; - --text-color: #ffffff; - --background-color: #1f1f1f; - --surface-color: #2d2f30; - --accent-color: #444d58; - --accent-text-color: #ffffff; - --error-color: #f84747; + color-scheme: dark; + --text-color: #ffffff; + --background-color: #1f1f1f; + --surface-color: #2d2f30; + --accent-color: #444d58; + --accent-text-color: #ffffff; + --error-color: #f84747; } [data-theme="blizzard"] { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #cde7f5; - --surface-color: #abd0e6; - --accent-color: #71b2d3; - --accent-text-color: #2c2c2c; - --error-color: #ce7474; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #cde7f5; + --surface-color: #abd0e6; + --accent-color: #71b2d3; + --accent-text-color: #2c2c2c; + --error-color: #ce7474; } [data-theme="sage"] { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #bacbb0; - --surface-color: #a0b395; - --accent-color: #5c8a5b; - --accent-text-color: #2c2c2c; - --error-color: #b85f5f; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #bacbb0; + --surface-color: #a0b395; + --accent-color: #5c8a5b; + --accent-text-color: #2c2c2c; + --error-color: #b85f5f; } [data-theme="dune"] { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #cbc3af; - --surface-color: #b7ac93; - --accent-color: #917856; - --accent-text-color: #2c2c2c; - --error-color: #bd5858; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #cbc3af; + --surface-color: #b7ac93; + --accent-color: #917856; + --accent-text-color: #2c2c2c; + --error-color: #bd5858; } [data-theme="rose"] { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #F4DBD8; - --surface-color: #e0c4c0; - --accent-color: #ae817c; - --accent-text-color: #2c2c2c; - --error-color: #b42222; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #f4dbd8; + --surface-color: #e0c4c0; + --accent-color: #ae817c; + --accent-text-color: #2c2c2c; + --error-color: #b42222; } [data-theme="lavender"] { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #c3c0e5; - --surface-color: #aca8d4; - --accent-color: #7e78be; - --accent-text-color: #2c2c2c; - --error-color: #b85f5f; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #c3c0e5; + --surface-color: #aca8d4; + --accent-color: #7e78be; + --accent-text-color: #2c2c2c; + --error-color: #b85f5f; } [data-theme="cream"] { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #f1e8db; - --surface-color: #ddd3c6; - --accent-color: #505f45; - --accent-text-color: #ffffff; - --error-color: #b85f5f; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #f1e8db; + --surface-color: #ddd3c6; + --accent-color: #505f45; + --accent-text-color: #ffffff; + --error-color: #b85f5f; } [data-theme="ravenclaw"] { - color-scheme: dark; - --text-color: #ffffff; - --background-color: #202944; - --surface-color: #29365e; - --accent-color: #f0b569; - --accent-text-color: #2c2c2c; - --error-color: #b85f5f; + color-scheme: dark; + --text-color: #ffffff; + --background-color: #202944; + --surface-color: #29365e; + --accent-color: #f0b569; + --accent-text-color: #2c2c2c; + --error-color: #b85f5f; } [data-theme="hufflepuff"] { - color-scheme: light; - --text-color: #2c2c2c; - --background-color: #dbba71; - --surface-color: #c7a65e; - --accent-color: #2c2c2c; - --accent-text-color: #ffffff; - --error-color: #923737; + color-scheme: light; + --text-color: #2c2c2c; + --background-color: #dbba71; + --surface-color: #c7a65e; + --accent-color: #2c2c2c; + --accent-text-color: #ffffff; + --error-color: #923737; } [data-theme="slytherin"] { - color-scheme: dark; - --text-color: #ffffff; - --background-color: #1b3530; - --surface-color: #224941; - --accent-color: #cae7e1; - --accent-text-color: #2c2c2c; - --error-color: #b85f5f; + color-scheme: dark; + --text-color: #ffffff; + --background-color: #1b3530; + --surface-color: #224941; + --accent-color: #cae7e1; + --accent-text-color: #2c2c2c; + --error-color: #b85f5f; } [data-theme="gryffindor"] { - color-scheme: light; - --text-color: #ffffff; - --background-color: #6b242b; - --surface-color: #852b34; - --accent-color: #f3b84b; - --accent-text-color: #2c2c2c; - --error-color: #f76161; + color-scheme: light; + --text-color: #ffffff; + --background-color: #6b242b; + --surface-color: #852b34; + --accent-color: #f3b84b; + --accent-text-color: #2c2c2c; + --error-color: #f76161; } [data-theme="catppuccin-latte"] { - color-scheme: light; - /* Text */ - --text-color: #4c4f69; - /* Base */ - --background-color: #eff1f5; - /* Surface0 */ - --surface-color: #ccd0da; - /* Rosewater */ - --accent-color: #dc8a78; - /* Text */ - --accent-text-color: #4c4f69; - /* Red */ - --error-color: #d20f39; + color-scheme: light; + /* Text */ + --text-color: #4c4f69; + /* Base */ + --background-color: #eff1f5; + /* Surface0 */ + --surface-color: #ccd0da; + /* Rosewater */ + --accent-color: #dc8a78; + /* Text */ + --accent-text-color: #4c4f69; + /* Red */ + --error-color: #d20f39; } [data-theme="catppuccin-frappe"] { - color-scheme: dark; - /* Text */ - --text-color: #c6d0f5; - /* Base */ - --background-color: #303446; - /* Surface0 */ - --surface-color: #414559; - /* Overlay0 */ - --accent-color: #737994; - /* Text */ - --accent-text-color: #c6d0f5; - /* Red */ - --error-color: #e78284; + color-scheme: dark; + /* Text */ + --text-color: #c6d0f5; + /* Base */ + --background-color: #303446; + /* Surface0 */ + --surface-color: #414559; + /* Overlay0 */ + --accent-color: #737994; + /* Text */ + --accent-text-color: #c6d0f5; + /* Red */ + --error-color: #e78284; } [data-theme="catppuccin-macchiato"] { - color-scheme: dark; - /* Text */ - --text-color: #cad3f5; - /* Base */ - --background-color: #24273a; - /* Surface0 */ - --surface-color: #363a4f; - /* Overlay0 */ - --accent-color: #6e738d; - /* Text */ - --accent-text-color: #cad3f5; - /* Red */ - --error-color: #ed8796; + color-scheme: dark; + /* Text */ + --text-color: #cad3f5; + /* Base */ + --background-color: #24273a; + /* Surface0 */ + --surface-color: #363a4f; + /* Overlay0 */ + --accent-color: #6e738d; + /* Text */ + --accent-text-color: #cad3f5; + /* Red */ + --error-color: #ed8796; } [data-theme="catppuccin-mocha"] { - color-scheme: dark; - /* Text */ - --text-color: #cdd6f4; - /* Base */ - --background-color: #1e1e2e; - /* Surface0 */ - --surface-color: #313244; - /* Overlay0 */ - --accent-color: #6c7086; - /* Text */ - --accent-text-color: #cdd6f4; - /* Red */ - --error-color: #f38ba8; + color-scheme: dark; + /* Text */ + --text-color: #cdd6f4; + /* Base */ + --background-color: #1e1e2e; + /* Surface0 */ + --surface-color: #313244; + /* Overlay0 */ + --accent-color: #6c7086; + /* Text */ + --accent-text-color: #cdd6f4; + /* Red */ + --error-color: #f38ba8; } /* Seasonal */ [data-theme="halloween-2023"] { - color-scheme: dark; - --text-color: #ffffff; - --background-color: #2c173d; - --surface-color: #cc5a2a; - --accent-color: #d6b7e7; - --accent-text-color: #2c2c2c; - --error-color: #d7f066; + color-scheme: dark; + --text-color: #ffffff; + --background-color: #2c173d; + --surface-color: #cc5a2a; + --accent-color: #d6b7e7; + --accent-text-color: #2c2c2c; + --error-color: #d7f066; } /* Special */ [data-theme="dune-2"] { - color-scheme: dark; - --text-color: #d8d6d3; - --background-color: #242220; - --surface-color: #2e2c2a; - --accent-color: #1455b6; - --accent-text-color: #cbd9ff; - --error-color: #8b2626; + color-scheme: dark; + --text-color: #d8d6d3; + --background-color: #242220; + --surface-color: #2e2c2a; + --accent-color: #1455b6; + --accent-text-color: #cbd9ff; + --error-color: #8b2626; } #atreides-logo { - display: none; + display: none; } body[data-theme="dune-2"] #atreides-logo { - display: unset; + display: unset; } -body[data-theme="dune-2"] #header>.virtual-clicker-logo { - display: none; -} \ No newline at end of file +body[data-theme="dune-2"] #header > .virtual-clicker-logo { + display: none; +} diff --git a/src/themes/themes.js b/src/themes/themes.js index 70f43ff..0d1ad4c 100644 --- a/src/themes/themes.js +++ b/src/themes/themes.js @@ -1,79 +1,84 @@ import "./themes.css"; import themes from "./themes.json"; +import "./butterfly/butterfly.js"; +import "./festive/festive.js"; + import * as ui from "/src/modules/ui.js"; import storage from "/src/modules/storage.js"; -themes.forEach(theme => { - const value = theme[0]; - const name = theme[1] || theme[0]; +let selectedTheme = ""; + +themes.forEach((theme) => { + const value = theme[0]; + const name = theme[1] || theme[0]; - const option = document.createElement("option"); - option.value = value; - option.textContent = name; - document.querySelector("#theme-selector").append(option); + const button = document.createElement("button"); + button.textContent = name; + button.addEventListener("click", () => { + selectedTheme = value; + document.getElementById("theme-preview").setAttribute("data-theme", value); + }); + document.getElementById("theme-selector").append(button); }); if (storage.get("theme") == "custom") { - // Custom theme - applyCustomTheme(); - document.getElementById("theme-selector").value = ""; + // Custom theme + applyCustomTheme(); + selectedTheme = ""; } else { - // Built-in theme - document.body.setAttribute("data-theme", storage.get("theme") || ""); - document.getElementById("theme-preview").setAttribute("data-theme", storage.get("theme") || ""); - document.getElementById("theme-selector").value = storage.get("theme") || ""; + // Built-in theme + const theme = storage.get("theme") || ""; + document.body.setAttribute("data-theme", theme); + document.getElementById("theme-preview").setAttribute("data-theme", theme); + selectedTheme = theme; } enableTransitions(); -document.getElementById("theme-selector").addEventListener("input", e => { - const value = e.target.value; - document.getElementById("theme-preview").setAttribute("data-theme", value); -}); - document.getElementById("theme-apply").addEventListener("click", () => { - const value = document.getElementById("theme-selector").value; - disableTransitions(); - document.body.setAttribute("data-theme", value); - removeCustomTheme(); - enableTransitions(); - storage.set("theme", value); - // Update developer theme input + const value = selectedTheme; + disableTransitions(); + document.body.setAttribute("data-theme", value); + removeCustomTheme(); + enableTransitions(); + storage.set("theme", value); + // Update developer theme input + if (document.getElementById("theme-debug")) { document.getElementById("theme-debug").value = value; + } }); document.getElementById("theme-reset").addEventListener("click", resetTheme); export function resetTheme() { - disableTransitions(); - document.body.removeAttribute("data-theme"); - removeCustomTheme(); - document.getElementById("theme-preview").removeAttribute("data-theme"); - document.getElementById("theme-selector").value = ""; - enableTransitions(); - storage.delete("theme"); - storage.delete("custom-theme"); + disableTransitions(); + document.body.removeAttribute("data-theme"); + removeCustomTheme(); + document.getElementById("theme-preview").removeAttribute("data-theme"); + enableTransitions(); + storage.delete("theme"); + storage.delete("custom-theme"); } export function disableTransitions() { - document.body.classList.remove("enable-transitions"); + document.body.classList.remove("enable-transitions"); } export function enableTransitions() { - document.body.offsetHeight; - document.body.classList.add("enable-transitions"); + document.body.offsetHeight; + document.body.classList.add("enable-transitions"); } // Editor const defaultTheme = { - "color-scheme": "light", - "text-color": "#2c2c2c", - "background-color": "#fafafa", - "surface-color": "#e7e7e7", - "accent-color": "#424242", - "accent-text-color": "#ffffff", - "error-color": "#fa8796", + "color-scheme": "light", + "text-color": "#2c2c2c", + "background-color": "#fafafa", + "surface-color": "#e7e7e7", + "accent-color": "#424242", + "accent-text-color": "#ffffff", + "error-color": "#fa8796", }; Object.freeze(defaultTheme); @@ -83,148 +88,153 @@ updateEditorFields(); updateEditorPreview(); updateThemeCode(); -document.querySelectorAll("#theme-editor :is(input, select)").forEach(input => { - input.addEventListener("input", e => { - customTheme[e.target.name] = e.target.value; - updateEditorPreview(); - updateThemeCode(); - }); +document.querySelectorAll("#theme-editor :is(input, select)").forEach((input) => { + input.addEventListener("input", (e) => { + customTheme[e.target.name] = e.target.value; + updateEditorPreview(); + updateThemeCode(); + }); }); document.getElementById("editor-apply").addEventListener("click", () => { - storage.set("custom-theme", customTheme); - storage.set("theme", "custom"); - applyCustomTheme(); + storage.set("custom-theme", customTheme); + storage.set("theme", "custom"); + applyCustomTheme(); }); document.getElementById("editor-reset").addEventListener("click", () => { - Object.assign(customTheme, defaultTheme); - updateEditorFields(); - updateEditorPreview(); - updateThemeCode(); + Object.assign(customTheme, defaultTheme); + updateEditorFields(); + updateEditorPreview(); + updateThemeCode(); }); -document.getElementById("theme-code").addEventListener("input", e => { - if (e.target.value?.trim()) { - const theme = decodeThemeCode(e.target.value); - theme && updateEditorPreview(theme); - } +document.getElementById("theme-code").addEventListener("input", (e) => { + if (e.target.value?.trim()) { + const theme = decodeThemeCode(e.target.value); + theme && updateEditorPreview(theme); + } }); document.getElementById("theme-code").addEventListener("blur", validateThemeCode); -document.getElementById("theme-code").addEventListener("keydown", e => { - if (e.key == "Enter") { - validateThemeCode(); - } +document.getElementById("theme-code").addEventListener("keydown", (e) => { + if (e.key == "Enter") { + validateThemeCode(); + } }); function validateThemeCode() { - const code = document.getElementById("theme-code").value; - const theme = decodeThemeCode(code); - storage.get("developer") && console.log(theme); - if (theme) { - Object.assign(customTheme, theme); - updateEditorFields(); - updateEditorPreview(); - } - updateThemeCode(); + const code = document.getElementById("theme-code").value; + const theme = decodeThemeCode(code); + storage.get("developer") && console.log(theme); + if (theme) { + Object.assign(customTheme, theme); + updateEditorFields(); + updateEditorPreview(); + } + updateThemeCode(); } function updateEditorFields() { - Object.entries(customTheme).forEach(([key, value]) => { - const event = new Event("update"); - const input = document.querySelector(`#theme-editor [name="${key}"]`); - input.value = value; - input.dispatchEvent(event); - }); + Object.entries(customTheme).forEach(([key, value]) => { + const event = new Event("update"); + const input = document.querySelector(`#theme-editor [name="${key}"]`); + input.value = value; + input.dispatchEvent(event); + }); } function updateEditorPreview(theme = customTheme) { - const preview = document.getElementById("editor-preview"); - Object.entries(theme).forEach(([key, value]) => { - const prefix = key == "color-scheme" ? "" : "--"; - preview.style.setProperty(prefix + key, value); - }); + const preview = document.getElementById("editor-preview"); + Object.entries(theme).forEach(([key, value]) => { + const prefix = key == "color-scheme" ? "" : "--"; + preview.style.setProperty(prefix + key, value); + }); } function applyCustomTheme() { - if (!storage.get("custom-theme")) return; - Object.entries(storage.get("custom-theme")).forEach(([key, value]) => { - const prefix = key == "color-scheme" ? "" : "--"; - document.body.style.setProperty(prefix + key, value); - }); - document.getElementById("theme-selector").value = ""; - document.getElementById("theme-preview").removeAttribute("data-theme"); + if (!storage.get("custom-theme")) return; + Object.entries(storage.get("custom-theme")).forEach(([key, value]) => { + const prefix = key == "color-scheme" ? "" : "--"; + document.body.style.setProperty(prefix + key, value); + }); + document.getElementById("theme-preview").removeAttribute("data-theme"); } function removeCustomTheme() { - if (!storage.get("custom-theme")) return; - Object.keys(storage.get("custom-theme")).forEach(key => { - const prefix = key == "color-scheme" ? "" : "--"; - document.body.style.removeProperty(prefix + key); - }); + if (!storage.get("custom-theme")) return; + Object.keys(storage.get("custom-theme")).forEach((key) => { + const prefix = key == "color-scheme" ? "" : "--"; + document.body.style.removeProperty(prefix + key); + }); } function updateThemeCode() { - document.getElementById("theme-code").value = encodeThemeCode(customTheme); + document.getElementById("theme-code").value = encodeThemeCode(customTheme); } function encodeThemeCode(theme) { - return "VC" + btoa(Object.values(theme).join(",")); + return "VC" + btoa(Object.values(theme).join(",")); } function decodeThemeCode(code) { - try { - const keys = Object.keys(defaultTheme); - const values = atob(code.substring(2)).split(","); - if (values.length < keys.length) { - throw new Error(); - } - return Object.fromEntries(keys.map((key, i) => [key, values[i]])); - } catch (e) { - return false; + try { + const keys = Object.keys(defaultTheme); + const values = atob(code.substring(2)).split(","); + if (values.length < keys.length) { + throw new Error(); } + return Object.fromEntries(keys.map((key, i) => [key, values[i]])); + } catch (e) { + return false; + } } // Load theme editor document.querySelector(`[data-modal-page="editor"]`).addEventListener("view", () => { - Object.assign(customTheme, storage.get("custom-theme") || defaultTheme); - updateEditorFields(); - updateEditorPreview(); - updateThemeCode(); + Object.assign(customTheme, storage.get("custom-theme") || defaultTheme); + updateEditorFields(); + updateEditorPreview(); + updateThemeCode(); }); if (storage.get("developer")) { - // Add developer theme input - document.querySelector(`[data-modal-page="theme"]`).append( - new ui.Element("input", null, { - input: e => { - disableTransitions(); - document.getElementById("theme-preview").setAttribute("data-theme", e.target.value); - document.body.setAttribute("data-theme", e.target.value); - removeCustomTheme(); - enableTransitions(); - storage.set("theme", e.target.value); - }, - }, null, { - id: "theme-debug", - }).element - ); - // Populate field - document.getElementById("theme-debug").value = storage.get("theme") || ""; - // Add Copy CSS button - document.querySelector(`[data-modal-page="editor"]`).append( - new ui.Element("button", "Copy CSS", { - "click": copyThemeCSS, - }).element - ); + // Add developer theme input + document.querySelector(`[data-modal-page="theme"]`).append( + new ui.Element( + "input", + null, + { + input: (e) => { + disableTransitions(); + document.getElementById("theme-preview").setAttribute("data-theme", e.target.value); + document.body.setAttribute("data-theme", e.target.value); + removeCustomTheme(); + enableTransitions(); + storage.set("theme", e.target.value); + }, + }, + null, + { + id: "theme-debug", + }, + ).element, + ); + // Populate field + document.getElementById("theme-debug").value = storage.get("theme") || ""; + // Add Copy CSS button + document.querySelector(`[data-modal-page="editor"]`).append( + new ui.Element("button", "Copy CSS", { + "click": copyThemeCSS, + }).element, + ); } function copyThemeCSS() { - const properties = Object.entries(customTheme).map(([key, value]) => { - const prefix = key == "color-scheme" ? "" : "--"; - return `${prefix}${key}: ${value};`; - }); - const css = `[data-theme="custom"] {\n ${properties.join("\n ")}\n}`; - navigator.clipboard.writeText(css); -} \ No newline at end of file + const properties = Object.entries(customTheme).map(([key, value]) => { + const prefix = key == "color-scheme" ? "" : "--"; + return `${prefix}${key}: ${value};`; + }); + const css = `[data-theme="custom"] {\n ${properties.join("\n ")}\n}`; + navigator.clipboard.writeText(css); +} diff --git a/src/themes/themes.json b/src/themes/themes.json index ecbe405..307f9e3 100644 --- a/src/themes/themes.json +++ b/src/themes/themes.json @@ -1,61 +1,21 @@ [ - [ - "classic" - ], - [ - "abyss" - ], - [ - "graphite" - ], - [ - "blizzard" - ], - [ - "sage" - ], - [ - "dune" - ], - [ - "rose" - ], - [ - "lavender" - ], - [ - "cream" - ], - [ - "ravenclaw" - ], - [ - "hufflepuff" - ], - [ - "slytherin" - ], - [ - "gryffindor" - ], - [ - "catppuccin-latte", - "Catppuccin Latte" - ], - [ - "catppuccin-frappe", - "Catppuccin Frappé" - ], - [ - "catppuccin-macchiato", - "Catppuccin Macchiato" - ], - [ - "catppuccin-mocha", - "Catppuccin Mocha" - ], - [ - "dune-2", - "Dune: Part Two" - ] -] \ No newline at end of file + ["classic", "Classic"], + ["abyss", "Abyss"], + ["graphite", "Graphite"], + ["blizzard", "Blizzard"], + ["sage", "Sage"], + ["dune", "Dune"], + ["dune-2", "Dune 2"], + ["rose", "Rose"], + ["lavender", "Lavender"], + ["cream", "Cream"], + ["ravenclaw", "Ravenclaw"], + ["hufflepuff", "Hufflepuff"], + ["slytherin", "Slytherin"], + ["gryffindor", "Gryffindor"], + ["catppuccin-latte", "Catppuccin Latte"], + ["catppuccin-frappe", "Catppuccin Frappé"], + ["catppuccin-macchiato", "Catppuccin Macchiato"], + ["catppuccin-mocha", "Catppuccin Mocha"], + ["butterfly", "GUTS"] +] diff --git a/vite.config.js b/vite.config.js index 9147db2..5fe045d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,11 +1,6 @@ -/* eslint-disable no-undef */ import webfontDownload from "vite-plugin-webfont-dl"; +import version from "vite-plugin-package-version"; export default { - plugins: [ - webfontDownload(), - ], - define: { - __APP_VERSION__: JSON.stringify(process.env.npm_package_version), - } -}; \ No newline at end of file + plugins: [webfontDownload(), version()], +};