mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-10-03 00:03:02 +00:00
feat: qol improvements
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { preference } from "$lib/preferences";
|
||||
import { preference, userPreferences } from "$lib/preferences";
|
||||
import { initSerial } from "$lib/serial/connection";
|
||||
import {
|
||||
getPortName,
|
||||
@@ -9,13 +9,26 @@
|
||||
} from "$lib/serial/device";
|
||||
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
|
||||
import { onMount } from "svelte";
|
||||
import { persistentWritable } from "$lib/storage";
|
||||
|
||||
let ports = $state<SerialPort[]>([]);
|
||||
let element: HTMLDivElement | undefined = $state();
|
||||
|
||||
onMount(() => {
|
||||
refreshPorts();
|
||||
});
|
||||
|
||||
let hasDiscoveredAutoConnect = persistentWritable(
|
||||
"hasDiscoveredAutoConnect",
|
||||
false,
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if ($userPreferences.backup || $userPreferences.autoConnect) {
|
||||
$hasDiscoveredAutoConnect = true;
|
||||
}
|
||||
});
|
||||
|
||||
async function refreshPorts() {
|
||||
ports = await navigator.serial.getPorts();
|
||||
}
|
||||
@@ -28,62 +41,79 @@
|
||||
await showConnectionFailedDialog(String(error));
|
||||
}
|
||||
}
|
||||
|
||||
function closePopover() {
|
||||
element?.closest<HTMLElement>("[popover]")?.hidePopover();
|
||||
}
|
||||
|
||||
async function connectCC0(event: MouseEvent) {
|
||||
const { fetchCCOS } = await import("$lib/ccos/ccos");
|
||||
closePopover();
|
||||
const ccos = await fetchCCOS();
|
||||
if (ccos) {
|
||||
connect(ccos, !event.shiftKey);
|
||||
}
|
||||
}
|
||||
|
||||
async function connectDevice(event: MouseEvent) {
|
||||
const port = await navigator.serial.requestPort({
|
||||
filters: event.shiftKey ? [] : [...PORT_FILTERS.values()],
|
||||
});
|
||||
if (!port) return;
|
||||
closePopover();
|
||||
refreshPorts();
|
||||
connect(port, true);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="device-list">
|
||||
<fieldset>
|
||||
<label
|
||||
><input type="checkbox" use:preference={"autoConnect"} />
|
||||
<div class="title">{$LL.deviceManager.AUTO_CONNECT()}</div>
|
||||
</label>
|
||||
<div
|
||||
bind:this={element}
|
||||
class="device-list"
|
||||
onmouseenter={() => refreshPorts()}
|
||||
role="region"
|
||||
>
|
||||
{#if ports.length === 1}
|
||||
<fieldset class:promote={!$hasDiscoveredAutoConnect}>
|
||||
<label
|
||||
><input type="checkbox" use:preference={"autoConnect"} />
|
||||
<div class="title">{$LL.deviceManager.AUTO_CONNECT()}</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
><input type="checkbox" use:preference={"backup"} />
|
||||
<div class="title">{@html $LL.backup.AUTO_BACKUP()}</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
<button
|
||||
onclick={async (event) => {
|
||||
const { fetchCCOS } = await import("$lib/ccos/ccos");
|
||||
const ccos = await fetchCCOS();
|
||||
if (ccos) {
|
||||
connect(ccos, !event.shiftKey);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span class="icon">history</span>
|
||||
CC0</button
|
||||
>
|
||||
{#each ports as port}
|
||||
<div class="device">
|
||||
<button
|
||||
onclick={(event) => {
|
||||
connect(port, !event.shiftKey);
|
||||
}}
|
||||
>
|
||||
<span class="icon">history</span>
|
||||
{getPortName(port)}</button
|
||||
>
|
||||
<button
|
||||
class="error"
|
||||
onclick={() => {
|
||||
port.forget();
|
||||
refreshPorts();
|
||||
}}><span class="icon">link_off</span></button
|
||||
>
|
||||
<label
|
||||
><input type="checkbox" use:preference={"backup"} />
|
||||
<div class="title">{@html $LL.backup.AUTO_BACKUP()}</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
{/if}
|
||||
{#if ports.length !== 0}
|
||||
<h4>Recent Devices</h4>
|
||||
<div class="devices">
|
||||
<div class="device">
|
||||
<button onclick={connectCC0}> CC0</button>
|
||||
</div>
|
||||
{#each ports as port}
|
||||
<div class="device">
|
||||
<button
|
||||
onclick={(event) => {
|
||||
connect(port, !event.shiftKey);
|
||||
}}
|
||||
>
|
||||
{getPortName(port)}</button
|
||||
>
|
||||
<button
|
||||
class="error"
|
||||
onclick={() => {
|
||||
port.forget();
|
||||
refreshPorts();
|
||||
}}><span class="icon">visibility_off</span> Hide</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
<div class="pair">
|
||||
<button
|
||||
onclick={async (event) => {
|
||||
const port = await navigator.serial.requestPort({
|
||||
filters: event.shiftKey ? [] : [...PORT_FILTERS.values()],
|
||||
});
|
||||
if (!port) return;
|
||||
refreshPorts();
|
||||
connect(port, true);
|
||||
}}
|
||||
class="primary"><span class="icon">add</span>Pair</button
|
||||
<button onclick={connectDevice} class="primary"
|
||||
><span class="icon">add</span>Connect</button
|
||||
>
|
||||
<a href="/ccos/zero_wasm/"><span class="icon">add</span>Virtual Device</a>
|
||||
</div>
|
||||
@@ -93,7 +123,8 @@
|
||||
button,
|
||||
a {
|
||||
padding: 10px;
|
||||
height: 32px;
|
||||
padding-inline-end: 16px;
|
||||
height: 38px;
|
||||
font-size: 12px;
|
||||
|
||||
.icon {
|
||||
@@ -101,18 +132,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-block-start: 16px;
|
||||
margin-block-end: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.pair {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.devices {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.device {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
justify-content: flex-start;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,12 +175,57 @@
|
||||
gap: 8px;
|
||||
appearance: none;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes attention {
|
||||
0%,
|
||||
100% {
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes swoosh {
|
||||
0% {
|
||||
transform: translateX(-200%) skewX(-20deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(200%) skewX(-20deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.promote {
|
||||
label:not(:has(input:checked)) {
|
||||
animation: attention 1s ease;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
animation: swoosh 1s ease forwards;
|
||||
background-color: var(--md-sys-color-surface-variant);
|
||||
width: 25%;
|
||||
height: 200%;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
@@ -77,10 +77,15 @@
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="sync-box">
|
||||
<div
|
||||
class="sync-box"
|
||||
class:primary={!$serialPort}
|
||||
class:attention={$syncStatus !== "done"}
|
||||
>
|
||||
{#if !$serialPort}
|
||||
<button
|
||||
class="warning"
|
||||
class="no-connection"
|
||||
id="connect-button"
|
||||
popovertarget="connect-popup"
|
||||
transition:slide={{ axis: "x" }}
|
||||
><span class="icon">usb</span>{$LL.deviceManager.CONNECT()}</button
|
||||
@@ -103,7 +108,7 @@
|
||||
>
|
||||
{/if}
|
||||
|
||||
{#if $syncStatus !== "done"}
|
||||
{#if $syncStatus === "downloading"}
|
||||
<progress
|
||||
transition:fade
|
||||
max={$syncProgress?.max ?? 1}
|
||||
@@ -168,26 +173,54 @@
|
||||
</footer>
|
||||
|
||||
<style lang="scss">
|
||||
@keyframes attention {
|
||||
0%,
|
||||
100% {
|
||||
filter: brightness(0.5);
|
||||
}
|
||||
50% {
|
||||
filter: brightness(1);
|
||||
}
|
||||
}
|
||||
|
||||
$sync-border-radius: 16px;
|
||||
|
||||
.sync-box {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
translate: 0;
|
||||
transition: all 250ms ease;
|
||||
border-radius: 24px;
|
||||
overflow: hidden;
|
||||
|
||||
button {
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
translate: 0 -32px;
|
||||
background: var(--md-sys-color-primary);
|
||||
color: var(--md-sys-color-on-primary);
|
||||
}
|
||||
|
||||
&.attention {
|
||||
animation: attention 2s infinite;
|
||||
border-radius: $sync-border-radius;
|
||||
color: var(--md-sys-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
progress {
|
||||
$inset: 8px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 0;
|
||||
left: 16px;
|
||||
opacity: 0.3;
|
||||
z-index: -1;
|
||||
border-radius: 4px;
|
||||
width: calc(100% - 32px);
|
||||
height: 8px;
|
||||
inset: $inset;
|
||||
border-radius: #{$sync-border-radius - $inset};
|
||||
width: calc(100% - $inset * 2);
|
||||
height: calc(100% - $inset * 2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -241,7 +274,6 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
opacity: 0.4;
|
||||
padding: 8px;
|
||||
padding-inline-end: 16px;
|
||||
padding-block-start: 0;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
[
|
||||
{
|
||||
href: "/config/settings/",
|
||||
icon: "cable",
|
||||
title: "Device",
|
||||
icon: "tune",
|
||||
title: "Settings",
|
||||
primary: true,
|
||||
},
|
||||
{ href: "/config/chords/", icon: "dictionary", title: "Library" },
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { fade, slide } from "svelte/transition";
|
||||
import { lt as semverLt } from "semver";
|
||||
import type { LoaderOptions, ESPLoader } from "esptool-js";
|
||||
import ProgressButton from "$lib/ProgressButton.svelte";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -205,18 +206,13 @@
|
||||
{#if data.meta.update.ota && !data.meta.device.endsWith("m0")}
|
||||
{@const buttonError = error || (!success && isCorrectDevice === false)}
|
||||
<section>
|
||||
<button
|
||||
class="update-button"
|
||||
class:working={working && (progress <= 0 || progress >= 1)}
|
||||
class:progress={working && progress > 0 && progress < 1}
|
||||
style:--progress="{progress * 100}%"
|
||||
class:primary={!buttonError}
|
||||
class:error={buttonError}
|
||||
disabled={isTooOld ||
|
||||
working ||
|
||||
$serialPort === undefined ||
|
||||
!isCorrectDevice}
|
||||
onclick={update}>Apply Update</button
|
||||
<ProgressButton
|
||||
{working}
|
||||
{progress}
|
||||
style="--height: 42px; --border-radius: 8px; margin-block: 16px;"
|
||||
error={buttonError ? buttonError.toString() : undefined}
|
||||
disabled={isTooOld || $serialPort === undefined || !isCorrectDevice}
|
||||
onclick={update}>Apply Update</ProgressButton
|
||||
>
|
||||
{#if isTooOld}
|
||||
<div class="error" transition:slide>
|
||||
@@ -433,27 +429,6 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(120deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: rotate(120deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(270deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button.inline-button {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
@@ -479,67 +454,6 @@
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
button.update-button {
|
||||
position: relative;
|
||||
transition:
|
||||
border 200ms ease,
|
||||
color 200ms ease;
|
||||
|
||||
margin: 6px;
|
||||
margin-block: 16px;
|
||||
|
||||
outline: 2px dashed currentcolor;
|
||||
outline-offset: 4px;
|
||||
|
||||
border: 2px solid currentcolor;
|
||||
border-radius: 8px;
|
||||
|
||||
background: var(--md-sys-color-background);
|
||||
height: 42px;
|
||||
overflow: hidden;
|
||||
|
||||
&.primary {
|
||||
background: none;
|
||||
color: var(--md-sys-color-primary);
|
||||
}
|
||||
|
||||
&.progress,
|
||||
&.working {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.working::before {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
border-radius: 8px;
|
||||
background: var(--md-sys-color-background);
|
||||
width: calc(100% - 4px);
|
||||
height: calc(100% - 4px);
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.working::after {
|
||||
position: absolute;
|
||||
z-index: -2;
|
||||
animation: rotate 1s ease-out forwards infinite;
|
||||
background: var(--md-sys-color-primary);
|
||||
width: 120%;
|
||||
height: 30%;
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.progress::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
opacity: 0.2;
|
||||
z-index: -2;
|
||||
background: var(--md-sys-color-primary);
|
||||
width: var(--progress);
|
||||
height: 100%;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.version {
|
||||
color: var(--md-sys-color-secondary);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
syncStatus,
|
||||
} from "$lib/serial/connection";
|
||||
import { askForConfirmation } from "$lib/dialogs/confirm-dialog";
|
||||
import ProgressButton from "$lib/ProgressButton.svelte";
|
||||
|
||||
function undo(event: MouseEvent) {
|
||||
if (event.shiftKey) {
|
||||
@@ -40,11 +41,27 @@
|
||||
}
|
||||
}
|
||||
let redoQueue: Change[][] = $state([]);
|
||||
let error = $state<Error | undefined>(undefined);
|
||||
let progressButton: HTMLButtonElement | undefined = $state();
|
||||
let shouldSaveNext = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if ($serialPort && $syncStatus == "done" && shouldSaveNext) {
|
||||
shouldSaveNext = false;
|
||||
save();
|
||||
}
|
||||
});
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
const port = $serialPort;
|
||||
if (!port) return;
|
||||
if (!port) {
|
||||
document
|
||||
.getElementById("connect-popup")
|
||||
?.showPopover({ source: progressButton });
|
||||
shouldSaveNext = true;
|
||||
return;
|
||||
}
|
||||
$syncStatus = "uploading";
|
||||
|
||||
const layoutChanges = $overlay.layout.reduce(
|
||||
@@ -160,12 +177,14 @@
|
||||
);
|
||||
$changes = [];
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
console.error(e);
|
||||
error = e as Error;
|
||||
console.error("Error while saving changes:", error);
|
||||
} finally {
|
||||
$syncStatus = "done";
|
||||
}
|
||||
}
|
||||
|
||||
let progressPopover: HTMLElement | undefined = $state();
|
||||
</script>
|
||||
|
||||
<button
|
||||
@@ -180,32 +199,28 @@
|
||||
disabled={redoQueue.length === 0}
|
||||
onclick={redo}>redo</button
|
||||
>
|
||||
{#if $changes.length !== 0}
|
||||
<button
|
||||
{#if $changes.length !== 0 || $syncStatus === "uploading" || $syncStatus === "error"}
|
||||
<div
|
||||
transition:fly={{ x: 10 }}
|
||||
use:action={{ title: $LL.saveActions.SAVE(), shortcut: "ctrl+shift+s" }}
|
||||
onclick={save}
|
||||
class="click-me"
|
||||
><span class="icon">save</span>{$LL.saveActions.SAVE()}</button
|
||||
>
|
||||
<ProgressButton
|
||||
disabled={$syncStatus !== "done"}
|
||||
working={$syncStatus === "uploading" || $syncStatus === "downloading"}
|
||||
progress={$syncProgress && $syncStatus === "uploading"
|
||||
? $syncProgress.current / $syncProgress.max
|
||||
: 0}
|
||||
style="--height: 36px"
|
||||
error={error !== undefined
|
||||
? (error.message ?? error.toString())
|
||||
: undefined}
|
||||
onclick={save}
|
||||
bind:element={progressButton}
|
||||
>
|
||||
<span class="icon">save</span>{$LL.saveActions.SAVE()}
|
||||
</ProgressButton>
|
||||
<div bind:this={progressPopover} popover="hint">
|
||||
{$LL.saveActions.SAVE()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.click-me {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-inline: 8px;
|
||||
outline: 2px dashed var(--md-sys-color-primary);
|
||||
outline-offset: 2px;
|
||||
border: 2px solid var(--md-sys-color-primary);
|
||||
border-radius: 18px;
|
||||
padding-inline-start: 8px;
|
||||
padding-inline-end: 12px;
|
||||
padding-block: 2px;
|
||||
height: fit-content;
|
||||
color: var(--md-sys-color-primary);
|
||||
font-weight: bold;
|
||||
font-family: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { fade, fly } from "svelte/transition";
|
||||
import { canShare, triggerShare } from "$lib/share";
|
||||
import { action } from "$lib/title";
|
||||
import { activeProfile, serialPort } from "$lib/serial/connection";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import EditActions from "./EditActions.svelte";
|
||||
import { page } from "$app/state";
|
||||
import { expoOut } from "svelte/easing";
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
@@ -12,9 +14,12 @@
|
||||
<EditActions />
|
||||
</div>
|
||||
|
||||
<div class="profiles">
|
||||
{#if $serialPort}
|
||||
{#if $serialPort.profileCount > 1}
|
||||
<div>
|
||||
{#if $serialPort && $serialPort.profileCount > 1 && !page.route.id?.startsWith("/(app)/config/chords")}
|
||||
<div
|
||||
transition:fade={{ duration: 250, easing: expoOut }}
|
||||
class="profiles"
|
||||
>
|
||||
{#each Array.from({ length: $serialPort.profileCount }, (_, i) => i) as profile}
|
||||
<label
|
||||
><input
|
||||
@@ -25,7 +30,7 @@
|
||||
/>{String.fromCodePoint("A".codePointAt(0)! + profile)}</label
|
||||
>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -460,7 +460,7 @@
|
||||
}
|
||||
|
||||
.results {
|
||||
min-width: min(90vw, 16.5cm);
|
||||
min-width: min(90vw, 20cm);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { inputToAction } from "./input-converter";
|
||||
import { deviceMeta, serialPort } from "$lib/serial/connection";
|
||||
import { get } from "svelte/store";
|
||||
import { action } from "$lib/title";
|
||||
import { action, actionTooltip } from "$lib/title";
|
||||
import semverGte from "semver/functions/gte";
|
||||
import Action from "$lib/components/Action.svelte";
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
} else if (event.key === "Delete") {
|
||||
deleteAction(cursorPosition, 1, true);
|
||||
} else {
|
||||
if (event.key === "Shift") return;
|
||||
if (event.key === "Shift" || event.key === "Meta") return;
|
||||
const action = inputToAction(event, get(serialPort)?.device === "X");
|
||||
if (action !== undefined) {
|
||||
insertAction(cursorPosition, action);
|
||||
@@ -197,32 +197,40 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="textbox"
|
||||
class="wrapper"
|
||||
class:edited={!chord.deleted && chord.phraseChanged}
|
||||
onclick={() => {
|
||||
box.focus();
|
||||
box?.focus();
|
||||
}}
|
||||
>
|
||||
{#if supportsAutospace}
|
||||
<label
|
||||
class="auto-space-edit"
|
||||
use:action={{ title: "Remove previous concatenator" }}
|
||||
><span class="icon">join_inner</span><input
|
||||
checked={chord.phrase[0] === JOIN_ACTION}
|
||||
{#snippet tooltip()}
|
||||
{#if chord.phrase[0] === JOIN_ACTION}
|
||||
<b>Remove</b> preceding space
|
||||
{:else}
|
||||
<b>Keep</b> preceding space
|
||||
{/if}
|
||||
{/snippet}
|
||||
<label class="auto-space-edit" {@attach actionTooltip(tooltip)}
|
||||
><span class="icon">space_bar</span><input
|
||||
checked={chord.phrase[0] !== JOIN_ACTION}
|
||||
onchange={async (event) => {
|
||||
const autospace = hasAutospace;
|
||||
if ((event.target as HTMLInputElement).checked) {
|
||||
if (chord.phrase[0] !== JOIN_ACTION) {
|
||||
insertAction(0, JOIN_ACTION);
|
||||
moveCursor(cursorPosition + 1, true);
|
||||
}
|
||||
} else {
|
||||
if (chord.phrase[0] === JOIN_ACTION) {
|
||||
deleteAction(0, 1);
|
||||
await tick();
|
||||
moveCursor(cursorPosition - 1, true);
|
||||
}
|
||||
} else {
|
||||
if (chord.phrase[0] !== JOIN_ACTION) {
|
||||
insertAction(0, JOIN_ACTION);
|
||||
moveCursor(cursorPosition + 1, true);
|
||||
}
|
||||
}
|
||||
await tick();
|
||||
resolveAutospace(autospace);
|
||||
@@ -260,7 +268,14 @@
|
||||
{/each}
|
||||
</div>
|
||||
{#if supportsAutospace}
|
||||
<label class="auto-space-edit" use:action={{ title: "Add concatenator" }}
|
||||
{#snippet tooltip()}
|
||||
{#if hasAutospace}
|
||||
<b>Add</b> trailing space
|
||||
{:else}
|
||||
<b>Don't add</b> trailing space
|
||||
{/if}
|
||||
{/snippet}
|
||||
<label class="auto-space-edit" {@attach actionTooltip(tooltip)}
|
||||
><span class="icon">space_bar</span><input
|
||||
checked={hasAutospace}
|
||||
onchange={(event) =>
|
||||
@@ -324,8 +339,7 @@
|
||||
color: var(--md-sys-color-on-tertiary-container);
|
||||
font-size: 1.3em;
|
||||
|
||||
&:first-of-type:not(:has(:checked)),
|
||||
&:last-of-type:has(:checked) {
|
||||
&:has(:checked) {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
|
||||
<section>
|
||||
<nav>
|
||||
<a href="#connection">Connection</a>
|
||||
{#if $deviceMeta}
|
||||
{#each $deviceMeta?.settings as category}
|
||||
<a href={`#${category.name}`}>{titlecase(category.name)}</a>
|
||||
@@ -50,19 +49,6 @@
|
||||
<a href="#backup">Backup</a>
|
||||
</nav>
|
||||
<div class="content">
|
||||
<fieldset id="connection">
|
||||
<legend>Connection</legend>
|
||||
<label
|
||||
><input type="checkbox" use:preference={"autoConnect"} />
|
||||
<div class="title">{$LL.deviceManager.AUTO_CONNECT()}</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
><input type="checkbox" use:preference={"backup"} />
|
||||
<div class="title">{@html $LL.backup.AUTO_BACKUP()}</div>
|
||||
<div class="description">{@html $LL.backup.DISCLAIMER()}</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
{#if $deviceMeta}
|
||||
{#each $deviceMeta.settings as category}
|
||||
<fieldset id={category.name}>
|
||||
@@ -70,7 +56,7 @@
|
||||
{titlecase(category.name)}
|
||||
</legend>
|
||||
{#if category.description}
|
||||
<p>{category.description}</p>
|
||||
<p class="category-description">{@html category.description}</p>
|
||||
{/if}
|
||||
{#each category.items as item}
|
||||
{#if item.unit === "H"}
|
||||
@@ -112,7 +98,7 @@
|
||||
{/if}
|
||||
<div class="title">{titlecase(item.name)}</div>
|
||||
{#if item.description}
|
||||
<div class="description">{item.description}</div>
|
||||
<div class="description">{@html item.description}</div>
|
||||
{/if}
|
||||
</label>
|
||||
{/if}
|
||||
@@ -183,6 +169,13 @@
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.category-description {
|
||||
margin-inline: 16px;
|
||||
margin-block: 24px;
|
||||
color: var(--md-sys-color-on-surface-variant);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
legend {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
@@ -213,7 +206,7 @@
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
justify-content: flex-start !important;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user