@sequent-org/moodboard 1.2.119 → 1.3.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.
Files changed (122) hide show
  1. package/package.json +11 -1
  2. package/src/assets/icons/rotate-icon.svg +1 -1
  3. package/src/core/HistoryManager.js +16 -16
  4. package/src/core/KeyboardManager.js +48 -539
  5. package/src/core/PixiEngine.js +9 -9
  6. package/src/core/SaveManager.js +56 -31
  7. package/src/core/bootstrap/CoreInitializer.js +65 -0
  8. package/src/core/commands/DeleteObjectCommand.js +8 -0
  9. package/src/core/commands/GroupDeleteCommand.js +75 -0
  10. package/src/core/commands/GroupRotateCommand.js +6 -0
  11. package/src/core/commands/UpdateContentCommand.js +52 -0
  12. package/src/core/commands/UpdateFramePropertiesCommand.js +98 -0
  13. package/src/core/commands/UpdateFrameTypeCommand.js +85 -0
  14. package/src/core/commands/UpdateNoteStyleCommand.js +88 -0
  15. package/src/core/commands/UpdateTextStyleCommand.js +90 -0
  16. package/src/core/commands/index.js +6 -0
  17. package/src/core/events/Events.js +6 -0
  18. package/src/core/flows/ClipboardFlow.js +553 -0
  19. package/src/core/flows/LayerAndViewportFlow.js +283 -0
  20. package/src/core/flows/ObjectLifecycleFlow.js +336 -0
  21. package/src/core/flows/SaveFlow.js +34 -0
  22. package/src/core/flows/TransformFlow.js +277 -0
  23. package/src/core/flows/TransformFlowResizeHelpers.js +83 -0
  24. package/src/core/index.js +41 -1773
  25. package/src/core/keyboard/KeyboardClipboardImagePaste.js +190 -0
  26. package/src/core/keyboard/KeyboardContextGuards.js +35 -0
  27. package/src/core/keyboard/KeyboardEventRouter.js +92 -0
  28. package/src/core/keyboard/KeyboardSelectionActions.js +103 -0
  29. package/src/core/keyboard/KeyboardShortcutMap.js +31 -0
  30. package/src/core/keyboard/KeyboardToolSwitching.js +26 -0
  31. package/src/core/rendering/ObjectRenderer.js +3 -7
  32. package/src/grid/BaseGrid.js +26 -0
  33. package/src/grid/CrossGrid.js +7 -6
  34. package/src/grid/DotGrid.js +89 -33
  35. package/src/grid/DotGridZoomPhases.js +42 -0
  36. package/src/grid/LineGrid.js +22 -21
  37. package/src/moodboard/MoodBoard.js +31 -532
  38. package/src/moodboard/bootstrap/MoodBoardInitializer.js +47 -0
  39. package/src/moodboard/bootstrap/MoodBoardManagersFactory.js +38 -0
  40. package/src/moodboard/bootstrap/MoodBoardUiFactory.js +109 -0
  41. package/src/moodboard/integration/MoodBoardEventBindings.js +65 -0
  42. package/src/moodboard/integration/MoodBoardLoadApi.js +82 -0
  43. package/src/moodboard/integration/MoodBoardScreenshotApi.js +33 -0
  44. package/src/moodboard/integration/MoodBoardScreenshotCanvas.js +98 -0
  45. package/src/moodboard/lifecycle/MoodBoardDestroyer.js +97 -0
  46. package/src/objects/FileObject.js +17 -6
  47. package/src/objects/FrameObject.js +50 -10
  48. package/src/objects/NoteObject.js +5 -4
  49. package/src/services/BoardService.js +42 -2
  50. package/src/services/FrameService.js +83 -42
  51. package/src/services/ResizePolicyService.js +152 -0
  52. package/src/services/SettingsApplier.js +7 -2
  53. package/src/services/ZoomPanController.js +35 -9
  54. package/src/tools/ToolManager.js +30 -537
  55. package/src/tools/board-tools/PanTool.js +5 -11
  56. package/src/tools/manager/ToolActivationController.js +49 -0
  57. package/src/tools/manager/ToolEventRouter.js +396 -0
  58. package/src/tools/manager/ToolManagerGuards.js +33 -0
  59. package/src/tools/manager/ToolManagerLifecycle.js +110 -0
  60. package/src/tools/manager/ToolRegistry.js +33 -0
  61. package/src/tools/object-tools/DrawingTool.js +48 -14
  62. package/src/tools/object-tools/PlacementTool.js +50 -1049
  63. package/src/tools/object-tools/PlacementToolV2.js +88 -0
  64. package/src/tools/object-tools/SelectTool.js +174 -2681
  65. package/src/tools/object-tools/placement/GhostController.js +504 -0
  66. package/src/tools/object-tools/placement/PlacementCoordinateResolver.js +20 -0
  67. package/src/tools/object-tools/placement/PlacementEventsBridge.js +91 -0
  68. package/src/tools/object-tools/placement/PlacementInputRouter.js +267 -0
  69. package/src/tools/object-tools/placement/PlacementPayloadFactory.js +111 -0
  70. package/src/tools/object-tools/placement/PlacementSessionStore.js +18 -0
  71. package/src/tools/object-tools/selection/BoxSelectController.js +0 -5
  72. package/src/tools/object-tools/selection/CloneFlowController.js +71 -0
  73. package/src/tools/object-tools/selection/CoordinateMapper.js +10 -0
  74. package/src/tools/object-tools/selection/CursorController.js +78 -0
  75. package/src/tools/object-tools/selection/FileNameInlineEditorController.js +184 -0
  76. package/src/tools/object-tools/selection/HitTestService.js +102 -0
  77. package/src/tools/object-tools/selection/InlineEditorController.js +24 -0
  78. package/src/tools/object-tools/selection/InlineEditorDomFactory.js +50 -0
  79. package/src/tools/object-tools/selection/InlineEditorListenersRegistry.js +14 -0
  80. package/src/tools/object-tools/selection/InlineEditorPositioningService.js +25 -0
  81. package/src/tools/object-tools/selection/NoteInlineEditorController.js +113 -0
  82. package/src/tools/object-tools/selection/SelectInputRouter.js +267 -0
  83. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +128 -0
  84. package/src/tools/object-tools/selection/SelectToolSetup.js +134 -0
  85. package/src/tools/object-tools/selection/SelectionOverlayService.js +81 -0
  86. package/src/tools/object-tools/selection/SelectionStateController.js +91 -0
  87. package/src/tools/object-tools/selection/TextEditorDomFactory.js +65 -0
  88. package/src/tools/object-tools/selection/TextEditorInteractionController.js +266 -0
  89. package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +90 -0
  90. package/src/tools/object-tools/selection/TextEditorPositioningService.js +158 -0
  91. package/src/tools/object-tools/selection/TextEditorSyncService.js +110 -0
  92. package/src/tools/object-tools/selection/TextInlineEditorController.js +457 -0
  93. package/src/tools/object-tools/selection/TransformInteractionController.js +466 -0
  94. package/src/ui/FilePropertiesPanel.js +61 -32
  95. package/src/ui/FramePropertiesPanel.js +176 -101
  96. package/src/ui/HtmlHandlesLayer.js +121 -999
  97. package/src/ui/MapPanel.js +12 -7
  98. package/src/ui/NotePropertiesPanel.js +17 -2
  99. package/src/ui/TextPropertiesPanel.js +124 -738
  100. package/src/ui/Toolbar.js +71 -1180
  101. package/src/ui/Topbar.js +23 -25
  102. package/src/ui/ZoomPanel.js +16 -5
  103. package/src/ui/handles/GroupSelectionHandlesController.js +29 -0
  104. package/src/ui/handles/HandlesDomRenderer.js +278 -0
  105. package/src/ui/handles/HandlesEventBridge.js +102 -0
  106. package/src/ui/handles/HandlesInteractionController.js +772 -0
  107. package/src/ui/handles/HandlesPositioningService.js +206 -0
  108. package/src/ui/handles/SingleSelectionHandlesController.js +22 -0
  109. package/src/ui/styles/toolbar.css +2 -0
  110. package/src/ui/styles/workspace.css +13 -6
  111. package/src/ui/text-properties/TextPropertiesPanelBindings.js +92 -0
  112. package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +77 -0
  113. package/src/ui/text-properties/TextPropertiesPanelMapper.js +173 -0
  114. package/src/ui/text-properties/TextPropertiesPanelRenderer.js +434 -0
  115. package/src/ui/text-properties/TextPropertiesPanelState.js +39 -0
  116. package/src/ui/toolbar/ToolbarActionRouter.js +193 -0
  117. package/src/ui/toolbar/ToolbarDialogsController.js +186 -0
  118. package/src/ui/toolbar/ToolbarPopupsController.js +662 -0
  119. package/src/ui/toolbar/ToolbarRenderer.js +97 -0
  120. package/src/ui/toolbar/ToolbarStateController.js +79 -0
  121. package/src/ui/toolbar/ToolbarTooltipController.js +52 -0
  122. package/src/utils/emojiLoaderNoBundler.js +1 -1
