@sequent-org/moodboard 1.0.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 (123) hide show
  1. package/package.json +44 -0
  2. package/src/assets/icons/README.md +105 -0
  3. package/src/assets/icons/attachments.svg +3 -0
  4. package/src/assets/icons/clear.svg +5 -0
  5. package/src/assets/icons/comments.svg +3 -0
  6. package/src/assets/icons/emoji.svg +6 -0
  7. package/src/assets/icons/frame.svg +3 -0
  8. package/src/assets/icons/image.svg +3 -0
  9. package/src/assets/icons/note.svg +3 -0
  10. package/src/assets/icons/pan.svg +3 -0
  11. package/src/assets/icons/pencil.svg +3 -0
  12. package/src/assets/icons/redo.svg +3 -0
  13. package/src/assets/icons/select.svg +9 -0
  14. package/src/assets/icons/shapes.svg +3 -0
  15. package/src/assets/icons/text-add.svg +3 -0
  16. package/src/assets/icons/topbar/README.md +39 -0
  17. package/src/assets/icons/topbar/grid-cross.svg +6 -0
  18. package/src/assets/icons/topbar/grid-dot.svg +3 -0
  19. package/src/assets/icons/topbar/grid-line.svg +3 -0
  20. package/src/assets/icons/topbar/grid-off.svg +3 -0
  21. package/src/assets/icons/topbar/paint.svg +3 -0
  22. package/src/assets/icons/undo.svg +3 -0
  23. package/src/core/ApiClient.js +309 -0
  24. package/src/core/EventBus.js +42 -0
  25. package/src/core/HistoryManager.js +261 -0
  26. package/src/core/KeyboardManager.js +710 -0
  27. package/src/core/PixiEngine.js +439 -0
  28. package/src/core/SaveManager.js +381 -0
  29. package/src/core/StateManager.js +64 -0
  30. package/src/core/commands/BaseCommand.js +68 -0
  31. package/src/core/commands/CopyObjectCommand.js +44 -0
  32. package/src/core/commands/CreateObjectCommand.js +46 -0
  33. package/src/core/commands/DeleteObjectCommand.js +146 -0
  34. package/src/core/commands/EditFileNameCommand.js +107 -0
  35. package/src/core/commands/GroupMoveCommand.js +47 -0
  36. package/src/core/commands/GroupReorderZCommand.js +74 -0
  37. package/src/core/commands/GroupResizeCommand.js +37 -0
  38. package/src/core/commands/GroupRotateCommand.js +41 -0
  39. package/src/core/commands/MoveObjectCommand.js +89 -0
  40. package/src/core/commands/PasteObjectCommand.js +103 -0
  41. package/src/core/commands/ReorderZCommand.js +45 -0
  42. package/src/core/commands/ResizeObjectCommand.js +135 -0
  43. package/src/core/commands/RotateObjectCommand.js +70 -0
  44. package/src/core/commands/index.js +14 -0
  45. package/src/core/events/Events.js +147 -0
  46. package/src/core/index.js +1632 -0
  47. package/src/core/rendering/GeometryUtils.js +89 -0
  48. package/src/core/rendering/HitTestManager.js +186 -0
  49. package/src/core/rendering/LayerManager.js +137 -0
  50. package/src/core/rendering/ObjectRenderer.js +363 -0
  51. package/src/core/rendering/PixiRenderer.js +140 -0
  52. package/src/core/rendering/index.js +9 -0
  53. package/src/grid/BaseGrid.js +164 -0
  54. package/src/grid/CrossGrid.js +75 -0
  55. package/src/grid/DotGrid.js +148 -0
  56. package/src/grid/GridFactory.js +173 -0
  57. package/src/grid/LineGrid.js +115 -0
  58. package/src/index.js +2 -0
  59. package/src/moodboard/ActionHandler.js +114 -0
  60. package/src/moodboard/DataManager.js +114 -0
  61. package/src/moodboard/MoodBoard.js +359 -0
  62. package/src/moodboard/WorkspaceManager.js +103 -0
  63. package/src/objects/BaseObject.js +1 -0
  64. package/src/objects/CommentObject.js +115 -0
  65. package/src/objects/DrawingObject.js +114 -0
  66. package/src/objects/EmojiObject.js +98 -0
  67. package/src/objects/FileObject.js +318 -0
  68. package/src/objects/FrameObject.js +127 -0
  69. package/src/objects/ImageObject.js +72 -0
  70. package/src/objects/NoteObject.js +227 -0
  71. package/src/objects/ObjectFactory.js +61 -0
  72. package/src/objects/ShapeObject.js +134 -0
  73. package/src/objects/StampObject.js +0 -0
  74. package/src/objects/StickerObject.js +0 -0
  75. package/src/objects/TextObject.js +123 -0
  76. package/src/services/BoardService.js +85 -0
  77. package/src/services/FileUploadService.js +398 -0
  78. package/src/services/FrameService.js +138 -0
  79. package/src/services/ImageUploadService.js +246 -0
  80. package/src/services/ZOrderManager.js +50 -0
  81. package/src/services/ZoomPanController.js +78 -0
  82. package/src/src.7z +0 -0
  83. package/src/src.zip +0 -0
  84. package/src/src2.zip +0 -0
  85. package/src/tools/AlignmentGuides.js +326 -0
  86. package/src/tools/BaseTool.js +257 -0
  87. package/src/tools/ResizeHandles.js +381 -0
  88. package/src/tools/ToolManager.js +580 -0
  89. package/src/tools/board-tools/PanTool.js +43 -0
  90. package/src/tools/board-tools/ZoomTool.js +393 -0
  91. package/src/tools/object-tools/DrawingTool.js +404 -0
  92. package/src/tools/object-tools/PlacementTool.js +1005 -0
  93. package/src/tools/object-tools/SelectTool.js +2183 -0
  94. package/src/tools/object-tools/TextTool.js +416 -0
  95. package/src/tools/object-tools/selection/BoxSelectController.js +105 -0
  96. package/src/tools/object-tools/selection/GeometryUtils.js +101 -0
  97. package/src/tools/object-tools/selection/GroupDragController.js +61 -0
  98. package/src/tools/object-tools/selection/GroupResizeController.js +90 -0
  99. package/src/tools/object-tools/selection/GroupRotateController.js +61 -0
  100. package/src/tools/object-tools/selection/HandlesSync.js +96 -0
  101. package/src/tools/object-tools/selection/ResizeController.js +68 -0
  102. package/src/tools/object-tools/selection/RotateController.js +58 -0
  103. package/src/tools/object-tools/selection/SelectionModel.js +42 -0
  104. package/src/tools/object-tools/selection/SimpleDragController.js +45 -0
  105. package/src/ui/CommentPopover.js +187 -0
  106. package/src/ui/ContextMenu.js +340 -0
  107. package/src/ui/FilePropertiesPanel.js +298 -0
  108. package/src/ui/FramePropertiesPanel.js +462 -0
  109. package/src/ui/HtmlHandlesLayer.js +778 -0
  110. package/src/ui/HtmlTextLayer.js +279 -0
  111. package/src/ui/MapPanel.js +290 -0
  112. package/src/ui/NotePropertiesPanel.js +502 -0
  113. package/src/ui/SaveStatus.js +250 -0
  114. package/src/ui/TextPropertiesPanel.js +911 -0
  115. package/src/ui/Toolbar.js +1118 -0
  116. package/src/ui/Topbar.js +220 -0
  117. package/src/ui/ZoomPanel.js +116 -0
  118. package/src/ui/styles/workspace.css +854 -0
  119. package/src/utils/colors.js +0 -0
  120. package/src/utils/geometry.js +0 -0
  121. package/src/utils/iconLoader.js +270 -0
  122. package/src/utils/objectIdGenerator.js +17 -0
  123. package/src/utils/topbarIconLoader.js +114 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Горизонтальная верхняя панель (пока пустая)
