keyboard stuff, styling things

This commit is contained in:
2023-09-25 18:12:34 +02:00
parent c93246ee8c
commit d8f0679233
20 changed files with 247 additions and 236 deletions

7
package-lock.json generated
View File

@@ -35,6 +35,7 @@
"flexsearch": "^0.7.31",
"fontkit": "^2.0.2",
"glob": "^10.3.4",
"hotkeys-js": "^3.12.0",
"jsdom": "^22.1.0",
"npm-run-all": "^4.1.5",
"patch-package": "^8.0.0",
@@ -6273,6 +6274,12 @@
"node": ">=10"
}
},
"node_modules/hotkeys-js": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.12.0.tgz",
"integrity": "sha512-Z+N573ycUKIGwFYS3ID1RzMJiGmtWMGKMiaNLyJS8B1ei+MllF4ZYmKS2T0kMWBktOz+WZLVNikftEgnukOrXg==",
"dev": true
},
"node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",

View File

@@ -55,6 +55,7 @@
"flexsearch": "^0.7.31",
"fontkit": "^2.0.2",
"glob": "^10.3.4",
"hotkeys-js": "^3.12.0",
"jsdom": "^22.1.0",
"npm-run-all": "^4.1.5",
"patch-package": "^8.0.0",
@@ -80,4 +81,4 @@
"vitest": "^0.34.4"
},
"type": "module"
}
}

View File

@@ -22,7 +22,7 @@
<i>{key.description}</i>
{/if}
</div>
<span class:icon={!!key.icon} class="key">{key.icon || key.id || `0x${key.code.toString(16)}`}</span>
<kbd class:icon={!!key.icon}>{key.icon || key.id || `0x${key.code.toString(16)}`}</kbd>
{:else}
<span class="key">0x{key.toString(16)}</span>
{/if}
@@ -61,18 +61,4 @@
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

View File

