1 Commits

Author SHA1 Message Date
John de St Germain
29756834f8 Fix misspelling 2024-05-13 09:20:46 -05:00
62 changed files with 11906 additions and 7575 deletions

View File

@@ -21,22 +21,18 @@ jobs:
- name: ⏬ Install Python dependencies - name: ⏬ Install Python dependencies
run: pip install -r requirements.txt run: pip install -r requirements.txt
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: 🐉 Use Node.js 18.16.x - name: 🐉 Use Node.js 18.16.x
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18.16.x node-version: 18.16.x
cache: "pnpm" cache: "npm"
- name: ⏬ Install Node dependencies - name: ⏬ Install Node dependencies
run: pnpm install run: npm ci
- name: 🔥 Optimize icon font - name: 🔥 Optimize icon font
run: pnpm minify-icons run: npm run minify-icons
- name: 🔨 Build site - name: 🔨 Build site
run: pnpm build run: npm run build
- name: 📦 Upload build artifacts - name: 📦 Upload build artifacts
uses: actions/upload-artifact@v3.1.2 uses: actions/upload-artifact@v3.1.2

View File

@@ -29,7 +29,7 @@ You may need to run through some additional setup to get Rust running inside Int
- Python >=3.10 - Python >=3.10
- Rust Stable (For Tauri Development) - Rust Stable (For Tauri Development)
I know, python in JS projects is extremely annoying. Unfortunately, I know, python in JS projects is extremely annoying, unfortunately,
it seems to be the only platform that offers a functional it seems to be the only platform that offers a functional
way to subset variable woff2 fonts with ligatures. way to subset variable woff2 fonts with ligatures.

117
flake.nix
View File

@@ -4,75 +4,56 @@
rust-overlay.url = "github:oxalica/rust-overlay"; rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = outputs = {
{ self,
self, nixpkgs,
nixpkgs, flake-utils,
flake-utils, rust-overlay,
rust-overlay, }:
}: flake-utils.lib.eachDefaultSystem (system: let
flake-utils.lib.eachDefaultSystem ( overlays = [(import rust-overlay)];
system: pkgs = import nixpkgs {inherit system overlays;};
let rust-bin = pkgs.rust-bin.stable.latest.default.override {
overlays = [ (import rust-overlay) ]; extensions = ["rust-src" "rust-std" "clippy" "rust-analyzer"];
pkgs = import nixpkgs { inherit system overlays; }; };
rust-bin = pkgs.rust-bin.stable.latest.default.override { fontMin = pkgs.python311.withPackages (ps: with ps; [brotli fonttools] ++ (with fonttools.optional-dependencies; [woff]));
extensions = [ tauriPkgs = nixpkgs.legacyPackages.${system};
"rust-src" libraries = with tauriPkgs; [
"rust-std" webkitgtk
"clippy" gtk3
"rust-analyzer" cairo
]; gdk-pixbuf
}; glib
fontMin = pkgs.python311.withPackages ( dbus
ps: openssl_3
with ps; librsvg
[ ];
brotli packages =
fonttools (with pkgs; [
] nodejs_18
++ (with fonttools.optional-dependencies; [ woff ]) rust-bin
); fontMin
tauriPkgs = nixpkgs.legacyPackages.${system}; ])
libraries = with tauriPkgs; [ ++ (with tauriPkgs; [
webkitgtk curl
gtk3 wget
cairo pkg-config
gdk-pixbuf
glib
dbus dbus
openssl_3 openssl_3
glib
gtk3
libsoup
webkitgtk
librsvg librsvg
]; # serial plugin
packages = udev
(with pkgs; [ ]);
nodejs_18 in {
nodePackages.pnpm devShell = pkgs.mkShell {
rust-bin buildInputs = packages;
fontMin shellHook = ''
]) export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath libraries}:$LD_LIBRARY_PATH
++ (with tauriPkgs; [ '';
curl };
wget });
pkg-config
dbus
openssl_3
glib
gtk3
libsoup
webkitgtk
librsvg
# serial plugin
udev
]);
in
{
devShell = pkgs.mkShell {
buildInputs = packages;
shellHook = ''
export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath libraries}:$LD_LIBRARY_PATH
'';
};
}
);
} }

View File

@@ -112,7 +112,6 @@ const config = {
upload_2: "ff52", upload_2: "ff52",
stat_minus_2: "e69c", stat_minus_2: "e69c",
stat_2: "e699", stat_2: "e699",
routine: "e20c",
}, },
}; };

