feat: wasm zero

This commit is contained in:
2025-10-29 18:51:03 +01:00
parent 45682f0d1a
commit 1de52f7f81
14 changed files with 353 additions and 107 deletions

View File

@@ -11,7 +11,7 @@
argbFromHex,
themeFromSourceColor,
} from "@material/material-color-utilities";
import { canAutoConnect } from "$lib/serial/device";
import { canAutoConnect, getViablePorts } from "$lib/serial/device";
import { initSerial } from "$lib/serial/connection";
import type { LayoutData } from "./$types";
import { browser } from "$app/environment";
@@ -63,7 +63,8 @@
}
if (browser && $userPreferences.autoConnect && (await canAutoConnect())) {
await initSerial();
const [port] = await getViablePorts();
await initSerial(port!, true);
}
if (data.importFile) {

View File

@@ -0,0 +1,144 @@
<script lang="ts">
import LL from "$i18n/i18n-svelte";
import { preference } from "$lib/preferences";
import { initSerial } from "$lib/serial/connection";
import {
getPortName,
PORT_FILTERS,
type SerialPortLike,
} from "$lib/serial/device";
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
import { onMount } from "svelte";
let ports = $state<SerialPort[]>([]);
onMount(() => {
refreshPorts();
});
async function refreshPorts() {
ports = await navigator.serial.getPorts();
}
async function connect(port: SerialPortLike, withSync: boolean) {
try {
await initSerial(port, withSync);
} catch (error) {
console.error(error);
await showConnectionFailedDialog(String(error));
}
}
</script>
<div class="device-list">
<fieldset>
<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
>
</div>
{/each}
<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
>
<a href="/ccos/zero_wasm/"><span class="icon">add</span>Virtual Device</a>
</div>
</div>
<style lang="scss">
button,
a {
padding: 10px;
height: 32px;
font-size: 12px;
.icon {
font-size: 18px;
}
}
.pair {
display: flex;
}
.device {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
button {
flex: 1;
}
}
button.error {
color: var(--md-sys-color-error);
}
label {
display: flex;
position: relative;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
gap: 8px;
appearance: none;
padding: 0;
.title {
font-weight: 600;
}
}
fieldset {
display: flex;
gap: 16px;
border: none;
padding: 0;
}
</style>

View File

@@ -7,16 +7,14 @@
import { detectLocale, locales } from "$i18n/i18n-util";
import { loadLocaleAsync } from "$i18n/i18n-util.async";
import { tick } from "svelte";
import SyncOverlay from "./SyncOverlay.svelte";
import {
initSerial,
serialPort,
sync,
syncProgress,
syncStatus,
} from "$lib/serial/connection";
import { fade, slide } from "svelte/transition";
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
import ConnectPopup from "./ConnectPopup.svelte";
let locale = $state(
(browser && (localStorage.getItem("locale") as Locales)) || detectLocale(),
@@ -48,20 +46,11 @@
}
}
async function connect() {
try {
await initSerial(true);
} catch (error) {
console.error(error);
await showConnectionFailedDialog(String(error));
}
}
function disconnect(event: MouseEvent) {
if (event.shiftKey) {
sync();
} else {
$serialPort?.forget();
$serialPort?.close();
$serialPort = undefined;
}
}
@@ -90,9 +79,15 @@
</ul>
<div class="sync-box">
{#if !$serialPort}
<button class="warning" onclick={connect} transition:slide={{ axis: "x" }}
<button
class="warning"
popovertarget="connect-popup"
transition:slide={{ axis: "x" }}
><span class="icon">usb</span>{$LL.deviceManager.CONNECT()}</button
>
<div popover id="connect-popup">
<ConnectPopup />
</div>
{:else}
<button
transition:slide={{ axis: "x" }}

View File

@@ -72,7 +72,8 @@
async function connect() {
try {
await initSerial(true, false);
const port = await navigator.serial.requestPort();
await initSerial(port!, true);
step = 1;
} catch (e) {
error = e as Error;
@@ -197,6 +198,10 @@
</script>
<div class="container">
{#if data.meta.update.js && data.meta.update.wasm}
<button>Add Virtual Device</button>
{/if}
{#if data.meta.update.ota && !data.meta.device.endsWith("m0")}
{@const buttonError = error || (!success && isCorrectDevice === false)}
<section>
@@ -260,47 +265,49 @@
</div>
{/if}
<section>
<ol>
<li>
<button class="inline-button" onclick={connect}
><span class="icon">usb</span>Connect</button
>
your device
{#if step >= 1}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
{#if data.meta.update.uf2}
<section>
<ol>
<li>
<button class="inline-button" onclick={connect}
><span class="icon">usb</span>Connect</button
>
your device
{#if step >= 1}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
<li class:faded={step < 1}>
Make a <button class="inline-button" onclick={backup}
><span class="icon">download</span>Backup</button
>
{#if step >= 2}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
<li class:faded={step < 1}>
Make a <button class="inline-button" onclick={backup}
><span class="icon">download</span>Backup</button
>
{#if step >= 2}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
<li class:faded={step < 2}>
Reboot to <button class="inline-button" onclick={bootloader}
><span class="icon">restart_alt</span>Bootloader</button
>
{#if step >= 3}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
<li class:faded={step < 2}>
Reboot to <button class="inline-button" onclick={bootloader}
><span class="icon">restart_alt</span>Bootloader</button
>
{#if step >= 3}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
<li class:faded={step < 3}>
Replace <button class="inline-button" onclick={getFileSystem}
><span class="icon">deployed_code_update</span>CURRENT.UF2</button
>
on the new drive
{#if step >= 4}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
</ol>
</section>
<li class:faded={step < 3}>
Replace <button class="inline-button" onclick={getFileSystem}
><span class="icon">deployed_code_update</span>CURRENT.UF2</button
>
on the new drive
{#if step >= 4}
<span class="icon ok" transition:fade>check_circle</span>
{/if}
</li>
</ol>
</section>
{/if}
{#if false && data.meta.update.esptool}
<section>

View File

@@ -321,7 +321,7 @@
><td></td><td></td></tr
>
{/if}
{#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord] (JSON.stringify(chord?.id))}
{#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord]}
{#if chord}
<ChordEdit {chord} onduplicate={() => (page = 0)} />
{/if}