@@ -16,7 +16,13 @@
<form on:submit={submit}>
<div bind:this={io} class="io">
{#each $serialLog as { type, value }}
<p class={type} transition:slide>{value}</p>
{#if type === "input"}
<code transition:slide>{value}</code>
{:else if type === "output"}
<samp transition:slide>{value}</samp>
{:else}
<p transition:slide>{value}</p>
{/if}
{/each}
<div class="anchor" />
</div>
@@ -111,17 +117,15 @@
height: 1px;
}
code,
samp,
p {
display: block;
overflow-anchor: none;
margin-block: 0.15rem;
}
p.input {
margin-block-end: 0.25rem;
font-weight: bold;
}
p.system {
p {
display: flex;
justify-content: center;
@@ -134,8 +138,9 @@
border-radius: 8px;
}
p.input::before {
code::before {
content: "> ";
margin-block-end: 0.25rem;
font-weight: 900;
color: var(--md-sys-color-primary);
}

View File

@@ -0,0 +1,22 @@
<script lang="ts">
export let title: string | undefined
export let shortcut: string | undefined
</script>
{#if title}
<p>{title}</p>
{/if}
{#if shortcut}
<kbd>
{#each shortcut.split("+") as key}
<kbd>{key}</kbd>
{/each}
</kbd>
{/if}
<style lang="scss">
p {
margin-block: 0;
}
</style>

View File

@@ -5,6 +5,7 @@
import {createEventDispatcher} from "svelte"
import ActionListItem from "$lib/components/ActionListItem.svelte"
import LL from "../../../i18n/i18n-svelte"
import {action} from "$lib/title"
export let currentAction: number
@@ -38,10 +39,6 @@
function keyboardNavigation(event: KeyboardEvent) {
if (event.shiftKey && event.key === "Enter") {
dispatch("select", exact)
} else if (event.shiftKey && event.key === "Escape") {
dispatch("select", 0)
} else if (event.key === "Escape") {
dispatch("close")
} else if (event.key === "ArrowDown") {
const element =
resultList.querySelector("li:focus-within")?.nextSibling ?? resultList.querySelector("li:not(.exact)")
@@ -88,11 +85,14 @@
}}
placeholder={$LL.actionSearch.PLACEHOLDER()}
/>
<button on:click={() => select(0)}
><div><span class="icon key-hint">shift</span>+<span class="key-hint">ESC</span></div>
{$LL.actionSearch.DELETE()}</button
<button on:click={() => select(0)} use:action={{shortcut: "shift+esc"}}
>{$LL.actionSearch.DELETE()}</button
>
<button
use:action={{title: $LL.modal.CLOSE(), shortcut: "esc"}}
class="icon"
on:click={() => dispatch("close")}>close</button
>
<button title={$LL.modal.CLOSE()} class="icon" on:click={() => dispatch("close")}>close</button>
</div>
<aside>
<h3>{$LL.actionSearch.CURRENT_ACTION()}</h3>
@@ -101,11 +101,7 @@
<ul bind:this={resultList}>
{#if exact !== undefined}
<li class="exact">
<i
>Exact match&nbsp;<span class="icon key-hint">shift</span>+<span class="icon key-hint"
>keyboard_return</span
></i
>
<i>Exact match</i>
<ActionListItem id={exact} on:click={() => select(exact)} />
</li>
{/if}
@@ -165,38 +161,6 @@
gap: 4px;
align-items: center;
margin-inline: 16px;
> button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: fit-content;
color: currentcolor;
background: none;
border: none;
border-radius: 100%;
&:not(.icon) {
font-family: inherit;
font-weight: bold;
}
& > div {
display: flex;
gap: 2px;
align-items: center;
}
&:last-child {
aspect-ratio: 1;
color: var(--md-sys-color-on-surface-variant);
background: var(--md-sys-color-surface-variant);
}
}
}
.content {
@@ -281,26 +245,4 @@
border-radius: 0 0 8px 8px;
}
}
.key-hint {
display: inline-flex;
align-items: center;
justify-content: center;
height: 20px;
margin-block: 6px;
padding: 2px;
font-size: 14px;
font-weight: normal;
color: currentcolor;
border: 1px solid currentcolor;
border-radius: 4px;
&.icon {
padding: 0;
font-size: 18px;
}
}
</style>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import {serialPort} from "$lib/serial/connection"
import LayoutCC1 from "$lib/components/layout/LayoutCC1.svelte"
import {action} from "$lib/title"
$: device = $serialPort?.device ?? "ONE"
let activeLayer = 0
@@ -16,8 +17,8 @@
<fieldset>
{#each layers as [title, icon, value]}
<button
{title}
class="icon"
use:action={{title, shortcut: `alt+${value + 1}`}}
on:click={() => (activeLayer = value)}
class:active={activeLayer === value}
>
@@ -71,13 +72,19 @@
outline: 8px solid var(--md-sys-color-background);
}
&:first-child,
&:last-child {
aspect-ratio: unset;
height: unset;
}
&:first-child {
padding-inline-end: 16px;
padding-inline: 4px 16px;
border-radius: 16px 0 0 16px;
}
&:last-child {
padding-inline-start: 16px;
padding-inline: 16px 4px;
border-radius: 0 16px 16px 0;
}

View File

@@ -121,6 +121,8 @@
}
button {
all: unset;
cursor: pointer;
position: absolute;
@@ -131,7 +133,7 @@
height: 100cqh;
padding: 0;
font-family: "Noto Sans Mono", monospace;
font-family: inherit;
font-size: 16px;
font-weight: 900;
color: var(--md-sys-color-on-surface-variant);

35
src/lib/style/_kbd.scss Normal file
View File

@@ -0,0 +1,35 @@
kbd {
display: inline-flex;
align-items: center;
justify-content: center;
height: 20px;
margin-block: 6px;
padding: 4px;
font-size: 14px;
font-weight: normal;
color: currentcolor;
border: 1px solid currentcolor;
border-radius: 4px;
&.icon {
padding: 2px;
font-size: 18px;
}
&:has(> kbd) {
gap: 4px;
padding: 0;
border: none;
}
> kbd {
padding: 2px;
&.icon {
padding: 0;
}
}
}

View File

@@ -0,0 +1,69 @@
a {
text-decoration: none;
}
a,
label:has(input),
button {
cursor: pointer;
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
width: max-content;
height: 48px;
padding-block: 8px;
padding-inline: 16px;
font-family: inherit;
font-weight: 600;
color: currentcolor;
background: transparent;
border: none;
border-radius: 32px;
transition: all 250ms ease;
&.icon {
aspect-ratio: 1;
padding-block: 0;
padding-inline: 0;
font-size: 24px;
border-radius: 50%;
}
&.primary {
color: var(--md-sys-color-on-primary);
background: var(--md-sys-color-primary);
}
}
label:has(input):hover,
.button:hover:not(:active),
a:hover:not(:active),
button:hover:not(:active) {
filter: brightness(70%);
transition: filter 250ms ease;
&:has(:checked),
&.active {
filter: brightness(120%);
}
&:disabled,
&.disabled {
opacity: 0.5;
filter: none;
}
}
.disabled,
:disabled {
pointer-events: none;
opacity: 0.5;
}

8
src/lib/style/theme.scss Normal file
View File

@@ -0,0 +1,8 @@
@import "./form/button";
@import "./form/toggle";
@import "./kbd";
* {
box-sizing: border-box;
appearance: none;
}

View File

@@ -24,6 +24,13 @@ $padding: 16px;
}
}
.tippy-box[data-theme~="tooltip"] {
color: var(--md-sys-color-on-background);
background-color: var(--md-sys-color-background);
border: 1px solid var(--md-sys-color-outline);
border-radius: 8px;
}
.tippy-box[data-theme~="search-completion"] {
overflow: hidden;
filter: none;

42
src/lib/title.ts Normal file
View File

@@ -0,0 +1,42 @@
import type {Action} from "svelte/action"
import tippy from "tippy.js"
import type {SvelteComponent} from "svelte"
import Tooltip from "$lib/components/Tooltip.svelte"
import hotkeys from "hotkeys-js"
export const action: Action<HTMLElement, {title?: string; shortcut?: string}> = (
node: HTMLElement,
{title, shortcut},
) => {
let component: SvelteComponent | undefined
const tooltip = tippy(node, {
arrow: false,
theme: "tooltip",
animation: "fade",
delay: [500, 0],
onShow(instance) {
component ??= new Tooltip({
target: instance.popper.querySelector(".tippy-content") as HTMLElement,
props: {title, shortcut},
})
},
onHidden() {
component?.$destroy()
component = undefined
},
})
if (shortcut) {
hotkeys(shortcut, function (keyboardEvent) {
keyboardEvent.preventDefault()
node.click()
})
}
return {
destroy() {
tooltip.destroy()
hotkeys.unbind(shortcut)
},
}
}

View File

@@ -3,7 +3,7 @@
import "$lib/fonts/material-symbols-rounded.scss"
import "$lib/style/scrollbar.scss"
import "$lib/style/tippy.scss"
import "$lib/style/toggle.scss"
import "$lib/style/theme.scss"
import {onMount} from "svelte"
import {applyTheme, argbFromHex, themeFromSourceColor} from "@material/material-color-utilities"
import Navigation from "./Navigation.svelte"
@@ -76,34 +76,6 @@
{/if}
<style lang="scss" global>
* {
box-sizing: border-box;
appearance: none;
}
a {
color: var(--md-sys-color-tertiary);
}
label:has(input):hover,
.button:hover:not(:active),
a:hover:not(:active),
button:hover:not(:active) {
filter: brightness(70%);
transition: filter 250ms ease;
&:has(:checked),
&.active {
filter: brightness(120%);
}
&:disabled,
&.disabled {
opacity: 0.5;
filter: none;
}
}
body {
overflow: hidden;
display: flex;

View File

@@ -95,34 +95,4 @@
display: flex;
gap: 4px;
}
.button,
button {
cursor: pointer;
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
width: max-content;
height: 48px;
padding-block: 8px;
padding-inline: 16px;
font-family: "Noto Sans Mono", monospace;
font-weight: 600;
color: var(--md-sys-color-on-background);
background: transparent;
border: none;
border-radius: 32px;
transition: all 250ms ease;
}
button.primary {
color: var(--md-sys-color-on-primary);
background: var(--md-sys-color-primary);
}
</style>

View File

@@ -1,5 +1,6 @@
<script>
import {page} from "$app/stores"
import {action} from "$lib/title"
import LL from "../i18n/i18n-svelte"
$: paths = [
@@ -10,8 +11,8 @@
</script>
<nav>
{#each paths as { href, title, icon }}
<a {href} class:active={$page.url.pathname.startsWith(href)}>
{#each paths as { href, title, icon }, i}
<a {href} class:active={$page.url.pathname.startsWith(href)} use:action={{shortcut: `shift+${i + 1}`}}>
<span class="icon">{icon}</span>
{title}
</a>
@@ -27,30 +28,13 @@
padding: 8px;
color: var(--md-sys-color-on-surface-variant);
background: var(--md-sys-color-surface-variant);
border: none;
border-radius: 32px;
}
a {
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
margin: 0;
padding: 8px;
padding-inline: 16px;
font-weight: 600;
color: var(--md-sys-color-on-surface-variant);
text-decoration: none;
border-radius: 24px;
transition: all 250ms ease;
}
a.active {
--icon-fill: 1;

View File

@@ -153,43 +153,6 @@
background: var(--md-sys-color-secondary);
}
a,
button {
cursor: pointer;
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
height: 48px;
padding: 8px;
padding-inline-end: 16px;
font-size: 1rem;
color: var(--md-sys-color-on-background);
text-decoration: none;
background: transparent;
border: none;
border-radius: 32px;
transition: all 250ms ease;
&.icon {
aspect-ratio: 1;
padding-inline-end: 8px;
font-size: 24px;
border-radius: 50%;
}
}
a.disabled,
button:disabled {
cursor: default;
opacity: 0.5;
}
button:active:not(:disabled) {
color: var(--md-sys-color-on-surface-variant);
background: var(--md-sys-color-surface-variant);

View File

@@ -3,6 +3,7 @@
import {changes} from "$lib/serial/connection"
import type {Change} from "$lib/serial/connection"
import {fly} from "svelte/transition"
import {action} from "$lib/title"
function undo() {
redoQueue = [$changes.pop()!, ...redoQueue]
@@ -24,39 +25,27 @@
}
</script>
<button title={$LL.saveActions.UNDO()} class="icon" disabled={$changes.length === 0} on:click={undo}
>undo</button
<button
use:action={{title: $LL.saveActions.UNDO(), shortcut: "ctrl+z"}}
class="icon"
disabled={$changes.length === 0}
on:click={undo}>undo</button
>
<button title={$LL.saveActions.REDO()} class="icon" disabled={redoQueue.length === 0} on:click={redo}
>redo</button
<button
use:action={{title: $LL.saveActions.REDO(), shortcut: "ctrl+y"}}
class="icon"
disabled={redoQueue.length === 0}
on:click={redo}>redo</button
>
<div class="separator" />
<button title={$LL.saveActions.SAVE()} class="icon">save</button>
<button use:action={{title: $LL.saveActions.SAVE(), shortcut: "ctrl+shift+s"}} class="icon">save</button>
{#if $changes.length !== 0}
<button class="click-me" transition:fly={{x: 8}}
<button class="click-me" transition:fly={{x: 8}} use:action={{shortcut: "ctrl+s"}}
><span class="icon">bolt</span>{$LL.saveActions.APPLY()}</button
>
{/if}
<style lang="scss">
button {
cursor: pointer;
padding: 0;
color: currentcolor;
background: none;
border: none;
transition: all 250ms ease;
}
:disabled {
pointer-events: none;
opacity: 0.5;
}
.click-me {
display: flex;
align-items: center;