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
run: pip install -r requirements.txt
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: 🐉 Use Node.js 18.16.x
uses: actions/setup-node@v3
with:
node-version: 18.16.x
cache: "pnpm"
cache: "npm"
- name: ⏬ Install Node dependencies
run: pnpm install
run: npm ci
- name: 🔥 Optimize icon font
run: pnpm minify-icons
run: npm run minify-icons
- name: 🔨 Build site
run: pnpm build
run: npm run build
- name: 📦 Upload build artifacts
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
- 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
way to subset variable woff2 fonts with ligatures.

117
flake.nix
View File

@@ -4,75 +4,56 @@
rust-overlay.url = "github:oxalica/rust-overlay";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
rust-overlay,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs { inherit system overlays; };
rust-bin = pkgs.rust-bin.stable.latest.default.override {
extensions = [
"rust-src"
"rust-std"
"clippy"
"rust-analyzer"
];
};
fontMin = pkgs.python311.withPackages (
ps:
with ps;
[
brotli
fonttools
]
++ (with fonttools.optional-dependencies; [ woff ])
);
tauriPkgs = nixpkgs.legacyPackages.${system};
libraries = with tauriPkgs; [
webkitgtk
gtk3
cairo
gdk-pixbuf
glib
outputs = {
self,
nixpkgs,
flake-utils,
rust-overlay,
}:
flake-utils.lib.eachDefaultSystem (system: let
overlays = [(import rust-overlay)];
pkgs = import nixpkgs {inherit system overlays;};
rust-bin = pkgs.rust-bin.stable.latest.default.override {
extensions = ["rust-src" "rust-std" "clippy" "rust-analyzer"];
};
fontMin = pkgs.python311.withPackages (ps: with ps; [brotli fonttools] ++ (with fonttools.optional-dependencies; [woff]));
tauriPkgs = nixpkgs.legacyPackages.${system};
libraries = with tauriPkgs; [
webkitgtk
gtk3
cairo
gdk-pixbuf
glib
dbus
openssl_3
librsvg
];
packages =
(with pkgs; [
nodejs_18
rust-bin
fontMin
])
++ (with tauriPkgs; [
curl
wget
pkg-config
dbus
openssl_3
glib
gtk3
libsoup
webkitgtk
librsvg
];
packages =
(with pkgs; [
nodejs_18
nodePackages.pnpm
rust-bin
fontMin
])
++ (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
'';
};
}
);
# 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",
stat_minus_2: "e69c",
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",
"version": "1.5.2",
"version": "1.5.1",
"license": "AGPL-3.0-or-later",
"private": true,
"engines": {
"node": ">=18.16",
"pnpm": ">=8.6"
},
"repository": {
"type": "git",
"url": "https://github.com/CharaChorder/DeviceManager.git"
@@ -34,55 +30,52 @@
"typesafe-i18n": "typesafe-i18n"
},
"devDependencies": {
"@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0",
"@codemirror/autocomplete": "^6.15.0",
"@codemirror/commands": "^6.3.3",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/language": "^6.10.2",
"@codemirror/language": "^6.10.1",
"@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.28.4",
"@fontsource-variable/material-symbols-rounded": "^5.0.34",
"@fontsource-variable/noto-sans-mono": "^5.0.20",
"@lezer/highlight": "^1.2.0",
"@material/material-color-utilities": "^0.3.0",
"@fontsource-variable/material-symbols-rounded": "^5.0.27",
"@fontsource-variable/noto-sans-mono": "^5.0.19",
"@material/material-color-utilities": "^0.2.7",
"@modyfi/vite-plugin-yaml": "^1.1.0",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.5.18",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@tauri-apps/api": "^1.6.0",
"@tauri-apps/cli": "^1.6.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.30.4",
"@sveltejs/vite-plugin-svelte": "^2.5.3",
"@tauri-apps/api": "^1.5.3",
"@tauri-apps/cli": "^1.5.11",
"@types/dom-view-transitions": "^1.0.4",
"@types/flexsearch": "^0.7.6",
"@types/w3c-web-serial": "^1.0.6",
"@types/w3c-web-usb": "^1.0.10",
"@vite-pwa/sveltekit": "^0.6.0",
"@vite-pwa/sveltekit": "^0.2.10",
"autoprefixer": "^10.4.19",
"codemirror": "^6.0.1",
"cypress": "^13.13.0",
"cypress": "^13.7.2",
"flexsearch": "^0.7.43",
"fontkit": "^2.0.2",
"glob": "^10.4.3",
"jsdom": "^24.1.0",
"glob": "^10.3.12",
"jsdom": "^22.1.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.5",
"sass": "^1.77.6",
"stylelint": "^16.6.1",
"stylelint-config-clean-order": "^6.1.0",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.2",
"sass": "^1.74.1",
"stylelint": "^15.11.0",
"stylelint-config-clean-order": "^5.4.2",
"stylelint-config-html": "^1.1.0",
"stylelint-config-prettier-scss": "^1.0.0",
"stylelint-config-recommended-scss": "^14.0.0",
"stylelint-config-standard-scss": "^13.1.0",
"svelte": "^4.2.18",
"svelte-check": "^3.8.4",
"svelte-preprocess": "^6.0.1",
"stylelint-config-recommended-scss": "^13.1.0",
"stylelint-config-standard-scss": "^11.1.0",
"svelte": "^4.2.12",
"svelte-check": "^3.6.9",
"svelte-preprocess": "^5.1.3",
"tippy.js": "^6.3.7",
"typesafe-i18n": "^5.26.2",
"typescript": "^5.5.3",
"vite": "^5.3.3",
"typescript": "^5.4.4",
"vite": "^4.5.3",
"vite-plugin-mkcert": "^1.17.5",
"vite-plugin-pwa": "^0.20.0",
"vitest": "^1.6.0",
"workbox-window": "^7.1.0"
"vite-plugin-pwa": "^0.17.5",
"vitest": "^0.34.6"
},
"type": "module"
}

