feat: 3d click in layout

feat: action autocomplete

[deploy]
This commit is contained in:
2023-07-29 17:31:14 +02:00
parent 73c71836dc
commit 7d148d0c2c
13 changed files with 254 additions and 153 deletions

View File

@@ -0,0 +1,59 @@
import type {Action} from "svelte/action"
import Index from "flexsearch"
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
import tippy from "tippy.js"
import ActionAutocomplete from "$lib/components/ActionAutocomplete.svelte"
import {browser} from "$app/environment"
const index = browser ? new Index({tokenize: "full"}) : undefined
for (const action of Object.values(KEYMAP_CODES)) {
index?.add(
action.code,
`${action.title || ""} ${action.variant || ""} ${action.category} ${action.id || ""} ${
action.description || ""
}`,
)
}
const exact = Object.fromEntries(
Object.values(KEYMAP_CODES)
.filter(it => !!it.id)
.map(it => [it.id, it] as const),
)
export const actionAutocomplete: Action<HTMLInputElement> = node => {
if (!browser) return
let completionComponent: ActionAutocomplete
const completionDialog = tippy(node, {
interactive: true,
placement: "bottom-start",
hideOnClick: false,
theme: "surface-variant search-completion",
arrow: false,
trigger: "focus",
offset: [0, 0],
onCreate(instance) {
const target = instance.popper.querySelector(".tippy-content")!
completionComponent = new ActionAutocomplete({target, props: {width: node.clientWidth}})
},
onDestroy() {
completionComponent.$destroy()
},
})
function input(event: Event) {
completionComponent.$set({
results: index!.search(node.value),
exact: exact[node.value],
code: Number(node.value),
})
}
node.addEventListener("input", input)
return {
destroy() {
node.removeEventListener("input", input)
},
}
}

View File

@@ -1,67 +0,0 @@
name: Action Codes
description: Invalid action codes
actions:
0x00:
id: "0x00"
0x01:
id: "0x01"
0x02:
id: "0x02"
0x03:
id: "0x03"
0x04:
id: "0x04"
0x05:
id: "0x05"
0x06:
id: "0x06"
0x07:
id: "0x07"
0x08:
id: "0x08"
0x09:
id: "0x09"
0x0A:
id: "0x0A"
0x0B:
id: "0x0B"
0x0C:
id: "0x0C"
0x0D:
id: "0x0D"
0x0E:
id: "0x0E"
0x0F:
id: "0x0F"
0x10:
id: "0x10"
0x11:
id: "0x11"
0x12:
id: "0x12"
0x13:
id: "0x13"
0x14:
id: "0x14"
0x15:
id: "0x15"
0x16:
id: "0x16"
0x17:
id: "0x17"
0x18:
id: "0x18"
0x19:
id: "0x19"
0x1A:
id: "0x1A"
0x1B:
id: "0x1B"
0x1C:
id: "0x1C"
0x1D:
id: "0x1D"
0x1E:
id: "0x1E"
0x1F:
id: "0x1F"

View File

