feat: tauri serial polyfill

This commit is contained in:
2023-08-04 00:08:28 +02:00
parent 9c1918e683
commit 42922e7ce0
23 changed files with 660 additions and 486 deletions

View File

@@ -7,8 +7,6 @@
export let results: number[] = []
export let width: number
console.log(width)
</script>
<div class="list" style="width: {width}px">

View File

@@ -8,7 +8,6 @@ export const popup: Action<HTMLButtonElement, ComponentType> = (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")

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import {createEventDispatcher} from "svelte"
export let ports: SerialPort[]
const dispatch = createEventDispatcher<{confirm: SerialPort | undefined}>()
let selected = ports[0].getInfo().name
</script>
<dialog>
{#each ports as port}
{@const info = port.getInfo()}
<label>{info.product}<input type="radio" name="port" value={info.name} bind:group={selected} /></label>
{/each}
<button on:click={() => dispatch("confirm", undefined)}>Cancel</button>
<button
on:click={() =>
dispatch(
"confirm",
ports.find(it => it.getInfo().name === selected),
)}>Ok</button
>
</dialog>

View File

@@ -6,7 +6,7 @@ import type {CharaLayout} from "$lib/serialization/layout"
import {persistentWritable} from "$lib/storage"
import {userPreferences} from "$lib/preferences"
export const serialPort = writable<CharaDevice>()
export const serialPort = writable<CharaDevice | undefined>()
export interface SerialLogEntry {
type: "input" | "output" | "system"

View File

@@ -11,7 +11,6 @@ if (browser && import.meta.env.TAURI_FAMILY !== undefined) {
}
export async function getViablePorts(): Promise<SerialPort[]> {
console.log(await navigator.serial.getPorts().then(it => it.map(it => it.getInfo())))
return navigator.serial.getPorts().then(ports => ports.filter(it => it.getInfo().usbVendorId === VENDOR_ID))
}

View File

@@ -0,0 +1,8 @@
/// <references types="@types/w3c-web-serial" />
interface SerialPortInfo {
name?: string
serialNumber?: string
manufacturer?: string
product?: string
}

View File

@@ -1,22 +1,65 @@
import {invoke} from "@tauri-apps/api"
import TauriSerialDialog from "$lib/serial/TauriSerialDialog.svelte"
export type TauriSerialPort = Pick<
SerialPort,
"getInfo" | "open" | "close" | "readable" | "writable" | "forget"
>
function NativeSerialPort(info: SerialPortInfo): TauriSerialPort {
return {
getInfo() {
return info
},
async open({baudRate}: SerialOptions) {
await invoke("plugin:serial|open", {path: info.name, baudRate})
},
async close() {
await invoke("plugin:serial|close", {path: info.name})
},
async forget() {
// noop
},
readable: new ReadableStream({
async pull(controller) {
const result = await invoke<number[]>("plugin:serial|read", {path: info.name})
controller.enqueue(new Uint8Array(result))
},
}),
writable: new WritableStream({
async write(chunk) {
await invoke("plugin:serial|write", {path: info.name, chunk: Array.from(chunk)})
},
}),
}
}
// @ts-expect-error polyfill
// noinspection JSConstantReassignment
navigator.serial = {
getPorts(): Promise<SerialPort[]> {
async getPorts(): Promise<SerialPort[]> {
return invoke<any[]>("plugin:serial|get_serial_ports").then(ports =>
ports.map<Partial<SerialPort>>(port => ({
getInfo() {
return {
name: port["name"],
usbVendorId: port["vendor_id"],
usbProductId: port["product_id"],
serialNumber: port["serial_number"],
manufacturer: port["manufacturer"],
product: port["product"],
} as SerialPortInfo
},
})),
ports.map(NativeSerialPort),
) as Promise<SerialPort[]>
},
async requestPort(options?: SerialPortRequestOptions): Promise<SerialPort> {
const ports = await navigator.serial.getPorts().then(ports =>
options?.filters !== undefined
? ports.filter(port =>
options.filters!.some(({usbVendorId, usbProductId}) => {
const info = port.getInfo()
return (
(usbVendorId === undefined || info.usbVendorId === usbVendorId) &&
(usbProductId === undefined || info.usbProductId === usbProductId)
)
}),
)
: ports,
)
const dialog = new TauriSerialDialog({target: document.body, props: {ports}})
const port = await new Promise<SerialPort>(resolve => dialog.$on("confirm", resolve))
dialog.$destroy()
return port
},
}