@@ -1,47 +1,98 @@
1
1
  import { BaseGrid } from './BaseGrid.js';
2
+ import { getActivePhases, getEffectiveSize } from './DotGridZoomPhases.js';
2
3
 
3
4
  /**
4
- * Точечная сетка
5
+ * Точечная сетка с фазовым переключением при зуме (как в Miro).
6
+ * При переходе между фазами точки затухают, на смену приходит другой набор.
5
7
  */
6
8
  export class DotGrid extends BaseGrid {
7
9
  constructor(options = {}) {
8
10
  super(options);
9
11
  this.type = 'dot';
10
12
 
11
- // Настройки точек
12
- this.dotSize = options.dotSize || 2;
13
13
  this.dotStyle = options.dotStyle || 'circle'; // 'circle' | 'square'
14
14
  this.highlightIntersections = options.highlightIntersections ?? true;
15
- // Пересечения делаем такими же, как обычные точки по умолчанию
16
- this.intersectionSize = options.intersectionSize || this.dotSize;
17
- this.intersectionColor = options.intersectionColor || this.color;
15
+ this.dotSize = options.dotSize ?? 1; // для serialize/setDotSize; фазы переопределяют при отрисовке
16
+ this.snapSize = options.snapSize ?? 20; // фиксированный шаг привязки в world-координатах
17
+ // Порог видимости точки на экране и лимит плотности отрисовки.
18
+ this.minScreenDotRadius = options.minScreenDotRadius ?? 0.45;
19
+ this.minScreenSpacing = options.minScreenSpacing ?? 8;
20
+ this.maxDotsPerPhase = options.maxDotsPerPhase ?? 25000;
21
+ this.intersectionSize = options.intersectionSize ?? this.dotSize;
22
+ this.intersectionColor = options.intersectionColor ?? this.color;
23
+
24
+ /** Текущий zoom (scale) для выбора фазы. 1 = 100%. */
25
+ this._zoom = 1;
26
+ }
27
+
28
+ /**
29
+ * Устанавливает текущий масштаб (для фазового переключения).
30
+ * @param {number} scale - world.scale.x (1 = 100%)
31
+ */
32
+ setZoom(scale) {
33
+ this._zoom = Math.max(0.02, Math.min(5, scale));
34
+ }
35
+
36
+ /** @returns {Array<{ phase: object, alpha: number }>} */
37
+ _getActivePhases() {
38
+ return getActivePhases(this._zoom);
39
+ }
40
+
41
+ /** @returns {number} */
42
+ _getEffectiveSize() {
43
+ return getEffectiveSize(this._zoom);
18
44
  }
19
45
 
20
46
  /**
21
47
  * Создает визуальное представление точечной сетки
22
48
  */
23
49
  createVisual() {
24
- // Рисуем обычные точки
25
- this.drawDots();
26
-
27
- // Выделение пересечений отключено для равномерного тона точек (как в Miro)
28
- // if (this.highlightIntersections) {
29
- // this.drawIntersections();
30
- // }
50
+ this.size = this._getEffectiveSize();
51
+ const phases = this._getActivePhases();
52
+ const baseOpacity = this.opacity ?? 0.7;
53
+ for (const { phase, alpha } of phases) {
54
+ this._drawPhaseDots(phase, baseOpacity * alpha);
55
+ }
31
56
  }
32
-
57
+
33
58
  /**
34
- * Рисует основные точки сетки
59
+ * Рисует точки одной фазы с заданной прозрачностью.
35
60
  */
36
- drawDots() {
37
- this.graphics.beginFill(this.color);
38
-
39
- for (let x = 0; x <= this.width; x += this.size) {
40
- for (let y = 0; y <= this.height; y += this.size) {
41
- this.drawDot(x, y, this.dotSize);
61
+ _drawPhaseDots(phase, alpha) {
62
+ const b = this.getDrawBounds();
63
+ const scale = Math.max(0.001, this._zoom || 1);
64
+ const baseSize = phase.size;
65
+ const minWorldSpacing = this.minScreenSpacing / scale;
66
+ let size = Math.max(baseSize, minWorldSpacing);
67
+
68
+ const widthWorld = Math.max(0, b.right - b.left);
69
+ const heightWorld = Math.max(0, b.bottom - b.top);
70
+ const estimateDots = () => {
71
+ const nx = Math.floor(widthWorld / size) + 3;
72
+ const ny = Math.floor(heightWorld / size) + 3;
73
+ return nx * ny;
74
+ };
75
+ const dots = estimateDots();
76
+ if (dots > this.maxDotsPerPhase) {
77
+ const densityFactor = Math.sqrt(dots / this.maxDotsPerPhase);
78
+ size *= Math.max(1, densityFactor);
79
+ }
80
+
81
+ const startX = Math.floor(b.left / size) * size;
82
+ const startY = Math.floor(b.top / size) * size;
83
+ const endX = Math.ceil(b.right / size) * size;
84
+ const endY = Math.ceil(b.bottom / size) * size;
85
+
86
+ const minWorldRadius = this.minScreenDotRadius / scale;
87
+ const maxWorldRadius = size * 0.2;
88
+ const dotSize = Math.min(Math.max(phase.dotSize, minWorldRadius), maxWorldRadius);
89
+
90
+ this.graphics.beginFill(this.color, alpha);
91
+ for (let x = startX; x <= endX; x += size) {
92
+ for (let y = startY; y <= endY; y += size) {
93
+ this.drawDot(x, y, dotSize);
42
94
  }
43
95
  }
44
-
45
96
  this.graphics.endFill();
46
97
  }
47
98
 
@@ -50,16 +101,17 @@ export class DotGrid extends BaseGrid {
50
101
  */
51
102
  drawIntersections() {
52
103
  const intersectionStep = this.size * 5;
53
-
54
- // Используем те же параметры, что и у обычных точек
104
+ const b = this.getDrawBounds();
105
+ const startX = Math.floor(b.left / intersectionStep) * intersectionStep;
106
+ const startY = Math.floor(b.top / intersectionStep) * intersectionStep;
107
+ const endX = Math.ceil(b.right / intersectionStep) * intersectionStep;
108
+ const endY = Math.ceil(b.bottom / intersectionStep) * intersectionStep;
55
109
  this.graphics.beginFill(this.color);
56
-
57
- for (let x = 0; x <= this.width; x += intersectionStep) {
58
- for (let y = 0; y <= this.height; y += intersectionStep) {
110
+ for (let x = startX; x <= endX; x += intersectionStep) {
111
+ for (let y = startY; y <= endY; y += intersectionStep) {
59
112
  this.drawDot(x, y, this.dotSize);
60
113
  }
61
114
  }
62
-
63
115
  this.graphics.endFill();
64
116
  }
65
117
 
@@ -79,8 +131,9 @@ export class DotGrid extends BaseGrid {
79
131
  * Вычисляет точку привязки для точечной сетки
80
132
  */
81
133
  calculateSnapPoint(x, y) {
82
- const snapX = Math.round(x / this.size) * this.size;
83
- const snapY = Math.round(y / this.size) * this.size;
134
+ const step = this.snapSize || this.size;
135
+ const snapX = Math.round(x / step) * step;
136
+ const snapY = Math.round(y / step) * step;
84
137
 
85
138
  return { x: snapX, y: snapY };
86
139
  }
@@ -98,11 +151,10 @@ export class DotGrid extends BaseGrid {
98
151
  }
99
152
 
100
153
  /**
101
- * Устанавливает размер точек
154
+ * Устанавливает размер точек (для совместимости; при фазовом зуме фазы переопределяют размер).
102
155
  */
103
156
  setDotSize(size) {
104
- this.dotSize = Math.max(1, size);
105
- // Синхронизируем размер точек пересечений
157
+ this.dotSize = Math.max(0.5, size);
106
158
  this.intersectionSize = this.dotSize;
107
159
  this.updateVisual();
108
160
  }
@@ -140,6 +192,10 @@ export class DotGrid extends BaseGrid {
140
192
  ...super.serialize(),
141
193
  dotSize: this.dotSize,
142
194
  dotStyle: this.dotStyle,
195
+ snapSize: this.snapSize,
196
+ minScreenDotRadius: this.minScreenDotRadius,
197
+ minScreenSpacing: this.minScreenSpacing,
198
+ maxDotsPerPhase: this.maxDotsPerPhase,
143
199
  highlightIntersections: this.highlightIntersections,
144
200
  intersectionSize: this.intersectionSize,
145
201
  intersectionColor: this.intersectionColor
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Логика фаз точечной сетки при зуме (чистые функции, тестируемые без PIXI).
3
+ * Фазы подобраны по зафиксированным checkpoint'ам Miro:
4
+ * - high zoom: базовый шаг 20 world units;
5
+ * - low zoom: шаг укрупняется, чтобы избежать перегруза при отрисовке.
6
+ */
7
+
8
+ /** @type {{ zoomMin: number, zoomMax: number, size: number, dotSize: number }[]} */
9
+ export const PHASES = [
10
+ { zoomMin: 0.02, zoomMax: 0.12, size: 160, dotSize: 0.7 },
11
+ { zoomMin: 0.12, zoomMax: 0.25, size: 80, dotSize: 0.8 },
12
+ { zoomMin: 0.25, zoomMax: 0.5, size: 40, dotSize: 0.9 },
13
+ { zoomMin: 0.5, zoomMax: 5, size: 20, dotSize: 1 }
14
+ ];
15
+
16
+ /**
17
+ * Возвращает активные фазы и их alpha для crossfade.
18
+ * @param {number} zoom - world.scale.x (1 = 100%)
19
+ * @returns {Array<{ phase: object, alpha: number }>}
20
+ */
21
+ export function getActivePhases(zoom) {
22
+ const z = Math.max(0.02, Math.min(5, zoom));
23
+ for (let i = 0; i < PHASES.length; i++) {
24
+ const phase = PHASES[i];
25
+ const inRange = i === PHASES.length - 1
26
+ ? (z >= phase.zoomMin && z <= phase.zoomMax)
27
+ : (z >= phase.zoomMin && z < phase.zoomMax);
28
+ if (inRange) {
29
+ return [{ phase, alpha: 1 }];
30
+ }
31
+ }
32
+ return [{ phase: PHASES[PHASES.length - 1], alpha: 1 }];
33
+ }
34
+
35
+ /**
36
+ * Эффективный size для snap (доминирующая фаза).
37
+ * @param {number} zoom
38
+ * @returns {number}
39
+ */
40
+ export function getEffectiveSize(zoom) {
41
+ return getActivePhases(zoom)[0].phase.size;
42
+ }
@@ -47,23 +47,24 @@ export class LineGrid extends BaseGrid {
47
47
  * Рисует основную сетку
48
48
  */
49
49
  drawMainGrid() {
50
- const w = this.width;
51
- const h = this.height;
50
+ const b = this.getDrawBounds();
52
51
  const step = this.size;
53
52
  const half = this.lineWidth / 2;
54
- // Выравниваем к половине пикселя для чётких линий на любых DPI
53
+ const startX = Math.floor(b.left / step) * step;
54
+ const endX = Math.ceil(b.right / step) * step;
55
+ const startY = Math.floor(b.top / step) * step;
56
+ const endY = Math.ceil(b.bottom / step) * step;
55
57
  // Вертикальные линии
56
- for (let x = 0; x <= w; x += step) {
58
+ for (let x = startX; x <= endX; x += step) {
57
59
  const px = Math.round(x) + (Number.isFinite(half) ? 0.5 : 0);
58
- this.graphics.moveTo(px, 0);
59
- this.graphics.lineTo(px, h);
60
+ this.graphics.moveTo(px, b.top);
61
+ this.graphics.lineTo(px, b.bottom);
60
62
  }
61
-
62
63
  // Горизонтальные линии
63
- for (let y = 0; y <= h; y += step) {
64
+ for (let y = startY; y <= endY; y += step) {
64
65
  const py = Math.round(y) + (Number.isFinite(half) ? 0.5 : 0);
65
- this.graphics.moveTo(0, py);
66
- this.graphics.lineTo(w, py);
66
+ this.graphics.moveTo(b.left, py);
67
+ this.graphics.lineTo(b.right, py);
67
68
  }
68
69
  }
69
70
 
@@ -77,23 +78,23 @@ export class LineGrid extends BaseGrid {
77
78
  } catch (_) {
78
79
  this.graphics.lineStyle(0.5, this.subGridColor, this.subGridOpacity);
79
80
  }
80
- const w = this.width;
81
- const h = this.height;
82
- // Вертикальные подлинии
83
- for (let x = subSize; x < w; x += subSize) {
81
+ const b = this.getDrawBounds();
82
+ const startX = Math.floor(b.left / subSize) * subSize;
83
+ const endX = Math.ceil(b.right / subSize) * subSize;
84
+ const startY = Math.floor(b.top / subSize) * subSize;
85
+ const endY = Math.ceil(b.bottom / subSize) * subSize;
86
+ for (let x = startX; x <= endX; x += subSize) {
84
87
  if (x % this.size !== 0) {
85
88
  const px = Math.round(x) + 0.5;
86
- this.graphics.moveTo(px, 0);
87
- this.graphics.lineTo(px, h);
89
+ this.graphics.moveTo(px, b.top);
90
+ this.graphics.lineTo(px, b.bottom);
88
91
  }
89
92
  }
90
-
91
- // Горизонтальные подлинии
92
- for (let y = subSize; y < h; y += subSize) {
93
+ for (let y = startY; y <= endY; y += subSize) {
93
94
  if (y % this.size !== 0) {
94
95
  const py = Math.round(y) + 0.5;
95
- this.graphics.moveTo(0, py);
96
- this.graphics.lineTo(w, py);
96
+ this.graphics.moveTo(b.left, py);
97
+ this.graphics.lineTo(b.right, py);
97
98
  }
98
99
  }
99
100
  }