mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-19 16:32:58 +00:00
feat: new sharing system
feat: support legacy layout import
This commit is contained in:
52
src/lib/share/action-array.ts
Normal file
52
src/lib/share/action-array.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import {compressActions, decompressActions} from "$lib/serialization/actions"
|
||||
import {CHARA_FILE_TYPES} from "$lib/share/share-url"
|
||||
|
||||
export type ActionArray = number[] | ActionArray[]
|
||||
export function serializeActionArray(array: ActionArray): Uint8Array {
|
||||
let out = new Uint8Array(5)
|
||||
const writer = new DataView(out.buffer)
|
||||
writer.setUint32(0, array.length)
|
||||
|
||||
if (array.length === 0) {
|
||||
return out
|
||||
} else if (typeof array[0] === "number") {
|
||||
writer.setUint8(4, CHARA_FILE_TYPES.indexOf("number"))
|
||||
return concatUint8Arrays(out, compressActions(array as number[]))
|
||||
} else if (Array.isArray(array[0])) {
|
||||
writer.setUint8(4, CHARA_FILE_TYPES.indexOf("array"))
|
||||
return concatUint8Arrays(out, ...(array as ActionArray[]).map(serializeActionArray))
|
||||
} else {
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
export function deserializeActionArray(raw: Uint8Array): ActionArray {
|
||||
const reader = new DataView(raw.buffer)
|
||||
const length = reader.getUint32(0)
|
||||
const type = CHARA_FILE_TYPES[reader.getUint8(4)]
|
||||
|
||||
if (type === "number") {
|
||||
return decompressActions(raw.slice(5, 5 + length))
|
||||
} else if (type === "array") {
|
||||
const innerLength = reader.getUint32(5)
|
||||
const out = []
|
||||
let cursor = 5
|
||||
for (let i = 0; i < length; i++) {
|
||||
out.push(deserializeActionArray(raw.slice(cursor, cursor + innerLength)))
|
||||
cursor += innerLength
|
||||
}
|
||||
return out
|
||||
} else {
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
export function concatUint8Arrays(...arrays: Uint8Array[]): Uint8Array {
|
||||
const out = new Uint8Array(arrays.reduce((a, b) => a + b.length, 0))
|
||||
let offset = 0
|
||||
for (const array of arrays) {
|
||||
out.set(array, offset)
|
||||
offset += array.length
|
||||
}
|
||||
return out
|
||||
}
|
||||
15
src/lib/share/chara-file.ts
Normal file
15
src/lib/share/chara-file.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export interface CharaFile<T extends string> {
|
||||
charaVersion: 1
|
||||
type: T
|
||||
}
|
||||
|
||||
export interface CharaLayoutFile extends CharaFile<"layout"> {
|
||||
device: "one" | "lite" | string
|
||||
layout: [number[], number[], number[]]
|
||||
}
|
||||
|
||||
export interface CharaChordFile extends CharaFile<"chords"> {
|
||||
chords: [number[], number[]]
|
||||
}
|
||||
|
||||
export type CharaFiles = CharaLayoutFile | CharaChordFile
|
||||
61
src/lib/share/share-url.ts
Normal file
61
src/lib/share/share-url.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type {CharaFile, CharaFiles} from "$lib/share/chara-file"
|
||||
import type {ActionArray} from "$lib/share/action-array"
|
||||
import {deserializeActionArray, serializeActionArray} from "$lib/share/action-array"
|
||||
import {fromBase64, toBase64} from "$lib/serialization/base64"
|
||||
|
||||
type CharaLayoutOrder = {
|
||||
[K in CharaFiles["type"]]: Array<
|
||||
[Exclude<keyof Extract<CharaFiles, {type: K}>, keyof CharaFile<any>>, (typeof CHARA_FILE_TYPES)[number]]
|
||||
>
|
||||
}
|
||||
|
||||
const keys: CharaLayoutOrder = {
|
||||
layout: [
|
||||
["layout", "array"],
|
||||
["device", "string"],
|
||||
],
|
||||
chords: [["chords", "array"]],
|
||||
}
|
||||
|
||||
export const CHARA_FILE_TYPES = ["unknown", "number", "string", "array"] as const
|
||||
|
||||
const sep = "\n"
|
||||
|
||||
export async function charaFileToUriComponent<T extends CharaFiles>(file: T): Promise<string> {
|
||||
let url = `${file.type}${sep}${file.charaVersion}`
|
||||
|
||||
for (const [key, type] of keys[file.type]) {
|
||||
const value = file[key as keyof T]
|
||||
url += sep
|
||||
if (type === "string") {
|
||||
url += value as string
|
||||
} else if (type === "array") {
|
||||
const stream = new Blob([serializeActionArray(value as ActionArray)])
|
||||
.stream()
|
||||
.pipeThrough(new CompressionStream("deflate"))
|
||||
url += await toBase64(await new Response(stream).blob())
|
||||
} else {
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export async function charaFileFromUriComponent<T extends CharaFiles>(uriComponent: string): Promise<T> {
|
||||
const [fileType, version, ...values] = uriComponent.split(sep)
|
||||
const file: any = {type: fileType, version: Number(version)}
|
||||
|
||||
for (const [key, type] of keys[fileType as keyof typeof keys]) {
|
||||
const value = values.pop()!
|
||||
if (type === "string") {
|
||||
file[key] = value
|
||||
} else if (type === "array") {
|
||||
const stream = (await fromBase64(value)).stream().pipeThrough(new DecompressionStream("deflate"))
|
||||
const actions = new Uint8Array(await new Response(stream).arrayBuffer())
|
||||
file[key] = deserializeActionArray(actions)
|
||||
}
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
Reference in New Issue
Block a user