diff --git a/src/lib/stores/serial.options.ts b/src/lib/stores/serial.options.ts new file mode 100644 index 0000000..7f8d5fe --- /dev/null +++ b/src/lib/stores/serial.options.ts @@ -0,0 +1,29 @@ +import { derived, get } from 'svelte/store'; +import { baudRate, dataBits, stopBits, parity, flowControl } from './serial'; + +export function getSerialOptions(): SerialOptions { + return { + baudRate: get(baudRate), + dataBits: get(dataBits) as 7 | 8, + stopBits: get(stopBits) as 1 | 2, + parity: get(parity), + flowControl: get(flowControl), + }; +} + +export const serialOptions = derived( + [baudRate, dataBits, stopBits, parity, flowControl], + ([ + $baudRate, + $dataBits, + $stopBits, + $parity, + $flowControl, + ]): SerialOptions => ({ + baudRate: $baudRate, + dataBits: $dataBits as 7 | 8, + stopBits: $stopBits as 1 | 2, + parity: $parity, + flowControl: $flowControl, + }) +); diff --git a/src/lib/stores/serial.ts b/src/lib/stores/serial.ts new file mode 100644 index 0000000..44aa4ed --- /dev/null +++ b/src/lib/stores/serial.ts @@ -0,0 +1,47 @@ +import { derived, writable, get } from 'svelte/store'; +import { useLocalStorage } from './utils/useLocalStorage'; + +export type ReadType = 'hex' | 'ascii' | 'dec'; + +const defaultBaudRatesList = [9600, 19200, 38400, 57600, 115200]; + +/* ---------------- 派生状态 ---------------- */ +export const baudRate = useLocalStorage('baudRate', 9600); +export const baudRateList = useLocalStorage( + 'baudRateList', + defaultBaudRatesList +); + +export const dataBits = writable(8); +export const stopBits = writable(1); +export const parity = writable('none'); +export const flowControl = writable('none'); + +export const readType = useLocalStorage('readType', 'hex'); +export const sendType = useLocalStorage('sendType', 'hex'); + +export const hasDecTypes = useLocalStorage('hasDecTypes', false); + +/* ---------------- 派生状态 ---------------- */ +export const recordTypes = derived(hasDecTypes, ($hasDecTypes) => + $hasDecTypes + ? (['hex', 'ascii', 'dec'] satisfies ReadType[]) + : (['hex', 'ascii'] satisfies ReadType[]) +); + +/* ---------------- Actions ---------------- */ +export function nextReadType() { + const types = get(recordTypes); + const current = get(readType); + + const index = types.indexOf(current); + readType.set(types[(index + 1) % types.length]); +} + +export function nextSendType() { + const types = get(recordTypes); + const current = get(sendType); + + const index = types.indexOf(current); + sendType.set(types[(index + 1) % types.length]); +} diff --git a/src/lib/stores/serial.ui.ts b/src/lib/stores/serial.ui.ts new file mode 100644 index 0000000..e3d228c --- /dev/null +++ b/src/lib/stores/serial.ui.ts @@ -0,0 +1,60 @@ +import { derived } from 'svelte/store'; +import { baudRate, baudRateList, dataBits, parity, stopBits } from './serial'; +import { createUIBridge } from './utils/uiBridge'; +import { isOneOf } from './utils/typeGuard'; + +export const baudRateItems = derived(baudRateList, ($list) => + $list.map((rate) => ({ + value: String(rate), + label: `${rate}`, + })) +); + +const baudRateBridge = createUIBridge( + baudRate, + (v) => String(v), + (v) => { + const n = Number(v); + return Number.isFinite(n) ? n : undefined; + } +); + +export const baudRateValue = baudRateBridge.store; + +const dataBitsBridge = createUIBridge( + dataBits, + (v) => String(v), + (v) => { + const n = Number(v); + return n; + } +); + +export const dataBitsValue = dataBitsBridge.store; + +const parityBridge = createUIBridge( + parity, + (v) => String(v), + (v) => { + const PARITY_VALUES = ['none', 'odd', 'even'] as const; + if (!isOneOf(v, PARITY_VALUES)) { + console.warn(`Invalid parity value: ${v}`); + return undefined; + } + const n = v; + return n; + } +); + +export const parityValue = parityBridge.store; + +const stopBitsBridge = createUIBridge( + stopBits, + (v) => String(v), + (v) => { + const n = Number(v); + return n; + } +); + +export const stopBitsValue = stopBitsBridge.store; diff --git a/src/lib/stores/utils/typeGuard.ts b/src/lib/stores/utils/typeGuard.ts new file mode 100644 index 0000000..46abb08 --- /dev/null +++ b/src/lib/stores/utils/typeGuard.ts @@ -0,0 +1,6 @@ +export function isOneOf( + value: unknown, + allowed: T +): value is T[number] { + return allowed.includes(value as T[number]); +} diff --git a/src/lib/stores/utils/uiBridge.ts b/src/lib/stores/utils/uiBridge.ts new file mode 100644 index 0000000..37b7700 --- /dev/null +++ b/src/lib/stores/utils/uiBridge.ts @@ -0,0 +1,36 @@ +import { get, writable, type Writable } from 'svelte/store'; + +export function createUIBridge( + source: Writable, + toUI: (value: T) => U, + fromUI: (value: U) => T | undefined +) { + const ui = writable(toUI(get(source))); + + let syncing = false; + + const unsubSource = source.subscribe((v) => { + if (syncing) return; + syncing = true; + ui.set(toUI(v)); + syncing = false; + }); + + const unsubUI = ui.subscribe((v) => { + if (syncing) return; + const next = fromUI(v); + if (next === undefined) return; + + syncing = true; + source.set(next); + syncing = false; + }); + + return { + store: ui, + destroy() { + unsubSource(); + unsubUI(); + }, + }; +} diff --git a/src/lib/stores/utils/useLocalStorage.ts b/src/lib/stores/utils/useLocalStorage.ts new file mode 100644 index 0000000..10d79d5 --- /dev/null +++ b/src/lib/stores/utils/useLocalStorage.ts @@ -0,0 +1,22 @@ +import { writable, type Writable } from 'svelte/store'; + +export function useLocalStorage(key: string, initialValue: T): Writable { + let startValue = initialValue; + + if (typeof localStorage !== 'undefined') { + const stored = localStorage.getItem(key); + if (stored !== null) { + startValue = JSON.parse(stored); + } + } + + const store = writable(startValue); + + store.subscribe((value) => { + if (typeof localStorage !== 'undefined') { + localStorage.setItem(key, JSON.stringify(value)); + } + }); + + return store; +}