feat: add ability to add special actions to chord inputs

resolves #10
This commit is contained in:
2023-12-21 19:19:47 +01:00
parent 81af9f2e82
commit 03dd528465
5 changed files with 101 additions and 47 deletions

View File

@@ -91,6 +91,7 @@ const config = {
"stat_minus_2",
"stat_2",
"description",
"add_circle",
],
codePoints: {
speed: "e9e4",

View File

@@ -116,7 +116,10 @@
<section bind:this={results}>
<table>
{#if page === 0}
<tr><th><ChordActionEdit on:submit={({detail}) => insertChord(detail)} /></th><td /><td /></tr>
<tr
><th class="new-chord"><ChordActionEdit on:submit={({detail}) => insertChord(detail)} /></th><td /><td
/></tr
>
{/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;

View File

@@ -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
})
})
}
</script>
<button
class:deleted={chord && chord.deleted}
class:edited={chord && chord.actionsChanged}
class:invalid={chord && chord.actions.toSorted(compare).some((it, i) => chord?.actions[i] !== it)}
class="chord"
on:click={edit}
on:keydown={keydown}
on:keyup={keyup}
@@ -60,6 +76,7 @@
<span>{$LL.configure.chords.NEW_CHORD()}</span>
{/if}
<ActionString display="keys" actions={editing ? [...pressedKeys].sort(compare) : chord?.actions ?? []} />
<button class="icon add" on:click|stopPropagation={addSpecial}>add_circle</button>
<sup></sup>
</button>
@@ -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;

View File

@@ -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}
<div transition:scale class="cursor" style:translate="{cursorOffset}px 0">
<button class="icon" bind:this={button} on:click={selectAction}>add</button>
<button class="icon" bind:this={button} on:click={addSpecial}>add</button>
</div>
{:else}
<div />

View File

@@ -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()
})
}