@operato/image-marker 8.0.0-alpha.17 → 8.0.0-alpha.22
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/CHANGELOG.md +18 -0
- package/dist/src/base-image-marker.d.ts +16 -4
- package/dist/src/base-image-marker.js +96 -15
- package/dist/src/base-image-marker.js.map +1 -1
- package/dist/src/ox-image-marker-mode.js +2 -2
- package/dist/src/ox-image-marker-mode.js.map +1 -1
- package/dist/src/ox-image-marker-view.js +2 -2
- package/dist/src/ox-image-marker-view.js.map +1 -1
- package/dist/src/ox-image-marker.d.ts +9 -4
- package/dist/src/ox-image-marker.js +162 -92
- package/dist/src/ox-image-marker.js.map +1 -1
- package/dist/stories/ox-image-marker.stories.js +16 -0
- package/dist/stories/ox-image-marker.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/base-image-marker.ts +114 -19
- package/src/ox-image-marker-mode.ts +2 -2
- package/src/ox-image-marker-view.ts +2 -2
- package/src/ox-image-marker.ts +181 -100
- package/stories/ox-image-marker.stories.ts +16 -0
|
@@ -15,16 +15,31 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
15
15
|
this.startY = 0;
|
|
16
16
|
this.offsetX = 0;
|
|
17
17
|
this.offsetY = 0;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
this.onSvgTouchStart = (event) => {
|
|
19
|
+
if (event.touches.length === 1) {
|
|
20
|
+
this.selectedShape = null;
|
|
21
|
+
this.shapes = this.shapes.map(s => {
|
|
22
|
+
return { ...s, selected: false };
|
|
23
|
+
});
|
|
24
|
+
if (this.currentMode === 'move') {
|
|
25
|
+
this.startPanning(event);
|
|
26
|
+
}
|
|
27
|
+
else if (this.currentMode === 'draw') {
|
|
28
|
+
this.startDrawing(event);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (event.touches.length === 2) {
|
|
32
|
+
this.startPinchZooming(event);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
this.onDrawingMouseMove = (event) => {
|
|
36
|
+
if (!this.drawing) {
|
|
21
37
|
return;
|
|
38
|
+
}
|
|
22
39
|
const { x, y } = this.getSvgPoint(event);
|
|
23
|
-
// 최소 거리 확인 (예: 5px 이상이어야 도형을 생성)
|
|
24
40
|
const minDistance = 5;
|
|
25
41
|
const distance = Math.sqrt(Math.pow(x - this.startX, 2) + Math.pow(y - this.startY, 2));
|
|
26
42
|
if (!this.selectedShape && distance >= minDistance) {
|
|
27
|
-
// 거리가 충분할 때만 새로운 도형을 생성
|
|
28
43
|
let newShape;
|
|
29
44
|
switch (this.currentShapeType) {
|
|
30
45
|
case 'rectangle':
|
|
@@ -81,13 +96,11 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
81
96
|
return;
|
|
82
97
|
}
|
|
83
98
|
this.selectedShape = newShape;
|
|
84
|
-
this.shapes = [...this.shapes, newShape];
|
|
99
|
+
this.shapes = [...this.shapes, newShape];
|
|
85
100
|
}
|
|
86
101
|
if (this.selectedShape) {
|
|
87
|
-
// 선택된 도형의 크기를 업데이트
|
|
88
102
|
switch (this.selectedShape.type) {
|
|
89
103
|
case 'rectangle':
|
|
90
|
-
// x, y를 업데이트하여 음의 방향으로 드래그할 때도 사각형이 올바르게 그려지도록 함
|
|
91
104
|
this.selectedShape.x = Math.min(x, this.startX);
|
|
92
105
|
this.selectedShape.y = Math.min(y, this.startY);
|
|
93
106
|
this.selectedShape.width = Math.abs(x - this.startX);
|
|
@@ -111,14 +124,20 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
111
124
|
this.selectedShape.y2 = y;
|
|
112
125
|
break;
|
|
113
126
|
}
|
|
114
|
-
this.requestUpdate();
|
|
127
|
+
this.requestUpdate();
|
|
115
128
|
}
|
|
116
129
|
};
|
|
117
|
-
this.
|
|
130
|
+
this.onDrawingMouseUp = (event) => {
|
|
118
131
|
this.drawing = false;
|
|
119
132
|
const svg = this.svg;
|
|
120
|
-
|
|
121
|
-
|
|
133
|
+
if ('touches' in event) {
|
|
134
|
+
svg.removeEventListener('touchmove', this.onDrawingMouseMove);
|
|
135
|
+
svg.removeEventListener('touchend', this.onDrawingMouseUp);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
svg.removeEventListener('mousemove', this.onDrawingMouseMove);
|
|
139
|
+
svg.removeEventListener('mouseup', this.onDrawingMouseUp);
|
|
140
|
+
}
|
|
122
141
|
if (this.selectedShape) {
|
|
123
142
|
this.selectedShape.selected = true;
|
|
124
143
|
}
|
|
@@ -129,7 +148,6 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
129
148
|
return;
|
|
130
149
|
}
|
|
131
150
|
const { x, y } = this.getSvgPoint(event);
|
|
132
|
-
// 마우스 이동에 따라 도형의 위치 업데이트
|
|
133
151
|
switch (this.selectedShape.type) {
|
|
134
152
|
case 'text':
|
|
135
153
|
case 'rectangle':
|
|
@@ -159,26 +177,66 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
159
177
|
this.selectedShape.y2 = this.selectedShape.y2 + deltaY;
|
|
160
178
|
break;
|
|
161
179
|
}
|
|
162
|
-
this.requestUpdate();
|
|
180
|
+
this.requestUpdate();
|
|
163
181
|
};
|
|
164
|
-
this.onShapeMouseUp = () => {
|
|
182
|
+
this.onShapeMouseUp = (event) => {
|
|
165
183
|
const svg = this.svg;
|
|
166
|
-
|
|
167
|
-
|
|
184
|
+
if ('touches' in event) {
|
|
185
|
+
svg.removeEventListener('touchmove', this.onShapeMouseMove);
|
|
186
|
+
svg.removeEventListener('touchend', this.onShapeMouseUp);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
svg.removeEventListener('mousemove', this.onShapeMouseMove);
|
|
190
|
+
svg.removeEventListener('mouseup', this.onShapeMouseUp);
|
|
191
|
+
}
|
|
168
192
|
};
|
|
169
193
|
this.onHandleMouseMove = (event) => {
|
|
170
|
-
if (!this.selectedShape)
|
|
194
|
+
if (!this.selectedShape) {
|
|
171
195
|
return;
|
|
196
|
+
}
|
|
172
197
|
const { x, y } = this.getSvgPoint(event);
|
|
173
198
|
switch (this.selectedShape.type) {
|
|
174
|
-
case 'text':
|
|
199
|
+
// case 'text':
|
|
200
|
+
// this.selectedShape.width = Math.abs(x - this.selectedShape.x!)
|
|
201
|
+
// this.selectedShape.height = Math.abs(y - this.selectedShape.y!)
|
|
202
|
+
// if (x < this.selectedShape.x!) this.selectedShape.x = x
|
|
203
|
+
// if (y < this.selectedShape.y!) this.selectedShape.y = y
|
|
204
|
+
// break
|
|
175
205
|
case 'rectangle':
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
206
|
+
const startX = this.selectedShape.x;
|
|
207
|
+
const startY = this.selectedShape.y;
|
|
208
|
+
const width = this.selectedShape.width;
|
|
209
|
+
const height = this.selectedShape.height;
|
|
210
|
+
// 핸들의 좌표에 따라 너비 및 높이 조정
|
|
211
|
+
if (this.offsetX === startX && this.offsetY === startY) {
|
|
212
|
+
// 상좌 핸들 (왼쪽 위)
|
|
179
213
|
this.selectedShape.x = x;
|
|
180
|
-
if (y < this.selectedShape.y)
|
|
181
214
|
this.selectedShape.y = y;
|
|
215
|
+
this.selectedShape.width = width + (startX - x);
|
|
216
|
+
this.selectedShape.height = height + (startY - y);
|
|
217
|
+
}
|
|
218
|
+
else if (this.offsetX === startX + width && this.offsetY === startY) {
|
|
219
|
+
// 상우 핸들 (오른쪽 위)
|
|
220
|
+
this.selectedShape.y = y;
|
|
221
|
+
this.selectedShape.width = Math.abs(x - startX);
|
|
222
|
+
this.selectedShape.height = height + (startY - y);
|
|
223
|
+
}
|
|
224
|
+
else if (this.offsetX === startX && this.offsetY === startY + height) {
|
|
225
|
+
// 하좌 핸들 (왼쪽 아래)
|
|
226
|
+
this.selectedShape.x = x;
|
|
227
|
+
this.selectedShape.width = width + (startX - x);
|
|
228
|
+
this.selectedShape.height = Math.abs(y - startY);
|
|
229
|
+
}
|
|
230
|
+
else if (this.offsetX === startX + width && this.offsetY === startY + height) {
|
|
231
|
+
// 하우 핸들 (오른쪽 아래)
|
|
232
|
+
this.selectedShape.width = Math.abs(x - startX);
|
|
233
|
+
this.selectedShape.height = Math.abs(y - startY);
|
|
234
|
+
}
|
|
235
|
+
// 최소 너비 및 높이 유지
|
|
236
|
+
this.selectedShape.width = Math.max(10, this.selectedShape.width);
|
|
237
|
+
this.selectedShape.height = Math.max(10, this.selectedShape.height);
|
|
238
|
+
this.offsetX = x;
|
|
239
|
+
this.offsetY = y;
|
|
182
240
|
break;
|
|
183
241
|
case 'circle':
|
|
184
242
|
const dx = x - this.selectedShape.cx;
|
|
@@ -211,12 +269,29 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
211
269
|
}
|
|
212
270
|
this.requestUpdate();
|
|
213
271
|
};
|
|
214
|
-
this.onHandleMouseUp = () => {
|
|
272
|
+
this.onHandleMouseUp = (event) => {
|
|
215
273
|
const svg = this.svg;
|
|
216
|
-
|
|
217
|
-
|
|
274
|
+
if ('touches' in event) {
|
|
275
|
+
svg.removeEventListener('touchmove', this.onHandleMouseMove);
|
|
276
|
+
svg.removeEventListener('touchend', this.onHandleMouseUp);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
svg.removeEventListener('mousemove', this.onHandleMouseMove);
|
|
280
|
+
svg.removeEventListener('mouseup', this.onHandleMouseUp);
|
|
281
|
+
}
|
|
218
282
|
this.selectedShape = null;
|
|
219
283
|
};
|
|
284
|
+
this.onFocus = () => {
|
|
285
|
+
this.addEventListener('keydown', this.handleKeyDown);
|
|
286
|
+
};
|
|
287
|
+
this.onBlur = () => {
|
|
288
|
+
this.removeEventListener('keydown', this.handleKeyDown);
|
|
289
|
+
};
|
|
290
|
+
this.handleKeyDown = (event) => {
|
|
291
|
+
if ((event.key === 'Delete' || event.key === 'Backspace') && this.selectedShape) {
|
|
292
|
+
this.deleteSelectedShape();
|
|
293
|
+
}
|
|
294
|
+
};
|
|
220
295
|
}
|
|
221
296
|
static { this.styles = [
|
|
222
297
|
...BaseImageMarker.styles,
|
|
@@ -247,13 +322,25 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
247
322
|
}
|
|
248
323
|
`
|
|
249
324
|
]; }
|
|
325
|
+
connectedCallback() {
|
|
326
|
+
super.connectedCallback();
|
|
327
|
+
this.setAttribute('tabindex', '0');
|
|
328
|
+
this.addEventListener('focus', this.onFocus);
|
|
329
|
+
this.addEventListener('blur', this.onBlur);
|
|
330
|
+
}
|
|
331
|
+
disconnectedCallback() {
|
|
332
|
+
super.disconnectedCallback();
|
|
333
|
+
this.removeEventListener('focus', this.onFocus);
|
|
334
|
+
this.removeEventListener('blur', this.onBlur);
|
|
335
|
+
}
|
|
250
336
|
render() {
|
|
251
337
|
return html `
|
|
252
338
|
<div class="container">
|
|
253
339
|
<svg
|
|
254
340
|
@mousedown=${this.onSvgMouseDown}
|
|
255
|
-
@
|
|
256
|
-
@
|
|
341
|
+
@touchstart=${this.onSvgTouchStart}
|
|
342
|
+
@wheel=${this.onSvgWheel}
|
|
343
|
+
@dblclick=${this.onSvgDoubleClick}
|
|
257
344
|
viewBox=${this.viewBox}
|
|
258
345
|
>
|
|
259
346
|
<image href=${this.imageUrl} @load=${this.onImageLoad}></image>
|
|
@@ -268,13 +355,8 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
268
355
|
const { mode, shapeType } = e.detail;
|
|
269
356
|
this.setMode(mode, shapeType);
|
|
270
357
|
}}
|
|
271
|
-
@
|
|
272
|
-
|
|
273
|
-
this.shapes = this.shapes.filter(s => s.id !== this.selectedShape.id);
|
|
274
|
-
this.requestUpdate();
|
|
275
|
-
}
|
|
276
|
-
}}
|
|
277
|
-
@clicked-save=${() => {
|
|
358
|
+
@tap-delete=${() => this.deleteSelectedShape()}
|
|
359
|
+
@tap-save=${() => {
|
|
278
360
|
this.dispatchEvent(new CustomEvent('shapes-changed', {
|
|
279
361
|
detail: this.shapes,
|
|
280
362
|
bubbles: true,
|
|
@@ -285,72 +367,56 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
285
367
|
`;
|
|
286
368
|
}
|
|
287
369
|
onSvgMouseDown(event) {
|
|
288
|
-
const svg = this.svg;
|
|
289
370
|
this.selectedShape = null;
|
|
290
371
|
this.shapes = this.shapes.map(s => {
|
|
291
372
|
return { ...s, selected: false };
|
|
292
373
|
});
|
|
293
|
-
this.selectedShape = null;
|
|
294
374
|
if (this.currentMode === 'move') {
|
|
295
|
-
this.
|
|
296
|
-
this.panStart = { x: event.clientX, y: event.clientY };
|
|
297
|
-
svg.classList.add('dragging');
|
|
298
|
-
svg.addEventListener('mousemove', this.onPanMouseMove);
|
|
299
|
-
svg.addEventListener('mouseup', this.onPanMouseUp);
|
|
300
|
-
}
|
|
301
|
-
else if (this.currentMode === 'select') {
|
|
302
|
-
this.startShapeDragging(event);
|
|
303
|
-
svg.addEventListener('mousemove', this.onShapeMouseMove);
|
|
304
|
-
svg.addEventListener('mouseup', this.onShapeMouseUp);
|
|
375
|
+
this.startPanning(event);
|
|
305
376
|
}
|
|
306
377
|
else if (this.currentMode === 'draw') {
|
|
307
378
|
this.startDrawing(event);
|
|
308
|
-
svg.addEventListener('mousemove', this.onMouseMove);
|
|
309
|
-
svg.addEventListener('mouseup', this.onMouseUp);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
startShapeDragging(event) {
|
|
313
|
-
const { x, y } = this.getSvgPoint(event);
|
|
314
|
-
if (this.selectedShape) {
|
|
315
|
-
this.offsetX = x - (this.selectedShape.x || this.selectedShape.cx || 0);
|
|
316
|
-
this.offsetY = y - (this.selectedShape.y || this.selectedShape.cy || 0);
|
|
317
379
|
}
|
|
318
380
|
}
|
|
319
381
|
startDrawing(event) {
|
|
320
382
|
const { x, y } = this.getSvgPoint(event);
|
|
321
|
-
// currentShapeType이 'text'일 경우에만 input을 보여 텍스트 입력을 받음
|
|
322
383
|
if (this.currentShapeType === 'text') {
|
|
323
384
|
const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
324
385
|
textElement.setAttribute('x', x.toString());
|
|
325
386
|
textElement.setAttribute('y', y.toString());
|
|
326
|
-
textElement.textContent = 'ABC';
|
|
387
|
+
textElement.textContent = 'ABC';
|
|
327
388
|
this.svg.appendChild(textElement);
|
|
328
|
-
// input 요소 생성
|
|
329
389
|
requestAnimationFrame(() => {
|
|
330
390
|
this.showTextInput(textElement, null, { x, y });
|
|
331
391
|
});
|
|
332
392
|
}
|
|
333
393
|
else {
|
|
334
|
-
// 'text'가 아닌 경우 기존 도형 그리기 동작 유지
|
|
335
394
|
this.startX = x;
|
|
336
395
|
this.startY = y;
|
|
337
396
|
this.drawing = true;
|
|
338
397
|
}
|
|
398
|
+
if ('touches' in event) {
|
|
399
|
+
this.svg.addEventListener('touchmove', this.onDrawingMouseMove);
|
|
400
|
+
this.svg.addEventListener('touchend', this.onDrawingMouseUp);
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
this.svg.addEventListener('mousemove', this.onDrawingMouseMove);
|
|
404
|
+
this.svg.addEventListener('mouseup', this.onDrawingMouseUp);
|
|
405
|
+
}
|
|
339
406
|
}
|
|
340
407
|
setMode(mode, shapeType) {
|
|
341
408
|
this.currentMode = mode;
|
|
342
409
|
this.currentShapeType = shapeType;
|
|
343
410
|
}
|
|
344
|
-
// SVG 좌표계로 마우스 좌표 변환
|
|
345
411
|
getSvgPoint(event) {
|
|
412
|
+
const { x, y } = this.getPointFromEvent(event);
|
|
346
413
|
const svg = this.svg;
|
|
347
414
|
const point = svg.createSVGPoint();
|
|
348
|
-
point.x =
|
|
349
|
-
point.y =
|
|
415
|
+
point.x = x;
|
|
416
|
+
point.y = y;
|
|
350
417
|
const ctm = svg.getScreenCTM().inverse();
|
|
351
418
|
return point.matrixTransform(ctm);
|
|
352
419
|
}
|
|
353
|
-
// 도형 렌더링 함수
|
|
354
420
|
renderShape(shape) {
|
|
355
421
|
const commonAttrs = {
|
|
356
422
|
stroke: shape.selected ? 'blue' : 'red',
|
|
@@ -367,6 +433,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
367
433
|
fill="${commonAttrs.stroke}"
|
|
368
434
|
style="cursor: ${commonAttrs.cursor}; font-size: 16px;"
|
|
369
435
|
@mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
436
|
+
@touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
370
437
|
@dblclick="${(e) => this.onTextDoubleClick(e, shape)}"
|
|
371
438
|
>${shape.text}</text>
|
|
372
439
|
`;
|
|
@@ -382,6 +449,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
382
449
|
fill="${commonAttrs.fill}"
|
|
383
450
|
style="cursor: ${commonAttrs.cursor}"
|
|
384
451
|
@mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
452
|
+
@touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
385
453
|
></rect>
|
|
386
454
|
${shape.selected ? this.renderResizeHandles(shape) : ''}
|
|
387
455
|
`;
|
|
@@ -396,6 +464,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
396
464
|
fill="${commonAttrs.fill}"
|
|
397
465
|
style="cursor: ${commonAttrs.cursor}"
|
|
398
466
|
@mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
467
|
+
@touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
399
468
|
></circle>
|
|
400
469
|
${shape.selected ? this.renderResizeHandles(shape) : ''}
|
|
401
470
|
`;
|
|
@@ -408,6 +477,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
408
477
|
fill="${commonAttrs.fill}"
|
|
409
478
|
style="cursor: ${commonAttrs.cursor}"
|
|
410
479
|
@mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
480
|
+
@touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
411
481
|
></polygon>
|
|
412
482
|
${shape.selected ? this.renderResizeHandles(shape) : ''}
|
|
413
483
|
`;
|
|
@@ -422,6 +492,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
422
492
|
stroke-width="${commonAttrs['stroke-width']}"
|
|
423
493
|
style="cursor: ${commonAttrs.cursor}"
|
|
424
494
|
@mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
495
|
+
@touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
425
496
|
></line>
|
|
426
497
|
${shape.selected ? this.renderResizeHandles(shape) : ''}
|
|
427
498
|
`;
|
|
@@ -442,6 +513,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
442
513
|
marker-end="url(#arrowhead-${shape.id})"
|
|
443
514
|
style="cursor: ${commonAttrs.cursor}"
|
|
444
515
|
@mousedown="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
516
|
+
@touchstart="${(e) => this.onShapeMouseDown(e, shape)}"
|
|
445
517
|
></line>
|
|
446
518
|
${shape.selected ? this.renderResizeHandles(shape) : ''}
|
|
447
519
|
`;
|
|
@@ -456,7 +528,6 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
456
528
|
selected: s.id === shape.id
|
|
457
529
|
}));
|
|
458
530
|
this.selectedShape = this.shapes.find(s => s.id == shape.id);
|
|
459
|
-
// 마우스 클릭 좌표와 도형의 현재 좌표 차이 계산
|
|
460
531
|
const { x, y } = this.getSvgPoint(event);
|
|
461
532
|
switch (shape.type) {
|
|
462
533
|
case 'text':
|
|
@@ -477,19 +548,15 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
477
548
|
this.offsetY = y - shape.y1;
|
|
478
549
|
break;
|
|
479
550
|
}
|
|
480
|
-
// 드래그 이벤트 리스너 추가
|
|
481
551
|
const svg = this.svg;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
552
|
+
if ('touches' in event) {
|
|
553
|
+
svg.addEventListener('touchmove', this.onShapeMouseMove);
|
|
554
|
+
svg.addEventListener('touchend', this.onShapeMouseUp);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
svg.addEventListener('mousemove', this.onShapeMouseMove);
|
|
558
|
+
svg.addEventListener('mouseup', this.onShapeMouseUp);
|
|
488
559
|
}
|
|
489
|
-
const [vbWidth, vbHeight] = this.viewBox.split(' ').map(Number);
|
|
490
|
-
const scaleX = vbWidth / this.svg.clientWidth;
|
|
491
|
-
const scaleY = vbHeight / this.svg.clientHeight;
|
|
492
|
-
return Math.max(scaleX, scaleY);
|
|
493
560
|
}
|
|
494
561
|
renderResizeHandles(shape) {
|
|
495
562
|
let handles = [];
|
|
@@ -539,6 +606,7 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
539
606
|
class="resize-handle"
|
|
540
607
|
style="cursor: ${handle.cursor};"
|
|
541
608
|
@mousedown="${(e) => this.onHandleMouseDown(e, shape, handle)}"
|
|
609
|
+
@touchstart="${(e) => this.onHandleMouseDown(e, shape, handle)}"
|
|
542
610
|
></rect>
|
|
543
611
|
</g>
|
|
544
612
|
`)}
|
|
@@ -550,8 +618,14 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
550
618
|
this.offsetX = handle.x;
|
|
551
619
|
this.offsetY = handle.y;
|
|
552
620
|
const svg = this.svg;
|
|
553
|
-
|
|
554
|
-
|
|
621
|
+
if ('touches' in event) {
|
|
622
|
+
svg.addEventListener('touchmove', this.onHandleMouseMove);
|
|
623
|
+
svg.addEventListener('touchend', this.onHandleMouseUp);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
svg.addEventListener('mousemove', this.onHandleMouseMove);
|
|
627
|
+
svg.addEventListener('mouseup', this.onHandleMouseUp);
|
|
628
|
+
}
|
|
555
629
|
}
|
|
556
630
|
onTextDoubleClick(event, shape) {
|
|
557
631
|
event.stopPropagation();
|
|
@@ -559,26 +633,20 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
559
633
|
this.showTextInput(textElement, shape);
|
|
560
634
|
}
|
|
561
635
|
showTextInput(textElement, shape = null, options) {
|
|
562
|
-
// input 요소 생성
|
|
563
636
|
const input = document.createElement('input');
|
|
564
637
|
input.style.position = 'absolute';
|
|
565
|
-
// SVG 텍스트의 CTM 얻기
|
|
566
638
|
const ctm = textElement.getScreenCTM() || { a: 1, e: 0, d: 1, f: 0 };
|
|
567
639
|
const bbox = textElement.getBBox();
|
|
568
|
-
// 텍스트의 화면 위치 계산
|
|
569
640
|
const screenX = bbox.x * ctm.a + ctm.e;
|
|
570
641
|
const screenY = bbox.y * ctm.d + ctm.f;
|
|
571
|
-
// input 위치 조정 (scale과 transform을 고려한 정확한 좌표로 변환)
|
|
572
642
|
input.style.left = `${screenX}px`;
|
|
573
643
|
input.style.top = `${screenY}px`;
|
|
574
|
-
// 텍스트 폰트 스타일을 가져와 input에 적용
|
|
575
644
|
const computedStyle = window.getComputedStyle(textElement);
|
|
576
645
|
const fontSize = parseFloat(computedStyle.fontSize) * ctm.a;
|
|
577
646
|
const fontFamily = computedStyle.fontFamily;
|
|
578
647
|
const fontWeight = computedStyle.fontWeight;
|
|
579
648
|
const textColor = computedStyle.fill || computedStyle.color;
|
|
580
649
|
const lineHeight = parseFloat(computedStyle.lineHeight) * ctm.d;
|
|
581
|
-
// input 스타일을 텍스트와 일치시킴
|
|
582
650
|
input.style.fontSize = `${fontSize}px`;
|
|
583
651
|
input.style.fontFamily = fontFamily;
|
|
584
652
|
input.style.fontWeight = fontWeight;
|
|
@@ -592,13 +660,10 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
592
660
|
if (shape) {
|
|
593
661
|
input.value = shape.text || '';
|
|
594
662
|
}
|
|
595
|
-
// SVG 텍스트 숨김
|
|
596
663
|
textElement.style.visibility = 'hidden';
|
|
597
|
-
// input 요소를 추가하고, 텍스트 길이에 따라 너비 조정
|
|
598
664
|
document.body.appendChild(input);
|
|
599
|
-
this.adjustInputWidth(input);
|
|
665
|
+
this.adjustInputWidth(input);
|
|
600
666
|
requestAnimationFrame(() => input.focus());
|
|
601
|
-
// 입력 중에 동적으로 input의 너비 조정
|
|
602
667
|
input.addEventListener('input', () => {
|
|
603
668
|
this.adjustInputWidth(input);
|
|
604
669
|
});
|
|
@@ -656,19 +721,24 @@ let OxImageMarker = class OxImageMarker extends BaseImageMarker {
|
|
|
656
721
|
});
|
|
657
722
|
});
|
|
658
723
|
}
|
|
659
|
-
// 텍스트 길이에 맞춰 input 박스의 너비를 조정하는 함수 (최소 길이 유지)
|
|
660
724
|
adjustInputWidth(input) {
|
|
661
|
-
const minWidth = 50;
|
|
725
|
+
const minWidth = 50;
|
|
662
726
|
const textWidth = this.measureTextWidth(input.value || input.placeholder, input);
|
|
663
|
-
input.style.width = `${Math.max(textWidth + 10, minWidth)}px`;
|
|
727
|
+
input.style.width = `${Math.max(textWidth + 10, minWidth)}px`;
|
|
664
728
|
}
|
|
665
|
-
// 텍스트 길이를 측정하는 함수
|
|
666
729
|
measureTextWidth(text, input) {
|
|
667
730
|
const canvas = document.createElement('canvas');
|
|
668
731
|
const context = canvas.getContext('2d');
|
|
669
732
|
context.font = window.getComputedStyle(input).font;
|
|
670
733
|
return context.measureText(text).width;
|
|
671
734
|
}
|
|
735
|
+
deleteSelectedShape() {
|
|
736
|
+
if (this.selectedShape) {
|
|
737
|
+
this.shapes = this.shapes.filter(s => s.id !== this.selectedShape.id);
|
|
738
|
+
this.selectedShape = null;
|
|
739
|
+
this.requestUpdate();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
672
742
|
};
|
|
673
743
|
__decorate([
|
|
674
744
|
state()
|