@8btc/whiteboard 0.0.17 → 0.0.19-beta.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/index.umd.js DELETED
@@ -1,3574 +0,0 @@
1
- (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react/jsx-runtime"), require("react"), require("konva"), require("mitt"), require("uuid"), require("@radix-ui/react-slot"), require("class-variance-authority"), require("clsx"), require("tailwind-merge"), require("lucide-react")) : typeof define === "function" && define.amd ? define(["exports", "react/jsx-runtime", "react", "konva", "mitt", "uuid", "@radix-ui/react-slot", "class-variance-authority", "clsx", "tailwind-merge", "lucide-react"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.konvaWhiteboard = {}, global.jsxRuntime, global.React, global.Konva, global.mitt, global.uuid, global.ReactSlot, global.ClassVarianceAuthority, global.clsx, global.tailwindMerge, global.LucideReact));
3
- })(this, (function(exports2, jsxRuntime, react, Konva, mitt, uuid, reactSlot, classVarianceAuthority, clsx, tailwindMerge, lucideReact) {
4
- "use strict";var __defProp = Object.defineProperty;
5
- var __typeError = (msg) => {
6
- throw TypeError(msg);
7
- };
8
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
10
- var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
11
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
12
- var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
13
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
14
- var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
15
-
16
- var _core, _stage, _viewport, _handleWheel, _handlePointerDown, _handlePointerMove, _handlePointerUp, _handleDragStart, _handleDragMove, _handleDragEnd, _CanvasStage_instances, setupEventListeners_fn, _core2, _transformer, _handleTransformStart, _handleTransform, _handleTransformEnd, _handleDragStart2, _handleDragMove2, _handleDragEnd2, _CanvasTransformer_instances, setupEventListeners_fn2, _toolTypeChangeHandler, _RectNode_instances, setupEventHandlers_fn, _ImageNode_instances, loadImage_fn, _toolTypeChangeHandler2, setupEventHandlers_fn2, syncImageMarkers_fn, syncImageMarkersToState_fn, _rect, _markerGroup, _circle, _text, _handleViewportChange, _handleNodesSelected, _ImageMarkerNode_instances, changeVisulStyle_fn, setupEventHandlers_fn3, _canvasStage, _mainLayer, _canvasTransformer, _draftNode, _container, _handleKeyDown, _CanvasCore_instances, setupKeyboardEvents_fn;
17
- var __vite_style__ = document.createElement("style");
18
- __vite_style__.textContent = `/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
19
- @layer properties {
20
- @supports (((-webkit-hyphens: none)) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
21
- *, :before, :after, ::backdrop {
22
- --tw-translate-x: 0;
23
- --tw-translate-y: 0;
24
- --tw-translate-z: 0;
25
- --tw-rotate-x: initial;
26
- --tw-rotate-y: initial;
27
- --tw-rotate-z: initial;
28
- --tw-skew-x: initial;
29
- --tw-skew-y: initial;
30
- --tw-border-style: solid;
31
- --tw-font-weight: initial;
32
- --tw-shadow: 0 0 #0000;
33
- --tw-shadow-color: initial;
34
- --tw-shadow-alpha: 100%;
35
- --tw-inset-shadow: 0 0 #0000;
36
- --tw-inset-shadow-color: initial;
37
- --tw-inset-shadow-alpha: 100%;
38
- --tw-ring-color: initial;
39
- --tw-ring-shadow: 0 0 #0000;
40
- --tw-inset-ring-color: initial;
41
- --tw-inset-ring-shadow: 0 0 #0000;
42
- --tw-ring-inset: initial;
43
- --tw-ring-offset-width: 0px;
44
- --tw-ring-offset-color: #fff;
45
- --tw-ring-offset-shadow: 0 0 #0000;
46
- --tw-outline-style: solid;
47
- --tw-animation-delay: 0s;
48
- --tw-animation-direction: normal;
49
- --tw-animation-duration: initial;
50
- --tw-animation-fill-mode: none;
51
- --tw-animation-iteration-count: 1;
52
- --tw-enter-blur: 0;
53
- --tw-enter-opacity: 1;
54
- --tw-enter-rotate: 0;
55
- --tw-enter-scale: 1;
56
- --tw-enter-translate-x: 0;
57
- --tw-enter-translate-y: 0;
58
- --tw-exit-blur: 0;
59
- --tw-exit-opacity: 1;
60
- --tw-exit-rotate: 0;
61
- --tw-exit-scale: 1;
62
- --tw-exit-translate-x: 0;
63
- --tw-exit-translate-y: 0;
64
- }
65
- }
66
- }
67
-
68
- @layer theme {
69
- :root, :host {
70
- --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
71
- --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
72
- --color-red-500: oklch(63.7% .237 25.331);
73
- --color-red-600: oklch(57.7% .245 27.325);
74
- --color-orange-500: oklch(70.5% .213 47.604);
75
- --color-orange-600: oklch(64.6% .222 41.116);
76
- --color-green-500: oklch(72.3% .219 149.579);
77
- --color-green-600: oklch(62.7% .194 149.214);
78
- --color-cyan-500: oklch(71.5% .143 215.221);
79
- --color-cyan-600: oklch(60.9% .126 221.723);
80
- --color-cyan-700: oklch(52% .105 223.128);
81
- --color-blue-500: oklch(62.3% .214 259.815);
82
- --color-blue-600: oklch(54.6% .245 262.881);
83
- --color-indigo-500: oklch(58.5% .233 277.117);
84
- --color-indigo-600: oklch(51.1% .262 276.966);
85
- --color-purple-500: oklch(62.7% .265 303.9);
86
- --color-purple-600: oklch(55.8% .288 302.321);
87
- --color-gray-50: oklch(98.5% .002 247.839);
88
- --color-gray-100: oklch(96.7% .003 264.542);
89
- --color-gray-200: oklch(92.8% .006 264.531);
90
- --color-gray-400: oklch(70.7% .022 261.325);
91
- --color-gray-600: oklch(44.6% .03 256.802);
92
- --color-gray-700: oklch(37.3% .034 259.733);
93
- --color-gray-800: oklch(27.8% .033 256.848);
94
- --color-white: #fff;
95
- --spacing: .25rem;
96
- --container-xs: 20rem;
97
- --text-xs: .75rem;
98
- --text-xs--line-height: calc(1 / .75);
99
- --text-sm: .875rem;
100
- --text-sm--line-height: calc(1.25 / .875);
101
- --font-weight-medium: 500;
102
- --font-weight-bold: 700;
103
- --default-transition-duration: .15s;
104
- --default-transition-timing-function: cubic-bezier(.4, 0, .2, 1);
105
- --default-font-family: var(--font-sans);
106
- --default-mono-font-family: var(--font-mono);
107
- }
108
- }
109
-
110
- @layer base {
111
- *, :after, :before, ::backdrop {
112
- box-sizing: border-box;
113
- border: 0 solid;
114
- margin: 0;
115
- padding: 0;
116
- }
117
-
118
- ::file-selector-button {
119
- box-sizing: border-box;
120
- border: 0 solid;
121
- margin: 0;
122
- padding: 0;
123
- }
124
-
125
- html, :host {
126
- -webkit-text-size-adjust: 100%;
127
- tab-size: 4;
128
- line-height: 1.5;
129
- font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
130
- font-feature-settings: var(--default-font-feature-settings, normal);
131
- font-variation-settings: var(--default-font-variation-settings, normal);
132
- -webkit-tap-highlight-color: transparent;
133
- }
134
-
135
- hr {
136
- height: 0;
137
- color: inherit;
138
- border-top-width: 1px;
139
- }
140
-
141
- abbr:where([title]) {
142
- -webkit-text-decoration: underline dotted;
143
- text-decoration: underline dotted;
144
- }
145
-
146
- h1, h2, h3, h4, h5, h6 {
147
- font-size: inherit;
148
- font-weight: inherit;
149
- }
150
-
151
- a {
152
- color: inherit;
153
- -webkit-text-decoration: inherit;
154
- -webkit-text-decoration: inherit;
155
- -webkit-text-decoration: inherit;
156
- text-decoration: inherit;
157
- }
158
-
159
- b, strong {
160
- font-weight: bolder;
161
- }
162
-
163
- code, kbd, samp, pre {
164
- font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
165
- font-feature-settings: var(--default-mono-font-feature-settings, normal);
166
- font-variation-settings: var(--default-mono-font-variation-settings, normal);
167
- font-size: 1em;
168
- }
169
-
170
- small {
171
- font-size: 80%;
172
- }
173
-
174
- sub, sup {
175
- vertical-align: baseline;
176
- font-size: 75%;
177
- line-height: 0;
178
- position: relative;
179
- }
180
-
181
- sub {
182
- bottom: -.25em;
183
- }
184
-
185
- sup {
186
- top: -.5em;
187
- }
188
-
189
- table {
190
- text-indent: 0;
191
- border-color: inherit;
192
- border-collapse: collapse;
193
- }
194
-
195
- :-moz-focusring {
196
- outline: auto;
197
- }
198
-
199
- progress {
200
- vertical-align: baseline;
201
- }
202
-
203
- summary {
204
- display: list-item;
205
- }
206
-
207
- ol, ul, menu {
208
- list-style: none;
209
- }
210
-
211
- img, svg, video, canvas, audio, iframe, embed, object {
212
- vertical-align: middle;
213
- display: block;
214
- }
215
-
216
- img, video {
217
- max-width: 100%;
218
- height: auto;
219
- }
220
-
221
- button, input, select, optgroup, textarea {
222
- font: inherit;
223
- font-feature-settings: inherit;
224
- font-variation-settings: inherit;
225
- letter-spacing: inherit;
226
- color: inherit;
227
- opacity: 1;
228
- background-color: #0000;
229
- border-radius: 0;
230
- }
231
-
232
- ::file-selector-button {
233
- font: inherit;
234
- font-feature-settings: inherit;
235
- font-variation-settings: inherit;
236
- letter-spacing: inherit;
237
- color: inherit;
238
- opacity: 1;
239
- background-color: #0000;
240
- border-radius: 0;
241
- }
242
-
243
- :where(select:is([multiple], [size])) optgroup {
244
- font-weight: bolder;
245
- }
246
-
247
- :where(select:is([multiple], [size])) optgroup option {
248
- padding-inline-start: 20px;
249
- }
250
-
251
- ::file-selector-button {
252
- margin-inline-end: 4px;
253
- }
254
-
255
- ::placeholder {
256
- opacity: 1;
257
- }
258
-
259
- @supports (not ((-webkit-appearance: -apple-pay-button))) or (contain-intrinsic-size: 1px) {
260
- ::placeholder {
261
- color: currentColor;
262
- }
263
-
264
- @supports (color: color-mix(in lab, red, red)) {
265
- ::placeholder {
266
- color: color-mix(in oklab, currentcolor 50%, transparent);
267
- }
268
- }
269
- }
270
-
271
- textarea {
272
- resize: vertical;
273
- }
274
-
275
- ::-webkit-search-decoration {
276
- -webkit-appearance: none;
277
- }
278
-
279
- ::-webkit-date-and-time-value {
280
- min-height: 1lh;
281
- text-align: inherit;
282
- }
283
-
284
- ::-webkit-datetime-edit {
285
- display: inline-flex;
286
- }
287
-
288
- ::-webkit-datetime-edit-fields-wrapper {
289
- padding: 0;
290
- }
291
-
292
- ::-webkit-datetime-edit {
293
- padding-block: 0;
294
- }
295
-
296
- ::-webkit-datetime-edit-year-field {
297
- padding-block: 0;
298
- }
299
-
300
- ::-webkit-datetime-edit-month-field {
301
- padding-block: 0;
302
- }
303
-
304
- ::-webkit-datetime-edit-day-field {
305
- padding-block: 0;
306
- }
307
-
308
- ::-webkit-datetime-edit-hour-field {
309
- padding-block: 0;
310
- }
311
-
312
- ::-webkit-datetime-edit-minute-field {
313
- padding-block: 0;
314
- }
315
-
316
- ::-webkit-datetime-edit-second-field {
317
- padding-block: 0;
318
- }
319
-
320
- ::-webkit-datetime-edit-millisecond-field {
321
- padding-block: 0;
322
- }
323
-
324
- ::-webkit-datetime-edit-meridiem-field {
325
- padding-block: 0;
326
- }
327
-
328
- ::-webkit-calendar-picker-indicator {
329
- line-height: 1;
330
- }
331
-
332
- :-moz-ui-invalid {
333
- box-shadow: none;
334
- }
335
-
336
- button, input:where([type="button"], [type="reset"], [type="submit"]) {
337
- appearance: button;
338
- }
339
-
340
- ::file-selector-button {
341
- appearance: button;
342
- }
343
-
344
- ::-webkit-inner-spin-button {
345
- height: auto;
346
- }
347
-
348
- ::-webkit-outer-spin-button {
349
- height: auto;
350
- }
351
-
352
- [hidden]:where(:not([hidden="until-found"])) {
353
- display: none !important;
354
- }
355
-
356
- * {
357
- border-color: var(--border);
358
- outline-color: var(--ring);
359
- }
360
-
361
- @supports (color: color-mix(in lab, red, red)) {
362
- * {
363
- outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
364
- }
365
- }
366
-
367
- body {
368
- background-color: var(--background);
369
- color: var(--foreground);
370
- }
371
- }
372
-
373
- @layer components;
374
-
375
- @layer utilities {
376
- .pointer-events-auto {
377
- pointer-events: auto;
378
- }
379
-
380
- .visible {
381
- visibility: visible;
382
- }
383
-
384
- .absolute {
385
- position: absolute;
386
- }
387
-
388
- .relative {
389
- position: relative;
390
- }
391
-
392
- .top-0 {
393
- top: calc(var(--spacing) * 0);
394
- }
395
-
396
- .top-4 {
397
- top: calc(var(--spacing) * 4);
398
- }
399
-
400
- .right-4 {
401
- right: calc(var(--spacing) * 4);
402
- }
403
-
404
- .bottom-4 {
405
- bottom: calc(var(--spacing) * 4);
406
- }
407
-
408
- .left-0 {
409
- left: calc(var(--spacing) * 0);
410
- }
411
-
412
- .left-4 {
413
- left: calc(var(--spacing) * 4);
414
- }
415
-
416
- .z-10 {
417
- z-index: 10;
418
- }
419
-
420
- .z-20 {
421
- z-index: 20;
422
- }
423
-
424
- .z-50 {
425
- z-index: 50;
426
- }
427
-
428
- .container {
429
- width: 100%;
430
- }
431
-
432
- @media (min-width: 40rem) {
433
- .container {
434
- max-width: 40rem;
435
- }
436
- }
437
-
438
- @media (min-width: 48rem) {
439
- .container {
440
- max-width: 48rem;
441
- }
442
- }
443
-
444
- @media (min-width: 64rem) {
445
- .container {
446
- max-width: 64rem;
447
- }
448
- }
449
-
450
- @media (min-width: 80rem) {
451
- .container {
452
- max-width: 80rem;
453
- }
454
- }
455
-
456
- @media (min-width: 96rem) {
457
- .container {
458
- max-width: 96rem;
459
- }
460
- }
461
-
462
- .mt-2 {
463
- margin-top: calc(var(--spacing) * 2);
464
- }
465
-
466
- .mt-4 {
467
- margin-top: calc(var(--spacing) * 4);
468
- }
469
-
470
- .mb-2 {
471
- margin-bottom: calc(var(--spacing) * 2);
472
- }
473
-
474
- .mb-3 {
475
- margin-bottom: calc(var(--spacing) * 3);
476
- }
477
-
478
- .mb-4 {
479
- margin-bottom: calc(var(--spacing) * 4);
480
- }
481
-
482
- .mb-6 {
483
- margin-bottom: calc(var(--spacing) * 6);
484
- }
485
-
486
- .flex {
487
- display: flex;
488
- }
489
-
490
- .grid {
491
- display: grid;
492
- }
493
-
494
- .inline-flex {
495
- display: inline-flex;
496
- }
497
-
498
- .size-8 {
499
- width: calc(var(--spacing) * 8);
500
- height: calc(var(--spacing) * 8);
501
- }
502
-
503
- .size-9 {
504
- width: calc(var(--spacing) * 9);
505
- height: calc(var(--spacing) * 9);
506
- }
507
-
508
- .size-10 {
509
- width: calc(var(--spacing) * 10);
510
- height: calc(var(--spacing) * 10);
511
- }
512
-
513
- .size-full {
514
- width: 100%;
515
- height: 100%;
516
- }
517
-
518
- .h-8 {
519
- height: calc(var(--spacing) * 8);
520
- }
521
-
522
- .h-9 {
523
- height: calc(var(--spacing) * 9);
524
- }
525
-
526
- .h-10 {
527
- height: calc(var(--spacing) * 10);
528
- }
529
-
530
- .h-\\[500px\\] {
531
- height: 500px;
532
- }
533
-
534
- .h-full {
535
- height: 100%;
536
- }
537
-
538
- .max-h-40 {
539
- max-height: calc(var(--spacing) * 40);
540
- }
541
-
542
- .w-\\[700px\\] {
543
- width: 700px;
544
- }
545
-
546
- .w-full {
547
- width: 100%;
548
- }
549
-
550
- .max-w-xs {
551
- max-width: var(--container-xs);
552
- }
553
-
554
- .min-w-16 {
555
- min-width: calc(var(--spacing) * 16);
556
- }
557
-
558
- .shrink-0 {
559
- flex-shrink: 0;
560
- }
561
-
562
- .-translate-x-1\\/2 {
563
- --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
564
- translate: var(--tw-translate-x) var(--tw-translate-y);
565
- }
566
-
567
- .transform {
568
- transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );
569
- }
570
-
571
- .touch-none {
572
- touch-action: none;
573
- }
574
-
575
- .grid-cols-2 {
576
- grid-template-columns: repeat(2, minmax(0, 1fr));
577
- }
578
-
579
- .flex-col {
580
- flex-direction: column;
581
- }
582
-
583
- .items-center {
584
- align-items: center;
585
- }
586
-
587
- .justify-center {
588
- justify-content: center;
589
- }
590
-
591
- .gap-1\\.5 {
592
- gap: calc(var(--spacing) * 1.5);
593
- }
594
-
595
- .gap-2 {
596
- gap: calc(var(--spacing) * 2);
597
- }
598
-
599
- .overflow-auto {
600
- overflow: auto;
601
- }
602
-
603
- .rounded {
604
- border-radius: .25rem;
605
- }
606
-
607
- .rounded-lg {
608
- border-radius: var(--radius);
609
- }
610
-
611
- .rounded-md {
612
- border-radius: calc(var(--radius) - 2px);
613
- }
614
-
615
- .border {
616
- border-style: var(--tw-border-style);
617
- border-width: 1px;
618
- }
619
-
620
- .border-t {
621
- border-top-style: var(--tw-border-style);
622
- border-top-width: 1px;
623
- }
624
-
625
- .bg-background {
626
- background-color: var(--background);
627
- }
628
-
629
- .bg-blue-500 {
630
- background-color: var(--color-blue-500);
631
- }
632
-
633
- .bg-cyan-500 {
634
- background-color: var(--color-cyan-500);
635
- }
636
-
637
- .bg-cyan-600 {
638
- background-color: var(--color-cyan-600);
639
- }
640
-
641
- .bg-destructive {
642
- background-color: var(--destructive);
643
- }
644
-
645
- .bg-gray-50 {
646
- background-color: var(--color-gray-50);
647
- }
648
-
649
- .bg-gray-100 {
650
- background-color: var(--color-gray-100);
651
- }
652
-
653
- .bg-green-500 {
654
- background-color: var(--color-green-500);
655
- }
656
-
657
- .bg-indigo-500 {
658
- background-color: var(--color-indigo-500);
659
- }
660
-
661
- .bg-orange-500 {
662
- background-color: var(--color-orange-500);
663
- }
664
-
665
- .bg-primary {
666
- background-color: var(--primary);
667
- }
668
-
669
- .bg-purple-500 {
670
- background-color: var(--color-purple-500);
671
- }
672
-
673
- .bg-red-500 {
674
- background-color: var(--color-red-500);
675
- }
676
-
677
- .bg-secondary {
678
- background-color: var(--secondary);
679
- }
680
-
681
- .bg-white {
682
- background-color: var(--color-white);
683
- }
684
-
685
- .p-2 {
686
- padding: calc(var(--spacing) * 2);
687
- }
688
-
689
- .p-4 {
690
- padding: calc(var(--spacing) * 4);
691
- }
692
-
693
- .px-3 {
694
- padding-inline: calc(var(--spacing) * 3);
695
- }
696
-
697
- .px-4 {
698
- padding-inline: calc(var(--spacing) * 4);
699
- }
700
-
701
- .px-6 {
702
- padding-inline: calc(var(--spacing) * 6);
703
- }
704
-
705
- .py-2 {
706
- padding-block: calc(var(--spacing) * 2);
707
- }
708
-
709
- .pt-4 {
710
- padding-top: calc(var(--spacing) * 4);
711
- }
712
-
713
- .text-sm {
714
- font-size: var(--text-sm);
715
- line-height: var(--tw-leading, var(--text-sm--line-height));
716
- }
717
-
718
- .text-xs {
719
- font-size: var(--text-xs);
720
- line-height: var(--tw-leading, var(--text-xs--line-height));
721
- }
722
-
723
- .font-bold {
724
- --tw-font-weight: var(--font-weight-bold);
725
- font-weight: var(--font-weight-bold);
726
- }
727
-
728
- .font-medium {
729
- --tw-font-weight: var(--font-weight-medium);
730
- font-weight: var(--font-weight-medium);
731
- }
732
-
733
- .whitespace-nowrap {
734
- white-space: nowrap;
735
- }
736
-
737
- .whitespace-pre-wrap {
738
- white-space: pre-wrap;
739
- }
740
-
741
- .text-gray-400 {
742
- color: var(--color-gray-400);
743
- }
744
-
745
- .text-gray-600 {
746
- color: var(--color-gray-600);
747
- }
748
-
749
- .text-gray-700 {
750
- color: var(--color-gray-700);
751
- }
752
-
753
- .text-gray-800 {
754
- color: var(--color-gray-800);
755
- }
756
-
757
- .text-primary {
758
- color: var(--primary);
759
- }
760
-
761
- .text-primary-foreground {
762
- color: var(--primary-foreground);
763
- }
764
-
765
- .text-secondary-foreground {
766
- color: var(--secondary-foreground);
767
- }
768
-
769
- .text-white {
770
- color: var(--color-white);
771
- }
772
-
773
- .underline-offset-4 {
774
- text-underline-offset: 4px;
775
- }
776
-
777
- .shadow-lg {
778
- --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, #0000001a), 0 4px 6px -4px var(--tw-shadow-color, #0000001a);
779
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
780
- }
781
-
782
- .shadow-xs {
783
- --tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, #0000000d);
784
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
785
- }
786
-
787
- .outline {
788
- outline-style: var(--tw-outline-style);
789
- outline-width: 1px;
790
- }
791
-
792
- .transition-all {
793
- transition-property: all;
794
- transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
795
- transition-duration: var(--tw-duration, var(--default-transition-duration));
796
- }
797
-
798
- .outline-none {
799
- --tw-outline-style: none;
800
- outline-style: none;
801
- }
802
-
803
- @media (hover: hover) {
804
- .hover\\:bg-accent:hover {
805
- background-color: var(--accent);
806
- }
807
-
808
- .hover\\:bg-blue-600:hover {
809
- background-color: var(--color-blue-600);
810
- }
811
-
812
- .hover\\:bg-cyan-600:hover {
813
- background-color: var(--color-cyan-600);
814
- }
815
-
816
- .hover\\:bg-cyan-700:hover {
817
- background-color: var(--color-cyan-700);
818
- }
819
-
820
- .hover\\:bg-destructive\\/90:hover {
821
- background-color: var(--destructive);
822
- }
823
-
824
- @supports (color: color-mix(in lab, red, red)) {
825
- .hover\\:bg-destructive\\/90:hover {
826
- background-color: color-mix(in oklab, var(--destructive) 90%, transparent);
827
- }
828
- }
829
-
830
- .hover\\:bg-gray-200:hover {
831
- background-color: var(--color-gray-200);
832
- }
833
-
834
- .hover\\:bg-green-600:hover {
835
- background-color: var(--color-green-600);
836
- }
837
-
838
- .hover\\:bg-indigo-600:hover {
839
- background-color: var(--color-indigo-600);
840
- }
841
-
842
- .hover\\:bg-orange-600:hover {
843
- background-color: var(--color-orange-600);
844
- }
845
-
846
- .hover\\:bg-primary\\/90:hover {
847
- background-color: var(--primary);
848
- }
849
-
850
- @supports (color: color-mix(in lab, red, red)) {
851
- .hover\\:bg-primary\\/90:hover {
852
- background-color: color-mix(in oklab, var(--primary) 90%, transparent);
853
- }
854
- }
855
-
856
- .hover\\:bg-purple-600:hover {
857
- background-color: var(--color-purple-600);
858
- }
859
-
860
- .hover\\:bg-red-600:hover {
861
- background-color: var(--color-red-600);
862
- }
863
-
864
- .hover\\:bg-secondary\\/80:hover {
865
- background-color: var(--secondary);
866
- }
867
-
868
- @supports (color: color-mix(in lab, red, red)) {
869
- .hover\\:bg-secondary\\/80:hover {
870
- background-color: color-mix(in oklab, var(--secondary) 80%, transparent);
871
- }
872
- }
873
-
874
- .hover\\:text-accent-foreground:hover {
875
- color: var(--accent-foreground);
876
- }
877
-
878
- .hover\\:underline:hover {
879
- text-decoration-line: underline;
880
- }
881
- }
882
-
883
- .focus-visible\\:border-ring:focus-visible {
884
- border-color: var(--ring);
885
- }
886
-
887
- .focus-visible\\:ring-\\[3px\\]:focus-visible {
888
- --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
889
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
890
- }
891
-
892
- .focus-visible\\:ring-destructive\\/20:focus-visible {
893
- --tw-ring-color: var(--destructive);
894
- }
895
-
896
- @supports (color: color-mix(in lab, red, red)) {
897
- .focus-visible\\:ring-destructive\\/20:focus-visible {
898
- --tw-ring-color: color-mix(in oklab, var(--destructive) 20%, transparent);
899
- }
900
- }
901
-
902
- .focus-visible\\:ring-ring\\/50:focus-visible {
903
- --tw-ring-color: var(--ring);
904
- }
905
-
906
- @supports (color: color-mix(in lab, red, red)) {
907
- .focus-visible\\:ring-ring\\/50:focus-visible {
908
- --tw-ring-color: color-mix(in oklab, var(--ring) 50%, transparent);
909
- }
910
- }
911
-
912
- .disabled\\:pointer-events-none:disabled {
913
- pointer-events: none;
914
- }
915
-
916
- .disabled\\:cursor-not-allowed:disabled {
917
- cursor: not-allowed;
918
- }
919
-
920
- .disabled\\:opacity-50:disabled {
921
- opacity: .5;
922
- }
923
-
924
- .has-\\[\\>svg\\]\\:px-2\\.5:has( > svg) {
925
- padding-inline: calc(var(--spacing) * 2.5);
926
- }
927
-
928
- .has-\\[\\>svg\\]\\:px-3:has( > svg) {
929
- padding-inline: calc(var(--spacing) * 3);
930
- }
931
-
932
- .has-\\[\\>svg\\]\\:px-4:has( > svg) {
933
- padding-inline: calc(var(--spacing) * 4);
934
- }
935
-
936
- .aria-invalid\\:border-destructive[aria-invalid="true"] {
937
- border-color: var(--destructive);
938
- }
939
-
940
- .aria-invalid\\:ring-destructive\\/20[aria-invalid="true"] {
941
- --tw-ring-color: var(--destructive);
942
- }
943
-
944
- @supports (color: color-mix(in lab, red, red)) {
945
- .aria-invalid\\:ring-destructive\\/20[aria-invalid="true"] {
946
- --tw-ring-color: color-mix(in oklab, var(--destructive) 20%, transparent);
947
- }
948
- }
949
-
950
- .dark\\:border-input:is(.dark *) {
951
- border-color: var(--input);
952
- }
953
-
954
- .dark\\:bg-destructive\\/60:is(.dark *) {
955
- background-color: var(--destructive);
956
- }
957
-
958
- @supports (color: color-mix(in lab, red, red)) {
959
- .dark\\:bg-destructive\\/60:is(.dark *) {
960
- background-color: color-mix(in oklab, var(--destructive) 60%, transparent);
961
- }
962
- }
963
-
964
- .dark\\:bg-input\\/30:is(.dark *) {
965
- background-color: var(--input);
966
- }
967
-
968
- @supports (color: color-mix(in lab, red, red)) {
969
- .dark\\:bg-input\\/30:is(.dark *) {
970
- background-color: color-mix(in oklab, var(--input) 30%, transparent);
971
- }
972
- }
973
-
974
- @media (hover: hover) {
975
- .dark\\:hover\\:bg-accent\\/50:is(.dark *):hover {
976
- background-color: var(--accent);
977
- }
978
-
979
- @supports (color: color-mix(in lab, red, red)) {
980
- .dark\\:hover\\:bg-accent\\/50:is(.dark *):hover {
981
- background-color: color-mix(in oklab, var(--accent) 50%, transparent);
982
- }
983
- }
984
-
985
- .dark\\:hover\\:bg-input\\/50:is(.dark *):hover {
986
- background-color: var(--input);
987
- }
988
-
989
- @supports (color: color-mix(in lab, red, red)) {
990
- .dark\\:hover\\:bg-input\\/50:is(.dark *):hover {
991
- background-color: color-mix(in oklab, var(--input) 50%, transparent);
992
- }
993
- }
994
- }
995
-
996
- .dark\\:focus-visible\\:ring-destructive\\/40:is(.dark *):focus-visible {
997
- --tw-ring-color: var(--destructive);
998
- }
999
-
1000
- @supports (color: color-mix(in lab, red, red)) {
1001
- .dark\\:focus-visible\\:ring-destructive\\/40:is(.dark *):focus-visible {
1002
- --tw-ring-color: color-mix(in oklab, var(--destructive) 40%, transparent);
1003
- }
1004
- }
1005
-
1006
- .dark\\:aria-invalid\\:ring-destructive\\/40:is(.dark *)[aria-invalid="true"] {
1007
- --tw-ring-color: var(--destructive);
1008
- }
1009
-
1010
- @supports (color: color-mix(in lab, red, red)) {
1011
- .dark\\:aria-invalid\\:ring-destructive\\/40:is(.dark *)[aria-invalid="true"] {
1012
- --tw-ring-color: color-mix(in oklab, var(--destructive) 40%, transparent);
1013
- }
1014
- }
1015
-
1016
- .\\[\\&_svg\\]\\:pointer-events-none svg {
1017
- pointer-events: none;
1018
- }
1019
-
1020
- .\\[\\&_svg\\]\\:shrink-0 svg {
1021
- flex-shrink: 0;
1022
- }
1023
-
1024
- .\\[\\&_svg\\:not\\(\\[class\\*\\=\\'size-\\'\\]\\)\\]\\:size-4 svg:not([class*="size-"]) {
1025
- width: calc(var(--spacing) * 4);
1026
- height: calc(var(--spacing) * 4);
1027
- }
1028
- }
1029
-
1030
- @property --tw-animation-delay {
1031
- syntax: "*";
1032
- inherits: false;
1033
- initial-value: 0s;
1034
- }
1035
-
1036
- @property --tw-animation-direction {
1037
- syntax: "*";
1038
- inherits: false;
1039
- initial-value: normal;
1040
- }
1041
-
1042
- @property --tw-animation-duration {
1043
- syntax: "*";
1044
- inherits: false
1045
- }
1046
-
1047
- @property --tw-animation-fill-mode {
1048
- syntax: "*";
1049
- inherits: false;
1050
- initial-value: none;
1051
- }
1052
-
1053
- @property --tw-animation-iteration-count {
1054
- syntax: "*";
1055
- inherits: false;
1056
- initial-value: 1;
1057
- }
1058
-
1059
- @property --tw-enter-blur {
1060
- syntax: "*";
1061
- inherits: false;
1062
- initial-value: 0;
1063
- }
1064
-
1065
- @property --tw-enter-opacity {
1066
- syntax: "*";
1067
- inherits: false;
1068
- initial-value: 1;
1069
- }
1070
-
1071
- @property --tw-enter-rotate {
1072
- syntax: "*";
1073
- inherits: false;
1074
- initial-value: 0;
1075
- }
1076
-
1077
- @property --tw-enter-scale {
1078
- syntax: "*";
1079
- inherits: false;
1080
- initial-value: 1;
1081
- }
1082
-
1083
- @property --tw-enter-translate-x {
1084
- syntax: "*";
1085
- inherits: false;
1086
- initial-value: 0;
1087
- }
1088
-
1089
- @property --tw-enter-translate-y {
1090
- syntax: "*";
1091
- inherits: false;
1092
- initial-value: 0;
1093
- }
1094
-
1095
- @property --tw-exit-blur {
1096
- syntax: "*";
1097
- inherits: false;
1098
- initial-value: 0;
1099
- }
1100
-
1101
- @property --tw-exit-opacity {
1102
- syntax: "*";
1103
- inherits: false;
1104
- initial-value: 1;
1105
- }
1106
-
1107
- @property --tw-exit-rotate {
1108
- syntax: "*";
1109
- inherits: false;
1110
- initial-value: 0;
1111
- }
1112
-
1113
- @property --tw-exit-scale {
1114
- syntax: "*";
1115
- inherits: false;
1116
- initial-value: 1;
1117
- }
1118
-
1119
- @property --tw-exit-translate-x {
1120
- syntax: "*";
1121
- inherits: false;
1122
- initial-value: 0;
1123
- }
1124
-
1125
- @property --tw-exit-translate-y {
1126
- syntax: "*";
1127
- inherits: false;
1128
- initial-value: 0;
1129
- }
1130
-
1131
- :root {
1132
- --radius: .625rem;
1133
- --background: oklch(100% 0 0);
1134
- --foreground: oklch(14.5% 0 0);
1135
- --card: oklch(100% 0 0);
1136
- --card-foreground: oklch(14.5% 0 0);
1137
- --popover: oklch(100% 0 0);
1138
- --popover-foreground: oklch(14.5% 0 0);
1139
- --primary: oklch(20.5% 0 0);
1140
- --primary-foreground: oklch(98.5% 0 0);
1141
- --secondary: oklch(97% 0 0);
1142
- --secondary-foreground: oklch(20.5% 0 0);
1143
- --muted: oklch(97% 0 0);
1144
- --muted-foreground: oklch(55.6% 0 0);
1145
- --accent: oklch(97% 0 0);
1146
- --accent-foreground: oklch(20.5% 0 0);
1147
- --destructive: oklch(57.7% .245 27.325);
1148
- --border: oklch(92.2% 0 0);
1149
- --input: oklch(92.2% 0 0);
1150
- --ring: oklch(70.8% 0 0);
1151
- --chart-1: oklch(64.6% .222 41.116);
1152
- --chart-2: oklch(60% .118 184.704);
1153
- --chart-3: oklch(39.8% .07 227.392);
1154
- --chart-4: oklch(82.8% .189 84.429);
1155
- --chart-5: oklch(76.9% .188 70.08);
1156
- --sidebar: oklch(98.5% 0 0);
1157
- --sidebar-foreground: oklch(14.5% 0 0);
1158
- --sidebar-primary: oklch(20.5% 0 0);
1159
- --sidebar-primary-foreground: oklch(98.5% 0 0);
1160
- --sidebar-accent: oklch(97% 0 0);
1161
- --sidebar-accent-foreground: oklch(20.5% 0 0);
1162
- --sidebar-border: oklch(92.2% 0 0);
1163
- --sidebar-ring: oklch(70.8% 0 0);
1164
- }
1165
-
1166
- .dark {
1167
- --background: oklch(14.5% 0 0);
1168
- --foreground: oklch(98.5% 0 0);
1169
- --card: oklch(20.5% 0 0);
1170
- --card-foreground: oklch(98.5% 0 0);
1171
- --popover: oklch(20.5% 0 0);
1172
- --popover-foreground: oklch(98.5% 0 0);
1173
- --primary: oklch(92.2% 0 0);
1174
- --primary-foreground: oklch(20.5% 0 0);
1175
- --secondary: oklch(26.9% 0 0);
1176
- --secondary-foreground: oklch(98.5% 0 0);
1177
- --muted: oklch(26.9% 0 0);
1178
- --muted-foreground: oklch(70.8% 0 0);
1179
- --accent: oklch(26.9% 0 0);
1180
- --accent-foreground: oklch(98.5% 0 0);
1181
- --destructive: oklch(70.4% .191 22.216);
1182
- --border: oklch(100% 0 0 / .1);
1183
- --input: oklch(100% 0 0 / .15);
1184
- --ring: oklch(55.6% 0 0);
1185
- --chart-1: oklch(48.8% .243 264.376);
1186
- --chart-2: oklch(69.6% .17 162.48);
1187
- --chart-3: oklch(76.9% .188 70.08);
1188
- --chart-4: oklch(62.7% .265 303.9);
1189
- --chart-5: oklch(64.5% .246 16.439);
1190
- --sidebar: oklch(20.5% 0 0);
1191
- --sidebar-foreground: oklch(98.5% 0 0);
1192
- --sidebar-primary: oklch(48.8% .243 264.376);
1193
- --sidebar-primary-foreground: oklch(98.5% 0 0);
1194
- --sidebar-accent: oklch(26.9% 0 0);
1195
- --sidebar-accent-foreground: oklch(98.5% 0 0);
1196
- --sidebar-border: oklch(100% 0 0 / .1);
1197
- --sidebar-ring: oklch(55.6% 0 0);
1198
- }
1199
-
1200
- @property --tw-translate-x {
1201
- syntax: "*";
1202
- inherits: false;
1203
- initial-value: 0;
1204
- }
1205
-
1206
- @property --tw-translate-y {
1207
- syntax: "*";
1208
- inherits: false;
1209
- initial-value: 0;
1210
- }
1211
-
1212
- @property --tw-translate-z {
1213
- syntax: "*";
1214
- inherits: false;
1215
- initial-value: 0;
1216
- }
1217
-
1218
- @property --tw-rotate-x {
1219
- syntax: "*";
1220
- inherits: false
1221
- }
1222
-
1223
- @property --tw-rotate-y {
1224
- syntax: "*";
1225
- inherits: false
1226
- }
1227
-
1228
- @property --tw-rotate-z {
1229
- syntax: "*";
1230
- inherits: false
1231
- }
1232
-
1233
- @property --tw-skew-x {
1234
- syntax: "*";
1235
- inherits: false
1236
- }
1237
-
1238
- @property --tw-skew-y {
1239
- syntax: "*";
1240
- inherits: false
1241
- }
1242
-
1243
- @property --tw-border-style {
1244
- syntax: "*";
1245
- inherits: false;
1246
- initial-value: solid;
1247
- }
1248
-
1249
- @property --tw-font-weight {
1250
- syntax: "*";
1251
- inherits: false
1252
- }
1253
-
1254
- @property --tw-shadow {
1255
- syntax: "*";
1256
- inherits: false;
1257
- initial-value: 0 0 #0000;
1258
- }
1259
-
1260
- @property --tw-shadow-color {
1261
- syntax: "*";
1262
- inherits: false
1263
- }
1264
-
1265
- @property --tw-shadow-alpha {
1266
- syntax: "<percentage>";
1267
- inherits: false;
1268
- initial-value: 100%;
1269
- }
1270
-
1271
- @property --tw-inset-shadow {
1272
- syntax: "*";
1273
- inherits: false;
1274
- initial-value: 0 0 #0000;
1275
- }
1276
-
1277
- @property --tw-inset-shadow-color {
1278
- syntax: "*";
1279
- inherits: false
1280
- }
1281
-
1282
- @property --tw-inset-shadow-alpha {
1283
- syntax: "<percentage>";
1284
- inherits: false;
1285
- initial-value: 100%;
1286
- }
1287
-
1288
- @property --tw-ring-color {
1289
- syntax: "*";
1290
- inherits: false
1291
- }
1292
-
1293
- @property --tw-ring-shadow {
1294
- syntax: "*";
1295
- inherits: false;
1296
- initial-value: 0 0 #0000;
1297
- }
1298
-
1299
- @property --tw-inset-ring-color {
1300
- syntax: "*";
1301
- inherits: false
1302
- }
1303
-
1304
- @property --tw-inset-ring-shadow {
1305
- syntax: "*";
1306
- inherits: false;
1307
- initial-value: 0 0 #0000;
1308
- }
1309
-
1310
- @property --tw-ring-inset {
1311
- syntax: "*";
1312
- inherits: false
1313
- }
1314
-
1315
- @property --tw-ring-offset-width {
1316
- syntax: "<length>";
1317
- inherits: false;
1318
- initial-value: 0;
1319
- }
1320
-
1321
- @property --tw-ring-offset-color {
1322
- syntax: "*";
1323
- inherits: false;
1324
- initial-value: #fff;
1325
- }
1326
-
1327
- @property --tw-ring-offset-shadow {
1328
- syntax: "*";
1329
- inherits: false;
1330
- initial-value: 0 0 #0000;
1331
- }
1332
-
1333
- @property --tw-outline-style {
1334
- syntax: "*";
1335
- inherits: false;
1336
- initial-value: solid;
1337
- }
1338
- /*$vite$:1*/`;
1339
- document.head.appendChild(__vite_style__);
1340
- class CanvasStage {
1341
- constructor(core, config) {
1342
- __privateAdd(this, _CanvasStage_instances);
1343
- __privateAdd(this, _core);
1344
- __privateAdd(this, _stage);
1345
- __privateAdd(this, _viewport, { x: 0, y: 0, scale: 1 });
1346
- /**
1347
- * 处理滚轮缩放和平移
1348
- */
1349
- __privateAdd(this, _handleWheel, (e) => {
1350
- e.evt.preventDefault();
1351
- const stage = __privateGet(this, _stage);
1352
- const pointer = stage.getPointerPosition();
1353
- if (!pointer) return;
1354
- if (e.evt.ctrlKey) {
1355
- const oldScale = __privateGet(this, _viewport).scale;
1356
- const mousePointTo = {
1357
- x: (pointer.x - __privateGet(this, _viewport).x) / oldScale,
1358
- y: (pointer.y - __privateGet(this, _viewport).y) / oldScale
1359
- };
1360
- const scaleBy = 1.01;
1361
- const direction = e.evt.deltaY > 0 ? -1 : 1;
1362
- const steps = Math.min(Math.abs(e.evt.deltaY), 10);
1363
- let newScale = oldScale;
1364
- for (let i = 0; i < steps; i++) {
1365
- newScale = direction > 0 ? newScale * scaleBy : newScale / scaleBy;
1366
- }
1367
- const scale = Math.max(0.1, Math.min(5, newScale));
1368
- const newPos = {
1369
- x: pointer.x - mousePointTo.x * scale,
1370
- y: pointer.y - mousePointTo.y * scale
1371
- };
1372
- __privateGet(this, _core).updateViewport({ x: newPos.x, y: newPos.y, scale });
1373
- } else {
1374
- const deltaX = e.evt.shiftKey ? e.evt.deltaY : e.evt.deltaX;
1375
- const deltaY = e.evt.shiftKey ? 0 : e.evt.deltaY;
1376
- __privateGet(this, _core).updateViewport({
1377
- x: __privateGet(this, _viewport).x - deltaX,
1378
- y: __privateGet(this, _viewport).y - deltaY
1379
- });
1380
- }
1381
- });
1382
- __privateAdd(this, _handlePointerDown, (event) => {
1383
- const toolType = __privateGet(this, _core).getState().toolType;
1384
- if (event.evt.button !== 0 || toolType === "hand") {
1385
- return;
1386
- }
1387
- const clickedOnEmpty = event.target === __privateGet(this, _stage);
1388
- const pointerPos = __privateGet(this, _stage).getRelativePointerPosition();
1389
- if (toolType === "select" && !clickedOnEmpty) {
1390
- const nodeId = event.target.id();
1391
- if (nodeId) {
1392
- __privateGet(this, _core).selectNode(nodeId, event.evt.shiftKey);
1393
- }
1394
- return;
1395
- }
1396
- if (toolType === "rectangle" && pointerPos) {
1397
- __privateGet(this, _core).createDraftNode(toolType, pointerPos);
1398
- }
1399
- if (toolType === "image-marker" && pointerPos) {
1400
- const imageShape = __privateGet(this, _core).findImageAtPosition(pointerPos);
1401
- if (imageShape) {
1402
- const width = imageShape.width();
1403
- const height = imageShape.height();
1404
- if (width && height) {
1405
- const imageBounds = {
1406
- x: imageShape.x(),
1407
- y: imageShape.y(),
1408
- width,
1409
- height
1410
- };
1411
- __privateGet(this, _core).createDraftNode(toolType, pointerPos, {
1412
- parent: imageShape.id(),
1413
- bounds: imageBounds,
1414
- startPosition: pointerPos
1415
- });
1416
- }
1417
- }
1418
- }
1419
- __privateGet(this, _core).selectNode();
1420
- });
1421
- __privateAdd(this, _handlePointerMove, () => {
1422
- const toolType = __privateGet(this, _core).getState().toolType;
1423
- if (toolType === "hand") {
1424
- return;
1425
- }
1426
- const pointerPos = __privateGet(this, _stage).getRelativePointerPosition();
1427
- if ((toolType === "rectangle" || toolType === "image-marker") && pointerPos) {
1428
- __privateGet(this, _core).updateDraftNode(pointerPos);
1429
- }
1430
- });
1431
- __privateAdd(this, _handlePointerUp, () => {
1432
- const toolType = __privateGet(this, _core).getState().toolType;
1433
- if (toolType === "hand") {
1434
- return;
1435
- }
1436
- if (toolType === "rectangle" || toolType === "image-marker") {
1437
- __privateGet(this, _core).finalizeDraftNode();
1438
- }
1439
- });
1440
- __privateAdd(this, _handleDragStart, (event) => {
1441
- if (event.target !== __privateGet(this, _stage)) {
1442
- return;
1443
- }
1444
- const toolType = __privateGet(this, _core).getState().toolType;
1445
- if (toolType === "hand") {
1446
- this.setCursor("grabbing");
1447
- } else if (toolType === "select") {
1448
- this.setCursor("all-scroll");
1449
- }
1450
- });
1451
- __privateAdd(this, _handleDragMove, (event) => {
1452
- if (event.target !== __privateGet(this, _stage)) {
1453
- return;
1454
- }
1455
- __privateGet(this, _core).updateViewport({
1456
- x: __privateGet(this, _stage).x(),
1457
- y: __privateGet(this, _stage).y()
1458
- });
1459
- });
1460
- __privateAdd(this, _handleDragEnd, (event) => {
1461
- if (event.target !== __privateGet(this, _stage)) {
1462
- return;
1463
- }
1464
- __privateGet(this, _core).updateViewport({
1465
- x: __privateGet(this, _stage).x(),
1466
- y: __privateGet(this, _stage).y()
1467
- });
1468
- this.resetCursor();
1469
- });
1470
- __privateSet(this, _core, core);
1471
- __privateSet(this, _stage, new Konva.Stage({
1472
- container: config.container,
1473
- width: config.width,
1474
- height: config.height,
1475
- x: 0,
1476
- y: 0,
1477
- scaleX: 1,
1478
- scaleY: 1,
1479
- draggable: config.draggable ?? false,
1480
- className: config.className
1481
- }));
1482
- __privateMethod(this, _CanvasStage_instances, setupEventListeners_fn).call(this);
1483
- }
1484
- /**
1485
- * 获取原生 Konva.Stage 实例
1486
- */
1487
- getStage() {
1488
- return __privateGet(this, _stage);
1489
- }
1490
- /**
1491
- * 获取当前视口状态
1492
- */
1493
- getViewport() {
1494
- return { ...__privateGet(this, _viewport) };
1495
- }
1496
- /**
1497
- * 设置视口(包括位置、缩放和尺寸)
1498
- */
1499
- setViewport(viewport) {
1500
- const newViewport = { ...__privateGet(this, _viewport), ...viewport };
1501
- __privateSet(this, _viewport, newViewport);
1502
- if (viewport.x !== void 0) {
1503
- __privateGet(this, _stage).x(viewport.x);
1504
- }
1505
- if (viewport.y !== void 0) {
1506
- __privateGet(this, _stage).y(viewport.y);
1507
- }
1508
- if (viewport.scale !== void 0) {
1509
- __privateGet(this, _stage).scaleX(viewport.scale);
1510
- __privateGet(this, _stage).scaleY(viewport.scale);
1511
- }
1512
- if (viewport.width !== void 0) {
1513
- __privateGet(this, _stage).width(viewport.width);
1514
- }
1515
- if (viewport.height !== void 0) {
1516
- __privateGet(this, _stage).height(viewport.height);
1517
- }
1518
- }
1519
- /**
1520
- * 设置是否可拖拽
1521
- */
1522
- setDraggable(draggable) {
1523
- __privateGet(this, _stage).draggable(draggable);
1524
- }
1525
- /**
1526
- * 设置光标样式
1527
- */
1528
- setCursor(cursor) {
1529
- const container = __privateGet(this, _stage).container();
1530
- container.style.cursor = cursor;
1531
- }
1532
- /**
1533
- * 重置光标样式
1534
- */
1535
- resetCursor() {
1536
- const container = __privateGet(this, _stage).container();
1537
- const toolType = __privateGet(this, _core).getState().toolType;
1538
- if (toolType === "hand") {
1539
- container.style.cursor = "grab";
1540
- return;
1541
- }
1542
- container.style.cursor = "default";
1543
- }
1544
- /**
1545
- * 销毁 Stage
1546
- */
1547
- destroy() {
1548
- __privateGet(this, _stage).destroy();
1549
- }
1550
- }
1551
- _core = new WeakMap();
1552
- _stage = new WeakMap();
1553
- _viewport = new WeakMap();
1554
- _handleWheel = new WeakMap();
1555
- _handlePointerDown = new WeakMap();
1556
- _handlePointerMove = new WeakMap();
1557
- _handlePointerUp = new WeakMap();
1558
- _handleDragStart = new WeakMap();
1559
- _handleDragMove = new WeakMap();
1560
- _handleDragEnd = new WeakMap();
1561
- _CanvasStage_instances = new WeakSet();
1562
- /**
1563
- * 设置事件监听器
1564
- */
1565
- setupEventListeners_fn = function() {
1566
- __privateGet(this, _stage).on("wheel", __privateGet(this, _handleWheel));
1567
- __privateGet(this, _stage).on("pointerdown", __privateGet(this, _handlePointerDown));
1568
- __privateGet(this, _stage).on("pointermove", __privateGet(this, _handlePointerMove));
1569
- __privateGet(this, _stage).on("pointerup", __privateGet(this, _handlePointerUp));
1570
- __privateGet(this, _stage).on("dragstart", __privateGet(this, _handleDragStart));
1571
- __privateGet(this, _stage).on("dragmove", __privateGet(this, _handleDragMove));
1572
- __privateGet(this, _stage).on("dragend", __privateGet(this, _handleDragEnd));
1573
- };
1574
- class CanvasTransformer {
1575
- constructor(core, config) {
1576
- __privateAdd(this, _CanvasTransformer_instances);
1577
- __privateAdd(this, _core2);
1578
- __privateAdd(this, _transformer);
1579
- /**
1580
- * 处理 transformstart 事件
1581
- */
1582
- __privateAdd(this, _handleTransformStart, () => {
1583
- this.emitPositionChange();
1584
- });
1585
- /**
1586
- * 处理 transform 事件
1587
- */
1588
- __privateAdd(this, _handleTransform, () => {
1589
- this.emitPositionChange();
1590
- });
1591
- /**
1592
- * 处理 transformend 事件
1593
- */
1594
- __privateAdd(this, _handleTransformEnd, () => {
1595
- this.emitPositionChange();
1596
- });
1597
- /**
1598
- * 处理 dragstart 事件
1599
- */
1600
- __privateAdd(this, _handleDragStart2, () => {
1601
- this.emitPositionChange();
1602
- });
1603
- /**
1604
- * 处理 dragmove 事件
1605
- */
1606
- __privateAdd(this, _handleDragMove2, () => {
1607
- this.emitPositionChange();
1608
- });
1609
- /**
1610
- * 处理 dragend 事件
1611
- */
1612
- __privateAdd(this, _handleDragEnd2, () => {
1613
- this.emitPositionChange();
1614
- });
1615
- __privateSet(this, _core2, core);
1616
- __privateSet(this, _transformer, new Konva.Transformer({
1617
- rotateEnabled: config?.rotateEnabled ?? true,
1618
- ignoreStroke: config?.ignoreStroke ?? true,
1619
- anchorSize: config?.anchorSize ?? 8,
1620
- borderDash: config?.borderDash ?? [4, 4],
1621
- anchorCornerRadius: config?.anchorCornerRadius ?? 4,
1622
- padding: config?.padding ?? 6
1623
- }));
1624
- __privateMethod(this, _CanvasTransformer_instances, setupEventListeners_fn2).call(this);
1625
- }
1626
- /**
1627
- * 获取原生 Konva.Transformer 实例
1628
- */
1629
- getTransformer() {
1630
- return __privateGet(this, _transformer);
1631
- }
1632
- /**
1633
- * 获取 Transformer 的位置信息
1634
- */
1635
- getPosition() {
1636
- const nodes = __privateGet(this, _transformer).nodes();
1637
- if (nodes.length === 0) {
1638
- return null;
1639
- }
1640
- const box = __privateGet(this, _transformer).getClientRect();
1641
- return {
1642
- x: box.x,
1643
- y: box.y,
1644
- width: box.width,
1645
- height: box.height,
1646
- rotation: __privateGet(this, _transformer).rotation()
1647
- };
1648
- }
1649
- /**
1650
- * 设置要变换的节点
1651
- */
1652
- setNodes(nodes) {
1653
- if (nodes.length === 0) {
1654
- this.clearNodes();
1655
- return;
1656
- }
1657
- __privateGet(this, _transformer).nodes(nodes);
1658
- __privateGet(this, _transformer).moveToTop();
1659
- this.emitPositionChange();
1660
- }
1661
- /**
1662
- * 获取当前变换的节点
1663
- */
1664
- getNodes() {
1665
- return __privateGet(this, _transformer).nodes();
1666
- }
1667
- /**
1668
- * 清除所有节点
1669
- */
1670
- clearNodes() {
1671
- __privateGet(this, _transformer).nodes([]);
1672
- __privateGet(this, _transformer).moveToBottom();
1673
- this.emitPositionChange();
1674
- }
1675
- /**
1676
- * emit Transformer 位置
1677
- */
1678
- emitPositionChange() {
1679
- const position = this.getPosition();
1680
- __privateGet(this, _core2).emitEvent("transformer:positionChange", position);
1681
- }
1682
- /**
1683
- * 销毁 Transformer
1684
- */
1685
- destroy() {
1686
- __privateGet(this, _transformer).destroy();
1687
- }
1688
- }
1689
- _core2 = new WeakMap();
1690
- _transformer = new WeakMap();
1691
- _handleTransformStart = new WeakMap();
1692
- _handleTransform = new WeakMap();
1693
- _handleTransformEnd = new WeakMap();
1694
- _handleDragStart2 = new WeakMap();
1695
- _handleDragMove2 = new WeakMap();
1696
- _handleDragEnd2 = new WeakMap();
1697
- _CanvasTransformer_instances = new WeakSet();
1698
- /**
1699
- * 设置事件监听器
1700
- */
1701
- setupEventListeners_fn2 = function() {
1702
- __privateGet(this, _transformer).on("transformstart", __privateGet(this, _handleTransformStart));
1703
- __privateGet(this, _transformer).on("transform", __privateGet(this, _handleTransform));
1704
- __privateGet(this, _transformer).on("transformend", __privateGet(this, _handleTransformEnd));
1705
- __privateGet(this, _transformer).on("dragstart", __privateGet(this, _handleDragStart2));
1706
- __privateGet(this, _transformer).on("dragmove", __privateGet(this, _handleDragMove2));
1707
- __privateGet(this, _transformer).on("dragend", __privateGet(this, _handleDragEnd2));
1708
- };
1709
- class CanvasState {
1710
- constructor(initialState) {
1711
- __publicField(this, "_past", []);
1712
- __publicField(this, "_present");
1713
- __publicField(this, "_future", []);
1714
- __publicField(this, "_emitter");
1715
- this._present = initialState;
1716
- this._emitter = mitt();
1717
- }
1718
- /**
1719
- * 获取当前状态
1720
- */
1721
- getState() {
1722
- return { ...this._present };
1723
- }
1724
- /**
1725
- * 获取完整历史状态
1726
- */
1727
- getHistory() {
1728
- return {
1729
- past: [...this._past],
1730
- present: { ...this._present },
1731
- future: [...this._future]
1732
- };
1733
- }
1734
- /**
1735
- * 是否可以撤销
1736
- */
1737
- canUndo() {
1738
- return this._past.length > 0;
1739
- }
1740
- /**
1741
- * 是否可以重做
1742
- */
1743
- canRedo() {
1744
- return this._future.length > 0;
1745
- }
1746
- /**
1747
- * 订阅状态事件
1748
- */
1749
- on(event, handler) {
1750
- this._emitter.on(event, handler);
1751
- }
1752
- /**
1753
- * 取消订阅状态事件
1754
- */
1755
- off(event, handler) {
1756
- this._emitter.off(event, handler);
1757
- }
1758
- /**
1759
- * 发送事件
1760
- */
1761
- emit(event, data) {
1762
- this._emitter.emit(event, data);
1763
- }
1764
- /**
1765
- * 撤销操作
1766
- */
1767
- undo() {
1768
- if (this._past.length === 0) return;
1769
- const previous = this._past[this._past.length - 1];
1770
- const newPast = this._past.slice(0, this._past.length - 1);
1771
- this._past = newPast;
1772
- this._future = [this._present, ...this._future];
1773
- this._present = previous;
1774
- this._syncState(previous);
1775
- this._emitter.emit("state:undo", previous);
1776
- this._emitter.emit("state:change", previous);
1777
- }
1778
- /**
1779
- * 重做操作
1780
- */
1781
- redo() {
1782
- if (this._future.length === 0) return;
1783
- const next = this._future[0];
1784
- const newFuture = this._future.slice(1);
1785
- this._past = [...this._past, this._present];
1786
- this._future = newFuture;
1787
- this._present = next;
1788
- this._syncState(next);
1789
- this._emitter.emit("state:redo", next);
1790
- this._emitter.emit("state:change", next);
1791
- }
1792
- /**
1793
- * 重置历史记录
1794
- */
1795
- resetHistory() {
1796
- this._past = [];
1797
- this._future = [];
1798
- this._emitter.emit("state:reset", this._present);
1799
- this._emitter.emit("state:change", this._present);
1800
- }
1801
- /**
1802
- * 更新状态
1803
- * @param partial - 部分状态更新
1804
- * @param addToHistory - 是否添加到历史记录
1805
- */
1806
- _updateState(partial, addToHistory = true) {
1807
- const newState = { ...this._present, ...partial };
1808
- if (addToHistory) {
1809
- this._past = [...this._past, this._present];
1810
- this._future = [];
1811
- }
1812
- this._present = newState;
1813
- this._emitter.emit("state:change", newState);
1814
- }
1815
- /**
1816
- * 同步状态到外部系统(由子类实现)
1817
- * @param _state - 要同步的状态
1818
- */
1819
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1820
- _syncState(_state) {
1821
- }
1822
- /**
1823
- * 清理资源
1824
- */
1825
- _dispose() {
1826
- this._emitter.all.clear();
1827
- }
1828
- }
1829
- const NODE_NAMES = {
1830
- nodeRoot: "nodeRoot_intrinsic",
1831
- selectable: "selectable_intrinsic",
1832
- rect: "rect_intrinsic",
1833
- image: "image_intrinsic",
1834
- imageMarker: "image_marker_intrinsic"
1835
- };
1836
- const RECT = {
1837
- CORNER_RADIUS: 6,
1838
- MIN_SIZE: 10
1839
- };
1840
- const IMAGE = {
1841
- MIN_SIZE: 10
1842
- };
1843
- class BaseCanvasNode {
1844
- constructor(core, node, isDraft = false) {
1845
- __publicField(this, "core");
1846
- __publicField(this, "node");
1847
- __publicField(this, "element");
1848
- __publicField(this, "isDraft");
1849
- this.core = core;
1850
- this.node = node;
1851
- this.isDraft = isDraft;
1852
- this.element = this.createElement();
1853
- }
1854
- /**
1855
- * 获取 Konva 元素
1856
- */
1857
- getElement() {
1858
- return this.element;
1859
- }
1860
- /**
1861
- * 获取节点数据
1862
- */
1863
- getNode() {
1864
- return this.node;
1865
- }
1866
- }
1867
- function getRectSize(rect) {
1868
- return {
1869
- width: Math.max(RECT.MIN_SIZE, rect.width() * rect.scaleX()),
1870
- height: Math.max(RECT.MIN_SIZE, rect.height() * rect.scaleY())
1871
- };
1872
- }
1873
- class RectNode extends BaseCanvasNode {
1874
- constructor(core, node, isDraft = false) {
1875
- super(core, node, isDraft);
1876
- __privateAdd(this, _RectNode_instances);
1877
- __privateAdd(this, _toolTypeChangeHandler, (toolType) => {
1878
- const isSelect = toolType === "select";
1879
- this.element.listening(isSelect);
1880
- });
1881
- __privateMethod(this, _RectNode_instances, setupEventHandlers_fn).call(this, this.getElement());
1882
- }
1883
- createElement() {
1884
- const width = Math.max(
1885
- this.node.props.width ?? RECT.MIN_SIZE,
1886
- RECT.MIN_SIZE
1887
- );
1888
- const height = Math.max(
1889
- this.node.props.height ?? RECT.MIN_SIZE,
1890
- RECT.MIN_SIZE
1891
- );
1892
- const config = {
1893
- id: this.node.id,
1894
- ...this.node.props,
1895
- ...this.node.style,
1896
- width,
1897
- height,
1898
- cornerRadius: RECT.CORNER_RADIUS,
1899
- name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.selectable} ${NODE_NAMES.rect}`,
1900
- draggable: true,
1901
- stroke: "black",
1902
- strokeWidth: 2
1903
- };
1904
- const rect = new Konva.Rect(config);
1905
- rect.setAttrs({
1906
- width,
1907
- height
1908
- });
1909
- return rect;
1910
- }
1911
- /**
1912
- * 获取 Konva.Rect 实例
1913
- */
1914
- getElement() {
1915
- return this.element;
1916
- }
1917
- /**
1918
- * 更新节点数据
1919
- */
1920
- updateNode(node) {
1921
- this.node = {
1922
- ...this.node,
1923
- ...node,
1924
- props: {
1925
- ...this.node.props,
1926
- ...node.props
1927
- },
1928
- style: {
1929
- ...this.node.style,
1930
- ...node.style
1931
- }
1932
- };
1933
- const rect = this.getElement();
1934
- rect.x(this.node.props.x);
1935
- rect.y(this.node.props.y);
1936
- const width = Math.max(
1937
- this.node.props.width ?? RECT.MIN_SIZE,
1938
- RECT.MIN_SIZE
1939
- );
1940
- const height = Math.max(
1941
- this.node.props.height ?? RECT.MIN_SIZE,
1942
- RECT.MIN_SIZE
1943
- );
1944
- rect.width(width);
1945
- rect.height(height);
1946
- }
1947
- /**
1948
- * 销毁
1949
- */
1950
- destroy() {
1951
- this.core.off("toolType:change", __privateGet(this, _toolTypeChangeHandler));
1952
- this.element.destroy();
1953
- }
1954
- }
1955
- _toolTypeChangeHandler = new WeakMap();
1956
- _RectNode_instances = new WeakSet();
1957
- /**
1958
- * 设置事件处理器
1959
- */
1960
- setupEventHandlers_fn = function(rect) {
1961
- const element = rect;
1962
- this.core.on("toolType:change", __privateGet(this, _toolTypeChangeHandler));
1963
- element.on("transform", (event) => {
1964
- const rect2 = event.target;
1965
- const { width, height } = getRectSize(rect2);
1966
- rect2.scale({ x: 1, y: 1 });
1967
- rect2.width(width);
1968
- rect2.height(height);
1969
- });
1970
- element.on("transformend", (event) => {
1971
- const rect2 = event.target;
1972
- const { width, height } = getRectSize(rect2);
1973
- const newProps = {
1974
- ...this.node.props,
1975
- x: rect2.x(),
1976
- y: rect2.y(),
1977
- width,
1978
- height,
1979
- rotation: rect2.rotation()
1980
- };
1981
- this.node.props = newProps;
1982
- this.core._syncNodeFromElement(this.node.id, {
1983
- props: newProps
1984
- });
1985
- });
1986
- element.on("dragend", (event) => {
1987
- const rect2 = event.target;
1988
- const newProps = {
1989
- ...this.node.props,
1990
- x: rect2.x(),
1991
- y: rect2.y()
1992
- };
1993
- this.node.props = newProps;
1994
- this.core._syncNodeFromElement(this.node.id, {
1995
- props: newProps
1996
- });
1997
- });
1998
- };
1999
- class ImageNode extends BaseCanvasNode {
2000
- constructor(core, node, isDraft = false) {
2001
- super(core, node, isDraft);
2002
- __privateAdd(this, _ImageNode_instances);
2003
- __privateAdd(this, _toolTypeChangeHandler2, (toolType) => {
2004
- const isSelect = toolType === "select";
2005
- this.element.listening(isSelect);
2006
- });
2007
- __privateMethod(this, _ImageNode_instances, loadImage_fn).call(this);
2008
- __privateMethod(this, _ImageNode_instances, setupEventHandlers_fn2).call(this, this.getElement());
2009
- }
2010
- createElement() {
2011
- const width = this.node.props.width || IMAGE.MIN_SIZE;
2012
- const height = this.node.props.height || IMAGE.MIN_SIZE;
2013
- const placeholder = document.createElement("canvas");
2014
- placeholder.width = width;
2015
- placeholder.height = height;
2016
- const img = new Konva.Image({
2017
- id: this.node.id,
2018
- x: this.node.props.x,
2019
- y: this.node.props.y,
2020
- width,
2021
- height,
2022
- rotation: this.node.props.rotation || 0,
2023
- name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.selectable} ${NODE_NAMES.image}`,
2024
- draggable: true,
2025
- image: placeholder
2026
- });
2027
- return img;
2028
- }
2029
- /**
2030
- * 获取 Konva.Image 实例
2031
- */
2032
- getElement() {
2033
- return this.element;
2034
- }
2035
- /**
2036
- * 更新节点数据
2037
- */
2038
- updateNode(node) {
2039
- this.node = {
2040
- ...this.node,
2041
- ...node,
2042
- props: {
2043
- ...this.node.props,
2044
- ...node.props
2045
- },
2046
- style: {
2047
- ...this.node.style,
2048
- ...node.style
2049
- },
2050
- meta: {
2051
- ...this.node.meta,
2052
- ...node.meta
2053
- }
2054
- };
2055
- const img = this.getElement();
2056
- img.x(this.node.props.x);
2057
- img.y(this.node.props.y);
2058
- if (this.node.props.width && this.node.props.height) {
2059
- const width = Math.max(this.node.props.width, IMAGE.MIN_SIZE);
2060
- const height = Math.max(this.node.props.height, IMAGE.MIN_SIZE);
2061
- img.width(width);
2062
- img.height(height);
2063
- }
2064
- if (this.node.props.rotation !== void 0) {
2065
- img.rotation(this.node.props.rotation);
2066
- }
2067
- if (node.meta?.imageUrl && node.meta.imageUrl !== this.node.meta.imageUrl) {
2068
- __privateMethod(this, _ImageNode_instances, loadImage_fn).call(this);
2069
- }
2070
- }
2071
- /**
2072
- * 销毁
2073
- */
2074
- destroy() {
2075
- this.core.off("toolType:change", __privateGet(this, _toolTypeChangeHandler2));
2076
- this.element.destroy();
2077
- }
2078
- }
2079
- _ImageNode_instances = new WeakSet();
2080
- /**
2081
- * 加载图片
2082
- */
2083
- loadImage_fn = function() {
2084
- const imageUrl = this.node.meta.imageUrl;
2085
- if (!imageUrl) {
2086
- console.warn("Image URL is missing");
2087
- return;
2088
- }
2089
- const img = new window.Image();
2090
- img.crossOrigin = "anonymous";
2091
- img.src = imageUrl;
2092
- img.onload = () => {
2093
- this.getElement().image(img);
2094
- const width = this.node.props.width ?? img.width;
2095
- const height = this.node.props.height ?? img.height;
2096
- this.getElement().width(Math.max(width, IMAGE.MIN_SIZE));
2097
- this.getElement().height(Math.max(height, IMAGE.MIN_SIZE));
2098
- };
2099
- img.onerror = () => {
2100
- console.error("Failed to load image:", imageUrl);
2101
- };
2102
- };
2103
- _toolTypeChangeHandler2 = new WeakMap();
2104
- /**
2105
- * 设置事件处理器
2106
- */
2107
- setupEventHandlers_fn2 = function(img) {
2108
- this.core.on("toolType:change", __privateGet(this, _toolTypeChangeHandler2));
2109
- img.on("transform", (event) => {
2110
- const img2 = event.target;
2111
- const width = Math.max(IMAGE.MIN_SIZE, img2.width() * img2.scaleX());
2112
- const height = Math.max(IMAGE.MIN_SIZE, img2.height() * img2.scaleY());
2113
- img2.scale({ x: 1, y: 1 });
2114
- img2.width(width);
2115
- img2.height(height);
2116
- __privateMethod(this, _ImageNode_instances, syncImageMarkers_fn).call(this);
2117
- });
2118
- img.on("transformend", (event) => {
2119
- const img2 = event.target;
2120
- const newProps = {
2121
- ...this.node.props,
2122
- x: img2.x(),
2123
- y: img2.y(),
2124
- width: img2.width(),
2125
- height: img2.height(),
2126
- rotation: img2.rotation()
2127
- };
2128
- this.node.props = newProps;
2129
- this.core._syncNodeFromElement(this.node.id, {
2130
- props: newProps
2131
- });
2132
- __privateMethod(this, _ImageNode_instances, syncImageMarkersToState_fn).call(this);
2133
- });
2134
- img.on("dragmove", () => {
2135
- __privateMethod(this, _ImageNode_instances, syncImageMarkers_fn).call(this);
2136
- });
2137
- img.on("dragend", (event) => {
2138
- const img2 = event.target;
2139
- const newProps = {
2140
- ...this.node.props,
2141
- x: img2.x(),
2142
- y: img2.y()
2143
- };
2144
- this.node.props = newProps;
2145
- this.core._syncNodeFromElement(this.node.id, {
2146
- props: newProps
2147
- });
2148
- __privateMethod(this, _ImageNode_instances, syncImageMarkersToState_fn).call(this);
2149
- });
2150
- };
2151
- /**
2152
- * 同步 image-marker 节点的位置(实时更新 Konva 元素)
2153
- */
2154
- syncImageMarkers_fn = function() {
2155
- const img = this.getElement();
2156
- const layer = img.getLayer();
2157
- if (!layer) return;
2158
- const imgX = img.x();
2159
- const imgY = img.y();
2160
- const imgWidth = img.width();
2161
- const imgHeight = img.height();
2162
- const imageMarkerElements = layer.find(
2163
- (node) => node.hasName(this.node.id)
2164
- );
2165
- const nodes = this.core.getState().nodes || [];
2166
- imageMarkerElements.forEach((nodeElement) => {
2167
- const nodeData = nodes.find((n) => n.id === nodeElement.id());
2168
- if (nodeData?.type === "image-marker" && nodeData.meta.relativePosition) {
2169
- const { start, end } = nodeData.meta.relativePosition;
2170
- const startX = imgX + start.percentX / 100 * imgWidth;
2171
- const startY = imgY + start.percentY / 100 * imgHeight;
2172
- const endX = imgX + end.percentX / 100 * imgWidth;
2173
- const endY = imgY + end.percentY / 100 * imgHeight;
2174
- const newX = Math.min(startX, endX);
2175
- const newY = Math.min(startY, endY);
2176
- const newWidth = Math.abs(endX - startX);
2177
- const newHeight = Math.abs(endY - startY);
2178
- nodeElement.position({ x: newX, y: newY });
2179
- nodeElement.setAttrs({ width: newWidth, height: newHeight });
2180
- const children = nodeElement.getChildren();
2181
- children.forEach((child) => {
2182
- if (child.getClassName() === "Rect") {
2183
- child.setAttrs({ width: newWidth, height: newHeight });
2184
- } else if (child.getClassName() === "Group") {
2185
- child.setAttrs({ x: newWidth, y: newHeight });
2186
- }
2187
- });
2188
- }
2189
- });
2190
- };
2191
- /**
2192
- * 同步 image-marker 节点到状态
2193
- */
2194
- syncImageMarkersToState_fn = function() {
2195
- const img = this.getElement();
2196
- const layer = img.getLayer();
2197
- if (!layer) return;
2198
- const imageMarkerElements = layer.find(
2199
- (node) => node.hasName(this.node.id)
2200
- );
2201
- imageMarkerElements.forEach((nodeElement) => {
2202
- this.core._syncNodeFromElement(nodeElement.id(), {
2203
- props: {
2204
- x: nodeElement.x(),
2205
- y: nodeElement.y(),
2206
- width: nodeElement.width(),
2207
- height: nodeElement.height()
2208
- }
2209
- });
2210
- });
2211
- };
2212
- class ImageMarkerNode extends BaseCanvasNode {
2213
- constructor(core, node, isDraft = false) {
2214
- super(core, node, isDraft);
2215
- __privateAdd(this, _ImageMarkerNode_instances);
2216
- __privateAdd(this, _rect);
2217
- __privateAdd(this, _markerGroup);
2218
- __privateAdd(this, _circle);
2219
- __privateAdd(this, _text);
2220
- __privateAdd(this, _handleViewportChange);
2221
- __privateAdd(this, _handleNodesSelected);
2222
- const group = this.getElement();
2223
- __privateSet(this, _rect, group.findOne(".rect"));
2224
- __privateSet(this, _markerGroup, group.findOne(".marker-group"));
2225
- __privateSet(this, _circle, __privateGet(this, _markerGroup).findOne("Circle"));
2226
- __privateSet(this, _text, __privateGet(this, _markerGroup).findOne("Text"));
2227
- __privateMethod(this, _ImageMarkerNode_instances, setupEventHandlers_fn3).call(this);
2228
- __privateSet(this, _handleViewportChange, () => {
2229
- __privateMethod(this, _ImageMarkerNode_instances, changeVisulStyle_fn).call(this);
2230
- });
2231
- this.core.on("viewport:scale:change", __privateGet(this, _handleViewportChange));
2232
- __privateSet(this, _handleNodesSelected, (selectedIds) => {
2233
- const isSelected = selectedIds.includes(this.node.id);
2234
- this.setFocusState(isSelected);
2235
- });
2236
- this.core.on("nodes:selected", __privateGet(this, _handleNodesSelected));
2237
- }
2238
- createElement() {
2239
- const width = Math.max(
2240
- this.node.props.width ?? RECT.MIN_SIZE,
2241
- RECT.MIN_SIZE
2242
- );
2243
- const height = Math.max(
2244
- this.node.props.height ?? RECT.MIN_SIZE,
2245
- RECT.MIN_SIZE
2246
- );
2247
- const group = new Konva.Group({
2248
- id: this.node.id,
2249
- name: `${NODE_NAMES.nodeRoot} ${NODE_NAMES.imageMarker} ${this.node.meta.parent}`,
2250
- x: this.node.props.x,
2251
- y: this.node.props.y,
2252
- width,
2253
- height
2254
- });
2255
- const stageScale = this.core.getStageScale();
2256
- const rectStrokeWidth = 2 / stageScale;
2257
- const rectDash = [5 / stageScale, 5 / stageScale];
2258
- const rectCornerRadius = RECT.CORNER_RADIUS / stageScale;
2259
- const rect = new Konva.Rect({
2260
- name: "rect",
2261
- x: 0,
2262
- y: 0,
2263
- width,
2264
- height,
2265
- stroke: "#3B82F6",
2266
- strokeWidth: rectStrokeWidth,
2267
- dash: rectDash,
2268
- fill: "transparent",
2269
- cornerRadius: rectCornerRadius,
2270
- listening: false
2271
- });
2272
- const markerGroup = new Konva.Group({
2273
- name: "marker-group",
2274
- x: width,
2275
- y: height,
2276
- visible: this.isDraft ? false : true
2277
- });
2278
- const radius = 14 / stageScale;
2279
- const strokeWidth = 3 / stageScale;
2280
- const fontSize = 16 / stageScale;
2281
- const circle = new Konva.Circle({
2282
- radius,
2283
- fill: "#3B82F6",
2284
- stroke: "white",
2285
- strokeWidth
2286
- });
2287
- const text = new Konva.Text({
2288
- x: -radius,
2289
- y: -radius,
2290
- width: radius * 2,
2291
- height: radius * 2,
2292
- text: String(this.node.meta.markerNumber || ""),
2293
- align: "center",
2294
- verticalAlign: "middle",
2295
- fontSize,
2296
- fill: "white"
2297
- });
2298
- markerGroup.add(circle);
2299
- markerGroup.add(text);
2300
- group.add(rect);
2301
- group.add(markerGroup);
2302
- return group;
2303
- }
2304
- /**
2305
- * 获取 Konva.Group 实例
2306
- */
2307
- getElement() {
2308
- return this.element;
2309
- }
2310
- /**
2311
- * 更新节点数据
2312
- */
2313
- updateNode(node) {
2314
- this.node = {
2315
- ...this.node,
2316
- ...node,
2317
- props: {
2318
- ...this.node.props,
2319
- ...node.props
2320
- },
2321
- style: {
2322
- ...this.node.style,
2323
- ...node.style
2324
- },
2325
- meta: {
2326
- ...this.node.meta,
2327
- ...node.meta
2328
- }
2329
- };
2330
- const group = this.getElement();
2331
- group.x(this.node.props.x);
2332
- group.y(this.node.props.y);
2333
- const width = Math.max(
2334
- this.node.props.width ?? RECT.MIN_SIZE,
2335
- RECT.MIN_SIZE
2336
- );
2337
- const height = Math.max(
2338
- this.node.props.height ?? RECT.MIN_SIZE,
2339
- RECT.MIN_SIZE
2340
- );
2341
- group.width(width);
2342
- group.height(height);
2343
- __privateGet(this, _rect).width(width);
2344
- __privateGet(this, _rect).height(height);
2345
- __privateGet(this, _markerGroup).x(width);
2346
- __privateGet(this, _markerGroup).y(height);
2347
- if (node.style?.color) {
2348
- __privateGet(this, _rect).stroke(node.style.color);
2349
- }
2350
- if (node.meta?.markerNumber !== void 0) {
2351
- __privateGet(this, _text).text(String(node.meta.markerNumber));
2352
- }
2353
- }
2354
- /**
2355
- * 销毁
2356
- */
2357
- destroy() {
2358
- this.core.off("viewport:scale:change", __privateGet(this, _handleViewportChange));
2359
- this.core.off("nodes:selected", __privateGet(this, _handleNodesSelected));
2360
- this.element.destroy();
2361
- }
2362
- /**
2363
- * 更新焦点状态(hover 或 selected)
2364
- */
2365
- setFocusState(isFocus) {
2366
- const stageScale = this.core.getStageScale();
2367
- const strokeWidth = isFocus ? 4 / stageScale : 3 / stageScale;
2368
- const scale = isFocus ? 1.2 : 1;
2369
- __privateGet(this, _rect).strokeWidth(strokeWidth);
2370
- __privateGet(this, _circle).strokeWidth(strokeWidth);
2371
- __privateGet(this, _markerGroup).scaleX(scale);
2372
- __privateGet(this, _markerGroup).scaleY(scale);
2373
- }
2374
- }
2375
- _rect = new WeakMap();
2376
- _markerGroup = new WeakMap();
2377
- _circle = new WeakMap();
2378
- _text = new WeakMap();
2379
- _handleViewportChange = new WeakMap();
2380
- _handleNodesSelected = new WeakMap();
2381
- _ImageMarkerNode_instances = new WeakSet();
2382
- /**
2383
- * 更新标记点的缩放以保持视觉大小不变
2384
- */
2385
- changeVisulStyle_fn = function() {
2386
- const stageScale = this.core.getStageScale();
2387
- const radius = 14 / stageScale;
2388
- const strokeWidth = 3 / stageScale;
2389
- const fontSize = 16 / stageScale;
2390
- const rectStrokeWidth = 3 / stageScale;
2391
- const rectDash = [5 / stageScale, 5 / stageScale];
2392
- const rectCornerRadius = RECT.CORNER_RADIUS / stageScale;
2393
- __privateGet(this, _rect).strokeWidth(rectStrokeWidth);
2394
- __privateGet(this, _rect).dash(rectDash);
2395
- __privateGet(this, _rect).cornerRadius(rectCornerRadius);
2396
- __privateGet(this, _circle).radius(radius);
2397
- __privateGet(this, _circle).strokeWidth(strokeWidth);
2398
- __privateGet(this, _text).x(-radius);
2399
- __privateGet(this, _text).y(-radius);
2400
- __privateGet(this, _text).width(radius * 2);
2401
- __privateGet(this, _text).height(radius * 2);
2402
- __privateGet(this, _text).fontSize(fontSize);
2403
- };
2404
- /**
2405
- * 设置事件处理器
2406
- */
2407
- setupEventHandlers_fn3 = function() {
2408
- __privateGet(this, _markerGroup).on("pointerover", () => {
2409
- this.setFocusState(true);
2410
- this.core.setCursor("pointer");
2411
- });
2412
- __privateGet(this, _markerGroup).on("pointerout", () => {
2413
- const selectedIds = this.core.getState().selectedNodeIds || [];
2414
- const isSelected = selectedIds.includes(this.node.id);
2415
- this.setFocusState(isSelected);
2416
- this.core.resetCursor();
2417
- });
2418
- __privateGet(this, _markerGroup).on("pointerdown", () => {
2419
- this.core.selectNode(this.node.id);
2420
- });
2421
- __privateGet(this, _markerGroup).on("transform", () => {
2422
- console.log("image marker group on transform called");
2423
- });
2424
- };
2425
- function createCanvasNodeByType(core, type, config, isDraft = false) {
2426
- switch (type) {
2427
- case "rectangle":
2428
- return new RectNode(core, config, isDraft);
2429
- case "image":
2430
- return new ImageNode(core, config, isDraft);
2431
- case "image-marker":
2432
- return new ImageMarkerNode(core, config, isDraft);
2433
- default:
2434
- return null;
2435
- }
2436
- }
2437
- function clamp(value, range) {
2438
- return Math.min(Math.max(value, range[0]), range[1]);
2439
- }
2440
- const createNodeByType = (type, position, style, meta) => {
2441
- const baseNode = {
2442
- type,
2443
- id: uuid.v4(),
2444
- text: null,
2445
- style: {
2446
- opacity: 1,
2447
- line: "solid",
2448
- color: "black",
2449
- size: "medium",
2450
- animated: false
2451
- },
2452
- props: {
2453
- x: position.x,
2454
- y: position.y,
2455
- rotation: 0,
2456
- visible: true
2457
- },
2458
- meta: {
2459
- ...meta,
2460
- startPoint: position
2461
- }
2462
- };
2463
- if (type === "image-marker") {
2464
- return {
2465
- ...baseNode,
2466
- style: {
2467
- ...baseNode.style,
2468
- color: "#3B82F6"
2469
- }
2470
- };
2471
- }
2472
- return baseNode;
2473
- };
2474
- function updateNodeByType(node, position) {
2475
- let finalPosition = position;
2476
- if (node.type === "image-marker" && node.meta.bounds) {
2477
- const bounds = node.meta.bounds;
2478
- finalPosition = {
2479
- x: clamp(position.x, [bounds.x, bounds.x + bounds.width]),
2480
- y: clamp(position.y, [bounds.y, bounds.y + bounds.height])
2481
- };
2482
- }
2483
- if (node.type === "rectangle" || node.type === "image-marker") {
2484
- const startPoint = node.meta.startPoint ?? {
2485
- x: node.props.x,
2486
- y: node.props.y
2487
- };
2488
- const [p1, p2] = normalizePoints(startPoint, finalPosition);
2489
- return {
2490
- ...node,
2491
- props: {
2492
- ...node.props,
2493
- x: p1.x,
2494
- y: p1.y,
2495
- width: Math.max(p2.x - p1.x, RECT.MIN_SIZE),
2496
- height: Math.max(p2.y - p1.y, RECT.MIN_SIZE)
2497
- },
2498
- meta: {
2499
- ...node.meta,
2500
- // 保存初始起点,以便后续更新使用
2501
- startPoint
2502
- }
2503
- };
2504
- }
2505
- return node;
2506
- }
2507
- function normalizePoints(p1, p2) {
2508
- let p1x = p1.x, p1y = p1.y, p2x = p2.x, p2y = p2.y, d;
2509
- if (p1x > p2x) {
2510
- d = Math.abs(p1x - p2x);
2511
- p1x = p2x;
2512
- p2x = p1x + d;
2513
- }
2514
- if (p1y > p2y) {
2515
- d = Math.abs(p1y - p2y);
2516
- p1y = p2y;
2517
- p2y = p1y + d;
2518
- }
2519
- return [
2520
- { x: p1x, y: p1y },
2521
- { x: p2x, y: p2y }
2522
- ];
2523
- }
2524
- class CanvasCore extends CanvasState {
2525
- constructor(el) {
2526
- super({
2527
- viewport: {
2528
- x: 0,
2529
- y: 0,
2530
- width: el.clientWidth,
2531
- height: el.clientHeight,
2532
- scale: 1
2533
- },
2534
- toolType: "select",
2535
- nodes: []
2536
- });
2537
- __privateAdd(this, _CanvasCore_instances);
2538
- __privateAdd(this, _canvasStage);
2539
- __privateAdd(this, _mainLayer);
2540
- __privateAdd(this, _canvasTransformer);
2541
- __privateAdd(this, _draftNode, null);
2542
- __privateAdd(this, _container);
2543
- __privateAdd(this, _handleKeyDown, (e) => {
2544
- if (e.key === "Delete" || e.key === "Backspace") {
2545
- e.preventDefault();
2546
- this.deleteSelectedNodes();
2547
- }
2548
- });
2549
- __privateSet(this, _container, el);
2550
- __privateSet(this, _canvasStage, new CanvasStage(this, {
2551
- container: el,
2552
- width: el.clientWidth,
2553
- height: el.clientHeight,
2554
- draggable: false,
2555
- className: "touch-none"
2556
- }));
2557
- __privateSet(this, _mainLayer, new Konva.Layer());
2558
- __privateSet(this, _canvasTransformer, new CanvasTransformer(this));
2559
- __privateGet(this, _canvasStage).getStage().add(__privateGet(this, _mainLayer));
2560
- __privateGet(this, _mainLayer).add(__privateGet(this, _canvasTransformer).getTransformer());
2561
- this.updateViewport(this.getState().viewport, false);
2562
- __privateMethod(this, _CanvasCore_instances, setupKeyboardEvents_fn).call(this);
2563
- }
2564
- /**
2565
- * 获取 CanvasStage 实例
2566
- */
2567
- getCanvasStage() {
2568
- return __privateGet(this, _canvasStage);
2569
- }
2570
- /**
2571
- * 获取 CanvasTransformer 实例
2572
- */
2573
- getCanvasTransformer() {
2574
- return __privateGet(this, _canvasTransformer);
2575
- }
2576
- /**
2577
- * 发射事件(供内部类使用)
2578
- */
2579
- emitEvent(event, data) {
2580
- this.emit(event, data);
2581
- }
2582
- /**
2583
- * 获取 Konva.Stage 实例
2584
- */
2585
- getStage() {
2586
- return __privateGet(this, _canvasStage).getStage();
2587
- }
2588
- /**
2589
- * 获取 Stage 容器元素
2590
- */
2591
- getContainer() {
2592
- return __privateGet(this, _canvasStage).getStage().container();
2593
- }
2594
- /**
2595
- * 获取主图层
2596
- */
2597
- getMainLayer() {
2598
- return __privateGet(this, _mainLayer);
2599
- }
2600
- /**
2601
- * 获取当前工具类型
2602
- */
2603
- getToolType() {
2604
- return this.getState().toolType;
2605
- }
2606
- /**
2607
- * 设置当前工具类型(内部使用)
2608
- */
2609
- setToolType(type) {
2610
- this.selectNode();
2611
- this._updateState(
2612
- {
2613
- toolType: type
2614
- },
2615
- false
2616
- );
2617
- this.emit("toolType:change", type);
2618
- if (type === "hand") {
2619
- __privateGet(this, _canvasStage).setDraggable(true);
2620
- __privateGet(this, _canvasStage).setCursor("grab");
2621
- } else {
2622
- __privateGet(this, _canvasStage).setDraggable(false);
2623
- __privateGet(this, _canvasStage).resetCursor();
2624
- }
2625
- }
2626
- /**
2627
- * 设置是否可拖拽(内部使用)
2628
- */
2629
- setDraggable(draggable) {
2630
- __privateGet(this, _canvasStage).setDraggable(draggable);
2631
- }
2632
- /**
2633
- * 设置光标
2634
- * @internal 仅供内部使用
2635
- */
2636
- setCursor(cursor) {
2637
- __privateGet(this, _canvasStage).setCursor(cursor);
2638
- }
2639
- /**
2640
- * 重置光标
2641
- * @internal 仅供内部使用
2642
- */
2643
- resetCursor() {
2644
- __privateGet(this, _canvasStage).resetCursor();
2645
- }
2646
- /**
2647
- * 获取当前 Stage 缩放比例
2648
- */
2649
- getStageScale() {
2650
- return __privateGet(this, _canvasStage).getStage().scaleX();
2651
- }
2652
- /**
2653
- * 更新视口位置
2654
- * @internal 仅供内部使用,外部请使用 CanvasApi
2655
- */
2656
- updateViewport(viewport, addToHistory = false) {
2657
- __privateGet(this, _canvasStage).setViewport(viewport);
2658
- const oldViewport = this.getState().viewport;
2659
- const newViewport = {
2660
- ...oldViewport,
2661
- ...viewport
2662
- };
2663
- this._updateState(
2664
- {
2665
- viewport: newViewport
2666
- },
2667
- addToHistory
2668
- );
2669
- this.emit("viewport:change", newViewport);
2670
- if (oldViewport.scale !== newViewport.scale) {
2671
- this.emit("viewport:scale:change", newViewport.scale);
2672
- }
2673
- __privateGet(this, _canvasTransformer).emitPositionChange();
2674
- }
2675
- createNodes(nodes) {
2676
- const canvasNodes = nodes.map((node) => createCanvasNodeByType(this, node.type, node, false)).filter((node) => node !== null);
2677
- canvasNodes.forEach((node) => {
2678
- __privateGet(this, _mainLayer).add(node.getElement());
2679
- });
2680
- const newNodes = [...this.getState().nodes || [], ...nodes];
2681
- this._updateState(
2682
- {
2683
- nodes: newNodes
2684
- },
2685
- true
2686
- );
2687
- this.emit("nodes:created", nodes);
2688
- }
2689
- /**
2690
- * 创建图片标注节点(内部使用)
2691
- */
2692
- createImageMarkerNode(parentImageId, startPosition, endPosition, imageBounds) {
2693
- const nodes = this.getState().nodes || [];
2694
- let maxMarkerNumber = 0;
2695
- nodes.forEach((node) => {
2696
- if (node.type === "image-marker" && node.meta.parent === parentImageId && typeof node.meta.markerNumber === "number") {
2697
- maxMarkerNumber = Math.max(maxMarkerNumber, node.meta.markerNumber);
2698
- }
2699
- });
2700
- const startPercentX = (startPosition.x - imageBounds.x) / imageBounds.width * 100;
2701
- const startPercentY = (startPosition.y - imageBounds.y) / imageBounds.height * 100;
2702
- const endPercentX = (endPosition.x - imageBounds.x) / imageBounds.width * 100;
2703
- const endPercentY = (endPosition.y - imageBounds.y) / imageBounds.height * 100;
2704
- const x = Math.min(startPosition.x, endPosition.x);
2705
- const y = Math.min(startPosition.y, endPosition.y);
2706
- const width = Math.abs(endPosition.x - startPosition.x);
2707
- const height = Math.abs(endPosition.y - startPosition.y);
2708
- const markerNode = {
2709
- id: uuid.v4(),
2710
- type: "image-marker",
2711
- props: {
2712
- x,
2713
- y,
2714
- width,
2715
- height,
2716
- rotation: 0,
2717
- visible: true
2718
- },
2719
- style: {
2720
- color: "#3B82F6",
2721
- line: "dashed",
2722
- size: "medium",
2723
- opacity: 1
2724
- },
2725
- meta: {
2726
- parent: parentImageId,
2727
- markerNumber: maxMarkerNumber + 1,
2728
- relativePosition: {
2729
- start: {
2730
- percentX: Math.max(0, Math.min(100, startPercentX)),
2731
- percentY: Math.max(0, Math.min(100, startPercentY))
2732
- },
2733
- end: {
2734
- percentX: Math.max(0, Math.min(100, endPercentX)),
2735
- percentY: Math.max(0, Math.min(100, endPercentY))
2736
- }
2737
- }
2738
- }
2739
- };
2740
- this.createNodes([markerNode]);
2741
- }
2742
- /**
2743
- * 在指定位置查找图片节点
2744
- * @internal 仅供内部使用
2745
- */
2746
- findImageAtPosition(position) {
2747
- const layer = __privateGet(this, _mainLayer);
2748
- const shapes = layer.getChildren();
2749
- const filteredShapes = shapes.filter(
2750
- (shape) => !shape.name().includes("imageMarker")
2751
- );
2752
- const sortedShapes = filteredShapes.slice().sort((a, b) => b.zIndex() - a.zIndex());
2753
- for (const shape of sortedShapes) {
2754
- const x = shape.x();
2755
- const y = shape.y();
2756
- const width = shape.width();
2757
- const height = shape.height();
2758
- if (position.x >= x && position.x <= x + width && position.y >= y && position.y <= y + height) {
2759
- if (shape.getClassName() === "Image") {
2760
- return shape;
2761
- }
2762
- return null;
2763
- }
2764
- }
2765
- return null;
2766
- }
2767
- /**
2768
- * @internal 仅供内部使用
2769
- */
2770
- createDraftNode(type, position, meta) {
2771
- if (__privateGet(this, _draftNode)) {
2772
- __privateGet(this, _draftNode).destroy();
2773
- }
2774
- const node = createNodeByType(type, position, void 0, meta);
2775
- __privateSet(this, _draftNode, createCanvasNodeByType(this, type, node, true));
2776
- if (!__privateGet(this, _draftNode)) return;
2777
- __privateGet(this, _mainLayer).add(__privateGet(this, _draftNode).getElement());
2778
- }
2779
- /**
2780
- * @internal 仅供内部使用
2781
- */
2782
- updateDraftNode(position) {
2783
- if (!__privateGet(this, _draftNode)) return;
2784
- const node = __privateGet(this, _draftNode).getNode();
2785
- const updatedNode = updateNodeByType(node, position);
2786
- __privateGet(this, _draftNode).updateNode(updatedNode);
2787
- }
2788
- /**
2789
- * @internal 仅供内部使用
2790
- */
2791
- finalizeDraftNode() {
2792
- if (!__privateGet(this, _draftNode)) return;
2793
- const id = uuid.v4();
2794
- const draftNode = __privateGet(this, _draftNode).getNode();
2795
- if (draftNode.type === "image-marker" && draftNode.meta.parent) {
2796
- const bounds = draftNode.meta.bounds;
2797
- const startPosition = draftNode.meta.startPosition;
2798
- const endPosition = {
2799
- x: draftNode.props.x + (draftNode.props.width || 0),
2800
- y: draftNode.props.y + (draftNode.props.height || 0)
2801
- };
2802
- const nodes = this.getState().nodes || [];
2803
- let maxMarkerNumber = 0;
2804
- nodes.forEach((node3) => {
2805
- if (node3.type === "image-marker" && node3.meta.parent === draftNode.meta.parent && typeof node3.meta.markerNumber === "number") {
2806
- maxMarkerNumber = Math.max(maxMarkerNumber, node3.meta.markerNumber);
2807
- }
2808
- });
2809
- const startPercentX = (startPosition.x - bounds.x) / bounds.width * 100;
2810
- const startPercentY = (startPosition.y - bounds.y) / bounds.height * 100;
2811
- const endPercentX = (endPosition.x - bounds.x) / bounds.width * 100;
2812
- const endPercentY = (endPosition.y - bounds.y) / bounds.height * 100;
2813
- const node2 = {
2814
- ...draftNode,
2815
- props: {
2816
- ...draftNode.props
2817
- },
2818
- style: {
2819
- ...draftNode.style
2820
- },
2821
- meta: {
2822
- parent: draftNode.meta.parent,
2823
- markerNumber: maxMarkerNumber + 1,
2824
- relativePosition: {
2825
- start: {
2826
- percentX: Math.max(0, Math.min(100, startPercentX)),
2827
- percentY: Math.max(0, Math.min(100, startPercentY))
2828
- },
2829
- end: {
2830
- percentX: Math.max(0, Math.min(100, endPercentX)),
2831
- percentY: Math.max(0, Math.min(100, endPercentY))
2832
- }
2833
- }
2834
- },
2835
- id,
2836
- type: "image-marker"
2837
- };
2838
- this.createNodes([node2]);
2839
- __privateGet(this, _draftNode).destroy();
2840
- __privateSet(this, _draftNode, null);
2841
- this.setToolType("select");
2842
- return;
2843
- }
2844
- const node = {
2845
- ...draftNode,
2846
- props: {
2847
- ...draftNode.props
2848
- },
2849
- style: {
2850
- ...draftNode.style
2851
- },
2852
- meta: {
2853
- ...draftNode.meta
2854
- },
2855
- id
2856
- };
2857
- this.createNodes([node]);
2858
- __privateGet(this, _draftNode).destroy();
2859
- __privateSet(this, _draftNode, null);
2860
- this.setToolType("select");
2861
- }
2862
- /**
2863
- * 选择节点
2864
- * @internal 仅供内部使用,外部请使用 CanvasApi
2865
- */
2866
- selectNode(nodeId, multiSelect = false) {
2867
- const curSelectedNodeIds = this.getState().selectedNodeIds ?? [];
2868
- let selectedNodeIds = [];
2869
- if (nodeId) {
2870
- if (multiSelect && curSelectedNodeIds.length > 0) {
2871
- selectedNodeIds = [...curSelectedNodeIds, nodeId];
2872
- } else {
2873
- selectedNodeIds = [nodeId];
2874
- }
2875
- } else if (curSelectedNodeIds.length === 0) {
2876
- return;
2877
- }
2878
- if (selectedNodeIds.length === 0) {
2879
- __privateGet(this, _canvasTransformer).clearNodes();
2880
- } else {
2881
- const nodes = this.getStage().find(`.${NODE_NAMES.selectable}`).filter((node) => selectedNodeIds.includes(node.id()));
2882
- __privateGet(this, _canvasTransformer).setNodes(nodes);
2883
- }
2884
- this._updateState({ selectedNodeIds }, false);
2885
- this.emit("nodes:selected", selectedNodeIds);
2886
- }
2887
- /**
2888
- * 删除指定的节点(内部使用)
2889
- * 如果删除的是 image 节点,会同步删除所有关联的 image-marker
2890
- * @internal 仅供内部使用,外部请使用 CanvasApi
2891
- * @param nodeIds - 要删除的节点 ID 数组
2892
- * @returns 被删除的节点数据数组
2893
- */
2894
- deleteNodes(nodeIds) {
2895
- if (nodeIds.length === 0) return [];
2896
- const nodes = this.getState().nodes || [];
2897
- const idsToDelete = new Set(nodeIds);
2898
- nodeIds.forEach((id) => {
2899
- const node = nodes.find((n) => n.id === id);
2900
- if (node?.type === "image") {
2901
- nodes.forEach((n) => {
2902
- if (n.type === "image-marker" && n.meta.parent === id) {
2903
- idsToDelete.add(n.id);
2904
- }
2905
- });
2906
- }
2907
- });
2908
- const deletedNodes = nodes.filter((n) => idsToDelete.has(n.id));
2909
- idsToDelete.forEach((id) => {
2910
- const shape = this.getStage().findOne(`#${id}`);
2911
- if (shape) {
2912
- shape.destroy();
2913
- }
2914
- });
2915
- const newNodes = nodes.filter((n) => !idsToDelete.has(n.id));
2916
- __privateGet(this, _canvasTransformer).clearNodes();
2917
- this._updateState(
2918
- {
2919
- nodes: newNodes,
2920
- selectedNodeIds: []
2921
- },
2922
- true
2923
- );
2924
- this.emit("nodes:deleted", deletedNodes);
2925
- return deletedNodes;
2926
- }
2927
- /**
2928
- * 删除选中的节点(内部使用)
2929
- * @internal 仅供内部使用,外部请使用 CanvasApi
2930
- */
2931
- deleteSelectedNodes() {
2932
- const selectedNodeIds = this.getState().selectedNodeIds || [];
2933
- if (selectedNodeIds.length === 0) return;
2934
- this.deleteNodes(selectedNodeIds);
2935
- }
2936
- /**
2937
- * 销毁 canvas
2938
- */
2939
- dispose() {
2940
- __privateGet(this, _container).removeEventListener("keydown", __privateGet(this, _handleKeyDown));
2941
- this.getCanvasTransformer().destroy();
2942
- this.getMainLayer().destroy();
2943
- this.getCanvasStage().destroy();
2944
- this._dispose();
2945
- }
2946
- /**
2947
- * 从元素同步节点数据(供节点类内部使用)
2948
- */
2949
- _syncNodeFromElement(nodeId, updates) {
2950
- const nodes = this.getState().nodes || [];
2951
- const nodeIndex = nodes.findIndex((n) => n.id === nodeId);
2952
- if (nodeIndex === -1) return;
2953
- const updatedNode = {
2954
- ...nodes[nodeIndex],
2955
- ...updates,
2956
- props: {
2957
- ...nodes[nodeIndex].props,
2958
- ...updates.props
2959
- },
2960
- style: {
2961
- ...nodes[nodeIndex].style,
2962
- ...updates.style
2963
- },
2964
- meta: {
2965
- ...nodes[nodeIndex].meta,
2966
- ...updates.meta
2967
- }
2968
- };
2969
- const newNodes = [...nodes];
2970
- newNodes[nodeIndex] = updatedNode;
2971
- this._updateState(
2972
- {
2973
- nodes: newNodes
2974
- },
2975
- true
2976
- );
2977
- }
2978
- /**
2979
- * 实现父类的状态同步方法
2980
- * 当 undo/redo 时被调用
2981
- */
2982
- _syncState(state) {
2983
- __privateGet(this, _canvasStage).setViewport({
2984
- x: state.viewport.x,
2985
- y: state.viewport.y,
2986
- scale: state.viewport.scale,
2987
- width: state.viewport.width,
2988
- height: state.viewport.height
2989
- });
2990
- }
2991
- }
2992
- _canvasStage = new WeakMap();
2993
- _mainLayer = new WeakMap();
2994
- _canvasTransformer = new WeakMap();
2995
- _draftNode = new WeakMap();
2996
- _container = new WeakMap();
2997
- _handleKeyDown = new WeakMap();
2998
- _CanvasCore_instances = new WeakSet();
2999
- /**
3000
- * 设置键盘事件监听
3001
- */
3002
- setupKeyboardEvents_fn = function() {
3003
- __privateGet(this, _container).tabIndex = 0;
3004
- __privateGet(this, _container).addEventListener("keydown", __privateGet(this, _handleKeyDown));
3005
- };
3006
- class CanvasApi extends CanvasCore {
3007
- /**
3008
- * 获取所有可用的工具类型
3009
- */
3010
- getAvailableTools() {
3011
- return ["select", "hand", "rectangle", "image-marker"];
3012
- }
3013
- /**
3014
- * 设置当前工具类型
3015
- */
3016
- setToolType(type) {
3017
- super.setToolType(type);
3018
- }
3019
- /**
3020
- * 手动创建多个节点
3021
- * 如果你不知道自己在干什么,请使用更高层的封装方法,如 createImageNode
3022
- */
3023
- createNodes(nodes) {
3024
- super.createNodes(nodes);
3025
- }
3026
- /**
3027
- * 根据 ID 获取节点
3028
- */
3029
- getNodeById(id) {
3030
- const nodes = this.getState().nodes || [];
3031
- const node = nodes.find((n) => n.id === id);
3032
- return node || null;
3033
- }
3034
- /**
3035
- * 更新视口位置
3036
- */
3037
- updateViewport(viewport, addToHistory = false) {
3038
- super.updateViewport(viewport, addToHistory);
3039
- }
3040
- /**
3041
- * 创建图片节点
3042
- */
3043
- createImageNode(imageUrl, position) {
3044
- const pos = position ?? { x: 100, y: 100 };
3045
- const imageNode = {
3046
- id: uuid.v4(),
3047
- type: "image",
3048
- props: {
3049
- x: pos.x,
3050
- y: pos.y,
3051
- width: void 0,
3052
- height: void 0,
3053
- rotation: 0,
3054
- visible: true
3055
- },
3056
- style: {
3057
- color: "#000000",
3058
- line: "solid",
3059
- size: "medium",
3060
- opacity: 1
3061
- },
3062
- meta: {
3063
- imageUrl
3064
- }
3065
- };
3066
- this.createNodes([imageNode]);
3067
- }
3068
- /**
3069
- * 导出全部图形为图片
3070
- * @param options - 导出配置
3071
- * @returns DataURL 格式的图片数据
3072
- */
3073
- exportAsImage(options) {
3074
- const stage = this.getStage();
3075
- const transformer = this.getCanvasTransformer().getTransformer();
3076
- const transformerVisible = transformer.visible();
3077
- transformer.visible(false);
3078
- try {
3079
- return stage.toDataURL({
3080
- pixelRatio: options?.pixelRatio ?? 2,
3081
- mimeType: options?.mimeType ?? "image/png",
3082
- quality: options?.quality ?? 1
3083
- });
3084
- } finally {
3085
- transformer.visible(transformerVisible);
3086
- }
3087
- }
3088
- /**
3089
- * 导出当前选区为图片
3090
- * @param options - 导出配置
3091
- * @returns DataURL 格式的图片数据,如果没有选区则返回 null
3092
- */
3093
- exportSelectionAsImage(options) {
3094
- const selectedNodeIds = this.getState().selectedNodeIds || [];
3095
- if (selectedNodeIds.length === 0) {
3096
- console.warn("No selection to export");
3097
- return null;
3098
- }
3099
- const padding = options?.padding ?? 0;
3100
- const tempGroup = new Konva.Group();
3101
- selectedNodeIds.forEach((id) => {
3102
- const shape = this.getStage().findOne(`#${id}`);
3103
- if (shape) {
3104
- const clone = shape.clone();
3105
- tempGroup.add(clone);
3106
- }
3107
- });
3108
- const box = tempGroup.getClientRect();
3109
- const dataURL = tempGroup.toDataURL({
3110
- x: box.x - padding,
3111
- y: box.y - padding,
3112
- width: box.width + padding * 2,
3113
- height: box.height + padding * 2,
3114
- pixelRatio: options?.pixelRatio ?? 2,
3115
- mimeType: options?.mimeType ?? "image/png",
3116
- quality: options?.quality ?? 1
3117
- });
3118
- tempGroup.destroy();
3119
- return dataURL;
3120
- }
3121
- /**
3122
- * 导出带有标注的图片节点为图片
3123
- * @param id - 图片节点 ID
3124
- * @returns DataURL 格式的图片数据,如果节点或者 marker 不存在则返回 null
3125
- */
3126
- exportImageWithMarker(id, options) {
3127
- const imageShape = this.getStage().findOne(`#${id}`);
3128
- if (!imageShape) {
3129
- console.warn("Image shape not found on stage");
3130
- return null;
3131
- }
3132
- const nodes = this.getState().nodes || [];
3133
- const markerNodes = nodes.filter(
3134
- (n) => n.type === "image-marker" && n.meta.parent === id
3135
- );
3136
- if (markerNodes.length === 0) {
3137
- console.warn("No image-marker nodes found for the given image ID");
3138
- return null;
3139
- }
3140
- const tempGroup = new Konva.Group();
3141
- const imageClone = imageShape.clone();
3142
- tempGroup.add(imageClone);
3143
- markerNodes.forEach((markerNode) => {
3144
- const markerShape = this.getStage().findOne(`#${markerNode.id}`);
3145
- if (markerShape) {
3146
- const markerClone = markerShape.clone();
3147
- const rect = markerClone.findOne(".rect");
3148
- const markerGroup = markerClone.findOne(".marker-group");
3149
- if (rect) {
3150
- rect.strokeWidth(3);
3151
- rect.dash([5, 5]);
3152
- rect.cornerRadius(6);
3153
- }
3154
- if (markerGroup) {
3155
- const circle = markerGroup.findOne("Circle");
3156
- const text = markerGroup.findOne("Text");
3157
- if (circle) {
3158
- circle.radius(14);
3159
- circle.strokeWidth(3);
3160
- }
3161
- if (text) {
3162
- const radius = 14;
3163
- text.x(-radius);
3164
- text.y(-radius);
3165
- text.width(radius * 2);
3166
- text.height(radius * 2);
3167
- text.fontSize(16);
3168
- }
3169
- }
3170
- tempGroup.add(markerClone);
3171
- }
3172
- });
3173
- const box = tempGroup.getClientRect();
3174
- console.log("Exporting image with markers, bounding box:", box);
3175
- const dataURL = tempGroup.toDataURL({
3176
- x: box.x,
3177
- y: box.y,
3178
- width: box.width,
3179
- height: box.height,
3180
- pixelRatio: options?.pixelRatio ?? 2,
3181
- mimeType: options?.mimeType ?? "image/png",
3182
- quality: options?.quality ?? 1
3183
- });
3184
- tempGroup.destroy();
3185
- return dataURL;
3186
- }
3187
- /**
3188
- * 删除当前选中的节点
3189
- * 如果删除的是 image 节点,会同步删除所有关联的 image-marker
3190
- */
3191
- deleteSelectedNodes() {
3192
- const selectedNodeIds = this.getState().selectedNodeIds || [];
3193
- this.deleteNodes(selectedNodeIds);
3194
- }
3195
- /**
3196
- * 删除指定的节点
3197
- * 如果删除的是 image 节点,会同步删除所有关联的 image-marker
3198
- * @param nodeIds - 要删除的节点 ID 数组
3199
- * @returns 被删除的节点数据数组
3200
- */
3201
- deleteNodes(nodeIds) {
3202
- return super.deleteNodes(nodeIds);
3203
- }
3204
- /**
3205
- * 将节点移动到最上层
3206
- */
3207
- moveNodesToTop(nodeIds) {
3208
- if (nodeIds.length === 0) return;
3209
- nodeIds.forEach((id) => {
3210
- const shape = this.getStage().findOne(`#${id}`);
3211
- if (shape) {
3212
- shape.moveToTop();
3213
- }
3214
- });
3215
- }
3216
- /**
3217
- * 将节点移动到最下层
3218
- */
3219
- moveNodesToBottom(nodeIds) {
3220
- if (nodeIds.length === 0) return;
3221
- nodeIds.forEach((id) => {
3222
- const shape = this.getStage().findOne(`#${id}`);
3223
- if (shape) {
3224
- shape.moveToBottom();
3225
- }
3226
- });
3227
- }
3228
- /**
3229
- * 滚动到内容区域
3230
- * - 如果提供了 nodeIds,将指定的节点居中显示
3231
- * - 如果没有提供 nodeIds 但有选中的节点,将选中节点居中显示
3232
- * - 如果没有选中节点,将所有内容居中显示
3233
- * @param options - 配置选项
3234
- * @param options.padding - 内容周围的留白,默认 50px
3235
- * @param options.scale - 是否自动调整缩放以适应内容,默认 false
3236
- * @param options.nodeIds - 要滚动到的节点 ID 数组
3237
- */
3238
- scrollToContent(options) {
3239
- const nodes = this.getState().nodes || [];
3240
- if (nodes.length === 0) return;
3241
- const padding = options?.padding ?? 50;
3242
- const shouldScale = options?.scale === true;
3243
- const targetNodeIds = options?.nodeIds;
3244
- let minX = Infinity;
3245
- let minY = Infinity;
3246
- let maxX = -Infinity;
3247
- let maxY = -Infinity;
3248
- const mainLayer = this.getMainLayer();
3249
- const selectedNodeIds = this.getState().selectedNodeIds || [];
3250
- const hasTargetIds = targetNodeIds && targetNodeIds.length > 0;
3251
- const hasSelection = !hasTargetIds && selectedNodeIds.length > 0;
3252
- const idsToShow = hasTargetIds ? targetNodeIds : hasSelection ? selectedNodeIds : null;
3253
- mainLayer.children.forEach((child) => {
3254
- if (child.visible() && child.getClassName() !== "Transformer" && child.hasName(NODE_NAMES.selectable)) {
3255
- if (idsToShow) {
3256
- const id = child.id();
3257
- if (!idsToShow.includes(id)) return;
3258
- }
3259
- const attrs = child.getAttrs();
3260
- const x2 = attrs.x || 0;
3261
- const y2 = attrs.y || 0;
3262
- const width = attrs.width || 0;
3263
- const height = attrs.height || 0;
3264
- const rotation = attrs.rotation || 0;
3265
- if (rotation) {
3266
- const box = child.getClientRect({ skipTransform: false });
3267
- const stage = this.getStage();
3268
- const currentScale = stage.scaleX();
3269
- const currentX = stage.x();
3270
- const currentY = stage.y();
3271
- const worldMinX = (box.x - currentX) / currentScale;
3272
- const worldMinY = (box.y - currentY) / currentScale;
3273
- const worldMaxX = (box.x + box.width - currentX) / currentScale;
3274
- const worldMaxY = (box.y + box.height - currentY) / currentScale;
3275
- minX = Math.min(minX, worldMinX);
3276
- minY = Math.min(minY, worldMinY);
3277
- maxX = Math.max(maxX, worldMaxX);
3278
- maxY = Math.max(maxY, worldMaxY);
3279
- } else {
3280
- minX = Math.min(minX, x2);
3281
- minY = Math.min(minY, y2);
3282
- maxX = Math.max(maxX, x2 + width);
3283
- maxY = Math.max(maxY, y2 + height);
3284
- }
3285
- }
3286
- });
3287
- if (minX === Infinity || minY === Infinity) return;
3288
- const contentWidth = maxX - minX;
3289
- const contentHeight = maxY - minY;
3290
- const contentCenterX = minX + contentWidth / 2;
3291
- const contentCenterY = minY + contentHeight / 2;
3292
- const viewport = this.getState().viewport;
3293
- let newScale = viewport.scale;
3294
- if (shouldScale) {
3295
- const scaleX = (viewport.width - padding * 2) / contentWidth;
3296
- const scaleY = (viewport.height - padding * 2) / contentHeight;
3297
- newScale = Math.min(scaleX, scaleY, 1);
3298
- }
3299
- const x = viewport.width / 2 - contentCenterX * newScale;
3300
- const y = viewport.height / 2 - contentCenterY * newScale;
3301
- this.updateViewport({ x, y, scale: newScale }, true);
3302
- }
3303
- }
3304
- function modulate(value, rangeA, rangeB, clamp2 = false) {
3305
- const [fromLow, fromHigh] = rangeA;
3306
- const [v0, v1] = rangeB;
3307
- const result = v0 + (value - fromLow) / (fromHigh - fromLow) * (v1 - v0);
3308
- return clamp2 ? v0 < v1 ? Math.max(Math.min(result, v1), v0) : Math.max(Math.min(result, v0), v1) : result;
3309
- }
3310
- const gridSteps = [
3311
- {
3312
- min: -1,
3313
- mid: 0.15,
3314
- step: 64
3315
- },
3316
- {
3317
- min: 0.05,
3318
- mid: 0.375,
3319
- step: 16
3320
- },
3321
- {
3322
- min: 0.15,
3323
- mid: 1,
3324
- step: 4
3325
- },
3326
- {
3327
- min: 0.7,
3328
- mid: 2.5,
3329
- step: 1
3330
- }
3331
- ];
3332
- function GridBackground({
3333
- viewportX,
3334
- viewportY,
3335
- scale,
3336
- size = 20,
3337
- showGrid = true
3338
- }) {
3339
- const x = viewportX / scale;
3340
- const y = viewportY / scale;
3341
- const z = scale;
3342
- if (!showGrid) {
3343
- return null;
3344
- }
3345
- return /* @__PURE__ */ jsxRuntime.jsxs(
3346
- "svg",
3347
- {
3348
- className: "canvas-grid w-full h-full absolute top-0 left-0",
3349
- version: "1.1",
3350
- xmlns: "http://www.w3.org/2000/svg",
3351
- "aria-hidden": "true",
3352
- children: [
3353
- /* @__PURE__ */ jsxRuntime.jsx("defs", { children: gridSteps.map(({ min, mid, step }, i) => {
3354
- const s = step * size * z;
3355
- const xo = 0.5 + x * z;
3356
- const yo = 0.5 + y * z;
3357
- const gxo = xo > 0 ? xo % s : s + xo % s;
3358
- const gyo = yo > 0 ? yo % s : s + yo % s;
3359
- const opacity = z < mid ? modulate(z, [min, mid], [0, 1]) : 1;
3360
- return /* @__PURE__ */ jsxRuntime.jsx(
3361
- "pattern",
3362
- {
3363
- id: `grid_${step}`,
3364
- width: s,
3365
- height: s,
3366
- patternUnits: "userSpaceOnUse",
3367
- children: /* @__PURE__ */ jsxRuntime.jsx(
3368
- "circle",
3369
- {
3370
- className: "tl-grid-dot",
3371
- cx: gxo,
3372
- cy: gyo,
3373
- r: 1,
3374
- opacity
3375
- }
3376
- )
3377
- },
3378
- i
3379
- );
3380
- }) }),
3381
- gridSteps.map(({ step }, i) => /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", fill: `url(#grid_${step})` }, i))
3382
- ]
3383
- }
3384
- );
3385
- }
3386
- function cn(...inputs) {
3387
- return tailwindMerge.twMerge(clsx.clsx(inputs));
3388
- }
3389
- const buttonVariants = classVarianceAuthority.cva(
3390
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
3391
- {
3392
- variants: {
3393
- variant: {
3394
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
3395
- destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
3396
- outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
3397
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
3398
- ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
3399
- link: "text-primary underline-offset-4 hover:underline"
3400
- },
3401
- size: {
3402
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
3403
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
3404
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
3405
- icon: "size-9",
3406
- "icon-sm": "size-8",
3407
- "icon-lg": "size-10"
3408
- }
3409
- },
3410
- defaultVariants: {
3411
- variant: "default",
3412
- size: "default"
3413
- }
3414
- }
3415
- );
3416
- function Button({
3417
- className,
3418
- variant = "default",
3419
- size = "default",
3420
- asChild = false,
3421
- ...props
3422
- }) {
3423
- const Comp = asChild ? reactSlot.Slot : "button";
3424
- return /* @__PURE__ */ jsxRuntime.jsx(
3425
- Comp,
3426
- {
3427
- "data-slot": "button",
3428
- "data-variant": variant,
3429
- "data-size": size,
3430
- className: cn(buttonVariants({ variant, size, className })),
3431
- ...props
3432
- }
3433
- );
3434
- }
3435
- function ZoomPanel({ api }) {
3436
- const [viewport, setViewport] = react.useState(api.getState().viewport);
3437
- react.useEffect(() => {
3438
- api.on("viewport:change", (newViewport) => {
3439
- setViewport(newViewport);
3440
- });
3441
- }, [setViewport, api]);
3442
- const updateScale = (scale) => {
3443
- const halfWidth = viewport.width / 2;
3444
- const halfHeight = viewport.height / 2;
3445
- const worldCenterX = (halfWidth - viewport.x) / viewport.scale;
3446
- const worldCenterY = (halfHeight - viewport.y) / viewport.scale;
3447
- const x = halfWidth - worldCenterX * scale;
3448
- const y = halfHeight - worldCenterY * scale;
3449
- api.updateViewport({ x, y, scale });
3450
- };
3451
- const handleZoomIn = () => {
3452
- const scale = Math.min(viewport.scale * 1.2, 5);
3453
- updateScale(scale);
3454
- };
3455
- const handleZoomOut = () => {
3456
- const scale = Math.max(viewport.scale / 1.2, 0.1);
3457
- updateScale(scale);
3458
- };
3459
- const handleReset = () => {
3460
- updateScale(1);
3461
- };
3462
- const percent = Math.round(viewport.scale * 100);
3463
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "zoom-panel flex items-center gap-2", children: [
3464
- /* @__PURE__ */ jsxRuntime.jsx(
3465
- Button,
3466
- {
3467
- size: "sm",
3468
- variant: "secondary",
3469
- onClick: handleZoomOut,
3470
- title: "缩小",
3471
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, {})
3472
- }
3473
- ),
3474
- /* @__PURE__ */ jsxRuntime.jsxs(
3475
- Button,
3476
- {
3477
- size: "sm",
3478
- variant: "secondary",
3479
- onClick: handleReset,
3480
- title: `${percent}%`,
3481
- className: "min-w-16",
3482
- children: [
3483
- percent,
3484
- "%"
3485
- ]
3486
- }
3487
- ),
3488
- /* @__PURE__ */ jsxRuntime.jsx(
3489
- Button,
3490
- {
3491
- size: "sm",
3492
- variant: "secondary",
3493
- onClick: handleZoomIn,
3494
- title: "放大",
3495
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, {})
3496
- }
3497
- )
3498
- ] });
3499
- }
3500
- function HistoryPanel({ api }) {
3501
- const [canUndo, setCanUndo] = react.useState(api.canUndo());
3502
- const [canRedo, setCanRedo] = react.useState(api.canRedo());
3503
- react.useEffect(() => {
3504
- const handleStateChange = () => {
3505
- setCanUndo(api.canUndo());
3506
- setCanRedo(api.canRedo());
3507
- };
3508
- api.on("state:change", handleStateChange);
3509
- return () => {
3510
- api.off("state:change", handleStateChange);
3511
- };
3512
- }, [api]);
3513
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "history-panel flex items-center gap-2", children: [
3514
- /* @__PURE__ */ jsxRuntime.jsx(
3515
- Button,
3516
- {
3517
- size: "sm",
3518
- variant: "secondary",
3519
- disabled: !canUndo,
3520
- onClick: () => api.undo(),
3521
- title: "撤销",
3522
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Undo2, {})
3523
- }
3524
- ),
3525
- /* @__PURE__ */ jsxRuntime.jsx(
3526
- Button,
3527
- {
3528
- size: "sm",
3529
- variant: "secondary",
3530
- disabled: !canRedo,
3531
- onClick: () => api.redo(),
3532
- title: "重做",
3533
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Redo2, {})
3534
- }
3535
- )
3536
- ] });
3537
- }
3538
- function PureCanvas({ setApi }) {
3539
- const containerRef = react.useRef(null);
3540
- const [_api, _setApi] = react.useState(null);
3541
- const [viewport, setViewport] = react.useState({ x: 0, y: 0, scale: 1 });
3542
- react.useEffect(() => {
3543
- if (!containerRef.current) return;
3544
- const core = new CanvasApi(containerRef.current);
3545
- _setApi(core);
3546
- setApi?.(core);
3547
- core.on("viewport:change", (newViewport) => {
3548
- setViewport(newViewport);
3549
- });
3550
- return () => {
3551
- core.dispose();
3552
- };
3553
- }, [setApi]);
3554
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pure-canvas relative size-full", children: [
3555
- /* @__PURE__ */ jsxRuntime.jsx(
3556
- GridBackground,
3557
- {
3558
- viewportX: viewport.x,
3559
- viewportY: viewport.y,
3560
- scale: viewport.scale
3561
- }
3562
- ),
3563
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "size-full" }),
3564
- _api && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3565
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "history-panel-wrapper absolute bottom-4 left-4 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(HistoryPanel, { api: _api }) }),
3566
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "zoom-panel-wrapper absolute bottom-4 right-4 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(ZoomPanel, { api: _api }) })
3567
- ] })
3568
- ] });
3569
- }
3570
- exports2.CanvasApi = CanvasApi;
3571
- exports2.NODE_NAMES = NODE_NAMES;
3572
- exports2.PureCanvas = PureCanvas;
3573
- Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
3574
- }));