diff --git a/client/src/components/app-bar/Playback.tsx b/client/src/components/app-bar/Playback.tsx index 594e37db..d46b2ec3 100644 --- a/client/src/components/app-bar/Playback.tsx +++ b/client/src/components/app-bar/Playback.tsx @@ -66,11 +66,11 @@ export function PlaybackService({ ({ result, offset, error }) => { if (!error) { if (result) { - notify(`Breakpoint hit: ${result}.`, `${offset}`); + notify(`Breakpoint hit: ${result}`, `${offset}`); pause(offset); } else tick(playbackRate); } else { - notify(`${trimEnd(error, ".")}.`, `${offset}`); + notify(`${trimEnd(error, ".")}`, `${offset}`); pause(); } } diff --git a/client/src/components/title-bar/TitleBar.tsx b/client/src/components/title-bar/TitleBar.tsx index 646923db..3b48c65c 100644 --- a/client/src/components/title-bar/TitleBar.tsx +++ b/client/src/components/title-bar/TitleBar.tsx @@ -71,6 +71,11 @@ export const TitleBar = () => { key: "workspace-save", action: save, }, + { + name: "Save workspace (JSON)", + key: "workspace-save", + action: () => save(true), + }, ], }, { @@ -136,7 +141,7 @@ function CommandsButton() { const notify = useSnackbar(); return ( notify("Commands are not yet implemented.")} + onClick={() => notify("Commands are not yet implemented")} sx={{ WebkitAppRegion: "no-drag", fontSize: 14, diff --git a/client/src/hooks/useWorkspace.tsx b/client/src/hooks/useWorkspace.tsx index 2ee6ce18..835f48fd 100644 --- a/client/src/hooks/useWorkspace.tsx +++ b/client/src/hooks/useWorkspace.tsx @@ -6,9 +6,13 @@ import { UIState, useUIState } from "slices/UIState"; import { formatByte, useBusyState } from "slices/busy"; import { Layers, useLayers } from "slices/layers"; import { generateUsername as id } from "unique-username-generator"; -import { parseYamlAsync } from "workers/async"; +import { + compressBinaryAsync as compress, + decompressBinaryAsync as decompress, + parseYamlAsync, +} from "workers/async"; -const acceptedFormats = [`.workspace.yaml`, `.workspace.json`]; +const acceptedFormats = [`.workspace.yaml`, `.workspace.json`, `.workspace`]; type Workspace = { UIState: UIState; @@ -31,7 +35,9 @@ export function useWorkspace() { if (f) { if (isWorkspaceFile(f)) { await usingBusyState(async () => { - const content = await f.text(); + const content = isCompressedFile(f) + ? await decompress(new Uint8Array(await f.arrayBuffer())) + : await f.text(); const parsed = (await parseYamlAsync(content)) as | Workspace | undefined; @@ -41,20 +47,30 @@ export function useWorkspace() { } }, `Opening workspace (${formatByte(f.size)})`); } else { - notify(`${f?.name} is not a workspace file.`); + notify(`${f?.name} is not a workspace file`); } } }, - save: () => { - download( - JSON.stringify({ layers, UIState }), - `${id("-")}.workspace.json`, - "application/json" - ); + save: async (raw?: boolean) => { + notify("Saving workspace..."); + const content = JSON.stringify({ layers, UIState }); + if (raw) { + const name = `${id("-")}.workspace.json`; + download(content, name, "application/json"); + notify("Workspace saved", name); + } else { + const name = `${id("-")}.workspace`; + download(await compress(content), name, "application/octet-stream"); + notify("Workspace saved", name); + } }, }; } +function isCompressedFile(f: File) { + return f.name.endsWith(`.workspace`); +} + export function isWorkspaceFile(f: File) { return find(acceptedFormats, (format) => f.name.endsWith(format)); } diff --git a/client/src/layers/query/index.tsx b/client/src/layers/query/index.tsx index 9149735d..5b177cb1 100644 --- a/client/src/layers/query/index.tsx +++ b/client/src/layers/query/index.tsx @@ -159,7 +159,7 @@ export const controller = { }) ); } else { - notify("Canceled."); + notify("Canceled"); } } } diff --git a/client/src/services/ConnectionsService.tsx b/client/src/services/ConnectionsService.tsx index e0fa1696..72e0da25 100644 --- a/client/src/services/ConnectionsService.tsx +++ b/client/src/services/ConnectionsService.tsx @@ -35,7 +35,7 @@ export function ConnectionsService() { await tp.connect(); const { result, delta } = await timed(() => tp.call("about")); if (result) { - notify(`Connected to ${result.name}.`); + notify(`Connected to ${result.name}`); cs = [ ...cs, { @@ -51,7 +51,7 @@ export function ConnectionsService() { if (!aborted) setConnections(() => cs); } if (!aborted) - notify(`Connected to ${cs.length} of ${remote.length} solvers.`); + notify(`Connected to ${cs.length} of ${remote.length} solvers`); } }); return () => { diff --git a/client/src/workers/async.ts b/client/src/workers/async.ts index 0c1bfbdb..4e7a9c40 100644 --- a/client/src/workers/async.ts +++ b/client/src/workers/async.ts @@ -1,5 +1,11 @@ import { memoize as memo } from "lodash"; -import { CompressWorker, HashWorker, ParseYamlWorker } from "."; +import { + CompressWorker, + CompressBinaryWorker, + DecompressBinaryWorker, + HashWorker, + ParseYamlWorker, +} from "."; import { usingWorkerTask } from "./usingWorker"; export const hashAsync = memo(usingWorkerTask(HashWorker)); @@ -7,6 +13,12 @@ export const hashAsync = memo(usingWorkerTask(HashWorker)); export const compressAsync = memo( usingWorkerTask(CompressWorker) ); +export const compressBinaryAsync = memo( + usingWorkerTask(CompressBinaryWorker) +); +export const decompressBinaryAsync = memo( + usingWorkerTask(DecompressBinaryWorker) +); export const parseYamlAsync = memo( usingWorkerTask(ParseYamlWorker) diff --git a/client/src/workers/compressBinary.worker.ts b/client/src/workers/compressBinary.worker.ts new file mode 100644 index 00000000..1eec84e1 --- /dev/null +++ b/client/src/workers/compressBinary.worker.ts @@ -0,0 +1,2 @@ +import { compressToUint8Array as compress } from "lz-string"; +onmessage = (str: MessageEvent) => postMessage(compress(str.data)); diff --git a/client/src/workers/decompressBinary.worker.ts b/client/src/workers/decompressBinary.worker.ts new file mode 100644 index 00000000..58d460d1 --- /dev/null +++ b/client/src/workers/decompressBinary.worker.ts @@ -0,0 +1,3 @@ +import { decompressFromUint8Array as decompress } from "lz-string"; +onmessage = (str: MessageEvent) => + postMessage(decompress(str.data)); diff --git a/client/src/workers/index.ts b/client/src/workers/index.ts index 575814c8..55403df6 100644 --- a/client/src/workers/index.ts +++ b/client/src/workers/index.ts @@ -1,6 +1,8 @@ import hashWorkerUrl from "./hash.worker.ts?worker&url"; import ipcWorkerUrl from "./ipc.worker.ts?worker&url"; import compressWorkerUrl from "./compress.worker.ts?worker&url"; +import compressBinaryWorkerUrl from "./compressBinary.worker.ts?worker&url"; +import decompressBinaryWorkerUrl from "./decompressBinary.worker.ts?worker&url"; import yamlWorkerUrl from "./parseYaml.worker.ts?worker&url"; export class HashWorker extends Worker { @@ -14,6 +16,16 @@ export class CompressWorker extends Worker { super(compressWorkerUrl, { type: "module" }); } } +export class CompressBinaryWorker extends Worker { + constructor() { + super(compressBinaryWorkerUrl, { type: "module" }); + } +} +export class DecompressBinaryWorker extends Worker { + constructor() { + super(decompressBinaryWorkerUrl, { type: "module" }); + } +} export class IPCWorker extends Worker { constructor() {