11684
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,8 @@
{ {
"name": "charachorder-device-manager", "name": "charachorder-device-manager",
"version": "1.5.2", "version": "1.5.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"private": true, "private": true,
"engines": {
"node": ">=18.16",
"pnpm": ">=8.6"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/CharaChorder/DeviceManager.git" "url": "https://github.com/CharaChorder/DeviceManager.git"
@@ -34,55 +30,52 @@
"typesafe-i18n": "typesafe-i18n" "typesafe-i18n": "typesafe-i18n"
}, },
"devDependencies": { "devDependencies": {
"@codemirror/autocomplete": "^6.17.0", "@codemirror/autocomplete": "^6.15.0",
"@codemirror/commands": "^6.6.0", "@codemirror/commands": "^6.3.3",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "^6.2.2",
"@codemirror/language": "^6.10.2", "@codemirror/language": "^6.10.1",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.28.4", "@fontsource-variable/material-symbols-rounded": "^5.0.27",
"@fontsource-variable/material-symbols-rounded": "^5.0.34", "@fontsource-variable/noto-sans-mono": "^5.0.19",
"@fontsource-variable/noto-sans-mono": "^5.0.20", "@material/material-color-utilities": "^0.2.7",
"@lezer/highlight": "^1.2.0",
"@material/material-color-utilities": "^0.3.0",
"@modyfi/vite-plugin-yaml": "^1.1.0", "@modyfi/vite-plugin-yaml": "^1.1.0",
"@sveltejs/adapter-static": "^3.0.2", "@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^2.5.18", "@sveltejs/kit": "^1.30.4",
"@sveltejs/vite-plugin-svelte": "^3.1.1", "@sveltejs/vite-plugin-svelte": "^2.5.3",
"@tauri-apps/api": "^1.6.0", "@tauri-apps/api": "^1.5.3",
"@tauri-apps/cli": "^1.6.0", "@tauri-apps/cli": "^1.5.11",
"@types/dom-view-transitions": "^1.0.4", "@types/dom-view-transitions": "^1.0.4",
"@types/flexsearch": "^0.7.6", "@types/flexsearch": "^0.7.6",
"@types/w3c-web-serial": "^1.0.6", "@types/w3c-web-serial": "^1.0.6",
"@types/w3c-web-usb": "^1.0.10", "@types/w3c-web-usb": "^1.0.10",
"@vite-pwa/sveltekit": "^0.6.0", "@vite-pwa/sveltekit": "^0.2.10",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"cypress": "^13.13.0", "cypress": "^13.7.2",
"flexsearch": "^0.7.43", "flexsearch": "^0.7.43",
"fontkit": "^2.0.2", "fontkit": "^2.0.2",
"glob": "^10.4.3", "glob": "^10.3.12",
"jsdom": "^24.1.0", "jsdom": "^22.1.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^3.3.2", "prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.5", "prettier-plugin-svelte": "^3.2.2",
"sass": "^1.77.6", "sass": "^1.74.1",
"stylelint": "^16.6.1", "stylelint": "^15.11.0",
"stylelint-config-clean-order": "^6.1.0", "stylelint-config-clean-order": "^5.4.2",
"stylelint-config-html": "^1.1.0", "stylelint-config-html": "^1.1.0",
"stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-prettier-scss": "^1.0.0",
"stylelint-config-recommended-scss": "^14.0.0", "stylelint-config-recommended-scss": "^13.1.0",
"stylelint-config-standard-scss": "^13.1.0", "stylelint-config-standard-scss": "^11.1.0",
"svelte": "^4.2.18", "svelte": "^4.2.12",
"svelte-check": "^3.8.4", "svelte-check": "^3.6.9",
"svelte-preprocess": "^6.0.1", "svelte-preprocess": "^5.1.3",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"typesafe-i18n": "^5.26.2", "typesafe-i18n": "^5.26.2",
"typescript": "^5.5.3", "typescript": "^5.4.4",
"vite": "^5.3.3", "vite": "^4.5.3",
"vite-plugin-mkcert": "^1.17.5", "vite-plugin-mkcert": "^1.17.5",
"vite-plugin-pwa": "^0.20.0", "vite-plugin-pwa": "^0.17.5",
"vitest": "^1.6.0", "vitest": "^0.34.6"
"workbox-window": "^7.1.0"
}, },
"type": "module" "type": "module"
} }

7074
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "app" name = "app"
version = "1.5.2" version = "1.5.1"
description = "A Tauri App" description = "A Tauri App"
authors = ["Thea Schöbl <dev@theaninova.de>"] authors = ["Thea Schöbl <dev@theaninova.de>"]
license = "AGPL-3" license = "AGPL-3"

View File

