Skip to content

Commit 9878719

Browse files
author
danhnguyen
committed
♻️ refactor(Home.vue, useCreateImage.ts, Image.vue): update style URL and enhance image handling with force recreation option
1 parent 4df717c commit 9878719

File tree

3 files changed

+108
-3
lines changed

3 files changed

+108
-3
lines changed

examples/src/views/Home.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import 'vue3-maplibre-gl/dist/style.css';
1010
1111
const options = computed<MapOptions>(() => ({
1212
container: 'map',
13-
style: 'https://worldwidemaps.sqkii.com/api/maps/purple/style.json',
13+
style: 'https://worldwidemaps.sqkii.com/api/maps/test/style.json',
1414
center: [103.8198, 1.3521],
1515
zoom: 12,
1616
minZoom: 9,

libs/components/Image.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,19 @@ interface ImageProps {
3030
showLoading?: boolean;
3131
/** Whether to enable debug logging */
3232
debug?: boolean;
33+
/**
34+
* Force recreation when image dimensions change instead of throwing error.
35+
* When true (default), images are always removed and re-added to prevent dimension mismatch errors.
36+
* This solves the common "width and height must be the same as the previous version" error.
37+
*/
38+
forceRecreateOnDimensionChange?: boolean;
3339
}
3440
3541
// Component props with sensible defaults
3642
const props = withDefaults(defineProps<ImageProps>(), {
3743
images: () => [],
3844
showLoading: true,
45+
forceRecreateOnDimensionChange: true,
3946
});
4047
4148
const { logError } = useLogger(props.debug);
@@ -69,6 +76,8 @@ async function loadImages(images: ImageItem[]): Promise<void> {
6976
id: image.id,
7077
image: image.image,
7178
options: image.options || props.options,
79+
debug: props.debug,
80+
forceRecreateOnDimensionChange: props.forceRecreateOnDimensionChange,
7281
});
7382
await actions.loadPromise;
7483
} catch (error) {

libs/composables/map/useCreateImage.ts

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ interface CreateImageProps {
2121
image: ImageDatas | string;
2222
options?: Partial<StyleImageMetadata>;
2323
debug?: boolean;
24+
/**
25+
* Force recreation when image dimensions change instead of throwing error.
26+
* When true (default), images are always removed and re-added to prevent dimension mismatch errors.
27+
* When false, uses the legacy behavior of trying updateImage first and falling back to recreation on error.
28+
*/
29+
forceRecreateOnDimensionChange?: boolean;
2430
}
2531

2632
interface CreateImageActions {
@@ -38,6 +44,9 @@ interface CreateImageActions {
3844
* Composable for creating and managing MapLibre GL Images
3945
* Provides reactive image management with error handling, performance optimizations, and enhanced API
4046
*
47+
* **Important:** MapLibre GL requires that updated images have the same dimensions as the previous version.
48+
* This composable automatically handles dimension changes by removing and re-adding images when necessary.
49+
*
4150
* @param props - Configuration options for the image
4251
* @returns Enhanced actions and state for the image
4352
*/
@@ -71,6 +80,41 @@ export function useCreateImage(props: CreateImageProps): CreateImageActions {
7180
return true;
7281
}
7382

83+
/**
84+
* Gets the dimensions of an image data object
85+
* @param imageData - Image data to get dimensions from
86+
* @returns Object with width and height properties, or null if not determinable
87+
*/
88+
function getImageDimensions(
89+
imageData: ImageDatas,
90+
): { width: number; height: number } | null {
91+
try {
92+
if (imageData instanceof HTMLImageElement) {
93+
return {
94+
width: imageData.naturalWidth || imageData.width,
95+
height: imageData.naturalHeight || imageData.height,
96+
};
97+
}
98+
if (imageData instanceof ImageBitmap) {
99+
return { width: imageData.width, height: imageData.height };
100+
}
101+
if (imageData instanceof ImageData) {
102+
return { width: imageData.width, height: imageData.height };
103+
}
104+
if (
105+
typeof imageData === 'object' &&
106+
'width' in imageData &&
107+
'height' in imageData
108+
) {
109+
return { width: imageData.width, height: imageData.height };
110+
}
111+
return null;
112+
} catch (error) {
113+
logError('Error getting image dimensions:', error);
114+
return null;
115+
}
116+
}
117+
74118
/**
75119
* Checks if the image exists on the map
76120
* @returns boolean indicating if image exists
@@ -138,8 +182,60 @@ export function useCreateImage(props: CreateImageProps): CreateImageActions {
138182

139183
// Update or add the image
140184
if (hasImage()) {
141-
map.updateImage(props.id, imageData);
142-
imageStatus.value = ImageStatus.Updated;
185+
// Check if we should force recreation when dimensions change
186+
const forceRecreate = props.forceRecreateOnDimensionChange ?? true; // Default to true for better UX
187+
188+
if (forceRecreate) {
189+
// Safe approach: always remove and re-add to avoid dimension mismatch errors
190+
const newDimensions = getImageDimensions(imageData);
191+
192+
try {
193+
// Remove the existing image
194+
map.removeImage(props.id);
195+
196+
// Add the new image
197+
map.addImage(props.id, imageData, props.options);
198+
imageStatus.value = ImageStatus.Updated;
199+
} catch (recreateError) {
200+
logError('Error recreating image:', recreateError, {
201+
imageId: props.id,
202+
newDimensions,
203+
});
204+
throw recreateError;
205+
}
206+
} else {
207+
// Legacy approach: try update first, fallback to recreate on dimension errors
208+
try {
209+
map.updateImage(props.id, imageData);
210+
imageStatus.value = ImageStatus.Updated;
211+
} catch (updateError: any) {
212+
// If update fails due to dimension mismatch, remove and re-add the image
213+
if (
214+
updateError?.message?.includes('width and height') ||
215+
updateError?.message?.includes('same as the previous version')
216+
) {
217+
const newDimensions = getImageDimensions(imageData);
218+
logError(
219+
'Image dimensions changed, removing and re-adding image:',
220+
updateError,
221+
{
222+
imageId: props.id,
223+
newDimensions,
224+
},
225+
);
226+
227+
// Remove the existing image
228+
map.removeImage(props.id);
229+
230+
// Add the new image with updated dimensions
231+
map.addImage(props.id, imageData, props.options);
232+
imageStatus.value = ImageStatus.Created;
233+
} else {
234+
// Re-throw if it's a different error
235+
throw updateError;
236+
}
237+
}
238+
}
143239
} else {
144240
map.addImage(props.id, imageData, props.options);
145241
imageStatus.value = ImageStatus.Created;

0 commit comments

Comments
 (0)