From b7bfe5add7e7ff232da43d4ae6bc677b423259b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Fri, 30 Jan 2026 17:04:36 +0100 Subject: [PATCH] feat: changes --- src/lib/chord-editor/action-plugin.ts | 21 ++++------ src/lib/chord-editor/action-tooltip.ts | 41 +++++++++++++++++++ src/lib/chord-editor/parse-meta.ts | 22 +++++++--- .../chord-editor/persistent-state-plugin.ts | 2 + src/lib/components/Action.svelte | 35 +++------------- .../components/action/ActionTooltip.svelte | 34 +++++++++++++++ src/lib/serial/device.ts | 5 +-- src/routes/(app)/Footer.svelte | 3 ++ 8 files changed, 113 insertions(+), 50 deletions(-) create mode 100644 src/lib/chord-editor/action-tooltip.ts create mode 100644 src/lib/components/action/ActionTooltip.svelte diff --git a/src/lib/chord-editor/action-plugin.ts b/src/lib/chord-editor/action-plugin.ts index b2377fcb..5811ccdc 100644 --- a/src/lib/chord-editor/action-plugin.ts +++ b/src/lib/chord-editor/action-plugin.ts @@ -10,19 +10,15 @@ import Action from "$lib/components/Action.svelte"; import type { Range } from "@codemirror/state"; import { parsedChordsField } from "./parsed-chords-plugin"; import { iterActions } from "./parse-meta"; +import type { KeyInfo } from "$lib/serial/keymap-codes"; export class ActionWidget extends WidgetType { component?: {}; - constructor(readonly id: string | number) { + constructor(readonly info: KeyInfo) { super(); - this.id = id; } - /*override eq(other: ActionWidget) { - return this.id == other.id; - }*/ - toDOM() { if (this.component) { unmount(this.component); @@ -32,15 +28,16 @@ export class ActionWidget extends WidgetType { this.component = mount(Action, { target: element, - props: { action: this.id, display: "keys", inText: true }, + props: { + action: this.info, + display: "keys", + inText: true, + withPopover: false, + }, }); return element; } - override ignoreEvent() { - return true; - } - override destroy() { if (this.component) { unmount(this.component); @@ -63,7 +60,7 @@ function actionWidgets(view: EditorView) { } if (action.info && action.explicit) { const deco = Decoration.replace({ - widget: new ActionWidget(action.code), + widget: new ActionWidget(action.info), }); widgets.push(deco.range(action.range[0], action.range[1])); } diff --git a/src/lib/chord-editor/action-tooltip.ts b/src/lib/chord-editor/action-tooltip.ts new file mode 100644 index 00000000..b82cba22 --- /dev/null +++ b/src/lib/chord-editor/action-tooltip.ts @@ -0,0 +1,41 @@ +import { hoverTooltip } from "@codemirror/view"; +import { parsedChordsField } from "./parsed-chords-plugin"; +import { type ActionMeta, iterActions } from "./parse-meta"; +import { mount, unmount } from "svelte"; +import ActionTooltip from "$lib/components/action/ActionTooltip.svelte"; + +function inRange(pos: number, side: 1 | -1, range: [number, number]) { + if (side < 0) { + return pos > range[0] && pos <= range[1]; + } else { + return pos >= range[0] && pos < range[1]; + } +} + +export const actionHover = hoverTooltip((view, pos, side) => { + const chord = view.state + .field(parsedChordsField) + .chords.find((chord) => inRange(pos, side, chord.range)); + if (!chord) return null; + let action = iterActions(chord, (action) => + inRange(pos, side, action.range) ? action : undefined, + ); + if (!action?.info) return null; + return { + pos: action.range[0], + end: action.range[1], + create() { + const dom = document.createElement("div"); + const element = mount(ActionTooltip, { + target: dom, + props: { info: action.info, valid: true }, + }); + return { + dom, + destroy() { + unmount(element); + }, + }; + }, + }; +}); diff --git a/src/lib/chord-editor/parse-meta.ts b/src/lib/chord-editor/parse-meta.ts index 3f503113..c8391057 100644 --- a/src/lib/chord-editor/parse-meta.ts +++ b/src/lib/chord-editor/parse-meta.ts @@ -152,25 +152,35 @@ export function mapParseResult( }; } -export function iterActions( +export function iterActions( chord: ChordMeta, - callback: (action: ActionMeta) => void, -) { + callback: (action: ActionMeta) => T | void, +): T | undefined { if (chord.input) { for (const action of chord.input.actions) { - callback(action); + const result = callback(action); + if (result !== undefined) { + return result; + } } } if (chord.compounds) { for (const compound of chord.compounds) { for (const action of compound.actions) { - callback(action); + const result = callback(action); + if (result !== undefined) { + return result; + } } } } if (chord.phrase) { for (const action of chord.phrase.actions) { - callback(action); + const result = callback(action); + if (result !== undefined) { + return result; + } } } + return undefined; } diff --git a/src/lib/chord-editor/persistent-state-plugin.ts b/src/lib/chord-editor/persistent-state-plugin.ts index 1af0660a..fd821110 100644 --- a/src/lib/chord-editor/persistent-state-plugin.ts +++ b/src/lib/chord-editor/persistent-state-plugin.ts @@ -29,6 +29,7 @@ import { actionMetaPlugin } from "./action-meta-plugin"; import { parsedChordsField } from "./parsed-chords-plugin"; import { changesPanel } from "./changes-panel.svelte"; import { searchKeymap } from "@codemirror/search"; +import { actionHover } from "./action-tooltip"; const serializedFields = { history: historyField, @@ -47,6 +48,7 @@ export function createConfig(params: EditorConfig) { actionMetaPlugin.plugin, deviceChordField, parsedChordsField, + actionHover, changesPanel(), lintGutter(), params.rawCode ? [lineNumbers()] : [delimPlugin, actionPlugin], diff --git a/src/lib/components/Action.svelte b/src/lib/components/Action.svelte index 9094d010..edd943b0 100644 --- a/src/lib/components/Action.svelte +++ b/src/lib/components/Action.svelte @@ -4,17 +4,20 @@ import { osLayout } from "$lib/os-layout"; import { isVerbose } from "./verbose-action"; import { actionTooltip } from "$lib/title"; + import ActionTooltip from "./action/ActionTooltip.svelte"; let { action, display, ignoreIcon = false, inText = false, + withPopover = true, }: { action: string | number | KeyInfo; display: "inline-keys" | "keys" | "verbose"; ignoreIcon?: boolean; inText?: boolean; + withPopover?: boolean; } = $props(); let retrievedInfo = $derived( @@ -35,39 +38,13 @@ let icon = $derived(ignoreIcon ? undefined : info.icon); let dynamicMapping = $derived(info.keyCode && $osLayout.get(info.keyCode)); let hasPopover = $derived( - !retrievedInfo || !info.id || info.title || info.description, + withPopover && + (!retrievedInfo || !info.id || info.title || info.description), ); {#snippet popover()} - {#if retrievedInfo} - {#if info.icon || info.display || !info.id} - <{info.id ?? `0x${info.code.toString(16)}`}> - {/if} - {#if info.title} - {info.title} - {/if} - {#if info.variant === "left"} - (Left) - {:else if info.variant === "right"} - (Right) - {/if} - {#if info.description} -
- {info.description} - {/if} - {#if info.breaking} -
 Prevents prepended autospaces - {/if} - {#if info.separator || info.breaking} -
 Stops autocorrect - {/if} - {:else} - Unknown Action
- {#if info.code > 1023} - This action cannot be translated and will be ingored. - {/if} - {/if} + {/snippet} {#snippet kbdText()} diff --git a/src/lib/components/action/ActionTooltip.svelte b/src/lib/components/action/ActionTooltip.svelte new file mode 100644 index 00000000..b6e97915 --- /dev/null +++ b/src/lib/components/action/ActionTooltip.svelte @@ -0,0 +1,34 @@ + + +{#if valid} + {#if info.icon || info.display || !info.id} + <{info.id ?? `0x${info.code.toString(16)}`}> + {/if} + {#if info.title} + {info.title} + {/if} + {#if info.variant === "left"} + (Left) + {:else if info.variant === "right"} + (Right) + {/if} + {#if info.description} +
+ {info.description} + {/if} + {#if info.breaking} +
 Prevents prepended autospaces + {/if} + {#if info.separator || info.breaking} +
 Stops autocorrect + {/if} +{:else} + Unknown Action
+ {#if info.code > 1023} + This action cannot be translated and will be ingored. + {/if} +{/if} diff --git a/src/lib/serial/device.ts b/src/lib/serial/device.ts index 9f77419f..a6f7deb0 100644 --- a/src/lib/serial/device.ts +++ b/src/lib/serial/device.ts @@ -142,7 +142,7 @@ export class CharaDevice { constructor( readonly port: SerialPortLike, - public baudRate = 115200, + public baudRate = navigator.userAgent.includes("Mac") ? 38400 : 115200, ) {} async init() { @@ -605,9 +605,8 @@ export class CharaDevice { }); } finally { writer.releaseLock(); + await this.suspend(); } - - await this.suspend(); } finally { delete this.lock; resolveLock!(true); diff --git a/src/routes/(app)/Footer.svelte b/src/routes/(app)/Footer.svelte index 40313265..23ea248d 100644 --- a/src/routes/(app)/Footer.svelte +++ b/src/routes/(app)/Footer.svelte @@ -15,6 +15,7 @@ } from "$lib/serial/connection"; import { fade, slide } from "svelte/transition"; import ConnectPopup from "./ConnectPopup.svelte"; + import { goto } from "$app/navigation"; let locale = $state( (browser && (localStorage.getItem("locale") as Locales)) || detectLocale(), @@ -49,6 +50,8 @@ function disconnect(event: MouseEvent) { if (event.shiftKey) { sync(); + } else if (event.altKey) { + goto("/terminal/"); } else { $serialPort?.close(); $serialPort = undefined;