From 4cc3343984748b661644421917882c9a3398501b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Sun, 23 Jul 2023 17:44:26 +0200 Subject: [PATCH] feat: new connection flow --- icons.config.ts | 10 ++ src/lib/popup.ts | 1 + src/lib/preferences.ts | 4 +- src/lib/serial/connection.ts | 4 +- src/lib/serial/device.ts | 106 +++++++++--------- src/lib/style/tippy.scss | 7 +- src/routes/+layout.svelte | 2 +- src/routes/ConnectionPopup.svelte | 178 ++++++++++++++++++++++++------ src/routes/Navigation.svelte | 5 +- 9 files changed, 221 insertions(+), 96 deletions(-) diff --git a/icons.config.ts b/icons.config.ts index cf552cca..652bae83 100644 --- a/icons.config.ts +++ b/icons.config.ts @@ -27,6 +27,7 @@ const config: IconsConfig = { "sync", "restart_alt", "usb", + "usb_off", "rule_settings", "123", "abc", @@ -34,6 +35,7 @@ const config: IconsConfig = { "cloud_done", "backup", "cloud_download", + "cloud_off", "share", "ios_share", "close", @@ -41,6 +43,14 @@ const config: IconsConfig = { "arrow_back_ios_new", "save", "settings_backup_restore", + "sort", + "filter", + "settings_power", + "link", + "link_off", + "chevron_right", + "check_circle", + "error", ], codePoints: { speed: "e9e4", diff --git a/src/lib/popup.ts b/src/lib/popup.ts index 5272976b..bceaff0f 100644 --- a/src/lib/popup.ts +++ b/src/lib/popup.ts @@ -8,6 +8,7 @@ export const popup: Action = (node, Component) const edit = tippy(node, { interactive: true, trigger: "click", + sticky: true, onShow(instance) { target = instance.popper.querySelector(".tippy-content") as HTMLElement target.classList.add("active") diff --git a/src/lib/preferences.ts b/src/lib/preferences.ts index 8a516a6e..3d60b29c 100644 --- a/src/lib/preferences.ts +++ b/src/lib/preferences.ts @@ -3,12 +3,12 @@ import type {Action} from "svelte/action" export interface UserPreferences { backup: boolean - autoSync: boolean + autoConnect: boolean } export const userPreferences = writable({ backup: false, - autoSync: true, + autoConnect: true, }) export const preference: Action = (node, key) => { diff --git a/src/lib/serial/connection.ts b/src/lib/serial/connection.ts index 40cfb311..575875d2 100644 --- a/src/lib/serial/connection.ts +++ b/src/lib/serial/connection.ts @@ -25,9 +25,9 @@ export const syncStatus: Writable<"done" | "error" | "downloading" | "uploading" let device: CharaDevice // @hmr:keep -export async function initSerial() { +export async function initSerial(manual = false) { const device = get(serialPort) ?? new CharaDevice() - await device.ready() + await device.init(manual) serialPort.set(device) syncStatus.set("downloading") diff --git a/src/lib/serial/device.ts b/src/lib/serial/device.ts index 10ec1aa8..3efb8a80 100644 --- a/src/lib/serial/device.ts +++ b/src/lib/serial/device.ts @@ -14,74 +14,71 @@ export async function canAutoConnect() { } export class CharaDevice { - private readonly port: Promise - private readonly reader: Promise> + private port!: SerialPort + private reader!: ReadableStreamDefaultReader private readonly abortController1 = new AbortController() private readonly abortController2 = new AbortController() + private streamClosed!: Promise + private lock?: Promise - version: Promise - deviceId: Promise + version!: string + deviceId!: string - constructor(baudRate = 115200) { - this.port = getViablePorts().then(async ports => { - const port = - ports.length === 1 - ? ports[0] - : await navigator.serial.requestPort({filters: [{usbVendorId: VENDOR_ID}]}) - await port.open({baudRate}) - const info = port.getInfo() - serialLog.update(it => { - it.push({ - type: "system", - value: `Connected; ID: 0x${info.usbProductId?.toString(16)}; Vendor: 0x${info.usbVendorId?.toString( - 16, - )}`, - }) - return it + constructor(private readonly baudRate = 115200) {} + + async init(manual = false) { + const ports = await getViablePorts() + this.port = + !manual && ports.length === 1 + ? ports[0] + : await navigator.serial.requestPort({filters: [{usbVendorId: VENDOR_ID}]}) + await this.port.open({baudRate: this.baudRate}) + const info = this.port.getInfo() + serialLog.update(it => { + it.push({ + type: "system", + value: `Connected; ID: 0x${info.usbProductId?.toString(16)}; Vendor: 0x${info.usbVendorId?.toString( + 16, + )}`, }) - return port + return it }) - this.reader = this.port.then(async port => { - const decoderStream = new TextDecoderStream() - void port.readable!.pipeTo(decoderStream.writable, {signal: this.abortController1.signal}) - return decoderStream - .readable!.pipeThrough(new TransformStream(new LineBreakTransformer()), { - signal: this.abortController2.signal, - }) - .getReader() + const decoderStream = new TextDecoderStream() + this.streamClosed = this.port.readable!.pipeTo(decoderStream.writable, { + signal: this.abortController1.signal, }) - this.lock = this.reader.then(() => { - delete this.lock - return true - }) - this.version = this.send("VERSION") - this.deviceId = this.send("ID") + + this.reader = decoderStream + .readable!.pipeThrough(new TransformStream(new LineBreakTransformer()), { + signal: this.abortController2.signal, + }) + .getReader() + + this.version = await this.send("VERSION") + this.deviceId = await this.send("ID") } private async internalRead() { - return this.reader.then(async it => { - const result: string = await it.read().then(({value}) => value!) - serialLog.update(it => { - it.push({ - type: "output", - value: result, - }) - return it + const {value} = await this.reader.read() + serialLog.update(it => { + it.push({ + type: "output", + value: value!, }) - return result + return it }) + return value! } /** * Send a command to the device */ private async internalSend(...command: string[]) { - const port = await this.port - const writer = port.writable!.getWriter() + const writer = this.port.writable!.getWriter() try { serialLog.update(it => { it.push({ @@ -96,19 +93,18 @@ export class CharaDevice { } } - async ready() { - await this.port - } - async forget() { - await (await this.port).forget() + await this.disconnect() + await this.port.forget() } async disconnect() { - this.abortController1.abort() - this.abortController2.abort() - ;(await this.reader).releaseLock() - await (await this.port).close() + await this.reader.cancel() + await this.streamClosed.catch(() => { + /** noop */ + }) + this.reader.releaseLock() + await this.port.close() } /** diff --git a/src/lib/style/tippy.scss b/src/lib/style/tippy.scss index b1b3d8bd..b00a4da2 100644 --- a/src/lib/style/tippy.scss +++ b/src/lib/style/tippy.scss @@ -1,7 +1,10 @@ $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); @@ -11,10 +14,10 @@ $padding: 16px; } h2 { - margin-block-start: 8px; - margin-block-end: calc(8px + $padding); display: flex; justify-content: center; + margin-block-start: 8px; + margin-block-end: calc(8px + $padding); } @each $placement in top, bottom, right, left { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 8c9f08cc..30f7fc5d 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -51,7 +51,7 @@ } satisfies RegisterSWOptions) } - if ($userPreferences.autoSync && (await canAutoConnect())) await initSerial() + if ($userPreferences.autoConnect && (await canAutoConnect())) await initSerial() }) $: webManifestLink = pwaInfo ? pwaInfo.webManifest.linkTag : "" diff --git a/src/routes/ConnectionPopup.svelte b/src/routes/ConnectionPopup.svelte index 2eda08d1..76edb67e 100644 --- a/src/routes/ConnectionPopup.svelte +++ b/src/routes/ConnectionPopup.svelte @@ -1,52 +1,159 @@ - -

Devices

+
+

Device

-
- - -
-{#if browser} - {#await ($serialPort, getViablePorts()) then ports} + {#if $serialPort} +

+ {$serialPort.deviceId} +
+ Version {$serialPort.version} +

+ {/if} + + {#if browser}
- {#if ports.length === 0} - - {:else if $serialPort} - - {:else} - - {/if} - {#if $serialPort} - - {/if} + +
- {/await} -{/if} + {#await ($serialPort, getViablePorts()) then ports} + {#if connectDialog} +
(connectDialog = !connectDialog)} + /> + + + {#if $serialPort} + + {:else} + {#if ports.length > 0} + + {/if} + + {/if} + {#if $serialPort} + + {/if} + + {/if} + {/await} + {#if powerDialog} +
(powerDialog = !powerDialog)} /> + +

Boot Menu

+ + +
+ {/if} + {/if} +