@@ -6,7 +6,7 @@
"devPath": "http://localhost:5173", "devPath": "http://localhost:5173",
"distDir": "../build" "distDir": "../build"
}, },
"package": { "productName": "amacc1ng", "version": "1.5.2" }, "package": { "productName": "amacc1ng", "version": "1.5.1" },
"tauri": { "tauri": {
"allowlist": { "all": false }, "allowlist": { "all": false },
"bundle": { "bundle": {

View File

@@ -70,9 +70,6 @@ actions:
title: Primary Keymap title: Primary Keymap
icon: counter_1 icon: counter_1
variant: left variant: left
description: |
Acts as a toggle if the same action is not assigned
to the target layer
549: 549:
variantOf: 548 variantOf: 548
<<: *primary_keymap <<: *primary_keymap
@@ -83,9 +80,6 @@ actions:
title: Numeric Layer title: Numeric Layer
icon: counter_2 icon: counter_2
variant: left variant: left
description: |
Acts as a toggle if the same action is not assigned
to the target layer
551: 551:
variantOf: 550 variantOf: 550
<<: *secondary_keymap <<: *secondary_keymap
@@ -96,34 +90,11 @@ actions:
title: Function Layer title: Function Layer
icon: counter_3 icon: counter_3
variant: left variant: left
description: |
Acts as a toggle if the same action is not assigned
to the target layer
553: 553:
variationOf: 552 variationOf: 552
<<: *tertiary_keymap <<: *tertiary_keymap
id: "KM_3_R" id: "KM_3_R"
variant: right variant: right
558:
id: HOLD_COMPOUND
title: Activate Chord Library
icon: layers
description: |
When used in a chord includes that chord as a base
compound chord for all subsequent chords.
This is effectively a library switch.
Since library activations can be nested, you
usually add a "Reset Chord Library" before this action.
559:
id: RELEASE_COMPOUND
title: Reset Chord Library
icon: layers_clear
description: |
Releases the active compound state, returning
to the default library.
While "Activate Chord Library" can only be used
as an output of a chord, this action can be assigned
to switches directly.
576: 576:
id: ACTION_DELAY_1000 id: ACTION_DELAY_1000
icon: clock_loader_90 icon: clock_loader_90

View File

@@ -422,8 +422,8 @@ actions:
title: Keyboard Non-US \ and | (US English) title: Keyboard Non-US \ and | (US English)
357: 357:
id: "COMPOSE" id: "COMPOSE"
icon: menu
title: Keyboard Application title: Keyboard Application
description: Officially supported by Win, Unix, and Boot
358: 358:
id: "POWER" id: "POWER"
keyCode: "Power" keyCode: "Power"
@@ -944,99 +944,99 @@ actions:
title: Keyboard Right GUI title: Keyboard Right GUI
488: 488:
id: "KSC_E8" id: "KSC_E8"
icon: play_pause
keyCode: "MediaPlayPause" keyCode: "MediaPlayPause"
title: Media Play Pause title: Media Play Pause
description: Not required to be supported by any OS. Possibly deprecated.
489: 489:
id: "KSC_E9" id: "KSC_E9"
icon: stop
keyCode: "MediaStop" keyCode: "MediaStop"
title: Media Stop CD title: Media Stop CD
description: Not required to be supported by any OS. Possibly deprecated.
490: 490:
id: "KSC_EA" id: "KSC_EA"
icon: skip_previous
keyCode: "MediaTrackPrevious" keyCode: "MediaTrackPrevious"
title: Media Previous Song title: Media Previous Song
description: Not required to be supported by any OS. Possibly deprecated.
491: 491:
id: "KSC_EB" id: "KSC_EB"
icon: skip_next
keyCode: "MediaTrackNext" keyCode: "MediaTrackNext"
title: Media Next Song title: Media Next Song
description: Not required to be supported by any OS. Possibly deprecated.
492: 492:
id: "KSC_EC" id: "KSC_EC"
icon: eject
keyCode: "Eject" keyCode: "Eject"
title: Media Eject CD title: Media Eject CD
description: MacOS only description: Not required to be supported by any OS. Possibly deprecated.
493: 493:
id: "KSC_ED" id: "KSC_ED"
icon: volume_up
keyCode: "AudioVolumeUp" keyCode: "AudioVolumeUp"
title: Media Volume Up title: Media Volume Up
description: Not required to be supported by any OS. Possibly deprecated.
494: 494:
id: "KSC_EE" id: "KSC_EE"
icon: volume_down
keyCode: "AudioVolumeDown" keyCode: "AudioVolumeDown"
title: Media Volume Down title: Media Volume Down
description: Not required to be supported by any OS. Possibly deprecated.
495: 495:
id: "KSC_EF" id: "KSC_EF"
icon: volume_off
keyCode: "AudioVolumeMute" keyCode: "AudioVolumeMute"
title: Media Mute title: Media Mute
description: Not required to be supported by any OS. Possibly deprecated.
496: 496:
id: "KSC_F0" id: "KSC_F0"
icon: language title: Media www
title: Media Browser description: Not required to be supported by any OS. Possibly deprecated.
497: 497:
id: "KSC_F1" id: "KSC_F1"
keyCode: "BrowserBack" keyCode: "BrowserBack"
title: Media Browser Back title: Media Back
description: Not required to be supported by any OS. Possibly deprecated.
498: 498:
id: "KSC_F2" id: "KSC_F2"
keyCode: "BrowserForward" keyCode: "BrowserForward"
title: Media Browser Forward title: Media Forward
description: Not required to be supported by any OS. Possibly deprecated.
499: 499:
id: "KSC_F3" id: "KSC_F3"
keyCode: "BrowserStop" keyCode: "BrowserStop"
title: Media Browser Stop title: Media Stop
description: Not supported on MacOS description: Not required to be supported by any OS. Possibly deprecated.
500: 500:
id: "KSC_F4" id: "KSC_F4"
icon: search
keyCode: "BrowserSearch" keyCode: "BrowserSearch"
title: Media Browser Search title: Media Find
description: Not required to be supported by any OS. Possibly deprecated.
501: 501:
id: "KSC_F5" id: "KSC_F5"
icon: brightness_high title: Media Scroll Up
title: Media Brightness Up description: Not required to be supported by any OS. Possibly deprecated.
502: 502:
id: "KSC_F6" id: "KSC_F6"
icon: brightness_low title: Media Scroll Down
title: Media Brightness Down description: Not required to be supported by any OS. Possibly deprecated.
503: 503:
id: "KSC_F7" id: "KSC_F7"
title: Media Edit title: Media Edit
description: Not required to be supported by any OS. Possibly deprecated. description: Not required to be supported by any OS. Possibly deprecated.
504: 504:
id: "KSC_F8" id: "KSC_F8"
icon: bedtime
keyCode: "Sleep" keyCode: "Sleep"
title: Media System Sleep title: Media Sleep
description: Not required to be supported by any OS. Possibly deprecated. description: Not required to be supported by any OS. Possibly deprecated.
505: 505:
id: "KSC_F9" id: "KSC_F9"
icon: routine
keyCode: "WakeUp" keyCode: "WakeUp"
title: Media System Wake title: Media Coffee
description: Not supported on Windows description: Not required to be supported by any OS. Possibly deprecated.
506: 506:
id: "KSC_FA" id: "KSC_FA"
keyCode: "BrowserRefresh" keyCode: "BrowserRefresh"
title: Media Browser Refresh title: Media Refresh
description: Not required to be supported by any OS. Possibly deprecated.
507: 507:
id: "KSC_FB" id: "KSC_FB"
title: Media Calculator title: Media Calc
description: Not supported on MacOS description: Not required to be supported by any OS. Possibly deprecated.
508: 508:
id: "KSC_FC" id: "KSC_FC"
description: Not required to be supported by any OS. description: Not required to be supported by any OS.

View File

@@ -96,12 +96,7 @@ export function restoreFromFile(
case "backup": { case "backup": {
const recent = file.history[0]; const recent = file.history[0];
if (!recent) return; if (!recent) return;
let backupDevice = recent[1].device; if (recent[1].device !== get(serialPort)?.device) {
if (backupDevice === "TWO") backupDevice = "ONE";
let currentDevice = get(serialPort)?.device;
if (currentDevice === "TWO") currentDevice = "ONE";
if (backupDevice !== currentDevice) {
alert("Backup is incompatible with this device"); alert("Backup is incompatible with this device");
throw new Error("Backup is incompatible with this device"); throw new Error("Backup is incompatible with this device");
} }

View File

@@ -3,7 +3,7 @@
import type { KeyInfo } from "$lib/serial/keymap-codes"; import type { KeyInfo } from "$lib/serial/keymap-codes";
import { action as title } from "$lib/title"; import { action as title } from "$lib/title";
import { osLayout } from "$lib/os-layout"; import { osLayout } from "$lib/os-layout";
import LL from "$i18n/i18n-svelte"; import LL from "../../i18n/i18n-svelte";
export let action: number | KeyInfo; export let action: number | KeyInfo;
export let display: "inline-keys" | "keys" = "inline-keys"; export let display: "inline-keys" | "keys" = "inline-keys";

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { KEYMAP_CODES } from "$lib/serial/keymap-codes"; import { KEYMAP_CODES } from "$lib/serial/keymap-codes";
import type { KeyInfo } from "$lib/serial/keymap-codes"; import type { KeyInfo } from "$lib/serial/keymap-codes";
import LL from "$i18n/i18n-svelte"; import LL from "../../i18n/i18n-svelte";
import Action from "$lib/components/Action.svelte"; import Action from "$lib/components/Action.svelte";
export let id: number | KeyInfo; export let id: number | KeyInfo;

View File

@@ -7,7 +7,7 @@
import FlexSearch from "flexsearch"; import FlexSearch from "flexsearch";
import { createEventDispatcher, onMount } from "svelte"; import { createEventDispatcher, onMount } from "svelte";
import ActionListItem from "$lib/components/ActionListItem.svelte"; import ActionListItem from "$lib/components/ActionListItem.svelte";
import LL from "$i18n/i18n-svelte"; import LL from "../../../i18n/i18n-svelte";
import { action } from "$lib/title"; import { action } from "$lib/title";
export let currentAction: number | undefined = undefined; export let currentAction: number | undefined = undefined;

View File

@@ -21,10 +21,6 @@
import("$lib/assets/layouts/one.yml").then( import("$lib/assets/layouts/one.yml").then(
(it) => it.default as VisualLayout, (it) => it.default as VisualLayout,
), ),
TWO: () =>
import("$lib/assets/layouts/one.yml").then(
(it) => it.default as VisualLayout,
),
LITE: () => LITE: () =>
import("$lib/assets/layouts/lite.yml").then( import("$lib/assets/layouts/lite.yml").then(
(it) => it.default as VisualLayout, (it) => it.default as VisualLayout,

View File

@@ -8,7 +8,7 @@
} from "$lib/undo-redo"; } from "$lib/undo-redo";
import { ChangeType, chords } from "$lib/undo-redo"; import { ChangeType, chords } from "$lib/undo-redo";
import ActionString from "$lib/components/ActionString.svelte"; import ActionString from "$lib/components/ActionString.svelte";
import LL from "$i18n/i18n-svelte"; import LL from "../../i18n/i18n-svelte";
import { KEYMAP_IDS } from "$lib/serial/keymap-codes"; import { KEYMAP_IDS } from "$lib/serial/keymap-codes";
export let changes: Change[] = [ export let changes: Change[] = [

View File

@@ -55,19 +55,3 @@ export function deserializeActions(native: bigint): number[] {
return actions; return actions;
} }
/**
* Hashes a chord input the same way as CCOS
*/
export function hashChord(actions: number[]) {
const chord = new Uint8Array(16);
const view = new DataView(chord.buffer);
const serialized = serializeActions(actions);
view.setBigUint64(0, serialized & 0xffff_ffff_ffff_ffffn, true);
view.setBigUint64(8, serialized >> 64n, true);
let hash = 2166136261;
for (let i = 0; i < 16; i++) {
hash = Math.imul(hash ^ view.getUint8(i), 16777619);
}
return hash & 0x3fff_ffff;
}

View File

@@ -12,7 +12,6 @@ import { browser } from "$app/environment";
const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([ const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
["ONE M0", { usbProductId: 32783, usbVendorId: 9114 }], ["ONE M0", { usbProductId: 32783, usbVendorId: 9114 }],
["TWO S3", { usbProductId: 0x0056, usbVendorId: 0x2886 }],
["LITE S2", { usbProductId: 33070, usbVendorId: 12346 }], ["LITE S2", { usbProductId: 33070, usbVendorId: 12346 }],
["LITE M0", { usbProductId: 32796, usbVendorId: 9114 }], ["LITE M0", { usbProductId: 32796, usbVendorId: 9114 }],
["X", { usbProductId: 33163, usbVendorId: 12346 }], ["X", { usbProductId: 33163, usbVendorId: 12346 }],
@@ -20,7 +19,6 @@ const PORT_FILTERS: Map<string, SerialPortFilter> = new Map([
const KEY_COUNTS = { const KEY_COUNTS = {
ONE: 90, ONE: 90,
TWO: 90,
LITE: 67, LITE: 67,
X: 256, X: 256,
} as const; } as const;
@@ -89,8 +87,8 @@ export class CharaDevice {
version!: SemVer; version!: SemVer;
company!: "CHARACHORDER"; company!: "CHARACHORDER";
device!: "ONE" | "TWO" | "LITE" | "X"; device!: "ONE" | "LITE" | "X";
chipset!: "M0" | "S2" | "S3"; chipset!: "M0" | "S2";
keyCount!: 90 | 67 | 256; keyCount!: 90 | 67 | 256;
get portInfo() { get portInfo() {
@@ -127,8 +125,8 @@ export class CharaDevice {
); );
const [company, device, chipset] = await this.send(3, "ID"); const [company, device, chipset] = await this.send(3, "ID");
this.company = company as "CHARACHORDER"; this.company = company as "CHARACHORDER";
this.device = device as "ONE" | "TWO" | "LITE" | "X"; this.device = device as "ONE" | "LITE" | "X";
this.chipset = chipset as "M0" | "S2" | "S3"; this.chipset = chipset as "M0" | "S2";
this.keyCount = KEY_COUNTS[this.device]; this.keyCount = KEY_COUNTS[this.device];
} catch (e) { } catch (e) {
alert(e); alert(e);

View File

@@ -72,6 +72,7 @@ export async function charaFileFromUriComponent<T extends CharaFiles>(
.stream() .stream()
.pipeThrough(new DecompressionStream("deflate")); .pipeThrough(new DecompressionStream("deflate"));
const actions = new Uint8Array(await new Response(stream).arrayBuffer()); const actions = new Uint8Array(await new Response(stream).arrayBuffer());
console.log(actions);
file[key] = deserializeActionArray(actions); file[key] = deserializeActionArray(actions);
} }
} }

View File

@@ -1,6 +1,6 @@
import { persistentWritable } from "$lib/storage"; import { persistentWritable } from "$lib/storage";
import { derived } from "svelte/store"; import { derived } from "svelte/store";
import { hashChord, type Chord } from "$lib/serial/chord"; import type { Chord } from "$lib/serial/chord";
import { import {
deviceChords, deviceChords,
deviceLayout, deviceLayout,
@@ -158,9 +158,3 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
a.localeCompare(b), a.localeCompare(b),
); );
}); });
export const chordHashes = derived(
chords,
(chords) =>
new Map(chords.map((chord) => [hashChord(chord.actions), chord] as const)),
);

View File

@@ -1,13 +0,0 @@
import type { LayoutLoad } from "./$types";
import { browser } from "$app/environment";
import { charaFileFromUriComponent } from "$lib/share/share-url";
export const load = (async ({ url, data, fetch }) => {
const importFile = browser && new URLSearchParams(url.search).get("import");
return {
...data,
importFile: importFile
? await charaFileFromUriComponent(importFile, fetch)
: undefined,
};
}) satisfies LayoutLoad;

View File

@@ -1,30 +0,0 @@
import type { CharaDevice } from "$lib/serial/device";
import type { KeyInfo } from "$lib/serial/keymap-codes";
export const charaMethods = [
"reboot",
"bootloader",
"getRamBytesAvailable",
"getSetting",
"setSetting",
"getLayoutKey",
"setLayoutKey",
"deleteChord",
"setChord",
"getChordPhrase",
"getChordCount",
"getChord",
"send",
] as const satisfies Array<keyof CharaDevice>;
export interface ChannelResponseEventData {
response: unknown;
}
export interface ChannelCharaEventData {
charaChannels: string[];
script: string;
actionCodes: Map<number, KeyInfo>;
}
export type ChannelEventData = ChannelResponseEventData | ChannelCharaEventData;

View File

@@ -20,10 +20,10 @@
import "tippy.js/dist/tippy.css"; import "tippy.js/dist/tippy.css";
import tippy from "tippy.js"; import tippy from "tippy.js";
import { theme, userPreferences } from "$lib/preferences.js"; import { theme, userPreferences } from "$lib/preferences.js";
import { LL, setLocale } from "$i18n/i18n-svelte"; import { LL, setLocale } from "../i18n/i18n-svelte";
import { loadLocale } from "$i18n/i18n-util.sync"; import { loadLocale } from "../i18n/i18n-util.sync";
import { detectLocale } from "$i18n/i18n-util"; import { detectLocale } from "../i18n/i18n-util";
import type { Locales } from "$i18n/i18n-types"; import type { Locales } from "../i18n/i18n-types";
import Footer from "./Footer.svelte"; import Footer from "./Footer.svelte";
import { osLayout, runLayoutDetection } from "$lib/os-layout.js"; import { osLayout, runLayoutDetection } from "$lib/os-layout.js";
import PageTransition from "./PageTransition.svelte"; import PageTransition from "./PageTransition.svelte";

View File

@@ -1,2 +1,16 @@
import type { LayoutLoad } from "./$types";
import { browser } from "$app/environment";
import { charaFileFromUriComponent } from "$lib/share/share-url";
export const prerender = true; export const prerender = true;
export const trailingSlash = "always"; export const trailingSlash = "always";
export const load = (async ({ url, data, fetch }) => {
const importFile = browser && new URLSearchParams(url.search).get("import");
return {
...data,
importFile: importFile
? await charaFileFromUriComponent(importFile, fetch)
: undefined,
};
}) satisfies LayoutLoad;

View File

@@ -2,5 +2,5 @@ import { redirect } from "@sveltejs/kit";
import type { PageLoad } from "./$types"; import type { PageLoad } from "./$types";
export const load = (() => { export const load = (() => {
redirect(302, "/config/"); throw redirect(302, "/config/");
}) satisfies PageLoad; }) satisfies PageLoad;

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { preference } from "$lib/preferences"; import { preference } from "$lib/preferences";
import LL from "$i18n/i18n-svelte"; import LL from "../i18n/i18n-svelte";
import { import {
createChordBackup, createChordBackup,
createLayoutBackup, createLayoutBackup,

View File

@@ -1,5 +1,5 @@
<script> <script>
import LL from "$i18n/i18n-svelte"; import LL from "../i18n/i18n-svelte";
</script> </script>
<dialog open> <dialog open>

View File

@@ -1,6 +1,6 @@
<script> <script>
import { page } from "$app/stores"; import { page } from "$app/stores";
import LL from "$i18n/i18n-svelte"; import LL from "../i18n/i18n-svelte";
$: paths = [ $: paths = [
{ {

View File

@@ -3,7 +3,7 @@
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { slide, fade } from "svelte/transition"; import { slide, fade } from "svelte/transition";
import { preference } from "$lib/preferences"; import { preference } from "$lib/preferences";
import LL from "$i18n/i18n-svelte"; import LL from "../i18n/i18n-svelte";
import { downloadBackup } from "$lib/backup/backup"; import { downloadBackup } from "$lib/backup/backup";
function reboot() { function reboot() {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import LL from "$i18n/i18n-svelte"; import LL from "../i18n/i18n-svelte";
import { import {
changes, changes,
ChangeType, ChangeType,

View File

@@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { browser, version } from "$app/environment"; import { browser, version } from "$app/environment";
import { action } from "$lib/title"; import { action } from "$lib/title";
import LL, { setLocale } from "$i18n/i18n-svelte"; import LL, { setLocale } from "../i18n/i18n-svelte";
import { theme } from "$lib/preferences.js"; import { theme } from "$lib/preferences.js";
import type { Locales } from "$i18n/i18n-types"; import type { Locales } from "../i18n/i18n-types";
import { detectLocale, locales } from "$i18n/i18n-util"; import { detectLocale, locales } from "../i18n/i18n-util";
import { loadLocaleAsync } from "$i18n/i18n-util.async"; import { loadLocaleAsync } from "../i18n/i18n-util.async";
import { tick } from "svelte"; import { tick } from "svelte";
import SyncOverlay from "./SyncOverlay.svelte"; import SyncOverlay from "./SyncOverlay.svelte";
import { serialPort } from "$lib/serial/connection"; import { serialPort } from "$lib/serial/connection";

View File

@@ -8,7 +8,7 @@
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { userPreferences } from "$lib/preferences"; import { userPreferences } from "$lib/preferences";
import { action } from "$lib/title"; import { action } from "$lib/title";
import LL from "$i18n/i18n-svelte"; import LL from "../i18n/i18n-svelte";
import ConfigTabs from "./ConfigTabs.svelte"; import ConfigTabs from "./ConfigTabs.svelte";
import EditActions from "./EditActions.svelte"; import EditActions from "./EditActions.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";

View File

@@ -5,7 +5,7 @@
syncStatus, syncStatus,
sync, sync,
} from "$lib/serial/connection"; } from "$lib/serial/connection";
import LL from "$i18n/i18n-svelte"; import LL from "../i18n/i18n-svelte";
import { slide } from "svelte/transition"; import { slide } from "svelte/transition";
</script> </script>

View File

@@ -2,5 +2,5 @@ import { redirect } from "@sveltejs/kit";
import type { PageLoad } from "./$types"; import type { PageLoad } from "./$types";
export const load = (() => { export const load = (() => {
redirect(302, "/config/layout/"); throw redirect(302, "/config/layout/");
}) satisfies PageLoad; }) satisfies PageLoad;

View File

@@ -1,5 +1,5 @@
<script> <script>
import LL from "$i18n/i18n-svelte"; import LL from "../../i18n/i18n-svelte";
</script> </script>
{$LL.share.URL_COPIED()} {$LL.share.URL_COPIED()}

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { KEYMAP_CODES } from "$lib/serial/keymap-codes"; import { KEYMAP_CATEGORIES, KEYMAP_CODES } from "$lib/serial/keymap-codes";
import FlexSearch from "flexsearch"; import FlexSearch from "flexsearch";
import LL from "$i18n/i18n-svelte"; import LL from "../../../i18n/i18n-svelte";
import { action } from "$lib/title"; import { action } from "$lib/title";
import { onDestroy, onMount, setContext, tick } from "svelte"; import { onDestroy, onMount, setContext, tick } from "svelte";
import { changes, ChangeType, chords } from "$lib/undo-redo"; import { changes, ChangeType, chords } from "$lib/undo-redo";
@@ -144,6 +144,7 @@
progress = i; progress = i;
if ("phrase" in chord) { if ("phrase" in chord) {
console.log(encodeChord(chord, osLayout));
await index.addAsync(i, encodeChord(chord, osLayout)); await index.addAsync(i, encodeChord(chord, osLayout));
} }
} }

View File

@@ -1,14 +1,13 @@
<script lang="ts"> <script lang="ts">
import type { ChordInfo } from "$lib/undo-redo"; import type { ChordInfo } from "$lib/undo-redo";
import { changes, chordHashes, ChangeType } from "$lib/undo-redo"; import { changes, ChangeType } from "$lib/undo-redo";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import LL from "$i18n/i18n-svelte"; import LL from "../../../i18n/i18n-svelte";
import ActionString from "$lib/components/ActionString.svelte"; import ActionString from "$lib/components/ActionString.svelte";
import { selectAction } from "./action-selector"; import { selectAction } from "./action-selector";
import { serialPort } from "$lib/serial/connection"; import { serialPort } from "$lib/serial/connection";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { inputToAction } from "./input-converter"; import { inputToAction } from "./input-converter";
import { hashChord } from "$lib/serial/chord";
export let chord: ChordInfo | undefined = undefined; export let chord: ChordInfo | undefined = undefined;
@@ -22,15 +21,14 @@
} }
function makeChordInput(...actions: number[]) { function makeChordInput(...actions: number[]) {
const compound = compoundInputs[0] const compound = compoundIndices ?? [];
? hashChord(compoundInputs[0].actions)
: 0;
return [ return [
...compound,
...Array.from( ...Array.from(
{ {
length: 12 - actions.length, length: 12 - (compound.length + actions.length + 1),
}, },
(_, i) => (compound >> (i * 10)) & 0x3ff, () => 0,
), ),
...actions.toSorted(compare), ...actions.toSorted(compare),
]; ];
@@ -75,6 +73,7 @@
function addSpecial(event: MouseEvent) { function addSpecial(event: MouseEvent) {
selectAction(event, (action) => { selectAction(event, (action) => {
changes.update((changes) => { changes.update((changes) => {
console.log(compoundIndices, chordActions, action);
changes.push({ changes.push({
type: ChangeType.Chord, type: ChangeType.Chord,
id: chord!.id, id: chord!.id,
@@ -86,30 +85,10 @@
}); });
} }
function* resolveCompound(chord?: ChordInfo) {
if (!chord) return;
let current = chord;
for (let i = 0; i < 10; i++) {
if (current.actions[3] !== 0) return;
const compound = current.actions
.slice(0, 3)
.reduce((a, b, i) => a | (b << (i * 10)));
if (compound === 0) return;
const next = $chordHashes.get(compound);
if (!next) {
return null;
}
current = next;
yield next;
}
return;
}
$: chordActions = chord?.actions $: chordActions = chord?.actions
.slice(chord.actions.lastIndexOf(0) + 1) .slice(chord.actions.lastIndexOf(0) + 1)
.toSorted(compare); .toSorted(compare);
$: compoundInputs = [...resolveCompound(chord)].reverse(); $: compoundIndices = chord?.actions.slice(0, chord.actions.indexOf(0));
</script> </script>
<button <button
@@ -131,15 +110,12 @@
<span>{$LL.configure.chords.NEW_CHORD()}</span> <span>{$LL.configure.chords.NEW_CHORD()}</span>
{/if} {/if}
{#if !editing} {#if !editing}
{#each compoundInputs as compound} {#each compoundIndices ?? [] as index}
<sub <sub>{index}</sub>
><ActionString
display="keys"
actions={compound.actions.slice(compound.actions.lastIndexOf(0) + 1)}
></ActionString>
</sub>
<span>&rarr;</span>
{/each} {/each}
{#if compoundIndices?.length}
<span>&rarr;</span>
{/if}
{/if} {/if}
<ActionString <ActionString
display="keys" display="keys"

View File

@@ -7,16 +7,11 @@
import { keymap } from "@codemirror/view"; import { keymap } from "@codemirror/view";
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { tags } from "@lezer/highlight"; import { tags } from "@lezer/highlight";
import LL from "$i18n/i18n-svelte"; import LL from "../../i18n/i18n-svelte";
import type { CompletionContext, Completion } from "@codemirror/autocomplete"; import type { CompletionContext } from "@codemirror/autocomplete";
import { syntaxTree } from "@codemirror/language";
import { serialPort } from "$lib/serial/connection"; import { serialPort } from "$lib/serial/connection";
import type { CharaDevice } from "$lib/serial/device";
import examplePlugin from "./example-plugin.js?raw"; import examplePlugin from "./example-plugin.js?raw";
import {
charaMethods,
type ChannelCharaEventData,
type ChannelResponseEventData,
} from "./plugin-types";
let theme = EditorView.baseTheme({ let theme = EditorView.baseTheme({
".cm-editor .cm-content": { ".cm-editor .cm-content": {
@@ -45,18 +40,6 @@
background: "transparent !important", background: "transparent !important",
backdropFilter: "invert(0.3)", backdropFilter: "invert(0.3)",
}, },
".cm-tooltip": {
backgroundColor: "var(--md-sys-color-background) !important",
color: "var(--md-sys-color-on-background)",
borderColor: "var(--md-sys-color-outline)",
},
".cm-tooltip-autocomplete ul li[aria-selected]": {
backgroundColor: "var(--md-sys-color-primary) !important",
color: "var(--md-sys-color-on-primary) !important",
},
".cm-completionIcon.cm-completionIcon-keyword::after": {
content: "'🗝'",
},
}); });
const highlightStyle = HighlightStyle.define( const highlightStyle = HighlightStyle.define(
[ [
@@ -73,71 +56,11 @@
all: { fontFamily: '"Noto Sans Mono", monospace', fontSize: "14px" }, all: { fontFamily: '"Noto Sans Mono", monospace', fontSize: "14px" },
}, },
); );
const globalsCompletion: Completion[] = [
{ label: "Chara", type: "class", boost: 90 },
{ label: "Actions", type: "class", boost: 90 },
];
const actionsCompletion: Completion[] = Array.from(
KEYMAP_CODES,
([id, info]) => {
const isValidIdentifier =
info.id && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(info.id);
return {
label: info.id
? isValidIdentifier
? info.id
: `["${info.id}"]`
: info.id!,
displayLabel: info.id,
detail: [info.title, `(0x${id.toString(16)})`, info.description]
.filter((it) => !!it)
.join(" "),
section: info.category,
boost: isValidIdentifier ? Math.min(info.id?.length ?? 0, 10) + 50 : 40,
type: "property",
};
},
).filter((it) => it.label !== undefined);
const completion = javascriptLanguage.data.of({ const completion = javascriptLanguage.data.of({
autocomplete: function completeGlobals(context: CompletionContext) { autocomplete: function completeGlobals(context: CompletionContext) {
let nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1); if (context.matchBefore(/Chara\./)) {
if (nodeBefore.name === "VariableName") { // TODO
return {
from: nodeBefore.from,
options: globalsCompletion,
};
} else if (nodeBefore.name === "Script") {
return {
from: context.pos,
options: globalsCompletion,
};
} else if (
(nodeBefore.name === "PropertyName" || nodeBefore.name === ".") &&
nodeBefore.parent?.name === "MemberExpression" &&
nodeBefore.parent.firstChild
) {
const variable = nodeBefore.parent.firstChild;
const variableName = context.state.sliceDoc(variable.from, variable.to);
if (variableName === "Actions") {
return {
from:
nodeBefore.name === "PropertyName"
? nodeBefore.from
: nodeBefore.to,
options: actionsCompletion,
};
}
let parent = nodeBefore.prevSibling;
while (parent !== null && parent?.name !== "VariableName") {
parent = parent.prevSibling;
}
if (parent) {
}
} }
return null;
}, },
}); });
@@ -155,6 +78,22 @@
doc: examplePlugin, doc: examplePlugin,
}); });
}); });
const charaMethods = [
"reboot",
"bootloader",
"getRamBytesAvailable",
"getSetting",
"setSetting",
"getLayoutKey",
"setLayoutKey",
"deleteChord",
"setChord",
"getChordPhrase",
"getChordCount",
"getChord",
"send",
] satisfies Array<keyof CharaDevice>;
$: channels = $serialPort $: channels = $serialPort
? ({ ? ({
getVersion: async (..._args: unknown[]) => $serialPort.version, getVersion: async (..._args: unknown[]) => $serialPort.version,
@@ -183,10 +122,7 @@
const [channel, params] = event.data; const [channel, params] = event.data;
const response = channels[channel as keyof typeof channels](...params); const response = channels[channel as keyof typeof channels](...params);
frame.contentWindow!.postMessage( frame.contentWindow!.postMessage({ response: await response }, "*");
{ response: await response } satisfies ChannelResponseEventData,
"*",
);
} }
function runPlugin() { function runPlugin() {
@@ -195,7 +131,7 @@
actionCodes: KEYMAP_CODES, actionCodes: KEYMAP_CODES,
script: editorView.state.doc.toString(), script: editorView.state.doc.toString(),
charaChannels: Object.keys(channels), charaChannels: Object.keys(channels),
} satisfies ChannelCharaEventData, },
"*", "*",
); );
} }

View File

View File

@@ -1,17 +1,15 @@
<script lang="ts"> <script>
import type { ChannelEventData } from "../(app)/plugin/plugin-types"; // @ts-nocheck
let ongoingRequest;
let ongoingRequest: Promise<unknown> | undefined = undefined; let resolveRequest;
let resolveRequest: ((data: unknown) => void) | undefined = undefined; let source;
let source: MessageEventSource | undefined = undefined; async function post(channel, args) {
async function post(channel: string, args: unknown[]) {
while (ongoingRequest) { while (ongoingRequest) {
await ongoingRequest; await ongoingRequest;
} }
ongoingRequest = new Promise((resolve) => { ongoingRequest = new Promise((resolve) => {
resolveRequest = resolve; resolveRequest = resolve;
source?.postMessage([channel, args], { targetOrigin: "*" }); source.postMessage([channel, args], "*");
}); });
ongoingRequest.then(() => { ongoingRequest.then(() => {
ongoingRequest = undefined; ongoingRequest = undefined;
@@ -19,13 +17,13 @@
return ongoingRequest; return ongoingRequest;
} }
function onMessage(event: MessageEvent<ChannelEventData>) { window.addEventListener("message", (event) => {
if ("response" in event.data) { if ("response" in event.data) {
resolveRequest?.(event.data.response); resolveRequest(event.data.response);
} else { } else {
source = event.source ?? undefined; source = event.source;
const Action = event.data.actionCodes; var Action = event.data.actionCodes;
Object.assign( Object.assign(
Action, Action,
Object.fromEntries( Object.fromEntries(
@@ -35,17 +33,12 @@
), ),
); );
new Function("Action", "Chara", event.data.script)( var Chara = {};
Action, for (const fn of event.data.charaChannels) {
Object.fromEntries( Chara[fn] = (...args) => post(fn, args);
event.data.charaChannels.map((name) => [ }
name,
(...args: unknown[]) => post(name, args),
]),
),
);
}
}
</script>
<svelte:window on:message={onMessage} /> eval(`(async function(){${event.data.script}})()`);
}
});
</script>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { LL } from "$i18n/i18n-svelte"; import { LL } from "../../i18n/i18n-svelte";
</script> </script>
<h1>{$LL.update.TITLE()}</h1> <h1>{$LL.update.TITLE()}</h1>

View File

@@ -1,60 +0,0 @@
<script>
/** @type {Promise<unknown> | undefined} */
let ongoingRequest = undefined;
/** @type {(data: unknown) => void | undefined} */
let resolveRequest = undefined;
/** @type {MessageEventSource | undefined} */
let source = undefined;
/**
* @param {string} channel
* @param {unknown} args
* @returns {Promise<unknown>}
*/
async function post(channel, args) {
while (ongoingRequest) {
await ongoingRequest;
}
ongoingRequest = new Promise((resolve) => {
resolveRequest = resolve;
source?.postMessage([channel, args], { targetOrigin: "*" });
});
ongoingRequest.then(() => {
ongoingRequest = undefined;
});
return ongoingRequest;
}
/**
* @param {MessageEvent<import('../../src/routes/plugin/plugin-types').ChannelEventData>} event
*/
function onMessage(event) {
if ("response" in event.data) {
resolveRequest?.(event.data.response);
} else {
source = event.source ?? undefined;
const Action = event.data.actionCodes;
Object.assign(
Action,
Object.fromEntries(
Object.values(event.data.actionCodes)
.filter((it) => !!it.id)
.map((it) => [it.id, it]),
),
);
new Function("Action", "Chara", event.data.script)(
Action,
Object.fromEntries(
event.data.charaChannels.map((name) => [
name,
(...args) => post(name, args),
]),
),
);
}
}
window.addEventListener("message", onMessage);
</script>

View File

@@ -1,5 +1,5 @@
import adapter from "@sveltejs/adapter-static"; import adapter from "@sveltejs/adapter-static";
import { sveltePreprocess } from "svelte-preprocess"; import preprocess from "svelte-preprocess";
import autoprefixer from "autoprefixer"; import autoprefixer from "autoprefixer";
import { readFile } from "fs/promises"; import { readFile } from "fs/promises";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
@@ -13,12 +13,9 @@ const { version } = JSON.parse(
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
preprocess: [sveltePreprocess({ postcss: { plugins: autoprefixer() } })], preprocess: [preprocess({ postcss: { plugins: autoprefixer() } })],
kit: { kit: {
adapter: adapter({ fallback: "404.html" }), adapter: adapter({ fallback: "404.html" }),
alias: {
$i18n: "./src/i18n",
},
version: { version: {
name: version, name: version,
}, },

View File

@@ -20,7 +20,7 @@ process.env["VITE_HOMEPAGE_URL"] = repository.url.replace(/\.git$/, "");
process.env["VITE_DOCS_URL"] = homepage; process.env["VITE_DOCS_URL"] = homepage;
process.env["VITE_BUGS_URL"] = bugs.url; process.env["VITE_BUGS_URL"] = bugs.url;
process.env["VITE_LEARN_URL"] = "https://www.iq-eq.io/"; process.env["VITE_LEARN_URL"] = "https://www.iq-eq.io/";
process.env["VITE_LATEST_FIRMWARE"] = "1.1.4"; process.env["VITE_LATEST_FIRMWARE"] = "1.1.3";
process.env["VITE_STORE_URL"] = "https://www.charachorder.com/"; process.env["VITE_STORE_URL"] = "https://www.charachorder.com/";
export default defineConfig({ export default defineConfig({
@@ -49,10 +49,9 @@ export default defineConfig({
workbox: { workbox: {
// https://vite-pwa-org.netlify.app/frameworks/sveltekit.html#globpatterns // https://vite-pwa-org.netlify.app/frameworks/sveltekit.html#globpatterns
globPatterns: [ globPatterns: [
"client/**/*.{js,map,css,ico,woff2,csv,png,webp,svg,webmanifest}", "client/**/*.{js,map,css,woff2,csv,png,svg}",
"prerendered/**/*.html", "prerendered/**/*.html",
], ],
ignoreURLParametersMatching: [/^import$/],
}, },
manifest: { manifest: {
name: "CharaChorder Device Manager", name: "CharaChorder Device Manager",