feat: de-clutter navbar

fix: backup option not working
refactor: persistent writable stores

[deploy]
This commit is contained in:
2023-07-29 22:50:18 +02:00
parent 7d148d0c2c
commit 4cc9462655
16 changed files with 166 additions and 244 deletions

View File

@@ -12,7 +12,6 @@
import {pwaInfo} from "virtual:pwa-info"
import type {LayoutServerData} from "./$types"
import type {RegisterSWOptions} from "vite-plugin-pwa/types"
import {initLocalStorage} from "$lib/serial/storage"
import {browser} from "$app/environment"
import BrowserWarning from "./BrowserWarning.svelte"
import "tippy.js/animations/shift-away.css"
@@ -47,7 +46,6 @@
const dark = it.mode === "dark" // window.matchMedia("(prefers-color-scheme: dark)").matches
applyTheme(theme, {target: document.body, dark})
})
initLocalStorage()
if (pwaInfo) {
const {registerSW} = await import("virtual:pwa-register")

View File

@@ -28,7 +28,7 @@
URL.revokeObjectURL(downloadUrl)
}
async function restoreBackup(event: InputEvent) {
async function restoreBackup(event: Event) {
const input = (event.target as HTMLInputElement).files![0]
if (!input) return
const backup = await parseCompressed<Backup>(input)
@@ -119,10 +119,10 @@
border-radius: 32px;
transition: all 250ms ease;
}
&.primary {
color: var(--md-sys-color-on-primary);
background: var(--md-sys-color-primary);
}
button.primary {
color: var(--md-sys-color-on-primary);
background: var(--md-sys-color-primary);
}
</style>

View File

@@ -82,10 +82,6 @@
mask: url("/browsers/googlechrome.svg");
}
&.brave::before {
mask: url("/browsers/brave.svg");
}
&.edge::before {
mask: url("/browsers/microsoftedge.svg");
}

View File

