@sequent-org/moodboard 1.4.48 → 1.4.49
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/package.json +1 -1
- package/src/core/events/Events.js +2 -0
- package/src/core/flows/ClipboardFlow.js +13 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +1 -1
- package/src/objects/ImageObject.js +5 -0
- package/src/ui/ImagePropertiesPanel.js +1473 -4
- package/src/ui/comments/CommentThreadPopover.js +68 -0
- package/src/ui/connectors/ConnectionAnchorsLayer.js +8 -1
- package/src/ui/styles/panels.css +388 -1
- package/src/utils/applyRoundedMask.js +39 -0
|
@@ -125,6 +125,7 @@ export class CommentThreadPopover {
|
|
|
125
125
|
this._onThreadOpened = this._onThreadOpened.bind(this);
|
|
126
126
|
this._onRemote = () => this._refreshIfOpen();
|
|
127
127
|
this._onOpenDraftAt = this._onOpenDraftAt.bind(this);
|
|
128
|
+
this._onOpenImageDraft = this._onOpenImageDraft.bind(this);
|
|
128
129
|
this._onThreadDeleted = this._onThreadDeleted.bind(this);
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -143,6 +144,7 @@ export class CommentThreadPopover {
|
|
|
143
144
|
this.eventBus.on(Events.Comment.RemoteUpdated, this._onRemote);
|
|
144
145
|
this.eventBus.on(Events.Comment.MessageAdded, this._onRemote);
|
|
145
146
|
this.eventBus.on(Events.Comment.OpenDraftAt, this._onOpenDraftAt);
|
|
147
|
+
this.eventBus.on(Events.Comment.OpenImageDraft, this._onOpenImageDraft);
|
|
146
148
|
this.eventBus.on(Events.Comment.ThreadDeleted, this._onThreadDeleted);
|
|
147
149
|
this.eventBus.on(Events.Viewport.Changed, () => this.reposition());
|
|
148
150
|
this.eventBus.on(Events.Tool.PanUpdate, () => this.reposition());
|
|
@@ -155,6 +157,7 @@ export class CommentThreadPopover {
|
|
|
155
157
|
this.eventBus.off(Events.Comment.RemoteUpdated, this._onRemote);
|
|
156
158
|
this.eventBus.off(Events.Comment.MessageAdded, this._onRemote);
|
|
157
159
|
this.eventBus.off(Events.Comment.OpenDraftAt, this._onOpenDraftAt);
|
|
160
|
+
this.eventBus.off(Events.Comment.OpenImageDraft, this._onOpenImageDraft);
|
|
158
161
|
this.eventBus.off(Events.Comment.ThreadDeleted, this._onThreadDeleted);
|
|
159
162
|
if (this.layer) this.layer.remove();
|
|
160
163
|
this.layer = null;
|
|
@@ -178,6 +181,70 @@ export class CommentThreadPopover {
|
|
|
178
181
|
this.openDraftAt({ x: local.x, y: local.y });
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Открывает черновик комментария, привязанный к центру объекта.
|
|
186
|
+
* Если на объекте уже есть комментарии, смещает точку от занятых позиций.
|
|
187
|
+
*/
|
|
188
|
+
_onOpenImageDraft({ objectId }) {
|
|
189
|
+
if (!objectId) return;
|
|
190
|
+
|
|
191
|
+
const posData = { objectId, position: null };
|
|
192
|
+
const sizeData = { objectId, size: null };
|
|
193
|
+
this.eventBus.emit(Events.Tool.GetObjectPosition, posData);
|
|
194
|
+
this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
195
|
+
|
|
196
|
+
if (!posData.position || !sizeData.size) return;
|
|
197
|
+
|
|
198
|
+
const { x: left, y: top } = posData.position;
|
|
199
|
+
const { width, height } = sizeData.size;
|
|
200
|
+
|
|
201
|
+
const anchored = this.commentService.getAllThreads().filter(
|
|
202
|
+
(t) => t.anchor_object_id === objectId
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// anchor_dx/anchor_dy хранятся как смещение от top-left объекта,
|
|
206
|
+
// поэтому центр = (width/2, height/2) в этом пространстве
|
|
207
|
+
const { dx, dy } = this._findFreeCommentOffset(anchored, width / 2, height / 2);
|
|
208
|
+
|
|
209
|
+
this.openDraftAt(
|
|
210
|
+
{ x: left + dx, y: top + dy },
|
|
211
|
+
{ anchor_object_id: objectId, anchor_dx: dx, anchor_dy: dy }
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Ищет свободный anchor-offset от top-left объекта.
|
|
217
|
+
* Начинает с центра (centerDx, centerDy), затем пробует 8 направлений шагами по 10px.
|
|
218
|
+
* «Свободным» считается offset, удалённый от всех занятых не менее чем на 12px.
|
|
219
|
+
*/
|
|
220
|
+
_findFreeCommentOffset(anchoredThreads, centerDx, centerDy) {
|
|
221
|
+
const STEP = 10;
|
|
222
|
+
const MIN_DIST = 12;
|
|
223
|
+
|
|
224
|
+
const isFree = (dx, dy) => anchoredThreads.every((t) => {
|
|
225
|
+
const ex = t.anchor_dx || 0;
|
|
226
|
+
const ey = t.anchor_dy || 0;
|
|
227
|
+
return Math.sqrt((dx - ex) ** 2 + (dy - ey) ** 2) >= MIN_DIST;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (isFree(centerDx, centerDy)) return { dx: centerDx, dy: centerDy };
|
|
231
|
+
|
|
232
|
+
const directions = [
|
|
233
|
+
[1, 0], [0, 1], [-1, 0], [0, -1],
|
|
234
|
+
[1, 1], [-1, 1], [1, -1], [-1, -1],
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
for (let radius = 1; radius <= 20; radius++) {
|
|
238
|
+
for (const [dirX, dirY] of directions) {
|
|
239
|
+
const dx = centerDx + dirX * STEP * radius;
|
|
240
|
+
const dy = centerDy + dirY * STEP * radius;
|
|
241
|
+
if (isFree(dx, dy)) return { dx, dy };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { dx: centerDx + STEP, dy: centerDy };
|
|
246
|
+
}
|
|
247
|
+
|
|
181
248
|
_onThreadOpened({ threadId, pinEl }) {
|
|
182
249
|
if (threadId == null && this._draftWorld) return;
|
|
183
250
|
this._draftWorld = null;
|
|
@@ -203,6 +270,7 @@ export class CommentThreadPopover {
|
|
|
203
270
|
this._closePalette();
|
|
204
271
|
this._disarmOutsideClose();
|
|
205
272
|
if (wasDraft) this.eventBus.emit(Events.Comment.DraftClosed, {});
|
|
273
|
+
this.eventBus.emit(Events.Comment.PopoverClosed, {});
|
|
206
274
|
}
|
|
207
275
|
|
|
208
276
|
reposition() {
|
|
@@ -18,6 +18,7 @@ export class ConnectionAnchorsLayer {
|
|
|
18
18
|
this.hoveredObjectId = null;
|
|
19
19
|
this._dragController = null;
|
|
20
20
|
this._onAnchorPointerDown = null;
|
|
21
|
+
this._commentPopoverOpen = false;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
attach() {
|
|
@@ -94,7 +95,12 @@ export class ConnectionAnchorsLayer {
|
|
|
94
95
|
[Events.Viewport.Changed, () => this.update()],
|
|
95
96
|
[Events.UI.ZoomPercent, () => this.update()],
|
|
96
97
|
[Events.History.Changed, () => this.update()],
|
|
97
|
-
[Events.Board.Loaded, () => this.update()]
|
|
98
|
+
[Events.Board.Loaded, () => this.update()],
|
|
99
|
+
[Events.Comment.ThreadOpened, () => { this._commentPopoverOpen = true; this.layer.innerHTML = ''; }],
|
|
100
|
+
[Events.Comment.DraftOpened, () => { this._commentPopoverOpen = true; this.layer.innerHTML = ''; }],
|
|
101
|
+
[Events.Comment.DraftClosed, () => { this._commentPopoverOpen = false; this.update(); }],
|
|
102
|
+
[Events.Comment.ThreadDeleted, () => { this._commentPopoverOpen = false; this.update(); }],
|
|
103
|
+
[Events.Comment.PopoverClosed, () => { this._commentPopoverOpen = false; this.update(); }],
|
|
98
104
|
];
|
|
99
105
|
|
|
100
106
|
bindings.forEach(([event, handler]) => {
|
|
@@ -137,6 +143,7 @@ export class ConnectionAnchorsLayer {
|
|
|
137
143
|
|
|
138
144
|
update() {
|
|
139
145
|
if (!this.layer) return;
|
|
146
|
+
if (this._commentPopoverOpen) return;
|
|
140
147
|
this.layer.innerHTML = '';
|
|
141
148
|
|
|
142
149
|
const selection = Array.from(this.core?.selectTool?.selectedObjects || []);
|
package/src/ui/styles/panels.css
CHANGED
|
@@ -355,7 +355,7 @@
|
|
|
355
355
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
356
356
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
357
357
|
font-size: 13px;
|
|
358
|
-
z-index:
|
|
358
|
+
z-index: 3000;
|
|
359
359
|
user-select: none;
|
|
360
360
|
white-space: nowrap;
|
|
361
361
|
}
|
|
@@ -604,3 +604,390 @@
|
|
|
604
604
|
color: #6B7280;
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
+
/* Border Radius Popover */
|
|
608
|
+
.ipp-border-radius-popover {
|
|
609
|
+
position: absolute;
|
|
610
|
+
top: calc(100% + 6px);
|
|
611
|
+
left: 50%;
|
|
612
|
+
transform: translateX(-50%);
|
|
613
|
+
background: #ffffff;
|
|
614
|
+
border: 1px solid #E5E7EB;
|
|
615
|
+
border-radius: 8px;
|
|
616
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
617
|
+
padding: 10px 12px;
|
|
618
|
+
display: none;
|
|
619
|
+
flex-direction: column;
|
|
620
|
+
gap: 8px;
|
|
621
|
+
min-width: 220px;
|
|
622
|
+
z-index: 1001;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.ipp-border-radius-popover.is-open {
|
|
626
|
+
display: flex;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.ipp-border-radius-label {
|
|
630
|
+
font-size: 12px;
|
|
631
|
+
font-weight: 500;
|
|
632
|
+
color: #374151;
|
|
633
|
+
font-family: inherit;
|
|
634
|
+
user-select: none;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.ipp-border-radius-controls {
|
|
638
|
+
display: flex;
|
|
639
|
+
align-items: center;
|
|
640
|
+
gap: 8px;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.ipp-border-radius-slider {
|
|
644
|
+
flex: 1;
|
|
645
|
+
height: 4px;
|
|
646
|
+
-webkit-appearance: none;
|
|
647
|
+
appearance: none;
|
|
648
|
+
background: #E5E7EB;
|
|
649
|
+
border-radius: 2px;
|
|
650
|
+
outline: none;
|
|
651
|
+
cursor: pointer;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.ipp-border-radius-slider::-webkit-slider-runnable-track {
|
|
655
|
+
height: 4px;
|
|
656
|
+
border-radius: 2px;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.ipp-border-radius-slider::-moz-range-track {
|
|
660
|
+
height: 4px;
|
|
661
|
+
border-radius: 2px;
|
|
662
|
+
background: #E5E7EB;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.ipp-border-radius-slider::-moz-range-progress {
|
|
666
|
+
height: 4px;
|
|
667
|
+
border-radius: 2px;
|
|
668
|
+
background: #444ce7;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.ipp-border-radius-slider::-webkit-slider-thumb {
|
|
672
|
+
-webkit-appearance: none;
|
|
673
|
+
appearance: none;
|
|
674
|
+
width: 14px;
|
|
675
|
+
height: 14px;
|
|
676
|
+
border-radius: 50%;
|
|
677
|
+
background: #444ce7;
|
|
678
|
+
cursor: pointer;
|
|
679
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
680
|
+
margin-top: -5px;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.ipp-border-radius-slider::-moz-range-thumb {
|
|
684
|
+
width: 14px;
|
|
685
|
+
height: 14px;
|
|
686
|
+
border-radius: 50%;
|
|
687
|
+
background: #444ce7;
|
|
688
|
+
cursor: pointer;
|
|
689
|
+
border: none;
|
|
690
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.ipp-border-radius-number {
|
|
694
|
+
width: 48px;
|
|
695
|
+
height: 28px;
|
|
696
|
+
border: 1px solid #E5E7EB;
|
|
697
|
+
border-radius: 6px;
|
|
698
|
+
font-size: 13px;
|
|
699
|
+
font-family: inherit;
|
|
700
|
+
color: #374151;
|
|
701
|
+
text-align: center;
|
|
702
|
+
padding: 0 4px;
|
|
703
|
+
line-height: 28px;
|
|
704
|
+
outline: none;
|
|
705
|
+
background: #fff;
|
|
706
|
+
-moz-appearance: textfield;
|
|
707
|
+
flex-shrink: 0;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.ipp-border-radius-number::-webkit-outer-spin-button,
|
|
711
|
+
.ipp-border-radius-number::-webkit-inner-spin-button {
|
|
712
|
+
-webkit-appearance: none;
|
|
713
|
+
margin: 0;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.ipp-border-radius-number:focus {
|
|
717
|
+
border-color: #6B7280;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/* Info Popover */
|
|
721
|
+
.ipp-info-popover {
|
|
722
|
+
position: absolute;
|
|
723
|
+
top: 0;
|
|
724
|
+
right: calc(100% + 4px);
|
|
725
|
+
background: #ffffff;
|
|
726
|
+
border: 1px solid #E5E7EB;
|
|
727
|
+
border-radius: 8px;
|
|
728
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
729
|
+
padding: 6px;
|
|
730
|
+
display: none;
|
|
731
|
+
flex-direction: column;
|
|
732
|
+
min-width: 200px;
|
|
733
|
+
z-index: 1002;
|
|
734
|
+
font-family: inherit;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.ipp-info-popover.is-open {
|
|
738
|
+
display: flex;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.ipp-info-section {
|
|
742
|
+
padding: 6px 8px;
|
|
743
|
+
display: flex;
|
|
744
|
+
flex-direction: column;
|
|
745
|
+
gap: 2px;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.ipp-info-section + .ipp-info-section {
|
|
749
|
+
border-top: 1px solid #F3F4F6;
|
|
750
|
+
margin-top: 2px;
|
|
751
|
+
padding-top: 8px;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
.ipp-info-section__label {
|
|
755
|
+
font-size: 11px;
|
|
756
|
+
color: #9CA3AF;
|
|
757
|
+
font-weight: 400;
|
|
758
|
+
line-height: 1.3;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.ipp-info-section__name {
|
|
762
|
+
font-size: 13px;
|
|
763
|
+
color: #374151;
|
|
764
|
+
font-weight: 400;
|
|
765
|
+
line-height: 1.4;
|
|
766
|
+
font-family: inherit;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.ipp-info-section__date {
|
|
770
|
+
font-size: 12px;
|
|
771
|
+
color: #9CA3AF;
|
|
772
|
+
font-family: inherit;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/* Border Style Popover */
|
|
776
|
+
.ipp-border-style-wrapper {
|
|
777
|
+
position: relative;
|
|
778
|
+
display: inline-flex;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.ipp-border-style-popover {
|
|
782
|
+
position: absolute;
|
|
783
|
+
top: calc(100% + 6px);
|
|
784
|
+
left: 50%;
|
|
785
|
+
transform: translateX(-50%);
|
|
786
|
+
background: #ffffff;
|
|
787
|
+
border: 1px solid #E5E7EB;
|
|
788
|
+
border-radius: 10px;
|
|
789
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
790
|
+
padding: 12px;
|
|
791
|
+
display: none;
|
|
792
|
+
flex-direction: column;
|
|
793
|
+
gap: 10px;
|
|
794
|
+
min-width: 220px;
|
|
795
|
+
z-index: 1001;
|
|
796
|
+
font-family: inherit;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.ipp-border-style-popover.is-open {
|
|
800
|
+
display: flex;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.ipp-bs-style-row {
|
|
804
|
+
display: flex;
|
|
805
|
+
gap: 4px;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.ipp-bs-style-btn {
|
|
809
|
+
flex: 1;
|
|
810
|
+
display: flex;
|
|
811
|
+
align-items: center;
|
|
812
|
+
justify-content: center;
|
|
813
|
+
height: 30px;
|
|
814
|
+
border: 1.5px solid #E5E7EB;
|
|
815
|
+
border-radius: 6px;
|
|
816
|
+
background: #fff;
|
|
817
|
+
color: #6B7280;
|
|
818
|
+
cursor: pointer;
|
|
819
|
+
transition: border-color 0.15s, color 0.15s;
|
|
820
|
+
padding: 0;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.ipp-bs-style-btn:hover {
|
|
824
|
+
border-color: #9CA3AF;
|
|
825
|
+
color: #374151;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.ipp-bs-style-btn.is-active {
|
|
829
|
+
border-color: #444ce7;
|
|
830
|
+
color: #444ce7;
|
|
831
|
+
background: #EEF2FF;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.ipp-bs-section {
|
|
835
|
+
display: flex;
|
|
836
|
+
flex-direction: column;
|
|
837
|
+
gap: 6px;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.ipp-bs-row-header {
|
|
841
|
+
display: flex;
|
|
842
|
+
align-items: center;
|
|
843
|
+
justify-content: space-between;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.ipp-bs-label {
|
|
847
|
+
font-size: 12px;
|
|
848
|
+
font-weight: 500;
|
|
849
|
+
color: #374151;
|
|
850
|
+
font-family: inherit;
|
|
851
|
+
user-select: none;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.ipp-bs-value {
|
|
855
|
+
font-size: 12px;
|
|
856
|
+
color: #6B7280;
|
|
857
|
+
font-family: inherit;
|
|
858
|
+
user-select: none;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
.ipp-bs-slider {
|
|
862
|
+
width: 100%;
|
|
863
|
+
height: 4px;
|
|
864
|
+
-webkit-appearance: none;
|
|
865
|
+
appearance: none;
|
|
866
|
+
background: #E5E7EB;
|
|
867
|
+
border-radius: 2px;
|
|
868
|
+
outline: none;
|
|
869
|
+
cursor: pointer;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.ipp-bs-slider::-webkit-slider-runnable-track {
|
|
873
|
+
height: 4px;
|
|
874
|
+
border-radius: 2px;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.ipp-bs-slider::-moz-range-track {
|
|
878
|
+
height: 4px;
|
|
879
|
+
border-radius: 2px;
|
|
880
|
+
background: #E5E7EB;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.ipp-bs-slider::-moz-range-progress {
|
|
884
|
+
height: 4px;
|
|
885
|
+
border-radius: 2px;
|
|
886
|
+
background: #444ce7;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.ipp-bs-slider::-webkit-slider-thumb {
|
|
890
|
+
-webkit-appearance: none;
|
|
891
|
+
appearance: none;
|
|
892
|
+
width: 14px;
|
|
893
|
+
height: 14px;
|
|
894
|
+
border-radius: 50%;
|
|
895
|
+
background: #444ce7;
|
|
896
|
+
cursor: pointer;
|
|
897
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
898
|
+
margin-top: -5px;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.ipp-bs-slider::-moz-range-thumb {
|
|
902
|
+
width: 14px;
|
|
903
|
+
height: 14px;
|
|
904
|
+
border-radius: 50%;
|
|
905
|
+
background: #444ce7;
|
|
906
|
+
cursor: pointer;
|
|
907
|
+
border: none;
|
|
908
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.ipp-bs-palette {
|
|
912
|
+
display: flex;
|
|
913
|
+
flex-direction: column;
|
|
914
|
+
gap: 4px;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.ipp-bs-palette-row {
|
|
918
|
+
display: flex;
|
|
919
|
+
gap: 4px;
|
|
920
|
+
justify-content: space-between;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.ipp-bs-swatch {
|
|
924
|
+
width: 22px;
|
|
925
|
+
height: 22px;
|
|
926
|
+
border-radius: 50%;
|
|
927
|
+
border: none;
|
|
928
|
+
cursor: pointer;
|
|
929
|
+
padding: 0;
|
|
930
|
+
flex-shrink: 0;
|
|
931
|
+
transition: transform 0.1s, box-shadow 0.15s;
|
|
932
|
+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.ipp-bs-swatch:hover {
|
|
936
|
+
transform: scale(1.18);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.ipp-bs-swatch.is-active {
|
|
940
|
+
box-shadow: 0 0 0 2px #fff, 0 0 0 3.5px #444ce7;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.ipp-bs-swatch--none {
|
|
944
|
+
background: #fff;
|
|
945
|
+
position: relative;
|
|
946
|
+
overflow: hidden;
|
|
947
|
+
box-shadow: 0 0 0 1px #E5E7EB;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.ipp-bs-swatch--none::after {
|
|
951
|
+
content: '';
|
|
952
|
+
position: absolute;
|
|
953
|
+
top: 50%;
|
|
954
|
+
left: -2px;
|
|
955
|
+
right: -2px;
|
|
956
|
+
height: 1.5px;
|
|
957
|
+
background: #9CA3AF;
|
|
958
|
+
transform: translateY(-50%) rotate(-45deg);
|
|
959
|
+
pointer-events: none;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.ipp-bs-swatch--none.is-active {
|
|
963
|
+
box-shadow: 0 0 0 2px #fff, 0 0 0 3.5px #444ce7;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.ipp-bs-custom-row {
|
|
967
|
+
display: flex;
|
|
968
|
+
flex-wrap: wrap;
|
|
969
|
+
gap: 4px;
|
|
970
|
+
align-items: center;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.ipp-bs-add-btn {
|
|
974
|
+
width: 22px;
|
|
975
|
+
height: 22px;
|
|
976
|
+
border-radius: 50%;
|
|
977
|
+
border: 1.5px dashed #D1D5DB;
|
|
978
|
+
background: #fff;
|
|
979
|
+
color: #9CA3AF;
|
|
980
|
+
cursor: pointer;
|
|
981
|
+
display: flex;
|
|
982
|
+
align-items: center;
|
|
983
|
+
justify-content: center;
|
|
984
|
+
padding: 0;
|
|
985
|
+
flex-shrink: 0;
|
|
986
|
+
transition: border-color 0.15s, color 0.15s;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.ipp-bs-add-btn:hover {
|
|
990
|
+
border-color: #9CA3AF;
|
|
991
|
+
color: #6B7280;
|
|
992
|
+
}
|
|
993
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Применяет PIXI-маску с закруглёнными углами к спрайту изображения.
|
|
5
|
+
* Sprite имеет anchor(0.5, 0.5), поэтому маска рисуется от (-w/2, -h/2) в локальных координатах.
|
|
6
|
+
* @param {PIXI.Sprite} pixiObject
|
|
7
|
+
* @param {number} radius — радиус в мировых пикселях (0 = убрать маску)
|
|
8
|
+
*/
|
|
9
|
+
export function applyRoundedMask(pixiObject, radius) {
|
|
10
|
+
const w = pixiObject.width;
|
|
11
|
+
const h = pixiObject.height;
|
|
12
|
+
if (!w || !h) return;
|
|
13
|
+
|
|
14
|
+
const maxR = Math.floor(Math.min(w, h) / 2);
|
|
15
|
+
const r = Math.max(0, Math.min(Math.round(radius), maxR));
|
|
16
|
+
|
|
17
|
+
if (pixiObject._borderRadiusMask) {
|
|
18
|
+
pixiObject._borderRadiusMask.destroy({ children: true });
|
|
19
|
+
pixiObject._borderRadiusMask = null;
|
|
20
|
+
}
|
|
21
|
+
pixiObject.mask = null;
|
|
22
|
+
|
|
23
|
+
if (r <= 0) return;
|
|
24
|
+
|
|
25
|
+
const sx = Math.abs(pixiObject.scale?.x || 1);
|
|
26
|
+
const sy = Math.abs(pixiObject.scale?.y || 1);
|
|
27
|
+
const localW = sx > 0 ? w / sx : w;
|
|
28
|
+
const localH = sy > 0 ? h / sy : h;
|
|
29
|
+
const localR = Math.min(r / Math.min(sx || 1, sy || 1), Math.floor(Math.min(localW, localH) / 2));
|
|
30
|
+
|
|
31
|
+
const g = new PIXI.Graphics();
|
|
32
|
+
g.beginFill(0xffffff);
|
|
33
|
+
g.drawRoundedRect(-localW / 2, -localH / 2, localW, localH, localR);
|
|
34
|
+
g.endFill();
|
|
35
|
+
|
|
36
|
+
pixiObject.addChild(g);
|
|
37
|
+
pixiObject._borderRadiusMask = g;
|
|
38
|
+
pixiObject.mask = g;
|
|
39
|
+
}
|