migrate to typescript

This commit is contained in:
2023-07-07 12:24:23 +02:00
parent e6df93b7ea
commit d63cf541fb
19 changed files with 132 additions and 176 deletions

View File

@@ -1,5 +1,5 @@
<script> <script>
import {serialPort, syncing} from "$lib/serial/connection.js" import {serialPort, syncing} from "$lib/serial/connection"
import {browser} from "$app/environment" import {browser} from "$app/environment"
import {page} from "$app/stores" import {page} from "$app/stores"
</script> </script>

View File

@@ -1,43 +1,32 @@
<script> <script lang="ts">
import {layout} from "$lib/serial/connection.js" import {layout} from "$lib/serial/connection"
import {KEYMAP_CODES} from "$lib/serial/keymap-codes.js" import type {CharaLayout} from "$lib/serial/connection"
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
import type {KeyInfo} from "$lib/serial/keymap-codes"
export let activeLayer = 0 export let activeLayer = 0
export let keys: Record<"d" | "n" | "w" | "e", number>
/** @type {{d: number, n: number, w: number, e: number, s: number}} */ export let type: "primary" | "secondary" | "tertiary" = "primary"
export let keys
/** @type {'primary' | 'secondary' | 'tertiary'} */
export let type = "primary"
const layerNames = ["Primary Layer", "Number Layer", "Function Layer"] const layerNames = ["Primary Layer", "Number Layer", "Function Layer"]
const virtualLayerMap = [1, 0, 2] const virtualLayerMap = [1, 0, 2]
const characterOffset = 8 const characterOffset = 8
function offsetDistance(quadrant, layer, activeLayer) { function offsetDistance(quadrant: number, layer: number, activeLayer: number): number {
const layerOffsetIndex = virtualLayerMap[layer] - virtualLayerMap[activeLayer] const layerOffsetIndex = virtualLayerMap[layer] - virtualLayerMap[activeLayer]
const layerOffset = quadrant > 2 ? -characterOffset : characterOffset const layerOffset = quadrant > 2 ? -characterOffset : characterOffset
return 25 * quadrant + layerOffsetIndex * layerOffset return 25 * quadrant + layerOffsetIndex * layerOffset
} }
/** function getKeyDescriptions(keys: KeyInfo[]): string {
* @param keys {import('$lib/serial/keymap.js').KeyInfo[]}
* @returns {*}
*/
function getKeyDescriptions(keys) {
return keys.map(({title, id, code}, i) => `${title || id || code} (${layerNames[i]})`).join("\n") return keys.map(({title, id, code}, i) => `${title || id || code} (${layerNames[i]})`).join("\n")
} }
/** function getActions(id: number, layout: CharaLayout): KeyInfo[] {
* @param id {number}
* @param layout {[number[], number[], number[]]}
* @returns import('$lib/serial/keymap.js').KeyInfo[]
*/
function getActions(id, layout) {
return Array.from({length: 3}).map((_, i) => { return Array.from({length: 3}).map((_, i) => {
const actionId = layout?.[i][id] const actionId = layout?.[i][id]
return actionId !== undefined ? KEYMAP_CODES[actionId] : {code: actionId} return KEYMAP_CODES[actionId]
}) })
} }
</script> </script>

View File

@@ -1,22 +1,16 @@
<script> <script lang="ts">
import {serialLog, serialPort} from "$lib/serial/connection.js" import {serialLog, serialPort} from "$lib/serial/connection"
import {slide} from "svelte/transition" import {slide} from "svelte/transition"
/** function submit(event: InputEvent) {
* @param event {InputEvent}
*/
function submit(event) {
event.preventDefault() event.preventDefault()
$serialPort.send(value.trim()) $serialPort.send(value.trim())
value = "" value = ""
io.scrollTo({top: io.scrollHeight}) io.scrollTo({top: io.scrollHeight})
} }
/** @type {string} */ let value: string
let value let io: HTMLDivElement
/** @type {HTMLDivElement} */
let io
export let resizable = false export let resizable = false
</script> </script>