3
+ */
4
+ import { Events } from '../core/events/Events.js';
5
+ import { TopbarIconLoader } from '../utils/topbarIconLoader.js';
6
+
7
+ export class Topbar {
8
+ constructor(container, eventBus, theme = 'light') {
9
+ this.container = container;
10
+ this.eventBus = eventBus;
11
+ this.theme = theme;
12
+ this.element = null;
13
+ this._paintPopover = null;
14
+ // Загружаем иконки
15
+ this.iconLoader = new TopbarIconLoader();
16
+ this.icons = this.iconLoader.icons;
17
+ this.init();
18
+ }
19
+
20
+ async init() {
21
+ try {
22
+ // Загружаем все иконки
23
+ this.icons = await this.iconLoader.loadAllIcons();
24
+ } catch (error) {
25
+ console.error('❌ Ошибка загрузки иконок верхней панели:', error);
26
+ }
27
+
28
+ this.createTopbar();
29
+ this.attachEvents();
30
+ // Активируем дефолтную кнопку (line) до прихода события из ядра
31
+ this.setActive('line');
32
+
33
+ // Синхронизация активного состояния по событию из ядра
34
+ this.eventBus.on(Events.UI.GridCurrent, ({ type }) => {
35
+ this.setActive(type);
36
+ });
37
+ }
38
+
39
+ createTopbar() {
40
+ this.element = document.createElement('div');
41
+ this.element.className = `moodboard-topbar moodboard-topbar--${this.theme}`;
42
+ // Кнопки выбора вида сетки (без функциональности)
43
+ const buttons = [
44
+ { id: 'grid-line', icon: 'grid-line', title: 'Сетка: линии', type: 'line' },
45
+ { id: 'grid-dot', icon: 'grid-dot', title: 'Сетка: точки', type: 'dot' },
46
+ { id: 'grid-cross', icon: 'grid-cross', title: 'Сетка: крестики', type: 'cross' },
47
+ { id: 'grid-off', icon: 'grid-off', title: 'Сетка: выкл', type: 'off' }
48
+ ];
49
+
50
+ buttons.forEach(cfg => {
51
+ const btn = document.createElement('button');
52
+ btn.className = 'moodboard-topbar__button';
53
+
54
+ // Создаем SVG иконку
55
+ if (this.icons[cfg.icon]) {
56
+ this.createSvgIcon(btn, cfg.icon);
57
+ } else {
58
+ // Fallback: создаем простую текстовую иконку
59
+ const fallbackIcon = document.createElement('span');
60
+ fallbackIcon.textContent = cfg.icon.charAt(0).toUpperCase();
61
+ fallbackIcon.style.fontSize = '14px';
62
+ fallbackIcon.style.fontWeight = 'bold';
63
+ btn.appendChild(fallbackIcon);
64
+ }
65
+
66
+ btn.title = cfg.title;
67
+ btn.dataset.grid = cfg.type;
68
+ this.element.appendChild(btn);
69
+ });
70
+
71
+ // Вертикальный разделитель
72
+ const divider = document.createElement('div');
73
+ divider.className = 'moodboard-topbar__divider';
74
+ this.element.appendChild(divider);
75
+
76
+ // Кнопка "краска" (иконка банки с краской)
77
+ const paintBtn = document.createElement('button');
78
+ paintBtn.className = 'moodboard-topbar__button moodboard-topbar__button--paint';
79
+ paintBtn.title = 'Палитра фона';
80
+
81
+ // Создаем SVG иконку
82
+ if (this.icons['paint']) {
83
+ this.createSvgIcon(paintBtn, 'paint');
84
+ } else {
85
+ // Fallback: создаем простую текстовую иконку
86
+ const fallbackIcon = document.createElement('span');
87
+ fallbackIcon.textContent = '🎨';
88
+ fallbackIcon.style.fontSize = '16px';
89
+ paintBtn.appendChild(fallbackIcon);
90
+ }
91
+
92
+ paintBtn.dataset.action = 'paint-toggle';
93
+ this.element.appendChild(paintBtn);
94
+
95
+ // (кнопки зума вынесены в отдельную панель справа)
96
+
97
+ this.container.appendChild(this.element);
98
+ }
99
+
100
+ /**
101
+ * Создает SVG иконку для кнопки
102
+ */
103
+ createSvgIcon(button, iconName) {
104
+ if (this.icons[iconName]) {
105
+ // Создаем SVG элемент из загруженного содержимого
106
+ const tempDiv = document.createElement('div');
107
+ tempDiv.innerHTML = this.icons[iconName];
108
+ const svg = tempDiv.querySelector('svg');
109
+
110
+ if (svg) {
111
+ // Убираем inline размеры, чтобы CSS мог их контролировать
112
+ svg.removeAttribute('width');
113
+ svg.removeAttribute('height');
114
+ svg.style.display = 'block';
115
+
116
+ // Добавляем SVG в кнопку
117
+ button.appendChild(svg);
118
+ }
119
+ }
120
+ }
121
+
122
+ attachEvents() {
123
+ this.element.addEventListener('click', (e) => {
124
+ const btn = e.target.closest('.moodboard-topbar__button');
125
+ if (!btn) return;
126
+ if (btn.dataset.grid) {
127
+ const type = btn.dataset.grid;
128
+ this.eventBus.emit(Events.UI.GridChange, { type });
129
+ this.setActive(type);
130
+ return;
131
+ }
132
+ if (btn.dataset.action === 'paint-toggle') {
133
+ this.togglePaintPopover(btn);
134
+ return;
135
+ }
136
+ });
137
+
138
+ // Зум обрабатывается в отдельной панели
139
+ }
140
+
141
+ setActive(type) {
142
+ const buttons = this.element.querySelectorAll('.moodboard-topbar__button');
143
+ buttons.forEach((b) => b.classList.remove('moodboard-topbar__button--active'));
144
+ const target = this.element.querySelector(`.moodboard-topbar__button[data-grid="${type}"]`);
145
+ if (target) target.classList.add('moodboard-topbar__button--active');
146
+ }
147
+
148
+ setTheme(theme) {
149
+ this.theme = theme;
150
+ this.element.className = `moodboard-topbar moodboard-topbar--${theme}`;
151
+ }
152
+
153
+ togglePaintPopover(anchorBtn) {
154
+ if (this._paintPopover && this._paintPopover.parentNode) {
155
+ this._paintPopover.remove();
156
+ this._paintPopover = null;
157
+ return;
158
+ }
159
+ const pop = document.createElement('div');
160
+ pop.className = 'moodboard-topbar__paint-popover';
161
+ // Пять цветов кнопок-палитры и соответствующие цвета фона доски
162
+ // 1: фон #f7fbff, кнопка #B3E5FC
163
+ // 2: фон #f8fff7, кнопка #E8F5E9
164
+ // 3: фон #fffcf7, кнопка #FFF3E0
165
+ // 4: фон #f5f5f5, кнопка #f5f5f5
166
+ // 5: фон #ffffff, кнопка #ffffff
167
+ const colors = [
168
+ { id: 1, name: 'default-light', hex: '#B3E5FC', board: '#f7fbff' },
169
+ { id: 2, name: 'mint-light', hex: '#E8F5E9', board: '#f8fff7' },
170
+ { id: 3, name: 'peach-light', hex: '#FFF3E0', board: '#fffcf7' },
171
+ { id: 4, name: 'gray-light', hex: '#f5f5f5', board: '#f5f5f5' },
172
+ { id: 5, name: 'white', hex: '#ffffff', board: '#ffffff' }
173
+ ];
174
+ const grid = document.createElement('div');
175
+ grid.className = 'moodboard-topbar__paint-grid';
176
+ colors.forEach(c => {
177
+ const b = document.createElement('button');
178
+ b.type = 'button';
179
+ b.className = 'moodboard-topbar__paint-btn';
180
+ b.title = `Цвет ${c.id}`;
181
+ b.style.background = c.hex;
182
+ b.dataset.colorId = String(c.id);
183
+ b.addEventListener('click', () => {
184
+ this.eventBus.emit(Events.UI.PaintPick, { id: c.id, color: c.board, name: c.name });
185
+ if (this._paintPopover) this._paintPopover.remove();
186
+ this._paintPopover = null;
187
+ });
188
+ grid.appendChild(b);
189
+ });
190
+ pop.appendChild(grid);
191
+
192
+ // позиционируем поповер
193
+ const rect = anchorBtn.getBoundingClientRect();
194
+ pop.style.position = 'absolute';
195
+ pop.style.left = `${rect.left - this.element.getBoundingClientRect().left}px`;
196
+ pop.style.top = `${rect.bottom - this.element.getBoundingClientRect().top + 6}px`;
197
+
198
+ this.element.appendChild(pop);
199
+ this._paintPopover = pop;
200
+
201
+ // закрытие по клику вне
202
+ const onDocClick = (ev) => {
203
+ if (!pop.contains(ev.target) && ev.target !== anchorBtn) {
204
+ pop.remove();
205
+ this._paintPopover = null;
206
+ document.removeEventListener('click', onDocClick, true);
207
+ }
208
+ };
209
+ setTimeout(() => document.addEventListener('click', onDocClick, true), 0);
210
+ }
211
+
212
+ destroy() {
213
+ if (this.element) {
214
+ this.element.remove();
215
+ this.element = null;
216
+ }
217
+ }
218
+ }
219
+
220
+
@@ -0,0 +1,116 @@
1
+ import { Events } from '../core/events/Events.js';
2
+
3
+ export class ZoomPanel {
4
+ constructor(container, eventBus) {
5
+ this.container = container;
6
+ this.eventBus = eventBus;
7
+ this.element = null;
8
+ this.labelEl = null;
9
+ this.menuEl = null;
10
+ this.create();
11
+ this.attach();
12
+ }
13
+
14
+ create() {
15
+ this.element = document.createElement('div');
16
+ this.element.className = 'moodboard-zoombar';
17
+
18
+ const zoomOutBtn = document.createElement('button');
19
+ zoomOutBtn.className = 'moodboard-zoombar__button';
20
+ zoomOutBtn.title = 'Уменьшить масштаб';
21
+ zoomOutBtn.textContent = '−';
22
+ zoomOutBtn.dataset.action = 'zoom-out';
23
+
24
+ const label = document.createElement('span');
25
+ label.className = 'moodboard-zoombar__label';
26
+ const value = document.createElement('span');
27
+ value.className = 'moodboard-zoombar__label-value';
28
+ value.textContent = '100%';
29
+ const caret = document.createElement('span');
30
+ caret.className = 'moodboard-zoombar__label-caret';
31
+ caret.textContent = '▾';
32
+ label.appendChild(value);
33
+ label.appendChild(caret);
34
+ this.labelEl = label;
35
+ this.valueEl = value;
36
+
37
+ const zoomInBtn = document.createElement('button');
38
+ zoomInBtn.className = 'moodboard-zoombar__button';
39
+ zoomInBtn.title = 'Увеличить масштаб';
40
+ zoomInBtn.textContent = '+';
41
+ zoomInBtn.dataset.action = 'zoom-in';
42
+
43
+ this.element.appendChild(zoomOutBtn);
44
+ this.element.appendChild(label);
45
+ this.element.appendChild(zoomInBtn);
46
+
47
+ this.container.appendChild(this.element);
48
+ }
49
+
50
+ attach() {
51
+ this.element.addEventListener('click', (e) => {
52
+ const btn = e.target.closest('.moodboard-zoombar__button');
53
+ if (!btn) return;
54
+ const action = btn.dataset.action;
55
+ if (action === 'zoom-in') this.eventBus.emit(Events.UI.ZoomIn);
56
+ else if (action === 'zoom-out') this.eventBus.emit(Events.UI.ZoomOut);
57
+ });
58
+
59
+ // Выпадающее меню по клику на процент
60
+ this.labelEl.addEventListener('click', (e) => {
61
+ e.stopPropagation();
62
+ if (this.menuEl) {
63
+ this.hideMenu();
64
+ } else {
65
+ this.showMenu();
66
+ }
67
+ });
68
+
69
+ document.addEventListener('mousedown', (e) => {
70
+ if (!this.menuEl) return;
71
+ if (this.element.contains(e.target)) return;
72
+ this.hideMenu();
73
+ });
74
+
75
+ this.eventBus.on(Events.UI.ZoomPercent, ({ percentage }) => {
76
+ if (this.valueEl) this.valueEl.textContent = `${percentage}%`;
77
+ });
78
+ }
79
+
80
+ showMenu() {
81
+ this.menuEl = document.createElement('div');
82
+ this.menuEl.className = 'moodboard-zoombar__menu';
83
+ this.menuEl.innerHTML = '';
84
+
85
+ const addItem = (label, onClick) => {
86
+ const item = document.createElement('div');
87
+ item.className = 'moodboard-zoombar__menu-item';
88
+ item.textContent = label;
89
+ item.addEventListener('click', (e) => {
90
+ e.stopPropagation();
91
+ this.hideMenu();
92
+ onClick();
93
+ });
94
+ this.menuEl.appendChild(item);
95
+ };
96
+
97
+ addItem('По размеру экрана', () => this.eventBus.emit(Events.UI.ZoomFit));
98
+ addItem('К выделению', () => this.eventBus.emit(Events.UI.ZoomSelection));
99
+ addItem('100%', () => this.eventBus.emit(Events.UI.ZoomReset));
100
+
101
+ this.element.appendChild(this.menuEl);
102
+ }
103
+
104
+ hideMenu() {
105
+ if (this.menuEl) this.menuEl.remove();
106
+ this.menuEl = null;
107
+ }
108
+
109
+ destroy() {
110
+ if (this.element) this.element.remove();
111
+ this.element = null;
112
+ this.labelEl = null;
113
+ }
114
+ }
115
+
116
+