@sequent-org/moodboard 1.4.32 → 1.4.33
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 +5 -1
- package/src/assets/fonts/inter/inter-cyrillic-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-cyrillic-500-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-500-normal.woff2 +0 -0
- package/src/assets/icons/attachments.svg +3 -1
- package/src/assets/icons/comments.svg +2 -2
- package/src/assets/icons/connector.svg +6 -0
- package/src/assets/icons/emoji.svg +6 -1
- package/src/assets/icons/frame.svg +4 -1
- package/src/assets/icons/image.svg +5 -1
- package/src/assets/icons/laser.svg +1 -0
- package/src/assets/icons/lasso.svg +5 -0
- package/src/assets/icons/mindmap.svg +10 -2
- package/src/assets/icons/note.svg +4 -1
- package/src/assets/icons/pan.svg +5 -2
- package/src/assets/icons/pencil.svg +4 -1
- package/src/assets/icons/reactions.svg +5 -0
- package/src/assets/icons/redo.svg +3 -2
- package/src/assets/icons/select.svg +2 -8
- package/src/assets/icons/shapes.svg +5 -1
- package/src/assets/icons/text-add.svg +15 -1
- package/src/assets/icons/undo.svg +3 -2
- package/src/assets/reactions/1f44d.svg +20 -0
- package/src/assets/reactions/1f44e.svg +20 -0
- package/src/assets/reactions/2705.svg +20 -0
- package/src/assets/reactions/274c.svg +19 -0
- package/src/assets/reactions/2753.svg +20 -0
- package/src/assets/reactions/2764.svg +22 -0
- package/src/assets/reactions/2b50.svg +19 -0
- package/src/assets/reactions/plus-one.svg +25 -0
- package/src/core/PixiEngine.js +23 -0
- package/src/core/bootstrap/CoreInitializer.js +43 -0
- package/src/core/commands/GroupDeleteCommand.js +13 -1
- package/src/core/commands/UpdateShapeStyleCommand.js +121 -0
- package/src/core/commands/UpdateTextStyleCommand.js +17 -6
- package/src/core/commands/index.js +3 -0
- package/src/core/events/Events.js +22 -0
- package/src/core/flows/LayerAndViewportFlow.js +1 -0
- package/src/core/flows/ObjectLifecycleFlow.js +155 -7
- package/src/core/index.js +28 -1
- package/src/grid/CrossGridZoomPhases.js +3 -3
- package/src/initNoBundler.js +1 -1
- package/src/moodboard/DataManager.js +28 -0
- package/src/moodboard/MoodBoard.js +27 -0
- package/src/moodboard/bootstrap/MoodBoardInitializer.js +69 -1
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +22 -4
- package/src/moodboard/integration/MoodBoardEventBindings.js +5 -1
- package/src/moodboard/integration/MoodBoardLoadApi.js +10 -1
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +9 -0
- package/src/objects/ConnectorObject.js +2 -2
- package/src/objects/FrameObject.js +119 -59
- package/src/objects/ShapeObject.js +49 -74
- package/src/objects/shape/ShapeDrawer.js +210 -0
- package/src/services/ConnectorBindingResolver.js +112 -0
- package/src/services/ConnectorRouter.js +210 -0
- package/src/services/comments/CommentService.js +344 -0
- package/src/tools/object-tools/CommentTool.js +85 -0
- package/src/tools/object-tools/DrawingTool.js +110 -10
- package/src/tools/object-tools/LaserPointerTool.js +121 -0
- package/src/tools/object-tools/SelectTool.js +25 -1
- package/src/tools/object-tools/TextTool.js +6 -1
- package/src/tools/object-tools/connector/ConnectorDragController.js +50 -3
- package/src/tools/object-tools/connector/connectorGesture.js +33 -19
- package/src/tools/object-tools/placement/PlacementInputRouter.js +22 -1
- package/src/tools/object-tools/selection/BoxSelectController.js +24 -2
- package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +139 -0
- package/src/tools/object-tools/selection/InlineEditorController.js +12 -0
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +36 -0
- package/src/tools/object-tools/selection/LassoSelectController.js +125 -0
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +1 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +64 -5
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +11 -1
- package/src/tools/object-tools/selection/SelectToolSetup.js +13 -1
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +46 -12
- package/src/tools/object-tools/selection/TextEditorSyncService.js +1 -0
- package/src/tools/object-tools/selection/TextInlineEditorController.js +65 -6
- package/src/ui/CommentPopover.js +6 -0
- package/src/ui/CommentsBar.js +91 -0
- package/src/ui/ConnectorPropertiesPanel.js +150 -0
- package/src/ui/ContextMenu.js +25 -0
- package/src/ui/DrawingPropertiesPanel.js +362 -0
- package/src/ui/FilePropertiesPanel.js +5 -0
- package/src/ui/FramePropertiesPanel.js +5 -0
- package/src/ui/HtmlTextLayer.js +246 -66
- package/src/ui/NotePropertiesPanel.js +6 -0
- package/src/ui/ShapePropertiesPanel.js +307 -0
- package/src/ui/TextPropertiesPanel.js +100 -1
- package/src/ui/Toolbar.js +25 -2
- package/src/ui/Topbar.js +2 -2
- package/src/ui/animation/HoverLiftController.js +6 -7
- package/src/ui/chat/ChatComposer.js +58 -7
- package/src/ui/chat/ChatWindow.js +60 -143
- package/src/ui/comments/CommentListPanel.js +213 -0
- package/src/ui/comments/CommentPinLayer.js +448 -0
- package/src/ui/comments/CommentThreadPopover.js +539 -0
- package/src/ui/comments/commentFormat.js +32 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelBindings.js +223 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelEventBridge.js +114 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +144 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelRenderer.js +447 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelState.js +61 -0
- package/src/ui/connectors/ConnectionAnchorsLayer.js +1 -0
- package/src/ui/connectors/ConnectorHandlesLayer.js +321 -0
- package/src/ui/connectors/ConnectorLabelLayer.js +334 -0
- package/src/ui/connectors/ConnectorLayer.js +264 -57
- package/src/ui/handles/HandlesDomRenderer.js +5 -13
- package/src/ui/handles/HandlesEventBridge.js +1 -0
- package/src/ui/handles/SingleSelectionHandlesController.js +4 -0
- package/src/ui/mindmap/MindmapCollapseLayer.js +1 -0
- package/src/ui/mindmap/MindmapConnectionLayer.js +1 -0
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +6 -0
- package/src/ui/shape-properties/ShapePropertiesPanelDom.js +533 -0
- package/src/ui/shape-properties/ShapePropertiesPanelSync.js +132 -0
- package/src/ui/styles/chat.css +709 -19
- package/src/ui/styles/index.css +1 -0
- package/src/ui/styles/panels.css +112 -2
- package/src/ui/styles/shape-properties-panel.css +250 -0
- package/src/ui/styles/toolbar.css +7 -2
- package/src/ui/styles/topbar.css +1 -1
- package/src/ui/styles/workspace.css +257 -6
- package/src/ui/text-properties/TextFormatControls.js +88 -0
- package/src/ui/text-properties/TextListRenderer.js +137 -0
- package/src/ui/text-properties/TextPropertiesPanelBindings.js +27 -0
- package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +3 -1
- package/src/ui/text-properties/TextPropertiesPanelMapper.js +56 -0
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +24 -0
- package/src/ui/text-properties/TextPropertiesPanelState.js +8 -0
- package/src/ui/toolbar/ReactionsPopupController.js +88 -0
- package/src/ui/toolbar/ToolbarActionRouter.js +71 -5
- package/src/ui/toolbar/ToolbarPopupsController.js +120 -118
- package/src/ui/toolbar/ToolbarRenderer.js +9 -1
- package/src/ui/toolbar/ToolbarStateController.js +4 -1
- package/src/utils/iconLoader.js +17 -16
- package/src/utils/markdown.js +14 -0
- package/src/utils/richText.js +125 -0
|
@@ -2,6 +2,73 @@ import { CoreMoodBoard } from '../../core/index.js';
|
|
|
2
2
|
import { createMoodBoardManagers, wireMoodBoardServices } from './MoodBoardManagersFactory.js';
|
|
3
3
|
import { createMoodBoardUi } from './MoodBoardUiFactory.js';
|
|
4
4
|
import { bindSaveCallbacks } from '../integration/MoodBoardEventBindings.js';
|
|
5
|
+
import { CommentService } from '../../services/comments/CommentService.js';
|
|
6
|
+
import { CommentPinLayer } from '../../ui/comments/CommentPinLayer.js';
|
|
7
|
+
import { CommentsBar } from '../../ui/CommentsBar.js';
|
|
8
|
+
import { CommentThreadPopover } from '../../ui/comments/CommentThreadPopover.js';
|
|
9
|
+
import { CommentTool } from '../../tools/object-tools/CommentTool.js';
|
|
10
|
+
import { CommentListPanel } from '../../ui/comments/CommentListPanel.js';
|
|
11
|
+
|
|
12
|
+
export async function wireCommentsSubsystem(board) {
|
|
13
|
+
if (!board.options.enableComments || !board.options.comments) return;
|
|
14
|
+
|
|
15
|
+
const core = board.coreMoodboard;
|
|
16
|
+
const boardId = board.options.boardId || 'workspace-board';
|
|
17
|
+
|
|
18
|
+
board.commentService = new CommentService({
|
|
19
|
+
eventBus: core.eventBus,
|
|
20
|
+
boardId,
|
|
21
|
+
adapter: board.options.comments,
|
|
22
|
+
currentUser: board.options.currentUser || null,
|
|
23
|
+
});
|
|
24
|
+
board.commentService.attach();
|
|
25
|
+
|
|
26
|
+
board.commentThreadPopover = new CommentThreadPopover(
|
|
27
|
+
board.canvasContainer,
|
|
28
|
+
core.eventBus,
|
|
29
|
+
core,
|
|
30
|
+
board.commentService
|
|
31
|
+
);
|
|
32
|
+
board.commentThreadPopover.attach();
|
|
33
|
+
|
|
34
|
+
board.commentPinLayer = new CommentPinLayer(
|
|
35
|
+
board.canvasContainer,
|
|
36
|
+
core.eventBus,
|
|
37
|
+
core,
|
|
38
|
+
board.commentService
|
|
39
|
+
);
|
|
40
|
+
board.commentPinLayer.attach();
|
|
41
|
+
|
|
42
|
+
board.commentsBar = new CommentsBar(board.workspaceElement, core.eventBus);
|
|
43
|
+
board.commentsBar.attach();
|
|
44
|
+
|
|
45
|
+
board.commentListPanel = new CommentListPanel(board.workspaceElement, core.eventBus, core, board.commentService);
|
|
46
|
+
board.commentListPanel.attach();
|
|
47
|
+
|
|
48
|
+
const commentTool = new CommentTool(
|
|
49
|
+
core.eventBus,
|
|
50
|
+
core,
|
|
51
|
+
board.commentService,
|
|
52
|
+
board.commentThreadPopover
|
|
53
|
+
);
|
|
54
|
+
core.toolManager.registerTool(commentTool);
|
|
55
|
+
|
|
56
|
+
board.comments = {
|
|
57
|
+
applyRemote: (event) => board.commentService.applyRemote(event),
|
|
58
|
+
openThread: (threadId) => board.commentService.openThread(threadId),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await board.commentService.loadInitial();
|
|
63
|
+
board.commentPinLayer.rebuild();
|
|
64
|
+
const initialThreadId = board.options.initialThreadId;
|
|
65
|
+
if (initialThreadId != null) {
|
|
66
|
+
board.commentService.openThread(Number(initialThreadId));
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('Comments load failed:', err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
5
72
|
|
|
6
73
|
export async function initCoreMoodBoard(board) {
|
|
7
74
|
const canvasSize = board.workspaceManager.getCanvasSize();
|
|
@@ -10,7 +77,7 @@ export async function initCoreMoodBoard(board) {
|
|
|
10
77
|
boardId: board.options.boardId || 'workspace-board',
|
|
11
78
|
width: canvasSize.width,
|
|
12
79
|
height: canvasSize.height,
|
|
13
|
-
backgroundColor: board.options.theme === 'dark' ? 0x2a2a2a :
|
|
80
|
+
backgroundColor: board.options.theme === 'dark' ? 0x2a2a2a : 0xDAEEFB,
|
|
14
81
|
saveEndpoint: board.options.saveEndpoint,
|
|
15
82
|
loadEndpoint: board.options.loadEndpoint,
|
|
16
83
|
};
|
|
@@ -34,6 +101,7 @@ export async function initializeMoodBoard(board) {
|
|
|
34
101
|
await initCoreMoodBoard(board);
|
|
35
102
|
createMoodBoardManagers(board);
|
|
36
103
|
createMoodBoardUi(board);
|
|
104
|
+
await wireCommentsSubsystem(board);
|
|
37
105
|
wireMoodBoardServices(board);
|
|
38
106
|
bindSaveCallbacks(board);
|
|
39
107
|
|
|
@@ -10,13 +10,17 @@ import { MindmapHtmlTextLayer } from '../../ui/mindmap/MindmapHtmlTextLayer.js';
|
|
|
10
10
|
import { MindmapConnectionLayer } from '../../ui/mindmap/MindmapConnectionLayer.js';
|
|
11
11
|
import { MindmapCollapseLayer } from '../../ui/mindmap/MindmapCollapseLayer.js';
|
|
12
12
|
import { ConnectorLayer } from '../../ui/connectors/ConnectorLayer.js';
|
|
13
|
+
import { ConnectorLabelLayer } from '../../ui/connectors/ConnectorLabelLayer.js';
|
|
13
14
|
import { ConnectionAnchorsLayer } from '../../ui/connectors/ConnectionAnchorsLayer.js';
|
|
15
|
+
import { ConnectorHandlesLayer } from '../../ui/connectors/ConnectorHandlesLayer.js';
|
|
14
16
|
import { HtmlHandlesLayer } from '../../ui/HtmlHandlesLayer.js';
|
|
15
|
-
import { CommentPopover } from '../../ui/CommentPopover.js';
|
|
16
17
|
import { TextPropertiesPanel } from '../../ui/TextPropertiesPanel.js';
|
|
17
18
|
import { FramePropertiesPanel } from '../../ui/FramePropertiesPanel.js';
|
|
18
19
|
import { NotePropertiesPanel } from '../../ui/NotePropertiesPanel.js';
|
|
19
20
|
import { FilePropertiesPanel } from '../../ui/FilePropertiesPanel.js';
|
|
21
|
+
import { ConnectorPropertiesPanel } from '../../ui/ConnectorPropertiesPanel.js';
|
|
22
|
+
import { ShapePropertiesPanel } from '../../ui/ShapePropertiesPanel.js';
|
|
23
|
+
import { DrawingPropertiesPanel } from '../../ui/DrawingPropertiesPanel.js';
|
|
20
24
|
import { ChatWindow } from '../../ui/chat/ChatWindow.js';
|
|
21
25
|
import { bindToolbarEvents, bindTopbarEvents } from '../integration/MoodBoardEventBindings.js';
|
|
22
26
|
|
|
@@ -29,6 +33,7 @@ function initToolbar(board) {
|
|
|
29
33
|
emojiBasePath: board.options.emojiBasePath || null,
|
|
30
34
|
}
|
|
31
35
|
);
|
|
36
|
+
board.toolbar.enableComments = !!board.options.enableComments;
|
|
32
37
|
|
|
33
38
|
if (typeof window !== 'undefined') {
|
|
34
39
|
window.reloadIcon = (iconName) => board.toolbar.reloadToolbarIcon(iconName);
|
|
@@ -88,6 +93,9 @@ function initContextMenu(board) {
|
|
|
88
93
|
board.canvasContainer,
|
|
89
94
|
board.coreMoodboard.eventBus
|
|
90
95
|
);
|
|
96
|
+
if (board.options.enableComments) {
|
|
97
|
+
board.contextMenu.setEnableComments(true);
|
|
98
|
+
}
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
function initChatWindow(board) {
|
|
@@ -110,10 +118,18 @@ function initHtmlLayersAndPanels(board) {
|
|
|
110
118
|
|
|
111
119
|
board.connectorLayer = new ConnectorLayer(board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
112
120
|
board.connectorLayer.attach();
|
|
121
|
+
board.coreMoodboard.connectorLayer = board.connectorLayer;
|
|
122
|
+
|
|
123
|
+
board.connectorLabelLayer = new ConnectorLabelLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
124
|
+
board.connectorLabelLayer.attach();
|
|
125
|
+
board.coreMoodboard.connectorLabelLayer = board.connectorLabelLayer;
|
|
113
126
|
|
|
114
127
|
board.connectionAnchorsLayer = new ConnectionAnchorsLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
115
128
|
board.connectionAnchorsLayer.attach();
|
|
116
129
|
|
|
130
|
+
board.connectorHandlesLayer = new ConnectorHandlesLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
131
|
+
board.connectorHandlesLayer.attach();
|
|
132
|
+
|
|
117
133
|
board.htmlHandlesLayer = new HtmlHandlesLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
118
134
|
board.htmlHandlesLayer.attach();
|
|
119
135
|
|
|
@@ -123,19 +139,21 @@ function initHtmlLayersAndPanels(board) {
|
|
|
123
139
|
window.moodboardMindmapConnectionLayer = board.mindmapConnectionLayer;
|
|
124
140
|
window.moodboardMindmapCollapseLayer = board.mindmapCollapseLayer;
|
|
125
141
|
window.moodboardConnectorLayer = board.connectorLayer;
|
|
142
|
+
window.moodboardConnectorLabelLayer = board.connectorLabelLayer;
|
|
126
143
|
window.moodboardConnectionAnchorsLayer = board.connectionAnchorsLayer;
|
|
144
|
+
window.moodboardConnectorHandlesLayer = board.connectorHandlesLayer;
|
|
127
145
|
window.moodboardHtmlHandlesLayer = board.htmlHandlesLayer;
|
|
128
146
|
}
|
|
129
147
|
|
|
130
|
-
board.commentPopover = new CommentPopover(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
131
|
-
board.commentPopover.attach();
|
|
132
|
-
|
|
133
148
|
board.textPropertiesPanel = new TextPropertiesPanel(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
134
149
|
board.textPropertiesPanel.attach();
|
|
135
150
|
|
|
136
151
|
board.framePropertiesPanel = new FramePropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
137
152
|
board.notePropertiesPanel = new NotePropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
138
153
|
board.filePropertiesPanel = new FilePropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
154
|
+
board.connectorPropertiesPanel = new ConnectorPropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
155
|
+
board.shapePropertiesPanel = new ShapePropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
156
|
+
board.drawingPropertiesPanel = new DrawingPropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
139
157
|
}
|
|
140
158
|
|
|
141
159
|
export function createMoodBoardUi(board) {
|
|
@@ -54,7 +54,11 @@ export function bindToolbarEvents(board) {
|
|
|
54
54
|
}
|
|
55
55
|
const createdObject = board.actionHandler.handleToolbarAction(action);
|
|
56
56
|
if (createdObject?.id) {
|
|
57
|
-
|
|
57
|
+
// Рисунки карандашом/кистью не выделяем и не переключаем инструмент:
|
|
58
|
+
// пользователь должен сразу продолжить рисовать следующий штрих.
|
|
59
|
+
if (action?.type !== 'drawing') {
|
|
60
|
+
focusCreatedObject(board, createdObject);
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
});
|
|
60
64
|
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { Events } from '../../core/events/Events.js';
|
|
2
2
|
|
|
3
|
+
const BOARD_DEFAULTS = {
|
|
4
|
+
backgroundColor: '#daeefb',
|
|
5
|
+
grid: { type: 'cross' },
|
|
6
|
+
};
|
|
7
|
+
|
|
3
8
|
function getSeedData(board) {
|
|
4
|
-
|
|
9
|
+
const data = board.data || { objects: [] };
|
|
10
|
+
if (!data.settings) {
|
|
11
|
+
return { ...data, settings: { ...BOARD_DEFAULTS } };
|
|
12
|
+
}
|
|
13
|
+
return data;
|
|
5
14
|
}
|
|
6
15
|
|
|
7
16
|
function invokeOnLoad(board, payload) {
|
|
@@ -41,6 +41,9 @@ export function destroyMoodBoard(board) {
|
|
|
41
41
|
safeDestroy(board.filePropertiesPanel, 'filePropertiesPanel');
|
|
42
42
|
board.filePropertiesPanel = null;
|
|
43
43
|
|
|
44
|
+
safeDestroy(board.drawingPropertiesPanel, 'drawingPropertiesPanel');
|
|
45
|
+
board.drawingPropertiesPanel = null;
|
|
46
|
+
|
|
44
47
|
safeDestroy(board.alignmentGuides, 'alignmentGuides');
|
|
45
48
|
board.alignmentGuides = null;
|
|
46
49
|
|
|
@@ -64,6 +67,9 @@ export function destroyMoodBoard(board) {
|
|
|
64
67
|
safeDestroy(board.connectionAnchorsLayer, 'connectionAnchorsLayer');
|
|
65
68
|
board.connectionAnchorsLayer = null;
|
|
66
69
|
|
|
70
|
+
safeDestroy(board.connectorHandlesLayer, 'connectorHandlesLayer');
|
|
71
|
+
board.connectorHandlesLayer = null;
|
|
72
|
+
|
|
67
73
|
safeDestroy(board.htmlHandlesLayer, 'htmlHandlesLayer');
|
|
68
74
|
board.htmlHandlesLayer = null;
|
|
69
75
|
|
|
@@ -118,6 +124,9 @@ export function destroyMoodBoard(board) {
|
|
|
118
124
|
if (window.moodboardConnectionAnchorsLayer === board.connectionAnchorsLayer) {
|
|
119
125
|
window.moodboardConnectionAnchorsLayer = null;
|
|
120
126
|
}
|
|
127
|
+
if (window.moodboardConnectorHandlesLayer === board.connectorHandlesLayer) {
|
|
128
|
+
window.moodboardConnectorHandlesLayer = null;
|
|
129
|
+
}
|
|
121
130
|
if (window.moodboardHtmlHandlesLayer === board.htmlHandlesLayer) {
|
|
122
131
|
window.moodboardHtmlHandlesLayer = null;
|
|
123
132
|
}
|
|
@@ -40,22 +40,28 @@ export class FrameObject {
|
|
|
40
40
|
this.graphics = new PIXI.Graphics();
|
|
41
41
|
this.container.addChild(this.graphics);
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
this.baseFontSize = 14;
|
|
45
|
-
this.currentWorldScale = 1.0;
|
|
46
|
-
this.originalTitle = this.title;
|
|
43
|
+
// Заголовок фрейма — слой над верхней границей с собственной подложкой
|
|
44
|
+
this.baseFontSize = 14;
|
|
45
|
+
this.currentWorldScale = 1.0;
|
|
46
|
+
this.originalTitle = this.title;
|
|
47
|
+
|
|
48
|
+
// Под-контейнер: масштаб компенсирует зум, поэтому заголовок всегда одного размера на экране
|
|
49
|
+
this.titleLayer = new PIXI.Container();
|
|
50
|
+
this.titleLayer.eventMode = 'none'; // не перехватывать указатель
|
|
51
|
+
|
|
52
|
+
this.titleBg = new PIXI.Graphics();
|
|
53
|
+
this.titleLayer.addChild(this.titleBg);
|
|
54
|
+
|
|
47
55
|
this.titleText = new PIXI.Text(this.title, {
|
|
48
|
-
fontFamily: 'Arial, sans-serif',
|
|
56
|
+
fontFamily: 'Inter, Arial, sans-serif',
|
|
49
57
|
fontSize: this.baseFontSize,
|
|
50
58
|
fill: 0x333333,
|
|
51
|
-
fontWeight: '
|
|
59
|
+
fontWeight: '500'
|
|
52
60
|
});
|
|
53
|
-
// Размещаем заголовок внутри верхней части фрейма, чтобы не влиять на внешние границы
|
|
54
61
|
this.titleText.anchor.set(0, 0);
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
this.container.addChild(this.titleText);
|
|
62
|
+
this.titleLayer.addChild(this.titleText);
|
|
63
|
+
|
|
64
|
+
this.container.addChild(this.titleLayer);
|
|
59
65
|
|
|
60
66
|
// Подписываемся на события зума для компенсации масштабирования заголовка
|
|
61
67
|
if (this.eventBus) {
|
|
@@ -69,6 +75,24 @@ export class FrameObject {
|
|
|
69
75
|
this.eventBus.on(Events.Tool.SelectionClear, this._boundOnSelectionClear);
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
// Логические габариты фрейма = только прямоугольник, без плавающего
|
|
79
|
+
// заголовка над верхней границей. Заголовок — отдельный слой с
|
|
80
|
+
// отрицательным y, и по умолчанию он раздул бы getLocalBounds вверх.
|
|
81
|
+
// Через него width/height контейнера считает GetObjectPosition
|
|
82
|
+
// (position.y = centerY - height/2) — лишняя высота сверху уводила
|
|
83
|
+
// рамку выделения вверх. Переопределяем getLocalBounds, чтобы
|
|
84
|
+
// width/height отражали именно прямоугольник. getBounds НЕ трогаем:
|
|
85
|
+
// hover-lift DropShadowFilter берёт область из getBounds, и заголовок
|
|
86
|
+
// не должен обрезаться фильтром.
|
|
87
|
+
this.container.getLocalBounds = (rect) => {
|
|
88
|
+
const b = rect || new PIXI.Rectangle();
|
|
89
|
+
b.x = 0;
|
|
90
|
+
b.y = 0;
|
|
91
|
+
b.width = this.width;
|
|
92
|
+
b.height = this.height;
|
|
93
|
+
return b;
|
|
94
|
+
};
|
|
95
|
+
|
|
72
96
|
this._draw(this.width, this.height, this.fillColor);
|
|
73
97
|
// Применяем начальный масштаб и обрезку заголовка
|
|
74
98
|
this._updateTitleScale();
|
|
@@ -116,6 +140,27 @@ export class FrameObject {
|
|
|
116
140
|
if (data?.objects?.includes(myId)) this.setBorderVisible(true);
|
|
117
141
|
}
|
|
118
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Применить текущий масштаб мира к заголовку.
|
|
145
|
+
* Нужно при создании объекта: viewport-зум восстанавливается раньше,
|
|
146
|
+
* чем фрейм успевает подписаться на ZoomPercent, поэтому стартовый зум
|
|
147
|
+
* до него не доходит и заголовок остаётся в мировом масштабе (мелкий).
|
|
148
|
+
* @param {number} worldScale Текущий масштаб мира (world.scale.x)
|
|
149
|
+
*/
|
|
150
|
+
applyWorldScale(worldScale) {
|
|
151
|
+
if (typeof worldScale !== 'number' || !(worldScale > 0)) return;
|
|
152
|
+
this.currentWorldScale = worldScale;
|
|
153
|
+
this._updateTitleScale();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
hideTitle() {
|
|
157
|
+
if (this.titleLayer) this.titleLayer.visible = false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
showTitle() {
|
|
161
|
+
if (this.titleLayer) this.titleLayer.visible = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
119
164
|
/**
|
|
120
165
|
* Установить заголовок фрейма
|
|
121
166
|
* @param {string} title Новый заголовок
|
|
@@ -213,98 +258,113 @@ export class FrameObject {
|
|
|
213
258
|
}
|
|
214
259
|
|
|
215
260
|
/**
|
|
216
|
-
*
|
|
261
|
+
* Масштаб и позиция слоя заголовка — компенсируем зум, держим постоянный экранный размер
|
|
217
262
|
*/
|
|
218
263
|
_updateTitleScale() {
|
|
219
|
-
if (!this.
|
|
220
|
-
|
|
221
|
-
// Компенсируем зум мира обратным масштабированием заголовка
|
|
264
|
+
if (!this.titleLayer) return;
|
|
265
|
+
|
|
222
266
|
const compensationScale = 1 / this.currentWorldScale;
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
this.
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
this.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
//
|
|
267
|
+
|
|
268
|
+
// Весь слой масштабируется обратно → содержимое выглядит одинаково на любом зуме
|
|
269
|
+
this.titleLayer.scale.set(compensationScale);
|
|
270
|
+
|
|
271
|
+
// Высота подложки в базовых пикселях: baseFontSize + 4px сверху + 4px снизу
|
|
272
|
+
const labelBaseH = this.baseFontSize + 8;
|
|
273
|
+
const gap = 4; // зазор между нижним краем подписи и верхней границей фрейма
|
|
274
|
+
|
|
275
|
+
// Позиционируем над фреймом (y=0 — верхний край фрейма в локальных координатах контейнера)
|
|
276
|
+
this.titleLayer.x = 0;
|
|
277
|
+
this.titleLayer.y = -Math.round((labelBaseH + gap) * compensationScale);
|
|
278
|
+
|
|
232
279
|
this._updateTitleText();
|
|
233
280
|
}
|
|
234
281
|
|
|
235
282
|
/**
|
|
236
|
-
* Обновить текст заголовка
|
|
283
|
+
* Обновить текст заголовка и перерисовать подложку
|
|
237
284
|
*/
|
|
238
285
|
_updateTitleText() {
|
|
239
286
|
if (!this.titleText) return;
|
|
240
287
|
|
|
241
288
|
const truncatedText = this._truncateTextToFit(this.originalTitle);
|
|
242
289
|
this.titleText.text = truncatedText;
|
|
290
|
+
this._redrawTitleBg();
|
|
243
291
|
}
|
|
244
292
|
|
|
245
293
|
/**
|
|
246
|
-
*
|
|
294
|
+
* Нарисовать скруглённую подложку под текущую ширину текста
|
|
295
|
+
*/
|
|
296
|
+
_redrawTitleBg() {
|
|
297
|
+
if (!this.titleBg || !this.titleText) return;
|
|
298
|
+
|
|
299
|
+
const padH = 8; // горизонтальный отступ с каждой стороны
|
|
300
|
+
const padV = 4; // вертикальный отступ с каждой стороны
|
|
301
|
+
|
|
302
|
+
// Измеряем текст в базовых единицах
|
|
303
|
+
const style = new PIXI.TextStyle({
|
|
304
|
+
fontFamily: this.titleText.style.fontFamily,
|
|
305
|
+
fontSize: this.baseFontSize,
|
|
306
|
+
fontWeight: this.titleText.style.fontWeight
|
|
307
|
+
});
|
|
308
|
+
const metrics = PIXI.TextMetrics.measureText(this.titleText.text || '', style);
|
|
309
|
+
|
|
310
|
+
const bgW = Math.max(1, Math.round(metrics.width + padH * 2));
|
|
311
|
+
const bgH = Math.round(this.baseFontSize + padV * 2);
|
|
312
|
+
|
|
313
|
+
const g = this.titleBg;
|
|
314
|
+
g.clear();
|
|
315
|
+
try {
|
|
316
|
+
g.lineStyle({ width: 1, color: this.strokeColor, alpha: 1 });
|
|
317
|
+
} catch (_) {
|
|
318
|
+
g.lineStyle(1, this.strokeColor, 1);
|
|
319
|
+
}
|
|
320
|
+
g.beginFill(0xFFFFFF, 1);
|
|
321
|
+
g.drawRoundedRect(0, 0, bgW, bgH, 6);
|
|
322
|
+
g.endFill();
|
|
323
|
+
|
|
324
|
+
// Текст внутри подложки
|
|
325
|
+
this.titleText.x = padH;
|
|
326
|
+
this.titleText.y = padV;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Обрезать текст до ширины фрейма (потолок) с добавлением многоточия.
|
|
331
|
+
* Сравниваем в базовых пикселях — слой уже компенсирует зум отдельно.
|
|
247
332
|
* @param {string} text Исходный текст
|
|
248
|
-
* @returns {string} Обрезанный текст
|
|
333
|
+
* @returns {string} Обрезанный текст или оригинал
|
|
249
334
|
*/
|
|
250
335
|
_truncateTextToFit(text) {
|
|
251
336
|
if (!text || !this.titleText) return text;
|
|
252
337
|
|
|
253
|
-
//
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
// Доступная ширина = ширина фрейма - отступы слева и справа (с учетом масштаба)
|
|
257
|
-
const leftPadding = 8 * compensationScale;
|
|
258
|
-
const rightPadding = 8 * compensationScale;
|
|
259
|
-
const availableWidth = this.width - leftPadding - rightPadding;
|
|
338
|
+
// Подложка не должна быть шире самого фрейма (8px паддинг с каждой стороны)
|
|
339
|
+
const availableWidth = Math.max(1, this.width - 16);
|
|
260
340
|
|
|
261
|
-
// Создаем временный стиль для измерения текста
|
|
262
|
-
// Используем базовый размер шрифта, а масштаб учтем отдельно
|
|
263
341
|
const style = new PIXI.TextStyle({
|
|
264
342
|
fontFamily: this.titleText.style.fontFamily,
|
|
265
343
|
fontSize: this.baseFontSize,
|
|
266
344
|
fontWeight: this.titleText.style.fontWeight
|
|
267
345
|
});
|
|
268
346
|
|
|
269
|
-
// Измеряем ширину оригинального текста с учетом масштаба
|
|
270
347
|
const textMetrics = PIXI.TextMetrics.measureText(text, style);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// Если текст помещается, возвращаем его как есть
|
|
274
|
-
if (scaledTextWidth <= availableWidth) {
|
|
275
|
-
return text;
|
|
276
|
-
}
|
|
348
|
+
if (textMetrics.width <= availableWidth) return text;
|
|
277
349
|
|
|
278
|
-
// Измеряем ширину многоточия с учетом масштаба
|
|
279
350
|
const ellipsisMetrics = PIXI.TextMetrics.measureText('...', style);
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
// Доступная ширина для текста без многоточия
|
|
283
|
-
const textAvailableWidth = availableWidth - ellipsisWidth;
|
|
284
|
-
|
|
285
|
-
if (textAvailableWidth <= 0) {
|
|
286
|
-
return '...';
|
|
287
|
-
}
|
|
351
|
+
const textAvailableWidth = availableWidth - ellipsisMetrics.width;
|
|
352
|
+
if (textAvailableWidth <= 0) return '...';
|
|
288
353
|
|
|
289
|
-
// Бинарный поиск оптимальной длины текста
|
|
290
354
|
let left = 0;
|
|
291
355
|
let right = text.length;
|
|
292
356
|
let result = '';
|
|
293
|
-
|
|
294
357
|
while (left <= right) {
|
|
295
358
|
const mid = Math.floor((left + right) / 2);
|
|
296
359
|
const subText = text.substring(0, mid);
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (scaledSubTextWidth <= textAvailableWidth) {
|
|
360
|
+
const subMetrics = PIXI.TextMetrics.measureText(subText, style);
|
|
361
|
+
if (subMetrics.width <= textAvailableWidth) {
|
|
301
362
|
result = subText;
|
|
302
363
|
left = mid + 1;
|
|
303
364
|
} else {
|
|
304
365
|
right = mid - 1;
|
|
305
366
|
}
|
|
306
367
|
}
|
|
307
|
-
|
|
308
368
|
return result + '...';
|
|
309
369
|
}
|
|
310
370
|
|