@@ -0,0 +1,66 @@
<script lang="ts">
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
import ActionListItem from "$lib/components/ActionListItem.svelte"
export let exact: number | undefined = undefined
export let code: number = Number.NaN
export let results: number[] = []
export let width: number
console.log(width)
</script>
<div class="list" style="width: {width}px">
{#if exact !== undefined}
<div class="exact">
<i>Exact match</i>
<ActionListItem id={exact} />
</div>
{/if}
{#if !exact && code}
{#if code >= 2 ** 5 && code < 2 ** 13}
<button>USE CODE</button>
{:else}
<div>Action code is out of range</div>
{/if}
{/if}
{#each results as id (id)}
<ActionListItem {id} />
{/each}
</div>
<style lang="scss">
.list {
--scrollbar-color: var(--md-sys-color-on-surface-variant);
scrollbar-gutter: stable both-edges;
overflow-x: hidden;
overflow-y: auto;
display: flex;
flex-direction: column;
max-height: 500px;
padding-block: 8px;
}
.exact {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
border: 1px solid var(--md-sys-color-primary);
border-radius: 8px;
> i {
padding-inline: 8px;
color: var(--md-sys-color-on-primary);
background: var(--md-sys-color-primary);
border-radius: 0 0 8px 8px;
}
}
</style>

View File

@@ -0,0 +1,71 @@
<script lang="ts">
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
import type {KeyInfo} from "$lib/serial/keymap-codes"
export let id: number | KeyInfo
$: key = (typeof id === "number" ? KEYMAP_CODES[id] ?? id : id) as number | KeyInfo
</script>
<button>
{#if typeof key === "object"}
<div class="title">
<b>
{key.title || ""}
{#if key.variant === "left"}
(Left)
{:else if key.variant === "right"}
(Right)
{/if}
</b>
{#if key.description}
<i>{key.description}</i>
{/if}
</div>
<span class:icon={!!key.icon} class="key">{key.icon || key.id || `0x${key.code.toString(16)}`}</span>
{:else}
<span class="key">0x{key.toString(16)}</span>
{/if}
</button>
<style lang="scss">
button {
display: flex;
gap: 4px;
align-items: center;
width: 100%;
margin: 0;
padding: 8px;
font-family: "Noto Sans Mono", monospace;
color: inherit;
background: transparent;
border: none;
}
.title {
display: flex;
flex: 1;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
text-align: start;
}
.key {
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
padding: 4px;
font-weight: 600;
border: 1px solid currentcolor;
border-radius: 4px;
}
</style>

View File

@@ -3,52 +3,16 @@
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
import ActionSelector from "$lib/components/layout/ActionSelector.svelte"
import {popup} from "$lib/popup"
import ActionListItem from "$lib/components/ActionListItem.svelte"
export let id: number = 0
</script>
<table>
{#each $layout as layer, i}
{@const action = KEYMAP_CODES[layer[id]]}
<tr>
<th class="icon">counter_{i + 1}</th>
<td
><button use:popup={ActionSelector}
>{action?.title || action?.id} <span class="icon">edit</span></button
></td
>
<ActionListItem id={layer[id]} />
</tr>
{/each}
</table>
<style lang="scss">
span.icon {
opacity: 0;
transition: opacity 250ms ease;
}
button {
cursor: pointer;
display: flex;
gap: 4px;
align-items: center;
justify-content: space-between;
width: 100%;
font-family: "Noto Sans Mono", monospace;
font-weight: 600;
color: var(--md-sys-color-on-surface);
background: transparent;
border: none;
border-radius: 8px;
transition: all 250ms ease;
}
button:hover > span.icon {
opacity: 1;
}
</style>

View File

@@ -1,9 +1,15 @@
<script>
<script lang="ts">
import {serialPort} from "$lib/serial/connection"
import LayoutCC1 from "$lib/components/layout/LayoutCC1.svelte"
$: device = $serialPort?.device ?? "ONE"
let activeLayer = 0
const layers = [
["Numeric Layer", "123", 1],
["Primary Layer", "abc", 0],
["Function Layer", "function", 2],
] as const
</script>
<div>
@@ -13,7 +19,7 @@
</select>
<fieldset>
{#each [["Numeric Layer", "123", 1], ["Primary Layer", "abc", 0], ["Function Layer", "function", 2]] as [title, icon, value]}
{#each layers as [title, icon, value]}
<button
{title}
class="icon"

View File

@@ -29,7 +29,7 @@
</script>
<div class="radial {type}">
{#each [keys.n, keys.e, keys.s, keys.w] as id, quadrant}
{#each [keys.n, keys.e, keys.s, keys.w, keys.d] as id, quadrant}
{@const actions = getActions(id, $layout)}
<button
use:editableLayout={{id, quadrant}}
@@ -154,6 +154,21 @@
&:nth-child(4) {
clip-path: polygon(50% 50%, 0 0, 0 100%);
}
&:last-child {
top: 50%;
left: 50%;
translate: -50% -50%;
overflow: hidden;
width: 25cqw;
height: 25cqh;
border-radius: 50%;
mask-image: none;
}
}
.secondary > button {

View File

@@ -1,8 +0,0 @@
import type {Chord} from "$lib/serial/chord"
import type {CharaLayout} from "$lib/serialization/layout"
export interface Profile {
name: string
layout: CharaLayout
chords: Chord[]
}

View File

@@ -1,4 +0,0 @@
import type {Index} from "flexsearch"
import FlexSearch from "flexsearch"
export function createIndex() {}

View File

@@ -1,10 +1,7 @@
$padding: 16px;
.tippy-box[data-theme~="surface-variant"] {
// overflow: hidden;
color: var(--md-sys-color-on-surface-variant);
background-color: var(--md-sys-color-surface-variant);
filter: drop-shadow(0 0 12px #000a);
border-radius: calc(24px + $padding);
@@ -26,3 +23,13 @@ $padding: 16px;
}
}
}
.tippy-box[data-theme~="search-completion"] {
filter: none;
border-radius: 0 0 16px 16px;
overflow: hidden;
.tippy-content {
padding: 0;
}
}

View File

@@ -50,7 +50,6 @@
initLocalStorage()
if (pwaInfo) {
// noinspection TypeScriptCheckImport
const {registerSW} = await import("virtual:pwa-register")
registerSW({
immediate: true,

View File

@@ -4,10 +4,8 @@
import Index from "flexsearch"
import {tick} from "svelte"
import type {Chord} from "$lib/serial/chord"
import tippy from "tippy.js"
import {calculateChordCoverage} from "$lib/chords/coverage"
import type {MouseEventHandler} from "svelte/elements"
import LL from "../../../i18n/i18n-svelte"
import {actionAutocomplete} from "$lib/action-autocomplete"
$: searchIndex = $chords?.length > 0 ? buildIndex($chords) : undefined
@@ -29,10 +27,6 @@
})
}
function sort(event: Event) {
tippy(event.target as HTMLInputElement, {})
}
$: items = searchFilter?.map(it => [$chords[it], it] as const) ?? $chords.map((it, i) => [it, i] as const)
</script>
@@ -40,15 +34,22 @@
<title>Chord Manager</title>
</svelte:head>
<div>
<input
type="search"
placeholder={$LL.configure.chords.search.PLACEHOLDER($chords.length)}
use:actionAutocomplete
/>
</div>
<!--
{#if searchIndex}
<input
on:input={search}
type="search"
placeholder={$LL.configure.chords.search.PLACEHOLDER($chords.length)}
/>
{/if}
<button class="icon" on:click={sort}>sort</button>
<button class="icon">filter_list</button>
{/if}-->
<section>
<table>
@@ -72,29 +73,21 @@
</tr>
{/each}
</table>
<div>
<p>15 Duplicate Chords</p>
<p>12 Chords use</p>
{#await calculateChordCoverage($chords) then { missing, coverage }}
<p>{(coverage * 100).toFixed(1)}% of English 200</p>
{/await}
</div>
</section>
<style lang="scss">
input[type="search"] {
width: 300px;
width: 512px;
margin-block-start: 16px;
padding-block: 8px;
padding-inline: 32px;
padding-inline: 16px;
font-size: 16px;
color: var(--md-sys-color-on-surface-variant);
text-align: center;
color: inherit;
background: var(--md-sys-color-surface-variant);
clip-path: polygon(0 0, 100% 0, 90% 100%, 10% 100%);
filter: brightness(80%);
border: none;
background: none;
border: 0 solid var(--md-sys-color-surface-variant);
border-bottom-width: 1px;
transition: all 250ms ease;
@@ -104,7 +97,7 @@
}
&:focus {
filter: brightness(90%);
border-color: var(--md-sys-color-primary);
outline: none;
}
}

View File

@@ -13,11 +13,11 @@
}
})
async function shareLayout(event) {
async function shareLayout(event: Event) {
const url = new URL(window.location.href)
url.searchParams.set("layout", await layoutAsUrlComponent($layout))
await navigator.clipboard.writeText(url.toString())
tippy(event.target, {
tippy(event.target as HTMLElement, {
content: "Share url copied!",
delay: [0, 1000000],
onHidden(instance) {