@@ -1,18 +0,0 @@
<script lang="ts">
import {locales} from "../i18n/i18n-util"
import type {Locales} from "../i18n/i18n-types"
import {loadLocaleAsync} from "../i18n/i18n-util.async"
import {setLocale} from "../i18n/i18n-svelte"
async function applyLocale(locale: Locales) {
localStorage.setItem("locale", locale)
await loadLocaleAsync(locale)
setLocale(locale)
}
</script>
<ul>
{#each locales as locale}
<li><button on:click={() => applyLocale(locale)}>{locale}</button></li>
{/each}
</ul>

View File

@@ -9,9 +9,8 @@
import {canAutoConnect} from "$lib/serial/device"
import {browser} from "$app/environment"
import {userPreferences} from "$lib/preferences"
import Theme from "./Theme.svelte"
import Languages from "./Languages.svelte"
import LL from "../i18n/i18n-svelte"
import Profile from "./Profile.svelte"
const training = [
{slug: "cpm", title: "CPM - Characters Per Minute", icon: "music_note"},
@@ -45,7 +44,7 @@
<div class="actions">
{#if $canShare}
<a transition:fly={{x: -8}} class="icon" on:click={triggerShare}>share</a>
<button transition:fly={{x: -8}} class="icon" on:click={triggerShare}>share</button>
<div transition:slide class="separator" />
{/if}
{#await import("$lib/components/PwaStatus.svelte") then { default: PwaStatus }}
@@ -64,7 +63,6 @@
{/if}
</button>
{/if}
<button class="icon" use:popup={Languages}>translate</button>
<button
bind:this={connectButton}
title="Devices"
@@ -74,8 +72,7 @@
>
cable
</button>
<button title="Theme" use:popup={Theme} class="icon">format_paint</button>
<a href="/stats/" title="Statistics" class="icon account">person</a>
<button title={$LL.profile.TITLE()} use:popup={Profile} class="icon account">person</button>
</div>
</nav>

109
src/routes/Profile.svelte Normal file
View File

@@ -0,0 +1,109 @@
<script lang="ts">
import LL, {setLocale} from "../i18n/i18n-svelte"
import {theme} from "$lib/preferences"
import {tick} from "svelte"
import {detectLocale, locales} from "../i18n/i18n-util"
import {loadLocaleAsync} from "../i18n/i18n-util.async"
import type {Locales} from "../i18n/i18n-types"
let locale = (localStorage.getItem("locale") as Locales) || detectLocale()
$: (async () => {
localStorage.setItem("locale", locale)
await loadLocaleAsync(locale)
setLocale(locale)
})()
</script>
<section>
<h2>{$LL.profile.TITLE()}</h2>
<fieldset>
<legend>
<span class="icon">format_paint</span>
{$LL.profile.theme.TITLE()}
</legend>
<input title={$LL.profile.theme.COLOR_SCHEME()} type="color" bind:value={$theme.color} />
<button
title={$theme.mode === "light" ? $LL.profile.theme.LIGHT_MODE() : $LL.profile.theme.DARK_MODE()}
class="icon"
on:click={() => {
document.startViewTransition(async () => {
$theme.mode = $theme.mode === "light" ? "dark" : "light"
await tick()
})
}}
>
{#if $theme.mode === "light"}
light_mode
{:else if $theme.mode === "dark"}
dark_mode
{:else}
TODO
{/if}
</button>
</fieldset>
<fieldset>
<legend>
<span class="icon">translate</span>
{$LL.profile.LANGUAGE()}
</legend>
{#each locales as code}
<label>{code}<input bind:group={locale} type="radio" value={code} name="language" /></label>
{/each}
</fieldset>
</section>
<style lang="scss">
h2 {
grid-column: 1 / span 2;
}
section {
display: grid;
grid-template-columns: auto auto;
min-width: 300px;
}
fieldset {
display: flex;
justify-content: space-around;
border: 1px solid var(--md-sys-color-outline);
border-radius: 16px;
}
legend {
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
}
button,
input[type="color"] {
cursor: pointer;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
inline-size: 24px;
block-size: 24px;
margin: 0;
padding: 0;
color: inherit;
background: transparent;
border: none;
border-radius: 50%;
&::-webkit-color-swatch-wrapper {
padding: 0;
}
&::-webkit-color-swatch {
border: none;
}
}
</style>

View File

@@ -1,61 +0,0 @@
<script>
import {theme} from "$lib/preferences"
import {tick} from "svelte"
</script>
<section>
<input type="color" bind:value={$theme.color} />
<button
class="icon"
on:click={() => {
document.startViewTransition(async () => {
$theme.mode = $theme.mode === "light" ? "dark" : "light"
await tick()
})
}}
>
{#if $theme.mode === "light"}
light_mode
{:else if $theme.mode === "dark"}
dark_mode
{:else}
TODO
{/if}
</button>
</section>
<style lang="scss">
section {
display: flex;
gap: 16px;
}
button,
input[type="color"] {
cursor: pointer;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
inline-size: 24px;
block-size: 24px;
margin: 0;
padding: 0;
color: inherit;
background: transparent;
border: none;
border-radius: 50%;
&::-webkit-color-swatch-wrapper {
padding: 0;
}
&::-webkit-color-swatch {
border: none;
}
}
</style>

View File

@@ -1,108 +0,0 @@
<script lang="ts">
import {getSharableUrl, parseCompressed, stringifyCompressed} from "$lib/serial/serialization"
import {chords, layout} from "$lib/serial/connection"
import type {Chord} from "$lib/serial/chord"
import type {CharaLayout} from "$lib/serialization/layout"
interface CharaBackup {
isCharaBackup: "v1.0"
chords: Chord[]
layout: CharaLayout
}
async function downloadBackup() {
const downloadUrl = URL.createObjectURL(
await stringifyCompressed<CharaBackup>({
isCharaBackup: "v1.0",
chords: $chords,
layout: $layout,
}),
)
const element = document.createElement("a")
element.setAttribute("download", "chords.chb")
element.href = downloadUrl
element.setAttribute("target", "_blank")
element.click()
URL.revokeObjectURL(downloadUrl)
}
async function restoreBackup(event: Event) {
const input = (event.target as HTMLInputElement).files![0]
if (!input) return
const backup = await parseCompressed<CharaBackup>(input)
if (backup.isCharaBackup !== "v1.0") throw new Error("Invalid Backup")
if (backup.chords) {
$chords = backup.chords
}
if (backup.layout) {
$layout = backup.layout
}
}
async function createShareUrl() {
console.log(await getSharableUrl("chords", $chords))
}
</script>
<section>
<h1>Backup & Restore</h1>
<p class="disclaimer">
<i
>We automatically backup your device settings. Backups remain on your computer and are never shared or
uploaded to our servers.</i
>
</p>
<div class="save">
<button class="primary" on:click={downloadBackup}><span class="icon">save</span> Download Backup</button>
<label class="button"
><input on:input={restoreBackup} type="file" /><span class="icon">settings_backup_restore</span> Restore</label
>
</div>
</section>
<style lang="scss">
.disclaimer {
max-width: 16cm;
font-size: 12px;
opacity: 0.7;
}
input[type="file"] {
display: none;
}
.save {
display: flex;
gap: 4px;
}
.button,
button {
cursor: pointer;
display: flex;
gap: 4px;
align-items: center;
justify-content: center;
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>