feat: enable stricter type checking options

feat: make the app more fault tolerant
This commit is contained in:
2024-04-06 14:28:23 +02:00
parent bef51d2a7d
commit 2808973ad0
38 changed files with 152 additions and 121 deletions

View File

@@ -22,8 +22,7 @@
"tauri": "tauri", "tauri": "tauri",
"test": "vitest run --coverage", "test": "vitest run --coverage",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"minify-icons": "node src/tools/minify-icon-font.js", "minify-icons": "node src/tools/minify-icon-font.js",
"version": "node src/tools/version.js && git add src-tauri/Cargo.toml && git add src-tauri/tauri.conf.json", "version": "node src/tools/version.js && git add src-tauri/Cargo.toml && git add src-tauri/tauri.conf.json",
"lint": "prettier --check .", "lint": "prettier --check .",

2
src/env.d.ts vendored
View File

@@ -11,7 +11,7 @@ interface ImportMetaEnv {
readonly VITE_HOMEPAGE_URL: string; readonly VITE_HOMEPAGE_URL: string;
readonly VITE_BUGS_URL: string; readonly VITE_BUGS_URL: string;
readonly VITE_DOCS_URL: string; readonly VITE_DOCS_URL: string;
readonly VIET_LEARN_URL: string; readonly VITE_LEARN_URL: string;
readonly VITE_LATEST_FIRMWARE: string; readonly VITE_LATEST_FIRMWARE: string;
} }

View File

