mirror of
https://github.com/idrawjs/idraw
synced 2026-05-23 17:48:23 +00:00
commit
9227526f9e
8 changed files with 73 additions and 135 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"private": false,
|
||||
"version": "0.4.0-beta.41",
|
||||
"version": "0.4.0-beta.42",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,14 @@
|
|||
import type { RendererLoader, LoaderEventMap, LoadFunc, LoadContent, LoadItem, LoadItemMap, LoadElementType, Element, ElementAssets } from '@idraw/types';
|
||||
import type {
|
||||
RendererLoader,
|
||||
LoaderEventMap,
|
||||
LoadFunc,
|
||||
LoadContent,
|
||||
LoadItem,
|
||||
LoadItemMap,
|
||||
LoadElementType,
|
||||
Element,
|
||||
ElementAssets
|
||||
} from '@idraw/types';
|
||||
import { loadImage, loadHTML, loadSVG, EventEmitter, createAssetId, isAssetId, createUUID } from '@idraw/util';
|
||||
|
||||
const supportElementTypes: LoadElementType[] = ['image', 'svg', 'html'];
|
||||
|
|
@ -16,9 +26,9 @@ const getAssetIdFromElement = (element: Element<'image' | 'svg' | 'html'>) => {
|
|||
if (isAssetId(source)) {
|
||||
return source;
|
||||
}
|
||||
return createAssetId(source);
|
||||
return createAssetId(source, element.uuid);
|
||||
}
|
||||
return createAssetId(`${createUUID()}-${element.uuid}-${createUUID()}-${createUUID()}`);
|
||||
return createAssetId(`${createUUID()}-${element.uuid}-${createUUID()}-${createUUID()}`, element.uuid);
|
||||
};
|
||||
|
||||
export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoader {
|
||||
|
|
@ -169,7 +179,12 @@ export class Loader extends EventEmitter<LoaderEventMap> implements RendererLoad
|
|||
#isExistingErrorStorage(element: Element<LoadElementType>) {
|
||||
const assetId = getAssetIdFromElement(element);
|
||||
const existItem = this.#currentLoadItemMap?.[assetId];
|
||||
if (existItem && existItem.status === 'error' && existItem.source && existItem.source === this.#getLoadElementSource(element)) {
|
||||
if (
|
||||
existItem &&
|
||||
existItem.status === 'error' &&
|
||||
existItem.source &&
|
||||
existItem.source === this.#getLoadElementSource(element)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -15,28 +15,6 @@ const originData: Data = {
|
|||
src: imageBase64
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: '39308517-e10f-76df-43a9-50ed7295e61e',
|
||||
type: 'svg',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: {
|
||||
svg: svg
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: 'ef934ab7-a32e-040c-9ac0-ed193405e6e4',
|
||||
type: 'html',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: {
|
||||
html: html
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: '063e3a80-1ede-7912-f919-975e34a9bd01',
|
||||
type: 'group',
|
||||
|
|
@ -46,17 +24,6 @@ const originData: Data = {
|
|||
h: 100,
|
||||
detail: {
|
||||
children: [
|
||||
{
|
||||
uuid: 'e0889472-1f16-d6cd-3c7a-4b827d52279d',
|
||||
type: 'image',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: {
|
||||
src: imageBase64
|
||||
}
|
||||
},
|
||||
{
|
||||
uuid: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c',
|
||||
type: 'svg',
|
||||
|
|
@ -99,25 +66,7 @@ describe('@idraw/util: data ', () => {
|
|||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: { src: '@assets/1k7sknuo56gr0h9ug9hs5g5xxgzeee07' }
|
||||
},
|
||||
{
|
||||
uuid: '39308517-e10f-76df-43a9-50ed7295e61e',
|
||||
type: 'svg',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: { svg: '@assets/36jxqyevkyph8yveb6zalsgxj5vc8not' }
|
||||
},
|
||||
{
|
||||
uuid: 'ef934ab7-a32e-040c-9ac0-ed193405e6e4',
|
||||
type: 'html',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: { html: '@assets/cevdw4d1r85ynahctsjex89y03yev87a' }
|
||||
detail: { src: '@assets/0a920a91-0aba-0af3-0aeb-0a730accafb' }
|
||||
},
|
||||
{
|
||||
uuid: '063e3a80-1ede-7912-f919-975e34a9bd01',
|
||||
|
|
@ -128,15 +77,6 @@ describe('@idraw/util: data ', () => {
|
|||
h: 100,
|
||||
detail: {
|
||||
children: [
|
||||
{
|
||||
uuid: 'e0889472-1f16-d6cd-3c7a-4b827d52279d',
|
||||
type: 'image',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: { src: '@assets/1k7sknuo56gr0h9ug9hs5g5xxgzeee07' }
|
||||
},
|
||||
{
|
||||
uuid: 'b60e64e8-833e-e112-d7eb-1ab6e7d6870c',
|
||||
type: 'svg',
|
||||
|
|
@ -144,7 +84,7 @@ describe('@idraw/util: data ', () => {
|
|||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: { svg: '@assets/36jxqyevkyph8yveb6zalsgxj5vc8not' }
|
||||
detail: { svg: '@assets/0a830ab3-0a5d-0a5b-0a63-0a740a6cb34' }
|
||||
},
|
||||
{
|
||||
uuid: '61f2a61e-cdd5-ae36-983f-686ba8e35973',
|
||||
|
|
@ -153,27 +93,28 @@ describe('@idraw/util: data ', () => {
|
|||
y: 0,
|
||||
w: 100,
|
||||
h: 100,
|
||||
detail: { html: '@assets/cevdw4d1r85ynahctsjex89y03yev87a' }
|
||||
detail: { html: '@assets/0a2b0ab4-0b45-0b19-0a0d-0add0a0dab5' }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
assets: {
|
||||
'@assets/1k7sknuo56gr0h9ug9hs5g5xxgzeee07': {
|
||||
'@assets/0a920a91-0aba-0af3-0aeb-0a730accafb': {
|
||||
type: 'image',
|
||||
value: imageBase64
|
||||
},
|
||||
'@assets/36jxqyevkyph8yveb6zalsgxj5vc8not': {
|
||||
'@assets/0a830ab3-0a5d-0a5b-0a63-0a740a6cb34': {
|
||||
type: 'svg',
|
||||
value: svg
|
||||
},
|
||||
'@assets/cevdw4d1r85ynahctsjex89y03yev87a': {
|
||||
'@assets/0a2b0ab4-0b45-0b19-0a0d-0add0a0dab5': {
|
||||
type: 'html',
|
||||
value: html
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(compactData).toStrictEqual(expectData);
|
||||
|
||||
const data2: Data = deepClone<Data>(expectData);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@ import { createAssetId, isAssetId } from '@idraw/util';
|
|||
describe('@idraw/util: createAssetId ', () => {
|
||||
test('url', () => {
|
||||
const url1 = 'https://example.com/2025/01/01/000001.jpg';
|
||||
const assetId1 = createAssetId(url1);
|
||||
const assetId1 = createAssetId(url1, '001');
|
||||
expect(isAssetId(assetId1)).toBeTruthy();
|
||||
expect(assetId1).toBe('@assets/01d801ff-0200-023c-019c-015a0150251');
|
||||
|
||||
const url2 = 'https://example.com/2025/01/01/000002.jpg';
|
||||
const assetId2 = createAssetId(url2);
|
||||
const assetId2 = createAssetId(url2, '002');
|
||||
expect(isAssetId(assetId2)).toBeTruthy();
|
||||
expect(assetId2).toBe('@assets/01d90200-0201-023d-019d-015b0151252');
|
||||
|
||||
expect(url1).not.toBe(url2);
|
||||
expect(assetId1).not.toBe(assetId2);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
/**
|
||||
* Synchronously generates 32-character Base36 encoded hash (enhanced 256-bit algorithm)
|
||||
* @param str - Input string (any length)
|
||||
* @returns 32-character lowercase Base36 string (0-9a-z)
|
||||
*/
|
||||
export function generate32Base36Hash(str: string): string {
|
||||
// Generate 256-bit hash (4x64-bit FNV hashes)
|
||||
const hash256 = generate256BitHash(str);
|
||||
// Convert to Base36 and format to 32 characters
|
||||
return bigIntToBase36(hash256).padStart(32, '0').slice(0, 32);
|
||||
}
|
||||
// // Usage example
|
||||
// console.log(generate32Base36Hash('hello world'));
|
||||
// // Sample output: 2yj8q4z7kpr6s9d5m2w3x1g6h8j4n0q
|
||||
|
||||
// Core algorithm for generating 256-bit hash
|
||||
function generate256BitHash(str: string): bigint {
|
||||
let h1 = 0xcbf29ce484222325n,
|
||||
h2 = 0x84222325cbf29ce4n;
|
||||
let h3 = 0x1b3n * h1,
|
||||
h4 = 0x1000000n * h2; // Different initial values
|
||||
const prime = 0x100000001b3n;
|
||||
|
||||
// Chunk processing for large texts (per 4096 characters)
|
||||
const chunkSize = 4096;
|
||||
for (let i = 0; i < str.length; i += chunkSize) {
|
||||
const chunk = str.slice(i, i + chunkSize);
|
||||
for (let j = 0; j < chunk.length; j++) {
|
||||
const code = BigInt(chunk.charCodeAt(j) + i + j); // Position-sensitive
|
||||
h1 = (h1 ^ code) * prime;
|
||||
h2 = ((h2 ^ h1) * prime) ^ h3;
|
||||
h3 = (h3 ^ h2) * prime + h4;
|
||||
h4 = ((h4 ^ h3) * prime) | 0x1234567890abcdefn;
|
||||
}
|
||||
}
|
||||
|
||||
// Combine 4x64-bit hashes into 256-bit
|
||||
return (h1 << 192n) | (h2 << 128n) | (h3 << 64n) | h4;
|
||||
}
|
||||
|
||||
// Utility function for BigInt to Base36 conversion
|
||||
function bigIntToBase36(num: bigint): string {
|
||||
const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
if (num === 0n) return '0';
|
||||
let result = '';
|
||||
while (num > 0n) {
|
||||
const rem = num % 36n;
|
||||
result = chars[Number(rem)] + result;
|
||||
num = num / 36n;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { generate32Base36Hash } from './hash';
|
||||
// import { generate32Base36Hash } from './hash';
|
||||
|
||||
export function createUUID(): string {
|
||||
function _createStr() {
|
||||
|
|
@ -7,8 +7,39 @@ export function createUUID(): string {
|
|||
return `${_createStr()}${_createStr()}-${_createStr()}-${_createStr()}-${_createStr()}-${_createStr()}${_createStr()}${_createStr()}`;
|
||||
}
|
||||
|
||||
export function createAssetId(assetStr: string): string {
|
||||
return `@assets/${generate32Base36Hash(assetStr)}`;
|
||||
function limitHexStr(str: string, seed: number) {
|
||||
let count = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
count += str.charCodeAt(i);
|
||||
}
|
||||
return (count + seed).toString(16).substring(0, 4);
|
||||
}
|
||||
|
||||
function sumCharCodes(str: string): number {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
sum += str.charCodeAt(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
export function createAssetId(assetStr: string, elemUUID: string): string {
|
||||
const len = assetStr.length;
|
||||
const seed = sumCharCodes(elemUUID);
|
||||
|
||||
const mid = Math.floor(len / 2);
|
||||
const start4 = assetStr.substring(0, 4).padStart(4, '0');
|
||||
const end4 = assetStr.substring(0, 4).padStart(4, '0');
|
||||
const str1 = limitHexStr(len.toString(16).padStart(4, start4), seed).padStart(4, '0');
|
||||
const str2 = limitHexStr(assetStr.substring(mid - 4, mid).padStart(4, start4), seed).padStart(4, '0');
|
||||
const str3 = limitHexStr(assetStr.substring(mid - 8, mid - 4).padStart(4, start4), seed).padStart(4, '0');
|
||||
const str4 = limitHexStr(assetStr.substring(mid - 12, mid - 8).padStart(4, start4), seed).padStart(4, '0');
|
||||
const str5 = limitHexStr(assetStr.substring(mid - 16, mid - 12).padStart(4, end4), seed).padStart(4, '0');
|
||||
const str6 = limitHexStr(assetStr.substring(mid, mid + 4).padStart(4, end4), seed).padStart(4, '0');
|
||||
const str7 = limitHexStr(assetStr.substring(mid + 4, mid + 8).padStart(4, end4), seed).padStart(4, '0');
|
||||
const str8 = limitHexStr(end4.padStart(4, start4).padStart(4, end4), seed);
|
||||
|
||||
return `@assets/${str1}${str2}-${str3}-${str4}-${str5}-${str6}${str7}${str8}`;
|
||||
}
|
||||
|
||||
export function isAssetId(id: any | string): boolean {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export function sortDataAsserts(data: Data, opts?: { clone?: boolean }): Data {
|
|||
elems.forEach((elem: Element<ElementType>) => {
|
||||
if (elem.type === 'image' && (elem as Element<'image'>).detail.src) {
|
||||
const src = (elem as Element<'image'>).detail.src;
|
||||
const assetUUID = createAssetId(src);
|
||||
const assetUUID = createAssetId(src, elem.uuid);
|
||||
if (!assets[assetUUID]) {
|
||||
assets[assetUUID] = {
|
||||
type: 'image',
|
||||
|
|
@ -82,7 +82,7 @@ export function sortDataAsserts(data: Data, opts?: { clone?: boolean }): Data {
|
|||
(elem as Element<'image'>).detail.src = assetUUID;
|
||||
} else if (elem.type === 'svg') {
|
||||
const svg = (elem as Element<'svg'>).detail.svg;
|
||||
const assetUUID = createAssetId(svg);
|
||||
const assetUUID = createAssetId(svg, elem.uuid);
|
||||
if (!assets[assetUUID]) {
|
||||
assets[assetUUID] = {
|
||||
type: 'svg',
|
||||
|
|
@ -92,7 +92,7 @@ export function sortDataAsserts(data: Data, opts?: { clone?: boolean }): Data {
|
|||
(elem as Element<'svg'>).detail.svg = assetUUID;
|
||||
} else if (elem.type === 'html') {
|
||||
const html = (elem as Element<'html'>).detail.html;
|
||||
const assetUUID = createAssetId(html);
|
||||
const assetUUID = createAssetId(html, elem.uuid);
|
||||
if (!assets[assetUUID]) {
|
||||
assets[assetUUID] = {
|
||||
type: 'html',
|
||||
|
|
@ -133,7 +133,7 @@ export function filterCompactData(data: Data, opts?: { loadItemMap?: LoadItemMap
|
|||
value: loadItemMap[src].source as string
|
||||
};
|
||||
} else if (!assets[src]) {
|
||||
const assetUUID = createAssetId(src);
|
||||
const assetUUID = createAssetId(src, elem.uuid);
|
||||
if (!assets[assetUUID]) {
|
||||
assets[assetUUID] = {
|
||||
type: 'image',
|
||||
|
|
@ -151,7 +151,7 @@ export function filterCompactData(data: Data, opts?: { loadItemMap?: LoadItemMap
|
|||
value: loadItemMap[svg].source as string
|
||||
};
|
||||
} else if (!assets[svg]) {
|
||||
const assetUUID = createAssetId(svg);
|
||||
const assetUUID = createAssetId(svg, elem.uuid);
|
||||
if (!assets[assetUUID]) {
|
||||
assets[assetUUID] = {
|
||||
type: 'svg',
|
||||
|
|
@ -169,7 +169,7 @@ export function filterCompactData(data: Data, opts?: { loadItemMap?: LoadItemMap
|
|||
value: loadItemMap[html].source as string
|
||||
};
|
||||
} else if (!assets[html]) {
|
||||
const assetUUID = createAssetId(html);
|
||||
const assetUUID = createAssetId(html, elem.uuid);
|
||||
if (!assets[assetUUID]) {
|
||||
assets[assetUUID] = {
|
||||
type: 'html',
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ export function filterElementAsset<T extends Element<LoadElementType>>(
|
|||
}
|
||||
|
||||
if (typeof resource === 'string' && !isAssetId(resource)) {
|
||||
assetId = createAssetId(resource);
|
||||
assetId = createAssetId(resource, element.uuid);
|
||||
assetItem = {
|
||||
type: element.type as LoadElementType,
|
||||
value: resource
|
||||
|
|
|
|||
Loading…
Reference in a new issue