@zzalai/leafer-point-annotation 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/App.vue CHANGED
@@ -2,7 +2,7 @@
2
2
  <div class="app">
3
3
  <h1>LeaferJS Point Annotation Test</h1>
4
4
 
5
- <div class="editor-container">
5
+ <div class="editor-container" :class="{ 'multi-instance': enableMultiInstance }">
6
6
  <PointAnnotation
7
7
  ref="pointAnnotation"
8
8
  :imageSource="imageSource"
@@ -14,6 +14,13 @@
14
14
  @loadError="handleLoadError"
15
15
  @update:currentLayer="handleLayerChange"
16
16
  />
17
+ <PointAnnotation
18
+ v-if="enableMultiInstance"
19
+ ref="pointAnnotation2"
20
+ :imageSource="imageSource2"
21
+ :options="editorOptions2"
22
+ @pointChange="handlePointChange2"
23
+ />
17
24
  </div>
18
25
 
19
26
  <div class="controls">
@@ -73,6 +80,7 @@
73
80
  <label><input type="checkbox" v-model="showToolbar" /> 显示组件工具栏</label>
74
81
  <label><input type="checkbox" v-model="showZoomController" /> 显示缩放控制器</label>
75
82
  <label><input type="checkbox" v-model="enableBrush" /> 启用笔刷功能</label>
83
+ <label><input type="checkbox" v-model="enableMultiInstance" /> 双实例测试(验证多实例快捷键不冲突)</label>
76
84
  </div>
77
85
  <div class="multi-layer-row">
78
86
  <label>背景色: <input type="color" v-model="canvasBackground" style="width: 40px; height: 28px; vertical-align: middle; margin-left: 6px;" /></label>
@@ -315,6 +323,36 @@ const canvasBackground = ref('#f6f6f6')
315
323
  const zoomMin = ref(0.2)
316
324
  const zoomMax = ref(4)
317
325
  const enableBrush = ref(true)
326
+ const enableMultiInstance = ref(false)
327
+
328
+ const imageSource2 = computed(() => {
329
+ if (!imageUrl.value) return undefined
330
+ return { id: 'test-image-2', url: imageUrl.value }
331
+ })
332
+
333
+ const editorOptions2 = computed<OptionsSource>(() => {
334
+ return {
335
+ pointStyle: {
336
+ circleFill: '#00ff00',
337
+ circleStroke: '#fff',
338
+ selectedCircleFill: '#ff0',
339
+ selectedCircleStroke: '#000'
340
+ },
341
+ brushStyle: {
342
+ color: '#00ff00',
343
+ opacity: 0.55,
344
+ size: 100
345
+ },
346
+ showToolbar: true,
347
+ showZoomController: true,
348
+ enableBrush: enableBrush.value,
349
+ enableHotkeys: false
350
+ }
351
+ })
352
+
353
+ const handlePointChange2 = (points: any[]) => {
354
+ console.log('Editor 2 - Points changed:', points)
355
+ }
318
356
 