7074
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -70,9 +70,6 @@ actions:
title: Primary Keymap
icon: counter_1
variant: left
description: |
Acts as a toggle if the same action is not assigned
to the target layer
549:
variantOf: 548
<<: *primary_keymap
@@ -83,9 +80,6 @@ actions:
title: Numeric Layer
icon: counter_2
variant: left
description: |
Acts as a toggle if the same action is not assigned
to the target layer
551:
variantOf: 550
<<: *secondary_keymap
@@ -96,34 +90,11 @@ actions:
title: Function Layer
icon: counter_3
variant: left
description: |
Acts as a toggle if the same action is not assigned
to the target layer
553:
variationOf: 552
<<: *tertiary_keymap
id: "KM_3_R"
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:
id: ACTION_DELAY_1000
icon: clock_loader_90

View File

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

View File

@@ -96,12 +96,7 @@ export function restoreFromFile(
case "backup": {
const recent = file.history[0];
if (!recent) return;
let backupDevice = recent[1].device;
if (backupDevice === "TWO") backupDevice = "ONE";
let currentDevice = get(serialPort)?.device;
if (currentDevice === "TWO") currentDevice = "ONE";
if (backupDevice !== currentDevice) {
if (recent[1].device !== get(serialPort)?.device) {
alert("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 { action as title } from "$lib/title";
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 display: "inline-keys" | "keys" = "inline-keys";

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { KEYMAP_CODES } 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";
export let id: number | KeyInfo;

View File

@@ -7,7 +7,7 @@
import FlexSearch from "flexsearch";
import { createEventDispatcher, onMount } from "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";
export let currentAction: number | undefined = undefined;

View File

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

View File

@@ -8,7 +8,7 @@
} from "$lib/undo-redo";
import { ChangeType, chords } from "$lib/undo-redo";
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";
export let changes: Change[] = [

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { persistentWritable } from "$lib/storage";
import { derived } from "svelte/store";
import { hashChord, type Chord } from "$lib/serial/chord";
import type { Chord } from "$lib/serial/chord";
import {
deviceChords,
deviceLayout,
@@ -158,9 +158,3 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
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 from "tippy.js";
import { theme, userPreferences } from "$lib/preferences.js";
import { LL, setLocale } from "$i18n/i18n-svelte";
import { loadLocale } from "$i18n/i18n-util.sync";
import { detectLocale } from "$i18n/i18n-util";
import type { Locales } from "$i18n/i18n-types";
import { LL, setLocale } from "../i18n/i18n-svelte";
import { loadLocale } from "../i18n/i18n-util.sync";
import { detectLocale } from "../i18n/i18n-util";
import type { Locales } from "../i18n/i18n-types";
import Footer from "./Footer.svelte";
import { osLayout, runLayoutDetection } from "$lib/os-layout.js";
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 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";
export const load = (() => {
redirect(302, "/config/");
throw redirect(302, "/config/");
}) satisfies PageLoad;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { browser, version } from "$app/environment";
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 type { Locales } from "$i18n/i18n-types";
import { detectLocale, locales } from "$i18n/i18n-util";
import { loadLocaleAsync } from "$i18n/i18n-util.async";
import type { Locales } from "../i18n/i18n-types";
import { detectLocale, locales } from "../i18n/i18n-util";
import { loadLocaleAsync } from "../i18n/i18n-util.async";
import { tick } from "svelte";
import SyncOverlay from "./SyncOverlay.svelte";
import { serialPort } from "$lib/serial/connection";

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<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 LL from "$i18n/i18n-svelte";
import LL from "../../../i18n/i18n-svelte";
import { action } from "$lib/title";
import { onDestroy, onMount, setContext, tick } from "svelte";
import { changes, ChangeType, chords } from "$lib/undo-redo";
@@ -144,6 +144,7 @@
progress = i;
if ("phrase" in chord) {
console.log(encodeChord(chord, osLayout));
await index.addAsync(i, encodeChord(chord, osLayout));
}
}

View File

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

View File

@@ -7,16 +7,11 @@
import { keymap } from "@codemirror/view";
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { tags } from "@lezer/highlight";
import LL from "$i18n/i18n-svelte";
import type { CompletionContext, Completion } from "@codemirror/autocomplete";
import { syntaxTree } from "@codemirror/language";
import LL from "../../i18n/i18n-svelte";
import type { CompletionContext } from "@codemirror/autocomplete";
import { serialPort } from "$lib/serial/connection";
import type { CharaDevice } from "$lib/serial/device";
import examplePlugin from "./example-plugin.js?raw";
import {
charaMethods,
type ChannelCharaEventData,
type ChannelResponseEventData,
} from "./plugin-types";
let theme = EditorView.baseTheme({
".cm-editor .cm-content": {
@@ -45,18 +40,6 @@
background: "transparent !important",
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(
[
@@ -73,71 +56,11 @@
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({
autocomplete: function completeGlobals(context: CompletionContext) {
let nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1);
if (nodeBefore.name === "VariableName") {
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) {
}
if (context.matchBefore(/Chara\./)) {
// TODO
}
return null;
},
});
@@ -155,6 +78,22 @@
doc: examplePlugin,
});
});
const charaMethods = [
"reboot",
"bootloader",
"getRamBytesAvailable",
"getSetting",
"setSetting",
"getLayoutKey",
"setLayoutKey",
"deleteChord",
"setChord",
"getChordPhrase",
"getChordCount",
"getChord",
"send",
] satisfies Array<keyof CharaDevice>;
$: channels = $serialPort
? ({
getVersion: async (..._args: unknown[]) => $serialPort.version,
@@ -183,10 +122,7 @@
const [channel, params] = event.data;
const response = channels[channel as keyof typeof channels](...params);
frame.contentWindow!.postMessage(
{ response: await response } satisfies ChannelResponseEventData,
"*",
);
frame.contentWindow!.postMessage({ response: await response }, "*");
}
function runPlugin() {
@@ -195,7 +131,7 @@
actionCodes: KEYMAP_CODES,
script: editorView.state.doc.toString(),
charaChannels: Object.keys(channels),
} satisfies ChannelCharaEventData,
},
"*",
);
}

View File

View File

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

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { LL } from "$i18n/i18n-svelte";
import { LL } from "../../i18n/i18n-svelte";
</script>
<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 { sveltePreprocess } from "svelte-preprocess";
import preprocess from "svelte-preprocess";
import autoprefixer from "autoprefixer";
import { readFile } from "fs/promises";
import { fileURLToPath } from "url";
@@ -13,12 +13,9 @@ const { version } = JSON.parse(
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [sveltePreprocess({ postcss: { plugins: autoprefixer() } })],
preprocess: [preprocess({ postcss: { plugins: autoprefixer() } })],
kit: {
adapter: adapter({ fallback: "404.html" }),
alias: {
$i18n: "./src/i18n",
},
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_BUGS_URL"] = bugs.url;
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/";
export default defineConfig({
@@ -49,10 +49,9 @@ export default defineConfig({
workbox: {
// https://vite-pwa-org.netlify.app/frameworks/sveltekit.html#globpatterns
globPatterns: [
"client/**/*.{js,map,css,ico,woff2,csv,png,webp,svg,webmanifest}",
"client/**/*.{js,map,css,woff2,csv,png,svg}",
"prerendered/**/*.html",
],
ignoreURLParametersMatching: [/^import$/],
},
manifest: {
name: "CharaChorder Device Manager",