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",
"version": "1.5.0",
"version": "1.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "charachorder-device-manager",
"version": "1.5.0",
"version": "1.5.1",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@codemirror/autocomplete": "^6.15.0",

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
name: CharaChorder
description: CharaChorder specific actions
actions:
0:
id: "NO_ACTION"
display: "No Action"
528:
id: "RESTART"
title: Restart Device
@@ -58,6 +61,7 @@ actions:
544:
variantOf: 36
id: "SPACERIGHT"
display: " "
title: Right Spacebar (eg CC Lite)
icon: space_bar
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",
"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!",
"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",
"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",
@@ -32,5 +32,7 @@
"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",
"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);
$: tooltip =
(info.title ?? info.id ?? `0x${info.code.toString(16)}`) +
`&lt;${info.id ?? `0x${info.code.toString(16)}`}&gt; ` +
(info.title ?? "") +
(info.variant === "left"
? " (left)"
: info.variant === "right"

View File

@@ -127,12 +127,11 @@
const clickedGroup = groupParent.children.item(index) as SVGGElement;
const nextAction = get(layout)[get(activeLayer)]?.[keyInfo.id];
const currentAction = get(deviceLayout)[get(activeLayer)]?.[keyInfo.id];
if (!nextAction || !currentAction) return;
const component = new ActionSelector({
target: document.body,
props: {
currentAction,
nextAction: nextAction.isApplied ? undefined : nextAction.action,
nextAction: nextAction?.isApplied ? undefined : nextAction?.action,
},
});
const dialog = document.querySelector("dialog > div") as HTMLDivElement;

View File

@@ -1,5 +1,5 @@
<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 { action } from "$lib/title";
@@ -35,7 +35,7 @@
resizeObserver?.disconnect();
});
let index = new FlexSearch.Index({ tokenize: "full" });
let index = new FlexSearch.Index();
let searchIndex = writable<FlexSearch.Index | undefined>(undefined);
$: {
abortIndexing?.();
@@ -43,22 +43,72 @@
buildIndex($chords, $osLayout).then(searchIndex.set);
}
function plainPhrase(phrase: number[], osLayout: Map<string, string>) {
return phrase
function encodeChord(chord: ChordInfo, osLayout: Map<string, string>) {
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) => {
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 =
(info.keyCode && osLayout.get(info.keyCode)) ||
info.display ||
info.id ||
"";
return bestGuess.length === 1 ? bestGuess : "";
})
.filter((it) => !!it)
.join("");
return [
...plainPhrase,
`+${input.join("+")}`,
...new Set(extraActions),
...new Set(extraCodes),
].join(" ");
}
async function buildIndex(
@@ -66,7 +116,23 @@
osLayout: Map<string, string>,
): Promise<FlexSearch.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;
abortIndexing = () => {
abort = true;
@@ -78,7 +144,8 @@
progress = i;
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;