319
357
  const editorOptions = computed<OptionsSource>(() => {
320
358
  if (useMultiLayer.value) {
@@ -327,7 +365,8 @@ const editorOptions = computed<OptionsSource>(() => {
327
365
  canvasBackground: canvasBackground.value,
328
366
  zoomMin: zoomMin.value,
329
367
  zoomMax: zoomMax.value,
330
- enableBrush: enableBrush.value
368
+ enableBrush: enableBrush.value,
369
+ enableHotkeys: false
331
370
  }
332
371
  }
333
372
  return {
@@ -337,7 +376,8 @@ const editorOptions = computed<OptionsSource>(() => {
337
376
  canvasBackground: canvasBackground.value,
338
377
  zoomMin: zoomMin.value,
339
378
  zoomMax: zoomMax.value,
340
- enableBrush: enableBrush.value
379
+ enableBrush: enableBrush.value,
380
+ enableHotkeys: false
341
381
  }
342
382
  })
343
383
 
@@ -757,6 +797,22 @@ h1 {
757
797
  margin-bottom: 30px;
758
798
  }
759
799
 
800
+ .editor-container.multi-instance {
801
+ display: flex;
802
+ gap: 12px;
803
+ height: auto;
804
+ }
805
+
806
+ .editor-container.multi-instance > :deep(.point-annotation),
807
+ .editor-container.multi-instance > .point-annotation {
808
+ flex: 1;
809
+ min-width: 0;
810
+ height: 600px;
811
+ border: 1px solid #ddd;
812
+ border-radius: 8px;
813
+ overflow: hidden;
814
+ }
815
+
760
816
  .controls {
761
817
  margin-bottom: 30px;
762
818
  padding: 20px;
@@ -319,6 +319,7 @@ export interface OptionsSource {
319
319
  zoomMin?: number;
320
320
  zoomMax?: number;
321
321
  enableBrush?: boolean;
322
+ enableHotkeys?: boolean;
322
323
  }
323
324
 
324
325
  const props = defineProps({
@@ -524,6 +525,9 @@ const isDrawing = ref(false);
524
525
  // 撤销/重做管理器
525
526
  let commandManager: CommandManager | null = null;
526
527
 
528
+ // 🔧 多实例支持:tinykeys 解绑函数(保存在组件作用域,避免多个实例互相覆盖 window.__pointAnnotationHotkeysUnsubscribe)
529
+ let hotkeysUnsubscribe: (() => void) | null = null;
530
+
527
531
  // 根据配置强制【标注点】不跟随画布Scale变化
528
532
  const changePointScaleRelativeCanvas = (pointAnnotationLayer: Group | null) => {
529
533
  // 检查是否开启固定大小功能
@@ -1025,73 +1029,77 @@ onMounted(() => {
1025
1029
  window.addEventListener("mousemove", handleMouseMove);
1026
1030
  window.addEventListener("focusout", handleFocusOut);
1027
1031
 
1028
- const unsubscribe = tinykeys(window, {
1029
- v: (event: KeyboardEvent) => {
1030
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1031
- event.preventDefault();
1032
- selectTool();
1033
- },
1034
- p: (event: KeyboardEvent) => {
1035
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1036
- event.preventDefault();
1037
- pointTool();
1038
- },
1039
- b: (event: KeyboardEvent) => {
1040
- if (!effectiveEnableBrush.value) return;
1041
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1042
- event.preventDefault();
1043
- brushTool();
1044
- },
1045
- e: (event: KeyboardEvent) => {
1046
- if (!effectiveEnableBrush.value) return;
1047
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1048
- event.preventDefault();
1049
- eraserTool();
1050
- },
1051
- "$mod+KeyZ": (event: KeyboardEvent) => {
1052
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1053
- event.preventDefault();
1054
- event.stopPropagation();
1055
- undo();
1056
- },
1057
- "$mod+KeyY": (event: KeyboardEvent) => {
1058
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1059
- event.preventDefault();
1060
- event.stopPropagation();
1061
- redo();
1062
- },
1063
- Delete: (event: KeyboardEvent) => {
1064
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1065
- event.preventDefault();
1066
- event.stopPropagation();
1067
- deleteSelected();
1068
- },
1069
- "$mod+Equal": (event: KeyboardEvent) => {
1070
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1071
- event.preventDefault();
1072
- event.stopPropagation();
1073
- zoomIn();
1074
- },
1075
- "$mod+Minus": (event: KeyboardEvent) => {
1076
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1077
- event.preventDefault();
1078
- event.stopPropagation();
1079
- zoomOut();
1080
- },
1081
- "$mod+0": (event: KeyboardEvent) => {
1082
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1083
- event.preventDefault();
1084
- event.stopPropagation();
1085
- resetZoom();
1086
- },
1087
- Alt: (event: KeyboardEvent) => {
1088
- if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1089
- event.preventDefault();
1090
- showHotkeys.value = !showHotkeys.value;
1091
- },
1092
- });
1032
+ // 根据 enableHotkeys 配置决定是否注册热键(默认不启用,显式传 true 才启用)
1033
+ if (props.options.enableHotkeys) {
1034
+ const unsubscribe = tinykeys(window, {
1035
+ v: (event: KeyboardEvent) => {
1036
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1037
+ event.preventDefault();
1038
+ selectTool();
1039
+ },
1040
+ p: (event: KeyboardEvent) => {
1041
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1042
+ event.preventDefault();
1043
+ pointTool();
1044
+ },
1045
+ b: (event: KeyboardEvent) => {
1046
+ if (!effectiveEnableBrush.value) return;
1047
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1048
+ event.preventDefault();
1049
+ brushTool();
1050
+ },
1051
+ e: (event: KeyboardEvent) => {
1052
+ if (!effectiveEnableBrush.value) return;
1053
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1054
+ event.preventDefault();
1055
+ eraserTool();
1056
+ },
1057
+ "$mod+KeyZ": (event: KeyboardEvent) => {
1058
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1059
+ event.preventDefault();
1060
+ event.stopPropagation();
1061
+ undo();
1062
+ },
1063
+ "$mod+KeyY": (event: KeyboardEvent) => {
1064
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1065
+ event.preventDefault();
1066
+ event.stopPropagation();
1067
+ redo();
1068
+ },
1069
+ Delete: (event: KeyboardEvent) => {
1070
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1071
+ event.preventDefault();
1072
+ event.stopPropagation();
1073
+ deleteSelected();
1074
+ },
1075
+ "$mod+Equal": (event: KeyboardEvent) => {
1076
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1077
+ event.preventDefault();
1078
+ event.stopPropagation();
1079
+ zoomIn();
1080
+ },
1081
+ "$mod+Minus": (event: KeyboardEvent) => {
1082
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1083
+ event.preventDefault();
1084
+ event.stopPropagation();
1085
+ zoomOut();
1086
+ },
1087
+ "$mod+0": (event: KeyboardEvent) => {
1088
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1089
+ event.preventDefault();
1090
+ event.stopPropagation();
1091
+ resetZoom();
1092
+ },
1093
+ Alt: (event: KeyboardEvent) => {
1094
+ if (!isCanvasFocused.value && !isMouseOverCanvas.value) return;
1095
+ event.preventDefault();
1096
+ showHotkeys.value = !showHotkeys.value;
1097
+ },
1098
+ });
1093
1099
 
1094
- window.__pointAnnotationHotkeysUnsubscribe = unsubscribe;
1100
+ // 🔧 多实例支持:unsubscribe 保存在当前组件作用域,不再使用全局 window 存储
1101
+ hotkeysUnsubscribe = unsubscribe;
1102
+ }
1095
1103
  });
1096
1104
  });
1097
1105
 
@@ -1179,9 +1187,10 @@ onUnmounted(() => {
1179
1187
  window.removeEventListener("mousemove", handleMouseMove);
1180
1188
  window.removeEventListener('focusout', handleFocusOut);
1181
1189
 
1182
- if (window.__pointAnnotationHotkeysUnsubscribe) {
1183
- window.__pointAnnotationHotkeysUnsubscribe();
1184
- delete window.__pointAnnotationHotkeysUnsubscribe;
1190
+ // 🔧 多实例支持:调用当前实例自己的 unsubscribe,不再使用全局 window
1191
+ if (hotkeysUnsubscribe) {
1192
+ hotkeysUnsubscribe();
1193
+ hotkeysUnsubscribe = null;
1185
1194
  }
1186
1195
  });
1187
1196