Skip to content

Commit 502ba85

Browse files
committed
Fix some rendering issues with Vue component
1 parent 0db3b0b commit 502ba85

1 file changed

Lines changed: 151 additions & 120 deletions

File tree

packages/plugin-annotation/src/vue/components/annotations.vue

Lines changed: 151 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
<template>
2-
<template v-for="annotation in annotations" :key="annotation.object.id">
3-
<template v-if="resolveRenderer(annotation)">
2+
<template v-for="{ annotation, renderer } in resolvedAnnotations" :key="annotation.object.id">
3+
<template v-if="renderer">
44
<AnnotationContainer
55
:trackedAnnotation="annotation"
66
:isSelected="allSelectedIds.includes(annotation.object.id)"
77
:isEditing="editingId === annotation.object.id"
88
:isMultiSelected="isMultiSelected"
9-
:isDraggable="getFinalDraggable(annotation)"
10-
:isResizable="getResolvedResizable(annotation)"
11-
:lockAspectRatio="getResolvedLockAspectRatio(annotation)"
12-
:isRotatable="getResolvedRotatable(annotation)"
13-
:vertexConfig="resolveRenderer(annotation)!.vertexConfig"
14-
:selectionMenu="getSelectionMenu(annotation, resolveRenderer(annotation)!)"
15-
:onSelect="getOnSelect(annotation, resolveRenderer(annotation)!)"
16-
:onDoubleClick="getOnDoubleClick(resolveRenderer(annotation)!, annotation)"
17-
:zIndex="resolveRenderer(annotation)!.zIndex"
18-
:style="getContainerStyle(annotation, resolveRenderer(annotation)!)"
19-
:appearance="getAppearance(annotation, resolveRenderer(annotation)!)"
9+
:isDraggable="getFinalDraggable(annotation, renderer)"
10+
:isResizable="getResolvedResizable(annotation, renderer)"
11+
:lockAspectRatio="getResolvedLockAspectRatio(annotation, renderer)"
12+
:isRotatable="getResolvedRotatable(annotation, renderer)"
13+
:vertexConfig="renderer.vertexConfig"
14+
:selectionMenu="getSelectionMenu(annotation, renderer)"
15+
:onSelect="getOnSelect(annotation, renderer)"
16+
:onDoubleClick="getOnDoubleClick(renderer, annotation)"
17+
:zIndex="renderer.zIndex"
18+
:style="getContainerStyle(annotation, renderer)"
19+
:appearance="getAppearance(annotation, renderer)"
2020
v-bind="containerProps"
2121
>
2222
<template #default="{ annotation: currentObject, appearanceActive }">
2323
<component
24-
:is="resolveRenderer(annotation)!.component"
24+
:is="renderer.component"
2525
:annotation="annotation"
2626
:currentObject="currentObject"
2727
:isSelected="allSelectedIds.includes(annotation.object.id)"
2828
:isEditing="editingId === annotation.object.id"
2929
:scale="scale"
3030
:pageIndex="pageIndex"
3131
:documentId="documentId"
32-
:onClick="getOnSelect(annotation, resolveRenderer(annotation)!)"
32+
:onClick="getOnSelect(annotation, renderer)"
3333
:appearanceActive="appearanceActive"
3434
/>
3535
</template>
@@ -82,7 +82,7 @@
8282
</template>
8383

