diff --git a/src/i18n/de/index.ts b/src/i18n/de/index.ts index e99908c0..c8df7f99 100644 --- a/src/i18n/de/index.ts +++ b/src/i18n/de/index.ts @@ -77,6 +77,8 @@ const de = { configure: { chords: { TITLE: "Akkorde", + HOLD_KEYS: "Akkord halten", + NEW_CHORD: "Neuer Akkord", search: { PLACEHOLDER: "{0} Akkord{{|e}} durchsuchen", }, diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts index c1703a08..c6c55431 100644 --- a/src/i18n/en/index.ts +++ b/src/i18n/en/index.ts @@ -75,6 +75,8 @@ const en = { configure: { chords: { TITLE: "Chords", + HOLD_KEYS: "Hold chord", + NEW_CHORD: "New chord", search: { PLACEHOLDER: "Search {0} chord{{|s}}", }, diff --git a/src/lib/share/chara-file.ts b/src/lib/share/chara-file.ts index aed79eb3..fcc620e1 100644 --- a/src/lib/share/chara-file.ts +++ b/src/lib/share/chara-file.ts @@ -4,7 +4,7 @@ export interface CharaFile { } export interface CharaLayoutFile extends CharaFile<"layout"> { - device: "one" | "lite" | string + device?: "ONE" | "LITE" | string layout: [number[], number[], number[]] } @@ -12,8 +12,12 @@ export interface CharaChordFile extends CharaFile<"chords"> { chords: [number[], number[]][] } -export interface CharaChordSettings extends CharaFile<"settings"> { +export interface CharaSettingsFile extends CharaFile<"settings"> { settings: number[] } +export interface CharaBackupFile extends CharaFile<"backup"> { + history: [CharaChordFile, CharaLayoutFile, CharaSettingsFile][] +} + export type CharaFiles = CharaLayoutFile | CharaChordFile diff --git a/src/lib/undo-redo.ts b/src/lib/undo-redo.ts index ee3a4177..9e4206d5 100644 --- a/src/lib/undo-redo.ts +++ b/src/lib/undo-redo.ts @@ -46,7 +46,6 @@ export interface Overlay { } export const overlay = derived(changes, changes => { - console.time("overlay building") const overlay: Overlay = { layout: [new Map(), new Map(), new Map()], chords: new Map(), @@ -66,7 +65,6 @@ export const overlay = derived(changes, changes => { break } } - console.timeEnd("overlay building") return overlay }) @@ -91,33 +89,48 @@ export const layout = derived([overlay, deviceLayout], ([overlay, layout]) => export type ChordInfo = Chord & ChangeInfo & {phraseChanged: boolean; actionsChanged: boolean; sortBy: string} & {id: number[]} -export const chords = derived([overlay, deviceChords], ([overlay, chords]) => - chords - .map(chord => { - const id = JSON.stringify(chord.actions) - if (overlay.chords.has(id)) { - const changedChord = overlay.chords.get(id)! - return { - id: chord.actions, - // use the old phrase for stable editing - sortBy: chord.phrase.map(it => KEYMAP_CODES[it].id || it).join(), - 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, - sortBy: chord.phrase.map(it => KEYMAP_CODES[it].id || it).join(), - actions: chord.actions, - phrase: chord.phrase, - phraseChanged: false, - actionsChanged: false, - isApplied: true, - } +export const chords = derived([overlay, deviceChords], ([overlay, chords]) => { + const newChords = new Set(overlay.chords.keys()) + + const changedChords = chords.map(chord => { + const id = JSON.stringify(chord.actions) + if (overlay.chords.has(id)) { + newChords.delete(id) + const changedChord = overlay.chords.get(id)! + return { + id: chord.actions, + // use the old phrase for stable editing + sortBy: chord.phrase.map(it => KEYMAP_CODES[it].id || it).join(), + 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, + sortBy: chord.phrase.map(it => KEYMAP_CODES[it].id || it).join(), + actions: chord.actions, + phrase: chord.phrase, + phraseChanged: false, + actionsChanged: false, + isApplied: true, } + } + }) + for (const id of newChords) { + const chord = overlay.chords.get(id)! + changedChords.push({ + sortBy: "", + isApplied: false, + actionsChanged: true, + phraseChanged: false, + id: JSON.parse(id), + phrase: chord.phrase, + actions: chord.actions, }) - .sort(({sortBy: a}, {sortBy: b}) => a.localeCompare(b)), -) + } + + return changedChords.sort(({sortBy: a}, {sortBy: b}) => a.localeCompare(b)) +}) diff --git a/src/routes/BackupPopup.svelte b/src/routes/BackupPopup.svelte index 29cfe3eb..af8bb02a 100644 --- a/src/routes/BackupPopup.svelte +++ b/src/routes/BackupPopup.svelte @@ -1,27 +1,42 @@ diff --git a/src/routes/config/chords/+page.svelte b/src/routes/config/chords/+page.svelte index cf007789..69780340 100644 --- a/src/routes/config/chords/+page.svelte +++ b/src/routes/config/chords/+page.svelte @@ -4,11 +4,12 @@ import LL from "../../../i18n/i18n-svelte" import {action} from "$lib/title" import {onDestroy, onMount, setContext} from "svelte" - import {chords} from "$lib/undo-redo" + import {changes, ChangeType, chords} from "$lib/undo-redo" import type {ChordInfo} from "$lib/undo-redo" import {derived, writable} from "svelte/store" import ChordEdit from "./ChordEdit.svelte" import {crossfade} from "svelte/transition" + import ChordActionEdit from "./ChordActionEdit.svelte" const resultSize = 38 let results: HTMLElement @@ -46,12 +47,27 @@ searchFilter.set(query && searchIndex ? searchIndex.search(query) : undefined) } + function insertChord(actions: number[]) { + changes.update(changes => { + changes.push({ + type: ChangeType.Chord, + id: actions, + actions, + phrase: [], + }) + return changes + }) + } + const items = derived( [searchFilter, chords], ([filter, chords]) => filter?.map(it => [chords[it], it] as const) ?? chords.map((it, i) => [it, i] as const), ) - const lastPage = derived([items, pageSize], ([items, pageSize]) => Math.ceil(items.length / pageSize) - 1) + const lastPage = derived( + [items, pageSize], + ([items, pageSize]) => Math.ceil((items.length + 1) / pageSize) - 1, + ) setContext("cursor-crossfade", crossfade({})) @@ -91,8 +107,11 @@
+ {#if page === 0} + + {/if} {#if $lastPage !== -1} - {#each $items.slice(page * $pageSize, (page + 1) * $pageSize) as [chord] (chord.id)} + {#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord.id))} diff --git a/src/routes/config/chords/ChordActionEdit.svelte b/src/routes/config/chords/ChordActionEdit.svelte index 9c1776e5..eb46baa4 100644 --- a/src/routes/config/chords/ChordActionEdit.svelte +++ b/src/routes/config/chords/ChordActionEdit.svelte @@ -2,8 +2,12 @@ import {KEYMAP_CODES, KEYMAP_IDS} from "$lib/serial/keymap-codes" import type {ChordInfo} from "$lib/undo-redo" import {changes, ChangeType} from "$lib/undo-redo" + import {createEventDispatcher} from "svelte" + import LL from "../../../i18n/i18n-svelte" - export let chord: ChordInfo + export let chord: ChordInfo | undefined = undefined + + const dispatch = createEventDispatcher() let pressedKeys = new Set() let editing = false @@ -24,12 +28,13 @@ if (!editing) return editing = false if (pressedKeys.size < 2) return + if (!chord) return dispatch("submit", [...pressedKeys]) changes.update(changes => { changes.push({ type: ChangeType.Chord, - id: chord.id, + id: chord!.id, actions: [...pressedKeys], - phrase: chord.phrase, + phrase: chord!.phrase, }) return changes }) @@ -37,16 +42,18 @@
insertChord(detail)} />
- {#if chord.phrase.length === 0} - - {:else} + {#if chord.phrase.length !== 0} + {:else if chord.phraseChanged} + {/if}
diff --git a/src/routes/config/chords/ChordPhraseEdit.svelte b/src/routes/config/chords/ChordPhraseEdit.svelte index ec2e7622..a3d910f0 100644 --- a/src/routes/config/chords/ChordPhraseEdit.svelte +++ b/src/routes/config/chords/ChordPhraseEdit.svelte @@ -135,7 +135,7 @@ role="textbox" tabindex="0" bind:this={box} - class:edited={chord.phraseChanged} + class:edited={chord.phrase.length !== 0 && chord.phraseChanged} on:focusin={() => (hasFocus = true)} on:focusout={event => { if (event.relatedTarget !== button) hasFocus = false @@ -236,12 +236,13 @@ background: currentcolor; transition: - opacity 250ms ease, + opacity 150ms ease, scale 250ms ease; } &::after { scale: 0 1; + transition-duration: 250ms; } &:hover::before {