@sequent-org/moodboard 1.4.51 → 1.4.53
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/bootstrap/CoreInitializer.js +3 -0
- package/src/core/flows/ObjectLifecycleFlow.js +4 -2
- package/src/core/index.js +3 -3
- package/src/initNoBundler.js +1 -1
- package/src/moodboard/integration/MoodBoardLoadApi.js +1 -1
- package/src/objects/FrameObject.js +171 -32
- package/src/services/FrameService.js +55 -9
- package/src/services/ImageAttachmentService.js +260 -0
- package/src/tools/object-tools/placement/GhostController.js +2 -2
- package/src/tools/object-tools/placement/PlacementPayloadFactory.js +9 -1
- package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +13 -4
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +15 -10
- package/src/ui/ConnectorPropertiesPanel.js +11 -7
- package/src/ui/FramePropertiesPanel.js +150 -25
- package/src/ui/ImagePropertiesPanel.js +8 -0
- package/src/ui/Topbar.js +24 -10
- package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +39 -0
- package/src/ui/styles/panels.css +43 -3
- package/src/ui/styles/workspace.css +2 -2
|
@@ -42,19 +42,19 @@ function hexToHsv(hex) {
|
|
|
42
42
|
const FRAME_COLORS = [
|
|
43
43
|
{ name: 'Белый', hex: '#FFFFFF' },
|
|
44
44
|
{ name: 'Светло-серый', hex: '#EBEBEB' },
|
|
45
|
-
{ name: 'Серый', hex: '#
|
|
46
|
-
{ name: 'Светло-розовый', hex: '#
|
|
47
|
-
{ name: 'Розовый', hex: '#
|
|
48
|
-
{ name: 'Персиковый', hex: '#
|
|
49
|
-
{ name: 'Оранжевый', hex: '#
|
|
50
|
-
{ name: 'Светло-жёлтый',
|
|
51
|
-
{ name: 'Жёлтый', hex: '#
|
|
52
|
-
{ name: 'Светло-зелёный', hex: '#
|
|
53
|
-
{ name: 'Зелёный', hex: '#
|
|
54
|
-
{ name: 'Небесный', hex: '#
|
|
55
|
-
{ name: 'Голубой', hex: '#
|
|
56
|
-
{ name: 'Лавандовый', hex: '#
|
|
57
|
-
{ name: 'Фиолетовый', hex: '#
|
|
45
|
+
{ name: 'Серый', hex: '#E4E4E4' },
|
|
46
|
+
{ name: 'Светло-розовый', hex: '#FFEFEF' },
|
|
47
|
+
{ name: 'Розовый', hex: '#FDD8D8' },
|
|
48
|
+
{ name: 'Персиковый', hex: '#FFEDD5' },
|
|
49
|
+
{ name: 'Оранжевый', hex: '#FFD3A4' },
|
|
50
|
+
{ name: 'Светло-жёлтый', hex: '#FFFBE0' },
|
|
51
|
+
{ name: 'Жёлтый', hex: '#FCF3AF' },
|
|
52
|
+
{ name: 'Светло-зелёный', hex: '#E9F9EE' },
|
|
53
|
+
{ name: 'Зелёный', hex: '#CCEBD7' },
|
|
54
|
+
{ name: 'Небесный', hex: '#EDF6FF' },
|
|
55
|
+
{ name: 'Голубой', hex: '#CEE7FE' },
|
|
56
|
+
{ name: 'Лавандовый', hex: '#F5F2FF' },
|
|
57
|
+
{ name: 'Фиолетовый', hex: '#E4DEFC' },
|
|
58
58
|
];
|
|
59
59
|
|
|
60
60
|
const ICONS = {
|
|
@@ -75,6 +75,7 @@ const ICONS = {
|
|
|
75
75
|
bgBordered: '<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect x="3" y="3" width="16" height="16" rx="2.5" fill="#D1D5DB"/><rect x="3" y="3" width="16" height="16" rx="2.5" stroke="#374151" stroke-width="1.5"/></svg>',
|
|
76
76
|
bgOutline: '<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><rect x="3" y="3" width="16" height="16" rx="2.5" stroke="#374151" stroke-width="1.5"/></svg>',
|
|
77
77
|
eyedropper: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 20.5l4-4"/><path d="M6.5 17.5L17 7a2.5 2.5 0 0 0 0-3.5l-2-2a2.5 2.5 0 0 0-3.5 0L3 10l4 4z"/></svg>',
|
|
78
|
+
check: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>',
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
const RATIO_LABELS = {
|
|
@@ -241,8 +242,7 @@ export class FramePropertiesPanel {
|
|
|
241
242
|
|
|
242
243
|
const divider = this._makeDivider();
|
|
243
244
|
|
|
244
|
-
|
|
245
|
-
this._btn_lock.addEventListener('click', (e) => { e.stopPropagation(); this._toggleLocked(); });
|
|
245
|
+
const lockControl = this._makeLockControl();
|
|
246
246
|
|
|
247
247
|
const more = this._makeMoreButton();
|
|
248
248
|
|
|
@@ -251,7 +251,7 @@ export class FramePropertiesPanel {
|
|
|
251
251
|
panel.appendChild(rename);
|
|
252
252
|
panel.appendChild(eye);
|
|
253
253
|
panel.appendChild(divider);
|
|
254
|
-
panel.appendChild(
|
|
254
|
+
panel.appendChild(lockControl);
|
|
255
255
|
panel.appendChild(more);
|
|
256
256
|
|
|
257
257
|
this._lockableEls = [ratio, color, rename, eye, divider];
|
|
@@ -271,11 +271,81 @@ export class FramePropertiesPanel {
|
|
|
271
271
|
const btn = document.createElement('button');
|
|
272
272
|
btn.className = 'ipp-btn';
|
|
273
273
|
btn.title = title;
|
|
274
|
-
if (key) {
|
|
274
|
+
if (key) {
|
|
275
|
+
btn.id = 'fpp-btn-' + key;
|
|
276
|
+
btn.dataset.id = 'fpp-btn-' + key;
|
|
277
|
+
}
|
|
275
278
|
btn.innerHTML = iconHtml;
|
|
276
279
|
return btn;
|
|
277
280
|
}
|
|
278
281
|
|
|
282
|
+
_makeLockControl() {
|
|
283
|
+
const wrapper = document.createElement('div');
|
|
284
|
+
wrapper.className = 'ipp-btn-split-wrapper fpp-lock-wrap';
|
|
285
|
+
|
|
286
|
+
const mainBtn = document.createElement('button');
|
|
287
|
+
mainBtn.className = 'ipp-btn-split-main';
|
|
288
|
+
mainBtn.id = 'fpp-btn-lock';
|
|
289
|
+
mainBtn.dataset.id = 'fpp-btn-lock';
|
|
290
|
+
mainBtn.title = 'Заблокировать';
|
|
291
|
+
mainBtn.innerHTML = ICONS.unlock;
|
|
292
|
+
mainBtn.addEventListener('click', (e) => { e.stopPropagation(); this._toggleLocked(); });
|
|
293
|
+
this._lockSplitMain = mainBtn;
|
|
294
|
+
|
|
295
|
+
const expandBtn = document.createElement('button');
|
|
296
|
+
expandBtn.className = 'ipp-btn-split-expand fpp-lock-chevron';
|
|
297
|
+
expandBtn.title = 'Режим блокировки';
|
|
298
|
+
expandBtn.innerHTML = ICONS.expand;
|
|
299
|
+
expandBtn.addEventListener('click', (e) => {
|
|
300
|
+
e.stopPropagation();
|
|
301
|
+
const isOpen = this._lockDropdown.classList.contains('is-open');
|
|
302
|
+
this._closeMenus();
|
|
303
|
+
if (!isOpen) {
|
|
304
|
+
this._lockDropdown.classList.add('is-open');
|
|
305
|
+
expandBtn.classList.add('is-expanded');
|
|
306
|
+
this._attachOutside();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
this._lockExpandBtn = expandBtn;
|
|
310
|
+
|
|
311
|
+
const dropdown = document.createElement('div');
|
|
312
|
+
dropdown.className = 'fpp-lock-dropdown';
|
|
313
|
+
this._lockDropdown = dropdown;
|
|
314
|
+
|
|
315
|
+
const lockModeItems = [
|
|
316
|
+
{ id: 'frame', label: 'Только фрейм' },
|
|
317
|
+
{ id: 'frame-and-content', label: 'Фрейм и содержимое' },
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
lockModeItems.forEach((item) => {
|
|
321
|
+
const btn = document.createElement('button');
|
|
322
|
+
btn.className = 'ipp-dropdown-item';
|
|
323
|
+
btn.id = 'fpp-lock-mode-' + item.id;
|
|
324
|
+
btn.dataset.lockMode = item.id;
|
|
325
|
+
|
|
326
|
+
const checkSpan = document.createElement('span');
|
|
327
|
+
checkSpan.className = 'fpp-lock-mode-check';
|
|
328
|
+
checkSpan.innerHTML = ICONS.check;
|
|
329
|
+
btn.appendChild(checkSpan);
|
|
330
|
+
|
|
331
|
+
const labelSpan = document.createElement('span');
|
|
332
|
+
labelSpan.textContent = item.label;
|
|
333
|
+
btn.appendChild(labelSpan);
|
|
334
|
+
|
|
335
|
+
btn.addEventListener('click', (e) => {
|
|
336
|
+
e.stopPropagation();
|
|
337
|
+
this._closeMenus();
|
|
338
|
+
this._applyLockMode(item.id);
|
|
339
|
+
});
|
|
340
|
+
dropdown.appendChild(btn);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
wrapper.appendChild(mainBtn);
|
|
344
|
+
wrapper.appendChild(expandBtn);
|
|
345
|
+
wrapper.appendChild(dropdown);
|
|
346
|
+
return wrapper;
|
|
347
|
+
}
|
|
348
|
+
|
|
279
349
|
_makeDivider() {
|
|
280
350
|
const div = document.createElement('div');
|
|
281
351
|
div.className = 'ipp-divider';
|
|
@@ -288,6 +358,7 @@ export class FramePropertiesPanel {
|
|
|
288
358
|
|
|
289
359
|
const btn = document.createElement('button');
|
|
290
360
|
btn.className = 'fpp-ratio-btn';
|
|
361
|
+
btn.id = 'fpp-ratio-btn';
|
|
291
362
|
btn.dataset.id = 'fpp-ratio-btn';
|
|
292
363
|
|
|
293
364
|
const textBox = document.createElement('span');
|
|
@@ -324,6 +395,7 @@ export class FramePropertiesPanel {
|
|
|
324
395
|
items.forEach((item) => {
|
|
325
396
|
const el = document.createElement('button');
|
|
326
397
|
el.className = 'ipp-dropdown-item';
|
|
398
|
+
el.id = 'fpp-ratio-item-' + item.id;
|
|
327
399
|
const ic = document.createElement('span');
|
|
328
400
|
ic.className = 'ipp-dropdown-icon';
|
|
329
401
|
ic.innerHTML = item.icon;
|
|
@@ -362,6 +434,7 @@ export class FramePropertiesPanel {
|
|
|
362
434
|
|
|
363
435
|
const colorButton = document.createElement('button');
|
|
364
436
|
colorButton.className = 'fpp-color-button';
|
|
437
|
+
colorButton.id = 'fpp-color-btn';
|
|
365
438
|
colorButton.addEventListener('click', (e) => {
|
|
366
439
|
e.stopPropagation();
|
|
367
440
|
this._toggleColorPopup(colorButton);
|
|
@@ -394,6 +467,7 @@ export class FramePropertiesPanel {
|
|
|
394
467
|
BG_TYPES.forEach(({ id, title, icon }) => {
|
|
395
468
|
const btn = document.createElement('button');
|
|
396
469
|
btn.className = 'fpp-bg-type-btn';
|
|
470
|
+
btn.id = 'fpp-bg-type-btn-' + id;
|
|
397
471
|
btn.title = title;
|
|
398
472
|
btn.dataset.bgMode = id;
|
|
399
473
|
btn.innerHTML = ICONS[icon];
|
|
@@ -425,6 +499,7 @@ export class FramePropertiesPanel {
|
|
|
425
499
|
// "None" swatch — transparent
|
|
426
500
|
const noneSwatch = document.createElement('button');
|
|
427
501
|
noneSwatch.className = 'fpp-color-swatch fpp-color-swatch--none';
|
|
502
|
+
noneSwatch.id = 'fpp-swatch-none';
|
|
428
503
|
noneSwatch.title = 'Прозрачный';
|
|
429
504
|
noneSwatch.dataset.colorHex = 'none';
|
|
430
505
|
noneSwatch.innerHTML = '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><line x1="3" y1="17" x2="17" y2="3" stroke="#EF4444" stroke-width="1.5" stroke-linecap="round"/></svg>';
|
|
@@ -440,6 +515,7 @@ export class FramePropertiesPanel {
|
|
|
440
515
|
FRAME_COLORS.forEach((color) => {
|
|
441
516
|
const btn = document.createElement('button');
|
|
442
517
|
btn.className = 'fpp-color-swatch';
|
|
518
|
+
btn.id = 'fpp-swatch-' + color.hex.replace('#', '').toLowerCase();
|
|
443
519
|
btn.title = color.name;
|
|
444
520
|
btn.dataset.colorHex = color.hex.toUpperCase();
|
|
445
521
|
btn.style.setProperty('--swatch-color', color.hex);
|
|
@@ -470,6 +546,7 @@ export class FramePropertiesPanel {
|
|
|
470
546
|
|
|
471
547
|
const addBtn = document.createElement('button');
|
|
472
548
|
addBtn.className = 'fpp-add-color-btn';
|
|
549
|
+
addBtn.id = 'fpp-add-color-btn';
|
|
473
550
|
addBtn.title = 'Добавить цвет';
|
|
474
551
|
addBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><line x1="7" y1="1" x2="7" y2="13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><line x1="1" y1="7" x2="13" y2="7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>';
|
|
475
552
|
addBtn.addEventListener('click', (e) => {
|
|
@@ -562,9 +639,18 @@ export class FramePropertiesPanel {
|
|
|
562
639
|
this.colorButton.classList.remove('fpp-color-button--none');
|
|
563
640
|
}
|
|
564
641
|
|
|
642
|
+
const targetBgMode = this._getCurrentBgMode();
|
|
643
|
+
|
|
644
|
+
this._bgTypeBtns.forEach((btn) => {
|
|
645
|
+
btn.classList.toggle('is-active', btn.dataset.bgMode === targetBgMode);
|
|
646
|
+
});
|
|
647
|
+
|
|
565
648
|
this.eventBus.emit(Events.Object.StateChanged, {
|
|
566
649
|
objectId: this.currentId,
|
|
567
|
-
updates: {
|
|
650
|
+
updates: {
|
|
651
|
+
backgroundColor: pixi,
|
|
652
|
+
properties: { bgMode: targetBgMode }
|
|
653
|
+
},
|
|
568
654
|
});
|
|
569
655
|
}
|
|
570
656
|
|
|
@@ -574,9 +660,10 @@ export class FramePropertiesPanel {
|
|
|
574
660
|
}
|
|
575
661
|
|
|
576
662
|
_setColorSectionDisabled(disabled) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
663
|
+
// Disabled logic removed as per requirements (all colour buttons remain active)
|
|
664
|
+
// if (this._colorSection) {
|
|
665
|
+
// this._colorSection.classList.toggle('is-disabled', disabled);
|
|
666
|
+
// }
|
|
580
667
|
}
|
|
581
668
|
|
|
582
669
|
_markNoneSelected() {
|
|
@@ -633,6 +720,7 @@ export class FramePropertiesPanel {
|
|
|
633
720
|
canvas.width = 212;
|
|
634
721
|
canvas.height = 148;
|
|
635
722
|
canvas.className = 'fpp-color-picker__canvas';
|
|
723
|
+
canvas.id = 'fpp-hsv-canvas';
|
|
636
724
|
this._hsvCanvas = canvas;
|
|
637
725
|
|
|
638
726
|
const cursor = document.createElement('div');
|
|
@@ -661,6 +749,7 @@ export class FramePropertiesPanel {
|
|
|
661
749
|
hueSlider.max = '360';
|
|
662
750
|
hueSlider.value = '0';
|
|
663
751
|
hueSlider.className = 'fpp-color-picker__hue-slider';
|
|
752
|
+
hueSlider.id = 'fpp-hue-slider';
|
|
664
753
|
this._hueSlider = hueSlider;
|
|
665
754
|
|
|
666
755
|
hueSlider.addEventListener('input', (e) => {
|
|
@@ -679,6 +768,7 @@ export class FramePropertiesPanel {
|
|
|
679
768
|
|
|
680
769
|
const eyedropper = document.createElement('button');
|
|
681
770
|
eyedropper.className = 'fpp-color-picker__eyedropper';
|
|
771
|
+
eyedropper.id = 'fpp-eyedropper-btn';
|
|
682
772
|
eyedropper.title = 'Пипетка';
|
|
683
773
|
eyedropper.innerHTML = ICONS.eyedropper;
|
|
684
774
|
eyedropper.addEventListener('click', (e) => {
|
|
@@ -694,6 +784,7 @@ export class FramePropertiesPanel {
|
|
|
694
784
|
const hexInput = document.createElement('input');
|
|
695
785
|
hexInput.type = 'text';
|
|
696
786
|
hexInput.className = 'fpp-color-picker__hex-input';
|
|
787
|
+
hexInput.id = 'fpp-hex-input';
|
|
697
788
|
hexInput.value = '#ffffff';
|
|
698
789
|
hexInput.maxLength = 7;
|
|
699
790
|
hexInput.spellcheck = false;
|
|
@@ -724,6 +815,7 @@ export class FramePropertiesPanel {
|
|
|
724
815
|
// Apply button
|
|
725
816
|
const applyBtn = document.createElement('button');
|
|
726
817
|
applyBtn.className = 'fpp-color-picker__apply';
|
|
818
|
+
applyBtn.id = 'fpp-picker-apply-btn';
|
|
727
819
|
applyBtn.textContent = 'Добавить';
|
|
728
820
|
applyBtn.addEventListener('click', (e) => {
|
|
729
821
|
e.stopPropagation();
|
|
@@ -867,6 +959,7 @@ export class FramePropertiesPanel {
|
|
|
867
959
|
const mainBtn = document.createElement('button');
|
|
868
960
|
mainBtn.className = 'ipp-btn';
|
|
869
961
|
mainBtn.title = 'Ещё';
|
|
962
|
+
mainBtn.id = 'fpp-btn-more';
|
|
870
963
|
mainBtn.dataset.id = 'fpp-btn-more';
|
|
871
964
|
mainBtn.innerHTML = ICONS.more;
|
|
872
965
|
|
|
@@ -911,6 +1004,7 @@ export class FramePropertiesPanel {
|
|
|
911
1004
|
|
|
912
1005
|
const btn = document.createElement('button');
|
|
913
1006
|
btn.className = 'ipp-dropdown-item';
|
|
1007
|
+
btn.id = 'fpp-more-' + item.id;
|
|
914
1008
|
btn.dataset.id = 'fpp-more-' + item.id;
|
|
915
1009
|
|
|
916
1010
|
if (item.icon) {
|
|
@@ -1052,6 +1146,8 @@ export class FramePropertiesPanel {
|
|
|
1052
1146
|
if (this._moreDropdown) { this._moreDropdown.classList.remove('is-open'); }
|
|
1053
1147
|
if (this._moreMainBtn) { this._moreMainBtn.classList.remove('is-active'); }
|
|
1054
1148
|
if (this._infoPopover) { this._infoPopover.classList.remove('is-open'); }
|
|
1149
|
+
if (this._lockDropdown) { this._lockDropdown.classList.remove('is-open'); }
|
|
1150
|
+
if (this._lockExpandBtn) { this._lockExpandBtn.classList.remove('is-expanded'); }
|
|
1055
1151
|
this._detachOutside();
|
|
1056
1152
|
}
|
|
1057
1153
|
|
|
@@ -1098,23 +1194,52 @@ export class FramePropertiesPanel {
|
|
|
1098
1194
|
_toggleLocked() {
|
|
1099
1195
|
if (!this.currentId) { return; }
|
|
1100
1196
|
const newLocked = !this._isLocked();
|
|
1197
|
+
const updates = { locked: newLocked };
|
|
1198
|
+
if (newLocked) {
|
|
1199
|
+
const data = this._getObjectData();
|
|
1200
|
+
updates.lockMode = (data && data.properties && data.properties.lockMode) || 'frame';
|
|
1201
|
+
}
|
|
1101
1202
|
this.eventBus.emit(Events.Object.StateChanged, {
|
|
1102
1203
|
objectId: this.currentId,
|
|
1103
|
-
updates: { properties:
|
|
1204
|
+
updates: { properties: updates },
|
|
1205
|
+
});
|
|
1206
|
+
this._updateLockUI();
|
|
1207
|
+
this.reposition();
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
_applyLockMode(mode) {
|
|
1211
|
+
if (!this.currentId) { return; }
|
|
1212
|
+
const locked = this._isLocked();
|
|
1213
|
+
const updates = { lockMode: mode };
|
|
1214
|
+
if (!locked) { updates.locked = true; }
|
|
1215
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
1216
|
+
objectId: this.currentId,
|
|
1217
|
+
updates: { properties: updates },
|
|
1104
1218
|
});
|
|
1105
1219
|
this._updateLockUI();
|
|
1106
1220
|
this.reposition();
|
|
1107
1221
|
}
|
|
1108
1222
|
|
|
1109
1223
|
_updateLockUI() {
|
|
1110
|
-
if (!this.
|
|
1224
|
+
if (!this._lockSplitMain) { return; }
|
|
1111
1225
|
const locked = this._isLocked();
|
|
1112
1226
|
|
|
1113
1227
|
const data = this._getObjectData();
|
|
1114
1228
|
const hidden = !!(data && data.properties && data.properties.hidden);
|
|
1229
|
+
const lockMode = (data && data.properties && data.properties.lockMode) || 'frame';
|
|
1230
|
+
|
|
1231
|
+
this._lockSplitMain.innerHTML = locked ? ICONS.lock : ICONS.unlock;
|
|
1232
|
+
this._lockSplitMain.title = locked ? 'Разблокировать' : 'Заблокировать';
|
|
1115
1233
|
|
|
1116
|
-
this.
|
|
1117
|
-
|
|
1234
|
+
if (this._lockExpandBtn) {
|
|
1235
|
+
this._lockExpandBtn.style.display = locked ? 'none' : '';
|
|
1236
|
+
}
|
|
1237
|
+
if (this._lockDropdown) {
|
|
1238
|
+
if (locked) { this._lockDropdown.classList.remove('is-open'); }
|
|
1239
|
+
this._lockDropdown.querySelectorAll('[data-lock-mode]').forEach((btn) => {
|
|
1240
|
+
btn.classList.toggle('is-active', btn.dataset.lockMode === lockMode);
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1118
1243
|
|
|
1119
1244
|
if (this._eyeBtn) {
|
|
1120
1245
|
this._eyeBtn.dataset.hidden = hidden ? '1' : '0';
|
|
@@ -1050,6 +1050,14 @@ export class ImagePropertiesPanel {
|
|
|
1050
1050
|
|
|
1051
1051
|
if (item.id === 'copy') {
|
|
1052
1052
|
this.eventBus.emit(Events.Keyboard.Copy);
|
|
1053
|
+
} else if (item.id === 'bring-front') {
|
|
1054
|
+
if (this.currentId) this.eventBus.emit(Events.UI.LayerBringToFront, { objectId: this.currentId });
|
|
1055
|
+
} else if (item.id === 'bring-forward') {
|
|
1056
|
+
if (this.currentId) this.eventBus.emit(Events.UI.LayerBringForward, { objectId: this.currentId });
|
|
1057
|
+
} else if (item.id === 'send-backward') {
|
|
1058
|
+
if (this.currentId) this.eventBus.emit(Events.UI.LayerSendBackward, { objectId: this.currentId });
|
|
1059
|
+
} else if (item.id === 'send-back') {
|
|
1060
|
+
if (this.currentId) this.eventBus.emit(Events.UI.LayerSendToBack, { objectId: this.currentId });
|
|
1053
1061
|
} else if (item.id === 'duplicate') {
|
|
1054
1062
|
this._duplicateImage();
|
|
1055
1063
|
} else if (item.id === 'lock') {
|
package/src/ui/Topbar.js
CHANGED
|
@@ -176,10 +176,18 @@ export class Topbar {
|
|
|
176
176
|
_getCurrentBoardBackgroundHex() {
|
|
177
177
|
try {
|
|
178
178
|
const app = window?.moodboard?.coreMoodboard?.pixi?.app || window?.moodboard?.core?.pixi?.app;
|
|
179
|
-
const
|
|
179
|
+
const renderer = app?.renderer;
|
|
180
|
+
if (!renderer) return null;
|
|
181
|
+
const bg = renderer.background;
|
|
182
|
+
// PIXI v7+: bg.color — экземпляр Color с методом toHex()
|
|
183
|
+
if (bg?.color && typeof bg.color.toHex === 'function') {
|
|
184
|
+
return String(bg.color.toHex()).toLowerCase();
|
|
185
|
+
}
|
|
186
|
+
const colorInt = (typeof bg?.color === 'number')
|
|
187
|
+
? bg.color
|
|
188
|
+
: (typeof renderer.backgroundColor === 'number' ? renderer.backgroundColor : null);
|
|
180
189
|
if (typeof colorInt !== 'number') return null;
|
|
181
|
-
|
|
182
|
-
return hex.toLowerCase();
|
|
190
|
+
return `#${colorInt.toString(16).padStart(6, '0')}`.toLowerCase();
|
|
183
191
|
} catch (_) { return null; }
|
|
184
192
|
}
|
|
185
193
|
|
|
@@ -288,14 +296,20 @@ export class Topbar {
|
|
|
288
296
|
// Выделяем активный цвет галочкой по фактическому boardHex
|
|
289
297
|
try {
|
|
290
298
|
const ap = window?.moodboard?.coreMoodboard?.settingsApplier;
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
this.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
const raw = (ap && ap.get && ap.get().backgroundColor)
|
|
300
|
+
|| this._currentBoardHex
|
|
301
|
+
|| this._getCurrentBoardBackgroundHex()
|
|
302
|
+
|| '';
|
|
303
|
+
let boardHex = '';
|
|
304
|
+
if (typeof raw === 'number') {
|
|
305
|
+
boardHex = `#${raw.toString(16).padStart(6, '0')}`;
|
|
306
|
+
} else if (typeof raw === 'string') {
|
|
307
|
+
boardHex = raw;
|
|
308
|
+
}
|
|
309
|
+
boardHex = boardHex.toLowerCase();
|
|
297
310
|
const match = boardHex ? grid.querySelector(`[data-board="${boardHex}"]`) : null;
|
|
298
|
-
|
|
311
|
+
const toActivate = match || grid.querySelector('[data-board="#ffffff"]');
|
|
312
|
+
if (toActivate) toActivate.classList.add('is-active');
|
|
299
313
|
} catch (_) {}
|
|
300
314
|
|
|
301
315
|
// позиционируем поповер
|
|
@@ -119,6 +119,45 @@ export function getConnectorMidpointScreen(core, connectorId) {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Возвращает наивысшую экранную точку коннектора (минимальный y) и горизонтальный центр.
|
|
124
|
+
* Координаты — целые числа (screen-space integer contract).
|
|
125
|
+
* @returns {{ x: number, topY: number } | null}
|
|
126
|
+
*/
|
|
127
|
+
export function getConnectorTopScreen(core, connectorId) {
|
|
128
|
+
const segments = core?.connectorLayer?._lastSegments;
|
|
129
|
+
if (!segments) return null;
|
|
130
|
+
const seg = segments.find(s => s.id === connectorId);
|
|
131
|
+
if (!seg) return null;
|
|
132
|
+
|
|
133
|
+
const worldLayer = core?.pixi?.worldLayer;
|
|
134
|
+
const scale = worldLayer?.scale?.x ?? 1;
|
|
135
|
+
const worldX = worldLayer?.x ?? 0;
|
|
136
|
+
const worldY = worldLayer?.y ?? 0;
|
|
137
|
+
|
|
138
|
+
let pts;
|
|
139
|
+
if (seg.points && seg.points.length >= 2) {
|
|
140
|
+
pts = seg.points;
|
|
141
|
+
} else if (seg.start && seg.end) {
|
|
142
|
+
pts = [seg.start, seg.end];
|
|
143
|
+
} else {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let minWorldY = Infinity;
|
|
148
|
+
let sumWorldX = 0;
|
|
149
|
+
for (const p of pts) {
|
|
150
|
+
if (p.y < minWorldY) minWorldY = p.y;
|
|
151
|
+
sumWorldX += p.x;
|
|
152
|
+
}
|
|
153
|
+
const avgWorldX = sumWorldX / pts.length;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
x: Math.round(avgWorldX * scale + worldX),
|
|
157
|
+
topY: Math.round(minWorldY * scale + worldY),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
122
161
|
/**
|
|
123
162
|
* Формирует updates.properties.style для StateChanged-эмита.
|
|
124
163
|
*/
|
package/src/ui/styles/panels.css
CHANGED
|
@@ -429,12 +429,12 @@
|
|
|
429
429
|
}
|
|
430
430
|
.fpp-color-swatch--none svg { pointer-events: none; }
|
|
431
431
|
|
|
432
|
-
/* Disabled colour section when transparent is selected */
|
|
433
|
-
.fpp-section--color.is-disabled .fpp-color-swatch:not(.fpp-color-swatch--none) {
|
|
432
|
+
/* Disabled colour section when transparent is selected (removed) */
|
|
433
|
+
/* .fpp-section--color.is-disabled .fpp-color-swatch:not(.fpp-color-swatch--none) {
|
|
434
434
|
opacity: 0.32;
|
|
435
435
|
pointer-events: none;
|
|
436
436
|
cursor: default;
|
|
437
|
-
}
|
|
437
|
+
} */
|
|
438
438
|
|
|
439
439
|
/* Custom colours row */
|
|
440
440
|
.fpp-custom-row {
|
|
@@ -1326,3 +1326,43 @@
|
|
|
1326
1326
|
color: #6B7280;
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
|
+
/* Lock Mode Dropdown */
|
|
1330
|
+
.fpp-lock-wrap {
|
|
1331
|
+
position: relative;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
.fpp-lock-dropdown {
|
|
1335
|
+
position: absolute;
|
|
1336
|
+
top: calc(100% + 6px);
|
|
1337
|
+
left: 50%;
|
|
1338
|
+
transform: translateX(-50%);
|
|
1339
|
+
background: #ffffff;
|
|
1340
|
+
border: 1px solid #E5E7EB;
|
|
1341
|
+
border-radius: 8px;
|
|
1342
|
+
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1), 0 4px 10px rgba(0, 0, 0, 0.05);
|
|
1343
|
+
padding: 6px;
|
|
1344
|
+
display: none;
|
|
1345
|
+
flex-direction: column;
|
|
1346
|
+
min-width: 190px;
|
|
1347
|
+
z-index: 1001;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
.fpp-lock-dropdown.is-open {
|
|
1351
|
+
display: flex;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
.fpp-lock-mode-check {
|
|
1355
|
+
width: 14px;
|
|
1356
|
+
height: 14px;
|
|
1357
|
+
display: inline-flex;
|
|
1358
|
+
align-items: center;
|
|
1359
|
+
justify-content: center;
|
|
1360
|
+
color: #374151;
|
|
1361
|
+
flex-shrink: 0;
|
|
1362
|
+
opacity: 0;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.ipp-dropdown-item.is-active .fpp-lock-mode-check {
|
|
1366
|
+
opacity: 1;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
@@ -731,7 +731,7 @@
|
|
|
731
731
|
|
|
732
732
|
/* Light Theme */
|
|
733
733
|
.moodboard-workspace--light {
|
|
734
|
-
background: #
|
|
734
|
+
background: #ffffff;
|
|
735
735
|
color: #333;
|
|
736
736
|
}
|
|
737
737
|
|
|
@@ -929,7 +929,7 @@
|
|
|
929
929
|
.moodboard-root, :root {
|
|
930
930
|
/* CSS variables for frame style */
|
|
931
931
|
--frame-border-color: #C7CDD6;
|
|
932
|
-
--frame-border-width:
|
|
932
|
+
--frame-border-width: 1; /* px */
|
|
933
933
|
--frame-corner-radius: 6; /* px */
|
|
934
934
|
}
|
|
935
935
|
.frame-popup {
|