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", "flexsearch": "^0.7.31",
"fontkit": "^2.0.2", "fontkit": "^2.0.2",
"glob": "^10.3.4", "glob": "^10.3.4",
"hotkeys-js": "^3.12.0",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
@@ -6273,6 +6274,12 @@
"node": ">=10" "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": { "node_modules/html-encoding-sniffer": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "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", "flexsearch": "^0.7.31",
"fontkit": "^2.0.2", "fontkit": "^2.0.2",
"glob": "^10.3.4", "glob": "^10.3.4",
"hotkeys-js": "^3.12.0",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",

View File

@@ -22,7 +22,7 @@
<i>{key.description}</i> <i>{key.description}</i>
{/if} {/if}
</div> </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} {:else}
<span class="key">0x{key.toString(16)}</span> <span class="key">0x{key.toString(16)}</span>
{/if} {/if}
@@ -61,18 +61,4 @@
text-align: 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> </style>

View File

View File

@@ -16,7 +16,13 @@
<form on:submit={submit}> <form on:submit={submit}>
<div bind:this={io} class="io"> <div bind:this={io} class="io">
{#each $serialLog as { type, value }} {#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} {/each}
<div class="anchor" /> <div class="anchor" />
</div> </div>
@@ -111,17 +117,15 @@
height: 1px; height: 1px;
} }
code,
samp,
p { p {
display: block;
overflow-anchor: none; overflow-anchor: none;
margin-block: 0.15rem; margin-block: 0.15rem;
} }
p.input { p {
margin-block-end: 0.25rem;
font-weight: bold;
}
p.system {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -134,8 +138,9 @@
border-radius: 8px; border-radius: 8px;
} }
p.input::before { code::before {
content: "> "; content: "> ";
margin-block-end: 0.25rem;
font-weight: 900; font-weight: 900;
color: var(--md-sys-color-primary); 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 {createEventDispatcher} from "svelte"
import ActionListItem from "$lib/components/ActionListItem.svelte" import ActionListItem from "$lib/components/ActionListItem.svelte"
import LL from "../../../i18n/i18n-svelte" import LL from "../../../i18n/i18n-svelte"
import {action} from "$lib/title"
export let currentAction: number export let currentAction: number
@@ -38,10 +39,6 @@
function keyboardNavigation(event: KeyboardEvent) { function keyboardNavigation(event: KeyboardEvent) {
if (event.shiftKey && event.key === "Enter") { if (event.shiftKey && event.key === "Enter") {
dispatch("select", exact) 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") { } else if (event.key === "ArrowDown") {
const element = const element =
resultList.querySelector("li:focus-within")?.nextSibling ?? resultList.querySelector("li:not(.exact)") resultList.querySelector("li:focus-within")?.nextSibling ?? resultList.querySelector("li:not(.exact)")
@@ -88,11 +85,14 @@
}} }}
placeholder={$LL.actionSearch.PLACEHOLDER()} placeholder={$LL.actionSearch.PLACEHOLDER()}
/> />
<button on:click={() => select(0)} <button on:click={() => select(0)} use:action={{shortcut: "shift+esc"}}
><div><span class="icon key-hint">shift</span>+<span class="key-hint">ESC</span></div> >{$LL.actionSearch.DELETE()}</button
{$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> </div>
<aside> <aside>
<h3>{$LL.actionSearch.CURRENT_ACTION()}</h3> <h3>{$LL.actionSearch.CURRENT_ACTION()}</h3>
@@ -101,11 +101,7 @@
<ul bind:this={resultList}> <ul bind:this={resultList}>
{#if exact !== undefined} {#if exact !== undefined}
<li class="exact"> <li class="exact">
<i <i>Exact match</i>
>Exact match&nbsp;<span class="icon key-hint">shift</span>+<span class="icon key-hint"
>keyboard_return</span
></i
>
<ActionListItem id={exact} on:click={() => select(exact)} /> <ActionListItem id={exact} on:click={() => select(exact)} />
</li> </li>
{/if} {/if}
@@ -165,38 +161,6 @@
gap: 4px; gap: 4px;
align-items: center; align-items: center;
margin-inline: 16px; 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 { .content {
@@ -281,26 +245,4 @@
border-radius: 0 0 8px 8px; 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> </style>

View File

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

View File

@@ -121,6 +121,8 @@
} }
button { button {
all: unset;
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
@@ -131,7 +133,7 @@
height: 100cqh; height: 100cqh;
padding: 0; padding: 0;
font-family: "Noto Sans Mono", monospace; font-family: inherit;
font-size: 16px; font-size: 16px;
font-weight: 900; font-weight: 900;
color: var(--md-sys-color-on-surface-variant); 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"] { .tippy-box[data-theme~="search-completion"] {
overflow: hidden; overflow: hidden;
filter: none; 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/fonts/material-symbols-rounded.scss"
import "$lib/style/scrollbar.scss" import "$lib/style/scrollbar.scss"
import "$lib/style/tippy.scss" import "$lib/style/tippy.scss"
import "$lib/style/toggle.scss" import "$lib/style/theme.scss"
import {onMount} from "svelte" import {onMount} from "svelte"
import {applyTheme, argbFromHex, themeFromSourceColor} from "@material/material-color-utilities" import {applyTheme, argbFromHex, themeFromSourceColor} from "@material/material-color-utilities"
import Navigation from "./Navigation.svelte" import Navigation from "./Navigation.svelte"
@@ -76,34 +76,6 @@
{/if} {/if}
<style lang="scss" global> <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 { body {
overflow: hidden; overflow: hidden;
display: flex; display: flex;

View File

@@ -95,34 +95,4 @@
display: flex; display: flex;
gap: 4px; 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> </style>

View File

@@ -1,5 +1,6 @@
<script> <script>
import {page} from "$app/stores" import {page} from "$app/stores"
import {action} from "$lib/title"
import LL from "../i18n/i18n-svelte" import LL from "../i18n/i18n-svelte"
$: paths = [ $: paths = [
@@ -10,8 +11,8 @@
</script> </script>
<nav> <nav>
{#each paths as { href, title, icon }} {#each paths as { href, title, icon }, i}
<a {href} class:active={$page.url.pathname.startsWith(href)}> <a {href} class:active={$page.url.pathname.startsWith(href)} use:action={{shortcut: `shift+${i + 1}`}}>
<span class="icon">{icon}</span> <span class="icon">{icon}</span>
{title} {title}
</a> </a>
@@ -27,30 +28,13 @@
padding: 8px; padding: 8px;
color: var(--md-sys-color-on-surface-variant);
background: var(--md-sys-color-surface-variant); background: var(--md-sys-color-surface-variant);
border: none; border: none;
border-radius: 32px; 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 { a.active {
--icon-fill: 1; --icon-fill: 1;

View File

@@ -153,43 +153,6 @@
background: var(--md-sys-color-secondary); 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) { button:active:not(:disabled) {
color: var(--md-sys-color-on-surface-variant); color: var(--md-sys-color-on-surface-variant);
background: var(--md-sys-color-surface-variant); background: var(--md-sys-color-surface-variant);

View File

@@ -3,6 +3,7 @@
import {changes} from "$lib/serial/connection" import {changes} from "$lib/serial/connection"
import type {Change} from "$lib/serial/connection" import type {Change} from "$lib/serial/connection"
import {fly} from "svelte/transition" import {fly} from "svelte/transition"
import {action} from "$lib/title"
function undo() { function undo() {
redoQueue = [$changes.pop()!, ...redoQueue] redoQueue = [$changes.pop()!, ...redoQueue]
@@ -24,39 +25,27 @@
} }
</script> </script>
<button title={$LL.saveActions.UNDO()} class="icon" disabled={$changes.length === 0} on:click={undo} <button
>undo</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} <button
>redo</button use:action={{title: $LL.saveActions.REDO(), shortcut: "ctrl+y"}}
class="icon"
disabled={redoQueue.length === 0}
on:click={redo}>redo</button
> >
<div class="separator" /> <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} {#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 ><span class="icon">bolt</span>{$LL.saveActions.APPLY()}</button
> >
{/if} {/if}
<style lang="scss"> <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 { .click-me {
display: flex; display: flex;
align-items: center; align-items: center;