mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-04 09:02:50 +00:00
@@ -91,6 +91,7 @@ const config = {
|
|||||||
"stat_minus_2",
|
"stat_minus_2",
|
||||||
"stat_2",
|
"stat_2",
|
||||||
"description",
|
"description",
|
||||||
|
"add_circle",
|
||||||
],
|
],
|
||||||
codePoints: {
|
codePoints: {
|
||||||
speed: "e9e4",
|
speed: "e9e4",
|
||||||
|
|||||||
@@ -116,7 +116,10 @@
|
|||||||
<section bind:this={results}>
|
<section bind:this={results}>
|
||||||
<table>
|
<table>
|
||||||
{#if page === 0}
|
{#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}
|
||||||
{#if $lastPage !== -1}
|
{#if $lastPage !== -1}
|
||||||
{#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord.id))}
|
{#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;
|
min-width: 8ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.new-chord :global(.add) {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
transition: border-color 250ms ease;
|
transition: border-color 250ms ease;
|
||||||
background: none;
|
background: none;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import {createEventDispatcher} from "svelte"
|
import {createEventDispatcher} from "svelte"
|
||||||
import LL from "../../../i18n/i18n-svelte"
|
import LL from "../../../i18n/i18n-svelte"
|
||||||
import ActionString from "$lib/components/ActionString.svelte"
|
import ActionString from "$lib/components/ActionString.svelte"
|
||||||
|
import {selectAction} from "./action-selector"
|
||||||
|
|
||||||
export let chord: ChordInfo | undefined = undefined
|
export let chord: ChordInfo | undefined = undefined
|
||||||
|
|
||||||
@@ -44,12 +45,27 @@
|
|||||||
return changes
|
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>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class:deleted={chord && chord.deleted}
|
class:deleted={chord && chord.deleted}
|
||||||
class:edited={chord && chord.actionsChanged}
|
class:edited={chord && chord.actionsChanged}
|
||||||
class:invalid={chord && chord.actions.toSorted(compare).some((it, i) => chord?.actions[i] !== it)}
|
class:invalid={chord && chord.actions.toSorted(compare).some((it, i) => chord?.actions[i] !== it)}
|
||||||
|
class="chord"
|
||||||
on:click={edit}
|
on:click={edit}
|
||||||
on:keydown={keydown}
|
on:keydown={keydown}
|
||||||
on:keyup={keyup}
|
on:keyup={keyup}
|
||||||
@@ -60,6 +76,7 @@
|
|||||||
<span>{$LL.configure.chords.NEW_CHORD()}</span>
|
<span>{$LL.configure.chords.NEW_CHORD()}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<ActionString display="keys" actions={editing ? [...pressedKeys].sort(compare) : chord?.actions ?? []} />
|
<ActionString display="keys" actions={editing ? [...pressedKeys].sort(compare) : chord?.actions ?? []} />
|
||||||
|
<button class="icon add" on:click|stopPropagation={addSpecial}>add_circle</button>
|
||||||
<sup>•</sup>
|
<sup>•</sup>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -74,7 +91,19 @@
|
|||||||
transition: opacity 250ms ease;
|
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;
|
position: relative;
|
||||||
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -88,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button::after {
|
.chord::after {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
import type {ChordInfo} from "$lib/undo-redo"
|
import type {ChordInfo} from "$lib/undo-redo"
|
||||||
import {scale} from "svelte/transition"
|
import {scale} from "svelte/transition"
|
||||||
import ActionString from "$lib/components/ActionString.svelte"
|
import ActionString from "$lib/components/ActionString.svelte"
|
||||||
|
import {selectAction} from "./action-selector"
|
||||||
|
|
||||||
export let chord: ChordInfo
|
export let chord: ChordInfo
|
||||||
|
|
||||||
function keypress(event: KeyboardEvent) {
|
function keypress(event: KeyboardEvent) {
|
||||||
if (event.key === "ArrowUp") {
|
if (event.key === "ArrowUp") {
|
||||||
selectAction()
|
addSpecial(event)
|
||||||
} else if (event.key === "ArrowLeft") {
|
} else if (event.key === "ArrowLeft") {
|
||||||
moveCursor(cursorPosition - 1)
|
moveCursor(cursorPosition - 1)
|
||||||
} else if (event.key === "ArrowRight") {
|
} else if (event.key === "ArrowRight") {
|
||||||
@@ -77,49 +78,15 @@
|
|||||||
moveCursor(i - 1)
|
moveCursor(i - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectAction() {
|
function addSpecial(event: MouseEvent | KeyboardEvent) {
|
||||||
const component = new ActionSelector({target: document.body})
|
selectAction(
|
||||||
const dialog = document.querySelector("dialog > div") as HTMLDivElement
|
event,
|
||||||
const backdrop = document.querySelector("dialog") as HTMLDialogElement
|
action => {
|
||||||
const dialogRect = dialog.getBoundingClientRect()
|
insertAction(cursorPosition, action)
|
||||||
const groupRect = button.getBoundingClientRect()
|
tick().then(() => moveCursor(cursorPosition + 1))
|
||||||
|
},
|
||||||
const scale = 0.5
|
() => box.focus(),
|
||||||
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()
|
|
||||||
box.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
component.$on("close", closed)
|
|
||||||
component.$on("select", ({detail}) => {
|
|
||||||
insertAction(cursorPosition, detail)
|
|
||||||
tick().then(() => moveCursor(cursorPosition + 1))
|
|
||||||
closed()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let button: HTMLButtonElement
|
let button: HTMLButtonElement
|
||||||
@@ -144,7 +111,7 @@
|
|||||||
>
|
>
|
||||||
{#if hasFocus}
|
{#if hasFocus}
|
||||||
<div transition:scale class="cursor" style:translate="{cursorOffset}px 0">
|
<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>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div />
|
<div />
|
||||||
|
|||||||
50
src/routes/config/chords/action-selector.ts
Normal file
50
src/routes/config/chords/action-selector.ts
Normal 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user