@sequent-org/moodboard 1.0.24 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequent-org/moodboard",
3
- "version": "1.0.24",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -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>
@@ -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
- this.objectData = JSON.parse(JSON.stringify(object));
23
-
24
- // Сохраняем в буфер обмена приложения
25
- this.coreMoodboard.clipboard = {
26
- type: 'object',
27
- data: this.objectData
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: this.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)