Skip to content

Commit

Permalink
fix: image asset ids (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelOsborne authored Nov 15, 2024
1 parent bd17668 commit 2566abe
Show file tree
Hide file tree
Showing 17 changed files with 12,354 additions and 9,856 deletions.
5 changes: 5 additions & 0 deletions .changeset/tricky-drinks-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@dotlottie/dotlottie-js": patch
---

fixes issues with wrong images ids
120 changes: 51 additions & 69 deletions apps/node/script.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1168,88 +1168,70 @@ async function create_showcase() {
});
}

async function debugScenario() {
async function logImages(arrayBuffer) {
// Create a new DotLottie instance from the array buffer
const dotLottie = await new DotLottie().fromArrayBuffer(arrayBuffer);

// Build the dotLottie instance
await dotLottie.build();

// Retrieve and log the image assets' IDs
const images = dotLottie.getImages().map((image) => image.id);

console.log({
images,
});

// Return the instance for further operations
return dotLottie;
}

async function debugScenario1() {
const dotLottie = new DotLottie();

await dotLottie
.setAuthor('Sam')
.setVersion('1.0')
.addAnimation({
id: 'folder',
url: 'https://lottie.host/32e49c72-af7a-4a79-97e4-fb0d115eae3e/wIqFzTMKsk.json',
})
.addStateMachine(
{
descriptor: {
id: 'openCloseFolder',
initial: 0,
},
states: [
{
name: "initialState",
type: "PlaybackState",
autoplay: true,
loop: false,
segment: [1.0, 2.0],
},
{
name: "open",
type: "PlaybackState",
autoplay: true,
loop: false,
segment: [1.0, 30.0],
},
{
name: "close",
type: "PlaybackState",
autoplay: true,
mode: "Reverse",
loop: false,
segment: [1.0, 30.0],
}
],
transitions: [
{
type: 'Transition',
from_state: 0,
to_state: 1,
string_event: {
value: 'open',
},
},
{
type: 'Transition',
from_state: 1,
to_state: 2,
string_event: {
value: 'close',
},
},
{
type: 'Transition',
from_state: 2,
to_state: 0,
on_complete_event: {},
},
],
listeners: [],
context_variables: [],
}
)
// Fetch the dotLottie file and convert it to an ArrayBuffer
const res = await fetch('https://lottie.host/7c65acc7-80e7-4bad-8098-6cebd17c8b92/ee8uf2TX6N.lottie');
const arrayBuffer = await res.arrayBuffer();

// Log the image assets on the first import
const dotLottie1 = await logImages(arrayBuffer);

// Rebuild the dotLottie instance (simulating re-import)
await dotLottie1.build();

// Convert the instance back to an ArrayBuffer
const buffer = await dotLottie1.toArrayBuffer({});

// Log the image assets after the second import
await logImages(buffer);
}

async function debugScenario2() {
const dotLottie = new DotLottie();

await dotLottie.addAnimation({
id: 'bull',
url: 'https://lottie.host/c7d4e60a-102a-4ca1-97f1-f73b2b5f0f7b/s2z6VrPpnn.json',
}).addAnimation({
id: 'shrek',
url: 'https://lottie.host/204e1620-32b7-46a1-8f99-eda64771b781/hYeiO04uqx.json'
})
.build()
.then((value) => {
return value.toArrayBuffer();
})
.then((value) => {
fs.writeFileSync('folder.lottie', Buffer.from(value));
fs.writeFileSync('new-issue.lottie', Buffer.from(value));
});

}

// create_showcase();

// debugScenario();
debugScenario1();
// debugScenario2();

createExplodingPigeon();
// createExplodingPigeon();
// createListenersAnimation();
// create_pigeon_fsm_eq_guard();
// create_pigeon_gt_gte_guard();
Expand Down
4 changes: 2 additions & 2 deletions apps/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"devDependencies": {
"@dotlottie/dotlottie-js": "workspace:^",
"@vitejs/plugin-vue": "^4.1.0",
"typescript": "^5.0.2",
"typescript": "^5.6.3",
"vite": "^4.3.0",
"vue-tsc": "^1.2.0"
"vue-tsc": "^2.1.10"
}
}
3 changes: 2 additions & 1 deletion packages/dotlottie-js/jasmine/tsup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default defineConfig(({ platform }) => {
platform,
target: ['esnext'],
tsconfig: 'tsconfig.build.json',
noExternal: platform === 'browser' ? ['fflate', 'browser-image-hash', 'valibot'] : ['browser-image-hash'],
noExternal:
platform === 'browser' ? ['fflate', 'file-type', 'browser-image-hash', 'valibot'] : ['browser-image-hash'],
loader: {
'.lottie': 'binary',
},
Expand Down
1 change: 1 addition & 0 deletions packages/dotlottie-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@lottie-animation-community/lottie-types": "^1.2.0",
"browser-image-hash": "^0.0.5",
"fflate": "^0.8.1",
"file-type": "^19.6.0",
"sharp": "^0.33.2",
"sharp-phash": "^2.1.0",
"valibot": "^0.13.1"
Expand Down
58 changes: 42 additions & 16 deletions packages/dotlottie-js/src/common/dotlottie-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,12 @@ export class DotLottieCommon {
* @param newName - desired id and fileName,
* @param imageId - The id of the LottieImage to rename
*/
private _renameImage(animation: LottieAnimationCommon, newName: string, imageId: string): void {
animation.imageAssets.forEach((imageAsset) => {
private async _renameImage(animation: LottieAnimationCommon, newName: string, imageId: string): Promise<void> {
for (const imageAsset of animation.imageAssets) {
if (imageAsset.id === imageId) {
// Rename the LottieImage
imageAsset.renameImage(newName);
const oldPath = imageAsset.fileName;

await imageAsset.renameImage(newName);

if (!animation.data) throw createError('No animation data available.');

Expand All @@ -236,27 +237,42 @@ export class DotLottieCommon {
// Find the image asset inside the animation data and rename its path
for (const asset of animationAssets) {
if ('w' in asset && 'h' in asset) {
if (asset.id === imageId) {
if (asset.p === oldPath) {
asset.p = imageAsset.fileName;
}
}
}
}
});
}
}

private _renameImageAssets(): void {
const images: Map<string, LottieImageCommon[]> = new Map();
/**
* Generates a map of duplicate image ids and their count.
* @returns Map of duplicate image ids and their count.
*/
private _generateMapOfOccurencesFromImageIds(): Map<string, number> {
const dupeMap = new Map<string, number>();

this.animations.forEach((animation) => {
images.set(animation.id, animation.imageAssets);
animation.imageAssets.forEach((imageAsset) => {
if (dupeMap.has(imageAsset.id)) {
const count = dupeMap.get(imageAsset.id) ?? 0;

dupeMap.set(imageAsset.id, count + 1);
} else {
dupeMap.set(imageAsset.id, 1);
}
});
});

let size = 0;
return dupeMap;
}

images.forEach((value) => {
size += value.length;
});
/**
* Renames the image assets in all animations to avoid conflicts.
*/
private async _renameImageAssets(): Promise<void> {
const dupeMap = this._generateMapOfOccurencesFromImageIds();

for (let i = this.animations.length - 1; i >= 0; i -= 1) {
const animation = this.animations.at(i);
Expand All @@ -266,8 +282,18 @@ export class DotLottieCommon {
const image = animation.imageAssets.at(j);

if (image) {
this._renameImage(animation, `image_${size}`, image.id);
size -= 1;
let count = dupeMap.get(image.id) ?? 0;

if (count > 0) {
count -= 1;
}

dupeMap.set(image.id, count);

if (count > 0) {
// Rename the image
await this._renameImage(animation, `${image.id}_${count}`, image.id);
}
}
}
}
Expand Down Expand Up @@ -509,7 +535,7 @@ export class DotLottieCommon {

if (this.animations.length > 1) {
// Rename assets incrementally if there are multiple animations
this._renameImageAssets();
await this._renameImageAssets();
this._renameAudioAssets();
}

Expand Down
16 changes: 6 additions & 10 deletions packages/dotlottie-js/src/common/lottie-image-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { ZipOptions } from 'fflate';

import type { LottieAnimationCommon } from './lottie-animation-common';
import { dataUrlFromU8, DotLottieError } from './utils';
import { dataUrlFromU8, DotLottieError, getMimeTypeFromBase64 } from './utils';

export type ImageData = string | ArrayBuffer | Blob;

Expand Down Expand Up @@ -129,18 +129,14 @@ export class LottieImageCommon {
* Renames the id and fileName to newName.
* @param newName - A new id and filename for the image.
*/
public renameImage(newName: string): void {
public async renameImage(newName: string): Promise<void> {
this.id = newName;

if (this.fileName) {
let fileExt = this.fileName.split('.').pop();
const data = await this.toDataURL();

if (!fileExt) {
fileExt = '.png';
}
// Default to png if the file extension isn't available
this.fileName = `${newName}.${fileExt}`;
}
const mimeType = await getMimeTypeFromBase64(data);

this.fileName = `${newName}.${mimeType ? mimeType.split('/')[1] : 'png'}`;
}

public async toArrayBuffer(): Promise<ArrayBuffer> {
Expand Down
Loading

0 comments on commit 2566abe

Please sign in to comment.