mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-21 17:32:41 +00:00
@@ -73,6 +73,10 @@ const config = {
|
|||||||
"reply",
|
"reply",
|
||||||
"navigate_before",
|
"navigate_before",
|
||||||
"navigate_next",
|
"navigate_next",
|
||||||
|
"library_add",
|
||||||
|
"reset_wrench",
|
||||||
|
"reset_settings",
|
||||||
|
"delete_sweep",
|
||||||
"print",
|
"print",
|
||||||
"restore_from_trash",
|
"restore_from_trash",
|
||||||
"history",
|
"history",
|
||||||
|
|||||||
@@ -107,32 +107,32 @@ export function restoreFromFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push(
|
changes.push([
|
||||||
...getChangesFromChordFile(recent[0]),
|
...getChangesFromChordFile(recent[0]),
|
||||||
...getChangesFromLayoutFile(recent[1]),
|
...getChangesFromLayoutFile(recent[1]),
|
||||||
...getChangesFromSettingsFile(recent[2]),
|
...getChangesFromSettingsFile(recent[2]),
|
||||||
);
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "chords": {
|
case "chords": {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push(...getChangesFromChordFile(file));
|
changes.push(getChangesFromChordFile(file));
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "layout": {
|
case "layout": {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push(...getChangesFromLayoutFile(file));
|
changes.push(getChangesFromLayoutFile(file));
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "settings": {
|
case "settings": {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push(...getChangesFromSettingsFile(file));
|
changes.push(getChangesFromSettingsFile(file));
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -137,12 +137,14 @@
|
|||||||
},
|
},
|
||||||
onselect(action) {
|
onselect(action) {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Layout,
|
{
|
||||||
id: keyInfo.id,
|
type: ChangeType.Layout,
|
||||||
layer: get(activeLayer),
|
id: keyInfo.id,
|
||||||
action,
|
layer: get(activeLayer),
|
||||||
});
|
action,
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
closed();
|
closed();
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { serialPort } from "$lib/serial/connection";
|
import { deviceMeta, serialPort } from "$lib/serial/connection";
|
||||||
import { action } from "$lib/title";
|
import { action } from "$lib/title";
|
||||||
import GenericLayout from "$lib/components/layout/GenericLayout.svelte";
|
import GenericLayout from "$lib/components/layout/GenericLayout.svelte";
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import type { VisualLayout } from "$lib/serialization/visual-layout";
|
import type { VisualLayout } from "$lib/serialization/visual-layout";
|
||||||
import { fade } from "svelte/transition";
|
import { fade, fly } from "svelte/transition";
|
||||||
|
import { restoreFromFile } from "$lib/backup/backup";
|
||||||
|
|
||||||
let device = $derived($serialPort?.device);
|
let device = $derived($serialPort?.device);
|
||||||
const activeLayer = getContext<Writable<number>>("active-layer");
|
const activeLayer = getContext<Writable<number>>("active-layer");
|
||||||
@@ -58,6 +59,16 @@
|
|||||||
{icon}
|
{icon}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if $deviceMeta?.factoryDefaults?.layout}
|
||||||
|
<button
|
||||||
|
use:action={{ title: "Reset Layout" }}
|
||||||
|
transition:fly={{ x: -8 }}
|
||||||
|
class="icon reset-layout"
|
||||||
|
onclick={() =>
|
||||||
|
restoreFromFile($deviceMeta!.factoryDefaults!.layout)}
|
||||||
|
>reset_wrench</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<GenericLayout {visualLayout} />
|
<GenericLayout {visualLayout} />
|
||||||
@@ -113,7 +124,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:first-child,
|
&:first-child,
|
||||||
&:last-child {
|
&:nth-child(3) {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
height: unset;
|
height: unset;
|
||||||
}
|
}
|
||||||
@@ -124,12 +135,21 @@
|
|||||||
border-radius: 16px 0 0 16px;
|
border-radius: 16px 0 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:nth-child(3) {
|
||||||
margin-inline-start: -8px;
|
margin-inline-start: -8px;
|
||||||
padding-inline: 24px 4px;
|
padding-inline: 24px 4px;
|
||||||
border-radius: 0 16px 16px 0;
|
border-radius: 0 16px 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.reset-layout {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translate(100%, -50%);
|
||||||
|
background: none;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
color: var(--md-sys-color-on-tertiary);
|
color: var(--md-sys-color-on-tertiary);
|
||||||
|
|||||||
@@ -63,11 +63,13 @@ export const setting: Action<
|
|||||||
}
|
}
|
||||||
|
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Setting,
|
{
|
||||||
id: id,
|
type: ChangeType.Setting,
|
||||||
setting: value,
|
id: id,
|
||||||
});
|
setting: value,
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export interface ChangeInfo {
|
|||||||
|
|
||||||
export type Change = LayoutChange | ChordChange | SettingChange;
|
export type Change = LayoutChange | ChordChange | SettingChange;
|
||||||
|
|
||||||
export const changes = persistentWritable<Change[]>("changes", []);
|
export const changes = persistentWritable<Change[][]>("changes", []);
|
||||||
|
|
||||||
export interface Overlay {
|
export interface Overlay {
|
||||||
layout: [Map<number, number>, Map<number, number>, Map<number, number>];
|
layout: [Map<number, number>, Map<number, number>, Map<number, number>];
|
||||||
@@ -57,21 +57,23 @@ export const overlay = derived(changes, (changes) => {
|
|||||||
settings: new Map(),
|
settings: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const change of changes) {
|
for (const changeset of changes) {
|
||||||
switch (change.type) {
|
for (const change of changeset) {
|
||||||
case ChangeType.Layout:
|
switch (change.type) {
|
||||||
overlay.layout[change.layer]?.set(change.id, change.action);
|
case ChangeType.Layout:
|
||||||
break;
|
overlay.layout[change.layer]?.set(change.id, change.action);
|
||||||
case ChangeType.Chord:
|
break;
|
||||||
overlay.chords.set(JSON.stringify(change.id), {
|
case ChangeType.Chord:
|
||||||
actions: change.actions,
|
overlay.chords.set(JSON.stringify(change.id), {
|
||||||
phrase: change.phrase,
|
actions: change.actions,
|
||||||
deleted: change.deleted ?? false,
|
phrase: change.phrase,
|
||||||
});
|
deleted: change.deleted ?? false,
|
||||||
break;
|
});
|
||||||
case ChangeType.Setting:
|
break;
|
||||||
overlay.settings.set(change.id, change.setting);
|
case ChangeType.Setting:
|
||||||
break;
|
overlay.settings.set(change.id, change.setting);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import { action } from "$lib/title";
|
import { action } from "$lib/title";
|
||||||
import LL from "$i18n/i18n-svelte";
|
import LL from "$i18n/i18n-svelte";
|
||||||
import EditActions from "./EditActions.svelte";
|
import EditActions from "./EditActions.svelte";
|
||||||
import { sync, syncStatus } from "$lib/serial/connection";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { action } from "$lib/title";
|
import { action } from "$lib/title";
|
||||||
import { onDestroy, onMount, setContext, tick } from "svelte";
|
import { onDestroy, onMount, setContext, tick } from "svelte";
|
||||||
import { changes, ChangeType, chords } from "$lib/undo-redo";
|
import { changes, ChangeType, chords } from "$lib/undo-redo";
|
||||||
import type { ChordInfo } from "$lib/undo-redo";
|
import type { ChordChange, ChordInfo } from "$lib/undo-redo";
|
||||||
import { derived, writable } from "svelte/store";
|
import { derived, writable } from "svelte/store";
|
||||||
import ChordEdit from "./ChordEdit.svelte";
|
import ChordEdit from "./ChordEdit.svelte";
|
||||||
import { crossfade, fly } from "svelte/transition";
|
import { crossfade, fly } from "svelte/transition";
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
import { expoOut } from "svelte/easing";
|
import { expoOut } from "svelte/easing";
|
||||||
import { osLayout } from "$lib/os-layout";
|
import { osLayout } from "$lib/os-layout";
|
||||||
import randomTips from "$lib/assets/random-tips/en.json";
|
import randomTips from "$lib/assets/random-tips/en.json";
|
||||||
|
import { deviceMeta } from "$lib/serial/connection";
|
||||||
|
import { restoreFromFile } from "$lib/backup/backup";
|
||||||
|
|
||||||
const resultSize = 38;
|
const resultSize = 38;
|
||||||
let results: HTMLElement;
|
let results: HTMLElement;
|
||||||
@@ -182,12 +184,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Chord,
|
{
|
||||||
id: actions,
|
type: ChangeType.Chord,
|
||||||
actions,
|
id: actions,
|
||||||
phrase: [],
|
actions,
|
||||||
});
|
phrase: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -212,6 +216,21 @@
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearChords() {
|
||||||
|
changes.update((changes) => {
|
||||||
|
changes.push(
|
||||||
|
$chords.map<ChordChange>((it) => ({
|
||||||
|
type: ChangeType.Chord,
|
||||||
|
id: it.id,
|
||||||
|
actions: it.actions,
|
||||||
|
phrase: it.phrase,
|
||||||
|
deleted: true,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
return changes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const items = derived(
|
const items = derived(
|
||||||
[searchFilter, chords],
|
[searchFilter, chords],
|
||||||
([filter, chords]) =>
|
([filter, chords]) =>
|
||||||
@@ -292,6 +311,17 @@
|
|||||||
"\n\nDid you know? " +
|
"\n\nDid you know? " +
|
||||||
randomTips[Math.floor(randomTips.length * Math.random())]}
|
randomTips[Math.floor(randomTips.length * Math.random())]}
|
||||||
></textarea>
|
></textarea>
|
||||||
|
<button onclick={clearChords}
|
||||||
|
><span class="icon">delete_sweep</span>
|
||||||
|
Clear Chords</button
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{#each Object.entries($deviceMeta?.factoryDefaults?.chords ?? {}) as [title, library]}
|
||||||
|
<button onclick={() => restoreFromFile(library)}
|
||||||
|
><span class="icon">library_add</span>{title}</button
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
<button onclick={downloadVocabulary}
|
<button onclick={downloadVocabulary}
|
||||||
><span class="icon">download</span>
|
><span class="icon">download</span>
|
||||||
{$LL.configure.chords.VOCABULARY()}</button
|
{$LL.configure.chords.VOCABULARY()}</button
|
||||||
|
|||||||
@@ -61,12 +61,14 @@
|
|||||||
if (pressedKeys.size < 1) return;
|
if (pressedKeys.size < 1) return;
|
||||||
if (!chord) return onsubmit(makeChordInput(...pressedKeys));
|
if (!chord) return onsubmit(makeChordInput(...pressedKeys));
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Chord,
|
{
|
||||||
id: chord!.id,
|
type: ChangeType.Chord,
|
||||||
actions: makeChordInput(...pressedKeys),
|
id: chord!.id,
|
||||||
phrase: chord!.phrase,
|
actions: makeChordInput(...pressedKeys),
|
||||||
});
|
phrase: chord!.phrase,
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -76,12 +78,14 @@
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
selectAction(event, (action) => {
|
selectAction(event, (action) => {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Chord,
|
{
|
||||||
id: chord!.id,
|
type: ChangeType.Chord,
|
||||||
actions: makeChordInput(...chordActions!, action),
|
id: chord!.id,
|
||||||
phrase: chord!.phrase,
|
actions: makeChordInput(...chordActions!, action),
|
||||||
});
|
phrase: chord!.phrase,
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Chord,
|
{
|
||||||
id: chord.id,
|
type: ChangeType.Chord,
|
||||||
actions: chord.actions,
|
id: chord.id,
|
||||||
phrase: chord.phrase,
|
actions: chord.actions,
|
||||||
deleted: true,
|
phrase: chord.phrase,
|
||||||
});
|
deleted: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -35,9 +37,13 @@
|
|||||||
|
|
||||||
function restore() {
|
function restore() {
|
||||||
changes.update((changes) =>
|
changes.update((changes) =>
|
||||||
changes.filter(
|
changes
|
||||||
(it) => !(it.type === ChangeType.Chord && isSameChord(it, chord)),
|
.map((it) =>
|
||||||
),
|
it.filter(
|
||||||
|
(it) => !(it.type === ChangeType.Chord && isSameChord(it, chord)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.filter((it) => it.length > 0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,12 +56,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Chord,
|
{
|
||||||
id,
|
type: ChangeType.Chord,
|
||||||
actions: [...chord.actions],
|
id,
|
||||||
phrase: [...chord.phrase],
|
actions: [...chord.actions],
|
||||||
});
|
phrase: [...chord.phrase],
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -49,24 +49,28 @@
|
|||||||
function deleteAction(at: number, count = 1) {
|
function deleteAction(at: number, count = 1) {
|
||||||
if (!(at in chord.phrase)) return;
|
if (!(at in chord.phrase)) return;
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Chord,
|
{
|
||||||
id: chord.id,
|
type: ChangeType.Chord,
|
||||||
actions: chord.actions,
|
id: chord.id,
|
||||||
phrase: chord.phrase.toSpliced(at, count),
|
actions: chord.actions,
|
||||||
});
|
phrase: chord.phrase.toSpliced(at, count),
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertAction(at: number, action: number) {
|
function insertAction(at: number, action: number) {
|
||||||
changes.update((changes) => {
|
changes.update((changes) => {
|
||||||
changes.push({
|
changes.push([
|
||||||
type: ChangeType.Chord,
|
{
|
||||||
id: chord.id,
|
type: ChangeType.Chord,
|
||||||
actions: chord.actions,
|
id: chord.id,
|
||||||
phrase: chord.phrase.toSpliced(at, 0, action),
|
actions: chord.actions,
|
||||||
});
|
phrase: chord.phrase.toSpliced(at, 0, action),
|
||||||
|
},
|
||||||
|
]);
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import Action from "$lib/components/Action.svelte";
|
import Action from "$lib/components/Action.svelte";
|
||||||
import { popup } from "$lib/popup";
|
import { popup } from "$lib/popup";
|
||||||
import { serialPort } from "$lib/serial/connection";
|
import { deviceMeta, serialPort } from "$lib/serial/connection";
|
||||||
import { setting } from "$lib/setting";
|
import { setting } from "$lib/setting";
|
||||||
import ResetPopup from "./ResetPopup.svelte";
|
import ResetPopup from "./ResetPopup.svelte";
|
||||||
import LL from "$i18n/i18n-svelte";
|
import LL from "$i18n/i18n-svelte";
|
||||||
@@ -12,8 +12,11 @@
|
|||||||
downloadBackup,
|
downloadBackup,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
restoreBackup,
|
restoreBackup,
|
||||||
|
restoreFromFile,
|
||||||
} from "$lib/backup/backup";
|
} from "$lib/backup/backup";
|
||||||
import { preference } from "$lib/preferences";
|
import { preference } from "$lib/preferences";
|
||||||
|
import { action } from "$lib/title";
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -77,7 +80,15 @@
|
|||||||
use:setting={{ id: 0x92 }}
|
use:setting={{ id: 0x92 }}
|
||||||
/></label
|
/></label
|
||||||
>
|
>
|
||||||
<button class="outline" use:popup={ResetPopup}>Reset...</button>
|
{#if $deviceMeta?.factoryDefaults?.settings}
|
||||||
|
<button
|
||||||
|
use:action={{ title: "Reset Settings" }}
|
||||||
|
transition:fly={{ x: -8 }}
|
||||||
|
onclick={() => restoreFromFile($deviceMeta.factoryDefaults!.settings)}
|
||||||
|
><span class="icon">reset_settings</span>Reset Settings</button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
<button class="outline" use:popup={ResetPopup}>Recovery...</button>
|
||||||
{/if}
|
{/if}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user