mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2025-12-10 21:06:17 +00:00
Compare commits
3 Commits
f2a18cafe8
...
048dee0a6d
| Author | SHA1 | Date | |
|---|---|---|---|
|
048dee0a6d
|
|||
|
977bdf3043
|
|||
|
9ca30f412e
|
54
package.json
54
package.json
@@ -36,61 +36,61 @@
|
||||
"devDependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/commands": "^6.8.1",
|
||||
"@codemirror/lang-javascript": "^6.2.3",
|
||||
"@codemirror/language": "^6.11.0",
|
||||
"@codemirror/lang-javascript": "^6.2.4",
|
||||
"@codemirror/language": "^6.11.2",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.36.5",
|
||||
"@fontsource-variable/material-symbols-rounded": "^5.2.8",
|
||||
"@fontsource-variable/noto-sans-mono": "^5.2.6",
|
||||
"@codemirror/view": "^6.38.1",
|
||||
"@fontsource-variable/material-symbols-rounded": "^5.2.17",
|
||||
"@fontsource-variable/noto-sans-mono": "^5.2.7",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@material/material-color-utilities": "^0.3.0",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.86.6",
|
||||
"@modyfi/vite-plugin-yaml": "^1.1.1",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.20.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@sveltejs/kit": "^2.26.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.1.0",
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"@tauri-apps/cli": "^1.6.0",
|
||||
"@types/dom-view-transitions": "^1.0.6",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/w3c-web-serial": "^1.0.8",
|
||||
"@types/w3c-web-usb": "^1.0.10",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@types/wicg-file-system-access": "^2023.10.6",
|
||||
"@vite-pwa/sveltekit": "^1.0.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"codemirror": "^6.0.1",
|
||||
"cypress": "^14.2.1",
|
||||
"codemirror": "^6.0.2",
|
||||
"cypress": "^14.5.3",
|
||||
"d3": "^7.9.0",
|
||||
"esptool-js": "^0.5.4",
|
||||
"flexsearch": "^0.8.147",
|
||||
"esptool-js": "^0.5.6",
|
||||
"flexsearch": "^0.8.205",
|
||||
"fontkit": "^2.0.4",
|
||||
"glob": "^11.0.1",
|
||||
"jsdom": "^26.0.0",
|
||||
"matrix-js-sdk": "^37.2.0",
|
||||
"glob": "^11.0.3",
|
||||
"jsdom": "^26.1.0",
|
||||
"matrix-js-sdk": "^37.12.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"rxjs": "^7.8.2",
|
||||
"sass": "^1.86.0",
|
||||
"sass": "^1.89.2",
|
||||
"semver": "^7.7.2",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"stylelint": "^16.17.0",
|
||||
"stylelint": "^16.23.0",
|
||||
"stylelint-config-clean-order": "^7.0.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-prettier-scss": "^1.0.0",
|
||||
"stylelint-config-recommended-scss": "^14.1.0",
|
||||
"stylelint-config-standard-scss": "^14.0.0",
|
||||
"svelte": "5.25.3",
|
||||
"svelte-check": "^4.1.5",
|
||||
"stylelint-config-recommended-scss": "^15.0.1",
|
||||
"stylelint-config-standard-scss": "^15.0.1",
|
||||
"svelte": "5.37.1",
|
||||
"svelte-check": "^4.3.0",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"typesafe-i18n": "^5.26.2",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-mkcert": "^1.17.8",
|
||||
"vite-plugin-pwa": "^1.0.0",
|
||||
"vitest": "^3.1.1",
|
||||
"vite-plugin-pwa": "^1.0.2",
|
||||
"vitest": "^3.2.4",
|
||||
"web-serial-polyfill": "^1.0.15",
|
||||
"workbox-window": "^7.3.0"
|
||||
},
|
||||
|
||||
1323
pnpm-lock.yaml
generated
1323
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { KEYMAP_CODES } from "$lib/serial/keymap-codes";
|
||||
import type { KeyInfo } from "$lib/serial/keymap-codes";
|
||||
import { action as title } from "$lib/title";
|
||||
import { osLayout } from "$lib/os-layout";
|
||||
import { tooltip } from "$lib/hover-popover";
|
||||
|
||||
let {
|
||||
action,
|
||||
@@ -16,42 +16,49 @@
|
||||
);
|
||||
let dynamicMapping = $derived(info.keyCode && $osLayout.get(info.keyCode));
|
||||
|
||||
let tooltip = $derived(
|
||||
`<${info.id ?? `0x${info.code.toString(16)}`}> ` +
|
||||
(info.title ?? "") +
|
||||
(info.variant === "left"
|
||||
? " (left)"
|
||||
: info.variant === "right"
|
||||
? " (right)"
|
||||
: ""),
|
||||
);
|
||||
let popover: HTMLElement | undefined = $state(undefined);
|
||||
</script>
|
||||
|
||||
{#snippet popoverSnippet()}
|
||||
<div bind:this={popover} popover="hint">
|
||||
<{info.id ?? `0x${info.code.toString(16)}`}>
|
||||
{#if info.title}
|
||||
{info.title}
|
||||
{/if}
|
||||
{#if info.variant === "left"}
|
||||
(Left)
|
||||
{:else if info.variant === "right"}
|
||||
(Right)
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#if display === "keys"}
|
||||
<kbd
|
||||
class:icon={!!info.icon}
|
||||
class:left={info.variant === "left"}
|
||||
class:right={info.variant === "right"}
|
||||
use:title={{ title: tooltip }}
|
||||
{@attach tooltip(popover)}
|
||||
>
|
||||
{dynamicMapping ??
|
||||
info.icon ??
|
||||
info.display ??
|
||||
info.id ??
|
||||
`0x${info.code.toString(16)}`}
|
||||
{@render popoverSnippet()}
|
||||
</kbd>
|
||||
{:else if display === "inline-keys"}
|
||||
{#if !info.icon && dynamicMapping?.length === 1}
|
||||
<span
|
||||
use:title={{ title: tooltip }}
|
||||
{@attach tooltip(popover)}
|
||||
class:left={info.variant === "left"}
|
||||
class:right={info.variant === "right"}>{dynamicMapping}</span
|
||||
class:right={info.variant === "right"}>{dynamicMapping}{@render popoverSnippet()}</span
|
||||
>
|
||||
{:else if !info.icon && info.id?.length === 1}
|
||||
<span
|
||||
use:title={{ title: tooltip }}
|
||||
{@attach tooltip(popover)}
|
||||
class:left={info.variant === "left"}
|
||||
class:right={info.variant === "right"}>{info.id}</span
|
||||
class:right={info.variant === "right"}>{info.id}{@render popoverSnippet()}</span
|
||||
>
|
||||
{:else}
|
||||
<kbd
|
||||
@@ -59,13 +66,13 @@
|
||||
class:left={info.variant === "left"}
|
||||
class:right={info.variant === "right"}
|
||||
class:icon={!!info.icon}
|
||||
use:title={{ title: tooltip }}
|
||||
{@attach tooltip(popover)}
|
||||
>
|
||||
{dynamicMapping ??
|
||||
info.icon ??
|
||||
info.display ??
|
||||
info.id ??
|
||||
`0x${info.code.toString(16)}`}</kbd
|
||||
`0x${info.code.toString(16)}`}{@render popoverSnippet()}</kbd
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
39
src/lib/hover-popover.ts
Normal file
39
src/lib/hover-popover.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { Attachment } from "svelte/attachments";
|
||||
|
||||
export const hotkeys = new Map<string, HTMLElement>();
|
||||
|
||||
export function tooltip(
|
||||
target: HTMLElement | undefined,
|
||||
shortcut?: string,
|
||||
): Attachment<HTMLElement> {
|
||||
return (node: HTMLElement) => {
|
||||
function show() {
|
||||
if (!target) return;
|
||||
target.showPopover({ source: node });
|
||||
}
|
||||
function hide() {
|
||||
if (!target) return;
|
||||
target.hidePopover();
|
||||
}
|
||||
|
||||
node.addEventListener("mouseenter", show);
|
||||
node.addEventListener("focus", show);
|
||||
node.addEventListener("mouseout", hide);
|
||||
node.addEventListener("blur", hide);
|
||||
|
||||
if (shortcut && node instanceof HTMLElement) {
|
||||
hotkeys.set(shortcut, node);
|
||||
}
|
||||
|
||||
return () => {
|
||||
node.removeEventListener("mouseenter", show);
|
||||
node.removeEventListener("focus", show);
|
||||
node.removeEventListener("mouseout", hide);
|
||||
node.removeEventListener("blur", hide);
|
||||
|
||||
if (shortcut && node instanceof HTMLElement) {
|
||||
hotkeys.delete(shortcut);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -69,5 +69,8 @@ export function hashChord(actions: number[]) {
|
||||
for (let i = 0; i < 16; i++) {
|
||||
hash = Math.imul(hash ^ view.getUint8(i), 16777619);
|
||||
}
|
||||
if ((hash & 0xff) === 0xff) {
|
||||
hash ^= 0xff;
|
||||
}
|
||||
return hash & 0x3fff_ffff;
|
||||
}
|
||||
|
||||
67
src/lib/style/elements/_popover.scss
Normal file
67
src/lib/style/elements/_popover.scss
Normal file
@@ -0,0 +1,67 @@
|
||||
$animation-duration: 150ms;
|
||||
$translate: translateY(8px);
|
||||
|
||||
[popover] {
|
||||
position: absolute;
|
||||
inset: unset;
|
||||
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--md-sys-color-outline);
|
||||
border-radius: 8px;
|
||||
|
||||
font-family: "Noto Sans Mono", monospace;
|
||||
font-size: initial;
|
||||
font-weight: initial;
|
||||
color: var(--md-sys-color-on-surface);
|
||||
|
||||
opacity: 0;
|
||||
background: var(--md-sys-color-surface);
|
||||
|
||||
transition:
|
||||
transform $animation-duration ease,
|
||||
opacity $animation-duration linear,
|
||||
overlay $animation-duration allow-discrete,
|
||||
display $animation-duration allow-discrete;
|
||||
|
||||
position-area: bottom span-all;
|
||||
position-try-fallbacks:
|
||||
top span-all,
|
||||
bottom span-right,
|
||||
top span-right,
|
||||
bottom span-left,
|
||||
top span-left;
|
||||
|
||||
position-visibility: no-overflow;
|
||||
|
||||
&:popover-open {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> h1:first-child,
|
||||
h2:first-child,
|
||||
h3:first-child {
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
[popover="auto"] {
|
||||
transform: $translate;
|
||||
}
|
||||
|
||||
[popover="hint"] {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
[popover]:popover-open {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[popover="auto"] {
|
||||
transform: $translate;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
@use "print";
|
||||
|
||||
@use "elements/h1";
|
||||
@use "elements/popover";
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
|
||||
@@ -5,6 +5,9 @@ import Tooltip from "$lib/components/Tooltip.svelte";
|
||||
|
||||
export const hotkeys = new Map<string, HTMLElement>();
|
||||
|
||||
/**
|
||||
* @deprecated Use `tooltip` instead.
|
||||
*/
|
||||
export const action: Action<Element, { title?: string; shortcut?: string }> = (
|
||||
node: Element,
|
||||
{ title, shortcut },
|
||||
|
||||
@@ -183,7 +183,13 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="wrapper" class:edited={!chord.deleted && chord.phraseChanged}>
|
||||
<div
|
||||
class="wrapper"
|
||||
class:edited={!chord.deleted && chord.phraseChanged}
|
||||
onclick={() => {
|
||||
box.focus();
|
||||
}}
|
||||
>
|
||||
{#if supportsAutospace}
|
||||
<label
|
||||
class="auto-space-edit"
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { serializeActions } from "$lib/serial/chord";
|
||||
import { chords } from "$lib/undo-redo";
|
||||
import ChordEdit from "../ChordEdit.svelte";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const broken = $derived(
|
||||
$chords.filter((it) => (hashChord(it.actions) & 0xff) === 0xff),
|
||||
);
|
||||
</script>
|
||||
|
||||
<h1>Will my compound break</h1>
|
||||
<p>
|
||||
Pre-2.2.0 there was a bug where creating a compound with specific chords as a
|
||||
base could corrupt your library.
|
||||
</p>
|
||||
|
||||
{#if broken.length > 0}
|
||||
<p class="warning">Chords have been detected.</p>
|
||||
<p>
|
||||
If you have ever tried to create a compound chord with <b class="warning"
|
||||
>any of these as a base</b
|
||||
>, your library might have been corrupted.
|
||||
</p>
|
||||
{#each broken as chord}
|
||||
<ChordEdit {chord} onduplicate={() => {}} />
|
||||
{/each}
|
||||
{:else}
|
||||
<p>No problematic chords found</p>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.warning {
|
||||
color: var(--md-sys-color-error);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chord {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.compound {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
||||
@@ -161,7 +161,10 @@
|
||||
><span class="icon">reset_settings</span>Reset Settings</button
|
||||
>
|
||||
{/if}
|
||||
<button use:popup={ResetPopup}>Recovery...</button>
|
||||
<button popovertarget="reset-device" popovertargetaction="toggle"
|
||||
>Recovery...</button
|
||||
>
|
||||
<div id="reset-device" popover="auto"><ResetPopup /></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { serialPort } from "$lib/serial/connection";
|
||||
|
||||
let { challenge, onconfirm }: { challenge: string; onconfirm: () => void } =
|
||||
$props();
|
||||
|
||||
let challengeInput = $state("");
|
||||
let challengeString = $derived(`${challenge} ${$serialPort!.device}`);
|
||||
let isValid = $derived(challengeInput === challengeString);
|
||||
</script>
|
||||
|
||||
<h3>Type the following to confirm the action</h3>
|
||||
|
||||
<p>{challengeString}</p>
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
type="text"
|
||||
bind:value={challengeInput}
|
||||
placeholder={challengeString}
|
||||
/>
|
||||
|
||||
<button disabled={!isValid} onclick={onconfirm}>Confirm {challenge}</button>
|
||||
|
||||
<style lang="scss">
|
||||
input[type="text"] {
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid currentcolor;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--md-sys-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
color: var(--md-sys-color-error);
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { confirmChallenge } from "./confirm-challenge";
|
||||
import { serialPort } from "$lib/serial/connection";
|
||||
|
||||
const options = [
|
||||
@@ -17,21 +16,27 @@
|
||||
</script>
|
||||
|
||||
<h3>Reset Device</h3>
|
||||
<p>Resetting might take <b>up to 2 Minutes</b>.</p>
|
||||
{#each options as category, i}
|
||||
{#if i > 0}
|
||||
<hr />
|
||||
{/if}
|
||||
{#each category as [command, description]}
|
||||
<button
|
||||
class="error"
|
||||
use:confirmChallenge={{
|
||||
onConfirm() {
|
||||
$serialPort?.reset(command);
|
||||
$serialPort = undefined;
|
||||
},
|
||||
challenge: description,
|
||||
}}>{description}...</button
|
||||
<form
|
||||
onsubmit={(event) => {
|
||||
event.preventDefault();
|
||||
$serialPort?.reset(command);
|
||||
$serialPort = undefined;
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={description}
|
||||
required
|
||||
pattern="^{description}$"
|
||||
/>
|
||||
<button class="icon" type="submit">send</button>
|
||||
</form>
|
||||
{/each}
|
||||
{/each}
|
||||
|
||||
@@ -39,4 +44,43 @@
|
||||
hr {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
p {
|
||||
width: 22ch;
|
||||
}
|
||||
|
||||
button.icon {
|
||||
font-size: 20px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
width: fit-content;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: var(--md-sys-color-outline);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="text"]:valid {
|
||||
color: var(--md-sys-color-error);
|
||||
|
||||
& + button {
|
||||
color: var(--md-sys-color-error);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { Action } from "svelte/action";
|
||||
import ConfirmChallenge from "./ConfirmChallenge.svelte";
|
||||
import tippy from "tippy.js";
|
||||
import { mount, unmount } from "svelte";
|
||||
|
||||
export const confirmChallenge: Action<
|
||||
HTMLElement,
|
||||
{ onConfirm: () => void; challenge: string }
|
||||
> = (node, { onConfirm, challenge }) => {
|
||||
let component: {} | undefined;
|
||||
let target: HTMLElement | undefined;
|
||||
const edit = tippy(node, {
|
||||
interactive: true,
|
||||
trigger: "click",
|
||||
onShow(instance) {
|
||||
target = instance.popper.querySelector(".tippy-content") as HTMLElement;
|
||||
target.classList.add("active");
|
||||
if (component === undefined) {
|
||||
component = mount(ConfirmChallenge, {
|
||||
target,
|
||||
props: {
|
||||
challenge,
|
||||
onconfirm() {
|
||||
edit.hide();
|
||||
onConfirm();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
onHidden() {
|
||||
if (component) {
|
||||
unmount(component);
|
||||
}
|
||||
target?.classList.remove("active");
|
||||
component = undefined;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
edit.destroy();
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user