From 03dd528465a1a56d9a18f053f2d4c1e29d912224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Thu, 21 Dec 2023 19:19:47 +0100 Subject: [PATCH] feat: add ability to add special actions to chord inputs resolves #10 --- icons.config.js | 1 + src/routes/config/chords/+page.svelte | 9 ++- .../config/chords/ChordActionEdit.svelte | 33 ++++++++++- .../config/chords/ChordPhraseEdit.svelte | 55 ++++--------------- src/routes/config/chords/action-selector.ts | 50 +++++++++++++++++ 5 files changed, 101 insertions(+), 47 deletions(-) create mode 100644 src/routes/config/chords/action-selector.ts diff --git a/icons.config.js b/icons.config.js index 02e6425d..0dc473f1 100644 --- a/icons.config.js +++ b/icons.config.js @@ -91,6 +91,7 @@ const config = { "stat_minus_2", "stat_2", "description", + "add_circle", ], codePoints: { speed: "e9e4", diff --git a/src/routes/config/chords/+page.svelte b/src/routes/config/chords/+page.svelte index 54b356ba..23d845e9 100644 --- a/src/routes/config/chords/+page.svelte +++ b/src/routes/config/chords/+page.svelte @@ -116,7 +116,10 @@
{#if page === 0} - + {/if} {#if $lastPage !== -1} {#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord.id))} @@ -144,6 +147,10 @@ min-width: 8ch; } + .new-chord :global(.add) { + visibility: hidden; + } + textarea { transition: border-color 250ms ease; background: none; diff --git a/src/routes/config/chords/ChordActionEdit.svelte b/src/routes/config/chords/ChordActionEdit.svelte index b5755534..c1412824 100644 --- a/src/routes/config/chords/ChordActionEdit.svelte +++ b/src/routes/config/chords/ChordActionEdit.svelte @@ -5,6 +5,7 @@ import {createEventDispatcher} from "svelte" import LL from "../../../i18n/i18n-svelte" import ActionString from "$lib/components/ActionString.svelte" + import {selectAction} from "./action-selector" export let chord: ChordInfo | undefined = undefined @@ -44,12 +45,27 @@ return changes }) } + + function addSpecial(event: MouseEvent) { + selectAction(event, action => { + changes.update(changes => { + changes.push({ + type: ChangeType.Chord, + id: chord!.id, + actions: [...chord!.actions, action].sort(compare), + phrase: chord!.phrase, + }) + return changes + }) + }) + } @@ -74,7 +91,19 @@ transition: opacity 250ms ease; } - button { + .add { + font-size: 18px; + margin-inline-start: 4px; + height: 20px; + opacity: 0; + --icon-fill: 1; + } + + .chord:hover .add { + opacity: 1; + } + + .chord { position: relative; display: inline-flex; @@ -88,7 +117,7 @@ } } - button::after { + .chord::after { content: ""; position: absolute; diff --git a/src/routes/config/chords/ChordPhraseEdit.svelte b/src/routes/config/chords/ChordPhraseEdit.svelte index eff2bf75..b1b5e88b 100644 --- a/src/routes/config/chords/ChordPhraseEdit.svelte +++ b/src/routes/config/chords/ChordPhraseEdit.svelte @@ -6,12 +6,13 @@ import type {ChordInfo} from "$lib/undo-redo" import {scale} from "svelte/transition" import ActionString from "$lib/components/ActionString.svelte" + import {selectAction} from "./action-selector" export let chord: ChordInfo function keypress(event: KeyboardEvent) { if (event.key === "ArrowUp") { - selectAction() + addSpecial(event) } else if (event.key === "ArrowLeft") { moveCursor(cursorPosition - 1) } else if (event.key === "ArrowRight") { @@ -77,49 +78,15 @@ moveCursor(i - 1) } - function selectAction() { - const component = new ActionSelector({target: document.body}) - const dialog = document.querySelector("dialog > div") as HTMLDivElement - const backdrop = document.querySelector("dialog") as HTMLDialogElement - const dialogRect = dialog.getBoundingClientRect() - const groupRect = button.getBoundingClientRect() - - const scale = 0.5 - const dialogScale = `${1 - scale * (1 - groupRect.width / dialogRect.width)} ${ - 1 - scale * (1 - groupRect.height / dialogRect.height) - }` - const dialogTranslate = `${scale * (groupRect.x - dialogRect.x)}px ${ - scale * (groupRect.y - dialogRect.y) - }px` - - const duration = 150 - const options = {duration, easing: "ease"} - const dialogAnimation = dialog.animate( - [ - {scale: dialogScale, translate: dialogTranslate}, - {translate: "0 0", scale: "1"}, - ], - options, + function addSpecial(event: MouseEvent | KeyboardEvent) { + selectAction( + event, + action => { + insertAction(cursorPosition, action) + tick().then(() => moveCursor(cursorPosition + 1)) + }, + () => box.focus(), ) - const backdropAnimation = backdrop.animate([{opacity: 0}, {opacity: 1}], options) - - async function closed() { - dialogAnimation.reverse() - backdropAnimation.reverse() - - await dialogAnimation.finished - - component.$destroy() - await tick() - box.focus() - } - - component.$on("close", closed) - component.$on("select", ({detail}) => { - insertAction(cursorPosition, detail) - tick().then(() => moveCursor(cursorPosition + 1)) - closed() - }) } let button: HTMLButtonElement @@ -144,7 +111,7 @@ > {#if hasFocus}
- +
{:else}
diff --git a/src/routes/config/chords/action-selector.ts b/src/routes/config/chords/action-selector.ts new file mode 100644 index 00000000..98036a02 --- /dev/null +++ b/src/routes/config/chords/action-selector.ts @@ -0,0 +1,50 @@ +import ActionSelector from "$lib/components/layout/ActionSelector.svelte" +import {tick} from "svelte" + +export function selectAction( + event: MouseEvent | KeyboardEvent, + select: (action: number) => void, + dismissed?: () => void, +) { + const component = new ActionSelector({target: document.body}) + const dialog = document.querySelector("dialog > div") as HTMLDivElement + const backdrop = document.querySelector("dialog") as HTMLDialogElement + const dialogRect = dialog.getBoundingClientRect() + const groupRect = (event.target as HTMLElement).getBoundingClientRect() + + const scale = 0.5 + const dialogScale = `${1 - scale * (1 - groupRect.width / dialogRect.width)} ${ + 1 - scale * (1 - groupRect.height / dialogRect.height) + }` + const dialogTranslate = `${scale * (groupRect.x - dialogRect.x)}px ${ + scale * (groupRect.y - dialogRect.y) + }px` + + const duration = 150 + const options = {duration, easing: "ease"} + const dialogAnimation = dialog.animate( + [ + {scale: dialogScale, translate: dialogTranslate}, + {translate: "0 0", scale: "1"}, + ], + options, + ) + const backdropAnimation = backdrop.animate([{opacity: 0}, {opacity: 1}], options) + + async function closed() { + dialogAnimation.reverse() + backdropAnimation.reverse() + + await dialogAnimation.finished + + component.$destroy() + await tick() + dismissed?.() + } + + component.$on("close", closed) + component.$on("select", ({detail}) => { + select(detail) + closed() + }) +}
insertChord(detail)} />
insertChord(detail)} />