8484
<script setup lang="ts">
85-
import { ref, computed, watchEffect, type CSSProperties } from 'vue';
85+
import { ref, shallowRef, computed, watch, type CSSProperties } from 'vue';
8686
import {
8787
blendModeToCss,
8888
PdfAnnotationObject,
@@ -138,14 +138,14 @@ const props = defineProps<{
138138
139139
const { provides: annotationCapability } = useAnnotationCapability();
140140
const { provides: selectionProvides } = useSelectionCapability();
141-
const annotations = ref<TrackedAnnotation[]>([]);
142-
const allSelectedIds = ref<string[]>([]);
141+
const annotations = shallowRef<TrackedAnnotation[]>([]);
142+
const allSelectedIds = shallowRef<string[]>([]);
143143
const { register } = usePointerHandlers({
144144
documentId: () => props.documentId,
145145
pageIndex: props.pageIndex,
146146
});
147147
const editingId = ref<string | null>(null);
148-
const appearanceMap = ref<AnnotationAppearanceMap>({});
148+
const appearanceMap = shallowRef<AnnotationAppearanceMap>({});
149149
let prevScale = props.scale;
150150
151151
const annotationProvides = computed(() =>
@@ -173,39 +173,136 @@ const getAppearanceForAnnotation = (ta: TrackedAnnotation): AnnotationAppearance
173173
return appearances;
174174
};
175175
176-
// Subscribe to annotation state
177-
watchEffect((onCleanup) => {
178-
if (annotationProvides.value) {
179-
const currentState = annotationProvides.value.getState();
180-
annotations.value = getAnnotationsByPageIndex(currentState, props.pageIndex);
181-
allSelectedIds.value = getSelectedAnnotationIds(currentState);
176+
// Subscribe to annotation state. Explicit watch dependencies avoid accidental
177+
// reactive tracking inside provider methods that can create update loops.
178+
watch(
179+
[annotationProvides, () => props.pageIndex],
180+
([provides, pageIndex], _prev, onCleanup) => {
181+
if (!provides) {
182+
annotations.value = [];
183+
allSelectedIds.value = [];
184+
return;
185+
}
182186
183-
const off = annotationProvides.value.onStateChange((state) => {
184-
annotations.value = getAnnotationsByPageIndex(state, props.pageIndex);
187+
const syncState = (state: ReturnType<typeof provides.getState>) => {
188+
annotations.value = getAnnotationsByPageIndex(state, pageIndex);
185189
allSelectedIds.value = getSelectedAnnotationIds(state);
186-
});
190+
};
191+
192+
syncState(provides.getState());
193+
const off = provides.onStateChange(syncState);
187194
onCleanup(off);
188-
}
189-
});
195+
},
196+
{ immediate: true },
197+
);
190198
191199
// Fetch appearance map, invalidate on scale change
192-
watchEffect(() => {
193-
if (!annotationProvides.value) return;
200+
watch(
201+
[annotationProvides, () => props.pageIndex, () => props.scale],
202+
([provides, pageIndex, scale], _prev, onCleanup) => {
203+
if (!provides) {
204+
appearanceMap.value = {};
205+
return;
206+
}
194207
195-
if (prevScale !== props.scale) {
196-
annotationProvides.value.invalidatePageAppearances(props.pageIndex);
197-
prevScale = props.scale;
198-
}
208+
if (prevScale !== scale) {
209+
provides.invalidatePageAppearances(pageIndex);
210+
prevScale = scale;
211+
}
199212
200-
const task = annotationProvides.value.getPageAppearances(props.pageIndex, {
201-
scaleFactor: props.scale,
202-
dpr: typeof window !== 'undefined' ? window.devicePixelRatio : 1,
203-
});
204-
task.wait(
205-
(map) => (appearanceMap.value = map),
206-
() => (appearanceMap.value = {}),
213+
let cancelled = false;
214+
onCleanup(() => {
215+
cancelled = true;
216+
});
217+
218+
const task = provides.getPageAppearances(pageIndex, {
219+
scaleFactor: scale,
220+
dpr: typeof window !== 'undefined' ? window.devicePixelRatio : 1,
221+
});
222+
task.wait(
223+
(map) => {
224+
if (!cancelled) appearanceMap.value = map;
225+
},
226+
() => {
227+
if (!cancelled) appearanceMap.value = {};
228+
},
229+
);
230+
},
231+
{ immediate: true },
232+
);
233+
234+
const resolvedAnnotations = computed(() =>
235+
annotations.value.map((annotation) => ({
236+
annotation,
237+
renderer: resolveRenderer(annotation),
238+
})),
239+
);
240+
241+
const getFinalDraggable = (annotation: TrackedAnnotation, renderer: BoxedAnnotationRenderer) => {
242+
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
243+
const defaults = renderer.interactionDefaults;
244+
const isEditing = editingId.value === annotation.object.id;
245+
const resolvedDraggable = resolveInteractionProp(
246+
tool?.interaction.isDraggable,
247+
annotation.object,
248+
defaults?.isDraggable ?? true,
207249
);
208-
});
250+
return renderer.isDraggable
251+
? renderer.isDraggable(resolvedDraggable, { isEditing })
252+
: resolvedDraggable;
253+
};
254+
255+
const getResolvedResizable = (annotation: TrackedAnnotation, renderer: BoxedAnnotationRenderer) => {
256+
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
257+
return resolveInteractionProp(
258+
tool?.interaction.isResizable,
259+
annotation.object,
260+
renderer.interactionDefaults?.isResizable ?? false,
261+
);
262+
};
263+
264+
const getResolvedLockAspectRatio = (
265+
annotation: TrackedAnnotation,
266+
renderer: BoxedAnnotationRenderer,
267+
) => {
268+
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
269+
return resolveInteractionProp(
270+
tool?.interaction.lockAspectRatio,
271+
annotation.object,
272+
renderer.interactionDefaults?.lockAspectRatio ?? false,
273+
);
274+
};
275+
276+
const getResolvedRotatable = (annotation: TrackedAnnotation, renderer: BoxedAnnotationRenderer) => {
277+
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
278+
return resolveInteractionProp(
279+
tool?.interaction.isRotatable,
280+
annotation.object,
281+
renderer.interactionDefaults?.isRotatable ?? false,
282+
);
283+
};
284+
285+
const getSelectionMenu = (annotation: TrackedAnnotation, renderer: BoxedAnnotationRenderer) => {
286+
if (renderer.hideSelectionMenu?.(annotation.object)) return undefined;
287+
if (isMultiSelected.value) return undefined;
288+
return props.selectionMenu;
289+
};
290+
291+
const getOnSelect = (annotation: TrackedAnnotation, renderer: BoxedAnnotationRenderer) => {
292+
if (renderer.selectOverride) {
293+
const selectHelpers: SelectOverrideHelpers = {
294+
defaultSelect: handleClick,
295+
selectAnnotation: (pi: number, id: string) =>
296+
annotationProvides.value?.selectAnnotation(pi, id),
297+
clearSelection: () => selectionProvides.value?.clear(),
298+
allAnnotations: annotations.value,
299+
pageIndex: props.pageIndex,
300+
};
301+
return (e: AnnotationInteractionEvent) =>
302+
renderer.selectOverride!(e, annotation, selectHelpers);
303+
}
304+
return (e: AnnotationInteractionEvent) => handleClick(e, annotation);
305+
};
209306
210307
const handlePointerDown = (_pos: Position, pe: EmbedPdfPointerEvent<PointerEvent>) => {
211308
if (pe.target === pe.currentTarget && annotationProvides.value) {
@@ -237,14 +334,19 @@ const setEditingId = (id: string) => {
237334
editingId.value = id;
238335
};
239336
240-
watchEffect((onCleanup) => {
241-
if (annotationProvides.value) {
242-
const unregister = register({ onPointerDown: handlePointerDown });
337+
const pointerHandlers = { onPointerDown: handlePointerDown };
338+
339+
watch(
340+
annotationProvides,
341+
(provides, _prev, onCleanup) => {
342+
if (!provides) return;
343+
const unregister = register(pointerHandlers);
243344
if (unregister) {
244345
onCleanup(unregister);
245346
}
246-
}
247-
});
347+
},
348+
{ immediate: true },
349+
);
248350
249351
const selectedAnnotationsOnPage = computed(() =>
250352
annotations.value.filter((anno) => allSelectedIds.value.includes(anno.object.id)),
@@ -315,77 +417,6 @@ const allSelectedOnSamePage = computed(() => {
315417
316418
// --- Renderer resolution helpers ---
317419
318-
const getFinalDraggable = (annotation: TrackedAnnotation) => {
319-
const renderer = resolveRenderer(annotation);
320-
if (!renderer) return false;
321-
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
322-
const defaults = renderer.interactionDefaults;
323-
const isEditing = editingId.value === annotation.object.id;
324-
const resolvedDraggable = resolveInteractionProp(
325-
tool?.interaction.isDraggable,
326-
annotation.object,
327-
defaults?.isDraggable ?? true,
328-
);
329-
return renderer.isDraggable
330-
? renderer.isDraggable(resolvedDraggable, { isEditing })
331-
: resolvedDraggable;
332-
};
333-
334-
const getResolvedResizable = (annotation: TrackedAnnotation) => {
335-
const renderer = resolveRenderer(annotation);
336-
if (!renderer) return false;
337-
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
338-
return resolveInteractionProp(
339-
tool?.interaction.isResizable,
340-
annotation.object,
341-
renderer.interactionDefaults?.isResizable ?? false,
342-
);
343-
};
344-
345-
const getResolvedLockAspectRatio = (annotation: TrackedAnnotation) => {
346-
const renderer = resolveRenderer(annotation);
347-
if (!renderer) return false;
348-
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
349-
return resolveInteractionProp(
350-
tool?.interaction.lockAspectRatio,
351-
annotation.object,
352-
renderer.interactionDefaults?.lockAspectRatio ?? false,
353-
);
354-
};
355-
356-
const getResolvedRotatable = (annotation: TrackedAnnotation) => {
357-
const renderer = resolveRenderer(annotation);
358-
if (!renderer) return false;
359-
const tool = annotationProvides.value?.findToolForAnnotation(annotation.object);
360-
return resolveInteractionProp(
361-
tool?.interaction.isRotatable,
362-
annotation.object,
363-
renderer.interactionDefaults?.isRotatable ?? false,
364-
);
365-
};
366-
367-
const getSelectionMenu = (annotation: TrackedAnnotation, renderer: BoxedAnnotationRenderer) => {
368-
if (renderer.hideSelectionMenu?.(annotation.object)) return undefined;
369-
if (isMultiSelected.value) return undefined;
370-
return props.selectionMenu;
371-
};
372-
373-
const getOnSelect = (annotation: TrackedAnnotation, renderer: BoxedAnnotationRenderer) => {
374-
if (renderer.selectOverride) {
375-
const selectHelpers: SelectOverrideHelpers = {
376-
defaultSelect: handleClick,
377-
selectAnnotation: (pi: number, id: string) =>
378-
annotationProvides.value?.selectAnnotation(pi, id),
379-
clearSelection: () => selectionProvides.value?.clear(),
380-
allAnnotations: annotations.value,
381-
pageIndex: props.pageIndex,
382-
};
383-
return (e: AnnotationInteractionEvent) =>
384-
renderer.selectOverride!(e, annotation, selectHelpers);
385-
}
386-
return (e: AnnotationInteractionEvent) => handleClick(e, annotation);
387-
};
388-
389420
const getOnDoubleClick = (renderer: BoxedAnnotationRenderer, annotation: TrackedAnnotation) => {
390421
if (!renderer.onDoubleClick) return undefined;
391422
return (e: AnnotationInteractionEvent) => {

0 commit comments

Comments
 (0)