@@ -2,7 +2,7 @@ import type { FormattersInitializer } from "typesafe-i18n";
import type { Locales, Formatters } from "./i18n-types"; import type { Locales, Formatters } from "./i18n-types";
export const initFormatters: FormattersInitializer<Locales, Formatters> = ( export const initFormatters: FormattersInitializer<Locales, Formatters> = (
locale: Locales, _locale: Locales,
) => { ) => {
const formatters: Formatters = { const formatters: Formatters = {
// add your formatter functions here // add your formatter functions here

View File

@@ -95,6 +95,7 @@ export function restoreFromFile(
switch (file.type) { switch (file.type) {
case "backup": { case "backup": {
const recent = file.history[0]; const recent = file.history[0];
if (!recent) return;
if (recent[1].device !== get(serialPort)?.device) { if (recent[1].device !== get(serialPort)?.device) {
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");
@@ -177,7 +178,7 @@ export function getChangesFromLayoutFile(file: CharaLayoutFile) {
const changes: Change[] = []; const changes: Change[] = [];
for (const [layer, keys] of file.layout.entries()) { for (const [layer, keys] of file.layout.entries()) {
for (const [id, action] of keys.entries()) { for (const [id, action] of keys.entries()) {
if (get(layout)[layer][id].action !== action) { if (get(layout)[layer]?.[id]?.action !== action) {
changes.push({ changes.push({
type: ChangeType.Layout, type: ChangeType.Layout,
layer, layer,

View File

@@ -13,11 +13,11 @@ export function csvChordsToJson(csv: string): CharaChordFile {
.map((line) => { .map((line) => {
const [input, output] = line.split(/,(?=[^,]*$)/, 2); const [input, output] = line.split(/,(?=[^,]*$)/, 2);
return [ return [
input input!
.split("+") .split("+")
.map((it) => KEYMAP_IDS.get(it.trim())?.code ?? 0) .map((it) => KEYMAP_IDS.get(it.trim())?.code ?? 0)
.sort((a, b) => a - b), .sort((a, b) => a - b),
output output!
.trim() .trim()
.split("") .split("")
.map((it) => KEYMAP_IDS.get(SPECIAL_KEYS.get(it) ?? it)?.code ?? 0), .map((it) => KEYMAP_IDS.get(SPECIAL_KEYS.get(it) ?? it)?.code ?? 0),

View File

@@ -17,7 +17,7 @@ export function csvLayoutToJson(
for (const layer of csv.trim().split("\n")) { for (const layer of csv.trim().split("\n")) {
const [layerId, key, action] = layer.substring(1).split(",").map(Number); const [layerId, key, action] = layer.substring(1).split(",").map(Number);
layout.layout[Number(layerId) - 1][Number(key)] = Number(action); layout.layout[Number(layerId) - 1]![Number(key)] = Number(action);
} }
return layout; return layout;

View File

@@ -10,7 +10,7 @@
$: info = $: info =
typeof action === "number" typeof action === "number"
? KEYMAP_CODES[action] ?? { code: action } ? KEYMAP_CODES.get(action) ?? { code: action }
: action; : action;
$: dynamicMapping = info.keyCode && $osLayout.get(info.keyCode); $: dynamicMapping = info.keyCode && $osLayout.get(info.keyCode);

View File

@@ -6,7 +6,7 @@
export let id: number | KeyInfo; export let id: number | KeyInfo;
$: key = (typeof id === "number" ? KEYMAP_CODES[id] ?? id : id) as $: key = (typeof id === "number" ? KEYMAP_CODES.get(id) ?? id : id) as
| number | number
| KeyInfo; | KeyInfo;
</script> </script>
@@ -25,10 +25,10 @@
{#if key.description} {#if key.description}
<i>{key.description}</i> <i>{key.description}</i>
{/if} {/if}
{#if key.category.name === "ASCII Macros"} {#if key.category?.name === "ASCII Macros"}
<span class="warning">{@html $LL.actionSearch.SHIFT_WARNING()}</span> <span class="warning">{@html $LL.actionSearch.SHIFT_WARNING()}</span>
{/if} {/if}
{#if key.category.name === "CP-1252"} {#if key.category?.name === "CP-1252"}
<span class="warning">{@html $LL.actionSearch.ALT_CODE_WARNING()}</span> <span class="warning">{@html $LL.actionSearch.ALT_CODE_WARNING()}</span>
{/if} {/if}
</div> </div>

View File

@@ -1,4 +1,5 @@
<script> <script>
// @ts-expect-error no types here
import { useRegisterSW } from "virtual:pwa-register/svelte"; import { useRegisterSW } from "virtual:pwa-register/svelte";
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW(); const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW();

View File

@@ -4,7 +4,7 @@
function submit(event: Event) { function submit(event: Event) {
event.preventDefault(); event.preventDefault();
$serialPort.send(value.trim()); $serialPort?.send(0, value.trim());
value = ""; value = "";
io.scrollTo({ top: io.scrollHeight }); io.scrollTo({ top: io.scrollHeight });
} }

View File

@@ -26,7 +26,7 @@
); );
function search() { function search() {
results = index!.search(searchBox.value); results = index!.search(searchBox.value) as number[];
exact = exactIndex[searchBox.value]?.code; exact = exactIndex[searchBox.value]?.code;
code = Number(searchBox.value); code = Number(searchBox.value);
} }

View File

@@ -121,9 +121,11 @@
function edit(index: number) { function edit(index: number) {
const keyInfo = layoutInfo.keys[index]; const keyInfo = layoutInfo.keys[index];
if (!keyInfo) return;
const clickedGroup = groupParent.children.item(index) as SVGGElement; const clickedGroup = groupParent.children.item(index) as SVGGElement;
const nextAction = get(layout)[get(activeLayer)][keyInfo.id]; const nextAction = get(layout)[get(activeLayer)]?.[keyInfo.id];
const currentAction = get(deviceLayout)[get(activeLayer)][keyInfo.id]; const currentAction = get(deviceLayout)[get(activeLayer)]?.[keyInfo.id];
if (!nextAction || !currentAction) return;
const component = new ActionSelector({ const component = new ActionSelector({
target: document.body, target: document.body,
props: { props: {

View File

@@ -23,13 +23,12 @@
</script> </script>
{#each positions as position, layer} {#each positions as position, layer}
{@const { action: actionId, isApplied } = $layout[layer][key.id] ?? { {@const { action: actionId, isApplied } = $layout[layer]?.[key.id] ?? {
action: 0, action: 0,
isApplied: true, isApplied: true,
}} }}
{@const { code, icon, id, display, title, keyCode, variant } = KEYMAP_CODES[ {@const { code, icon, id, display, title, keyCode, variant } =
actionId KEYMAP_CODES.get(actionId) ?? { code: actionId }}
] ?? { code: actionId }}
{@const dynamicMapping = keyCode && $osLayout.get(keyCode)} {@const dynamicMapping = keyCode && $osLayout.get(keyCode)}
{@const tooltip = {@const tooltip =
(title ?? id ?? `0x${code.toString(16)}`) + (title ?? id ?? `0x${code.toString(16)}`) +
@@ -53,7 +52,7 @@
style:scale={isActive ? 1 : `var(--inactive-scale, ${inactiveScale})`} style:scale={isActive ? 1 : `var(--inactive-scale, ${inactiveScale})`}
style:translate={isActive style:translate={isActive
? "0 0 0" ? "0 0 0"
: `${direction[0].toPrecision(2)}px ${direction[1].toPrecision(2)}px 0`} : `${direction[0]?.toPrecision(2)}px ${direction[1]?.toPrecision(2)}px 0`}
style:rotate="{rotate}deg" style:rotate="{rotate}deg"
use:action={{ title: tooltip }} use:action={{ title: tooltip }}
> >

View File

@@ -119,17 +119,21 @@
</label> </label>
</h3> </h3>
<ul> <ul>
{#each layoutChanges {#each layoutChanges as changes, i}
.map((it, i) => /** @type {const} */ ([it, i + 1])) {@const layer = i + 1}
.filter(([it]) => it.length > 0) as [changes, layer]} {#if changes.length > 0}
<li> <li>
<h4> <h4>
<label> <label>
<input type="checkbox" class="checkbox" /> <input type="checkbox" class="checkbox" />
{$LL.changes.layout.LAYER({ changes: changes.length, layer })} {$LL.changes.layout.LAYER({
changes: changes.length,
layer,
})}
</label> </label>
</h4> </h4>
</li> </li>
{/if}
{/each} {/each}
</ul> </ul>
</li> </li>

View File

@@ -3,7 +3,7 @@
export let ports: SerialPort[]; export let ports: SerialPort[];
const dispatch = createEventDispatcher<{ confirm: SerialPort | undefined }>(); const dispatch = createEventDispatcher<{ confirm: SerialPort | undefined }>();
let selected = ports[0].getInfo().name; let selected = ports[0]?.getInfo().name;
</script> </script>
<dialog> <dialog>

View File

@@ -37,7 +37,7 @@ export function serializeActions(actions: number[]): bigint {
let native = 0n; let native = 0n;
for (let i = 1; i <= actions.length; i++) { for (let i = 1; i <= actions.length; i++) {
native |= native |=
BigInt(actions[actions.length - i] & 0x3ff) << BigInt((12 - i) * 10); BigInt(actions[actions.length - i]! & 0x3ff) << BigInt((12 - i) * 10);
} }
return native; return native;
} }

View File

@@ -67,7 +67,9 @@ export async function sync() {
syncStatus.set("downloading"); syncStatus.set("downloading");
const max = const max =
Object.keys(settingInfo.settings).length + device.keyCount * 3 + chordCount; Object.keys(settingInfo["settings"]).length +
device.keyCount * 3 +
chordCount;
let current = 0; let current = 0;
syncProgress.set({ max, current }); syncProgress.set({ max, current });
function progressTick() { function progressTick() {
@@ -76,7 +78,7 @@ export async function sync() {
} }
const parsedSettings: number[] = []; const parsedSettings: number[] = [];
for (const key in settingInfo.settings) { for (const key in settingInfo["settings"]) {
try { try {
parsedSettings[Number.parseInt(key)] = await device.getSetting( parsedSettings[Number.parseInt(key)] = await device.getSetting(
Number.parseInt(key), Number.parseInt(key),
@@ -89,7 +91,7 @@ export async function sync() {
const parsedLayout: CharaLayout = [[], [], []]; const parsedLayout: CharaLayout = [[], [], []];
for (let layer = 1; layer <= 3; layer++) { for (let layer = 1; layer <= 3; layer++) {
for (let i = 0; i < device.keyCount; i++) { for (let i = 0; i < device.keyCount; i++) {
parsedLayout[layer - 1][i] = await device.getLayoutKey(layer, i); parsedLayout[layer - 1]![i] = await device.getLayoutKey(layer, i);
progressTick(); progressTick();
} }
} }

View File

@@ -48,11 +48,17 @@ export async function getViablePorts(): Promise<SerialPort[]> {
); );
} }
type LengthArray<T, N extends number, R extends T[] = []> = number extends N
? T[]
: R["length"] extends N
? R
: LengthArray<T, N, [T, ...R]>;
export async function canAutoConnect() { export async function canAutoConnect() {
return getViablePorts().then((it) => it.length === 1); return getViablePorts().then((it) => it.length === 1);
} }
function timeout<T>(promise: Promise<T>, ms: number): Promise<T> { async function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {
let timer: number; let timer: number;
return Promise.race([ return Promise.race([
promise, promise,
@@ -96,7 +102,7 @@ export class CharaDevice {
const ports = await getViablePorts(); const ports = await getViablePorts();
this.port = this.port =
!manual && ports.length === 1 !manual && ports.length === 1
? ports[0] ? ports[0]!
: await navigator.serial.requestPort({ : await navigator.serial.requestPort({
filters: [...PORT_FILTERS.values()], filters: [...PORT_FILTERS.values()],
}); });
@@ -115,9 +121,9 @@ export class CharaDevice {
await this.port.close(); await this.port.close();
this.version = new SemVer( this.version = new SemVer(
await this.send("VERSION").then(([version]) => version), await this.send(1, "VERSION").then(([version]) => version),
); );
const [company, device, chipset] = await this.send("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" | "LITE" | "X"; this.device = device as "ONE" | "LITE" | "X";
this.chipset = chipset as "M0" | "S2"; this.chipset = chipset as "M0" | "S2";
@@ -186,6 +192,7 @@ export class CharaDevice {
return it; return it;
}); });
} }
return undefined;
} }
/** /**
@@ -256,20 +263,38 @@ export class CharaDevice {
/** /**
* Send to serial port * Send to serial port
*/ */
async send(...command: string[]) { async send<T extends number>(
expectedLength: T,
...command: string[]
): Promise<LengthArray<string, T>> {
return this.runWith(async (send, read) => { return this.runWith(async (send, read) => {
await send(...command); await send(...command);
const commandString = command const commandString = command
.join(" ") .join(" ")
.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); .replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
return read().then((it) => const readResult = await read();
it.replace(new RegExp(`^${commandString} `), "").split(" "), if (readResult === undefined) {
); console.error("No response");
return Array(expectedLength).fill("NO_RESPONSE") as LengthArray<
string,
T
>;
}
const array = readResult
.replace(new RegExp(`^${commandString} `), "")
.split(" ");
if (array.length < expectedLength) {
console.error("Response too short");
return array.concat(
Array(expectedLength - array.length).fill("TOO_SHORT"),
) as LengthArray<string, T>;
}
return array as LengthArray<string, T>;
}); });
} }
async getChordCount(): Promise<number> { async getChordCount(): Promise<number> {
const [count] = await this.send("CML C0"); const [count] = await this.send(1, "CML C0");
return Number.parseInt(count); return Number.parseInt(count);
} }
@@ -277,7 +302,7 @@ export class CharaDevice {
* Retrieves a chord by index * Retrieves a chord by index
*/ */
async getChord(index: number | number[]): Promise<Chord> { async getChord(index: number | number[]): Promise<Chord> {
const [actions, phrase] = await this.send(`CML C1 ${index}`); const [actions, phrase] = await this.send(2, `CML C1 ${index}`);
return { return {
actions: parseChordActions(actions), actions: parseChordActions(actions),
phrase: parsePhrase(phrase), phrase: parsePhrase(phrase),
@@ -289,6 +314,7 @@ export class CharaDevice {
*/ */
async getChordPhrase(actions: number[]): Promise<number[] | undefined> { async getChordPhrase(actions: number[]): Promise<number[] | undefined> {
const [phrase] = await this.send( const [phrase] = await this.send(
1,
`CML C2 ${stringifyChordActions(actions)}`, `CML C2 ${stringifyChordActions(actions)}`,
); );
return phrase === "2" ? undefined : parsePhrase(phrase); return phrase === "2" ? undefined : parsePhrase(phrase);
@@ -296,6 +322,7 @@ export class CharaDevice {
async setChord(chord: Chord) { async setChord(chord: Chord) {
const [status] = await this.send( const [status] = await this.send(
1,
"CML", "CML",
"C3", "C3",
stringifyChordActions(chord.actions), stringifyChordActions(chord.actions),
@@ -306,10 +333,10 @@ export class CharaDevice {
async deleteChord(chord: Pick<Chord, "actions">) { async deleteChord(chord: Pick<Chord, "actions">) {
const status = await this.send( const status = await this.send(
1,
`CML C4 ${stringifyChordActions(chord.actions)}`, `CML C4 ${stringifyChordActions(chord.actions)}`,
); );
console.log(status); if (status?.at(-1) !== "2" && status?.at(-1) !== "0")
if (status.at(-1) !== "2" && status.at(-1) !== "0")
throw new Error(`Failed with status ${status}`); throw new Error(`Failed with status ${status}`);
} }
@@ -320,8 +347,7 @@ export class CharaDevice {
* @param action the assigned action id * @param action the assigned action id
*/ */
async setLayoutKey(layer: number, id: number, action: number) { async setLayoutKey(layer: number, id: number, action: number) {
const [status] = await this.send(`VAR B4 A${layer} ${id} ${action}`); const [status] = await this.send(1, `VAR B4 A${layer} ${id} ${action}`);
console.log(status);
if (status !== "0") throw new Error(`Failed with status ${status}`); if (status !== "0") throw new Error(`Failed with status ${status}`);
} }
@@ -332,7 +358,7 @@ export class CharaDevice {
* @returns the assigned action id * @returns the assigned action id
*/ */
async getLayoutKey(layer: number, id: number) { async getLayoutKey(layer: number, id: number) {
const [position, status] = await this.send(`VAR B3 A${layer} ${id}`); const [position, status] = await this.send(2, `VAR B3 A${layer} ${id}`);
if (status !== "0") throw new Error(`Failed with status ${status}`); if (status !== "0") throw new Error(`Failed with status ${status}`);
return Number(position); return Number(position);
} }
@@ -345,7 +371,7 @@ export class CharaDevice {
* **This does not need to be called for chords** * **This does not need to be called for chords**
*/ */
async commit() { async commit() {
const [status] = await this.send("VAR B0"); const [status] = await this.send(1, "VAR B0");
if (status !== "0") throw new Error(`Failed with status ${status}`); if (status !== "0") throw new Error(`Failed with status ${status}`);
} }
@@ -357,6 +383,7 @@ export class CharaDevice {
*/ */
async setSetting(id: number, value: number) { async setSetting(id: number, value: number) {
const [status] = await this.send( const [status] = await this.send(
1,
`VAR B2 ${id.toString(16).toUpperCase()} ${value}`, `VAR B2 ${id.toString(16).toUpperCase()} ${value}`,
); );
if (status !== "0") throw new Error(`Failed with status ${status}`); if (status !== "0") throw new Error(`Failed with status ${status}`);
@@ -367,6 +394,7 @@ export class CharaDevice {
*/ */
async getSetting(id: number): Promise<number> { async getSetting(id: number): Promise<number> {
const [value, status] = await this.send( const [value, status] = await this.send(
2,
`VAR B1 ${id.toString(16).toUpperCase()}`, `VAR B1 ${id.toString(16).toUpperCase()}`,
); );
if (status !== "0") if (status !== "0")
@@ -380,14 +408,14 @@ export class CharaDevice {
* Reboots the device * Reboots the device
*/ */
async reboot() { async reboot() {
await this.send("RST"); await this.send(0, "RST");
} }
/** /**
* Reboots the device to the bootloader * Reboots the device to the bootloader
*/ */
async bootloader() { async bootloader() {
await this.send("RST BOOTLOADER"); await this.send(0, "RST BOOTLOADER");
} }
/** /**
@@ -396,7 +424,7 @@ export class CharaDevice {
async reset( async reset(
type: "FACTORY" | "PARAMS" | "KEYMAPS" | "STARTER" | "CLEARCML" | "FUNC", type: "FACTORY" | "PARAMS" | "KEYMAPS" | "STARTER" | "CLEARCML" | "FUNC",
) { ) {
await this.send(`RST ${type}`); await this.send(0, `RST ${type}`);
} }
/** /**
@@ -405,6 +433,6 @@ export class CharaDevice {
* This is useful for debugging when there is a suspected heap or stack issue. * This is useful for debugging when there is a suspected heap or stack issue.
*/ */
async getRamBytesAvailable(): Promise<number> { async getRamBytesAvailable(): Promise<number> {
return Number(await this.send("RAM")); return Number(await this.send(1, "RAM").then(([bytes]) => bytes));
} }
} }

View File

@@ -2,7 +2,7 @@ import type { ActionInfo, KeymapCategory } from "$lib/assets/keymaps/keymap";
export interface KeyInfo extends Partial<ActionInfo> { export interface KeyInfo extends Partial<ActionInfo> {
code: number; code: number;
category: KeymapCategory; category?: KeymapCategory;
} }
export const KEYMAP_CATEGORIES = (await Promise.all( export const KEYMAP_CATEGORIES = (await Promise.all(
@@ -11,7 +11,7 @@ export const KEYMAP_CATEGORIES = (await Promise.all(
), ),
)) as KeymapCategory[]; )) as KeymapCategory[];
export const KEYMAP_CODES: Record<number, KeyInfo> = Object.fromEntries( export const KEYMAP_CODES = new Map<number, KeyInfo>(
KEYMAP_CATEGORIES.flatMap((category) => KEYMAP_CATEGORIES.flatMap((category) =>
Object.entries(category.actions).map(([code, action]) => [ Object.entries(category.actions).map(([code, action]) => [
Number(code), Number(code),
@@ -20,7 +20,7 @@ export const KEYMAP_CODES: Record<number, KeyInfo> = Object.fromEntries(
), ),
); );
export const KEYMAP_KEYCODES: Map<string, number> = new Map( export const KEYMAP_KEYCODES = new Map<string, number>(
KEYMAP_CATEGORIES.flatMap((category) => KEYMAP_CATEGORIES.flatMap((category) =>
Object.entries(category.actions).map( Object.entries(category.actions).map(
([code, action]) => [action.keyCode!, Number(code)] as const, ([code, action]) => [action.keyCode!, Number(code)] as const,
@@ -28,7 +28,7 @@ export const KEYMAP_KEYCODES: Map<string, number> = new Map(
).filter(([keyCode]) => keyCode !== undefined), ).filter(([keyCode]) => keyCode !== undefined),
); );
export const KEYMAP_IDS: Map<string, KeyInfo> = new Map( export const KEYMAP_IDS = new Map<string, KeyInfo>(
KEYMAP_CATEGORIES.flatMap((category) => KEYMAP_CATEGORIES.flatMap((category) =>
Object.entries(category.actions).map( Object.entries(category.actions).map(
([code, action]) => ([code, action]) =>

View File

@@ -14,9 +14,9 @@ export class SemVer {
console.error("Invalid version string:", versionString); console.error("Invalid version string:", versionString);
} else { } else {
const [, major, minor, patch, preRelease, meta] = result; const [, major, minor, patch, preRelease, meta] = result;
this.major = Number.parseInt(major); this.major = Number.parseInt(major ?? "NaN");
this.minor = Number.parseInt(minor); this.minor = Number.parseInt(minor ?? "NaN");
this.patch = Number.parseInt(patch); this.patch = Number.parseInt(patch ?? "NaN");
if (preRelease) this.preRelease = preRelease; if (preRelease) this.preRelease = preRelease;
if (meta) this.meta = meta; if (meta) this.meta = meta;
} }

View File

@@ -40,7 +40,6 @@ function NativeSerialPort(info: SerialPortInfo): TauriSerialPort {
} }
// @ts-expect-error polyfill // @ts-expect-error polyfill
// noinspection JSConstantReassignment
navigator.serial = { navigator.serial = {
async getPorts(): Promise<SerialPort[]> { async getPorts(): Promise<SerialPort[]> {
return invoke<any[]>("plugin:serial|get_serial_ports").then((ports) => return invoke<any[]>("plugin:serial|get_serial_ports").then((ports) =>
@@ -69,6 +68,7 @@ navigator.serial = {
props: { ports }, props: { ports },
}); });
const port = await new Promise<SerialPort>((resolve) => const port = await new Promise<SerialPort>((resolve) =>
// @ts-expect-error polyfill
dialog.$on("confirm", resolve), dialog.$on("confirm", resolve),
); );
dialog.$destroy(); dialog.$destroy();

View File

@@ -1,12 +0,0 @@
export async function updateDevice(port: SerialPort) {
await port.open({
baudRate: 115200,
dataBits: 8,
stopBits: 1,
parity: "none",
bufferSize: 255,;
})
const writer = port.writable!.getWriter()
const reader = port.readable!.getReader()
}

View File

@@ -23,9 +23,9 @@ export function compressActions(actions: number[]): Uint8Array {
export function decompressActions(raw: Uint8Array): number[] { export function decompressActions(raw: Uint8Array): number[] {
const actions: number[] = []; const actions: number[] = [];
for (let i = 0; i < raw.length; i++) { for (let i = 0; i < raw.length; i++) {
let action = raw[i]; let action = raw[i]!;
if (action > 0 && action < 32) { if (action > 0 && action < 32 && i + 1 < raw.length) {
action = (action << 8) | raw[++i]; action = (action << 8) | raw[++i]!;
} }
actions.push(action); actions.push(action);
} }

View File

@@ -16,7 +16,7 @@ export const setting: Action<
const unsubscribe = settings.subscribe(async (settings) => { const unsubscribe = settings.subscribe(async (settings) => {
if (id in settings) { if (id in settings) {
const { value, isApplied } = settings[id]; const { value, isApplied } = settings[id]!;
if (type === "number") { if (type === "number") {
node.value = ( node.value = (
inverse !== undefined inverse !== undefined

View File

@@ -10,7 +10,7 @@ export function triggerShare(event: Event) {
} }
export const share: Action<Window, (event: Event) => void> = ( export const share: Action<Window, (event: Event) => void> = (
node, _node,
callback: (event: Event) => void, callback: (event: Event) => void,
) => { ) => {
setCanShare.set(true); setCanShare.set(true);

View File

@@ -60,7 +60,7 @@ export const overlay = derived(changes, (changes) => {
for (const change of changes) { for (const change of changes) {
switch (change.type) { switch (change.type) {
case ChangeType.Layout: case ChangeType.Layout:
overlay.layout[change.layer].set(change.id, change.action); overlay.layout[change.layer]?.set(change.id, change.action);
break; break;
case ChangeType.Chord: case ChangeType.Chord:
overlay.chords.set(JSON.stringify(change.id), { overlay.chords.set(JSON.stringify(change.id), {
@@ -92,8 +92,8 @@ export const layout = derived([overlay, deviceLayout], ([overlay, layout]) =>
layout.map( layout.map(
(actions, layer) => (actions, layer) =>
actions.map<KeyInfo>((action, id) => ({ actions.map<KeyInfo>((action, id) => ({
action: overlay.layout[layer].get(id) ?? action, action: overlay.layout[layer]?.get(id) ?? action,
isApplied: !overlay.layout[layer].has(id), isApplied: !overlay.layout[layer]?.has(id),
})) as [KeyInfo, KeyInfo, KeyInfo], })) as [KeyInfo, KeyInfo, KeyInfo],
), ),
); );
@@ -118,7 +118,7 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
return { return {
id: chord.actions, id: chord.actions,
// use the old phrase for stable editing // use the old phrase for stable editing
sortBy: chord.phrase.map((it) => KEYMAP_CODES[it]?.id ?? it).join(), sortBy: chord.phrase.map((it) => KEYMAP_CODES.get(it)?.id ?? it).join(),
actions: changedChord.actions, actions: changedChord.actions,
phrase: changedChord.phrase, phrase: changedChord.phrase,
actionsChanged: id !== JSON.stringify(changedChord.actions), actionsChanged: id !== JSON.stringify(changedChord.actions),
@@ -130,7 +130,7 @@ export const chords = derived([overlay, deviceChords], ([overlay, chords]) => {
} else { } else {
return { return {
id: chord.actions, id: chord.actions,
sortBy: chord.phrase.map((it) => KEYMAP_CODES[it]?.id ?? it).join(), sortBy: chord.phrase.map((it) => KEYMAP_CODES.get(it)?.id ?? it).join(),
actions: chord.actions, actions: chord.actions,
phrase: chord.phrase, phrase: chord.phrase,
phraseChanged: false, phraseChanged: false,

View File

@@ -23,16 +23,6 @@
powerDialog = false; powerDialog = false;
} }
async function updateFirmware() {
const { usbVendorId: vendorId, usbProductId: productId } =
$serialPort!.portInfo;
$serialPort!.bootloader();
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log(
await navigator.usb.requestDevice({ filters: [{ vendorId, productId }] }),
);
}
let rebootInfo = false; let rebootInfo = false;
let terminal = false; let terminal = false;
let powerDialog = false; let powerDialog = false;

View File

@@ -32,12 +32,13 @@
} }
function redo() { function redo() {
const [change, ...queue] = redoQueue; const change = redoQueue.shift();
if (change) {
changes.update((it) => { changes.update((it) => {
it.push(change); it.push(change);
return it; return it;
}); });
redoQueue = queue; }
} }
let redoQueue: Change[] = []; let redoQueue: Change[] = [];
@@ -57,7 +58,7 @@
$LL.configure.chords.conflict.TITLE(), $LL.configure.chords.conflict.TITLE(),
$LL.configure.chords.conflict.DESCRIPTION( $LL.configure.chords.conflict.DESCRIPTION(
actions actions
.map((it) => `<kbd>${KEYMAP_CODES[it].id}</kbd>`) .map((it) => `<kbd>${KEYMAP_CODES.get(it)?.id}</kbd>`)
.join(" "), .join(" "),
), ),
$LL.configure.chords.conflict.CONFIRM(), $LL.configure.chords.conflict.CONFIRM(),

View File

@@ -68,6 +68,7 @@
{/if} {/if}
<SyncOverlay /> <SyncOverlay />
</div> </div>
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
<ul> <ul>
<li class="hide-forced-colors"> <li class="hide-forced-colors">
<input <input

View File

@@ -37,7 +37,7 @@
index.add( index.add(
i, i,
chord.phrase chord.phrase
.map((it) => KEYMAP_CODES[it]?.id) .map((it) => KEYMAP_CODES.get(it)?.id)
.filter((it) => !!it) .filter((it) => !!it)
.join(""), .join(""),
); );
@@ -51,7 +51,9 @@
function search(event: Event) { function search(event: Event) {
const query = (event.target as HTMLInputElement).value; const query = (event.target as HTMLInputElement).value;
searchFilter.set( searchFilter.set(
query && searchIndex ? searchIndex.search(query) : undefined, query && searchIndex
? (searchIndex.search(query) as number[])
: undefined,
); );
page = 0; page = 0;
} }
@@ -131,10 +133,12 @@
> >
{/if} {/if}
{#if $lastPage !== -1} {#if $lastPage !== -1}
{#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord.id))} {#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord?.id))}
{#if chord}
<tr> <tr>
<ChordEdit {chord} /> <ChordEdit {chord} />
</tr> </tr>
{/if}
{/each} {/each}
{:else} {:else}
<caption>{$LL.configure.chords.search.NO_RESULTS()}</caption> <caption>{$LL.configure.chords.search.NO_RESULTS()}</caption>

View File

@@ -51,6 +51,7 @@
}); });
return changes; return changes;
}); });
return undefined;
} }
function addSpecial(event: MouseEvent) { function addSpecial(event: MouseEvent) {

View File

@@ -59,7 +59,7 @@
onHidden(instance) { onHidden(instance) {
instance.destroy(); instance.destroy();
}, },
onDestroy(instance) { onDestroy(_instance) {
shareComponent.$destroy(); shareComponent.$destroy();
}, },
}).show(); }).show();

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
/******************************* /*******************************
* HOLD UP AND READ THIS FIRST * * HOLD UP AND READ THIS FIRST *
******************************* *******************************

View File

@@ -1,4 +1,5 @@
<script> <script>
// @ts-nocheck
let ongoingRequest; let ongoingRequest;
let resolveRequest; let resolveRequest;
let source; let source;

View File

@@ -48,18 +48,18 @@
if (next.length === 0) { if (next.length === 0) {
next = Array.from( next = Array.from(
{ length: 5 }, { length: 5 },
() => $chords[Math.floor(Math.random() * $chords.length)], () => $chords[Math.floor(Math.random() * $chords.length)]!,
); );
} else { } else {
next.shift(); next.shift();
next.push($chords[Math.floor(Math.random() * $chords.length)]); next.push($chords[Math.floor(Math.random() * $chords.length)]!);
next = next; next = next;
} }
} }
if ( if (
userInput === userInput ===
next[0].phrase next[0]!.phrase
.map((it) => (it === 32 ? " " : KEYMAP_CODES[it]!.id)) .map((it) => (it === 32 ? " " : KEYMAP_CODES.get(it)!.id))
.join("") + .join("") +
" " " "
) { ) {

View File

@@ -69,7 +69,7 @@ for (const icon of icons) {
.flatMap((it) => [...it]) .flatMap((it) => [...it])
.map((it) => it.codePointAt(0).toString(16)); .map((it) => it.codePointAt(0).toString(16));
const codePoint = config.codePoints[icon]; const codePoint = config.codePoints[icon ?? ""];
if (codePoint) { if (codePoint) {
glyphs.push(codePoint); glyphs.push(codePoint);
} else if (codePoints.length === 0) { } else if (codePoints.length === 0) {

View File

@@ -4,6 +4,14 @@
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,

View File

@@ -16,11 +16,11 @@ const { homepage, bugs, repository } = JSON.parse(
), ),
); );
process.env.VITE_HOMEPAGE_URL = repository.url.replace(/\.git$/, ""); 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.3"; process.env["VITE_LATEST_FIRMWARE"] = "1.1.3";
export default defineConfig({ export default defineConfig({
build: { build: {