wisemapping-frontend/packages/mindplot/src/components/Designer.ts

1031 lines
30 KiB
TypeScript
Raw Normal View History

2021-07-16 16:41:58 +02:00
/*
2021-12-25 23:39:34 +01:00
* Copyright [2021] [wisemapping]
2021-07-16 16:41:58 +02:00
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
2021-12-04 01:11:17 +01:00
import { $assert, $defined } from '@wisemapping/core-js';
2021-12-15 02:54:11 +01:00
import Messages, { $msg } from './Messages';
2021-12-03 19:58:25 +01:00
import Events from './Events';
2021-12-04 01:11:17 +01:00
import StandaloneActionDispatcher from './StandaloneActionDispatcher';
2021-07-16 16:41:58 +02:00
2021-12-04 01:11:17 +01:00
import CommandContext from './CommandContext';
import ActionDispatcher from './ActionDispatcher';
2021-07-16 16:41:58 +02:00
import DesignerModel from './DesignerModel';
import DesignerKeyboard from './DesignerKeyboard';
2021-07-16 16:41:58 +02:00
import ScreenManager from './ScreenManager';
import Workspace from './Workspace';
2021-07-16 16:41:58 +02:00
import DragConnector from './DragConnector';
import DragManager from './DragManager';
import RelationshipPivot from './RelationshipPivot';
import Relationship from './Relationship';
2021-07-16 16:41:58 +02:00
import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher';
import TopicFeatureFactory from './TopicFeature';
2021-07-16 16:41:58 +02:00
2021-12-03 19:58:25 +01:00
import { create } from './NodeGraphUtils';
2021-07-16 16:41:58 +02:00
import EventBus from './layout/EventBus';
import EventBusDispatcher from './layout/EventBusDispatcher';
2021-12-14 16:08:54 +01:00
import LayoutManager from './layout/LayoutManager';
2021-07-16 16:41:58 +02:00
2022-01-09 00:22:59 +01:00
import { TopicShape } from './model/INodeModel';
2021-12-05 18:25:16 +01:00
import { $notify } from './widget/ToolbarNotifier';
import ImageExpoterFactory from './export/ImageExporterFactory';
import TextExporterFactory from './export/TextExporterFactory';
2022-01-09 00:22:59 +01:00
import RelationshipModel from './model/RelationshipModel';
import { Mindmap } from '..';
import NodeModel from './model/NodeModel';
import Topic from './Topic';
import Point from '@wisemapping/web2d';
import { DesignerOptions } from './DesignerOptions';
2021-07-16 16:41:58 +02:00
2021-12-04 01:11:17 +01:00
class Designer extends Events {
2022-01-09 00:22:59 +01:00
private _mindmap: Mindmap;
private _options: DesignerOptions;
private _actionDispatcher: StandaloneActionDispatcher;
private _model: DesignerModel;
private _workspace: Workspace;
private _eventBussDispatcher: EventBusDispatcher;
private _dragManager: DragManager;
private _relPivot: RelationshipPivot;
private _clipboard: any[];
private _cleanScreen: any;
constructor(options: DesignerOptions, divElement) {
2021-12-04 01:11:17 +01:00
$assert(options, 'options must be defined');
$assert(options.zoom, 'zoom must be defined');
$assert(options.size, 'size must be defined');
$assert(divElement, 'divElement must be defined');
super();
2021-07-16 16:41:58 +02:00
2021-12-04 01:11:17 +01:00
// Set up i18n location ...
Messages.init(options.locale);
2021-07-16 16:41:58 +02:00
2021-12-04 01:11:17 +01:00
this._options = options;
2021-07-16 16:41:58 +02:00
2021-12-04 01:11:17 +01:00
// Set full div elem render area ...
divElement.css(options.size);
2021-07-16 16:41:58 +02:00
2021-12-04 01:11:17 +01:00
// Dispatcher manager ...
const commandContext = new CommandContext(this);
this._actionDispatcher = new StandaloneActionDispatcher(commandContext);
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
const me = this;
this._actionDispatcher.addEvent('modelUpdate', (event) => {
me.fireEvent('modelUpdate', event);
});
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
ActionDispatcher.setInstance(this._actionDispatcher);
this._model = new DesignerModel(options);
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Init Screen manager..
const screenManager = new ScreenManager(divElement);
this._workspace = new Workspace(screenManager, this._model.getZoom(), !!options.readOnly);
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Init layout manager ...
2022-01-09 00:22:59 +01:00
this._eventBussDispatcher = new EventBusDispatcher();
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Register events
if (!this.isReadOnly()) {
// Register mouse events ...
this._registerMouseEvents();
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Register keyboard events ...
DesignerKeyboard.register(this);
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
this._dragManager = this._buildDragManager(this._workspace);
}
this._registerWheelEvents();
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
this._relPivot = new RelationshipPivot(this._workspace, this);
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Set editor working area ...
this.setViewPort(options.viewPort);
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
TopicEventDispatcher.configure(this.isReadOnly());
this._clipboard = [];
2021-12-05 18:25:16 +01:00
// Hack: There are static reference to designer variable. Needs to be reviewed.
global.designer = this;
2021-12-04 01:11:17 +01:00
}
2021-10-05 02:05:34 +02:00
2022-01-09 00:22:59 +01:00
private _registerWheelEvents(): void {
2021-12-04 01:11:17 +01:00
const zoomFactor = 1.006;
document.addEventListener('wheel', (event) => {
if (event.deltaX > 0 || event.deltaY > 0) {
2022-01-09 00:22:59 +01:00
this.zoomOut(zoomFactor);
} else {
2022-01-09 00:22:59 +01:00
this.zoomIn(zoomFactor);
2021-12-04 01:11:17 +01:00
}
event.preventDefault();
}, { passive: false });
2021-12-04 01:11:17 +01:00
}
2022-01-09 00:22:59 +01:00
// @ts-ignore
addEvent(type: string, listener: Function): void {
2021-12-04 01:11:17 +01:00
if (type === TopicEvent.EDIT || type === TopicEvent.CLICK) {
const editor = TopicEventDispatcher.getInstance();
editor.addEvent(type, listener);
} else {
super.addEvent(type, listener);
}
}
2022-01-09 00:22:59 +01:00
private _registerMouseEvents() {
2021-12-04 01:11:17 +01:00
const workspace = this._workspace;
const screenManager = workspace.getScreenManager();
const me = this;
// Initialize workspace event listeners.
screenManager.addEvent('update', () => {
// Topic must be set to his original state. All editors must be closed.
const topics = me.getModel().getTopics();
2021-12-05 17:14:15 +01:00
topics.forEach((object) => {
2021-12-04 01:11:17 +01:00
object.closeEditors();
2021-10-05 02:05:34 +02:00
});
2021-12-04 01:11:17 +01:00
// Clean some selected nodes on event ..
if (me._cleanScreen) me._cleanScreen();
});
// Deselect on click ...
screenManager.addEvent('click', (event) => {
me.onObjectFocusEvent(null, event);
});
// Create nodes on double click...
screenManager.addEvent('dblclick', (event) => {
if (workspace.isWorkspaceEventsEnabled()) {
const mousePos = screenManager.getWorkspaceMousePosition(event);
const centralTopic = me.getModel().getCentralTopic();
const model = me._createChildModel(centralTopic, mousePos);
this._actionDispatcher.addTopics([model], [centralTopic.getId()]);
}
});
}
/**
* @private
* @param {mindplot.Workspace} workspace
* @return {mindplot.DragManager} the new dragManager for the workspace with events
* registered
*/
2022-01-09 00:22:59 +01:00
_buildDragManager(workspace: Workspace) {
2021-12-04 01:11:17 +01:00
const designerModel = this.getModel();
const dragConnector = new DragConnector(designerModel, this._workspace);
const dragManager = new DragManager(workspace, this._eventBussDispatcher);
const topics = designerModel.getTopics();
2021-12-15 02:20:41 +01:00
// Enable all mouse events.
2021-12-04 01:11:17 +01:00
dragManager.addEvent('startdragging', () => {
2021-12-15 02:20:41 +01:00
topics.forEach((topic) => topic.setMouseEventsEnabled(false));
2021-12-04 01:11:17 +01:00
});
dragManager.addEvent('dragging', (event, dragTopic) => {
dragTopic.updateFreeLayout(event);
if (!dragTopic.isFreeLayoutOn(event)) {
// The node is being drag. Is the connection still valid ?
dragConnector.checkConnection(dragTopic);
if (!dragTopic.isVisible() && dragTopic.isConnected()) {
dragTopic.setVisibility(true);
2021-10-05 02:05:34 +02:00
}
}
2021-12-04 01:11:17 +01:00
});
dragManager.addEvent('enddragging', (event, dragTopic) => {
2021-12-15 02:20:41 +01:00
topics.forEach((topic) => topic.setMouseEventsEnabled(true));
2021-12-04 01:11:17 +01:00
dragTopic.applyChanges(workspace);
});
return dragManager;
}
/**
* @param {{width:Number, height:Number}} size
* sets width and height of the workspace
*/
setViewPort(size) {
this._workspace.setViewPort(size);
const model = this.getModel();
this._workspace.setZoom(model.getZoom(), true);
}
/**
* @private
* @param {mindplot.model.NodeModel} model
* @param {Boolean} readOnly
* @return {mindplot.CentralTopic|mindplot.MainTopic} the topic to the given model,
* connected, added to the drag manager, with events registered - complying type & read mode
*/
2022-01-09 00:22:59 +01:00
_buildNodeGraph(model: NodeModel, readOnly: boolean) {
2021-12-04 01:11:17 +01:00
// Create node graph ...
const topic = create(model, { readOnly });
this.getModel().addTopic(topic);
const me = this;
// Add Topic events ...
if (!readOnly) {
// If a node had gained focus, clean the rest of the nodes ...
topic.addEvent('mousedown', (event) => {
me.onObjectFocusEvent(topic, event);
2021-10-05 02:05:34 +02:00
});
2021-12-04 01:11:17 +01:00
// Register node listeners ...
2022-01-02 23:16:18 +01:00
if (topic.getType() !== 'CentralTopic') {
2021-12-04 01:11:17 +01:00
// Central Topic doesn't support to be dragged
this._dragManager.add(topic);
2021-10-05 02:05:34 +02:00
}
2021-12-04 01:11:17 +01:00
}
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Connect Topic ...
const isConnected = model.isConnected();
if (isConnected) {
// Improve this ...
const targetTopicModel = model.getParent();
2021-10-05 02:05:34 +02:00
2021-12-15 02:54:11 +01:00
// Find target topic with the same model ...
2021-12-04 01:11:17 +01:00
const topics = this.getModel().getTopics();
2021-12-15 02:54:11 +01:00
const targetTopic = topics.find((t) => t.getModel() === targetTopicModel);
if (targetTopic) {
model.disconnect();
} else {
$assert(targetTopic, 'Could not find a topic to connect');
2021-10-05 02:05:34 +02:00
}
2021-12-04 01:11:17 +01:00
topic.connectTo(targetTopic, this._workspace);
}
topic.addEvent('ontblur', () => {
const topics = me.getModel().filterSelectedTopics();
const rels = me.getModel().filterSelectedRelationships();
if (topics.length === 0 || rels.length === 0) {
me.fireEvent('onblur');
}
});
topic.addEvent('ontfocus', () => {
const topics = me.getModel().filterSelectedTopics();
const rels = me.getModel().filterSelectedRelationships();
if (topics.length === 1 || rels.length === 1) {
me.fireEvent('onfocus');
}
});
return topic;
}
/**
* @param {?mindplot.Topic} currentObject
* @param {Event=} event
* sets focus to the given currentObject and removes it from any other objects if not
* triggered with Ctrl pressed
*/
2022-01-09 00:22:59 +01:00
onObjectFocusEvent(currentObject: Topic = null, event = null): void {
2021-12-04 01:11:17 +01:00
// Close node editors ..
const topics = this.getModel().getTopics();
2021-12-15 02:20:41 +01:00
topics.forEach((topic) => topic.closeEditors());
2021-12-04 01:11:17 +01:00
const model = this.getModel();
const objects = model.getEntities();
2021-12-05 17:14:15 +01:00
objects.forEach((object) => {
2021-12-04 01:11:17 +01:00
// Disable all nodes on focus but not the current if Ctrl key isn't being pressed
if (!$defined(event) || (!event.ctrlKey && !event.metaKey)) {
if (object.isOnFocus() && object !== currentObject) {
object.setOnFocus(false);
2021-10-05 02:05:34 +02:00
}
}
2021-12-04 01:11:17 +01:00
});
}
/** sets focus to all model entities, i.e. relationships and topics */
2022-01-09 00:22:59 +01:00
selectAll(): void {
2021-12-04 01:11:17 +01:00
const model = this.getModel();
const objects = model.getEntities();
2021-12-05 17:14:15 +01:00
objects.forEach((object) => {
2021-12-04 01:11:17 +01:00
object.setOnFocus(true);
});
}
/** removes focus from all model entities, i.e. relationships and topics */
2022-01-09 00:22:59 +01:00
deselectAll(): void {
2021-12-04 01:11:17 +01:00
const objects = this.getModel().getEntities();
2021-12-05 17:14:15 +01:00
objects.forEach((object) => {
2021-12-04 01:11:17 +01:00
object.setOnFocus(false);
});
}
/**
* Set the zoom of the map
* @param {Number} zoom number between 0.3 and 1.9
*/
2022-01-09 00:22:59 +01:00
setZoom(zoom: number): void {
2021-12-04 01:11:17 +01:00
if (zoom > 1.9 || zoom < 0.3) {
$notify($msg('ZOOM_IN_ERROR'));
return;
}
this.getModel().setZoom(zoom);
this._workspace.setZoom(zoom);
}
/**
* @param {Number=} factor
* zoom out by the given factor, or 1.2, if undefined
*/
2022-01-09 00:22:59 +01:00
zoomOut(factor: number = 1.2) {
2021-12-04 01:11:17 +01:00
const model = this.getModel();
const scale = model.getZoom() * factor;
if (scale <= 1.9) {
model.setZoom(scale);
this._workspace.setZoom(scale);
} else {
$notify($msg('ZOOM_ERROR'));
}
}
2022-01-09 00:22:59 +01:00
export(formatType: 'png' | 'svg' | 'jpg' | 'wxml'): String {
2021-12-31 10:18:21 +01:00
const workspace = this._workspace;
const svgElement = workspace.getSVGElement();
const size = workspace.getSize();
let exporter;
switch (formatType) {
2022-01-04 22:10:29 +01:00
case 'png':
case 'jpg':
case 'svg': {
exporter = ImageExpoterFactory.create(formatType, this._mindmap, svgElement, size.width, size.height);
2021-12-31 10:52:24 +01:00
break;
}
case 'wxml': {
exporter = TextExporterFactory.create(formatType, this._mindmap);
2021-12-31 10:18:21 +01:00
break;
}
default:
throw new Error('Unsupported encoding');
}
2021-12-31 10:18:21 +01:00
return exporter.export();
}
2022-01-09 00:22:59 +01:00
2021-12-04 01:11:17 +01:00
/**
* @param {Number=} factor
* zoom in by the given factor, or 1.2, if undefined
*/
2022-01-09 00:22:59 +01:00
zoomIn(factor: number = 1.2) {
2021-12-04 01:11:17 +01:00
const model = this.getModel();
const scale = model.getZoom() / factor;
if (scale >= 0.3) {
model.setZoom(scale);
this._workspace.setZoom(scale);
} else {
$notify($msg('ZOOM_ERROR'));
}
}
/** copy selected topics to a private clipboard */
2022-01-09 00:22:59 +01:00
copyToClipboard(): void {
2021-12-04 01:11:17 +01:00
let topics = this.getModel().filterSelectedTopics();
if (topics.length <= 0) {
// If there are more than one node selected,
$notify($msg('AT_LEAST_ONE_TOPIC_MUST_BE_SELECTED'));
return;
}
// Exclude central topic ..
topics = topics.filter((topic) => !topic.isCentralTopic());
this._clipboard = topics.map((topic) => {
const nodeModel = topic.getModel().deepCopy();
// Change position to make the new topic evident...
const pos = nodeModel.getPosition();
nodeModel.setPosition(pos.x + 60 * Math.sign(pos.x), pos.y + 30);
return nodeModel;
});
$notify($msg('SELECTION_COPIED_TO_CLIPBOARD'));
}
/** paste clipboard contents to the mindmap */
2022-01-09 00:22:59 +01:00
pasteClipboard(): void {
2021-12-04 01:11:17 +01:00
if (this._clipboard.length === 0) {
$notify($msg('CLIPBOARD_IS_EMPTY'));
return;
}
this._actionDispatcher.addTopics(this._clipboard);
this._clipboard = [];
}
2022-01-09 00:22:59 +01:00
getModel(): DesignerModel {
2021-12-04 01:11:17 +01:00
return this._model;
}
/** collapse the subtree of the selected topic */
shrinkSelectedBranch() {
const nodes = this.getModel().filterSelectedTopics();
if (nodes.length <= 0 || nodes.length !== 1) {
// If there are more than one node selected,
$notify($msg('ONLY_ONE_TOPIC_MUST_BE_SELECTED_COLLAPSE'));
return;
}
// Execute event ...
const topic = nodes[0];
2022-01-02 23:16:18 +01:00
if (topic.getType() !== 'CentralTopic') {
2021-12-04 01:11:17 +01:00
this._actionDispatcher.shrinkBranch([topic.getId()], !topic.areChildrenShrunken());
}
}
/** create a NodeModel for the selected node's child and add it via the ActionDispatcher */
createChildForSelectedNode() {
const nodes = this.getModel().filterSelectedTopics();
if (nodes.length <= 0) {
// If there are more than one node selected,
$notify($msg('ONE_TOPIC_MUST_BE_SELECTED'));
return;
}
if (nodes.length !== 1) {
// If there are more than one node selected,
$notify($msg('ONLY_ONE_TOPIC_MUST_BE_SELECTED'));
return;
}
// Add new node ...
const parentTopic = nodes[0];
const parentTopicId = parentTopic.getId();
const childModel = this._createChildModel(parentTopic);
// Execute event ...
this._actionDispatcher.addTopics([childModel], [parentTopicId]);
}
/**
* @private
*/
2022-01-09 00:22:59 +01:00
_copyNodeProps(sourceModel: NodeModel, targetModel: NodeModel) {
2021-12-04 01:11:17 +01:00
// I don't copy the font size if the target is the source is the central topic.
2022-01-02 23:16:18 +01:00
if (sourceModel.getType() !== 'CentralTopic') {
2021-12-04 01:11:17 +01:00
const fontSize = sourceModel.getFontSize();
if (fontSize) {
targetModel.setFontSize(fontSize);
}
}
const fontFamily = sourceModel.getFontFamily();
if (fontFamily) {
targetModel.setFontFamily(fontFamily);
}
const fontColor = sourceModel.getFontColor();
if (fontColor) {
targetModel.setFontColor(fontColor);
}
const fontWeight = sourceModel.getFontWeight();
if (fontWeight) {
targetModel.setFontWeight(fontWeight);
}
const fontStyle = sourceModel.getFontStyle();
if (fontStyle) {
targetModel.setFontStyle(fontStyle);
}
const shape = sourceModel.getShapeType();
if (shape) {
targetModel.setShapeType(shape);
}
const borderColor = sourceModel.getBorderColor();
if (borderColor) {
targetModel.setBorderColor(borderColor);
}
const backgroundColor = sourceModel.getBackgroundColor();
if (backgroundColor) {
targetModel.setBackgroundColor(backgroundColor);
}
}
/**
* @private
2021-12-19 17:31:29 +01:00
* @param {Topic} topic the parent topic of the child to create the NodeModel for
* @param {Point} mousePos the mouse position
* @return {NodeModel} the node model for the new child
2021-12-04 01:11:17 +01:00
*/
2022-01-09 00:22:59 +01:00
_createChildModel(topic: Topic, mousePos: Point = null): NodeModel {
2021-12-04 01:11:17 +01:00
// Create a new node ...
const parentModel = topic.getModel();
const mindmap = parentModel.getMindmap();
const childModel = mindmap.createNode();
// Create a new node ...
const layoutManager = this._eventBussDispatcher.getLayoutManager();
const result = layoutManager.predict(topic.getId(), null, mousePos);
childModel.setOrder(result.order);
const { position } = result;
childModel.setPosition(position.x, position.y);
this._copyNodeProps(parentModel, childModel);
return childModel;
}
2022-01-09 00:22:59 +01:00
addDraggedNode(event, model: NodeModel) {
2021-12-04 01:11:17 +01:00
$assert(event, 'event can not be null');
$assert(model, 'model can not be null');
// Position far from the visual area ...
model.setPosition(1000, 1000);
this._actionDispatcher.addTopics([model]);
const topic = this.getModel().findTopicById(model.getId());
// Simulate a mouse down event to start the dragging ...
topic.fireEvent('mousedown', event);
}
/**
* creates a sibling or child node of the selected node, if the selected node is the
* central topic
*/
createSiblingForSelectedNode() {
const nodes = this.getModel().filterSelectedTopics();
if (nodes.length <= 0) {
// If there are no nodes selected,
$notify($msg('ONE_TOPIC_MUST_BE_SELECTED'));
return;
}
if (nodes.length > 1) {
// If there are more than one node selected,
$notify($msg('ONLY_ONE_TOPIC_MUST_BE_SELECTED'));
return;
}
const topic = nodes[0];
if (!topic.getOutgoingConnectedTopic()) {
// Central topic and isolated topics ....
// Central topic doesn't have siblings ...
this.createChildForSelectedNode();
} else {
2021-10-05 02:05:34 +02:00
const parentTopic = topic.getOutgoingConnectedTopic();
2021-12-04 01:11:17 +01:00
const siblingModel = this._createSiblingModel(topic);
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Hack: if parent is central topic, add node below not on opposite side.
// This should be done in the layout
2022-01-02 23:16:18 +01:00
if (parentTopic.getType() === 'CentralTopic') {
2021-12-04 01:11:17 +01:00
siblingModel.setOrder(topic.getOrder() + 2);
2021-10-05 02:05:34 +02:00
}
2021-12-04 01:11:17 +01:00
const parentTopicId = parentTopic.getId();
this._actionDispatcher.addTopics([siblingModel], [parentTopicId]);
}
}
/**
* @private
* @param {mindplot.Topic} topic the topic to create the sibling to
* @return {mindplot.NodeModel} the node model of the sibling
*/
2022-01-09 00:22:59 +01:00
_createSiblingModel(topic: Topic) {
2021-12-04 01:11:17 +01:00
let result = null;
let model = null;
const parentTopic = topic.getOutgoingConnectedTopic();
if (parentTopic != null) {
// Create a new node ...
model = topic.getModel();
const mindmap = model.getMindmap();
result = mindmap.createNode();
2021-10-05 02:05:34 +02:00
2021-12-04 01:11:17 +01:00
// Create a new node ...
const order = topic.getOrder() + 1;
result.setOrder(order);
result.setPosition(10, 10); // Set a dummy position ...
}
this._copyNodeProps(model, result);
return result;
}
/**
* @param {Event} event
*/
showRelPivot(event) {
const nodes = this.getModel().filterSelectedTopics();
if (nodes.length <= 0) {
// This could not happen ...
$notify($msg('RELATIONSHIP_COULD_NOT_BE_CREATED'));
return;
}
// Current mouse position ....
const screen = this._workspace.getScreenManager();
const pos = screen.getWorkspaceMousePosition(event);
// create a connection ...
this._relPivot.start(nodes[0], pos);
}
/** @return {{zoom:Number}} the zoom */
getMindmapProperties() {
const model = this.getModel();
return { zoom: model.getZoom() };
}
/**
2022-01-09 00:22:59 +01:00
* @param {mindplot.Mindmap} mindmap
2021-12-04 01:11:17 +01:00
* @throws will throw an error if mindmapModel is null or undefined
*/
2022-01-09 00:22:59 +01:00
loadMap(mindmap: Mindmap) {
$assert(mindmap, 'mindmapModel can not be null');
this._mindmap = mindmap;
2021-12-04 01:11:17 +01:00
// Init layout manager ...
const size = { width: 25, height: 25 };
2022-01-09 00:22:59 +01:00
const layoutManager = new LayoutManager(mindmap.getCentralTopic().getId(), size);
2021-12-04 01:11:17 +01:00
const me = this;
layoutManager.addEvent('change', (event) => {
const id = event.getId();
const topic = me.getModel().findTopicById(id);
topic.setPosition(event.getPosition());
topic.setOrder(event.getOrder());
});
this._eventBussDispatcher.setLayoutManager(layoutManager);
// Building node graph ...
2022-01-09 00:22:59 +01:00
const branches = mindmap.getBranches();
2021-12-15 02:20:41 +01:00
branches.forEach((branch) => {
const nodeGraph = this.nodeModelToNodeGraph(branch);
2021-12-04 01:11:17 +01:00
nodeGraph.setBranchVisibility(true);
2021-12-15 02:20:41 +01:00
});
2021-12-04 01:11:17 +01:00
2021-12-15 02:20:41 +01:00
// Connect relationships ...
2022-01-09 00:22:59 +01:00
const relationships = mindmap.getRelationships();
2021-12-15 02:20:41 +01:00
relationships.forEach((relationship) => this._relationshipModelToRelationship(relationship));
2021-12-04 01:11:17 +01:00
// Place the focus on the Central Topic
const centralTopic = this.getModel().getCentralTopic();
this.goToNode(centralTopic);
// Finally, sort the map ...
EventBus.instance.fireEvent(EventBus.events.DoLayout);
this.fireEvent('loadSuccess');
}
2022-01-09 00:22:59 +01:00
getMindmap(): Mindmap {
2021-12-04 01:11:17 +01:00
return this._mindmap;
}
/** */
undo() {
// @Todo: This is a hack...
this._actionDispatcher._actionRunner.undo();
}
/** */
redo() {
this._actionDispatcher._actionRunner.redo();
}
/** */
isReadOnly() {
return this._options.readOnly;
}
/**
* @param {mindplot.model.NodeModel} nodeModel
* @return {mindplot.Topic} the topic (extends mindplot.NodeGraph) created to the model
*/
2022-01-09 00:22:59 +01:00
nodeModelToNodeGraph(nodeModel: NodeModel) {
2021-12-04 01:11:17 +01:00
$assert(nodeModel, 'Node model can not be null');
let children = nodeModel.getChildren().slice();
children = children.sort((a, b) => a.getOrder() - b.getOrder());
2021-12-15 02:20:41 +01:00
const result = this._buildNodeGraph(nodeModel, this.isReadOnly());
result.setVisibility(false);
2021-12-04 01:11:17 +01:00
2021-12-15 02:20:41 +01:00
this._workspace.append(result);
children.forEach((child) => {
if ($defined(child)) {
this.nodeModelToNodeGraph(child);
}
});
return result;
2021-12-04 01:11:17 +01:00
}
/**
* @private
* @param {mindplot.model.RelationshipModel} model
* @return {mindplot.Relationship} the relationship created to the model
* @throws will throw an error if model is null or undefined
*/
2022-01-09 00:22:59 +01:00
private _relationshipModelToRelationship(model: RelationshipModel) {
2021-12-04 01:11:17 +01:00
$assert(model, 'Node model can not be null');
const result = this._buildRelationshipShape(model);
const sourceTopic = result.getSourceTopic();
sourceTopic.addRelationship(result);
const targetTopic = result.getTargetTopic();
targetTopic.addRelationship(result);
result.setVisibility(sourceTopic.isVisible() && targetTopic.isVisible());
this._workspace.append(result);
return result;
}
/**
* @param {mindplot.model.RelationshipModel} model
* @return {mindplot.Relationship} the relationship added to the mindmap
*/
2022-01-09 00:22:59 +01:00
addRelationship(model: RelationshipModel) {
2021-12-04 01:11:17 +01:00
const mindmap = this.getMindmap();
mindmap.addRelationship(model);
return this._relationshipModelToRelationship(model);
}
/**
* deletes the relationship from the linked topics, DesignerModel, Workspace and Mindmap
* @param {mindplot.Relationship} rel the relationship to delete
*/
deleteRelationship(rel) {
const sourceTopic = rel.getSourceTopic();
sourceTopic.deleteRelationship(rel);
const targetTopic = rel.getTargetTopic();
targetTopic.deleteRelationship(rel);
this.getModel().removeRelationship(rel);
this._workspace.removeChild(rel);
const mindmap = this.getMindmap();
mindmap.deleteRelationship(rel.getModel());
}
/**
* @private
* @param {mindplot.model.RelationshipModel} model
* @return {mindplot.Relationship} the new relationship with events registered
* @throws will throw an error if the target topic cannot be found
*/
2022-01-09 00:22:59 +01:00
_buildRelationshipShape(model: RelationshipModel) {
2021-12-04 01:11:17 +01:00
const dmodel = this.getModel();
const sourceTopicId = model.getFromNode();
const sourceTopic = dmodel.findTopicById(sourceTopicId);
const targetTopicId = model.getToNode();
const targetTopic = dmodel.findTopicById(targetTopicId);
$assert(
targetTopic,
Squashed commit of the following: commit 81e9041e19344df18d8e91a31edb69ea0bce6bf7 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sun Dec 5 14:35:11 2021 -0800 Fix serialization bug commit 290223f15aa3902ef3291e0f392a92d04e9ccd0e Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sun Dec 5 09:36:59 2021 -0800 Fix serialization bug commit 3c1a2e37cf7be34780b760eb6f2fd3e3ac577ed1 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sun Dec 5 09:24:13 2021 -0800 Fix missing references commit 98a7fd3e1659eb32cee43144da4a10b98cfcfed0 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sun Dec 5 09:08:38 2021 -0800 Fix compile commit 9f5146b353f0fdb34a5b83db6d120bc09a96a073 Merge: 218e074 d3dc44c Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sun Dec 5 09:03:17 2021 -0800 Merge branch 'develop' into feature/mindplot_tests_playground commit 218e074b7330d0a02ac7afee0b644737f3bb6b03 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sat Dec 4 17:46:22 2021 -0800 Add jquery missing dependency commit c01a748a84a2359dcf1a49d9593369c9fefe1e76 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sat Dec 4 17:41:47 2021 -0800 Remove undercore commit d680b063b819563f4fae38571055aad4c55180b7 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sat Dec 4 17:36:58 2021 -0800 Add missing dependencies commit be9aa21245e84c97fa9fe3545299c6bb01772ef1 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sat Dec 4 17:25:23 2021 -0800 Fix missign dependencies. commit 2472fa453c43b64286552edb7120d4d2c395eba4 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sat Dec 4 17:08:48 2021 -0800 Fix compilation commit a013d07dc15a3bddda1e42cad373abbdfc525c74 Merge: f2ac8fe a81e984 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Sat Dec 4 16:57:19 2021 -0800 Merge branch 'develop' into feature/mindplot_tests_playground commit f2ac8fe17de071a09f80a20f49eb42c66b5f2f2b Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Thu Dec 2 18:59:03 2021 -0800 Hack: Add declaration of designer as global variable. commit 432167acbb055fba19f1e8469db4ffe8ad7793b7 Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Thu Dec 2 18:51:13 2021 -0800 Hack the initialization of extended hotkey pluggin. commit 794db35e88f6bd232094b4f104c8fe6a8fab34be Author: Paulo Gustavo Veiga <pveiga@wisemapping.com> Date: Thu Dec 2 17:57:21 2021 -0800 Add keyboard function commit e47a48d51f1af0ebde4ebea07cb467a8e5a7a372 Author: Matias Arriola <matias.arriola@gmail.com> Date: Thu Dec 2 14:50:43 2021 -0300 Fix editor snapshot commit 9bf8baef1216352ed1f9dde4658c479c0044b093 Author: Matias Arriola <matias.arriola@gmail.com> Date: Thu Dec 2 13:14:13 2021 -0300 Add editor test to mindplot playground Add missing imports and fix BootstrapDialog references commit b2dcc3034fc638f58cdb59e96ab7d6e2435f634a Author: Matias Arriola <matias.arriola@gmail.com> Date: Thu Dec 2 11:56:10 2021 -0300 Add embedded test to mindplot playground commit 08b514e1aa7b51c4df56b2b3cb7f28c191adeae7 Author: Matias Arriola <matias.arriola@gmail.com> Date: Thu Dec 2 10:27:31 2021 -0300 Fix mindplot layout test Upgrade raphael to avoid error Load raphael from context-loader
2021-12-06 00:50:37 +01:00
`targetTopic could not be found:${targetTopicId},${dmodel
2021-12-04 01:11:17 +01:00
.getTopics()
.map((e) => e.getId())}`,
);
// Build relationship line ....
const result = new Relationship(sourceTopic, targetTopic, model);
const me = this;
result.addEvent('ontblur', () => {
const topics = me.getModel().filterSelectedTopics();
const rels = me.getModel().filterSelectedRelationships();
if (topics.length === 0 || rels.length === 0) {
me.fireEvent('onblur');
}
});
result.addEvent('ontfocus', () => {
const topics = me.getModel().filterSelectedTopics();
const rels = me.getModel().filterSelectedRelationships();
if (topics.length === 1 || rels.length === 1) {
me.fireEvent('onfocus');
}
});
// Append it to the workspace ...
dmodel.addRelationship(result);
return result;
}
/**
* @param {mindplot.Topic} node the topic to remove
* removes the given topic and its children from Workspace, DesignerModel and NodeModel
*/
removeTopic(node) {
if (!node.isCentralTopic()) {
const parent = node._parent;
node.disconnect(this._workspace);
// remove children
while (node.getChildren().length > 0) {
this.removeTopic(node.getChildren()[0]);
}
this._workspace.removeChild(node);
this.getModel().removeTopic(node);
// Delete this node from the model...
const model = node.getModel();
model.deleteNode();
if ($defined(parent)) {
this.goToNode(parent);
}
}
}
/**
* @private
*/
_resetEdition() {
const screenManager = this._workspace.getScreenManager();
screenManager.fireEvent('update');
screenManager.fireEvent('mouseup');
this._relPivot.dispose();
}
/** */
deleteSelectedEntities() {
// Is there some action in progress ?.
this._resetEdition();
const topics = this.getModel().filterSelectedTopics();
const relation = this.getModel().filterSelectedRelationships();
if (topics.length <= 0 && relation.length <= 0) {
// If there are more than one node selected,
$notify($msg('ENTITIES_COULD_NOT_BE_DELETED'));
return;
}
if (topics.length === 1 && topics[0].isCentralTopic()) {
$notify($msg('CENTRAL_TOPIC_CAN_NOT_BE_DELETED'));
return;
}
// If the central topic has been selected, I must filter ir
const topicIds = topics
.filter((topic) => !topic.isCentralTopic())
.map((topic) => topic.getId());
const relIds = relation.map((rel) => rel.getId());
// Finally delete the topics ...
if (topicIds.length > 0 || relIds.length > 0) {
this._actionDispatcher.deleteEntities(topicIds, relIds);
}
}
/** */
2022-01-09 00:22:59 +01:00
changeFontFamily(font: string) {
2021-12-04 01:11:17 +01:00
const topicsIds = this.getModel().filterTopicsIds();
if (topicsIds.length > 0) {
this._actionDispatcher.changeFontFamilyToTopic(topicsIds, font);
}
}
/** */
2022-01-09 00:22:59 +01:00
changeFontStyle(): void {
const topicsIds = this.getModel()
.filterTopicsIds();
2021-12-04 01:11:17 +01:00
if (topicsIds.length > 0) {
this._actionDispatcher.changeFontStyleToTopic(topicsIds);
}
}
/** */
changeFontColor(color) {
$assert(color, 'color can not be null');
2022-01-09 00:22:59 +01:00
const topicsIds = this.getModel()
.filterTopicsIds();
2021-12-04 01:11:17 +01:00
if (topicsIds.length > 0) {
this._actionDispatcher.changeFontColorToTopic(topicsIds, color);
}
}
/** */
changeBackgroundColor(color) {
const validateFunc = (topic) => topic.getShapeType() !== TopicShape.LINE;
const validateError = 'Color can not be set to line topics.';
const topicsIds = this.getModel().filterTopicsIds(validateFunc, validateError);
if (topicsIds.length > 0) {
this._actionDispatcher.changeBackgroundColorToTopic(topicsIds, color);
}
}
/** */
2022-01-09 00:22:59 +01:00
changeBorderColor(color:string) {
2021-12-04 01:11:17 +01:00
const validateFunc = (topic) => topic.getShapeType() !== TopicShape.LINE;
const validateError = 'Color can not be set to line topics.';
const topicsIds = this.getModel().filterTopicsIds(validateFunc, validateError);
if (topicsIds.length > 0) {
this._actionDispatcher.changeBorderColorToTopic(topicsIds, color);
}
}
/** */
2022-01-09 00:22:59 +01:00
changeFontSize(size:number) {
2021-12-04 01:11:17 +01:00
const topicsIds = this.getModel().filterTopicsIds();
if (topicsIds.length > 0) {
this._actionDispatcher.changeFontSizeToTopic(topicsIds, size);
}
}
/** */
changeTopicShape(shape) {
const validateFunc = (topic) => !(
2022-01-02 23:16:18 +01:00
topic.getType() === 'CentralTopic' && shape === TopicShape.LINE
2021-12-04 01:11:17 +01:00
);
const validateError = 'Central Topic shape can not be changed to line figure.';
const topicsIds = this.getModel().filterTopicsIds(validateFunc, validateError);
if (topicsIds.length > 0) {
this._actionDispatcher.changeShapeTypeToTopic(topicsIds, shape);
}
}
/** */
changeFontWeight() {
const topicsIds = this.getModel().filterTopicsIds();
if (topicsIds.length > 0) {
this._actionDispatcher.changeFontWeightToTopic(topicsIds);
}
}
/** */
addIconType(iconType) {
const topicsIds = this.getModel().filterTopicsIds();
if (topicsIds.length > 0) {
this._actionDispatcher.addFeatureToTopic(topicsIds[0], TopicFeatureFactory.Icon.id, {
2021-12-04 01:11:17 +01:00
id: iconType,
2021-10-05 02:05:34 +02:00
});
2021-12-04 01:11:17 +01:00
}
}
/**
* lets the selected topic open the link editor where the user can define or modify an
* existing link
*/
addLink() {
const model = this.getModel();
const topic = model.selectedTopic();
if (topic) {
topic.showLinkEditor();
this.onObjectFocusEvent();
}
}
/** */
addNote() {
const model = this.getModel();
const topic = model.selectedTopic();
if (topic) {
topic.showNoteEditor();
this.onObjectFocusEvent();
}
}
/**
* @param {mindplot.Topic} node
* sets the focus to the given node
*/
goToNode(node) {
node.setOnFocus(true);
this.onObjectFocusEvent(node);
}
/** @return {mindplot.Workspace} */
getWorkSpace() {
return this._workspace;
}
}
2021-07-16 16:41:58 +02:00
export default Designer;