@sequent-org/moodboard 1.0.23 → 1.1.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/package.json +1 -1
- package/src/assets/icons/rotate-icon.svg +3 -0
- package/src/core/PixiEngine.js +32 -0
- package/src/core/commands/CopyObjectCommand.js +20 -9
- package/src/core/commands/PasteObjectCommand.js +26 -15
- package/src/core/index.js +522 -26
- package/src/objects/DrawingObject.js +16 -7
- package/src/objects/FileObject.js +25 -11
- package/src/objects/FrameObject.js +37 -9
- package/src/objects/NoteObject.js +32 -17
- package/src/objects/ShapeObject.js +9 -8
- package/src/objects/TextObject.js +2 -20
- package/src/services/FrameService.js +95 -17
- package/src/tools/object-tools/PlacementTool.js +192 -51
- package/src/tools/object-tools/SelectTool.js +215 -44
- package/src/tools/object-tools/selection/BoxSelectController.js +5 -0
- package/src/ui/FilePropertiesPanel.js +9 -2
- package/src/ui/FramePropertiesPanel.js +177 -34
- package/src/ui/HtmlHandlesLayer.js +145 -89
- package/src/ui/HtmlTextLayer.js +9 -1
- package/src/ui/NotePropertiesPanel.js +13 -6
- package/src/ui/Toolbar.js +118 -15
- package/src/ui/styles/workspace.css +74 -4
package/package.json
CHANGED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5057 3.29438C10.7728 2.40348 9.79221 1.74987 8.68786 1.41619C7.58351 1.08251 6.405 1.08374 5.30135 1.41974C4.1977 1.75574 3.21848 2.41142 2.4875 3.30385C1.75652 4.19629 1.30661 5.28541 1.19467 6.4335C1.18724 6.50979 1.16485 6.58387 1.12879 6.65151C1.09272 6.71915 1.04369 6.77902 0.984485 6.82771C0.92528 6.8764 0.857063 6.91296 0.78373 6.93529C0.710396 6.95762 0.633381 6.96529 0.557083 6.95786C0.480785 6.95043 0.406698 6.92804 0.339051 6.89198C0.271405 6.85593 0.211524 6.8069 0.162827 6.7477C0.11413 6.6885 0.0775723 6.62029 0.0552394 6.54697C0.0329064 6.47364 0.0252359 6.39664 0.0326665 6.32035C0.172736 4.88154 0.754936 3.52139 1.69928 2.42676C2.64363 1.33213 3.90379 0.556733 5.30665 0.207084C6.70951 -0.142566 8.18623 -0.0493089 9.53394 0.474043C10.8816 0.997396 12.0342 1.92517 12.8333 3.12989V0.583365C12.8333 0.428674 12.8948 0.280319 13.0042 0.170936C13.1136 0.0615523 13.262 0.000101367 13.4167 0.000101367C13.5714 0.000101367 13.7197 0.0615523 13.8291 0.170936C13.9385 0.280319 14 0.428674 14 0.583365V4.08295C14 4.23764 13.9385 4.386 13.8291 4.49538C13.7197 4.60476 13.5714 4.66621 13.4167 4.66621H9.91667C9.76196 4.66621 9.61358 4.60476 9.50419 4.49538C9.39479 4.386 9.33333 4.23764 9.33333 4.08295C9.33333 3.92826 9.39479 3.7799 9.50419 3.67052C9.61358 3.56114 9.76196 3.49968 9.91667 3.49968H11.6667C11.6145 3.43006 11.5609 3.3616 11.5057 3.29438ZM13.4423 7.04126C13.5186 7.04857 13.5928 7.07085 13.6605 7.10682C13.7282 7.14279 13.7881 7.19176 13.8369 7.25091C13.8856 7.31007 13.9223 7.37825 13.9446 7.45157C13.967 7.52488 13.9747 7.60189 13.9673 7.67819C13.8273 9.117 13.2451 10.4771 12.3007 11.5718C11.3564 12.6664 10.0962 13.4418 8.69335 13.7915C7.29049 14.1411 5.81377 14.0478 4.46606 13.5245C3.11835 13.0011 1.96577 12.0734 1.16667 10.8686V13.4152C1.16667 13.5699 1.10521 13.7182 0.995812 13.8276C0.886416 13.937 0.738043 13.9984 0.583333 13.9984C0.428624 13.9984 0.280251 13.937 0.170854 13.8276C0.0614583 13.7182 0 13.5699 0 13.4152V9.91559C0 9.7609 0.0614583 9.61254 0.170854 9.50316C0.280251 9.39377 0.428624 9.33232 0.583333 9.33232H4.08333C4.23804 9.33232 4.38642 9.39377 4.49581 9.50316C4.60521 9.61254 4.66667 9.7609 4.66667 9.91559C4.66667 10.0703 4.60521 10.2186 4.49581 10.328C4.38642 10.4374 4.23804 10.4989 4.08333 10.4989H2.33333C2.38544 10.5688 2.43911 10.6373 2.49433 10.7042C3.22711 11.595 4.20762 12.2486 5.31187 12.5824C6.41613 12.9161 7.59456 12.915 8.69817 12.5791C9.80178 12.2432 10.781 11.5877 11.5121 10.6954C12.2431 9.80317 12.6932 8.7142 12.8053 7.5662C12.8126 7.48979 12.835 7.41556 12.871 7.34779C12.9071 7.28002 12.9561 7.22003 13.0154 7.17126C13.0747 7.12249 13.143 7.0859 13.2165 7.0636C13.29 7.04129 13.3671 7.0337 13.4435 7.04126" fill="#09E9B6"/>
|
|
3
|
+
</svg>
|
package/src/core/PixiEngine.js
CHANGED
|
@@ -106,6 +106,13 @@ export class PixiEngine {
|
|
|
106
106
|
pixiObject.x += pivotX;
|
|
107
107
|
pixiObject.y += pivotY;
|
|
108
108
|
}
|
|
109
|
+
} else if (pixiObject && pixiObject.pivot && (pixiObject.pivot.x !== 0 || pixiObject.pivot.y !== 0)) {
|
|
110
|
+
// Общий случай: если инстанс вернул Container с уже установленным pivot — компенсируем смещение
|
|
111
|
+
const needsCompensation = !objectData.transform || !objectData.transform.pivotCompensated;
|
|
112
|
+
if (needsCompensation) {
|
|
113
|
+
pixiObject.x += pixiObject.pivot.x;
|
|
114
|
+
pixiObject.y += pixiObject.pivot.y;
|
|
115
|
+
}
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
// Применяем поворот из сохраненного состояния
|
|
@@ -334,6 +341,20 @@ export class PixiEngine {
|
|
|
334
341
|
// Находим ID объекта
|
|
335
342
|
for (const [objectId, pixiObject] of this.objects.entries()) {
|
|
336
343
|
if (pixiObject === child) {
|
|
344
|
+
// Особая обработка для фреймов: если у фрейма есть дети и клик внутри внутренней области (без 20px периметра),
|
|
345
|
+
// то отдаём хит-тест как пустое место, чтобы позволить box-select.
|
|
346
|
+
const mb = child._mb || {};
|
|
347
|
+
if (mb.type === 'frame') {
|
|
348
|
+
const props = mb.properties || {};
|
|
349
|
+
const hasChildren = !!props && !!this._hasFrameChildren(objectId);
|
|
350
|
+
if (hasChildren) {
|
|
351
|
+
const b = child.getBounds();
|
|
352
|
+
const inner = { x: b.x + 20, y: b.y + 20, w: Math.max(0, b.width - 40), h: Math.max(0, b.height - 40) };
|
|
353
|
+
if (point.x >= inner.x && point.x <= inner.x + inner.w && point.y >= inner.y && point.y <= inner.y + inner.h) {
|
|
354
|
+
return { type: 'empty' };
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
337
358
|
return {
|
|
338
359
|
type: 'object',
|
|
339
360
|
object: objectId,
|
|
@@ -384,6 +405,17 @@ export class PixiEngine {
|
|
|
384
405
|
return { type: 'empty' };
|
|
385
406
|
}
|
|
386
407
|
|
|
408
|
+
_hasFrameChildren(frameId) {
|
|
409
|
+
// Проверяем среди зарегистрированных PIXI объектов, у кого frameId совпадает
|
|
410
|
+
for (const [objectId, pixiObject] of this.objects.entries()) {
|
|
411
|
+
if (objectId === frameId) continue;
|
|
412
|
+
const mb = pixiObject && pixiObject._mb;
|
|
413
|
+
const props = mb && mb.properties;
|
|
414
|
+
if (props && props.frameId === frameId) return true;
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
387
419
|
// Геометрические помощники/хит-тесты вынести в отдельный сервис при следующем рефакторинге
|
|
388
420
|
_distancePointToSegment(px, py, ax, ay, bx, by) {
|
|
389
421
|
const vectorABx = bx - ax;
|
|
@@ -18,18 +18,29 @@ export class CopyObjectCommand extends BaseCommand {
|
|
|
18
18
|
const object = objects.find(obj => obj.id === this.objectId);
|
|
19
19
|
|
|
20
20
|
if (object) {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
// Если копируем фрейм — кладём в буфер группу: сам фрейм + его дети
|
|
22
|
+
if (object.type === 'frame') {
|
|
23
|
+
const frame = JSON.parse(JSON.stringify(object));
|
|
24
|
+
const children = (objects || []).filter(o => o && o.properties && o.properties.frameId === object.id)
|
|
25
|
+
.map(o => JSON.parse(JSON.stringify(o)));
|
|
26
|
+
const groupData = [frame, ...children];
|
|
27
|
+
this.coreMoodboard.clipboard = {
|
|
28
|
+
type: 'group',
|
|
29
|
+
data: groupData,
|
|
30
|
+
meta: { pasteCount: 0, frameBundle: true }
|
|
31
|
+
};
|
|
32
|
+
} else {
|
|
33
|
+
// Обычный объект
|
|
34
|
+
this.objectData = JSON.parse(JSON.stringify(object));
|
|
35
|
+
this.coreMoodboard.clipboard = {
|
|
36
|
+
type: 'object',
|
|
37
|
+
data: this.objectData
|
|
38
|
+
};
|
|
39
|
+
}
|
|
29
40
|
|
|
30
41
|
this.emit(Events.Object.Updated, {
|
|
31
42
|
objectId: this.objectId,
|
|
32
|
-
objectData:
|
|
43
|
+
objectData: object
|
|
33
44
|
});
|
|
34
45
|
}
|
|
35
46
|
}
|
|
@@ -34,31 +34,42 @@ export class PasteObjectCommand extends BaseCommand {
|
|
|
34
34
|
};
|
|
35
35
|
this.newObjectId = generateObjectId(exists);
|
|
36
36
|
this.newObjectData.id = this.newObjectId;
|
|
37
|
+
|
|
38
|
+
// Название для фрейма, если не проставлено заранее (например, из DuplicateRequest)
|
|
39
|
+
try {
|
|
40
|
+
if (this.newObjectData.type === 'frame') {
|
|
41
|
+
const t0 = this.newObjectData?.properties?.title || '';
|
|
42
|
+
if (!/^\s*Фрейм\s+\d+\s*$/i.test(t0)) {
|
|
43
|
+
const objects = this.coreMoodboard.state?.state?.objects || [];
|
|
44
|
+
let maxNum = 0;
|
|
45
|
+
for (const o of objects) {
|
|
46
|
+
if (!o || o.type !== 'frame') continue;
|
|
47
|
+
const t = o?.properties?.title || '';
|
|
48
|
+
const m = t.match(/^\s*Фрейм\s+(\d+)\s*$/i);
|
|
49
|
+
if (m) {
|
|
50
|
+
const n = parseInt(m[1], 10);
|
|
51
|
+
if (Number.isFinite(n)) maxNum = Math.max(maxNum, n);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const nextIndex = maxNum + 1;
|
|
55
|
+
this.newObjectData.properties = this.newObjectData.properties || {};
|
|
56
|
+
this.newObjectData.properties.title = `Фрейм ${nextIndex}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (_) { /* no-op */ }
|
|
37
60
|
|
|
38
61
|
// Сохраняем ID оригинального объекта для отслеживания копий
|
|
39
62
|
this.newObjectData.originalId = originalData.id;
|
|
40
63
|
|
|
41
|
-
// Устанавливаем позицию
|
|
64
|
+
// Устанавливаем позицию вставки: если пришла — строго под курсор; иначе fallback со смещением
|
|
42
65
|
if (this.pastePosition) {
|
|
43
66
|
this.newObjectData.position = { ...this.pastePosition };
|
|
44
67
|
} else {
|
|
45
|
-
// Если позиция не указана, смещаем относительно оригинала
|
|
46
|
-
// Проверяем, сколько копий этого объекта уже создано
|
|
47
68
|
const existingObjects = this.coreMoodboard.state.state.objects;
|
|
48
69
|
const originalId = originalData.id;
|
|
49
|
-
|
|
50
|
-
// Ищем все копии этого конкретного объекта
|
|
51
|
-
const copies = existingObjects.filter(obj =>
|
|
52
|
-
obj.originalId === originalId || // Копии оригинального объекта
|
|
53
|
-
(obj.id === originalId && obj.originalId) // Если оригинал сам является копией
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
// Рассчитываем смещение на основе количества копий
|
|
70
|
+
const copies = existingObjects.filter(obj => obj.originalId === originalId || (obj.id === originalId && obj.originalId));
|
|
57
71
|
const offsetMultiplier = copies.length + 1;
|
|
58
|
-
const offsetStep = 25;
|
|
59
|
-
|
|
60
|
-
console.log(`📋 Вставка копии объекта ${originalId}: найдено ${copies.length} существующих копий, смещение ${offsetStep * offsetMultiplier}px`);
|
|
61
|
-
|
|
72
|
+
const offsetStep = 25;
|
|
62
73
|
this.newObjectData.position = {
|
|
63
74
|
x: originalData.position.x + (offsetStep * offsetMultiplier),
|
|
64
75
|
y: originalData.position.y + (offsetStep * offsetMultiplier)
|