@zzalai/leafer-point-annotation 1.0.0
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/LICENSE +21 -0
- package/README.md +308 -0
- package/README_EN.md +308 -0
- package/docs/assets/index-DGiYiG5f.css +1 -0
- package/docs/assets/index-L8gL3x2V.js +1 -0
- package/docs/index.html +14 -0
- package/index.html +13 -0
- package/package.json +64 -0
- package/project-docs/ARCHITECTURE.md +401 -0
- package/project-docs/IMPLEMENTATION_PLAN.md +196 -0
- package/project-docs/REQUIREMENTS.md +517 -0
- package/project-docs/TODO.md +167 -0
- package/project-docs/leafer-development-guide/LEAFER_DEVELOPMENT_GUIDE.md +835 -0
- package/project-docs/leafer-development-guide/LEAFER_UNDO_REDO_GUIDE.md +329 -0
- package/project-docs/leafer-development-guide/TINYKEYS_GUIDE.md +407 -0
- package/src/App.vue +464 -0
- package/src/components/BrushSizeSlider.vue +190 -0
- package/src/components/BrushStylePanel.vue +295 -0
- package/src/components/PointAnnotation.vue +1663 -0
- package/src/elements/PointAnnotationElement.ts +155 -0
- package/src/index.ts +4 -0
- package/src/main.ts +4 -0
- package/src/types/index.ts +122 -0
- package/src/utils/BrushCommands.ts +47 -0
- package/src/utils/BrushStroke.ts +96 -0
- package/src/utils/COCOExporter.ts +90 -0
- package/src/utils/CanvasBrush.ts +179 -0
- package/src/utils/PointCommands.ts +74 -0
- package/src/utils/YOLOExporter.ts +39 -0
- package/src/vite-env.d.ts +7 -0
- package/tsconfig.json +24 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +42 -0
- package/vite.docs.config.ts +28 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# tinykeys 热键系统使用指南
|
|
2
|
+
|
|
3
|
+
## 1. 库的概述
|
|
4
|
+
|
|
5
|
+
**tinykeys** 是一个轻量级的 JavaScript 热键系统库,它提供了简洁而强大的 API 来处理键盘快捷键。它的设计理念是简单易用,同时支持复杂的键盘组合和事件处理。
|
|
6
|
+
|
|
7
|
+
## 2. 核心功能
|
|
8
|
+
|
|
9
|
+
### 2.1 轻量级设计
|
|
10
|
+
- **体积小**:压缩后只有几 KB
|
|
11
|
+
- **无依赖**:纯 JavaScript 实现,无外部依赖
|
|
12
|
+
- **高性能**:事件处理高效,不会影响页面性能
|
|
13
|
+
|
|
14
|
+
### 2.2 灵活的键位定义
|
|
15
|
+
- **支持单个键**:如 `'v'`、`'p'`、`'b'`
|
|
16
|
+
- **支持组合键**:如 `'$mod+KeyZ'`(Ctrl/Command+Z)
|
|
17
|
+
- **支持修饰键**:如 `'Shift+'`、`'Alt+'`、`'Ctrl+'`
|
|
18
|
+
- **支持序列键**:如 `'g g'`(快速按两次 G)
|
|
19
|
+
|
|
20
|
+
### 2.3 强大的事件处理
|
|
21
|
+
- **事件阻止**:可选择是否阻止默认事件
|
|
22
|
+
- **事件冒泡控制**:可控制事件是否冒泡
|
|
23
|
+
- **上下文感知**:可根据当前上下文启用或禁用热键
|
|
24
|
+
|
|
25
|
+
## 3. 核心 API
|
|
26
|
+
|
|
27
|
+
### 3.1 基本用法
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { tinykeys } from 'tinykeys';
|
|
31
|
+
|
|
32
|
+
// 注册热键
|
|
33
|
+
const unsubscribe = tinykeys(window, {
|
|
34
|
+
// 单个键
|
|
35
|
+
'v': (event) => {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
// 执行选择工具
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// 组合键
|
|
41
|
+
'$mod+KeyZ': (event) => {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
// 撤销操作
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// 序列键
|
|
47
|
+
'g g': (event) => {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
// 执行全局操作
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 清理热键
|
|
54
|
+
unsubscribe();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3.2 键位语法
|
|
58
|
+
|
|
59
|
+
| 语法 | 描述 | 示例 |
|
|
60
|
+
|------|------|------|
|
|
61
|
+
| 单个字符 | 单个按键 | `'v'`, `'p'` |
|
|
62
|
+
| `$mod` | 修饰键(Ctrl/Command) | `'$mod+KeyZ'` |
|
|
63
|
+
| `Shift+` | Shift 修饰键 | `'Shift+KeyA'` |
|
|
64
|
+
| `Alt+` | Alt 修饰键 | `'Alt+KeyS'` |
|
|
65
|
+
| `Ctrl+` | Ctrl 修饰键 | `'Ctrl+KeyD'` |
|
|
66
|
+
| 空格 | 键位序列 | `'g g'`(按两次 G) |
|
|
67
|
+
|
|
68
|
+
### 3.3 事件对象
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface TinyKeysEvent extends KeyboardEvent {
|
|
72
|
+
// 原始 KeyboardEvent 属性
|
|
73
|
+
key: string;
|
|
74
|
+
code: string;
|
|
75
|
+
ctrlKey: boolean;
|
|
76
|
+
shiftKey: boolean;
|
|
77
|
+
altKey: boolean;
|
|
78
|
+
metaKey: boolean;
|
|
79
|
+
|
|
80
|
+
// 方法
|
|
81
|
+
preventDefault(): void;
|
|
82
|
+
stopPropagation(): void;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 4. 高级用法
|
|
87
|
+
|
|
88
|
+
### 4.1 上下文感知热键
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// 只在画布区域启用热键
|
|
92
|
+
const canvasElement = document.getElementById('canvas');
|
|
93
|
+
let isCanvasFocused = false;
|
|
94
|
+
|
|
95
|
+
canvasElement?.addEventListener('focus', () => {
|
|
96
|
+
isCanvasFocused = true;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
canvasElement?.addEventListener('blur', () => {
|
|
100
|
+
isCanvasFocused = false;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
tinykeys(window, {
|
|
104
|
+
'v': (event) => {
|
|
105
|
+
if (!isCanvasFocused) return; // 只在画布聚焦时生效
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
// 执行选择工具
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 4.2 动态热键映射
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// 可配置的热键映射
|
|
116
|
+
const hotkeyMap = {
|
|
117
|
+
selectTool: 'v',
|
|
118
|
+
pointTool: 'p',
|
|
119
|
+
brushTool: 'b',
|
|
120
|
+
undo: '$mod+KeyZ',
|
|
121
|
+
redo: '$mod+KeyY'
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handlers = {
|
|
125
|
+
selectTool: () => { /* 处理选择工具 */ },
|
|
126
|
+
pointTool: () => { /* 处理点工具 */ },
|
|
127
|
+
brushTool: () => { /* 处理笔刷工具 */ },
|
|
128
|
+
undo: () => { /* 处理撤销 */ },
|
|
129
|
+
redo: () => { /* 处理重做 */ }
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// 动态生成热键配置
|
|
133
|
+
const hotkeyConfig: Record<string, (event: KeyboardEvent) => void> = {};
|
|
134
|
+
|
|
135
|
+
Object.entries(hotkeyMap).forEach(([action, key]) => {
|
|
136
|
+
hotkeyConfig[key] = (event) => {
|
|
137
|
+
event.preventDefault();
|
|
138
|
+
handlers[action as keyof typeof handlers]();
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
tinykeys(window, hotkeyConfig);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 4.3 热键提示
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// 生成热键提示
|
|
149
|
+
const getHotkeyHint = (action: string): string => {
|
|
150
|
+
const hotkeyMap: Record<string, string> = {
|
|
151
|
+
selectTool: 'V',
|
|
152
|
+
pointTool: 'P',
|
|
153
|
+
brushTool: 'B',
|
|
154
|
+
undo: 'Ctrl+Z',
|
|
155
|
+
redo: 'Ctrl+Y'
|
|
156
|
+
};
|
|
157
|
+
return hotkeyMap[action] || '';
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// 在 UI 中显示热键提示
|
|
161
|
+
const toolButton = document.createElement('button');
|
|
162
|
+
toolButton.innerHTML = `
|
|
163
|
+
选择工具
|
|
164
|
+
<span class="hotkey-hint">${getHotkeyHint('selectTool')}</span>
|
|
165
|
+
`;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 5. 集成示例
|
|
169
|
+
|
|
170
|
+
### 5.1 Vue 组件集成
|
|
171
|
+
|
|
172
|
+
```vue
|
|
173
|
+
<template>
|
|
174
|
+
<div class="annotation-editor">
|
|
175
|
+
<div class="toolbar">
|
|
176
|
+
<button
|
|
177
|
+
class="tool-button"
|
|
178
|
+
:class="{ active: currentTool === 'select' }"
|
|
179
|
+
@click="selectTool"
|
|
180
|
+
title="选择工具 (V)"
|
|
181
|
+
>
|
|
182
|
+
选择
|
|
183
|
+
<span class="hotkey-hint" v-if="showHotkeys">V</span>
|
|
184
|
+
</button>
|
|
185
|
+
<button
|
|
186
|
+
class="tool-button"
|
|
187
|
+
:class="{ active: currentTool === 'point' }"
|
|
188
|
+
@click="pointTool"
|
|
189
|
+
title="点标注工具 (P)"
|
|
190
|
+
>
|
|
191
|
+
点标注
|
|
192
|
+
<span class="hotkey-hint" v-if="showHotkeys">P</span>
|
|
193
|
+
</button>
|
|
194
|
+
<button
|
|
195
|
+
class="tool-button"
|
|
196
|
+
:class="{ active: currentTool === 'brush' }"
|
|
197
|
+
@click="brushTool"
|
|
198
|
+
title="笔刷工具 (B)"
|
|
199
|
+
>
|
|
200
|
+
笔刷
|
|
201
|
+
<span class="hotkey-hint" v-if="showHotkeys">B</span>
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</template>
|
|
206
|
+
|
|
207
|
+
<script setup lang="ts">
|
|
208
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
209
|
+
import { tinykeys } from 'tinykeys';
|
|
210
|
+
|
|
211
|
+
const currentTool = ref<'select' | 'point' | 'brush'>('select');
|
|
212
|
+
const showHotkeys = ref(true);
|
|
213
|
+
|
|
214
|
+
const selectTool = () => currentTool.value = 'select';
|
|
215
|
+
const pointTool = () => currentTool.value = 'point';
|
|
216
|
+
const brushTool = () => currentTool.value = 'brush';
|
|
217
|
+
|
|
218
|
+
onMounted(() => {
|
|
219
|
+
const unsubscribe = tinykeys(window, {
|
|
220
|
+
'v': (event) => {
|
|
221
|
+
event.preventDefault();
|
|
222
|
+
selectTool();
|
|
223
|
+
},
|
|
224
|
+
'p': (event) => {
|
|
225
|
+
event.preventDefault();
|
|
226
|
+
pointTool();
|
|
227
|
+
},
|
|
228
|
+
'b': (event) => {
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
brushTool();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// 存储清理函数
|
|
235
|
+
window.__tinykeysUnsubscribe = unsubscribe;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
onUnmounted(() => {
|
|
239
|
+
window.__tinykeysUnsubscribe?.();
|
|
240
|
+
});
|
|
241
|
+
</script>
|
|
242
|
+
|
|
243
|
+
<style scoped>
|
|
244
|
+
.tool-button {
|
|
245
|
+
position: relative;
|
|
246
|
+
padding: 8px 12px;
|
|
247
|
+
margin: 0 4px;
|
|
248
|
+
border: 1px solid #ccc;
|
|
249
|
+
background: white;
|
|
250
|
+
border-radius: 4px;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.tool-button.active {
|
|
255
|
+
background: #e6f7ff;
|
|
256
|
+
border-color: #1890ff;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.hotkey-hint {
|
|
260
|
+
position: absolute;
|
|
261
|
+
bottom: 2px;
|
|
262
|
+
right: 4px;
|
|
263
|
+
font-size: 10px;
|
|
264
|
+
color: #999;
|
|
265
|
+
background: #f5f5f5;
|
|
266
|
+
padding: 1px 4px;
|
|
267
|
+
border-radius: 2px;
|
|
268
|
+
}
|
|
269
|
+
</style>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 5.2 React 组件集成
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import React, { useEffect, useState } from 'react';
|
|
276
|
+
import { tinykeys } from 'tinykeys';
|
|
277
|
+
|
|
278
|
+
const AnnotationEditor: React.FC = () => {
|
|
279
|
+
const [currentTool, setCurrentTool] = useState<'select' | 'point' | 'brush'>('select');
|
|
280
|
+
const [showHotkeys, setShowHotkeys] = useState(true);
|
|
281
|
+
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
const unsubscribe = tinykeys(window, {
|
|
284
|
+
'v': (event) => {
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
setCurrentTool('select');
|
|
287
|
+
},
|
|
288
|
+
'p': (event) => {
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
setCurrentTool('point');
|
|
291
|
+
},
|
|
292
|
+
'b': (event) => {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
setCurrentTool('brush');
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return () => unsubscribe();
|
|
299
|
+
}, []);
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div className="annotation-editor">
|
|
303
|
+
<div className="toolbar">
|
|
304
|
+
<button
|
|
305
|
+
className={`tool-button ${currentTool === 'select' ? 'active' : ''}`}
|
|
306
|
+
onClick={() => setCurrentTool('select')}
|
|
307
|
+
title="选择工具 (V)"
|
|
308
|
+
>
|
|
309
|
+
选择
|
|
310
|
+
{showHotkeys && <span className="hotkey-hint">V</span>}
|
|
311
|
+
</button>
|
|
312
|
+
<button
|
|
313
|
+
className={`tool-button ${currentTool === 'point' ? 'active' : ''}`}
|
|
314
|
+
onClick={() => setCurrentTool('point')}
|
|
315
|
+
title="点标注工具 (P)"
|
|
316
|
+
>
|
|
317
|
+
点标注
|
|
318
|
+
{showHotkeys && <span className="hotkey-hint">P</span>}
|
|
319
|
+
</button>
|
|
320
|
+
<button
|
|
321
|
+
className={`tool-button ${currentTool === 'brush' ? 'active' : ''}`}
|
|
322
|
+
onClick={() => setCurrentTool('brush')}
|
|
323
|
+
title="笔刷工具 (B)"
|
|
324
|
+
>
|
|
325
|
+
笔刷
|
|
326
|
+
{showHotkeys && <span className="hotkey-hint">B</span>}
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export default AnnotationEditor;
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## 6. 最佳实践
|
|
337
|
+
|
|
338
|
+
### 6.1 热键设计原则
|
|
339
|
+
- **一致性**:遵循操作系统和常见应用的热键约定
|
|
340
|
+
- **可发现性**:在 UI 中显示热键提示
|
|
341
|
+
- **可定制性**:允许用户自定义热键
|
|
342
|
+
- **冲突避免**:避免与浏览器默认热键冲突
|
|
343
|
+
|
|
344
|
+
### 6.2 性能优化
|
|
345
|
+
- **事件委托**:使用事件委托减少事件监听器数量
|
|
346
|
+
- **条件执行**:只在必要时处理热键事件
|
|
347
|
+
- **及时清理**:组件卸载时清理热键监听器
|
|
348
|
+
|
|
349
|
+
### 6.3 用户体验
|
|
350
|
+
- **反馈机制**:热键触发时提供视觉或听觉反馈
|
|
351
|
+
- **容错处理**:允许一定的按键延迟和错误
|
|
352
|
+
- **帮助系统**:提供热键帮助文档
|
|
353
|
+
|
|
354
|
+
## 7. 常见问题
|
|
355
|
+
|
|
356
|
+
### 7.1 热键不生效
|
|
357
|
+
**问题**:注册的热键没有响应
|
|
358
|
+
**解决方案**:
|
|
359
|
+
- 检查键位语法是否正确
|
|
360
|
+
- 确保事件没有被其他处理程序阻止
|
|
361
|
+
- 检查上下文条件是否满足
|
|
362
|
+
|
|
363
|
+
### 7.2 热键冲突
|
|
364
|
+
**问题**:自定义热键与浏览器默认热键冲突
|
|
365
|
+
**解决方案**:
|
|
366
|
+
- 选择不常用的键位组合
|
|
367
|
+
- 在特定上下文下启用热键
|
|
368
|
+
- 提供热键冲突检测
|
|
369
|
+
|
|
370
|
+
### 7.3 序列键不工作
|
|
371
|
+
**问题**:序列键(如 `'g g'`)不响应
|
|
372
|
+
**解决方案**:
|
|
373
|
+
- 确保按键速度足够快
|
|
374
|
+
- 检查是否有其他事件处理程序干扰
|
|
375
|
+
- 调整序列键的超时设置
|
|
376
|
+
|
|
377
|
+
## 8. 浏览器兼容性
|
|
378
|
+
|
|
379
|
+
| 浏览器 | 支持情况 | 备注 |
|
|
380
|
+
|--------|----------|------|
|
|
381
|
+
| Chrome | ✅ | 完全支持 |
|
|
382
|
+
| Firefox | ✅ | 完全支持 |
|
|
383
|
+
| Safari | ✅ | 完全支持 |
|
|
384
|
+
| Edge | ✅ | 完全支持 |
|
|
385
|
+
| IE 11 | ❌ | 不支持 |
|
|
386
|
+
|
|
387
|
+
## 9. 与其他库的比较
|
|
388
|
+
|
|
389
|
+
| 库 | 体积 | 功能 | 易用性 | 依赖 |
|
|
390
|
+
|----|------|------|--------|------|
|
|
391
|
+
| tinykeys | 极小 | 基础热键 | 高 | 无 |
|
|
392
|
+
| Mousetrap | 中等 | 丰富 | 中 | 无 |
|
|
393
|
+
| keymaster | 小 | 基础 | 高 | 无 |
|
|
394
|
+
| hotkeys-js | 中等 | 丰富 | 中 | 无 |
|
|
395
|
+
|
|
396
|
+
## 10. 版本历史
|
|
397
|
+
|
|
398
|
+
| 版本 | 日期 | 变更内容 |
|
|
399
|
+
|------|------|----------|
|
|
400
|
+
| 1.0.0 | 2023-01-01 | 初始版本 |
|
|
401
|
+
| 2.0.0 | 2024-06-15 | 重构 API,支持序列键 |
|
|
402
|
+
| 3.0.0 | 2025-03-10 | 优化性能,增加 TypeScript 支持 |
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
**文档更新时间**:2026-04-27
|
|
407
|
+
**作者**:AI Assistant
|