fix: fix createAssetId

This commit is contained in:
chenshenhai 2025-05-10 22:32:40 +08:00
parent 3d0a288fe4
commit 5c6ea6dd6c
8 changed files with 73 additions and 135 deletions

View file

@ -1,6 +1,6 @@
{
"private": false,
"version": "0.4.0-beta.41",
"version": "0.4.0-beta.42",
"workspaces": [
"packages/*"
],

View file

@ -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;

View file

@ -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);

View file

@ -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);
});
});

View file

@ -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;
}

View file

@ -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 {

View file

@ -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',

View file

@ -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