Add storybook theme tests

This commit is contained in:
Paulo Gustavo Veiga 2023-02-17 13:25:57 -08:00
parent c1d81cd074
commit 4fc015c6dc
25 changed files with 400 additions and 146 deletions

View File

@ -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');
});
});

View File

@ -33,4 +33,8 @@ context('Topic suite', () => {
cy.visit('/iframe.html?args=&id=mindplot-topic--shape-ellipse&viewMode=story'); cy.visit('/iframe.html?args=&id=mindplot-topic--shape-ellipse&viewMode=story');
cy.matchImageSnapshot('topic-shape-ellipse'); 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');
});
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -21,13 +21,14 @@ import { $assert } from '@wisemapping/core-js';
import { Mindmap } from '..'; import { Mindmap } from '..';
import CommandContext from './CommandContext'; import CommandContext from './CommandContext';
import { PivotType } from './RelationshipControlPoints'; import { PivotType } from './RelationshipControlPoints';
import Events from './Events'; import EventDispispatcher from './EventDispatcher';
import NodeModel from './model/NodeModel'; import NodeModel from './model/NodeModel';
import RelationshipModel from './model/RelationshipModel'; import RelationshipModel from './model/RelationshipModel';
import Topic from './Topic'; import Topic from './Topic';
import PositionType from './PositionType'; import PositionType from './PositionType';
import EventBusType from './EventBusType';
abstract class ActionDispatcher extends Events { abstract class ActionDispatcher extends EventDispispatcher<EventBusType> {
private static _instance: ActionDispatcher; private static _instance: ActionDispatcher;
private _commandContext: CommandContext; private _commandContext: CommandContext;

View File

@ -20,7 +20,7 @@ import $ from 'jquery';
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import Messages, { $msg } from './Messages'; import Messages, { $msg } from './Messages';
import Events from './Events'; import EventDispispatcher from './EventDispatcher';
import StandaloneActionDispatcher from './StandaloneActionDispatcher'; import StandaloneActionDispatcher from './StandaloneActionDispatcher';
import CommandContext from './CommandContext'; import CommandContext from './CommandContext';
@ -37,7 +37,7 @@ import DragManager from './DragManager';
import RelationshipPivot from './RelationshipPivot'; import RelationshipPivot from './RelationshipPivot';
import Relationship from './Relationship'; import Relationship from './Relationship';
import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher'; import TopicEventDispatcher from './TopicEventDispatcher';
import TopicFactory from './TopicFactory'; import TopicFactory from './TopicFactory';
import EventBus from './layout/EventBus'; import EventBus from './layout/EventBus';
@ -62,8 +62,11 @@ import ImageExpoterFactory from './export/ImageExporterFactory';
import PositionType from './PositionType'; import PositionType from './PositionType';
import ThemeType from './model/ThemeType'; import ThemeType from './model/ThemeType';
import ThemeFactory from './theme/ThemeFactory'; import ThemeFactory from './theme/ThemeFactory';
import ChangeEvent from './layout/ChangeEvent';
class Designer extends Events { type DesignerEventType = 'modelUpdate' | 'onfocus' | 'onblur' | 'loadSuccess';
class Designer extends EventDispispatcher<DesignerEventType> {
private _mindmap: Mindmap | null; private _mindmap: Mindmap | null;
private _options: DesignerOptions; private _options: DesignerOptions;
@ -159,14 +162,9 @@ class Designer extends Events {
return this._actionDispatcher; return this._actionDispatcher;
} }
addEvent(type: string, listener): Events { // eslint-disable-next-line @typescript-eslint/no-explicit-any
if (type === TopicEvent.EDIT || type === TopicEvent.CLICK) { addEvent(type: DesignerEventType, listener: (event: (args?: any) => void) => void): void {
const editor = TopicEventDispatcher.getInstance(); super.addEvent(type, listener);
editor.addEvent(type, listener);
} else {
super.addEvent(type, listener);
}
return this;
} }
private _registerMouseEvents() { private _registerMouseEvents() {
@ -619,7 +617,8 @@ class Designer extends Events {
// Init layout manager ... // Init layout manager ...
const size = { width: 25, height: 25 }; const size = { width: 25, height: 25 };
const layoutManager = new LayoutManager(mindmap.getCentralTopic().getId(), size); const layoutManager = new LayoutManager(mindmap.getCentralTopic().getId(), size);
layoutManager.addEvent('change', (event) => {
layoutManager.addEvent('change', (event: ChangeEvent) => {
const id = event.getId(); const id = event.getId();
const topic = this.getModel().findTopicById(id); const topic = this.getModel().findTopicById(id);
if (topic) { if (topic) {
@ -627,6 +626,7 @@ class Designer extends Events {
topic.setOrder(event.getOrder()); topic.setOrder(event.getOrder());
} }
}); });
this._eventBussDispatcher.setLayoutManager(layoutManager); this._eventBussDispatcher.setLayoutManager(layoutManager);
// Building node graph ... // Building node graph ...
@ -652,6 +652,7 @@ class Designer extends Events {
// Enable workspace drag events ... // Enable workspace drag events ...
this._canvas.registerEvents(); this._canvas.registerEvents();
// Finally, sort the map ... // Finally, sort the map ...
EventBus.instance.fireEvent('forceLayout'); EventBus.instance.fireEvent('forceLayout');
this.fireEvent('loadSuccess'); this.fireEvent('loadSuccess');

View File

@ -18,12 +18,11 @@
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import CentralTopic from './CentralTopic'; import CentralTopic from './CentralTopic';
import { DesignerOptions } from './DesignerOptionsBuilder'; import { DesignerOptions } from './DesignerOptionsBuilder';
import Events from './Events';
import Relationship from './Relationship'; import Relationship from './Relationship';
import Topic from './Topic'; import Topic from './Topic';
import { $notify } from './model/ToolbarNotifier'; import { $notify } from './model/ToolbarNotifier';
class DesignerModel extends Events { class DesignerModel {
private _zoom: number; private _zoom: number;
private _topics: Topic[]; private _topics: Topic[];
@ -31,7 +30,6 @@ class DesignerModel extends Events {
private _relationships: Relationship[]; private _relationships: Relationship[];
constructor(options: DesignerOptions) { constructor(options: DesignerOptions) {
super();
this._zoom = options.zoom; this._zoom = options.zoom;
this._topics = []; this._topics = [];
this._relationships = []; this._relationships = [];

View File

@ -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;

View File

@ -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<T> {
private _handlerByType: Map<T, ((args?: any) => void)[]>;
constructor() {
this._handlerByType = new Map();
}
private static _normalizeEventName<K>(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;

View File

@ -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;

View File

@ -20,11 +20,13 @@ import { FontStyle } from '@wisemapping/web2d/src/components/peer/svg/FontPeer';
import $ from 'jquery'; import $ from 'jquery';
import ActionDispatcher from './ActionDispatcher'; import ActionDispatcher from './ActionDispatcher';
import Events from './Events'; import EventDispatcher from './EventDispatcher';
import EventBus from './layout/EventBus'; import EventBus from './layout/EventBus';
import Topic from './Topic'; import Topic from './Topic';
class EditorComponent extends Events { type EditorEventType = 'input';
class EditorComponent extends EventDispatcher<EditorEventType> {
private _topic: Topic; private _topic: Topic;
private _oldText: string | undefined; private _oldText: string | undefined;

View File

@ -55,7 +55,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
this._actionRunner = new DesignerActionRunner(commandContext, this); 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); const command = new AddTopicCommand(models, parentTopicsId);
this.execute(command); this.execute(command);
} }

View File

@ -27,7 +27,7 @@ import EventBus from './layout/EventBus';
import ShirinkConnector from './ShrinkConnector'; import ShirinkConnector from './ShrinkConnector';
import ActionDispatcher from './ActionDispatcher'; import ActionDispatcher from './ActionDispatcher';
import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher'; import TopicEventDispatcher from './TopicEventDispatcher';
import { TopicShapeType } from './model/INodeModel'; import { TopicShapeType } from './model/INodeModel';
import NodeModel from './model/NodeModel'; import NodeModel from './model/NodeModel';
import Relationship from './Relationship'; import Relationship from './Relationship';
@ -528,7 +528,7 @@ abstract class Topic extends NodeGraph {
} }
const eventDispatcher = me._getTopicEventDispatcher(); const eventDispatcher = me._getTopicEventDispatcher();
eventDispatcher.process(TopicEvent.CLICK, me); eventDispatcher.process('clicknode', me);
event.stopPropagation(); event.stopPropagation();
}); });
} }
@ -594,7 +594,7 @@ abstract class Topic extends NodeGraph {
getShrinkConnector(): ShirinkConnector | null { getShrinkConnector(): ShirinkConnector | null {
let result = this._connector; let result = this._connector;
if (this._connector == null) { if (!this._connector) {
this._connector = new ShirinkConnector(this); this._connector = new ShirinkConnector(this);
this._connector.setVisibility(false); this._connector.setVisibility(false);
result = this._connector; result = this._connector;
@ -693,11 +693,8 @@ abstract class Topic extends NodeGraph {
* Point: references the center of the rect shape.!!! * Point: references the center of the rect shape.!!!
*/ */
setPosition(point: PositionType): void { 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 // 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); point.x = Math.ceil(point.x);
// eslint-disable-next-line no-param-reassign
point.y = Math.ceil(point.y); point.y = Math.ceil(point.y);
// Update model's position ... // Update model's position ...
@ -721,7 +718,6 @@ abstract class Topic extends NodeGraph {
this.invariant(); this.invariant();
} }
/** */
getOutgoingLine(): ConnectionLine | null { getOutgoingLine(): ConnectionLine | null {
return this._outgoingLine; return this._outgoingLine;
} }
@ -959,7 +955,7 @@ abstract class Topic extends NodeGraph {
this.redraw(); this.redraw();
} }
connectTo(targetTopic: Topic, workspace: Canvas): void { connectTo(targetTopic: Topic, canvas: Canvas): void {
// Connect Graphical Nodes ... // Connect Graphical Nodes ...
targetTopic.append(this); targetTopic.append(this);
this._parent = targetTopic; this._parent = targetTopic;
@ -971,10 +967,10 @@ abstract class Topic extends NodeGraph {
// Create a connection line ... // Create a connection line ...
const outgoingLine = this.createConnectionLine(targetTopic); const outgoingLine = this.createConnectionLine(targetTopic);
outgoingLine.setVisibility(false); // outgoingLine.setVisibility(false);
this._outgoingLine = outgoingLine; this._outgoingLine = outgoingLine;
workspace.append(outgoingLine); canvas.append(outgoingLine);
// Update figure is necessary. // Update figure is necessary.
this.updateTopicShape(targetTopic); this.updateTopicShape(targetTopic);

View File

@ -15,20 +15,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import Events from './Events'; import EventDispispatcher from './EventDispatcher';
import Topic from './Topic'; import Topic from './Topic';
import MultitTextEditor from './MultilineTextEditor'; import MultitTextEditor from './MultilineTextEditor';
const TopicEvent = { type TopicEventType = 'editnode' | 'clicknode';
EDIT: 'editnode',
CLICK: 'clicknode',
};
class TopicEventDispatcher extends Events { class TopicEventDispatcher extends EventDispispatcher<TopicEventType> {
private _readOnly: boolean; private _readOnly: boolean;
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
static _instance: TopicEventDispatcher; private static _instance: TopicEventDispatcher;
constructor(readOnly: boolean) { constructor(readOnly: boolean) {
super(); super();
@ -43,10 +40,10 @@ class TopicEventDispatcher extends Events {
} }
show(topic: Topic, textOverwrite?: string): void { 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 .... // Close all previous open editor ....
const editor = MultitTextEditor.getInstance(); const editor = MultitTextEditor.getInstance();
if (editor.isActive()) { if (editor.isActive()) {
@ -55,7 +52,7 @@ class TopicEventDispatcher extends Events {
// Open the new editor ... // Open the new editor ...
const model = topic.getModel(); const model = topic.getModel();
if (!this._readOnly && eventType === TopicEvent.EDIT) { if (!this._readOnly && eventType === 'editnode') {
editor.show(topic, textOverwrite); editor.show(topic, textOverwrite);
} else { } else {
this.fireEvent(eventType, { model, readOnly: this._readOnly }); this.fireEvent(eventType, { model, readOnly: this._readOnly });
@ -66,8 +63,13 @@ class TopicEventDispatcher extends Events {
return MultitTextEditor.getInstance().isActive(); 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); this._instance = new TopicEventDispatcher(readOnly);
return this._instance;
} }
static getInstance(): TopicEventDispatcher { static getInstance(): TopicEventDispatcher {
@ -77,6 +79,4 @@ class TopicEventDispatcher extends Events {
return this._instance; return this._instance;
} }
} }
export { TopicEvent };
export default TopicEventDispatcher; export default TopicEventDispatcher;

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* /*
* Copyright [2021] [wisemapping] * Copyright [2021] [wisemapping]
* *
@ -15,18 +16,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import Events from '../Events'; import EventDispispatcher from '../EventDispatcher';
import { EventBusType } from '../EventBusType';
export type EventType = class EventBus extends EventDispispatcher<EventBusType> {
| 'topicResize'
| 'topicMoved'
| 'childShrinked'
| 'topicConnected'
| 'topicAdded'
| 'topicRemoved'
| 'forceLayout'
| 'topicDisconect';
class EventBus extends Events {
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
static _instance: EventBus = new EventBus(); static _instance: EventBus = new EventBus();
@ -34,11 +27,11 @@ class EventBus extends Events {
return this._instance; return this._instance;
} }
fireEvent(type: EventType, eventArgs?: unknown[] | unknown): Events { fireEvent(type: EventBusType, arg?: any): void {
return super.fireEvent(type, eventArgs); 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); return super.addEvent(type, fn, internal);
} }
} }

View File

@ -45,15 +45,15 @@ class EventBusDispatcher {
} }
private _topicResizeEvent(args: { node: Topic; size: SizeType }) { 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 }) { 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) { private _topicDisconect(node: Topic) {
this._layoutManager!.disconnectNode(node.getId()); this.getLayoutManager().disconnectNode(node.getId());
} }
private _topicConnected(args: { parentNode: Topic; childNode: Topic }) { 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) { private _childShrinked(node: Topic) {
this._layoutManager!.updateShrinkState(node.getId(), node.areChildrenShrunken()); this.getLayoutManager().updateShrinkState(node.getId(), node.areChildrenShrunken());
} }
private _topicAdded(node: Topic) { private _topicAdded(node: Topic) {
// Central topic must not be added twice ... // Central topic must not be added twice ...
if (node.getId() !== 0) { if (node.getId() !== 0) {
this._layoutManager!.addNode(node.getId(), { width: 10, height: 10 }, node.getPosition()); this.getLayoutManager().addNode(node.getId(), { width: 10, height: 10 }, node.getPosition());
this._layoutManager!.updateShrinkState(node.getId(), node.areChildrenShrunken()); this.getLayoutManager().updateShrinkState(node.getId(), node.areChildrenShrunken());
} }
} }
private _topicRemoved(node: Topic) { private _topicRemoved(node: Topic) {
this._layoutManager!.removeNode(node.getId()); this.getLayoutManager().removeNode(node.getId());
} }
private _forceLayout() { private _forceLayout(): void {
this._layoutManager!.layout(true); this.getLayoutManager().layout(true);
}
getLayoutManager(): LayoutManager {
return this._layoutManager!;
} }
} }

View File

@ -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;

View File

@ -16,15 +16,16 @@
* limitations under the License. * limitations under the License.
*/ */
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import Events from '../Events'; import EventDispispatcher from '../EventDispatcher';
import RootedTreeSet from './RootedTreeSet'; import RootedTreeSet from './RootedTreeSet';
import OriginalLayout from './OriginalLayout'; import OriginalLayout from './OriginalLayout';
import ChangeEvent from './ChangeEvent'; import ChangeEvent from './ChangeEvent';
import SizeType from '../SizeType'; import SizeType from '../SizeType';
import Node from './Node'; import Node from './Node';
import PositionType from '../PositionType'; import PositionType from '../PositionType';
import LayoutEventType from './LayoutEventType';
class LayoutManager extends Events { class LayoutManager extends EventDispispatcher<LayoutEventType> {
private _treeSet: RootedTreeSet; private _treeSet: RootedTreeSet;
private _layout: OriginalLayout; private _layout: OriginalLayout;

View File

@ -15,6 +15,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
type ThemeType = 'classic' | 'prism'; type ThemeType = 'classic' | 'prism' | 'dark-prism';
export default ThemeType; export default ThemeType;

View File

@ -1,11 +1,10 @@
import NodeModel from '../model/NodeModel'; import NodeModel from '../model/NodeModel';
import ThemeType from '../model/ThemeType';
import ClassicTheme from './ClassicTheme'; import ClassicTheme from './ClassicTheme';
import DarkPrismTheme from './DarkPrismTheme'; import DarkPrismTheme from './DarkPrismTheme';
import PrismTheme from './PrismTheme'; import PrismTheme from './PrismTheme';
import Theme from './Theme'; import Theme from './Theme';
type ThemeId = 'prism' | 'classic' | 'dark-prism';
class ThemeFactory { class ThemeFactory {
private static prismTheme = new PrismTheme(); private static prismTheme = new PrismTheme();
@ -13,7 +12,7 @@ class ThemeFactory {
private static classicTheme = new ClassicTheme(); private static classicTheme = new ClassicTheme();
static createById(id: ThemeId): Theme { static createById(id: ThemeType): Theme {
let result: Theme; let result: Theme;
switch (id) { switch (id) {
case 'classic': case 'classic':

View File

@ -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<TopicArgs> = (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',
};

View File

@ -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;

View File

@ -21,6 +21,7 @@ export default {
noteText: { control: 'text' }, noteText: { control: 'text' },
linkText: { control: 'text' }, linkText: { control: 'text' },
eicon: { control: 'multi-select', options: ['❤️', '🌈', '🖇️'] }, eicon: { control: 'multi-select', options: ['❤️', '🌈', '🖇️'] },
theme: { control: 'select', options: ['classic', 'prism', 'dark-prism'] },
}, },
} as Meta; } as Meta;
@ -83,3 +84,19 @@ ShapeNone.args = {
eicon: ['🌈'], eicon: ['🌈'],
shapeType: 'none', 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',
};

View File

@ -8,6 +8,7 @@ import ScreenManager from '../../../src/components/ScreenManager';
import EmojiIconModel from '../../../src/components/model/EmojiIconModel'; import EmojiIconModel from '../../../src/components/model/EmojiIconModel';
import TopicEventDispatcher from '../../../src/components/TopicEventDispatcher'; import TopicEventDispatcher from '../../../src/components/TopicEventDispatcher';
import { TopicShapeType } from '../../../src/components/model/INodeModel'; import { TopicShapeType } from '../../../src/components/model/INodeModel';
import ThemeType from '../../../src/components/model/ThemeType';
const registerRefreshHook = (topic: Topic) => { const registerRefreshHook = (topic: Topic) => {
// Trigger a redraw after the node is added ... // Trigger a redraw after the node is added ...
@ -34,6 +35,7 @@ export type TopicArgs = {
noteText?: string; noteText?: string;
linkText?: string; linkText?: string;
eicon?: string[]; eicon?: string[];
theme?: ThemeType;
}; };
const createTopic = ({ const createTopic = ({
@ -47,6 +49,7 @@ const createTopic = ({
noteText = undefined, noteText = undefined,
linkText = undefined, linkText = undefined,
eicon = undefined, eicon = undefined,
theme = undefined,
readOnly = true, readOnly = true,
}: TopicArgs) => { }: TopicArgs) => {
// Build basic container ... // Build basic container ...
@ -92,6 +95,11 @@ const createTopic = ({
}); });
} }
// Theme ...
if (theme) {
mindmap.setTheme(theme);
}
// Create topic UI element ... // Create topic UI element ...
mindmap.addBranch(model); mindmap.addBranch(model);
const centralTopic = new CentralTopic(model, { readOnly }); const centralTopic = new CentralTopic(model, { readOnly });