yo-bug 0.1.3 → 0.2.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/dist/src/server/proxy-server.d.ts.map +1 -1
- package/dist/src/server/proxy-server.js +58 -23
- package/dist/src/server/proxy-server.js.map +1 -1
- package/dist/src/server/sdk-serve.d.ts.map +1 -1
- package/dist/src/server/sdk-serve.js +12 -5
- package/dist/src/server/sdk-serve.js.map +1 -1
- package/dist/vibe-feedback.js +713 -212
- package/package.json +1 -1
- package/src/client/annotation-mode/toolbar.ts +53 -63
- package/src/client/core/i18n.ts +23 -29
- package/src/client/core/sdk.ts +21 -48
- package/src/client/styles/icons.ts +85 -0
- package/src/client/styles/sdk.css.ts +652 -100
- package/src/client/ui/checklist-panel.ts +35 -77
- package/src/client/ui/feedback-panel.ts +7 -1
- package/src/client/ui/floating-button.ts +7 -35
- package/src/client/ui/toast.ts +12 -1
- package/src/client/ui/verify-panel.ts +14 -26
- package/src/server/proxy-server.ts +62 -28
- package/src/server/sdk-serve.ts +14 -5
- package/src/server/html-injector.ts +0 -46
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ICON_ARROW, ICON_RECT, ICON_CIRCLE, ICON_FREEHAND, ICON_TEXT,
|
|
3
|
+
ICON_UNDO, ICON_CHECK, ICON_X
|
|
4
|
+
} from '../styles/icons.js';
|
|
5
|
+
|
|
1
6
|
export type ToolType = 'arrow' | 'rect' | 'circle' | 'freehand' | 'text';
|
|
2
7
|
|
|
3
8
|
const TOOLS: { type: ToolType; icon: string; label: string }[] = [
|
|
4
|
-
{ type: 'arrow', icon:
|
|
5
|
-
{ type: 'rect', icon:
|
|
6
|
-
{ type: 'circle', icon:
|
|
7
|
-
{ type: 'freehand', icon:
|
|
8
|
-
{ type: 'text', icon:
|
|
9
|
+
{ type: 'arrow', icon: ICON_ARROW, label: 'Arrow' },
|
|
10
|
+
{ type: 'rect', icon: ICON_RECT, label: 'Rect' },
|
|
11
|
+
{ type: 'circle', icon: ICON_CIRCLE, label: 'Circle' },
|
|
12
|
+
{ type: 'freehand', icon: ICON_FREEHAND, label: 'Draw' },
|
|
13
|
+
{ type: 'text', icon: ICON_TEXT, label: 'Text' },
|
|
9
14
|
];
|
|
10
15
|
|
|
11
16
|
const COLORS = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6', '#8b5cf6', '#ec4899', '#000000', '#ffffff'];
|
|
@@ -13,6 +18,8 @@ const COLORS = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6', '#8b5cf6', '#ec4899'
|
|
|
13
18
|
export class Toolbar {
|
|
14
19
|
private container: HTMLDivElement;
|
|
15
20
|
private toolBtns: HTMLButtonElement[] = [];
|
|
21
|
+
private colorBtns: HTMLButtonElement[] = [];
|
|
22
|
+
private activeColor = '#ef4444';
|
|
16
23
|
|
|
17
24
|
constructor(
|
|
18
25
|
parent: HTMLElement,
|
|
@@ -25,25 +32,15 @@ export class Toolbar {
|
|
|
25
32
|
}
|
|
26
33
|
) {
|
|
27
34
|
this.container = document.createElement('div');
|
|
28
|
-
this.container.
|
|
29
|
-
position: absolute;
|
|
30
|
-
top: 12px;
|
|
31
|
-
left: 50%;
|
|
32
|
-
transform: translateX(-50%);
|
|
33
|
-
display: flex;
|
|
34
|
-
align-items: center;
|
|
35
|
-
gap: 6px;
|
|
36
|
-
background: #1e293b;
|
|
37
|
-
padding: 8px 12px;
|
|
38
|
-
border-radius: 10px;
|
|
39
|
-
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
|
|
40
|
-
z-index: 10;
|
|
41
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
42
|
-
`;
|
|
35
|
+
this.container.className = 'vf-toolbar';
|
|
43
36
|
|
|
44
37
|
// Tool buttons
|
|
45
38
|
for (const tool of TOOLS) {
|
|
46
|
-
const btn =
|
|
39
|
+
const btn = document.createElement('button');
|
|
40
|
+
btn.className = 'vf-tool-btn';
|
|
41
|
+
btn.innerHTML = tool.icon;
|
|
42
|
+
btn.title = tool.label;
|
|
43
|
+
btn.addEventListener('click', () => {
|
|
47
44
|
this.selectTool(tool.type);
|
|
48
45
|
callbacks.onToolSelect(tool.type);
|
|
49
46
|
});
|
|
@@ -56,41 +53,56 @@ export class Toolbar {
|
|
|
56
53
|
// Color buttons
|
|
57
54
|
for (const color of COLORS) {
|
|
58
55
|
const btn = document.createElement('button');
|
|
59
|
-
btn.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
btn.className = `vf-color-btn${color === this.activeColor ? ' active' : ''}`;
|
|
57
|
+
btn.style.background = color;
|
|
58
|
+
if (color === '#ffffff') {
|
|
59
|
+
btn.style.borderColor = '#475569';
|
|
60
|
+
}
|
|
64
61
|
btn.addEventListener('click', () => {
|
|
65
|
-
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
this.activeColor = color;
|
|
63
|
+
this.colorBtns.forEach((b) => {
|
|
64
|
+
b.classList.remove('active');
|
|
65
|
+
// Restore white's visible border
|
|
66
|
+
if (b.style.background === 'rgb(255, 255, 255)' || b.dataset.color === '#ffffff') {
|
|
67
|
+
b.style.borderColor = '#475569';
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
btn.classList.add('active');
|
|
70
71
|
callbacks.onColorChange(color);
|
|
71
72
|
});
|
|
72
73
|
btn.dataset.color = color;
|
|
74
|
+
this.colorBtns.push(btn);
|
|
73
75
|
this.container.appendChild(btn);
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
this.addSeparator();
|
|
77
79
|
|
|
78
80
|
// Undo
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
const undoBtn = document.createElement('button');
|
|
82
|
+
undoBtn.className = 'vf-tool-btn';
|
|
83
|
+
undoBtn.innerHTML = ICON_UNDO;
|
|
84
|
+
undoBtn.title = 'Undo (Ctrl+Z)';
|
|
85
|
+
undoBtn.addEventListener('click', callbacks.onUndo);
|
|
86
|
+
this.container.appendChild(undoBtn);
|
|
82
87
|
|
|
83
88
|
this.addSeparator();
|
|
84
89
|
|
|
85
|
-
// Done
|
|
86
|
-
const doneBtn =
|
|
90
|
+
// Done
|
|
91
|
+
const doneBtn = document.createElement('button');
|
|
92
|
+
doneBtn.className = 'vf-tool-action';
|
|
87
93
|
doneBtn.style.background = '#22c55e';
|
|
88
|
-
doneBtn.
|
|
94
|
+
doneBtn.innerHTML = ICON_CHECK;
|
|
95
|
+
doneBtn.title = 'Done';
|
|
96
|
+
doneBtn.addEventListener('click', callbacks.onDone);
|
|
89
97
|
this.container.appendChild(doneBtn);
|
|
90
98
|
|
|
91
|
-
|
|
99
|
+
// Cancel
|
|
100
|
+
const cancelBtn = document.createElement('button');
|
|
101
|
+
cancelBtn.className = 'vf-tool-action';
|
|
92
102
|
cancelBtn.style.background = '#ef4444';
|
|
93
|
-
cancelBtn.
|
|
103
|
+
cancelBtn.innerHTML = ICON_X;
|
|
104
|
+
cancelBtn.title = 'Cancel';
|
|
105
|
+
cancelBtn.addEventListener('click', callbacks.onCancel);
|
|
94
106
|
this.container.appendChild(cancelBtn);
|
|
95
107
|
|
|
96
108
|
parent.appendChild(this.container);
|
|
@@ -99,37 +111,15 @@ export class Toolbar {
|
|
|
99
111
|
this.selectTool('arrow');
|
|
100
112
|
}
|
|
101
113
|
|
|
102
|
-
private createBtn(icon: string, title: string, onClick: () => void): HTMLButtonElement {
|
|
103
|
-
const btn = document.createElement('button');
|
|
104
|
-
btn.textContent = icon;
|
|
105
|
-
btn.title = title;
|
|
106
|
-
btn.style.cssText = `
|
|
107
|
-
width: 34px; height: 34px; border: none; border-radius: 6px;
|
|
108
|
-
background: transparent; color: #94a3b8; font-size: 16px;
|
|
109
|
-
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
110
|
-
`;
|
|
111
|
-
btn.addEventListener('mouseenter', () => {
|
|
112
|
-
if (!btn.classList.contains('tool-active')) btn.style.background = '#334155';
|
|
113
|
-
});
|
|
114
|
-
btn.addEventListener('mouseleave', () => {
|
|
115
|
-
if (!btn.classList.contains('tool-active')) btn.style.background = 'transparent';
|
|
116
|
-
});
|
|
117
|
-
btn.addEventListener('click', onClick);
|
|
118
|
-
return btn;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
114
|
private addSeparator(): void {
|
|
122
115
|
const sep = document.createElement('div');
|
|
123
|
-
sep.
|
|
116
|
+
sep.className = 'vf-toolbar-sep';
|
|
124
117
|
this.container.appendChild(sep);
|
|
125
118
|
}
|
|
126
119
|
|
|
127
120
|
private selectTool(type: ToolType): void {
|
|
128
121
|
this.toolBtns.forEach((btn, i) => {
|
|
129
|
-
|
|
130
|
-
btn.classList.toggle('tool-active', isActive);
|
|
131
|
-
btn.style.background = isActive ? '#3b82f6' : 'transparent';
|
|
132
|
-
btn.style.color = isActive ? 'white' : '#94a3b8';
|
|
122
|
+
btn.classList.toggle('active', TOOLS[i].type === type);
|
|
133
123
|
});
|
|
134
124
|
}
|
|
135
125
|
|
package/src/client/core/i18n.ts
CHANGED
|
@@ -1,33 +1,30 @@
|
|
|
1
1
|
const zhCN: Record<string, string> = {
|
|
2
2
|
// Floating button
|
|
3
|
-
'hint.firstTime': '
|
|
3
|
+
'hint.firstTime': '发现问题?点我或按 Alt+E 报告',
|
|
4
4
|
// Mode bar
|
|
5
|
-
'mode.
|
|
6
|
-
'mode.
|
|
7
|
-
'mode.
|
|
8
|
-
'mode.
|
|
9
|
-
'mode.screenshot': '\u622A\u56FE\u6807\u6CE8',
|
|
10
|
-
'mode.screenshot.hint': '\u6846\u9009\u533A\u57DF + \u753B',
|
|
5
|
+
'mode.element': '元素报告',
|
|
6
|
+
'mode.element.hint': '点击问题元素',
|
|
7
|
+
'mode.screenshot': '截图标注',
|
|
8
|
+
'mode.screenshot.hint': '框选区域 + 画',
|
|
11
9
|
// Feedback panel
|
|
12
|
-
'panel.element.title': '
|
|
13
|
-
'panel.annotation.title': '
|
|
14
|
-
'panel.type': '
|
|
15
|
-
'panel.description': '
|
|
16
|
-
'panel.description.placeholder': '
|
|
17
|
-
'panel.cancel': '
|
|
18
|
-
'panel.submit': '
|
|
10
|
+
'panel.element.title': '元素反馈',
|
|
11
|
+
'panel.annotation.title': '截图反馈',
|
|
12
|
+
'panel.type': '问题类型',
|
|
13
|
+
'panel.description': '问题描述',
|
|
14
|
+
'panel.description.placeholder': '描述你发现的问题...',
|
|
15
|
+
'panel.cancel': '取消',
|
|
16
|
+
'panel.submit': '提交',
|
|
19
17
|
// Toast
|
|
20
|
-
'toast.
|
|
21
|
-
'toast.
|
|
22
|
-
'toast.
|
|
23
|
-
'toast.
|
|
24
|
-
'toast.clickToFlag': '\u70B9\u51FB\u95EE\u9898\u5143\u7D20\u6765\u6807\u8BB0',
|
|
18
|
+
'toast.submitted': '反馈已提交',
|
|
19
|
+
'toast.failed': '提交失败',
|
|
20
|
+
'toast.screenshotFailed': '截图失败',
|
|
21
|
+
'toast.clickToFlag': '点击问题元素来标记',
|
|
25
22
|
// Region selector
|
|
26
|
-
'region.hint': '
|
|
23
|
+
'region.hint': '拖拽选择区域 · ESC 取消',
|
|
27
24
|
// Verify
|
|
28
|
-
'verify.title': '
|
|
29
|
-
'verify.fixed': '
|
|
30
|
-
'verify.broken': '
|
|
25
|
+
'verify.title': '验证修复',
|
|
26
|
+
'verify.fixed': '已修复',
|
|
27
|
+
'verify.broken': '未修复',
|
|
31
28
|
// Problem types
|
|
32
29
|
'type.bug': 'Bug',
|
|
33
30
|
'type.ui-issue': 'UI',
|
|
@@ -37,11 +34,9 @@ const zhCN: Record<string, string> = {
|
|
|
37
34
|
};
|
|
38
35
|
|
|
39
36
|
const en: Record<string, string> = {
|
|
40
|
-
'hint.firstTime': 'Found a problem? Click me or press Alt+
|
|
41
|
-
'mode.
|
|
42
|
-
'mode.
|
|
43
|
-
'mode.detail': 'Describe',
|
|
44
|
-
'mode.detail.hint': 'Tap + fill form',
|
|
37
|
+
'hint.firstTime': 'Found a problem? Click me or press Alt+E to report.',
|
|
38
|
+
'mode.element': 'Element',
|
|
39
|
+
'mode.element.hint': 'Tap problem element',
|
|
45
40
|
'mode.screenshot': 'Screenshot',
|
|
46
41
|
'mode.screenshot.hint': 'Select area + draw',
|
|
47
42
|
'panel.element.title': 'Element Feedback',
|
|
@@ -51,7 +46,6 @@ const en: Record<string, string> = {
|
|
|
51
46
|
'panel.description.placeholder': 'Describe the issue...',
|
|
52
47
|
'panel.cancel': 'Cancel',
|
|
53
48
|
'panel.submit': 'Submit',
|
|
54
|
-
'toast.flagged': 'Flagged!',
|
|
55
49
|
'toast.submitted': 'Feedback submitted',
|
|
56
50
|
'toast.failed': 'Submit failed',
|
|
57
51
|
'toast.screenshotFailed': 'Screenshot failed',
|
package/src/client/core/sdk.ts
CHANGED
|
@@ -17,8 +17,9 @@ import { ErrorInterceptor, type ErrorEntry } from '../capture/error-interceptor.
|
|
|
17
17
|
import { ApiClient, type FeedbackPayload } from '../api/client.js';
|
|
18
18
|
import { ActionRecorder } from '../capture/action-recorder.js';
|
|
19
19
|
import { t } from './i18n.js';
|
|
20
|
+
import { ICON_FLAG, ICON_SCREENSHOT } from '../styles/icons.js';
|
|
20
21
|
|
|
21
|
-
type Mode = '
|
|
22
|
+
type Mode = 'element' | 'annotate';
|
|
22
23
|
|
|
23
24
|
export class VibeFeedback {
|
|
24
25
|
private shadowHost: ShadowHost;
|
|
@@ -91,22 +92,18 @@ export class VibeFeedback {
|
|
|
91
92
|
// Don't intercept inside our SDK panels
|
|
92
93
|
if (this.isSDKElement(e.target as Element)) return;
|
|
93
94
|
|
|
94
|
-
//
|
|
95
|
+
// Use e.code (physical key) — reliable across IME/keyboard layouts
|
|
95
96
|
if (e.altKey) {
|
|
96
|
-
switch (e.
|
|
97
|
-
case '
|
|
97
|
+
switch (e.code) {
|
|
98
|
+
case 'KeyE':
|
|
98
99
|
e.preventDefault();
|
|
99
|
-
this.enterMode('
|
|
100
|
+
this.enterMode('element');
|
|
100
101
|
return;
|
|
101
|
-
case '
|
|
102
|
-
e.preventDefault();
|
|
103
|
-
this.enterMode('detail');
|
|
104
|
-
return;
|
|
105
|
-
case 's': case 'S':
|
|
102
|
+
case 'KeyS':
|
|
106
103
|
e.preventDefault();
|
|
107
104
|
this.enterMode('annotate');
|
|
108
105
|
return;
|
|
109
|
-
case '
|
|
106
|
+
case 'KeyX':
|
|
110
107
|
e.preventDefault();
|
|
111
108
|
if (this.active) this.exitTestMode();
|
|
112
109
|
else this.toggleModeBar();
|
|
@@ -140,7 +137,7 @@ export class VibeFeedback {
|
|
|
140
137
|
this.hideModeBar();
|
|
141
138
|
this.showModeBar();
|
|
142
139
|
// Highlight active button
|
|
143
|
-
const idx = mode === '
|
|
140
|
+
const idx = mode === 'element' ? 0 : 1;
|
|
144
141
|
this.modeBar?.querySelectorAll('.vf-mode-btn').forEach((btn, i) => {
|
|
145
142
|
btn.classList.toggle('active', i === idx);
|
|
146
143
|
});
|
|
@@ -171,21 +168,19 @@ export class VibeFeedback {
|
|
|
171
168
|
this.modeBar = document.createElement('div');
|
|
172
169
|
this.modeBar.className = 'vf-mode-bar';
|
|
173
170
|
|
|
174
|
-
const modes: { mode: Mode; label: string; hint: string; key: string }[] = [
|
|
175
|
-
{ mode: '
|
|
176
|
-
{ mode: '
|
|
177
|
-
{ mode: 'annotate', label: t('mode.screenshot'), hint: t('mode.screenshot.hint'), key: 'Alt+S' },
|
|
171
|
+
const modes: { mode: Mode; label: string; hint: string; key: string; icon: string }[] = [
|
|
172
|
+
{ mode: 'element', label: t('mode.element'), hint: t('mode.element.hint'), key: 'E', icon: ICON_FLAG },
|
|
173
|
+
{ mode: 'annotate', label: t('mode.screenshot'), hint: t('mode.screenshot.hint'), key: 'S', icon: ICON_SCREENSHOT },
|
|
178
174
|
];
|
|
179
175
|
|
|
180
176
|
for (const m of modes) {
|
|
181
177
|
const btn = document.createElement('button');
|
|
182
178
|
btn.className = `vf-mode-btn${this.mode === m.mode ? ' active' : ''}`;
|
|
183
179
|
btn.innerHTML = `
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
</
|
|
188
|
-
<div style="font-size:10px;opacity:0.5;margin-top:1px">${m.hint}</div>
|
|
180
|
+
${m.icon}
|
|
181
|
+
<span class="vf-mode-label">${m.label}</span>
|
|
182
|
+
<span class="vf-mode-hint">${m.hint}</span>
|
|
183
|
+
<span class="vf-mode-key">${m.key}</span>
|
|
189
184
|
`;
|
|
190
185
|
btn.addEventListener('click', () => {
|
|
191
186
|
this.mode = m.mode;
|
|
@@ -208,7 +203,7 @@ export class VibeFeedback {
|
|
|
208
203
|
this.highlighter.deactivate();
|
|
209
204
|
if (!this.mode) return;
|
|
210
205
|
|
|
211
|
-
if (this.mode === '
|
|
206
|
+
if (this.mode === 'element') {
|
|
212
207
|
this.highlighter.activate();
|
|
213
208
|
} else {
|
|
214
209
|
this.startAnnotation();
|
|
@@ -227,37 +222,15 @@ export class VibeFeedback {
|
|
|
227
222
|
// ──────── Element selected ────────
|
|
228
223
|
private onElementSelected(el: Element): void {
|
|
229
224
|
this.selectedElement = inspectElement(el);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
} else {
|
|
234
|
-
this.highlighter.deactivate();
|
|
235
|
-
this.hideModeBar();
|
|
236
|
-
this.panel.showForElement(this.selectedElement);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// ──────── Quick flag ────────
|
|
241
|
-
private async quickFlag(): Promise<void> {
|
|
242
|
-
const payload = this.buildPayload('bug', '');
|
|
243
|
-
try {
|
|
244
|
-
const { id } = await this.apiClient.submitFeedback(payload);
|
|
245
|
-
showToast(this.shadowHost.shadow, `${t('toast.flagged')} (${id})`);
|
|
246
|
-
|
|
247
|
-
if (this.activeChecklistItemId !== null) {
|
|
248
|
-
await this.linkFeedbackToChecklist(id, this.activeChecklistItemId);
|
|
249
|
-
this.activeChecklistItemId = null;
|
|
250
|
-
}
|
|
251
|
-
} catch {
|
|
252
|
-
showToast(this.shadowHost.shadow, t('toast.failed'), true);
|
|
253
|
-
}
|
|
254
|
-
this.selectedElement = null;
|
|
225
|
+
this.highlighter.deactivate();
|
|
226
|
+
this.hideModeBar();
|
|
227
|
+
this.panel.showForElement(this.selectedElement);
|
|
255
228
|
}
|
|
256
229
|
|
|
257
230
|
// ──────── Checklist integration ────────
|
|
258
231
|
private onChecklistFail(itemId: number): void {
|
|
259
232
|
this.activeChecklistItemId = itemId;
|
|
260
|
-
this.enterMode('
|
|
233
|
+
this.enterMode('element');
|
|
261
234
|
showToast(this.shadowHost.shadow, t('toast.clickToFlag'));
|
|
262
235
|
}
|
|
263
236
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Inline SVG icons — crisp at any size, no external dependencies */
|
|
2
|
+
|
|
3
|
+
// yo-bug brand icon — cute beetle
|
|
4
|
+
export const ICON_BUG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
5
|
+
<ellipse cx="12" cy="15" rx="5.5" ry="6.5"/>
|
|
6
|
+
<circle cx="12" cy="6" r="2.5"/>
|
|
7
|
+
<line x1="12" y1="8.5" x2="12" y2="21.5"/>
|
|
8
|
+
<path d="M6.5 12L3 10"/>
|
|
9
|
+
<path d="M17.5 12L21 10"/>
|
|
10
|
+
<path d="M6.5 16L3 18"/>
|
|
11
|
+
<path d="M17.5 16L21 18"/>
|
|
12
|
+
<path d="M10 4L8 1.5"/>
|
|
13
|
+
<path d="M14 4L16 1.5"/>
|
|
14
|
+
</svg>`;
|
|
15
|
+
|
|
16
|
+
export const ICON_CLOSE = `<svg class="vf-close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
|
|
17
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
18
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
19
|
+
</svg>`;
|
|
20
|
+
|
|
21
|
+
// Mode icons
|
|
22
|
+
export const ICON_FLAG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
23
|
+
<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/>
|
|
24
|
+
<line x1="4" y1="22" x2="4" y2="15"/>
|
|
25
|
+
</svg>`;
|
|
26
|
+
|
|
27
|
+
export const ICON_DETAIL = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
28
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
|
29
|
+
<polyline points="14 2 14 8 20 8"/>
|
|
30
|
+
<line x1="16" y1="13" x2="8" y2="13"/>
|
|
31
|
+
<line x1="16" y1="17" x2="8" y2="17"/>
|
|
32
|
+
<polyline points="10 9 9 9 8 9"/>
|
|
33
|
+
</svg>`;
|
|
34
|
+
|
|
35
|
+
export const ICON_SCREENSHOT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
36
|
+
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
|
|
37
|
+
<circle cx="12" cy="13" r="4"/>
|
|
38
|
+
</svg>`;
|
|
39
|
+
|
|
40
|
+
// Toolbar icons
|
|
41
|
+
export const ICON_ARROW = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
42
|
+
<line x1="5" y1="19" x2="19" y2="5"/>
|
|
43
|
+
<polyline points="12 5 19 5 19 12"/>
|
|
44
|
+
</svg>`;
|
|
45
|
+
|
|
46
|
+
export const ICON_RECT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
47
|
+
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
|
48
|
+
</svg>`;
|
|
49
|
+
|
|
50
|
+
export const ICON_CIRCLE = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
51
|
+
<circle cx="12" cy="12" r="10"/>
|
|
52
|
+
</svg>`;
|
|
53
|
+
|
|
54
|
+
export const ICON_FREEHAND = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
55
|
+
<path d="M12 19l7-7 3 3-7 7-3-3z"/>
|
|
56
|
+
<path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>
|
|
57
|
+
<path d="M2 2l7.586 7.586"/>
|
|
58
|
+
<circle cx="11" cy="11" r="2"/>
|
|
59
|
+
</svg>`;
|
|
60
|
+
|
|
61
|
+
export const ICON_TEXT = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
62
|
+
<polyline points="4 7 4 4 20 4 20 7"/>
|
|
63
|
+
<line x1="9" y1="20" x2="15" y2="20"/>
|
|
64
|
+
<line x1="12" y1="4" x2="12" y2="20"/>
|
|
65
|
+
</svg>`;
|
|
66
|
+
|
|
67
|
+
export const ICON_UNDO = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
68
|
+
<polyline points="1 4 1 10 7 10"/>
|
|
69
|
+
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
|
|
70
|
+
</svg>`;
|
|
71
|
+
|
|
72
|
+
export const ICON_CHECK = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
|
73
|
+
<polyline points="4 12 10 18 20 6"/>
|
|
74
|
+
</svg>`;
|
|
75
|
+
|
|
76
|
+
export const ICON_X = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round">
|
|
77
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
78
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
79
|
+
</svg>`;
|
|
80
|
+
|
|
81
|
+
// Verify icon
|
|
82
|
+
export const ICON_VERIFY = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
83
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
84
|
+
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
85
|
+
</svg>`;
|