Skip to content

Commit bb746b5

Browse files
authored
Merge pull request #429 from embedpdf/fix/group-selection-annotation-permissions
Disable group drag/resize without permission
2 parents 6357faa + bb86490 commit bb746b5

File tree

6 files changed

+62
-17
lines changed

6 files changed

+62
-17
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@embedpdf/plugin-annotation': patch
3+
---
4+
5+
Fixed group selection box ignoring document permissions:
6+
7+
- Added `canModifyAnnotations` permission check to `GroupSelectionBox` component across React, Vue, and Svelte
8+
- Group drag and resize operations are now properly disabled when the user lacks annotation modification permissions
9+
- This aligns group selection behavior with individual annotation container permission checks

examples/svelte-tailwind/src/lib/config/commands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,9 @@ export const commands: Record<string, Command<State>> = {
10351035
selectedAnnotation.object.id,
10361036
);
10371037
},
1038+
disabled: ({ state, documentId }) => {
1039+
return lacksPermission(state, documentId, PdfPermissionFlag.ModifyAnnotations);
1040+
},
10381041
},
10391042

10401043
'annotation:apply-redaction': {

examples/vue-tailwind/src/config/commands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,9 @@ export const commands: Record<string, Command<State>> = {
10351035
selectedAnnotation.object.id,
10361036
);
10371037
},
1038+
disabled: ({ state, documentId }) => {
1039+
return lacksPermission(state, documentId, PdfPermissionFlag.ModifyAnnotations);
1040+
},
10381041
},
10391042

10401043
// ─────────────────────────────────────────────────────────

packages/plugin-annotation/src/shared/components/group-selection-box.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Rect, boundingRectOrEmpty } from '@embedpdf/models';
22
import { useInteractionHandles, CounterRotate } from '@embedpdf/utils/@framework';
33
import { TrackedAnnotation } from '@embedpdf/plugin-annotation';
44
import { useState, useMemo, useCallback, useRef, useEffect } from '@framework';
5+
import { useDocumentPermissions } from '@embedpdf/core/@framework';
56

