4 Commits

Author SHA1 Message Date
John de St Germain
29756834f8 Fix misspelling 2024-05-13 09:20:46 -05:00
3dd91a1cea 1.5.1 2024-04-29 11:19:37 +02:00
cbcf705f71 feat: massively improved chord search
fixes #119
2024-04-29 11:18:23 +02:00
4007810c7b fix: can't edit blank actions
fixes #110
2024-04-29 09:35:22 +02:00
9 changed files with 100 additions and 27 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "charachorder-device-manager", "name": "charachorder-device-manager",
"version": "1.5.0", "version": "1.5.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "charachorder-device-manager", "name": "charachorder-device-manager",
"version": "1.5.0", "version": "1.5.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"@codemirror/autocomplete": "^6.15.0", "@codemirror/autocomplete": "^6.15.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "charachorder-device-manager", "name": "charachorder-device-manager",
"version": "1.5.0", "version": "1.5.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"private": true, "private": true,
"repository": { "repository": {

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "app" name = "app"
version = "1.5.0" 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.0" }, "package": { "productName": "amacc1ng", "version": "1.5.1" },
"tauri": { "tauri": {
"allowlist": { "all": false }, "allowlist": { "all": false },
"bundle": { "bundle": {

View File

@@ -1,6 +1,9 @@
name: CharaChorder name: CharaChorder
description: CharaChorder specific actions description: CharaChorder specific actions
actions: actions:
0:
id: "NO_ACTION"
display: "No Action"
528: 528:
id: "RESTART" id: "RESTART"
title: Restart Device title: Restart Device
@@ -58,6 +61,7 @@ actions:
544: 544:
variantOf: 36 variantOf: 36
id: "SPACERIGHT" id: "SPACERIGHT"
display: " "
title: Right Spacebar (eg CC Lite) title: Right Spacebar (eg CC Lite)
icon: space_bar icon: space_bar
variant: right variant: right

View File

@@ -17,7 +17,7 @@
"You can use 'cursor warping' by adding arrow key actions to a chord, for example to chord '()' with the cursor in the middle of the brackets", "You can use 'cursor warping' by adding arrow key actions to a chord, for example to chord '()' with the cursor in the middle of the brackets",
"An arpeggiate is a single key press that modifies the preceding chord. Modifiers can be arpeggiated", "An arpeggiate is a single key press that modifies the preceding chord. Modifiers can be arpeggiated",
"Some actions are marked as a 'macro', which means the output is generated by a key sequence rather than a pure key press. Be cautious with those, as they can affect other keys when held together!", "Some actions are marked as a 'macro', which means the output is generated by a key sequence rather than a pure key press. Be cautious with those, as they can affect other keys when held together!",
"Spurring is a chording only mode which is more advanced, but can greatly imporve typing speed when mastered", "Spurring is a chording only mode which is more advanced, but can greatly improve typing speed when mastered",
"The forced chord phenomenon is when typing a word character by character starts to feel unnatural", "The forced chord phenomenon is when typing a word character by character starts to feel unnatural",
"Don't be afraid to delete chords you keep getting wrong", "Don't be afraid to delete chords you keep getting wrong",
"Most people find it easier to start their chord library from scratch rather than learning someone else's", "Most people find it easier to start their chord library from scratch rather than learning someone else's",
@@ -32,5 +32,7 @@
"You can use Nexus to track words you might want to add to your chord library", "You can use Nexus to track words you might want to add to your chord library",
"The CC1 default layout was 80% science, 20% art", "The CC1 default layout was 80% science, 20% art",
"There is little to no reason to use hjkl in VIM on a CC1 since the arrows keys are so close already", "There is little to no reason to use hjkl in VIM on a CC1 since the arrows keys are so close already",
"The device manager automatically creates a backup for you when you reboot your device into the bootloader" "The device manager automatically creates a backup for you when you reboot your device into the bootloader",
"You can use \"compound\", \"macro\", \"suffix\" and \"cursor warp\" in the chord search to find specific types of chords",
"You can search for chord inputs by using a leading \"+\", for example \"+a +DUP\" will show all chords with inputs that contain both a and DUP"
] ]

View File

@@ -15,7 +15,8 @@
$: dynamicMapping = info.keyCode && $osLayout.get(info.keyCode); $: dynamicMapping = info.keyCode && $osLayout.get(info.keyCode);
$: tooltip = $: tooltip =
(info.title ?? info.id ?? `0x${info.code.toString(16)}`) + `&lt;${info.id ?? `0x${info.code.toString(16)}`}&gt; ` +
(info.title ?? "") +
(info.variant === "left" (info.variant === "left"
? " (left)" ? " (left)"
: info.variant === "right" : info.variant === "right"

View File

@@ -127,12 +127,11 @@
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: {
currentAction, currentAction,
nextAction: nextAction.isApplied ? undefined : nextAction.action, nextAction: nextAction?.isApplied ? undefined : nextAction?.action,
}, },
}); });
const dialog = document.querySelector("dialog > div") as HTMLDivElement; const dialog = document.querySelector("dialog > div") as HTMLDivElement;

View File

@@ -1,5 +1,5 @@
<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";
@@ -35,7 +35,7 @@
resizeObserver?.disconnect(); resizeObserver?.disconnect();
}); });
let index = new FlexSearch.Index({ tokenize: "full" }); let index = new FlexSearch.Index();
let searchIndex = writable<FlexSearch.Index | undefined>(undefined); let searchIndex = writable<FlexSearch.Index | undefined>(undefined);
$: { $: {
abortIndexing?.(); abortIndexing?.();
@@ -43,22 +43,72 @@
buildIndex($chords, $osLayout).then(searchIndex.set); buildIndex($chords, $osLayout).then(searchIndex.set);
} }
function plainPhrase(phrase: number[], osLayout: Map<string, string>) { function encodeChord(chord: ChordInfo, osLayout: Map<string, string>) {
return phrase const plainPhrase: string[] = [""];
const extraActions: string[] = [];
const extraCodes: string[] = [];
for (const actionCode of chord.phrase ?? []) {
const action = KEYMAP_CODES.get(actionCode);
if (!action) {
extraCodes.push(`0x${actionCode.toString(16)}`);
continue;
}
const osCode = action.keyCode && osLayout.get(action.keyCode);
const token = osCode?.length === 1 ? osCode : action.display || action.id;
if (!token) {
extraCodes.push(`0x${action.code.toString(16)}`);
continue;
}
if (/^\s$/.test(token) && plainPhrase.at(-1) !== "") {
plainPhrase.push("");
} else if (token.length === 1) {
plainPhrase[plainPhrase.length - 1] =
plainPhrase[plainPhrase.length - 1] + token;
} else {
extraActions.push(token);
}
}
if (chord.phrase?.[0] === 298) {
plainPhrase.push("suffix");
}
if (
["ARROW_LT", "ARROW_RT", "ARROW_UP", "ARROW_DN"].some((it) =>
extraActions.includes(it),
)
) {
plainPhrase.push("cursor warp");
}
if (
["CTRL", "ALT", "GUI", "ENTER", "TAB"].some((it) =>
extraActions.includes(it),
)
) {
plainPhrase.push("macro");
}
if (chord.actions[0] !== 0) {
plainPhrase.push("compound");
}
const input = chord.actions
.slice(chord.actions.lastIndexOf(0) + 1)
.map((it) => { .map((it) => {
const info = KEYMAP_CODES.get(it); const info = KEYMAP_CODES.get(it);
if (!info) return ""; if (!info) return `0x${it.toString(16)}`;
const osCode = info.keyCode && osLayout.get(info.keyCode);
const result = osCode?.length === 1 ? osCode : info.id;
return result ?? `0x${it.toString(16)}`;
});
const bestGuess = return [
(info.keyCode && osLayout.get(info.keyCode)) || ...plainPhrase,
info.display || `+${input.join("+")}`,
info.id || ...new Set(extraActions),
""; ...new Set(extraCodes),
].join(" ");
return bestGuess.length === 1 ? bestGuess : "";
})
.filter((it) => !!it)
.join("");
} }
async function buildIndex( async function buildIndex(
@@ -66,7 +116,23 @@
osLayout: Map<string, string>, osLayout: Map<string, string>,
): Promise<FlexSearch.Index> { ): Promise<FlexSearch.Index> {
if (chords.length === 0 || !browser) return index; if (chords.length === 0 || !browser) return index;
index = new FlexSearch.Index({ tokenize: "full" }); index = new FlexSearch.Index({
tokenize: "full",
encode(phrase: string) {
return phrase.split(/\s+/).flatMap((it) => {
if (/^[A-Z_]+$/.test(it)) {
return it;
}
if (it.startsWith("+")) {
return it
.slice(1)
.split("+")
.map((it) => `+${it}`);
}
return it.toLowerCase();
});
},
});
let abort = false; let abort = false;
abortIndexing = () => { abortIndexing = () => {
abort = true; abort = true;
@@ -78,7 +144,8 @@
progress = i; progress = i;
if ("phrase" in chord) { if ("phrase" in chord) {
await index.addAsync(i, plainPhrase(chord.phrase, osLayout)); console.log(encodeChord(chord, osLayout));
await index.addAsync(i, encodeChord(chord, osLayout));
} }
} }
return index; return index;