refactor(workspace): split workspace interface and implementation (#5463)

@affine/workspace -> (@affine/workspace, @affine/workspace-impl)
This commit is contained in:
EYHN
2024-01-02 10:58:01 +00:00
parent 9d0b3b4947
commit 104c21d84c
77 changed files with 325 additions and 163 deletions
@@ -0,0 +1,18 @@
import { Buffer } from 'node:buffer';
import { describe, expect, test } from 'vitest';
import { isSvgBuffer } from '../buffer-to-blob';
describe('isSvgBuffer', () => {
test('basic', async () => {
expect(isSvgBuffer(Buffer.from('<svg></svg>'))).toBe(true);
expect(isSvgBuffer(Buffer.from(' \n\r\t<svg></svg>'))).toBe(true);
expect(isSvgBuffer(Buffer.from('<123>'))).toBe(false);
expect(
isSvgBuffer(
Buffer.from('<?xml version="1.0" encoding="UTF-8"?><svg></svg>')
)
).toBe(true);
});
});
@@ -0,0 +1,26 @@
import { Manager } from 'socket.io-client';
let ioManager: Manager | null = null;
function getBaseUrl(): string {
if (environment.isDesktop) {
return runtimeConfig.serverUrlPrefix;
}
const { protocol, hostname, port } = window.location;
return `${protocol === 'https:' ? 'wss' : 'ws'}://${hostname}${
port ? `:${port}` : ''
}`;
}
// use lazy initialization socket.io io manager
export function getIoManager(): Manager {
if (ioManager) {
return ioManager;
}
ioManager = new Manager(`${getBaseUrl()}/`, {
autoConnect: false,
transports: ['websocket'],
secure: location.protocol === 'https:',
});
return ioManager;
}
@@ -0,0 +1,28 @@
export function uint8ArrayToBase64(array: Uint8Array): Promise<string> {
return new Promise<string>(resolve => {
// Create a blob from the Uint8Array
const blob = new Blob([array]);
const reader = new FileReader();
reader.onload = function () {
const dataUrl = reader.result as string | null;
if (!dataUrl) {
resolve('');
return;
}
// The result includes the `data:` URL prefix and the MIME type. We only want the Base64 data
const base64 = dataUrl.split(',')[1];
resolve(base64);
};
reader.readAsDataURL(blob);
});
}
export function base64ToUint8Array(base64: string) {
const binaryString = atob(base64);
const binaryArray = binaryString.split('').map(function (char) {
return char.charCodeAt(0);
});
return new Uint8Array(binaryArray);
}
@@ -0,0 +1,59 @@
import isSvg from 'is-svg';
function fastCheckIsNotSvg(buffer: Uint8Array) {
// check first non-whitespace character is not '<svg' or '<?xml'
for (let i = 0; i < buffer.length; i++) {
const ch = buffer[i];
// skip whitespace
if (
ch === 0x20 /* \s */ ||
ch === 0x09 /* \t */ ||
ch === 0x0b /* \v */ ||
ch === 0x0c /* \f */ ||
ch === 0x0a /* \n */ ||
ch === 0x0d /* \r */ ||
ch === 0xa0
) {
continue;
}
return (
!(
buffer[i] === /* '<' */ 0x3c &&
buffer[i + 1] === /* 's' */ 0x73 &&
buffer[i + 2] === /* 'v' */ 0x76 &&
buffer[i + 3] === /* 'g' */ 0x67
) &&
!(
buffer[i] === /* '<' */ 0x3c &&
buffer[i + 1] === /* '?' */ 0x3f &&
buffer[i + 2] === /* 'x' */ 0x78 &&
buffer[i + 3] === /* 'm' */ 0x6d &&
buffer[i + 4] === /* 'l' */ 0x6c
)
);
}
return true;
}
// this has a overhead of converting to string for testing if it is svg.
export function isSvgBuffer(buffer: Uint8Array) {
if (fastCheckIsNotSvg(buffer)) {
return false;
}
const decoder = new TextDecoder('utf-8');
const str = decoder.decode(buffer);
return isSvg(str);
}
export function bufferToBlob(buffer: Uint8Array | ArrayBuffer) {
const isSVG = isSvgBuffer(
buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer
);
// for svg blob, we need to explicitly set the type to image/svg+xml
return isSVG
? new Blob([buffer], { type: 'image/svg+xml' })
: new Blob([buffer]);
}
@@ -0,0 +1,17 @@
import { applyUpdate, Doc, encodeStateAsUpdate } from 'yjs';
export function mergeUpdates(updates: Uint8Array[]) {
if (updates.length === 0) {
return new Uint8Array();
}
if (updates.length === 1) {
return updates[0];
}
const doc = new Doc();
doc.transact(() => {
updates.forEach(update => {
applyUpdate(doc, update);
});
});
return encodeStateAsUpdate(doc);
}