From 5c6ea6dd6c8323c5f0b634aff97d4c550c340e06 Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 10 May 2025 22:32:40 +0800 Subject: [PATCH] fix: fix createAssetId --- package.json | 2 +- packages/renderer/src/loader.ts | 23 ++++++-- packages/util/__tests__/lib/data.test.ts | 73 +++--------------------- packages/util/__tests__/lib/uuid.test.ts | 7 ++- packages/util/src/tool/hash.ts | 52 ----------------- packages/util/src/tool/uuid.ts | 37 +++++++++++- packages/util/src/view/data.ts | 12 ++-- packages/util/src/view/element.ts | 2 +- 8 files changed, 73 insertions(+), 135 deletions(-) delete mode 100644 packages/util/src/tool/hash.ts diff --git a/package.json b/package.json index 1464c2f..e92769a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": false, - "version": "0.4.0-beta.41", + "version": "0.4.0-beta.42", "workspaces": [ "packages/*" ], diff --git a/packages/renderer/src/loader.ts b/packages/renderer/src/loader.ts index 649e5e0..a29c2a2 100644 --- a/packages/renderer/src/loader.ts +++ b/packages/renderer/src/loader.ts @@ -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 implements RendererLoader { @@ -169,7 +179,12 @@ export class Loader extends EventEmitter implements RendererLoad #isExistingErrorStorage(element: Element) { 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; diff --git a/packages/util/__tests__/lib/data.test.ts b/packages/util/__tests__/lib/data.test.ts index 8eca2b7..1cb7dfa 100644 --- a/packages/util/__tests__/lib/data.test.ts +++ b/packages/util/__tests__/lib/data.test.ts @@ -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(expectData); diff --git a/packages/util/__tests__/lib/uuid.test.ts b/packages/util/__tests__/lib/uuid.test.ts index 2ac1909..a73e82e 100644 --- a/packages/util/__tests__/lib/uuid.test.ts +++ b/packages/util/__tests__/lib/uuid.test.ts @@ -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); }); }); diff --git a/packages/util/src/tool/hash.ts b/packages/util/src/tool/hash.ts deleted file mode 100644 index 52e9004..0000000 --- a/packages/util/src/tool/hash.ts +++ /dev/null @@ -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; -} diff --git a/packages/util/src/tool/uuid.ts b/packages/util/src/tool/uuid.ts index 37753c3..4a913f4 100644 --- a/packages/util/src/tool/uuid.ts +++ b/packages/util/src/tool/uuid.ts @@ -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 { diff --git a/packages/util/src/view/data.ts b/packages/util/src/view/data.ts index 889363d..d1bc7e0 100644 --- a/packages/util/src/view/data.ts +++ b/packages/util/src/view/data.ts @@ -72,7 +72,7 @@ export function sortDataAsserts(data: Data, opts?: { clone?: boolean }): Data { elems.forEach((elem: Element) => { 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', diff --git a/packages/util/src/view/element.ts b/packages/util/src/view/element.ts index 59ca536..0ec5611 100644 --- a/packages/util/src/view/element.ts +++ b/packages/util/src/view/element.ts @@ -373,7 +373,7 @@ export function filterElementAsset>( } if (typeof resource === 'string' && !isAssetId(resource)) { - assetId = createAssetId(resource); + assetId = createAssetId(resource, element.uuid); assetItem = { type: element.type as LoadElementType, value: resource