Merge branch 'develop' of https://bitbucket.org/wisemapping/wisemapping-frontend into feature/testing-editor

This commit is contained in:
Ezequiel-Vega 2022-01-26 12:38:21 -03:00
commit d81b68bf33
15 changed files with 204 additions and 200 deletions

View File

@ -43,6 +43,9 @@
"import/resolver": { "import/resolver": {
"webpack": { "webpack": {
"config": "./webpack.common.js" "config": "./webpack.common.js"
},
"node": {
"extensions": [".js",".ts"]
} }
} }
} }

View File

@ -48,8 +48,6 @@ import LayoutManager from './layout/LayoutManager';
import { TopicShape } from './model/INodeModel'; import { TopicShape } from './model/INodeModel';
import { $notify } from './widget/ToolbarNotifier'; import { $notify } from './widget/ToolbarNotifier';
import ImageExpoterFactory from './export/ImageExporterFactory';
import TextExporterFactory from './export/TextExporterFactory';
import RelationshipModel from './model/RelationshipModel'; import RelationshipModel from './model/RelationshipModel';
import Mindmap from './model/Mindmap'; import Mindmap from './model/Mindmap';
import NodeModel from './model/NodeModel'; import NodeModel from './model/NodeModel';
@ -57,8 +55,7 @@ import Topic from './Topic';
import { DesignerOptions } from './DesignerOptionsBuilder'; import { DesignerOptions } from './DesignerOptionsBuilder';
import MainTopic from './MainTopic'; import MainTopic from './MainTopic';
import DragTopic from './DragTopic'; import DragTopic from './DragTopic';
import CentralTopic from './CentralTopic';
export type ExportFormat = 'png' | 'svg' | 'jpg' | 'wxml';
class Designer extends Events { class Designer extends Events {
private _mindmap: Mindmap; private _mindmap: Mindmap;
@ -94,7 +91,9 @@ class Designer extends Events {
this._options = options; this._options = options;
// Set full div elem render area ... // Set full div elem render area ...
if (options.containerSize) {
divElement.css(options.containerSize); divElement.css(options.containerSize);
}
// Dispatcher manager ... // Dispatcher manager ...
const commandContext = new CommandContext(this); const commandContext = new CommandContext(this);
@ -180,14 +179,16 @@ class Designer extends Events {
// Deselect on click ... // Deselect on click ...
screenManager.addEvent('click', (event: UIEvent) => { screenManager.addEvent('click', (event: UIEvent) => {
me.onObjectFocusEvent(null, event); me.onObjectFocusEvent(undefined, event);
}); });
// Create nodes on double click... // Create nodes on double click...
screenManager.addEvent('dblclick', (event: MouseEvent) => { screenManager.addEvent('dblclick', (event: MouseEvent) => {
if (workspace.isWorkspaceEventsEnabled()) { if (workspace.isWorkspaceEventsEnabled()) {
const mousePos = screenManager.getWorkspaceMousePosition(event); const mousePos = screenManager.getWorkspaceMousePosition(event);
const centralTopic = me.getModel().getCentralTopic(); const centralTopic:CentralTopic = me.getModel()
.getCentralTopic();
const model = me._createChildModel(centralTopic, mousePos); const model = me._createChildModel(centralTopic, mousePos);
this._actionDispatcher.addTopics([model], [centralTopic.getId()]); this._actionDispatcher.addTopics([model], [centralTopic.getId()]);
} }
@ -282,13 +283,7 @@ class Designer extends Events {
return topic; return topic;
} }
/** onObjectFocusEvent(currentObject?: Topic, event?): void {
* @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
*/
onObjectFocusEvent(currentObject: Topic = null, event = null): void {
// Close node editors .. // Close node editors ..
const topics = this.getModel().getTopics(); const topics = this.getModel().getTopics();
topics.forEach((topic) => topic.closeEditors()); topics.forEach((topic) => topic.closeEditors());
@ -347,32 +342,6 @@ class Designer extends Events {
} }
} }
EXPORT_SUPPORTED_FORMATS: ExportFormat[] = ['png', 'svg', 'jpg', 'wxml'];
export(formatType: ExportFormat): Promise<string> {
const workspace = this._workspace;
const svgElement = workspace.getSVGElement();
const size = workspace.getSize();
let exporter;
switch (formatType) {
case 'png':
case 'jpg':
case 'svg': {
exporter = ImageExpoterFactory.create(formatType, this._mindmap, svgElement, size.width, size.height);
break;
}
case 'wxml': {
exporter = TextExporterFactory.create(formatType, this._mindmap);
break;
}
default:
throw new Error('Unsupported encoding');
}
return exporter.export();
}
zoomIn(factor = 1.2): void { zoomIn(factor = 1.2): void {
const model = this.getModel(); const model = this.getModel();
const scale = model.getZoom() / factor; const scale = model.getZoom() / factor;
@ -530,6 +499,7 @@ class Designer extends Events {
const parentTopic = topic.getOutgoingConnectedTopic(); const parentTopic = topic.getOutgoingConnectedTopic();
const siblingModel = this._createSiblingModel(topic); const siblingModel = this._createSiblingModel(topic);
if (siblingModel) {
// Hack: if parent is central topic, add node below not on opposite side. // Hack: if parent is central topic, add node below not on opposite side.
// This should be done in the layout // This should be done in the layout
if (parentTopic.getType() === 'CentralTopic') { if (parentTopic.getType() === 'CentralTopic') {
@ -540,11 +510,13 @@ class Designer extends Events {
this._actionDispatcher.addTopics([siblingModel], [parentTopicId]); this._actionDispatcher.addTopics([siblingModel], [parentTopicId]);
} }
} }
}
private _createSiblingModel(topic: Topic): NodeModel { private _createSiblingModel(topic: Topic): NodeModel | undefined {
let result = null; let result: NodeModel | undefined;
let model = null; let model: NodeModel;
const parentTopic = topic.getOutgoingConnectedTopic(); const parentTopic = topic.getOutgoingConnectedTopic();
if (parentTopic != null) { if (parentTopic != null) {
// Create a new node ... // Create a new node ...
model = topic.getModel(); model = topic.getModel();
@ -555,9 +527,9 @@ class Designer extends Events {
const order = topic.getOrder() + 1; const order = topic.getOrder() + 1;
result.setOrder(order); result.setOrder(order);
result.setPosition(10, 10); // Set a dummy position ... result.setPosition(10, 10); // Set a dummy position ...
}
this._copyNodeProps(model, result); this._copyNodeProps(model, result);
}
return result; return result;
} }
@ -598,8 +570,10 @@ class Designer extends Events {
layoutManager.addEvent('change', (event) => { layoutManager.addEvent('change', (event) => {
const id = event.getId(); const id = event.getId();
const topic = me.getModel().findTopicById(id); const topic = me.getModel().findTopicById(id);
if (topic) {
topic.setPosition(event.getPosition()); topic.setPosition(event.getPosition());
topic.setOrder(event.getOrder()); topic.setOrder(event.getOrder());
}
}); });
this._eventBussDispatcher.setLayoutManager(layoutManager); this._eventBussDispatcher.setLayoutManager(layoutManager);
@ -636,9 +610,8 @@ class Designer extends Events {
this._actionDispatcher.actionRunner.redo(); this._actionDispatcher.actionRunner.redo();
} }
/** */
isReadOnly(): boolean { isReadOnly(): boolean {
return this._options.readOnly; return Boolean(this._options?.readOnly);
} }
nodeModelToTopic(nodeModel: NodeModel): Topic { nodeModelToTopic(nodeModel: NodeModel): Topic {
@ -825,15 +798,14 @@ class Designer extends Events {
} }
} }
/** */
changeFontFamily(font: string) { changeFontFamily(font: string) {
const topicsIds = this.getModel().filterTopicsIds(); const topicsIds = this.getModel()
.filterTopicsIds();
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.changeFontFamilyToTopic(topicsIds, font); this._actionDispatcher.changeFontFamilyToTopic(topicsIds, font);
} }
} }
/** */
changeFontStyle(): void { changeFontStyle(): void {
const topicsIds = this.getModel() const topicsIds = this.getModel()
.filterTopicsIds(); .filterTopicsIds();
@ -842,7 +814,6 @@ class Designer extends Events {
} }
} }
/** */
changeFontColor(color: string) { changeFontColor(color: string) {
$assert(color, 'color can not be null'); $assert(color, 'color can not be null');
@ -883,9 +854,8 @@ class Designer extends Events {
} }
} }
/** */ changeTopicShape(shape: string) {
changeTopicShape(shape) { const validateFunc = (topic: Topic) => !(
const validateFunc = (topic) => !(
topic.getType() === 'CentralTopic' && shape === TopicShape.LINE topic.getType() === 'CentralTopic' && shape === TopicShape.LINE
); );
@ -896,16 +866,14 @@ class Designer extends Events {
} }
} }
/** */ changeFontWeight(): void {
changeFontWeight() {
const topicsIds = this.getModel().filterTopicsIds(); const topicsIds = this.getModel().filterTopicsIds();
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.changeFontWeightToTopic(topicsIds); this._actionDispatcher.changeFontWeightToTopic(topicsIds);
} }
} }
/** */ addIconType(iconType: string): void {
addIconType(iconType) {
const topicsIds = this.getModel().filterTopicsIds(); const topicsIds = this.getModel().filterTopicsIds();
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.addFeatureToTopic(topicsIds[0], TopicFeatureFactory.Icon.id, { this._actionDispatcher.addFeatureToTopic(topicsIds[0], TopicFeatureFactory.Icon.id, {
@ -937,19 +905,22 @@ class Designer extends Events {
} }
} }
/** goToNode(node: Topic): void {
* @param {mindplot.Topic} node
* sets the focus to the given node
*/
goToNode(node) {
node.setOnFocus(true); node.setOnFocus(true);
this.onObjectFocusEvent(node); this.onObjectFocusEvent(node);
} }
/** @return {mindplot.Workspace} */ getWorkSpace(): Workspace {
getWorkSpace() {
return this._workspace; return this._workspace;
} }
public get cleanScreen(): () => void {
return this._cleanScreen;
}
public set cleanScreen(value: () => void) {
this._cleanScreen = value;
}
} }
export default Designer; export default Designer;

