From d2276a53d0f01fbdcce7586baa652bcfdb7e9039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Fri, 10 Nov 2023 15:45:04 +0100 Subject: [PATCH] feat: chord editing --- src/i18n/de/index.ts | 7 +++ src/i18n/en/index.ts | 7 +++ src/lib/ConfirmDialog.svelte | 57 +++++++++++++++++++ src/lib/confirm-dialog.ts | 32 +++++++++++ src/lib/serial/device.ts | 6 +- src/lib/undo-redo.ts | 26 ++++++--- src/routes/EditActions.svelte | 33 +++++++++-- src/routes/config/chords/+page.svelte | 6 +- .../config/chords/ChordActionEdit.svelte | 48 ++++++++++++++-- src/routes/config/chords/ChordEdit.svelte | 44 +++++++------- .../config/chords/ChordPhraseEdit.svelte | 10 ++-- 11 files changed, 225 insertions(+), 51 deletions(-) create mode 100644 src/lib/ConfirmDialog.svelte create mode 100644 src/lib/confirm-dialog.ts diff --git a/src/i18n/de/index.ts b/src/i18n/de/index.ts index 0b7a6b28..e99908c0 100644 --- a/src/i18n/de/index.ts +++ b/src/i18n/de/index.ts @@ -80,6 +80,13 @@ const de = { search: { PLACEHOLDER: "{0} Akkord{{|e}} durchsuchen", }, + conflict: { + TITLE: "Akkordkonflikt", + DESCRIPTION: + "Der Akkord {0} würde einen bereits existierenden Akkord überschreiben. Wirklich fortfahren?", + CONFIRM: "Überschreiben", + ABORT: "Überspringen", + }, }, layout: { TITLE: "Layout", diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts index 36bc7222..c1703a08 100644 --- a/src/i18n/en/index.ts +++ b/src/i18n/en/index.ts @@ -78,6 +78,13 @@ const en = { search: { PLACEHOLDER: "Search {0} chord{{|s}}", }, + conflict: { + TITLE: "Chord conflict", + DESCRIPTION: + "Your chord {0} conflicts with an existing chord. Are you sure you want to overwrite this chord?", + CONFIRM: "Overwrite", + ABORT: "Skip", + }, }, layout: { TITLE: "Layout", diff --git a/src/lib/ConfirmDialog.svelte b/src/lib/ConfirmDialog.svelte new file mode 100644 index 00000000..b6066556 --- /dev/null +++ b/src/lib/ConfirmDialog.svelte @@ -0,0 +1,57 @@ + + + +

{@html title}

+ {#if message} +

{@html message}

+ {/if} +
+ + +
+
+ + diff --git a/src/lib/confirm-dialog.ts b/src/lib/confirm-dialog.ts new file mode 100644 index 00000000..5bc2e439 --- /dev/null +++ b/src/lib/confirm-dialog.ts @@ -0,0 +1,32 @@ +import ConfirmDialog from "$lib/ConfirmDialog.svelte" + +export async function askForConfirmation( + title: string, + message: string, + confirmTitle: string, + abortTitle: string, +): Promise { + const dialog = new ConfirmDialog({ + target: document.body, + props: { + title, + message, + confirmTitle, + abortTitle, + }, + }) + dialog.show() + + let resolvePromise: (value: boolean) => void + const resultPromise = new Promise(resolve => { + resolvePromise = resolve + }) + + dialog.$on("abort", () => resolvePromise(false)) + dialog.$on("confirm", () => resolvePromise(true)) + + const result = await resultPromise + dialog.$destroy() + + return result +} diff --git a/src/lib/serial/device.ts b/src/lib/serial/device.ts index 9061aab5..dfecb520 100644 --- a/src/lib/serial/device.ts +++ b/src/lib/serial/device.ts @@ -197,7 +197,7 @@ export class CharaDevice { */ async getChordPhrase(actions: number[]): Promise { const [phrase] = await this.send(`CML C2 ${stringifyChordActions(actions)}`) - return phrase === "0" ? undefined : parsePhrase(phrase) + return phrase === "2" ? undefined : parsePhrase(phrase) } async setChord(chord: Chord) { @@ -211,9 +211,9 @@ export class CharaDevice { } async deleteChord(chord: Pick) { - console.log(`CML C4 ${stringifyChordActions(chord.actions)}`) const status = await this.send(`CML C4 ${stringifyChordActions(chord.actions)}`) - if (status.at(-1) !== "0") throw new Error(`Failed with status ${status}`) + console.log(status) + if (status.at(-1) !== "2") throw new Error(`Failed with status ${status}`) } /** diff --git a/src/lib/undo-redo.ts b/src/lib/undo-redo.ts index 82804004..7a9cc78c 100644 --- a/src/lib/undo-redo.ts +++ b/src/lib/undo-redo.ts @@ -1,6 +1,5 @@ import {persistentWritable} from "$lib/storage" import {derived} from "svelte/store" -import {serializeActions} from "$lib/serial/chord" import type {Chord} from "$lib/serial/chord" import {deviceChords, deviceLayout, deviceSettings} from "$lib/serial/connection" @@ -19,6 +18,7 @@ export interface LayoutChange { export interface ChordChange { type: ChangeType.Chord + id: number[] actions: number[] phrase: number[] } @@ -40,7 +40,7 @@ export const changes = persistentWritable("changes", []) export interface Overlay { layout: [Map, Map, Map] - chords: Map + chords: Map settings: Map } @@ -58,7 +58,7 @@ export const overlay = derived(changes, changes => { overlay.layout[change.layer].set(change.id, change.action) break case ChangeType.Chord: - overlay.chords.set(serializeActions(change.actions), change.phrase) + overlay.chords.set(JSON.stringify(change.id), {actions: change.actions, phrase: change.phrase}) break case ChangeType.Setting: overlay.settings.set(change.id, change.setting) @@ -88,24 +88,32 @@ export const layout = derived([overlay, deviceLayout], ([overlay, layout]) => ), ) -export type ChordInfo = Chord & ChangeInfo +export type ChordInfo = Chord & + ChangeInfo & {phraseChanged: boolean; actionsChanged: boolean} & {id: number[]} export const chords = derived([overlay, deviceChords], ([overlay, chords]) => chords .map(chord => { - const key = serializeActions(chord.actions) - if (overlay.chords.has(key)) { + const id = JSON.stringify(chord.actions) + if (overlay.chords.has(id)) { + const changedChord = overlay.chords.get(id)! return { - actions: chord.actions, - phrase: overlay.chords.get(key)!, + id: chord.actions, + actions: changedChord.actions, + phrase: changedChord.phrase, + actionsChanged: id !== JSON.stringify(changedChord.actions), + phraseChanged: JSON.stringify(chord.phrase) !== JSON.stringify(changedChord.phrase), isApplied: false, } } else { return { + id: chord.actions, actions: chord.actions, phrase: chord.phrase, + phraseChanged: false, + actionsChanged: false, isApplied: true, } } }) - .sort((a, b) => (a.actions.some((it, i) => it > b.actions[i]) ? 1 : -1)), + .sort((a, b) => a.phrase.map((it, i) => it - b.phrase[i]).find(it => it !== 0) ?? 0), ) diff --git a/src/routes/EditActions.svelte b/src/routes/EditActions.svelte index c0f1b4a8..7b74e469 100644 --- a/src/routes/EditActions.svelte +++ b/src/routes/EditActions.svelte @@ -5,7 +5,8 @@ import {fly} from "svelte/transition" import {action} from "$lib/title" import {deviceChords, deviceLayout, deviceSettings, serialPort, syncStatus} from "$lib/serial/connection" - import {deserializeActions} from "$lib/serial/chord" + import {askForConfirmation} from "$lib/confirm-dialog" + import {KEYMAP_CODES} from "$lib/serial/keymap-codes" function undo(event: MouseEvent) { if (event.shiftKey) { @@ -32,9 +33,29 @@ $syncStatus = "uploading" - for (const [id, phrase] of $overlay.chords) { - const actions = deserializeActions(id) - if (actions.length > 0) { + for (const [id, {actions, phrase}] of $overlay.chords) { + if (phrase.length > 0) { + if (id !== JSON.stringify(actions)) { + const existingChord = await port.getChordPhrase(actions) + if ( + existingChord !== undefined && + !(await askForConfirmation( + $LL.configure.chords.conflict.TITLE(), + $LL.configure.chords.conflict.DESCRIPTION( + actions.map(it => `${KEYMAP_CODES[it].id}`).join(" "), + ), + $LL.configure.chords.conflict.CONFIRM(), + $LL.configure.chords.conflict.ABORT(), + )) + ) { + changes.update(changes => + changes.filter(it => !(it.type === ChangeType.Chord && JSON.stringify(it.id) === id)), + ) + continue + } + + await port.deleteChord({actions: JSON.parse(id)}) + } await port.setChord({actions, phrase}) } else { await port.deleteChord({actions}) @@ -56,7 +77,9 @@ number[], number[], ] - $deviceChords = $chords.map(({actions, phrase}) => ({actions, phrase})) + $deviceChords = $chords + .map(({actions, phrase}) => ({actions, phrase})) + .filter(({phrase}) => phrase.length > 1) $deviceSettings = $settings.map(({value}) => value) $changes = [] $syncStatus = "done" diff --git a/src/routes/config/chords/+page.svelte b/src/routes/config/chords/+page.svelte index 9693d496..cf007789 100644 --- a/src/routes/config/chords/+page.svelte +++ b/src/routes/config/chords/+page.svelte @@ -92,8 +92,10 @@
{#if $lastPage !== -1} - {#each $items.slice(page * $pageSize, (page + 1) * $pageSize) as [chord], i (`${page}:${i}`)} - + {#each $items.slice(page * $pageSize, (page + 1) * $pageSize) as [chord] (chord.id)} + + + {/each} {:else} diff --git a/src/routes/config/chords/ChordActionEdit.svelte b/src/routes/config/chords/ChordActionEdit.svelte index 9ed80fad..9c1776e5 100644 --- a/src/routes/config/chords/ChordActionEdit.svelte +++ b/src/routes/config/chords/ChordActionEdit.svelte @@ -1,8 +1,9 @@ - diff --git a/src/routes/config/chords/ChordPhraseEdit.svelte b/src/routes/config/chords/ChordPhraseEdit.svelte index 15b712a5..a34262ce 100644 --- a/src/routes/config/chords/ChordPhraseEdit.svelte +++ b/src/routes/config/chords/ChordPhraseEdit.svelte @@ -2,12 +2,11 @@ import {KEYMAP_CODES, KEYMAP_IDS, specialKeycodes} from "$lib/serial/keymap-codes" import {tick} from "svelte" import ActionSelector from "$lib/components/layout/ActionSelector.svelte" - import type {Chord} from "$lib/serial/chord" import {changes, ChangeType} from "$lib/undo-redo" + import type {ChordInfo} from "$lib/undo-redo" import {scale} from "svelte/transition" - export let chord: Chord - export let edited: boolean + export let chord: ChordInfo function keypress(event: KeyboardEvent) { if (event.key === "ArrowUp") { @@ -37,9 +36,11 @@ } function deleteAction(at: number, count = 1) { + if (!(at in chord.phrase)) return changes.update(changes => { changes.push({ type: ChangeType.Chord, + id: chord.id, actions: chord.actions, phrase: chord.phrase.toSpliced(at, count), }) @@ -51,6 +52,7 @@ changes.update(changes => { changes.push({ type: ChangeType.Chord, + id: chord.id, actions: chord.actions, phrase: chord.phrase.toSpliced(at, 0, action), }) @@ -132,7 +134,7 @@ role="textbox" tabindex="0" bind:this={box} - class:edited + class:edited={chord.phraseChanged} on:focusin={() => (hasFocus = true)} on:focusout={() => (hasFocus = false)} >
No Results