@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/README.md +17 -13
- package/README_EN.md +17 -13
- package/docs/assets/{index-BcqmlFff.js → index-B2EnBW5l.js} +1 -1
- package/docs/assets/{index-dq8tjOSG.css → index-BtyValpk.css} +1 -1
- package/docs/index.html +2 -2
- package/package.json +1 -1
- package/project-docs/ARCHITECTURE.md +2 -0
- package/project-docs/SHARED_UNDO_REDO_STACK.md +503 -0
- package/src/App.vue +59 -3
- package/src/components/PointAnnotation.vue +78 -69
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
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
event
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
event
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
event
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
event
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
event
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
event
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
event
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
event
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
event
|
|
1090
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1190
|
+
// 🔧 多实例支持:调用当前实例自己的 unsubscribe,不再使用全局 window
|
|
1191
|
+
if (hotkeysUnsubscribe) {
|
|
1192
|
+
hotkeysUnsubscribe();
|
|
1193
|
+
hotkeysUnsubscribe = null;
|
|
1185
1194
|
}
|
|
1186
1195
|
});
|
|
1187
1196
|
|