feat: layout url import

feat: backup import (except chords)
feat: legacy layout import
feat: separate layout, chord & setting backup downloads
This commit is contained in:
2023-11-15 01:14:34 +01:00
parent acd58646f6
commit c5d9defc9d
20 changed files with 299 additions and 143 deletions

View File

@@ -0,0 +1,53 @@
import {describe, it, expect} from "vitest"
import {deserializeActionArray, serializeActionArray} from "./action-array"
describe("action array", () => {
it("should work with number arrays", () => {
expect(deserializeActionArray(serializeActionArray([62, 256, 1235]))).toEqual([62, 256, 1235])
})
it("should work with nested arrays", () => {
expect(deserializeActionArray(serializeActionArray([[], [[]]]))).toEqual([[], [[]]])
})
it("should compress back and forth", () => {
expect(
deserializeActionArray(
serializeActionArray([
[43, 746, 634],
[34, 63],
[332, 34],
]),
),
).toEqual([
[43, 746, 634],
[34, 63],
[332, 34],
])
})
it("should compress a full layout", () => {
const layout = Object.freeze([
Object.freeze([
0, 0, 0, 0, 0, 53, 119, 45, 103, 122, 52, 107, 118, 109, 99, 51, 114, 36, 59, 101, 50, 105, 34, 46,
111, 49, 39, 515, 44, 117, 0, 512, 514, 513, 550, 0, 319, 318, 321, 320, 326, 315, 314, 317, 316, 0,
0, 0, 0, 0, 54, 98, 120, 536, 113, 55, 102, 112, 104, 100, 56, 97, 296, 544, 116, 57, 108, 299, 106,
110, 48, 121, 297, 61, 115, 0, 518, 516, 517, 553, 0, 336, 338, 335, 337, 0, 325, 322, 323, 324,
]),
Object.freeze([
0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 53, 0, 47, 52, 0, 51, 298, 0, 50, 0, 0, 127, 0, 49, 0, 0, 515, 0, 0,
0, 512, 514, 513, 550, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 0, 536, 0, 0, 54, 0, 92,
55, 0, 56, 296, 544, 57, 0, 96, 299, 0, 48, 0, 0, 297, 0, 0, 0, 518, 516, 517, 553, 0, 336, 338, 335,
337, 0, 0, 0, 0, 0,
]),
Object.freeze([
0, 0, 0, 0, 0, 0, 64, 95, 43, 0, 0, 126, 38, 63, 40, 0, 35, 298, 36, 123, 0, 33, 127, 37, 60, 0, 34,
515, 0, 0, 0, 512, 514, 513, 550, 0, 333, 331, 330, 334, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 536,
0, 0, 94, 58, 124, 41, 0, 42, 296, 544, 125, 0, 126, 299, 0, 62, 0, 0, 297, 0, 0, 0, 518, 516, 517,
553, 0, 336, 338, 335, 337, 0, 0, 0, 0, 0,
]),
])
expect(deserializeActionArray(serializeActionArray(layout as number[][]))).toEqual(layout)
})
})

View File

@@ -1,5 +1,5 @@
import {compressActions, decompressActions} from "$lib/serialization/actions"
import {CHARA_FILE_TYPES} from "$lib/share/share-url"
import {compressActions, decompressActions} from "../serialization/actions"
import {CHARA_FILE_TYPES} from "../share/share-url"
export type ActionArray = number[] | ActionArray[]
export function serializeActionArray(array: ActionArray): Uint8Array {
@@ -11,7 +11,9 @@ export function serializeActionArray(array: ActionArray): Uint8Array {
return out
} else if (typeof array[0] === "number") {
writer.setUint8(4, CHARA_FILE_TYPES.indexOf("number"))
return concatUint8Arrays(out, compressActions(array as number[]))
const compressed = compressActions(array as number[])
writer.setUint32(0, compressed.length)
return concatUint8Arrays(out, compressed)
} else if (Array.isArray(array[0])) {
writer.setUint8(4, CHARA_FILE_TYPES.indexOf("array"))
return concatUint8Arrays(out, ...(array as ActionArray[]).map(serializeActionArray))
@@ -20,20 +22,23 @@ export function serializeActionArray(array: ActionArray): Uint8Array {
}
}
export function deserializeActionArray(raw: Uint8Array): ActionArray {
export function deserializeActionArray(raw: Uint8Array, cursor = {pos: 0}): ActionArray {
const reader = new DataView(raw.buffer)
const length = reader.getUint32(0)
const type = CHARA_FILE_TYPES[reader.getUint8(4)]
const length = reader.getUint32(cursor.pos)
cursor.pos += 4
const type = CHARA_FILE_TYPES[reader.getUint8(cursor.pos)]
cursor.pos++
console.log(cursor, raw)
if (type === "number") {
return decompressActions(raw.slice(5, 5 + length))
const decompressed = decompressActions(raw.slice(cursor.pos, cursor.pos + length))
cursor.pos += length
return decompressed
} 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
out.push(deserializeActionArray(raw, cursor))
}
return out
} else {

View File

@@ -20,4 +20,4 @@ export interface CharaBackupFile extends CharaFile<"backup"> {
history: [CharaChordFile, CharaLayoutFile, CharaSettingsFile][]
}
export type CharaFiles = CharaLayoutFile | CharaChordFile
export type CharaFiles = CharaLayoutFile | CharaChordFile | CharaSettingsFile

View File

@@ -1,7 +1,7 @@
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"
import type {CharaFile, CharaFiles} from "../share/chara-file"
import type {ActionArray} from "../share/action-array"
import {deserializeActionArray, serializeActionArray} from "../share/action-array"
import {fromBase64, toBase64} from "../serialization/base64"
type CharaLayoutOrder = {
[K in CharaFiles["type"]]: Array<
@@ -15,6 +15,7 @@ const keys: CharaLayoutOrder = {
["device", "string"],
],
chords: [["chords", "array"]],
settings: [["settings", "array"]],
}
export const CHARA_FILE_TYPES = ["unknown", "number", "string", "array"] as const
@@ -42,17 +43,21 @@ export async function charaFileToUriComponent<T extends CharaFiles>(file: T): Pr
return url
}
export async function charaFileFromUriComponent<T extends CharaFiles>(uriComponent: string): Promise<T> {
export async function charaFileFromUriComponent<T extends CharaFiles>(
uriComponent: string,
fetch = window.fetch,
): Promise<T> {
const [fileType, version, ...values] = uriComponent.split(sep)
const file: any = {type: fileType, version: Number(version)}
const file: any = {type: fileType, charaVersion: Number(version)}
for (const [key, type] of keys[fileType as keyof typeof keys]) {
const value = values.pop()!
const value = values.shift()!
if (type === "string") {
file[key] = value
} else if (type === "array") {
const stream = (await fromBase64(value)).stream().pipeThrough(new DecompressionStream("deflate"))
const stream = (await fromBase64(value, fetch)).stream().pipeThrough(new DecompressionStream("deflate"))
const actions = new Uint8Array(await new Response(stream).arrayBuffer())
console.log(actions)
file[key] = deserializeActionArray(actions)
}
}