View File

@@ -1,3 +0,0 @@
export const Icon = {
close: 0xe5cd,
}

View File

@@ -1,19 +1,26 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {CharaDevice} from "$lib/serial/device.js" import {CharaDevice} from "$lib/serial/device"
/** @type {import('svelte/store').Writable<import('./device.js').CharaDevice>} */ export const serialPort = writable<CharaDevice>()
export const serialPort = writable()
/** @type {import('svelte/store').Writable<Array<{type: 'input' | 'output' | 'system'; value: string}>>} */ export interface SerialLogEntry {
export const serialLog = writable([]) type: "input" | "output" | "system"
value: string
}
/** @type {import('svelte/store').Writable<Array<{actions: number[]; phrase: string}>>} */ export const serialLog = writable<SerialLogEntry[]>([])
export const chords = writable([])
/** @type {import('svelte/store').Writable<[number[], number[], number[]]>} */ export interface Chord {
export const layout = writable([[], [], []]) actions: number[]
phrase: string
}
export const chords = writable<Chord[]>([])
export type CharaLayout = [number[], number[], number[]]
export const layout = writable<CharaLayout>([[], [], []])
/** @type {import('svelte/store').Writable<boolean>} */
export const syncing = writable(false) export const syncing = writable(false)
/** @type {CharaDevice} */ /** @type {CharaDevice} */
@@ -24,7 +31,7 @@ export async function initSerial() {
device ??= new CharaDevice() device ??= new CharaDevice()
serialPort.set(device) serialPort.set(device)
const parsedLayout = [[], [], []] const parsedLayout: CharaLayout = [[], [], []]
for (let layer = 1; layer <= 3; layer++) { for (let layer = 1; layer <= 3; layer++) {
for (let i = 0; i < 90; i++) { for (let i = 0; i < 90; i++) {
parsedLayout[layer - 1][i] = await device.getLayoutKey(layer, i) parsedLayout[layer - 1][i] = await device.getLayoutKey(layer, i)

View File

@@ -1,5 +1,6 @@
import {LineBreakTransformer} from "$lib/serial/line-break-transformer.js" import {LineBreakTransformer} from "$lib/serial/line-break-transformer"
import {serialLog} from "$lib/serial/connection.js" import {serialLog} from "$lib/serial/connection"
import type {Chord} from "$lib/serial/connection"
export const VENDOR_ID = 0x239a export const VENDOR_ID = 0x239a
@@ -11,29 +12,22 @@ export async function hasSerialPermission() {
} }
export class CharaDevice { export class CharaDevice {
/** @type {Promise<SerialPort>} */ private readonly port: Promise<SerialPort>
#port private readonly reader: Promise<ReadableStreamDefaultReader<string>>
/** @type {Promise<ReadableStreamDefaultReader<string>>} */
#reader
#encoder = new TextEncoder() private readonly abortController1 = new AbortController()
private readonly abortController2 = new AbortController()
#abortController1 = new AbortController() private lock?: Promise<true>
#abortController2 = new AbortController()
/** @type {Promise<true> | undefined} */ version: Promise<string>
#lock deviceId: Promise<string>
/** @type {Promise<string>} */
version
/** @type {Promise<string>} */
deviceId
/** /**
* @param baudRate * @param baudRate
*/ */
constructor(baudRate = 115200) { constructor(baudRate = 115200) {
this.#port = navigator.serial.getPorts().then(async ports => { this.port = navigator.serial.getPorts().then(async ports => {
const port = const port =
ports.find(it => it.getInfo().usbVendorId === VENDOR_ID) ?? ports.find(it => it.getInfo().usbVendorId === VENDOR_ID) ??
(await navigator.serial.requestPort({filters: [{usbVendorId: VENDOR_ID}]})) (await navigator.serial.requestPort({filters: [{usbVendorId: VENDOR_ID}]}))
@@ -42,7 +36,7 @@ export class CharaDevice {
serialLog.update(it => { serialLog.update(it => {
it.push({ it.push({
type: "system", type: "system",
value: `Connected; ID: 0x${info.usbProductId.toString(16)}; Vendor: 0x${info.usbVendorId.toString( value: `Connected; ID: 0x${info.usbProductId?.toString(16)}; Vendor: 0x${info.usbVendorId?.toString(
16, 16,
)}`, )}`,
}) })
@@ -50,29 +44,27 @@ export class CharaDevice {
}) })
return port return port
}) })
this.#reader = this.#port.then(async port => { this.reader = this.port.then(async port => {
const decoderStream = new TextDecoderStream() const decoderStream = new TextDecoderStream()
void port.readable.pipeTo(decoderStream.writable, {signal: this.#abortController1.signal}) void port.readable!.pipeTo(decoderStream.writable, {signal: this.abortController1.signal})
return decoderStream.readable return decoderStream
.pipeThrough(new TransformStream(new LineBreakTransformer()), {signal: this.#abortController2.signal}) .readable!.pipeThrough(new TransformStream(new LineBreakTransformer()), {
signal: this.abortController2.signal,
})
.getReader() .getReader()
}) })
this.#lock = this.#reader.then(() => { this.lock = this.reader.then(() => {
this.#lock = undefined delete this.lock
return true return true
}) })
this.version = this.send("VERSION") this.version = this.send("VERSION")
this.deviceId = this.send("ID") this.deviceId = this.send("ID")
} }
/** private async internalRead() {
* @returns {Promise<string>} return this.reader.then(async it => {
*/ const result: string = await it.read().then(({value}) => value!)
async #read() {
return this.#reader.then(async it => {
/** @type {string} */
const result = await it.read().then(({value}) => value)
serialLog.update(it => { serialLog.update(it => {
it.push({ it.push({
type: "output", type: "output",
@@ -86,12 +78,10 @@ export class CharaDevice {
/** /**
* Send a command to the device * Send a command to the device
* @param command {string}
* @returns {Promise<void>}
*/ */
async #send(...command) { private async internalSend(...command: string[]) {
const port = await this.#port const port = await this.port
const writer = port.writable.getWriter() const writer = port.writable!.getWriter()
try { try {
serialLog.update(it => { serialLog.update(it => {
it.push({ it.push({
@@ -108,35 +98,32 @@ export class CharaDevice {
/** /**
* Read/write to serial port * Read/write to serial port
* @template T
* @param callback {(send: (...commands: string) => Promise<void>, read: () => Promise<string>) => T | Promise<T>}
* @returns Promise<T>
*/ */
async runWith(callback) { async runWith<T>(
while (this.#lock) { callback: (send: typeof this.internalSend, read: typeof this.internalRead) => T | Promise<T>,
await this.#lock ): Promise<T> {
while (this.lock) {
await this.lock
} }
const send = this.#send.bind(this) const send = this.internalSend.bind(this)
const read = this.#read.bind(this) const read = this.internalRead.bind(this)
const exec = new Promise(async resolve => { const exec = new Promise<T>(async resolve => {
let result let result!: T
try { try {
result = await callback(send, read) result = await callback(send, read)
} finally { } finally {
this.#lock = undefined this.lock = undefined
resolve(result) resolve(result)
} }
}) })
this.#lock = exec.then(() => true) this.lock = exec.then(() => true)
return exec return exec
} }
/** /**
* Send to serial port * Send to serial port
* @param command {string}
* @returns Promise<string>
*/ */
async send(...command) { async send(...command: string[]) {
return this.runWith(async (send, read) => { return this.runWith(async (send, read) => {
await send(...command) await send(...command)
const commandString = command.join(" ").replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") const commandString = command.join(" ").replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
@@ -144,20 +131,13 @@ export class CharaDevice {
}) })
} }
/** async getChordCount(): Promise<number> {
* @returns {Promise<number>}
*/
async getChordCount() {
return Number.parseInt(await this.send("CML C0")) return Number.parseInt(await this.send("CML C0"))
} }
/** async getChord(index: number): Promise<Chord> {
* @param index {number}
* @returns {Promise<{actions: number[]; phrase: string, unk: number}>}
*/
async getChord(index) {
const chord = await this.send(`CML C1 ${index}`) const chord = await this.send(`CML C1 ${index}`)
const [keys, rawPhrase, b] = chord.split(" ") const [keys, rawPhrase] = chord.split(" ")
let phrase = [] let phrase = []
for (let i = 0; i < rawPhrase.length; i += 2) { for (let i = 0; i < rawPhrase.length; i += 2) {
phrase.push(Number.parseInt(rawPhrase.substring(i, i + 2), 16)) phrase.push(Number.parseInt(rawPhrase.substring(i, i + 2), 16))
@@ -175,16 +155,10 @@ export class CharaDevice {
return { return {
actions, actions,
phrase: String.fromCodePoint(...phrase), phrase: String.fromCodePoint(...phrase),
unk: Number(b),
} }
} }
/** async getLayoutKey(layer: number, id: number) {
* @param layer {number}
* @param id {number}
* @returns {Promise<number>}
*/
async getLayoutKey(layer, id) {
const layout = await this.send(`VAR B3 A${layer} ${id}`) const layout = await this.send(`VAR B3 A${layer} ${id}`)
const [position] = layout.split(" ").map(Number) const [position] = layout.split(" ").map(Number)
return position return position

View File

@@ -1,17 +0,0 @@
import keymapCodes from "$lib/assets/keymap_codes.json"
import keySymbols from "$lib/assets/key-symbols.json"
/** @type {Record<number, import('./keymap.js').KeyInfo>} */
export const KEYMAP_CODES = Object.fromEntries(
keymapCodes.map(([code, charset, id, title, description]) => [
code,
{
code: Number(code),
title: title || undefined,
charset: charset || undefined,
id: id || undefined,
symbol: id ? keySymbols[id] || undefined : undefined,
description: description || undefined,
},
]),
)

View File

@@ -1,3 +1,6 @@
import keymapCodes from "$lib/assets/keymap_codes.json"
import keySymbols from "$lib/assets/key-symbols.json"
export interface KeyInfo { export interface KeyInfo {
/** /**
* Numeric action code * Numeric action code
@@ -45,3 +48,17 @@ export type CharsetCategory =
| "Keybard" | "Keybard"
| "CharaChorder" | "CharaChorder"
| "CharaChorder One" | "CharaChorder One"
export const KEYMAP_CODES: Record<number, KeyInfo> = Object.fromEntries(
keymapCodes.map(([code, charset, id, title, description]) => [
code,
{
code: Number(code),
title: title || undefined,
charset: (charset || undefined) as CharsetCategory,
id: id || undefined,
symbol: id ? keySymbols[id as keyof typeof keySymbols] || undefined : undefined,
description: description || undefined,
},
]),
)

View File

@@ -1,21 +1,18 @@
// @ts-check
export class LineBreakTransformer { export class LineBreakTransformer {
constructor() { private chunks = ""
this.chunks = ""
}
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
transform(chunk, controller) { transform(chunk: string, controller: TransformStreamDefaultController) {
this.chunks += chunk this.chunks += chunk
const lines = this.chunks.split("\r\n") const lines = this.chunks.split("\r\n")
this.chunks = lines.pop() this.chunks = lines.pop()!
for (const line of lines) { for (const line of lines) {
controller.enqueue(line) controller.enqueue(line)
} }
} }
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
flush(controller) { flush(controller: TransformStreamDefaultController) {
controller.enqueue(this.chunks) controller.enqueue(this.chunks)
} }
} }

View File

@@ -1,10 +0,0 @@
import {themeBase, themeColor, themeSuccessBase} from "$lib/style/theme.server.js"
/** @type {import("./$types").LayoutServerLoad} */
export async function load() {
return {
themeSuccessBase,
themeBase,
themeColor,
}
}

View File

@@ -0,0 +1,8 @@
import {themeBase, themeColor, themeSuccessBase} from "$lib/style/theme.server"
import type {LayoutServerLoad} from "./$types"
export const load = (async () => ({
themeSuccessBase,
themeBase,
themeColor,
})) satisfies LayoutServerLoad

View File

@@ -1,15 +1,17 @@
<script> <script lang="ts">
import "$lib/fonts/noto-sans-mono.scss" import "$lib/fonts/noto-sans-mono.scss"
import "$lib/fonts/material-symbols-rounded.scss" import "$lib/fonts/material-symbols-rounded.scss"
import {onMount} from "svelte" import {onMount} from "svelte"
import {applyTheme, argbFromHex, themeFromSourceColor} from "@material/material-color-utilities" import {applyTheme, argbFromHex, themeFromSourceColor} from "@material/material-color-utilities"
import Navigation from "$lib/components/Navigation.svelte" import Navigation from "$lib/components/Navigation.svelte"
import {hasSerialPermission} from "$lib/serial/device.js" import {hasSerialPermission} from "$lib/serial/device"
import {initSerial} from "$lib/serial/connection.js" import {initSerial} from "$lib/serial/connection"
// noinspection TypeScriptCheckImport
import {pwaInfo} from "virtual:pwa-info" import {pwaInfo} from "virtual:pwa-info"
import type {LayoutServerData} from "./$types"
import type {RegisterSWOptions} from "vite-plugin-pwa/types"
/** @type {import('./$types').LayoutServerData} */ export let data: LayoutServerData
export let data
onMount(async () => { onMount(async () => {
const theme = themeFromSourceColor(argbFromHex("#6D81C7"), [ const theme = themeFromSourceColor(argbFromHex("#6D81C7"), [
@@ -19,15 +21,14 @@
applyTheme(theme, {target: document.body, dark}) applyTheme(theme, {target: document.body, dark})
if (pwaInfo) { if (pwaInfo) {
/** @type {import('vite-plugin-pwa/types').RegisterSWOptions} */ // noinspection TypeScriptCheckImport
const swOptions = { const {registerSW} = await import("virtual:pwa-register")
registerSW({
immediate: true, immediate: true,
onRegisterError(error) { onRegisterError(error) {
console.log("ServiceWorker Registration Error", error) console.log("ServiceWorker Registration Error", error)
}, },
} } satisfies RegisterSWOptions)
const {registerSW} = await import("virtual:pwa-register")
registerSW(swOptions)
} }
if (await hasSerialPermission()) await initSerial() if (await hasSerialPermission()) await initSerial()

View File

@@ -1,3 +1,2 @@
export const prerender = true export const prerender = true
export const trailingSlash = "always" export const trailingSlash = "always"

View File

@@ -1,6 +0,0 @@
import {redirect} from "@sveltejs/kit"
/** @type {import("./$types").PageLoad} */
export function load() {
throw redirect(302, "/config/")
}

6
src/routes/+page.ts Normal file
View File

@@ -0,0 +1,6 @@
import {redirect} from "@sveltejs/kit"
import type {PageLoad} from "./$types"
export const load = (() => {
throw redirect(302, "/config/")
}) satisfies PageLoad

View File

@@ -1,6 +0,0 @@
import {redirect} from "@sveltejs/kit"
/** @type {import("./$types").PageLoad} */
export function load() {
throw redirect(302, "/config/chords/")
}

View File

@@ -0,0 +1,6 @@
import {redirect} from "@sveltejs/kit"
import type {PageLoad} from "./$types"
export const load = (() => {
throw redirect(302, "/config/chords/")
}) satisfies PageLoad

View File

@@ -1,6 +1,6 @@
<script> <script lang="ts">
import {chords} from "$lib/serial/connection.js" import {chords} from "$lib/serial/connection"
import {KEYMAP_CODES} from "$lib/serial/keymap-codes.js" import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
</script> </script>
<svelte:head> <svelte:head>