View File

@ -24,7 +24,7 @@ import { $notifyModal } from './widget/ModalDialogNotifier';
import { $msg } from './Messages'; import { $msg } from './Messages';
import { DesignerOptions } from './DesignerOptionsBuilder'; import { DesignerOptions } from './DesignerOptionsBuilder';
let designer = null; let designer: Designer;
export function buildDesigner(options: DesignerOptions): Designer { export function buildDesigner(options: DesignerOptions): Designer {
const divContainer = $(`#${options.container}`); const divContainer = $(`#${options.container}`);
@ -64,10 +64,10 @@ export function buildDesigner(options: DesignerOptions): Designer {
// Register toolbar event ... // Register toolbar event ...
if ($('#toolbar').length) { if ($('#toolbar').length) {
const menu = new Menu(designer, 'toolbar', options.mapId); const menu = new Menu(designer, 'toolbar', options.mapId ? options.mapId : 'unknown');
// If a node has focus, focus can be move to another node using the keys. // If a node has focus, focus can be move to another node using the keys.
designer._cleanScreen = function _cleanScreen() { designer.cleanScreen = () => {
menu.clear(); menu.clear();
}; };
} }

View File

@ -15,7 +15,7 @@
* 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 { $assert, $defined } 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 Events from './Events';
@ -24,11 +24,11 @@ import Topic from './Topic';
import { $notify } from './widget/ToolbarNotifier'; import { $notify } from './widget/ToolbarNotifier';
class DesignerModel extends Events { class DesignerModel extends Events {
_zoom: number; private _zoom: number;
_topics: Topic[]; private _topics: Topic[];
_relationships: Relationship[]; private _relationships: Relationship[];
constructor(options: DesignerOptions) { constructor(options: DesignerOptions) {
super(); super();
@ -55,12 +55,11 @@ class DesignerModel extends Events {
getCentralTopic(): CentralTopic { getCentralTopic(): CentralTopic {
const topics = this.getTopics(); const topics = this.getTopics();
return topics[0] as CentralTopic; return topics[0] as unknown as CentralTopic;
} }
/** @return {mindplot.Topic[]} selected topics */
filterSelectedTopics(): Topic[] { filterSelectedTopics(): Topic[] {
const result = []; const result: Topic[] = [];
for (let i = 0; i < this._topics.length; i++) { for (let i = 0; i < this._topics.length; i++) {
if (this._topics[i].isOnFocus()) { if (this._topics[i].isOnFocus()) {
result.push(this._topics[i]); result.push(this._topics[i]);
@ -70,7 +69,7 @@ class DesignerModel extends Events {
} }
filterSelectedRelationships(): Relationship[] { filterSelectedRelationships(): Relationship[] {
const result = []; const result:Relationship[] = [];
for (let i = 0; i < this._relationships.length; i++) { for (let i = 0; i < this._relationships.length; i++) {
if (this._relationships[i].isOnFocus()) { if (this._relationships[i].isOnFocus()) {
result.push(this._relationships[i]); result.push(this._relationships[i]);
@ -80,7 +79,8 @@ class DesignerModel extends Events {
} }
getEntities(): (Relationship | Topic)[] { getEntities(): (Relationship | Topic)[] {
let result = [].concat(this._topics); let result:(Relationship|Topic)[] = [];
result = result.concat(this._topics);
result = result.concat(this._relationships); result = result.concat(this._relationships);
return result; return result;
} }
@ -106,13 +106,13 @@ class DesignerModel extends Events {
this._relationships.push(rel); this._relationships.push(rel);
} }
filterTopicsIds(validate: (topic: Topic) => boolean = null, errorMsg = null): number[] { filterTopicsIds(validate?: (topic: Topic) => boolean, errorMsg?: string): number[] {
const result = []; const result: number[] = [];
const topics = this.filterSelectedTopics(); const topics = this.filterSelectedTopics();
let isValid = true; let isValid = true;
topics.forEach((topic) => { topics.forEach((topic) => {
if ($defined(validate)) { if (validate) {
isValid = validate(topic); isValid = validate(topic);
} }
@ -127,13 +127,13 @@ class DesignerModel extends Events {
return result; return result;
} }
selectedTopic(): Topic { selectedTopic(): Topic | undefined {
const topics = this.filterSelectedTopics(); const topics = this.filterSelectedTopics();
return (topics.length > 0) ? topics[0] : null; return (topics.length > 0) ? topics[0] : undefined;
} }
findTopicById(id: number): Topic { findTopicById(id: number): Topic | undefined {
let result = null; let result: Topic | undefined;
for (let i = 0; i < this._topics.length; i++) { for (let i = 0; i < this._topics.length; i++) {
const topic = this._topics[i]; const topic = this._topics[i];
if (topic.getId() === id) { if (topic.getId() === id) {

View File

@ -38,13 +38,13 @@ abstract class IMindmap {
abstract setVersion(version: string): void; abstract setVersion(version: string): void;
abstract addBranch(nodeModel: INodeModel): void; abstract addBranch(nodeModel): void;
abstract getBranches(): Array<INodeModel>; abstract getBranches();
abstract removeBranch(node: INodeModel): void; abstract removeBranch(node): void;
abstract getRelationships(): Array<RelationshipModel>; abstract getRelationships(): RelationshipModel[];
connect(parent: INodeModel, child: INodeModel): void { connect(parent: INodeModel, child: INodeModel): void {
// Child already has a parent ? // Child already has a parent ?
@ -71,9 +71,9 @@ abstract class IMindmap {
this.addBranch(child); this.addBranch(child);
} }
abstract hasAlreadyAdded(node: INodeModel): boolean; abstract hasAlreadyAdded(node): boolean;
abstract createNode(type: NodeType, id: number): INodeModel abstract createNode(type: NodeType, id: number)
abstract createRelationship(fromNodeId: number, toNodeId: number): void; abstract createRelationship(fromNodeId: number, toNodeId: number): void;

View File

@ -28,7 +28,7 @@ const parseJsObject = (str: string) => JSON.parse(str.replace(/(['"])?([a-z0-9A-
abstract class INodeModel { abstract class INodeModel {
static MAIN_TOPIC_TO_MAIN_TOPIC_DISTANCE = 220; static MAIN_TOPIC_TO_MAIN_TOPIC_DISTANCE = 220;
static _next_uuid = 0; private static _next_uuid = 0;
protected _mindmap: Mindmap; protected _mindmap: Mindmap;
@ -43,8 +43,7 @@ abstract class INodeModel {
abstract getFeatures(): FeatureModel[]; abstract getFeatures(): FeatureModel[];
/** */ setId(id?: number): void {
setId(id: number): void {
if (!$defined(id)) { if (!$defined(id)) {
const newId = INodeModel._nextUUID(); const newId = INodeModel._nextUUID();
this.putProperty('id', newId); this.putProperty('id', newId);
@ -79,7 +78,7 @@ abstract class INodeModel {
getPosition(): { x: number, y: number } { getPosition(): { x: number, y: number } {
const value = this.getProperty('position') as string; const value = this.getProperty('position') as string;
let result = null; let result;
if (value != null) { if (value != null) {
result = parseJsObject(value); result = parseJsObject(value);
} }
@ -92,7 +91,7 @@ abstract class INodeModel {
getImageSize(): {width: number, height: number} { getImageSize(): {width: number, height: number} {
const value = this.getProperty('imageSize') as string; const value = this.getProperty('imageSize') as string;
let result = null; let result;
if (value != null) { if (value != null) {
result = parseJsObject(value); result = parseJsObject(value);
} }
@ -261,7 +260,7 @@ abstract class INodeModel {
const tmindmap = target.getMindmap(); const tmindmap = target.getMindmap();
children.forEach((snode) => { children.forEach((snode) => {
const tnode = tmindmap.createNode(snode.getType(), snode.getId()); const tnode:INodeModel = tmindmap.createNode(snode.getType(), snode.getId());
snode.copyTo(tnode); snode.copyTo(tnode);
target.append(tnode); target.append(tnode);
}); });
@ -276,16 +275,14 @@ abstract class INodeModel {
deleteNode(): void { deleteNode(): void {
const mindmap = this.getMindmap(); const mindmap = this.getMindmap();
// console.log("Before:" + mindmap.inspect());
const parent = this.getParent(); const parent = this.getParent();
if ($defined(parent)) { if (parent) {
parent.removeChild(this); parent.removeChild(this);
} else { } else {
// If it has not parent, it must be an isolate topic ... // If it has not parent, it must be an isolate topic ...
// @ts-ignore
mindmap.removeBranch(this); mindmap.removeBranch(this);
} }
// It's an isolated node. It must be a hole branch ...
// console.log("After:" + mindmap.inspect());
} }
abstract getPropertiesKeys(): string[]; abstract getPropertiesKeys(): string[];
@ -298,7 +295,7 @@ abstract class INodeModel {
abstract getChildren(): INodeModel[]; abstract getChildren(): INodeModel[];
abstract getParent(): INodeModel; abstract getParent(): INodeModel | null;
abstract clone(): INodeModel; abstract clone(): INodeModel;
@ -321,7 +318,7 @@ abstract class INodeModel {
findNodeById(id: number): INodeModel { findNodeById(id: number): INodeModel {
$assert(Number.isFinite(id)); $assert(Number.isFinite(id));
let result = null; let result;
if (this.getId() === id) { if (this.getId() === id) {
result = this; result = this;
} else { } else {

View File

@ -38,7 +38,7 @@ class Mindmap extends IMindmap {
$assert(id, 'Id can not be null'); $assert(id, 'Id can not be null');
this._branches = []; this._branches = [];
this._description = null; this._description = '';
this._relationships = []; this._relationships = [];
this._version = version; this._version = version;
this._id = id; this._id = id;
@ -79,7 +79,7 @@ class Mindmap extends IMindmap {
* @throws will throw an error if nodeModel is null, undefined or not a node model object * @throws will throw an error if nodeModel is null, undefined or not a node model object
* @throws will throw an error if * @throws will throw an error if
*/ */
addBranch(nodeModel: NodeModel): void { addBranch(nodeModel: INodeModel): void {
$assert(nodeModel && nodeModel.isNodeModel(), 'Add node must be invoked with model objects'); $assert(nodeModel && nodeModel.isNodeModel(), 'Add node must be invoked with model objects');
const branches = this.getBranches(); const branches = this.getBranches();
if (branches.length === 0) { if (branches.length === 0) {
@ -89,18 +89,18 @@ class Mindmap extends IMindmap {
$assert(nodeModel.getType() !== 'CentralTopic', 'Mindmaps only have one cental topic'); $assert(nodeModel.getType() !== 'CentralTopic', 'Mindmaps only have one cental topic');
} }
this._branches.push(nodeModel); this._branches.push(nodeModel as NodeModel);
} }
/** /**
* @param nodeModel * @param nodeModel
*/ */
removeBranch(nodeModel: INodeModel): void { removeBranch(nodeModel: NodeModel): void {
$assert(nodeModel && nodeModel.isNodeModel(), 'Remove node must be invoked with model objects'); $assert(nodeModel && nodeModel.isNodeModel(), 'Remove node must be invoked with model objects');
this._branches = this._branches.filter((b) => b !== nodeModel); this._branches = this._branches.filter((b) => b !== nodeModel);
} }
getBranches() { getBranches():NodeModel[] {
return this._branches; return this._branches;
} }
@ -122,7 +122,7 @@ class Mindmap extends IMindmap {
return result; return result;
} }
createNode(type: NodeModelType = 'MainTopic', id: number) { createNode(type: NodeModelType = 'MainTopic', id?: number):NodeModel {
return new NodeModel(type, this, id); return new NodeModel(type, this, id);
} }
@ -148,7 +148,7 @@ class Mindmap extends IMindmap {
} }
findNodeById(id: number) { findNodeById(id: number) {
let result = null; let result;
for (let i = 0; i < this._branches.length; i++) { for (let i = 0; i < this._branches.length; i++) {
const branch = this._branches[i]; const branch = this._branches[i];
result = branch.findNodeById(id); result = branch.findNodeById(id);

View File

@ -31,9 +31,9 @@ class NodeModel extends INodeModel {
private _features: FeatureModel[]; private _features: FeatureModel[];
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
private _parent: NodeModel; private _parent: NodeModel | null;
constructor(type: NodeModelType, mindmap: Mindmap, id: number) { constructor(type: NodeModelType, mindmap: Mindmap, id?: number) {
$assert(type, 'Node type can not be null'); $assert(type, 'Node type can not be null');
$assert(mindmap, 'mindmap can not be null'); $assert(mindmap, 'mindmap can not be null');
super(mindmap); super(mindmap);
@ -121,10 +121,9 @@ class NodeModel extends INodeModel {
return this._properties; return this._properties;
} }
getProperty(key: string) { getProperty(key: string): number | string | boolean {
$defined(key, 'key can not be null'); $defined(key, 'key can not be null');
const result = this._properties[key]; return this._properties[key];
return !$defined(result) ? null : result;
} }
/** /**
@ -187,7 +186,7 @@ class NodeModel extends INodeModel {
return this._children; return this._children;
} }
getParent(): NodeModel { getParent(): NodeModel | null {
return this._parent; return this._parent;
} }

View File

@ -106,7 +106,7 @@ class Menu extends IMenu {
} }
return result; return result;
}, },
setValue(value) { setValue(value:string) {
designer.changeTopicShape(value); designer.changeTopicShape(value);
}, },
}; };
@ -121,7 +121,7 @@ class Menu extends IMenu {
getValue() { getValue() {
return null; return null;
}, },
setValue(value) { setValue(value: string) {
designer.addIconType(value); designer.addIconType(value);
}, },
}; };
@ -146,7 +146,7 @@ class Menu extends IMenu {
} }
return result; return result;
}, },
setValue(hex) { setValue(hex:string) {
designer.changeBackgroundColor(hex); designer.changeBackgroundColor(hex);
}, },
}; };

View File

@ -24,6 +24,10 @@ import LocalStorageManager from './components/LocalStorageManager';
import RESTPersistenceManager from './components/RestPersistenceManager'; import RESTPersistenceManager from './components/RestPersistenceManager';
import Menu from './components/widget/Menu'; import Menu from './components/widget/Menu';
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder'; import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
import ImageExporterFactory from './components/export/ImageExporterFactory';
import TextExporterFactory from './components/export/TextExporterFactory';
import Exporter from './components/export/Exporter';
import { import {
buildDesigner, buildDesigner,
} from './components/DesignerBuilder'; } from './components/DesignerBuilder';
@ -32,6 +36,7 @@ import {
} from './components/widget/ToolbarNotifier'; } from './components/widget/ToolbarNotifier';
// This hack is required to initialize Bootstrap. In future, this should be removed. // This hack is required to initialize Bootstrap. In future, this should be removed.
// @ts-ignore
global.jQuery = jquery; global.jQuery = jquery;
require('@libraries/bootstrap/js/bootstrap'); require('@libraries/bootstrap/js/bootstrap');
@ -45,5 +50,8 @@ export {
LocalStorageManager, LocalStorageManager,
DesignerOptionsBuilder, DesignerOptionsBuilder,
buildDesigner, buildDesigner,
TextExporterFactory,
ImageExporterFactory,
Exporter,
$notify, $notify,
}; };

View File

@ -7,7 +7,10 @@
"moduleResolution": "Node", "moduleResolution": "Node",
"target": "es6", "target": "es6",
"allowJs": true, "allowJs": true,
"esModuleInterop": true "esModuleInterop": true,
"rootDirs": [
"src",
]
}, },
"exclude": ["node_modules"], "exclude": ["node_modules"],
"files": ["mindplot.d.ts"] "files": ["mindplot.d.ts"]

View File

@ -10,7 +10,7 @@ module.exports = {
}, },
}, },
entry: { entry: {
mindplot: './src/index.js', mindplot: './src/index.ts',
loader: './src/indexLoader.ts', loader: './src/indexLoader.ts',
}, },
mode: 'production', mode: 'production',

View File

@ -60,7 +60,7 @@
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57", "@material-ui/lab": "^4.0.0-alpha.57",
"@reduxjs/toolkit": "^1.5.0", "@reduxjs/toolkit": "^1.5.0",
"@wisemapping/editor": "^0.1.0", "@wisemapping/editor": "^0.4.0",
"axios": "^0.21.0", "axios": "^0.21.0",
"dayjs": "^1.10.4", "dayjs": "^1.10.4",
"react": "^17.0.0", "react": "^17.0.0",

View File

@ -10,8 +10,9 @@ import FormControlLabel from '@material-ui/core/FormControlLabel';
import Radio from '@material-ui/core/Radio'; import Radio from '@material-ui/core/Radio';
import Select from '@material-ui/core/Select'; import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from '@material-ui/core/MenuItem';
import { Designer, TextExporterFactory, ImageExpoterFactory, Exporter, MindMap, RESTPersistenceManager } from '@wisemapping/mindplot';
type ExportFormat = 'pdf' | 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'txt'; type ExportFormat = 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'md';
type ExportGroup = 'image' | 'document' | 'mindmap-tool'; type ExportGroup = 'image' | 'document' | 'mindmap-tool';
type ExportDialogProps = { type ExportDialogProps = {
@ -24,15 +25,11 @@ type ExportDialogProps = {
const ExportDialog = ({ const ExportDialog = ({
mapId, mapId,
onClose, onClose,
enableImgExport, enableImgExport
svgXml,
}: ExportDialogProps): React.ReactElement => { }: ExportDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const [submit, setSubmit] = React.useState<boolean>(false); const [submit, setSubmit] = React.useState<boolean>(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [formExportRef, setExportFormRef] = React.useState<any>();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [formTransformtRef, setTransformFormRef] = React.useState<any>();
const [exportGroup, setExportGroup] = React.useState<ExportGroup>( const [exportGroup, setExportGroup] = React.useState<ExportGroup>(
enableImgExport ? 'image' : 'document' enableImgExport ? 'image' : 'document'
); );
@ -52,7 +49,7 @@ const ExportDialog = ({
let defaultFormat: ExportFormat; let defaultFormat: ExportFormat;
switch (value) { switch (value) {
case 'document': case 'document':
defaultFormat = 'pdf'; defaultFormat = 'txt';
break; break;
case 'image': case 'image':
defaultFormat = 'svg'; defaultFormat = 'svg';
@ -72,21 +69,61 @@ const ExportDialog = ({
setSubmit(true); setSubmit(true);
}; };
useEffect(() => { const exporter = (formatType: ExportFormat) => {
if (submit) { let svgElement: Element | null = null;
// TODO: Remove usage of global "designer" let size;
const designer = global.designer; let mindmap: MindMap;
const designer: Designer = global.designer;
if (designer != null) {
// Depending on the type of export. It will require differt POST. // Depending on the type of export. It will require differt POST.
if ( const workspace = designer.getWorkspace();
designer && svgElement = workspace.getSVGElement();
designer.EXPORT_SUPPORTED_FORMATS.includes(exportFormat) size = workspace.getSize();
) { mindmap = designer.getMindmap();
designer.export(exportFormat) } else {
// Load mindmap ...
const persistence = new RESTPersistenceManager({
documentUrl: '/c/restful/maps/{id}/document',
revertUrl: '/c/restful/maps/{id}/history/latest',
lockUrl: '/c/restful/maps/{id}/lock',
timestamp: global.lockTimestamp,
session: global.lockSession,
});
mindmap = persistence.load(global.mapId)
}
let exporter: Exporter;
switch (formatType) {
case 'png':
case 'jpg':
case 'svg': {
exporter = ImageExpoterFactory.create(formatType, mindmap, svgElement, size.width, size.height);
break;
}
case 'wxml':
case 'md':
case 'txt': {
exporter = TextExporterFactory.create(formatType, mindmap);
break;
}
default:
throw new Error('Unsupported encoding');
}
return exporter.export();
};
useEffect(() => {
const { map } = fetchMapById(mapId);
if (submit) {
exporter(exportFormat)
.then((url: string) => { .then((url: string) => {
// Create hidden anchor to force download ... // Create hidden anchor to force download ...
const anchor: HTMLAnchorElement = document.createElement('a'); const anchor: HTMLAnchorElement = document.createElement('a');
anchor.style.display = 'display: none'; anchor.style.display = 'display: none';
anchor.download = `${mapId}.${exportFormat}`; anchor.download = `${map?.title}.${exportFormat}`;
anchor.href = url; anchor.href = url;
document.body.appendChild(anchor); document.body.appendChild(anchor);
@ -97,16 +134,10 @@ const ExportDialog = ({
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
document.body.removeChild(anchor); document.body.removeChild(anchor);
}); });
} else if (exportFormat === 'pdf') {
formTransformtRef?.submit();
} else {
formExportRef?.submit();
}
onClose(); onClose();
} }
}, [submit]); }, [submit]);
const { map } = fetchMapById(mapId);
return ( return (
<div> <div>
<BaseDialog <BaseDialog
@ -151,9 +182,6 @@ const ExportDialog = ({
<MenuItem value="svg" className={classes.menu}> <MenuItem value="svg" className={classes.menu}>
Scalable Vector Graphics (SVG) Scalable Vector Graphics (SVG)
</MenuItem> </MenuItem>
<MenuItem value="pdf" className={classes.menu}>
Portable Document Format (PDF)
</MenuItem>
<MenuItem value="png" className={classes.menu}> <MenuItem value="png" className={classes.menu}>
Portable Network Graphics (PNG) Portable Network Graphics (PNG)
</MenuItem> </MenuItem>
@ -189,6 +217,9 @@ const ExportDialog = ({
<MenuItem className={classes.select} value="txt"> <MenuItem className={classes.select} value="txt">
Plain Text File (TXT) Plain Text File (TXT)
</MenuItem> </MenuItem>
<MenuItem className={classes.select} value="md">
Markdown (MD)
</MenuItem>
</Select> </Select>
)} )}
</FormControl> </FormControl>
@ -227,26 +258,6 @@ const ExportDialog = ({
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
{/* Hidden form for the purpose of summit */}
<form
action={`/c/restful/maps/${mapId}.${exportFormat}`}
ref={setExportFormRef}
method="GET"
>
<input name="download" type="hidden" value={exportFormat} />
<input name="filename" type="hidden" value={map?.title} />
</form>
<form
action={`/c/restful/transform.${exportFormat}`}
ref={setTransformFormRef}
method="POST"
>
<input name="download" type="hidden" value={exportFormat} />
<input name="filename" type="hidden" value={map?.title} />
<input name="svgXml" id="svgXml" value={svgXml} type="hidden" />
</form>
</div> </div>
); );
}; };

View File

@ -3294,6 +3294,18 @@
jquery "3.6.0" jquery "3.6.0"
lodash "^4.17.21" lodash "^4.17.21"
"@wisemapping/mindplot@^0.4.15":
version "0.4.15"
resolved "https://registry.yarnpkg.com/@wisemapping/mindplot/-/mindplot-0.4.15.tgz#d4a7aa3a96bd5a91ec7f800eb392be820d97510b"
integrity sha512-4buCwA9VezQylHkZ4c7JB+MBqGAOzOnBUZEjeqkg5hZMSt0mobMD3inp+tStJbZtQQbt7p6KJ/04+ewHmGlBag==
dependencies:
"@types/jquery" "^3.5.11"
"@wisemapping/core-js" "^0.4.0"
"@wisemapping/web2d" "^0.4.0"
jest "^27.4.5"
jquery "3.6.0"
lodash "^4.17.21"
"@xtuc/ieee754@^1.2.0": "@xtuc/ieee754@^1.2.0":
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"