diff --git a/packages/mindplot/cypress/e2e/connection.test.js b/packages/mindplot/cypress/e2e/connection.test.js new file mode 100644 index 00000000..700fc7b2 --- /dev/null +++ b/packages/mindplot/cypress/e2e/connection.test.js @@ -0,0 +1,11 @@ +context('Connection suite', () => { + it('classic theme', () => { + cy.visit('/iframe.html?args=&id=mindplot-connection--classic&viewMode=story'); + cy.matchImageSnapshot('connection-classic'); + }); + + it('classic prism', () => { + cy.visit('/iframe.html?args=&id=mindplot-connection--prism&viewMode=story'); + cy.matchImageSnapshot('connection-prism'); + }); +}); diff --git a/packages/mindplot/cypress/e2e/topic.test.js b/packages/mindplot/cypress/e2e/topic.test.js index cef9a7df..b5f69642 100644 --- a/packages/mindplot/cypress/e2e/topic.test.js +++ b/packages/mindplot/cypress/e2e/topic.test.js @@ -33,4 +33,8 @@ context('Topic suite', () => { cy.visit('/iframe.html?args=&id=mindplot-topic--shape-ellipse&viewMode=story'); cy.matchImageSnapshot('topic-shape-ellipse'); }); -}); + it('topic none line', () => { + cy.visit('/iframe.html?args=&id=mindplot-topic--shape-none&viewMode=story'); + cy.matchImageSnapshot('topic-shape-none'); + }); +}); \ No newline at end of file diff --git a/packages/mindplot/cypress/snapshots/connection.test.js/connection-classic.snap.png b/packages/mindplot/cypress/snapshots/connection.test.js/connection-classic.snap.png new file mode 100644 index 00000000..e1685fb9 Binary files /dev/null and b/packages/mindplot/cypress/snapshots/connection.test.js/connection-classic.snap.png differ diff --git a/packages/mindplot/cypress/snapshots/connection.test.js/connection-prism.snap.png b/packages/mindplot/cypress/snapshots/connection.test.js/connection-prism.snap.png new file mode 100644 index 00000000..ee3524fb Binary files /dev/null and b/packages/mindplot/cypress/snapshots/connection.test.js/connection-prism.snap.png differ diff --git a/packages/mindplot/cypress/snapshots/topic.test.js/topic-shape-none.snap.png b/packages/mindplot/cypress/snapshots/topic.test.js/topic-shape-none.snap.png new file mode 100644 index 00000000..8e9c7195 Binary files /dev/null and b/packages/mindplot/cypress/snapshots/topic.test.js/topic-shape-none.snap.png differ diff --git a/packages/mindplot/src/components/ActionDispatcher.ts b/packages/mindplot/src/components/ActionDispatcher.ts index 202f04dd..f7f50848 100644 --- a/packages/mindplot/src/components/ActionDispatcher.ts +++ b/packages/mindplot/src/components/ActionDispatcher.ts @@ -21,13 +21,14 @@ import { $assert } from '@wisemapping/core-js'; import { Mindmap } from '..'; import CommandContext from './CommandContext'; import { PivotType } from './RelationshipControlPoints'; -import Events from './Events'; +import EventDispispatcher from './EventDispatcher'; import NodeModel from './model/NodeModel'; import RelationshipModel from './model/RelationshipModel'; import Topic from './Topic'; import PositionType from './PositionType'; +import EventBusType from './EventBusType'; -abstract class ActionDispatcher extends Events { +abstract class ActionDispatcher extends EventDispispatcher { private static _instance: ActionDispatcher; private _commandContext: CommandContext; diff --git a/packages/mindplot/src/components/Designer.ts b/packages/mindplot/src/components/Designer.ts index b45130f3..c71c42ed 100644 --- a/packages/mindplot/src/components/Designer.ts +++ b/packages/mindplot/src/components/Designer.ts @@ -20,7 +20,7 @@ import $ from 'jquery'; import { $assert, $defined } from '@wisemapping/core-js'; import Messages, { $msg } from './Messages'; -import Events from './Events'; +import EventDispispatcher from './EventDispatcher'; import StandaloneActionDispatcher from './StandaloneActionDispatcher'; import CommandContext from './CommandContext'; @@ -37,7 +37,7 @@ import DragManager from './DragManager'; import RelationshipPivot from './RelationshipPivot'; import Relationship from './Relationship'; -import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher'; +import TopicEventDispatcher from './TopicEventDispatcher'; import TopicFactory from './TopicFactory'; import EventBus from './layout/EventBus'; @@ -62,8 +62,11 @@ import ImageExpoterFactory from './export/ImageExporterFactory'; import PositionType from './PositionType'; import ThemeType from './model/ThemeType'; import ThemeFactory from './theme/ThemeFactory'; +import ChangeEvent from './layout/ChangeEvent'; -class Designer extends Events { +type DesignerEventType = 'modelUpdate' | 'onfocus' | 'onblur' | 'loadSuccess'; + +class Designer extends EventDispispatcher { private _mindmap: Mindmap | null; private _options: DesignerOptions; @@ -159,14 +162,9 @@ class Designer extends Events { return this._actionDispatcher; } - addEvent(type: string, listener): Events { - if (type === TopicEvent.EDIT || type === TopicEvent.CLICK) { - const editor = TopicEventDispatcher.getInstance(); - editor.addEvent(type, listener); - } else { - super.addEvent(type, listener); - } - return this; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + addEvent(type: DesignerEventType, listener: (event: (args?: any) => void) => void): void { + super.addEvent(type, listener); } private _registerMouseEvents() { @@ -619,7 +617,8 @@ class Designer extends Events { // Init layout manager ... const size = { width: 25, height: 25 }; const layoutManager = new LayoutManager(mindmap.getCentralTopic().getId(), size); - layoutManager.addEvent('change', (event) => { + + layoutManager.addEvent('change', (event: ChangeEvent) => { const id = event.getId(); const topic = this.getModel().findTopicById(id); if (topic) { @@ -627,6 +626,7 @@ class Designer extends Events { topic.setOrder(event.getOrder()); } }); + this._eventBussDispatcher.setLayoutManager(layoutManager); // Building node graph ... @@ -652,6 +652,7 @@ class Designer extends Events { // Enable workspace drag events ... this._canvas.registerEvents(); + // Finally, sort the map ... EventBus.instance.fireEvent('forceLayout'); this.fireEvent('loadSuccess'); diff --git a/packages/mindplot/src/components/DesignerModel.ts b/packages/mindplot/src/components/DesignerModel.ts index f59bb4e8..04f2f3ed 100644 --- a/packages/mindplot/src/components/DesignerModel.ts +++ b/packages/mindplot/src/components/DesignerModel.ts @@ -18,12 +18,11 @@ import { $assert } from '@wisemapping/core-js'; import CentralTopic from './CentralTopic'; import { DesignerOptions } from './DesignerOptionsBuilder'; -import Events from './Events'; import Relationship from './Relationship'; import Topic from './Topic'; import { $notify } from './model/ToolbarNotifier'; -class DesignerModel extends Events { +class DesignerModel { private _zoom: number; private _topics: Topic[]; @@ -31,7 +30,6 @@ class DesignerModel extends Events { private _relationships: Relationship[]; constructor(options: DesignerOptions) { - super(); this._zoom = options.zoom; this._topics = []; this._relationships = []; diff --git a/packages/mindplot/src/components/EventBusType.ts b/packages/mindplot/src/components/EventBusType.ts new file mode 100644 index 00000000..a99f9e7a --- /dev/null +++ b/packages/mindplot/src/components/EventBusType.ts @@ -0,0 +1,30 @@ +/* + * Copyright [2021] [wisemapping] + * + * 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. + */ + +export type EventBusType = + | 'topicResize' + | 'topicMoved' + | 'forceLayout' + | 'childShrinked' + | 'topicConnected' + | 'topicAdded' + | 'topicRemoved' + | 'topicDisconect' + | 'modelUpdate'; + +export default EventBusType; diff --git a/packages/mindplot/src/components/EventDispatcher.ts b/packages/mindplot/src/components/EventDispatcher.ts new file mode 100644 index 00000000..f353cae6 --- /dev/null +++ b/packages/mindplot/src/components/EventDispatcher.ts @@ -0,0 +1,73 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* + * Copyright [2021] [wisemapping] + * + * 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. + */ +class EventDispispatcher { + private _handlerByType: Map void)[]>; + + constructor() { + this._handlerByType = new Map(); + } + + private static _normalizeEventName(value: K): K { + return String(value).replace(/^on([A-Z])/, (_full, first) => first.toLowerCase()) as K; + } + + addEvent(typeName: T, fn: (args?: any) => void, internal?: boolean): void { + const type = EventDispispatcher._normalizeEventName(typeName); + + let events = this._handlerByType.get(type); + // Add function had not been added yet + events = events || []; + if (events && !events.includes(fn)) { + events.push(fn); + this._handlerByType.set(type, events); + } + + // Mark reference ... + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + fn.internal = Boolean(internal); + } + + fireEvent(typeName: T, arg?: any): void { + const type = EventDispispatcher._normalizeEventName(typeName); + const events = this._handlerByType.get(type); + if (events) { + const args: any = arg ? [arg] : []; + events.forEach((fn) => { + fn.apply(this, args); + }); + } + } + + removeEvent(typeName: T, fn: (...args: any) => void): void { + const type = EventDispispatcher._normalizeEventName(typeName); + const events = this._handlerByType.get(type); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (events && !fn.internal) { + const index = events.indexOf(fn); + if (index !== -1) { + events.splice(index, 1); + } + } + } +} + +export default EventDispispatcher; diff --git a/packages/mindplot/src/components/Events.ts b/packages/mindplot/src/components/Events.ts deleted file mode 100644 index 67ba0c8e..00000000 --- a/packages/mindplot/src/components/Events.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright [2021] [wisemapping] - * - * 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. - */ - -class Events { - protected _handlerByType; - - constructor() { - this._handlerByType = {}; - } - - static _normalizeEventName(string: string) { - return string.replace(/^on([A-Z])/, (_full, first) => first.toLowerCase()); - } - - addEvent(typeName: string, fn?, internal?: boolean): Events { - const type = Events._normalizeEventName(typeName); - - // Add function had not been added yet - const funByType = this._handlerByType[type] ? this._handlerByType[type] : []; - if (!funByType.includes(fn)) { - funByType.push(fn); - this._handlerByType[type] = funByType; - } - - // Mark reference ... - fn.internal = Boolean(internal); - return this; - } - - fireEvent(typeName: string, eventArgs?): Events { - const type = Events._normalizeEventName(typeName); - const events = this._handlerByType[type]; - if (!events) return this; - - const args = Array.isArray(eventArgs) ? eventArgs : [eventArgs]; - events.forEach((fn) => { - // Execute our of the main thread... - fn.apply(this, args); - }); - return this; - } - - removeEvent(typeName: string, fn?): Events { - const type = Events._normalizeEventName(typeName); - const events = this._handlerByType[type]; - if (events && !fn.internal) { - const index = events.indexOf(fn); - if (index !== -1) events.splice(index, 1); - } - return this; - } -} - -export default Events; diff --git a/packages/mindplot/src/components/MultilineTextEditor.ts b/packages/mindplot/src/components/MultilineTextEditor.ts index 1c69233f..fcd32297 100644 --- a/packages/mindplot/src/components/MultilineTextEditor.ts +++ b/packages/mindplot/src/components/MultilineTextEditor.ts @@ -20,11 +20,13 @@ import { FontStyle } from '@wisemapping/web2d/src/components/peer/svg/FontPeer'; import $ from 'jquery'; import ActionDispatcher from './ActionDispatcher'; -import Events from './Events'; +import EventDispatcher from './EventDispatcher'; import EventBus from './layout/EventBus'; import Topic from './Topic'; -class EditorComponent extends Events { +type EditorEventType = 'input'; + +class EditorComponent extends EventDispatcher { private _topic: Topic; private _oldText: string | undefined; diff --git a/packages/mindplot/src/components/StandaloneActionDispatcher.ts b/packages/mindplot/src/components/StandaloneActionDispatcher.ts index 5d1e1702..0875a9e4 100644 --- a/packages/mindplot/src/components/StandaloneActionDispatcher.ts +++ b/packages/mindplot/src/components/StandaloneActionDispatcher.ts @@ -55,7 +55,7 @@ class StandaloneActionDispatcher extends ActionDispatcher { this._actionRunner = new DesignerActionRunner(commandContext, this); } - addTopics(models: NodeModel[], parentTopicsId: number[] | null) { + addTopics(models: NodeModel[], parentTopicsId: number[] | null): void { const command = new AddTopicCommand(models, parentTopicsId); this.execute(command); } diff --git a/packages/mindplot/src/components/Topic.ts b/packages/mindplot/src/components/Topic.ts index 14cc0cb2..a72105c0 100644 --- a/packages/mindplot/src/components/Topic.ts +++ b/packages/mindplot/src/components/Topic.ts @@ -27,7 +27,7 @@ import EventBus from './layout/EventBus'; import ShirinkConnector from './ShrinkConnector'; import ActionDispatcher from './ActionDispatcher'; -import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher'; +import TopicEventDispatcher from './TopicEventDispatcher'; import { TopicShapeType } from './model/INodeModel'; import NodeModel from './model/NodeModel'; import Relationship from './Relationship'; @@ -528,7 +528,7 @@ abstract class Topic extends NodeGraph { } const eventDispatcher = me._getTopicEventDispatcher(); - eventDispatcher.process(TopicEvent.CLICK, me); + eventDispatcher.process('clicknode', me); event.stopPropagation(); }); } @@ -594,7 +594,7 @@ abstract class Topic extends NodeGraph { getShrinkConnector(): ShirinkConnector | null { let result = this._connector; - if (this._connector == null) { + if (!this._connector) { this._connector = new ShirinkConnector(this); this._connector.setVisibility(false); result = this._connector; @@ -693,11 +693,8 @@ abstract class Topic extends NodeGraph { * Point: references the center of the rect shape.!!! */ setPosition(point: PositionType): void { - $assert(point, 'position can not be null'); // allowed param reassign to avoid risks of existing code relying in this side-effect - // eslint-disable-next-line no-param-reassign point.x = Math.ceil(point.x); - // eslint-disable-next-line no-param-reassign point.y = Math.ceil(point.y); // Update model's position ... @@ -721,7 +718,6 @@ abstract class Topic extends NodeGraph { this.invariant(); } - /** */ getOutgoingLine(): ConnectionLine | null { return this._outgoingLine; } @@ -959,7 +955,7 @@ abstract class Topic extends NodeGraph { this.redraw(); } - connectTo(targetTopic: Topic, workspace: Canvas): void { + connectTo(targetTopic: Topic, canvas: Canvas): void { // Connect Graphical Nodes ... targetTopic.append(this); this._parent = targetTopic; @@ -971,10 +967,10 @@ abstract class Topic extends NodeGraph { // Create a connection line ... const outgoingLine = this.createConnectionLine(targetTopic); - outgoingLine.setVisibility(false); + // outgoingLine.setVisibility(false); this._outgoingLine = outgoingLine; - workspace.append(outgoingLine); + canvas.append(outgoingLine); // Update figure is necessary. this.updateTopicShape(targetTopic); diff --git a/packages/mindplot/src/components/TopicEventDispatcher.ts b/packages/mindplot/src/components/TopicEventDispatcher.ts index e22743f7..3c2ef9f9 100644 --- a/packages/mindplot/src/components/TopicEventDispatcher.ts +++ b/packages/mindplot/src/components/TopicEventDispatcher.ts @@ -15,20 +15,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Events from './Events'; +import EventDispispatcher from './EventDispatcher'; import Topic from './Topic'; import MultitTextEditor from './MultilineTextEditor'; -const TopicEvent = { - EDIT: 'editnode', - CLICK: 'clicknode', -}; +type TopicEventType = 'editnode' | 'clicknode'; -class TopicEventDispatcher extends Events { +class TopicEventDispatcher extends EventDispispatcher { private _readOnly: boolean; // eslint-disable-next-line no-use-before-define - static _instance: TopicEventDispatcher; + private static _instance: TopicEventDispatcher; constructor(readOnly: boolean) { super(); @@ -43,10 +40,10 @@ class TopicEventDispatcher extends Events { } show(topic: Topic, textOverwrite?: string): void { - this.process(TopicEvent.EDIT, topic, textOverwrite); + this.process('editnode', topic, textOverwrite); } - process(eventType: string, topic: Topic, textOverwrite?: string): void { + process(eventType: TopicEventType, topic: Topic, textOverwrite?: string): void { // Close all previous open editor .... const editor = MultitTextEditor.getInstance(); if (editor.isActive()) { @@ -55,7 +52,7 @@ class TopicEventDispatcher extends Events { // Open the new editor ... const model = topic.getModel(); - if (!this._readOnly && eventType === TopicEvent.EDIT) { + if (!this._readOnly && eventType === 'editnode') { editor.show(topic, textOverwrite); } else { this.fireEvent(eventType, { model, readOnly: this._readOnly }); @@ -66,8 +63,13 @@ class TopicEventDispatcher extends Events { return MultitTextEditor.getInstance().isActive(); } - static configure(readOnly: boolean): void { + static configure(readOnly: boolean): TopicEventDispatcher { + if (this._instance) { + throw new Error('events already initialized'); + } + this._instance = new TopicEventDispatcher(readOnly); + return this._instance; } static getInstance(): TopicEventDispatcher { @@ -77,6 +79,4 @@ class TopicEventDispatcher extends Events { return this._instance; } } - -export { TopicEvent }; export default TopicEventDispatcher; diff --git a/packages/mindplot/src/components/layout/EventBus.ts b/packages/mindplot/src/components/layout/EventBus.ts index f5727e36..16184510 100644 --- a/packages/mindplot/src/components/layout/EventBus.ts +++ b/packages/mindplot/src/components/layout/EventBus.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* * Copyright [2021] [wisemapping] * @@ -15,18 +16,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Events from '../Events'; +import EventDispispatcher from '../EventDispatcher'; +import { EventBusType } from '../EventBusType'; -export type EventType = - | 'topicResize' - | 'topicMoved' - | 'childShrinked' - | 'topicConnected' - | 'topicAdded' - | 'topicRemoved' - | 'forceLayout' - | 'topicDisconect'; -class EventBus extends Events { +class EventBus extends EventDispispatcher { // eslint-disable-next-line no-use-before-define static _instance: EventBus = new EventBus(); @@ -34,11 +27,11 @@ class EventBus extends Events { return this._instance; } - fireEvent(type: EventType, eventArgs?: unknown[] | unknown): Events { - return super.fireEvent(type, eventArgs); + fireEvent(type: EventBusType, arg?: any): void { + return super.fireEvent(type, arg); } - addEvent(type: EventType, fn?, internal?: boolean): Events { + addEvent(type: EventBusType, fn: (arg?: any) => void, internal?: boolean): void { return super.addEvent(type, fn, internal); } } diff --git a/packages/mindplot/src/components/layout/EventBusDispatcher.ts b/packages/mindplot/src/components/layout/EventBusDispatcher.ts index d73045a3..c9cd7fcd 100644 --- a/packages/mindplot/src/components/layout/EventBusDispatcher.ts +++ b/packages/mindplot/src/components/layout/EventBusDispatcher.ts @@ -45,15 +45,15 @@ class EventBusDispatcher { } private _topicResizeEvent(args: { node: Topic; size: SizeType }) { - this._layoutManager!.updateNodeSize(args.node.getId(), args.size); + this.getLayoutManager().updateNodeSize(args.node.getId(), args.size); } private _topicMoved(args: { node: Topic; position: PositionType }) { - this._layoutManager!.moveNode(args.node.getId(), args.position); + this.getLayoutManager().moveNode(args.node.getId(), args.position); } private _topicDisconect(node: Topic) { - this._layoutManager!.disconnectNode(node.getId()); + this.getLayoutManager().disconnectNode(node.getId()); } private _topicConnected(args: { parentNode: Topic; childNode: Topic }) { @@ -64,28 +64,31 @@ class EventBusDispatcher { ); } + getLayoutManager(): LayoutManager { + if (!this._layoutManager) { + throw new Error('Layout not initialized'); + } + return this._layoutManager; + } + private _childShrinked(node: Topic) { - this._layoutManager!.updateShrinkState(node.getId(), node.areChildrenShrunken()); + this.getLayoutManager().updateShrinkState(node.getId(), node.areChildrenShrunken()); } private _topicAdded(node: Topic) { // Central topic must not be added twice ... if (node.getId() !== 0) { - this._layoutManager!.addNode(node.getId(), { width: 10, height: 10 }, node.getPosition()); - this._layoutManager!.updateShrinkState(node.getId(), node.areChildrenShrunken()); + this.getLayoutManager().addNode(node.getId(), { width: 10, height: 10 }, node.getPosition()); + this.getLayoutManager().updateShrinkState(node.getId(), node.areChildrenShrunken()); } } private _topicRemoved(node: Topic) { - this._layoutManager!.removeNode(node.getId()); + this.getLayoutManager().removeNode(node.getId()); } - private _forceLayout() { - this._layoutManager!.layout(true); - } - - getLayoutManager(): LayoutManager { - return this._layoutManager!; + private _forceLayout(): void { + this.getLayoutManager().layout(true); } } diff --git a/packages/mindplot/src/components/layout/LayoutEventType.ts b/packages/mindplot/src/components/layout/LayoutEventType.ts new file mode 100644 index 00000000..fa88ac77 --- /dev/null +++ b/packages/mindplot/src/components/layout/LayoutEventType.ts @@ -0,0 +1,20 @@ +/* + * Copyright [2021] [wisemapping] + * + * 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. + */ +type LayoutEventType = 'change' | 'forceLayout'; + +export default LayoutEventType; diff --git a/packages/mindplot/src/components/layout/LayoutManager.ts b/packages/mindplot/src/components/layout/LayoutManager.ts index 297c6e20..5e048439 100644 --- a/packages/mindplot/src/components/layout/LayoutManager.ts +++ b/packages/mindplot/src/components/layout/LayoutManager.ts @@ -16,15 +16,16 @@ * limitations under the License. */ import { $assert, $defined } from '@wisemapping/core-js'; -import Events from '../Events'; +import EventDispispatcher from '../EventDispatcher'; import RootedTreeSet from './RootedTreeSet'; import OriginalLayout from './OriginalLayout'; import ChangeEvent from './ChangeEvent'; import SizeType from '../SizeType'; import Node from './Node'; import PositionType from '../PositionType'; +import LayoutEventType from './LayoutEventType'; -class LayoutManager extends Events { +class LayoutManager extends EventDispispatcher { private _treeSet: RootedTreeSet; private _layout: OriginalLayout; diff --git a/packages/mindplot/src/components/model/ThemeType.ts b/packages/mindplot/src/components/model/ThemeType.ts index e3c171a5..1878efc6 100644 --- a/packages/mindplot/src/components/model/ThemeType.ts +++ b/packages/mindplot/src/components/model/ThemeType.ts @@ -15,6 +15,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -type ThemeType = 'classic' | 'prism'; +type ThemeType = 'classic' | 'prism' | 'dark-prism'; export default ThemeType; diff --git a/packages/mindplot/src/components/theme/ThemeFactory.ts b/packages/mindplot/src/components/theme/ThemeFactory.ts index 4dee6af6..8f842bd7 100644 --- a/packages/mindplot/src/components/theme/ThemeFactory.ts +++ b/packages/mindplot/src/components/theme/ThemeFactory.ts @@ -1,11 +1,10 @@ import NodeModel from '../model/NodeModel'; +import ThemeType from '../model/ThemeType'; import ClassicTheme from './ClassicTheme'; import DarkPrismTheme from './DarkPrismTheme'; import PrismTheme from './PrismTheme'; import Theme from './Theme'; -type ThemeId = 'prism' | 'classic' | 'dark-prism'; - class ThemeFactory { private static prismTheme = new PrismTheme(); @@ -13,7 +12,7 @@ class ThemeFactory { private static classicTheme = new ClassicTheme(); - static createById(id: ThemeId): Theme { + static createById(id: ThemeType): Theme { let result: Theme; switch (id) { case 'classic': diff --git a/packages/mindplot/storybook/src/stories/Connection.stories.ts b/packages/mindplot/storybook/src/stories/Connection.stories.ts new file mode 100644 index 00000000..fabeef54 --- /dev/null +++ b/packages/mindplot/storybook/src/stories/Connection.stories.ts @@ -0,0 +1,30 @@ +import { Story, Meta } from '@storybook/html'; +import createConnection, { TopicArgs } from './Connection'; + +export default { + title: 'Mindplot/Connection', + // More on argTypes: https://storybook.js.org/docs/html/api/argtypes + argTypes: { + shapeType: { + options: ['none', 'rectangle', 'rounded rectangle', 'elipse', 'line'], + control: { type: 'select' }, + }, + }, +} as Meta; + +const Template: Story = (args: TopicArgs) => createConnection(args); + +export const Classic = Template.bind({}); +Classic.args = { + theme: 'classic', +}; + +export const Prism = Template.bind({}); +Prism.args = { + theme: 'prism', +}; + +export const DarkPrism = Template.bind({}); +DarkPrism.args = { + theme: 'dark-prism', +}; diff --git a/packages/mindplot/storybook/src/stories/Connection.ts b/packages/mindplot/storybook/src/stories/Connection.ts new file mode 100644 index 00000000..dd8c6f23 --- /dev/null +++ b/packages/mindplot/storybook/src/stories/Connection.ts @@ -0,0 +1,136 @@ +import $ from 'jquery'; + +import { Mindmap, Topic } from '../../../src'; +import NodeModel from '../../../src/components/model/NodeModel'; +import CentralTopic from '../../../src/components/CentralTopic'; +import Canvas from '../../../src/components/Canvas'; +import ScreenManager from '../../../src/components/ScreenManager'; +import TopicEventDispatcher from '../../../src/components/TopicEventDispatcher'; +import ThemeType from '../../../src/components/model/ThemeType'; +import MainTopic from '../../../src/components/MainTopic'; +import EventBusDispatcher from '../../../src/components/layout/EventBusDispatcher'; +import LayoutManager from '../../../src/components/layout/LayoutManager'; +import ChangeEvent from '../../../src/components/layout/ChangeEvent'; +import EventBus from '../../../src/components/layout/EventBus'; + +const registerRefreshHook = (topics: Topic[]) => { + // Trigger a redraw after the node is added ... + if (globalThis.observer) { + globalThis.observer.disconnect(); + } + + globalThis.observer = new MutationObserver(() => { + // Relayout... + topics.forEach((t) => t.redraw()); + EventBus.instance.fireEvent('forceLayout'); + }); + globalThis.observer.observe(document.getElementById('root')!, { childList: true }); +}; + +export type TopicArgs = { + readOnly?: boolean; + theme?: ThemeType; +}; + +const createConnection = ({ theme = undefined, readOnly = true }: TopicArgs) => { + // Build basic container ... + const divElem = document.createElement('div'); + const jqueryDiv = $(divElem); + jqueryDiv.css({ + height: '600px', + width: '800px', + backgroundColor: 'gray', + }); + + // Initialize designer helpers ... + const screenManager = new ScreenManager(divElem); + const canvas = new Canvas(screenManager, 0.7, readOnly); + TopicEventDispatcher.configure(readOnly); + + // Register event propagation .. + const mindmap = new Mindmap(); + const central = new NodeModel('CentralTopic', mindmap); + central.setText('Central Topic'); + mindmap.addBranch(central); + + // Add Children ... + const child1 = new NodeModel('MainTopic', mindmap); + child1.setOrder(0); + child1.setText('This is child one !\nwith other line'); + child1.setPosition(100, 100); + + const child2 = new NodeModel('MainTopic', mindmap); + child2.setOrder(1); + child2.setPosition(100, -100); + + const child3 = new NodeModel('MainTopic', mindmap); + child3.setOrder(0); + child3.setPosition(-100, 100); + + const child4 = new NodeModel('MainTopic', mindmap); + child4.setOrder(1); + child4.setPosition(-100, -100); + + const subchild1 = new NodeModel('MainTopic', mindmap); + subchild1.setOrder(0); + subchild1.setPosition(300, 80); + + const subchild2 = new NodeModel('MainTopic', mindmap); + subchild2.setOrder(1); + subchild2.setPosition(300, 120); + + // Theme ... + if (theme) { + mindmap.setTheme(theme); + } + + // Create and add to canvas.. + const centralTopic = new CentralTopic(central, { readOnly }); + + const child1Topic = new MainTopic(child1, { readOnly }); + const child2Topic = new MainTopic(child2, { readOnly }); + const child3Topic = new MainTopic(child3, { readOnly }); + const child4Topic = new MainTopic(child4, { readOnly }); + const subchild1Topic = new MainTopic(subchild1, { readOnly }); + const subchild2Topic = new MainTopic(subchild2, { readOnly }); + const topics = [ + child1Topic, + child2Topic, + child3Topic, + child4Topic, + centralTopic, + subchild1Topic, + subchild2Topic, + ]; + + // Configure event dispatcher ... + const dispatcher = new EventBusDispatcher(); + const size = { width: 25, height: 25 }; + const layoutManager = new LayoutManager(mindmap.getCentralTopic().getId(), size); + dispatcher.setLayoutManager(layoutManager); + layoutManager.addEvent('change', (event: ChangeEvent) => { + const id = event.getId(); + const topic = topics.filter((t) => t.getModel().getId() === id)[0]; + + topic.setPosition(event.getPosition()); + topic.setOrder(event.getOrder()); + }); + + // Add to canvas ... + topics.forEach((t) => canvas.append(t)); + + // Connect nodes ... + child1Topic.connectTo(centralTopic, canvas); + child2Topic.connectTo(centralTopic, canvas); + child3Topic.connectTo(centralTopic, canvas); + child4Topic.connectTo(centralTopic, canvas); + subchild1Topic.connectTo(child1Topic, canvas); + subchild2Topic.connectTo(child1Topic, canvas); + + // Register refresh hook .. + registerRefreshHook(topics); + + return divElem; +}; + +export default createConnection; diff --git a/packages/mindplot/storybook/src/stories/Topic.stories.ts b/packages/mindplot/storybook/src/stories/Topic.stories.ts index eef2bc82..ef0af565 100644 --- a/packages/mindplot/storybook/src/stories/Topic.stories.ts +++ b/packages/mindplot/storybook/src/stories/Topic.stories.ts @@ -21,6 +21,7 @@ export default { noteText: { control: 'text' }, linkText: { control: 'text' }, eicon: { control: 'multi-select', options: ['❤️', '🌈', '🖇️'] }, + theme: { control: 'select', options: ['classic', 'prism', 'dark-prism'] }, }, } as Meta; @@ -83,3 +84,19 @@ ShapeNone.args = { eicon: ['🌈'], shapeType: 'none', }; + +export const ThemeClassic = Template.bind({}); +ThemeClassic.args = { + text: 'Theme Classic', + eicon: ['🌈'], + shapeType: 'none', + theme: 'classic', +}; + +export const ThemePrime = Template.bind({}); +ThemePrime.args = { + text: 'Theme Prime', + eicon: ['🌈'], + shapeType: 'none', + theme: 'prism', +}; diff --git a/packages/mindplot/storybook/src/stories/Topic.ts b/packages/mindplot/storybook/src/stories/Topic.ts index 1545cbec..0e2bf47a 100644 --- a/packages/mindplot/storybook/src/stories/Topic.ts +++ b/packages/mindplot/storybook/src/stories/Topic.ts @@ -8,6 +8,7 @@ import ScreenManager from '../../../src/components/ScreenManager'; import EmojiIconModel from '../../../src/components/model/EmojiIconModel'; import TopicEventDispatcher from '../../../src/components/TopicEventDispatcher'; import { TopicShapeType } from '../../../src/components/model/INodeModel'; +import ThemeType from '../../../src/components/model/ThemeType'; const registerRefreshHook = (topic: Topic) => { // Trigger a redraw after the node is added ... @@ -34,6 +35,7 @@ export type TopicArgs = { noteText?: string; linkText?: string; eicon?: string[]; + theme?: ThemeType; }; const createTopic = ({ @@ -47,6 +49,7 @@ const createTopic = ({ noteText = undefined, linkText = undefined, eicon = undefined, + theme = undefined, readOnly = true, }: TopicArgs) => { // Build basic container ... @@ -92,6 +95,11 @@ const createTopic = ({ }); } + // Theme ... + if (theme) { + mindmap.setTheme(theme); + } + // Create topic UI element ... mindmap.addBranch(model); const centralTopic = new CentralTopic(model, { readOnly });