67
import { useAnnotationPlugin } from '../hooks';
78
import { ResizeHandleUI, GroupSelectionMenuRenderFn } from './types';
@@ -52,10 +53,15 @@ export function GroupSelectionBox({
5253
groupSelectionMenu,
5354
}: GroupSelectionBoxProps): JSX.Element | null {
5455
const { plugin } = useAnnotationPlugin();
56+
const { canModifyAnnotations } = useDocumentPermissions(documentId);
5557
const gestureBaseRef = useRef<Rect | null>(null);
5658
const isDraggingRef = useRef(false);
5759
const isResizingRef = useRef(false);
5860

61+
// Check permissions before allowing drag/resize
62+
const effectiveIsDraggable = canModifyAnnotations && isDraggable;
63+
const effectiveIsResizable = canModifyAnnotations && isResizable;
64+
5965
// Compute the group bounding box from all selected annotations
6066
const groupBox = useMemo(() => {
6167
const rects = selectedAnnotations.map((ta) => ta.object.rect);
@@ -88,7 +94,7 @@ export function GroupSelectionBox({
8894
const isResize = transformType === 'resize';
8995

9096
// Skip drag operations if group is not draggable
91-
if (isMove && !isDraggable) return;
97+
if (isMove && !effectiveIsDraggable) return;
9298

9399
if (event.state === 'start') {
94100
gestureBaseRef.current = groupBox;
@@ -156,7 +162,15 @@ export function GroupSelectionBox({
156162
}
157163
}
158164
},
159-
[plugin, documentId, pageWidth, pageHeight, groupBox, isDraggable, selectedAnnotations],
165+
[
166+
plugin,
167+
documentId,
168+
pageWidth,
169+
pageHeight,
170+
groupBox,
171+
effectiveIsDraggable,
172+
selectedAnnotations,
173+
],
160174
);
161175

162176
// UI constants
@@ -199,9 +213,9 @@ export function GroupSelectionBox({
199213

200214
return (
201215
<div data-group-selection-box data-no-interaction>
202-
{/* Group box - draggable only if isDraggable is true */}
216+
{/* Group box - draggable only if effectiveIsDraggable is true */}
203217
<div
204-
{...(isDraggable
218+
{...(effectiveIsDraggable
205219
? dragProps
206220
: {
207221
onPointerDown: (e) => e.stopPropagation(),
@@ -214,13 +228,13 @@ export function GroupSelectionBox({
214228
height: previewGroupBox.size.height * scale,
215229
outline: `2px dashed ${selectionOutlineColor}`,
216230
outlineOffset: outlineOffset - 1,
217-
cursor: isDraggable ? 'move' : 'default',
231+
cursor: effectiveIsDraggable ? 'move' : 'default',
218232
touchAction: 'none',
219233
zIndex,
220234
}}
221235
>
222236
{/* Resize handles */}
223-
{isResizable &&
237+
{effectiveIsResizable &&
224238
resize.map(({ key, ...hProps }) =>
225239
resizeUI?.component ? (
226240
resizeUI.component({

packages/plugin-annotation/src/svelte/components/GroupSelectionBox.svelte

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
CounterRotate,
88
type MenuWrapperProps,
99
} from '@embedpdf/utils/svelte';
10+
import { useDocumentPermissions } from '@embedpdf/core/svelte';
1011
import { useAnnotationPlugin } from '../hooks';
1112
import type {
1213
GroupSelectionContext,
@@ -62,10 +63,15 @@
6263
}: GroupSelectionBoxProps = $props();
6364
6465
const annotationPlugin = useAnnotationPlugin();
66+
const permissions = useDocumentPermissions(() => documentId);
6567
let gestureBase = $state<Rect | null>(null);
6668
let isDraggingRef = $state(false);
6769
let isResizingRef = $state(false);
6870
71+
// Check permissions before allowing drag/resize
72+
const effectiveIsDraggable = $derived(permissions.canModifyAnnotations && isDraggable);
73+
const effectiveIsResizable = $derived(permissions.canModifyAnnotations && isResizable);
74+
6975
// Helper to compute group box on demand
7076
function getGroupBox(): Rect {
7177
const rects = selectedAnnotations.map((ta) => ta.object.rect);
@@ -121,7 +127,7 @@
121127
const isResize = transformType === 'resize';
122128
123129
// Skip drag operations if group is not draggable
124-
if (isMove && !isDraggable) return;
130+
if (isMove && !effectiveIsDraggable) return;
125131
126132
if (event.state === 'start') {
127133
gestureBase = getGroupBox();
@@ -235,9 +241,9 @@
235241

236242
{#if selectedAnnotations.length >= 2}
237243
<div data-group-selection-box data-no-interaction>
238-
<!-- Group box - draggable only if isDraggable is true -->
244+
<!-- Group box - draggable only if effectiveIsDraggable is true -->
239245
<div
240-
{...isDraggable
246+
{...effectiveIsDraggable
241247
? interactionHandles.dragProps
242248
: { onpointerdown: (e: PointerEvent) => e.stopPropagation() }}
243249
style:position="absolute"
@@ -247,12 +253,12 @@
247253
style:height="{previewGroupBox.size.height * scale}px"
248254
style:outline="2px dashed {selectionOutlineColor}"
249255
style:outline-offset="{outlineOffset - 1}px"
250-
style:cursor={isDraggable ? 'move' : 'default'}
256+
style:cursor={effectiveIsDraggable ? 'move' : 'default'}
251257
style:touch-action="none"
252258
style:z-index={zIndex}
253259
>
254260
<!-- Resize handles -->
255-
{#if isResizable}
261+
{#if effectiveIsResizable}
256262
{#each resizeHandles as { key, style: handleStyle, ...hProps } (key)}
257263
{#if resizeUI?.component}
258264
{@render resizeUI.component({ ...hProps, backgroundColor: HANDLE_COLOR })}

packages/plugin-annotation/src/vue/components/group-selection-box.vue

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<template>
22
<div v-if="selectedAnnotations.length >= 2" data-group-selection-box data-no-interaction>
3-
<!-- Group box - draggable only if isDraggable is true -->
3+
<!-- Group box - draggable only if effectiveIsDraggable is true -->
44
<div
5-
v-bind="isDraggable ? dragProps : {}"
5+
v-bind="effectiveIsDraggable ? dragProps : {}"
66
:style="boxStyle"
7-
@pointerdown="!isDraggable ? $event.stopPropagation() : undefined"
7+
@pointerdown="!effectiveIsDraggable ? $event.stopPropagation() : undefined"
88
>
99
<!-- Resize handles -->
10-
<template v-if="isResizable">
10+
<template v-if="effectiveIsResizable">
1111
<template v-for="{ key, style, ...handle } in resize" :key="key">
1212
<slot
1313
v-if="slots['resize-handle']"
@@ -52,6 +52,7 @@ import {
5252
useInteractionHandles,
5353
} from '@embedpdf/utils/vue';
5454
import { TrackedAnnotation } from '@embedpdf/plugin-annotation';
55+
import { useDocumentPermissions } from '@embedpdf/core/vue';
5556
import { useAnnotationPlugin } from '../hooks';
5657
import {
5758
GroupSelectionContext,
@@ -94,10 +95,19 @@ const props = withDefaults(
9495
9596
const slots = useSlots();
9697
const { plugin: annotationPlugin } = useAnnotationPlugin();
98+
const permissions = useDocumentPermissions(() => props.documentId);
9799
const gestureBase = shallowRef<Rect | null>(null);
98100
const isDraggingRef = ref(false);
99101
const isResizingRef = ref(false);
100102
103+
// Check permissions before allowing drag/resize
104+
const effectiveIsDraggable = computed(
105+
() => permissions.value.canModifyAnnotations && props.isDraggable,
106+
);
107+
const effectiveIsResizable = computed(
108+
() => permissions.value.canModifyAnnotations && props.isResizable,
109+
);
110+
101111
const HANDLE_COLOR = computed(() => props.resizeUI?.color ?? '#007ACC');
102112
const HANDLE_SIZE = computed(() => props.resizeUI?.size ?? 12);
103113
@@ -131,7 +141,7 @@ const boxStyle = computed(() => ({
131141
height: `${previewGroupBox.value.size.height * props.scale}px`,
132142
outline: `2px dashed ${props.selectionOutlineColor}`,
133143
outlineOffset: `${props.outlineOffset - 1}px`,
134-
cursor: props.isDraggable ? 'move' : 'default',
144+
cursor: effectiveIsDraggable.value ? 'move' : 'default',
135145
touchAction: 'none',
136146
zIndex: props.zIndex,
137147
}));
@@ -211,7 +221,7 @@ const { dragProps, resize } = useInteractionHandles({
211221
const isResize = transformType === 'resize';
212222
213223
// Skip drag operations if group is not draggable
214-
if (isMove && !props.isDraggable) return;
224+
if (isMove && !effectiveIsDraggable.value) return;
215225
216226
if (event.state === 'start') {
217227
gestureBase.value = groupBox.value;

0 commit comments

Comments
 (0)