Merge branch 'develop'

This commit is contained in:
Paulo Gustavo Veiga 2022-02-25 20:26:53 -08:00
commit 165142a0f5
195 changed files with 8105 additions and 4856 deletions

View File

@ -21,7 +21,7 @@ pipelines:
- yarn bootstrap
- yarn build
- yarn lint
- yarn test
# - yarn test
artifacts:
- packages/**/cypress/snapshots/**/__diff_output__/*.diff.png
definitions:

View File

@ -0,0 +1,14 @@
{
"editor.try-welcome": {
"defaultMessage": "Это демо-версия редактора, можно попробовать его в деле!"
},
"editor.try-welcome-description": {
"defaultMessage": "Чтобы получить бесплатный неограниченный доступ — нужна только регистрация."
},
"login.signup": {
"defaultMessage": "Регистрация"
},
"action.share": {
"defaultMessage": "Поделиться"
}
}

7
packages/editor/src/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,26 @@
import FR from './../../compiled-lang/fr.json';
import ES from './../../compiled-lang/es.json';
import EN from './../../compiled-lang/en.json';
import DE from './../../compiled-lang/de.json';
import RU from './../../compiled-lang/ru.json';
class I18nMsg {
static loadLocaleData(locale: string) {
switch (locale) {
case 'fr':
return FR;
case 'en':
return EN;
case 'es':
return ES;
case 'de':
return DE;
case 'ru':
return RU;
default:
return EN;
}
}
}
export default I18nMsg;

View File

@ -0,0 +1,26 @@
{
"action.share": [
{
"type": 0,
"value": "Поделиться"
}
],
"editor.try-welcome": [
{
"type": 0,
"value": "Это демо-версия редактора, можно попробовать его в деле!"
}
],
"editor.try-welcome-description": [
{
"type": 0,
"value": "Чтобы получить бесплатный неограниченный доступ — нужна только регистрация."
}
],
"login.signup": [
{
"type": 0,
"value": "Регистрация"
}
]
}

View File

@ -7,12 +7,13 @@ import AddSvg from '../../../images/add.svg';
import MinusSvg from '../../../images/minus.svg';
import CenterFocusSvg from '../../../images/center_focus.svg';
import ActionButton from '../action-button';
import { EditorRenderMode } from '@wisemapping/mindplot';
export type FooterPropsType = {
showTryPanel?: boolean;
editorMode: EditorRenderMode;
};
const Footer = ({ showTryPanel }: FooterPropsType): React.ReactElement => {
const Footer = ({ editorMode }: FooterPropsType): React.ReactElement => {
const intl = useIntl();
return (
@ -37,7 +38,7 @@ const Footer = ({ showTryPanel }: FooterPropsType): React.ReactElement => {
</div>
<StyledLogo id="bottom-logo"></StyledLogo>
<Notifier id="headerNotifier"></Notifier>
{showTryPanel && (
{editorMode === 'showcase' && (
<div id="tryInfoPanel">
<p>{intl.formatMessage({ id: 'editor.try-welcome' })}</p>
<p>{intl.formatMessage({ id: 'editor.try-welcome-description' })}</p>

View File

@ -1,3 +1,72 @@
div#header {
width: 100%;
height:50px;
position: absolute;
top: 0;
z-index:1000;
}
div#headerNotifier {
border: 1px solid rgb(241, 163, 39);
background-color: rgb(252, 235, 192);
border-radius: 3px;
position: fixed;
padding: 5px 9px;
color: back;
white-space: nowrap;
margin-top: 5px;
display: none;
bottom: 10px;
}
div#toolbarRight {
float: right;
white-space: nowrap;
vertical-align: middle;
justify-content: center;
margin: 6px 10px;
height: 100%;
}
#account {
float: right;
display: inline;
}
#account >img {
width: 36x;
height: 36px;
}
#accountSettingsPanel{
padding:10px 10px;
}
#share {
margin: 0 30px;
float: right;
}
.actionButton {
cursor: pointer;
font-family: Arial, Helvetica, sans-serif;
user-select: none;
vertical-align: middle;
justify-content: center;
padding: 10px 25px;
font-size: 15px;
min-width: 64px;
box-sizing: border-box;
font-weight: 600;
border-radius: 9px;
color: white;
background-color: #ffa800;
}
.actionButton:hover {
transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
div#toolbar {
width: 100%;
height: 50px;
@ -118,8 +187,6 @@ div#exportAnchor {
background-color: #000000;
padding: 5px 5px;
color: #f5f5f5;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 11px;
}
@ -153,11 +220,7 @@ div.toolbarPanelLinkSelectedLink {
background-color: rgb(228, 226, 210);
padding: 5px 5px;
color: #f5f5f5;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 11px;
-moz-border-radius: 60px;
-webkit-border-radius: 6px;
border-radius: 6px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
border: 3px double rgb(190, 190, 190);

View File

@ -24,19 +24,21 @@ import PublicSvg from '../../../images/public.svg';
import HistorySvg from '../../../images/history.svg';
import PrintSvg from '../../../images/print.svg';
import AccountSvg from '../../../images/account.svg';
import './global-styled.css';
import { HeaderContainer, ToolbarButton, ToolbarButtonExt, ToolbarRightContainer } from './styled';
import ActionButton from '../action-button';
import { EditorRenderMode } from '@wisemapping/mindplot';
export type ToolbarActionType = 'export' | 'publish' | 'history' | 'print' | 'share';
export type ToolbarPropsType = {
isTryMode: boolean;
editorMode: EditorRenderMode;
onAction: (action: ToolbarActionType) => void;
};
export default function Toolbar({
isTryMode: isTryMode,
editorMode: editorMode,
onAction,
}: ToolbarPropsType): React.ReactElement {
const intl = useIntl();
@ -46,7 +48,7 @@ export default function Toolbar({
<div id="backToList">
<img src={BackIconSvg} />
</div>
{!isTryMode && (
{editorMode === 'edition' && (
<div id="persist" className="buttonContainer">
<ToolbarButton id="save" className="buttonOn">
<img src={SaveSvg} />
@ -110,7 +112,7 @@ export default function Toolbar({
</ToolbarButton>
</div>
<div id="separator" className="buttonContainer"></div>
{!isTryMode && (
{editorMode === 'edition' && (
<ToolbarRightContainer>
<ToolbarButton
id="export"

View File

@ -1,10 +1,12 @@
@import "compatibility.css";
/********************************************************************************/
/* Header & Toolbar Styles */
/********************************************************************************/
@import "header.css";
@import "../bootstrap/css/bootstrap.min.css";
@import "bootstrap.min.css";
html {
/* avoid bootstrap overriding font-size and breaking Mui */
font-size: initial;
}
body {
-webkit-touch-callout: none;
@ -14,6 +16,8 @@ body {
-ms-user-select: none;
user-select: none;
overflow: hidden;
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
div#mindplot {
@ -126,6 +130,7 @@ div.shareModalDialog {
}
.popover {
font-size: 13px;
max-width: none;
}
@ -206,3 +211,22 @@ div#shotcuts > img{
background-color: #000000;
color: #ffffff;
}
div#tryInfoPanel {
position: absolute;
margin: auto;
text-align: center;
top: 80px;
left: 20px;
width: 200px;
padding: 20px;
font-size: 15px;
border-radius: 9px;
background-color: white;
border: solid 2px #ffa800;
}
#tryInfoPanel > p {
justify-content: center;
padding-bottom: 20px;
}

View File

@ -1,135 +1,107 @@
import React from 'react';
import React, { useEffect } from 'react';
import Toolbar, { ToolbarActionType } from './components/toolbar';
import Footer from './components/footer';
import { IntlProvider } from 'react-intl';
import {
$notify,
buildDesigner,
LocalStorageManager,
PersistenceManager,
RESTPersistenceManager,
DesignerOptionsBuilder,
Designer
Designer,
DesignerKeyboard,
EditorRenderMode,
} from '@wisemapping/mindplot';
import FR from './compiled-lang/fr.json';
import ES from './compiled-lang/es.json';
import EN from './compiled-lang/en.json';
import DE from './compiled-lang/de.json';
import './global-styled.css';
import I18nMsg from './classes/i18n-msg';
import Messages from '@wisemapping/mindplot/src/components/Messages';
declare global {
var memoryPersistence: boolean;
var readOnly: boolean;
var lockTimestamp: string;
var lockSession: string;
var historyId: string;
var isAuth: boolean;
var mapId: number;
var userOptions: { zoom: string | number } | null;
var locale: string;
var mindmapLocked: boolean;
var mindmapLockedMsg: string;
var mapTitle: string;
// used in mindplot
var designer: Designer;
var accountEmail: string;
}
export type EditorPropsType = {
initCallback?: (locale: string) => void;
mapId?: number;
isTryMode: boolean;
readOnlyMode: boolean;
locale?: string;
export type EditorOptions = {
mode: EditorRenderMode,
locale: string,
zoom?: number,
locked?: boolean,
lockedMsg?: string;
mapTitle: string;
enableKeyboardEvents: boolean;
}
export type EditorProps = {
mapId: string;
options: EditorOptions;
persistenceManager: PersistenceManager;
onAction: (action: ToolbarActionType) => void;
};
const loadLocaleData = (locale: string) => {
switch (locale) {
case 'fr':
return FR;
case 'en':
return EN;
case 'es':
return ES;
case 'de':
return DE;
default:
return EN;
}
}
const initMindplot = (locale: string) => {
// Change page title ...
document.title = `${global.mapTitle} | WiseMapping `;
// Configure persistence manager ...
let persistence: PersistenceManager;
if (!global.memoryPersistence && !global.readOnly) {
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,
});
} else {
persistence = new LocalStorageManager(
`/c/restful/maps/{id}/${global.historyId ? `${global.historyId}/` : ''}document/xml${!global.isAuth ? '-pub' : ''
}`,
true
);
}
const params = new URLSearchParams(window.location.search.substring(1));
const zoomParam = Number.parseFloat(params.get('zoom'));
const options = DesignerOptionsBuilder.buildOptions({
persistenceManager: persistence,
readOnly: Boolean(global.readOnly || false),
mapId: String(global.mapId),
container: 'mindplot',
zoom:
zoomParam ||
(global.userOptions?.zoom != undefined
? Number.parseFloat(global.userOptions.zoom as string)
: 0.8),
locale: locale,
});
// Build designer ...
const designer = buildDesigner(options);
// Load map from XML file persisted on disk...
const instance = PersistenceManager.getInstance();
const mindmap = instance.load(String(global.mapId));
designer.loadMap(mindmap);
if (global.mindmapLocked) {
$notify(global.mindmapLockedMsg);
}
onLoad?: (designer: Designer) => void;
};
const Editor = ({
initCallback = initMindplot,
mapId,
isTryMode: isTryMode,
locale = 'en',
options,
persistenceManager,
onAction,
}: EditorPropsType): React.ReactElement => {
React.useEffect(() => {
initCallback(locale);
onLoad,
}: EditorProps) => {
useEffect(() => {
// Change page title ...
document.title = `${options.mapTitle} | WiseMapping `;
// Load mindmap ...
const designer = onLoadDesigner(mapId, options, persistenceManager);
// Has extended actions been customized ...
if (onLoad) {
onLoad(designer);
}
// Load mindmap ...
const instance = PersistenceManager.getInstance();
const mindmap = instance.load(mapId);
designer.loadMap(mindmap);
if (options.locked) {
$notify(options.lockedMsg, false);
}
}, []);
useEffect(() => {
if (options.enableKeyboardEvents) {
DesignerKeyboard.resume();
} else {
DesignerKeyboard.pause();
}
}, [options.enableKeyboardEvents]);
const onLoadDesigner = (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager): Designer => {
const buildOptions = DesignerOptionsBuilder.buildOptions({
persistenceManager,
mode: options.mode,
mapId: mapId,
container: 'mindplot',
zoom: options.zoom,
locale: options.locale,
});
// Build designer ...
return buildDesigner(buildOptions);
};
const locale = options.locale;
const msg = I18nMsg.loadLocaleData(locale);
const mindplotStyle = (options.mode === 'viewonly') ? { top: 0 } : { top: 'inherit' };
return (
<IntlProvider locale={locale} messages={loadLocaleData(locale)}>
<IntlProvider locale={locale} messages={msg}>
{(options.mode !== 'viewonly') &&
<Toolbar
isTryMode={isTryMode}
editorMode={options.mode}
onAction={onAction}
/>
<div id="mindplot"></div>
<Footer showTryPanel={isTryMode} />
}
<div id="mindplot" style={mindplotStyle}></div>
<Footer editorMode={options.mode} />
</IntlProvider >
);
}

View File

@ -25,7 +25,6 @@
<li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read
only mode</li>
<li><a href="/editor.html">Editor mode:</a> Example on how mindplot can be used for mindmap edition. Browser local storage is used for persistance.</li>
<li><a href="/container.html">Embedded:</a> Example on how to embeded editor in a iframe.</li>
</ul>
</div>
</body>

File diff suppressed because one or more lines are too long

View File

@ -1,190 +0,0 @@
html {
overflow: hidden;
}
body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
form,
fieldset,
input,
textarea,
p,
blockquote,
th,
td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
fieldset,
img {
border: 0;
}
address,
caption,
cite,
code,
dfn,
em,
strong,
th,
var {
font-style: normal;
font-weight: normal;
}
ol,
ul {
list-style: none;
}
caption,
th {
text-align: left;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
font-weight: normal;
}
q:before,
q:after {
content: '';
}
abbr,
acronym {
border: 0;
}
/**
* Percents could work for IE, but for backCompat purposes, we are using keywords.
* x-small is for IE6/7 quirks mode.
*
*/
body {
font-size: 13px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: small;
font: x-small;
}
table {
font-size: inherit;
font-size: 100%;
}
/**
* 99% for safari; 100% is too large
*/
select,
input,
textarea {
font: 99% arial, helvetica, clean, sans-serif;
}
/**
* Bump up !IE to get to 13px equivalent
*/
pre,
code {
font: 115% monospace;
font-size: 100%;
}
/**
* Default line-height based on font-size rather than "computed-value"
* see: http://www.w3.org/TR/CSS21/visudet.html#line-height
*/
body * {
line-height: 1.22em;
}
* {
margin: 0;
padding: 0;
}
body {
background-color: #fff;
}
img {
border: 0;
}
form {
padding: 0;
margin: 0;
}
p {
margin: 5px 0 5px 0;
}
ul {
list-style-position: inside;
}
a:hover,
a:active {
font: bold 100%;
text-decoration: underline;
color: black;
}
h2 {
font-size: 160%;
color: #8e9181;
}
h1 {
font-style: normal;
font-size: 180%;
color: white;
padding-bottom: 2px;
}
h3 {
/* use as subhead on main body */
clear: left;
font-style: normal;
font-size: 130%;
color: #6b6f5b;
}
h4 {
/* use as headers in footer */
font-weight: bold;
font-size: 120%;
border-bottom: 1px solid #8e9181;
color: #e2e3dd;
padding-bottom: 10px;
width: 400px;
}

View File

@ -1,95 +0,0 @@
@import "editor.css";
/* Overwrite some styles */
body {
position: inherit;
}
div#headerInfo {
height: 0;
}
div#header {
height: 35px;
}
div#headerMapTitle,
#headerActions,
#headerLogo {
display: none;
}
/* Footer Styles */
div#footer {
position: absolute;
height: 0px;
width: 100%;
bottom: 0;
left: 0;
}
div#zoomIn {
background: url(../images/zoom-in.png) no-repeat left top;
margin-top: 10px;
margin-left: 10px;
}
#zoomOut {
background: url(../images/zoom-out.png) no-repeat left top;
margin-top: 10px;
margin-left: 5px;
}
.button {
width: 20px;
height: 20px;
float: left;
cursor: pointer;
white-space: nowrap;
margin: 1px;
}
.button:hover {
float: left;
cursor: pointer;
border: 1px solid black;
border-top-color: white;
border-left-color: white;
margin: 0;
}
div#mapDetails {
float: right;
padding-top: 10px;
margin-right: 130px;
}
div#mapDetails .title {
font-weight: bold;
margin-left: 10px;
margin-right: 3px;
}
div#infoPanel {
border: 2px black solid;
position: absolute;
background: gray;
width: 100px;
height: 300px;
z-index: 100;
padding: 5px;
border-radius: 8px;
top: 150px;
right: 10px;
text-align: center;
}
div#infoPanel .textNode {
background-color: #E0E5EF;
height: 20px;
width: 80px;
border: 3px #023BB9 solid;
padding: 4px;
cursor: move
}

View File

@ -1,6 +0,0 @@
@import "toolbar.css";
#accountSettingsPanel{
padding:10px 10px;
}

View File

@ -1,10 +1,4 @@
@import "editor.css";
/* Overwrite some styles */
body {
position: inherit;
}
div#footer {
width: 100%;
padding: 20px 30px;
@ -33,3 +27,7 @@ div#footer-logo {
div#mindplot {
top:0;
}
#toolbar {
display: none;
}

View File

@ -1,68 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WiseMapping - Embedded Sample </title>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
<style>
body {
font-family: Arial, Tahoma;
background: #a9a9a9;
padding: 10px 30px;
}
iframe {
border: 0;
}
#editor {
width: 100%;
text-align: center;
}
#code {
border: 1px dashed #f5f5dc;
padding: 10px;
background: #838383;
}
</style>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
<div>
<div id="doc">
<h1>Embedded editor sample</h1>
<p>
This is a simple example of how WiseMapping can be embedded in a page.
Embedding WiseMapping editor is such simple as copying this line in your page:
</p>
<div id="code">&lt;iframe src="embedded.html?confUrl=html/container.json" width="800" height="600"&gt;&lt;/iframe&gt;</div>
<p>
The container.json file contains a set of properties that must be configured. Properties:
</p>
<ul>
<li>size: Must match with the size of the iframe</li>
<li>zoom: Scale to be applied to the map</li>
<li>readOnly: If the map could be modified.</li>
<li>persistenceManager: Persistence managers to be used. By default, local browser storage is used.</li>
<li>mapId: UUID of the map to be loaded.</li>
<li>container: div element where the mindmap will be embedded..</li>
</ul>
<p>
It's important to point out that embedded.html is a static html page that it's mean to be a template page
for advanced customization. In few words, go ahead and modify it.
</p>
</div>
<div id="editor">
<iframe src="embedded.html?confUrl=html/container.json" width="800" height="400"></iframe>
</div>
</div>
</body>
</html>

View File

@ -1,17 +0,0 @@
{
"readOnly":false,
"zoom":1.3,
"size":{
"width":800,
"height":400
},
"viewPort":
{
"width":800,
"height":400
},
"persistenceManager": "mindplot.LocalStorageManager",
"mapId": "welcome",
"container":"mindplot",
"locale": "en"
}

View File

@ -1,16 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WiseMapping - Editor </title>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
<link rel="icon" href="images/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
</head>
<body>
<div id="root" onselectstart="return false;"></div>
</body>
</html>

View File

@ -24,6 +24,7 @@
<option value="sample5">sample5</option>
<option value="sample6">sample6</option>
<option value="sample7">sample7</option>
<option value="sample8">sample8</option>
<option value="img-support">img-support</option>
<option value="error-on-load">error-on-load</option>
<option value="complex">complex</option>

View File

@ -1,44 +0,0 @@
import '../css/editor.css';
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '../../../../src/index';
import { buildDesigner, LocalStorageManager, PersistenceManager, DesignerOptionsBuilder } from '@wisemapping/mindplot';
global.accountName = 'Test User';
global.accountEmail = 'test@example.com';
global.memoryPersistence = false;
global.readOnly = false;
global.mapId = 'welcome';
global.locale = 'en';
const initialization = () => {
const p = new LocalStorageManager('samples/{id}.wxml');
const options = DesignerOptionsBuilder.buildOptions({
persistenceManager: p
});
const designer = buildDesigner(options);
designer.addEvent('loadSuccess', () => {
// Hack for automation testing ...
document.getElementById('mindplot').classList.add('ready');
});
// Load map from XML file persisted on disk...
const mapId = 'welcome';
const persistence = PersistenceManager.getInstance();
const mindmap = persistence.load(mapId);
designer.loadMap(mindmap);
}
ReactDOM.render(
<Editor
mapId={global.mapId}
memoryPersistence={global.memoryPersistence}
readOnlyMode={global.readOnly}
locale={global.locale}
onAction={(action) => console.log('action called:', action)}
initCallback={initialization}
/>,
document.getElementById('root'),
);

View File

@ -0,0 +1,53 @@
/*
* 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.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => {
const elem = document.getElementById('mindplot');
if (elem) {
elem.classList.add('ready');
}
});
};
const persistence = new LocalStorageManager('samples/{id}.wxml', false);
const mapId = 'welcome';
const options: EditorOptions = {
zoom: 0.8,
locked: false,
mapTitle: "Develop Mindnap",
mode: 'edition',
locale: 'en',
enableKeyboardEvents: true
};
ReactDOM.render(
<Editor
mapId={mapId}
options={options}
persistenceManager={persistence}
onAction={(action) => console.log('action called:', action)}
onLoad={initialization}
/>,
document.getElementById('root'),
);

View File

@ -1,35 +0,0 @@
import '../css/embedded.css';
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '../../../../src/index';
import { buildDesigner, LocalStorageManager, PersistenceManager, DesignerOptionsBuilder } from '@wisemapping/mindplot';
const initialization = () => {
// Options has been defined in by a external file ?
const p = new LocalStorageManager('samples/{id}.wxml');
const options = DesignerOptionsBuilder.buildOptions({ persistenceManager: p });
const designer = buildDesigner(options);
designer.addEvent('loadSuccess', () => {
document.getElementById('mindplot').classList.add('ready');
});
// Load map from XML file persisted on disk...
const mapId = 'welcome';
const persistence = PersistenceManager.getInstance();
const mindmap = persistence.load(mapId);
designer.loadMap(mindmap);
};
ReactDOM.render(
<Editor
mapId={global.mapId}
memoryPersistence={global.memoryPersistence}
readOnlyMode={global.readOnly}
locale={global.locale}
onAction={(action) => console.log('action called:', action)}
initCallback={initialization}
/>,
document.getElementById('root')
);

View File

@ -1,49 +0,0 @@
import '../css/viewmode.css';
import React from 'react';
import ReactDOM from 'react-dom';
import Editor from '../../../../src/index';
import { buildDesigner, LocalStorageManager, PersistenceManager, DesignerOptionsBuilder } from '@wisemapping/mindplot';
const initialization = () => {
const p = new LocalStorageManager('samples/{id}.wxml');
const options = DesignerOptionsBuilder.buildOptions({ persistenceManager: p, readOnly: true, saveOnLoad: false });
// Obtain map id from query param
const params = new URLSearchParams(window.location.search.substring(1));
const mapId = params.get('id') || 'welcome';
const designer = buildDesigner(options);
designer.addEvent('loadSuccess', () => {
document.getElementById('mindplot').classList.add('ready');
});
// Load map from XML file persisted on disk...
const persistence = PersistenceManager.getInstance();
const mindmap = persistence.load(mapId);
designer.loadMap(mindmap);
// Code for selector of map.
const mapSelectElem = document.getElementById('map-select');
mapSelectElem.addEventListener('change', (e) => {
const selectMap = e.target.value;
window.location = `${window.location.pathname}?id=${selectMap}`;
});
Array.from(mapSelectElem.options).forEach((option) => {
option.selected = option.value === mapId;
});
};
ReactDOM.render(
<Editor
mapId={global.mapId}
memoryPersistence={global.memoryPersistence}
readOnlyMode={global.readOnly}
locale={global.locale}
onAction={(action) => console.log('action called:', action)}
initCallback={initialization}
/>,
document.getElementById('root'),
);

View File

@ -0,0 +1,54 @@
import '../css/viewmode.css';
import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => {
const elem = document.getElementById('mindplot');
if (elem) {
elem.classList.add('ready');
}
// Code for selector of map.
const mapSelectElem = document.getElementById('map-select') as HTMLSelectElement;
if (mapSelectElem) {
mapSelectElem.addEventListener('change', (e) => {
// @ts-ignore
const selectMap = e.target?.value;
window.location.href = `${window.location.pathname}?id=${selectMap}`;
});
Array.from(mapSelectElem.options).forEach((option) => {
option.selected = option.value === mapId;
});
}
});
};
// Obtain map id from query param
const params = new URLSearchParams(window.location.search.substring(1));
const mapId = params.get('id') || 'welcome';
const persistence = new LocalStorageManager('samples/{id}.wxml', false);
const options: EditorOptions = {
zoom: 0.8,
locked: false,
mapTitle: "Develop Mindnap",
mode: 'viewonly',
locale: 'en',
enableKeyboardEvents: true
};
ReactDOM.render(
<Editor
mapId={mapId}
options={options}
persistenceManager={persistence}
onAction={(action) => console.log('action called:', action)}
onLoad={initialization}
/>,
document.getElementById('root'),
);

View File

@ -0,0 +1,305 @@
<map name="844354" version="tango">
<topic central="true" text="Школа Интернет Профессий" id="1" fontStyle=";15;;;;">
<topic position="281,-3696" order="0" text="Идеология" id="7" fontStyle=";15;;;;">
<topic position="441,-3828" order="0" text="Видение" id="14" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="660,-4554" order="0" text="Условия &quot;выживания&quot;" id="172" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="892,-4620" order="0" text="Автономия" id="174" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="915,-4554" order="1" text="Использование" id="176" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="872,-4488" order="2" text="Польза" id="175" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="588,-4356" order="1" text="Ресурсы" id="171" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="732,-4422" order="0" text="Личные" id="177" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="758,-4356" order="1" text="Добываемые" id="178" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="761,-4290" order="2" text="Создаваемые" id="179" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="579,-4158" order="2" text="Обмен" id="173" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="766,-4224" order="0" text="Война за ресурсы" id="180" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="764,-4158" order="1" text="Обмен ресурсами" id="181" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="794,-4092" order="2" text="Объединение ресурсов" id="182" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="594,-3960" order="3" text="Ценность" id="183" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="752,-4026" order="0" text="Создание" id="184" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="773,-3960" order="1" text="Предложение" id="185" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="736,-3894" order="2" text="Обмен" id="186" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="569,-3432" order="4" text="Роль" id="187" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="720,-3762" order="0" text="Собственник" id="188" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="942,-3828" order="0" text="Постановка целей" id="211" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="966,-3762" order="1" text="Инвестиции в системы" id="210" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1030,-3696" order="2" text="Контроль распределения ресурсов" id="217" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="747,-3366" order="2" text="Предприниматель" id="189" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1105,-3432" order="0" text="Создание систем повышения ценности" id="208" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1060,-3366" order="1" text="Поиск выгодных форм обмена" id="209" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="997,-3300" order="2" text="Масштабирование" id="216" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="716,-3234" order="3" text="Управленец" shrink="true" id="190" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="964,-3300" order="0" text="Организация процессов" id="206" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="954,-3234" order="1" text="Контроль результатов" id="207" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1080,-3168" order="2" text="Карьера от наемного сотрудника до партнера" id="215" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="704,-3168" order="4" text="Продавец" shrink="true" id="191" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="891,-3234" order="0" text="Коммуникации" id="204" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="848,-3168" order="1" text="Обмен" id="205" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1119,-3102" order="2" text="Карьера от ставки с премиями к распределению прибыли" id="214" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="715,-3102" order="5" text="Специалист" shrink="true" id="192" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="937,-3168" order="0" text="Создание ценности" id="200" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="954,-3102" order="1" text="Переработка ресурсов" id="203" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1008,-3036" order="2" text="Карьера от зарплаты к гонорару" id="213" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="730,-3036" order="6" text="Обслуживание" shrink="true" id="193" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="982,-3102" order="0" text="Обеспечение условий" id="201" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1002,-3036" order="1" text="Обслуживание процессов" id="202" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1089,-2970" order="2" text="Карьера от внутреннего найма к аутсорсу" id="212" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="702,-3564" order="1" text="Инвестор" id="257" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="977,-3630" order="1" text="Инвестиции в людей и проекты" id="258" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1004,-3498" order="2" text="Управление финансами и ресурсами" id="259" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1173,-3564" order="1" text="Создание множественных источников ресурсов, плодов и ценностей" id="260" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
</topic>
<topic position="446,-2970" order="1" text="Ценности" shrink="true" id="12" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="706,-3267" order="0" text="Социальная ответственность" id="223" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="671,-3201" order="1" text="Предпринимательство" id="226" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="638,-3135" order="2" text="Компетентность" id="227" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="750,-3069" order="3" text="Равные возможности на рынке труда" id="219" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="699,-3003" order="4" text="Финансовая независимость" id="220" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="694,-2937" order="5" text="Право на самореализацию" id="218" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="695,-2871" order="6" text="Свобода самоопределения" id="221" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="674,-2805" order="7" text="Социальное равенство" id="222" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="709,-2739" order="8" text="Уважение к любому возрасту" id="225" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="678,-2673" order="9" text="Обеспеченная старость" id="224" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="451,-2904" order="2" text="Принципы" shrink="true" id="13" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="678,-3003" order="0" text="Честность отношений" id="228" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="722,-2937" order="1" text="Ответственность за результат" id="230" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="698,-2871" order="2" text="Эффективность действий" id="231" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="685,-2805" order="3" text="Личная вовлеченность" id="229" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="453,-2838" order="3" text="Стандарты" shrink="true" id="15" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="664,-2970" order="0" text="Профессионализм" id="236" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="663,-2904" order="1" text="Высокое качество" id="232" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="639,-2838" order="2" text="Четкие сроки" id="234" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="695,-2772" order="3" text="Стабильные результаты" id="233" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="714,-2706" order="4" text="Достойное вознаграждение" id="235" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="436,-2772" order="4" text="Методы" shrink="true" id="16" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="644,-3003" order="0" text="Доступное обучение" id="237" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="788,-2937" order="1" text="Помощь в трудоустройстве/поиске сотрудников" id="238" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="725,-2871" order="2" text="Профессиональная инфраструктура" id="240" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="692,-2805" order="3" text="Консалтинг и Сопровождение" id="241" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="689,-2739" order="4" text="Аутсорсинг задач и проектов" id="242" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="742,-2673" order="5" text="Аутстаффинг сотрудников и процессов" id="239" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="597,-2607" order="6" text="Нетворкинг" id="243" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="627,-2541" order="7" text="Арбитраж споров" id="244" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="264,-2706" order="2" text="Миссия" shrink="true" id="8" fontStyle=";15;;;;">
<topic position="500,-2772" order="0" text="На что мы хотим повлиять" shrink="true" id="17" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="939,-2805" order="0" text="Положение людей предпенсионного возраста" id="245" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="981,-2739" order="1" text="Уровень диджитализации в малом и среднем бизнесе" id="246" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="484,-2706" order="1" text="Что мы хотим изменить" shrink="true" id="18" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="869,-2772" order="0" text="Подход к поиску и найму сотрудников" id="247" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="897,-2706" order="1" text="Ситуацию на рынке интернет специалистов" id="248" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="909,-2640" order="2" text="Степень доверия и взаимной ответственности" id="249" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="476,-2640" order="2" text="Что мы хотим создать" shrink="true" id="19" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="870,-2838" order="0" text="Центр подготовки интернет специалистов" id="250" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="837,-2772" order="1" text="Центр обучения предпринимателей" id="251" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="934,-2706" order="2" text="Систему профессиональных стандартов и требований" id="256" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="993,-2640" order="3" text="Площадку для коммуникации специалистов и предпринимателей" id="252" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="880,-2574" order="4" text="Агентство трудоустройства и аутстаффинга" id="253" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="959,-2508" order="5" text="Диджитал агентство по аутсорсу и управлению проектами" id="254" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1093,-2442" order="6" text="Профессиональное комьюнити интернет специалистов и онлайн предпринимателей" id="255" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="283,-2640" order="4" text="Концепция" shrink="true" id="6" fontStyle=";15;;;;">
<topic position="524,-2871" order="0" text="Формулировка проблем" id="20" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="511,-2805" order="1" text="Определение причин" id="21" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="495,-2739" order="2" text="Постановка целей" id="22" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="491,-2673" order="3" text="План достижения" id="23" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="601,-2607" order="4" text="Список необходимых преобразований" id="24" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="546,-2541" order="5" text="Список необходимых шагов" id="25" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="562,-2475" order="6" text="Список необходимых ресурсов" id="26" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="586,-2409" order="7" text="Список необходимых инструментов" id="27" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="354,-1617" order="6" text="Путь Героя (клиента)" id="9" fontStyle=";15;;;;">
<note><![CDATA[Описывается для каждой целевой группы (соискатели/работодатели)]]></note>
<topic position="584,-2244" order="0" text="Точка А" id="28" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="845,-2541" order="0" text="Главная неудовлетворенность" id="80" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1200,-2574" order="0" text="Существующие проблемы" id="85" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1240,-2508" order="1" text="Неудовлетворенные потребности" id="86" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="824,-2442" order="1" text="Причины глазами клиента " id="81" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="740,-2343" order="2" text="Состояния" id="82" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="937,-2376" order="0" text="Эмоциональное" id="98" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="916,-2310" order="1" text="Физическое" id="99" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="722,-2211" order="3" text="Страхи" id="83" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1010,-2244" order="0" text="Связанные с неудовлетворенностью" id="100" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1371,-2244" order="0" text="Как продукт избавит" id="101" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="941,-2178" order="1" text="Связанные с решением" id="102" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1301,-2178" order="0" text="Какие свойства продукта избавят" id="103" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="733,-2013" order="4" text="Желания" id="84" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="981,-2112" order="0" text="Главное внешнее желание" id="107" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="988,-2046" order="1" text="Скрытые мотивы и желания" id="108" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="894,-1947" order="2" text="Ожидания" id="109" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1074,-1980" order="0" text="Если решить" id="110" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1089,-1914" order="1" text="Если не решать" id="111" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
</topic>
<topic position="584,-1518" order="1" text="Точка Б" id="29" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="759,-1782" order="0" text="Приобретения" id="90" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1097,-1782" order="0" text="Чем станет обладать из того что хотел" id="91" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1418,-1848" order="0" text="Первичные" id="92" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1417,-1782" order="1" text="Вторичные" id="93" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1406,-1716" order="2" text="Скрытые" id="94" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="725,-1584" order="1" text="Выгоды" id="87" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="877,-1650" order="0" text="В усилиях" id="95" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="885,-1584" order="1" text="Во времени" id="96" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="876,-1518" order="2" text="В деньгах" id="97" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="739,-1419" order="2" text="Состояния" id="88" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="936,-1452" order="0" text="Эмоциональное" id="104" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="915,-1386" order="1" text="Физическое" id="105" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="724,-1254" order="3" text="Оценки" id="89" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="886,-1320" order="0" text="Самооценка" id="106" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="879,-1254" order="1" text="Признание" id="112" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="922,-1188" order="2" text="Изменение статуса" id="113" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="575,-891" order="2" text="Этапы" id="30" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="785,-1122" order="0" text="Необходимые условия" id="31" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="786,-1056" order="1" text="Необходимые ресурсы" id="32" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="779,-990" order="2" text="Необходимые знания" id="34" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="782,-924" order="3" text="Необходимые навыки" id="35" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="705,-858" order="4" text="Задачи" id="36" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="790,-792" order="5" text="Необходимые действия" id="33" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="815,-726" order="6" text="Промежуточные результаты" id="37" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="875,-660" order="7" text="Условия перехода к следующему этапу" id="38" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="328,1056" order="8" text="Матрица продуктов" id="10" fontStyle=";15;;;;">
<topic position="660,-561" order="0" text="Комплексное решение под ключ" id="39" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="963,-594" order="0" text="Для учеников" id="114" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1299,-594" order="0" text="Обучение+карьера в нашей структуре" id="116" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="1014,-528" order="1" text="Для предпринимателей" id="115" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1338,-528" order="0" text="Интернет проект под ключ" id="117" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="695,-429" order="1" text="Комплексное решение руками клиента" id="40" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1275,-462" order="0" text="Обучение+инструменты трудоустройства и продуктивности" shrink="true" id="118" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1375,-396" order="1" text="Обучение управлению интернет проектами+инструменты найма и управления" id="119" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="689,231" order="2" text="Решение конкретного этапа под ключ" id="41" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1044,-330" order="0" text="Постановка целей" id="141" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1023,-231" order="1" text="Анализ рынка" id="120" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1265,-264" order="0" text="На уровне компании" id="133" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1280,-198" order="1" text="На уровне специалиста" id="134" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="1019,-132" order="2" text="SWOT анализ" id="132" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1072,0" order="3" text="Упаковка предложения" id="121" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1376,-66" order="0" text="Ценностная концепция" id="137" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1390,0" order="1" text="Ценностное предложение" id="138" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1376,66" order="2" text="Торговое предложение" id="139" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="1110,132" order="4" text="Разработка структуры проекта" id="122" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1163,198" order="5" text="Разработка стратегии достижения целей" id="123" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1115,264" order="6" text="Разработка тактического плана" id="140" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1081,330" order="7" text="Техническая реализация" id="126" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1072,396" order="8" text="Производство контента" id="124" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1067,462" order="9" text="Настройка интеграций " id="125" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1051,528" order="10" text="Настройка трафика" id="127" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1067,594" order="11" text="Обработка обращений" id="130" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1101,660" order="12" text="Техническое сопровождение" id="128" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1089,726" order="13" text="Сопровождение процессов" id="131" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1070,792" order="14" text="Клиентская поддержка" id="129" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="723,858" order="3" text="Решение конкретного этапа руками клиента" id="42" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="692,924" order="4" text="Решение конкретной задачи под ключ" id="43" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="726,990" order="5" text="Решение конкретной задачи руками клиента" id="44" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="663,1155" order="6" text="Источник необходимых ресурсов" id="45" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="996,1056" order="0" text="База специалистов" id="142" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="998,1122" order="1" text="Штат специалистов" id="143" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1002,1188" order="2" text="База работодателей" id="144" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1042,1254" order="3" text="База партнерских проектов" id="167" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="671,1452" order="7" text="Набор необходимых инструментов" id="46" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="954,1320" order="0" text="Хостинг" id="145" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="958,1386" order="1" text="Сервисы" id="146" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="962,1452" order="2" text="Шаблоны" id="147" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="982,1518" order="3" text="Инструменты" id="148" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="939,1584" order="4" text="Софт" id="149" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="634,2079" order="9" text="Способ получения навыков" id="48" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="917,1914" order="0" text="Мастер-классы" id="150" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="893,1980" order="1" text="Воркшопы" id="151" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="896,2046" order="2" text="Интенсивы" id="158" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="888,2112" order="3" text="Тренинги" id="152" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="937,2178" order="4" text="Программы курсов" id="159" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="902,2244" order="5" text="Стажировки" id="153" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="724,2508" order="10" text="Создание среды с необходимыми условиями" id="49" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="1081,2310" order="0" text="Сообщество" id="160" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1149,2376" order="1" text="Платформа для общения" id="161" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1074,2442" order="2" text="Коворкинг" id="162" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1080,2508" order="3" text="Нетворкинг" id="163" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1267,2574" order="4" text="Платформа для поиска партнеров/соискателей" id="164" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1152,2640" order="5" text="Платформа для обучения" id="165" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="1141,2706" order="6" text="Платформа для работы" id="166" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="628,1749" order="8" text="База необходимых знаний" id="47" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
<topic position="890,1650" order="0" text="Публикации" id="154" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="920,1716" order="1" text="Открытые лекции" id="155" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="962,1782" order="2" text="Образовательные каналы" id="156" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="923,1848" order="3" text="Платное обучение" id="157" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
<topic position="303,3036" order="10" text="Бизнес модель" id="2" fontStyle=";15;;;;">
<topic position="553,2772" order="0" text="Клиентские сегменты" id="71" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="575,2838" order="1" text="Ценностное предложение" id="72" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="565,2904" order="2" text="Способы коммуникации" id="73" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="573,2970" order="3" text="Способы предоставления" id="74" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="598,3036" order="4" text="Ключевые виды деятельности" id="75" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="540,3102" order="5" text="Ключевые ресурсы" id="76" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="547,3168" order="6" text="Ключевые партнеры" id="77" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="545,3234" order="7" text="Структура расходов" id="78" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="541,3300" order="8" text="Источники доходов" id="79" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="333,3630" order="12" text="Ключевые процессы" id="5" fontStyle=";15;;;;">
<topic position="560,3366" order="0" text="Консалтинг" id="57" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="676,3432" order="1" text="Разработка обучающих программ" id="50" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="551,3498" order="2" text="Обучение" id="53" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="625,3564" order="3" text="Создание инструментов" id="51" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="654,3630" order="4" text="Систематизация информации" id="52" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="595,3696" order="5" text="Создание условий" id="54" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="621,3762" order="6" text="Управление проектами" id="55" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="582,3828" order="7" text="Оказание услуг" id="170" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="616,3894" order="8" text="Поддержка процессов" id="56" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
<topic position="374,3960" order="14" text="Организационная структура" id="3" fontStyle=";15;;;;"/>
<topic position="258,4323" order="16" text="Этапы" id="60" fontStyle=";15;;;;">
<topic position="495,4026" order="0" text="Определение потребностей" id="61" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="425,4092" order="1" text="Анализ рынка" id="62" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="552,4158" order="2" text="Разработка ценностного предложения" id="63" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="425,4224" order="3" text="Создание MVP" id="64" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="439,4290" order="4" text="Тестовый запуск" id="68" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="388,4356" order="5" text="Анализ" id="65" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="518,4422" order="6" text="Разработка основного продукта" id="66" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="381,4488" order="7" text="Релиз" id="69" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="544,4554" order="8" text="Систематизация ключевых процесов" id="70" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
<topic position="446,4620" order="9" text="Масштабирование" id="67" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
</topic>
</topic>
</map>

View File

@ -7,7 +7,8 @@ module.exports = {
publicPath: '',
library: {
type: 'umd',
}, },
},
},
stats: {
errorDetails: true
},
@ -35,7 +36,14 @@ module.exports = {
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
}, {
test: /\.css$/i,
loader: 'style-loader'
},
{
test: /\.css$/,
loader: 'css-loader',
}
],
},
}
};

View File

@ -9,7 +9,6 @@ const playgroundConfig = {
mode: 'development',
entry: {
viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'),
embedded: path.resolve(__dirname, './test/playground/map-render/js/embedded'),
editor: path.resolve(__dirname, './test/playground/map-render/js/editor'),
},
output: {
@ -24,17 +23,6 @@ const playgroundConfig = {
port: 8081,
open: false,
},
module: {
rules: [
{
test: /\.css$/i,
use: [
'style-loader',
'css-loader?url=false',
],
},
],
},
plugins: [
new CleanWebpackPlugin({
dangerouslyAllowCleanPatternsOutsideProject: true,
@ -46,10 +34,7 @@ const playgroundConfig = {
{ from: 'test/playground/map-render/images', to: 'images' },
{ from: 'test/playground/map-render/js', to: 'js' },
{ from: 'test/playground/map-render/samples', to: 'samples' },
{ from: '../../libraries/bootstrap', to: 'bootstrap' },
{ from: 'test/playground/index.html', to: 'index.html' },
{ from: 'test/playground/map-render/html/container.json', to: 'html/container.json' },
{ from: 'test/playground/map-render/html/container.html', to: 'container.html' },
],
}),
new HtmlWebpackPlugin({
@ -57,11 +42,6 @@ const playgroundConfig = {
filename: 'viewmode.html',
template: 'test/playground/map-render/html/viewmode.html',
}),
new HtmlWebpackPlugin({
chunks: ['embedded'],
filename: 'embedded.html',
template: 'test/playground/map-render/html/embedded.html',
}),
new HtmlWebpackPlugin({
chunks: ['editor'],
filename: 'editor.html',

View File

@ -1,6 +1,6 @@
{
"name": "@wisemapping/mindplot",
"version": "5.0.3",
"version": "5.0.8",
"description": "WiseMapping - Mindplot Canvas Library",
"homepage": "http://www.wisemapping.org/",
"directories": {
@ -37,7 +37,8 @@
"@wisemapping/web2d": "^0.4.0",
"jest": "^27.4.5",
"jquery": "3.6.0",
"lodash": "^4.17.21"
"lodash": "^4.17.21",
"xml-formatter": "^2.6.1"
},
"devDependencies": {
"@babel/core": "^7.14.6",
@ -69,6 +70,7 @@
"start-server-and-test": "^1.14.0",
"ts-jest": "^27.1.2",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"webpack": "^5.44.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.7.2",

View File

@ -0,0 +1,4 @@
declare module "*.svg" {
const content: any;
export default content;
}

View File

@ -30,9 +30,16 @@ import Topic from './Topic';
abstract class ActionDispatcher extends Events {
private static _instance: ActionDispatcher;
private _commandContext: CommandContext;
constructor(commandContext: CommandContext) {
$assert(commandContext, 'commandContext can not be null');
super();
this._commandContext = commandContext;
}
getCommandContext(): CommandContext {
return this._commandContext;
}
abstract addRelationship(model: RelationshipModel, mindmap: Mindmap): void;

View File

@ -40,7 +40,6 @@ class CommandContext {
this._designer = value;
}
/** */
findTopics(topicIds: number[]): Topic[] {
$assert($defined(topicIds), 'topicsIds can not be null');
const topicsIds = Array.isArray(topicIds) ? topicIds : [topicIds];

View File

@ -21,10 +21,23 @@ import {
Point, CurvedLine, PolyLine, Line,
} from '@wisemapping/web2d';
import { TopicShape } from './model/INodeModel';
import RelationshipModel from './model/RelationshipModel';
import Topic from './Topic';
import TopicConfig from './TopicConfig';
import Workspace from './Workspace';
class ConnectionLine {
constructor(sourceNode, targetNode, lineType) {
protected _targetTopic: Topic;
protected _sourceTopic: Topic;
protected _lineType: number;
protected _line2d: Line;
protected _model: RelationshipModel;
constructor(sourceNode: Topic, targetNode: Topic, lineType?: number) {
$assert(targetNode, 'parentNode node can not be null');
$assert(sourceNode, 'childNode node can not be null');
$assert(sourceNode !== targetNode, 'Circular connection');
@ -32,7 +45,7 @@ class ConnectionLine {
this._targetTopic = targetNode;
this._sourceTopic = sourceNode;
let line;
let line: Line;
const ctrlPoints = this._getCtrlPoints(sourceNode, targetNode);
if (targetNode.getType() === 'CentralTopic') {
line = this._createLine(lineType, ConnectionLine.CURVED);
@ -51,15 +64,15 @@ class ConnectionLine {
this._line2d = line;
}
_getCtrlPoints(sourceNode, targetNode) {
private _getCtrlPoints(sourceNode: Topic, targetNode: Topic) {
const srcPos = sourceNode.workoutOutgoingConnectionPoint(targetNode.getPosition());
const destPos = targetNode.workoutIncomingConnectionPoint(sourceNode.getPosition());
const deltaX = (srcPos.x - destPos.x) / 3;
return [new Point(deltaX, 0), new Point(-deltaX, 0)];
}
_createLine(lineTypeParam, defaultStyle) {
const lineType = $defined(lineTypeParam) ? parseInt(lineTypeParam, 10) : defaultStyle;
protected _createLine(lineTypeParam: number, defaultStyle: number): Line {
const lineType = $defined(lineTypeParam) ? lineTypeParam : defaultStyle;
this._lineType = lineType;
let line = null;
switch (lineType) {
@ -80,7 +93,7 @@ class ConnectionLine {
return line;
}
setVisibility(value) {
setVisibility(value: boolean): void {
this._line2d.setVisibility(value);
}
@ -88,11 +101,11 @@ class ConnectionLine {
return this._line2d.isVisible();
}
setOpacity(opacity) {
setOpacity(opacity: number): void {
this._line2d.setOpacity(opacity);
}
redraw() {
redraw(): void {
const line2d = this._line2d;
const sourceTopic = this._sourceTopic;
const sourcePosition = sourceTopic.getPosition();
@ -116,7 +129,7 @@ class ConnectionLine {
this._positionateConnector(targetTopic);
}
_positionateConnector(targetTopic) {
protected _positionateConnector(targetTopic: Topic): void {
const targetPosition = targetTopic.getPosition();
const offset = TopicConfig.CONNECTOR_WIDTH / 2;
const targetTopicSize = targetTopic.getSize();
@ -141,65 +154,68 @@ class ConnectionLine {
}
}
setStroke(color, style, opacity) {
setStroke(color: string, style: string, opacity: number) {
this._line2d.setStroke(null, null, color, opacity);
}
addToWorkspace(workspace) {
addToWorkspace(workspace: Workspace) {
workspace.append(this._line2d);
this._line2d.moveToBack();
}
removeFromWorkspace(workspace) {
removeFromWorkspace(workspace: Workspace) {
workspace.removeChild(this._line2d);
}
getTargetTopic() {
getTargetTopic(): Topic {
return this._targetTopic;
}
getSourceTopic() {
getSourceTopic(): Topic {
return this._sourceTopic;
}
getLineType() {
getLineType(): number {
return this._lineType;
}
getLine() {
getLine(): Line {
return this._line2d;
}
getModel() {
getModel(): RelationshipModel {
return this._model;
}
setModel(model) {
setModel(model: RelationshipModel): void {
this._model = model;
}
getType() {
getType(): string {
return 'ConnectionLine';
}
getId() {
getId(): number {
return this._model.getId();
}
moveToBack() {
moveToBack(): void {
this._line2d.moveToBack();
}
moveToFront() {
this._line2d.moveToFront();
}
static SIMPLE = 0;
static POLYLINE = 1;
static CURVED = 2;
static SIMPLE_CURVED = 3;
static getStrokeColor = () => '#495879';
}
ConnectionLine.getStrokeColor = () => '#495879';
ConnectionLine.SIMPLE = 0;
ConnectionLine.POLYLINE = 1;
ConnectionLine.CURVED = 2;
ConnectionLine.SIMPLE_CURVED = 3;
export default ConnectionLine;

View File

@ -69,7 +69,7 @@ class Designer extends Events {
private _workspace: Workspace;
private _eventBussDispatcher: EventBusDispatcher;
_eventBussDispatcher: EventBusDispatcher;
private _dragManager: DragManager;
@ -87,6 +87,7 @@ class Designer extends Events {
$assert(divElement, 'divElement must be defined');
// Set up i18n location ...
console.log(`Editor location: ${options.locale}`);
Messages.init(options.locale);
this._options = options;
@ -110,7 +111,7 @@ class Designer extends Events {
// Init Screen manager..
const screenManager = new ScreenManager(divElement);
this._workspace = new Workspace(screenManager, this._model.getZoom(), !!options.readOnly);
this._workspace = new Workspace(screenManager, this._model.getZoom(), options.mode === 'viewonly');
// Init layout manager ...
this._eventBussDispatcher = new EventBusDispatcher();
@ -208,15 +209,12 @@ class Designer extends Events {
});
dragManager.addEvent('dragging', (event: MouseEvent, dragTopic: DragTopic) => {
dragTopic.updateFreeLayout(event);
if (!dragTopic.isFreeLayoutOn()) {
// The node is being drag. Is the connection still valid ?
dragConnector.checkConnection(dragTopic);
if (!dragTopic.isVisible() && dragTopic.isConnected()) {
dragTopic.setVisibility(true);
}
}
});
dragManager.addEvent('enddragging', (event: MouseEvent, dragTopic: DragTopic) => {
@ -335,7 +333,7 @@ class Designer extends Events {
zoomOut(factor = 1.2) {
const model = this.getModel();
const scale = model.getZoom() * factor;
if (scale <= 1.9) {
if (scale <= 7.0) {
model.setZoom(scale);
this._workspace.setZoom(scale);
} else {
@ -626,7 +624,7 @@ class Designer extends Events {
}
isReadOnly(): boolean {
return Boolean(this._options?.readOnly);
return Boolean(this._options?.mode === 'viewonly');
}
nodeModelToTopic(nodeModel: NodeModel): Topic {

View File

@ -20,8 +20,6 @@ import $ from 'jquery';
import PersistenceManager from './PersistenceManager';
import Designer from './Designer';
import Menu from './widget/Menu';
import { $notifyModal } from './widget/ModalDialogNotifier';
import { $msg } from './Messages';
import { DesignerOptions } from './DesignerOptionsBuilder';
let designer: Designer;
@ -32,40 +30,6 @@ export function buildDesigner(options: DesignerOptions): Designer {
// Register load events ...
designer = new Designer(options, divContainer);
designer.addEvent('loadSuccess', () => {
globalThis.mindmapLoadReady = true;
console.log('Map loadded successfully');
});
const onerrorFn = (msg: string, url: string, lineNo: number, columnNo: number, error: Error) => {
const message = [
`Message: ${msg}`,
`URL: ${url}`,
`Line: ${lineNo}`,
`Column: ${columnNo}`,
].join(' - ');
console.log(message);
// Send error to server ...
$.ajax({
method: 'post',
url: '/c/restful/logger/editor',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
data: JSON.stringify({
jsErrorMsg: message,
jsStack: JSON.stringify(error),
userAgent: navigator.userAgent,
mapId: options.mapId,
}),
});
// Open error dialog only in case of mindmap loading errors. The rest of the error are reported but not display the dialog.
// Remove this in the near future.
if (!globalThis.mindmapLoadReady) {
$notifyModal($msg('UNEXPECTED_ERROR_LOADING'));
}
};
window.onerror = onerrorFn;
// Configure default persistence manager ...
const persistence = options.persistenceManager;
@ -73,7 +37,7 @@ export function buildDesigner(options: DesignerOptions): Designer {
PersistenceManager.init(persistence);
// Register toolbar event ...
if ($('#toolbar').length) {
if (options.mode === 'edition' || options.mode === 'showcase') {
const menu = new Menu(designer, 'toolbar');
// If a node has focus, focus can be move to another node using the keys.

View File

@ -21,16 +21,28 @@ import Keyboard from './Keyboard';
import { Designer } from '..';
import Topic from './Topic';
export type EventCallback = (event?: Event) => void;
class DesignerKeyboard extends Keyboard {
// eslint-disable-next-line no-use-before-define
static _instance: DesignerKeyboard;
static _disabled: boolean;
constructor(designer: Designer) {
super();
$assert(designer, 'designer can not be null');
this._registerEvents(designer);
}
addShortcut(shortcuts: string[] | string, callback: EventCallback): void {
super.addShortcut(shortcuts, (e: Event) => {
if (DesignerKeyboard.isDisabled()) {
return;
}
callback(e);
});
}
private _registerEvents(designer: Designer) {
// Try with the keyboard ..
const model = designer.getModel();
@ -246,6 +258,10 @@ class DesignerKeyboard extends Keyboard {
$(document).on('keypress', (event) => {
let keyCode: number;
if (DesignerKeyboard.isDisabled()) {
return;
}
// Firefox doesn't skip special keys for keypress event...
if (event.key && excludes.includes(event.key.toLowerCase())) {
return;
@ -264,11 +280,10 @@ class DesignerKeyboard extends Keyboard {
const nodes = designer.getModel().filterSelectedTopics();
if (nodes.length > 0) {
// If a modifier is press, the key selected must be ignored.
const pressKey = String.fromCharCode(keyCode);
if (event.ctrlKey || event.altKey || event.metaKey) {
return;
}
nodes[0].showTextEditor(pressKey);
nodes[0].showTextEditor('');
event.stopPropagation();
}
}
@ -347,7 +362,7 @@ class DesignerKeyboard extends Keyboard {
}
}
private _goToChild(designer, node) {
private _goToChild(designer: Designer, node: Topic) {
const children = node.getChildren();
if (children.length > 0) {
let target = children[0];
@ -373,6 +388,19 @@ class DesignerKeyboard extends Keyboard {
static register = function register(designer: Designer) {
this._instance = new DesignerKeyboard(designer);
this._disabled = false;
};
static pause = function pause() {
this._disabled = true;
};
static resume = function resume() {
this._disabled = false;
};
static isDisabled = function isDisabled() {
return this._disabled;
};
static specialKeys = {

View File

@ -16,13 +16,14 @@
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import EditorRenderMode from './EditorRenderMode';
import PersistenceManager from './PersistenceManager';
import SizeType from './SizeType';
export type DesignerOptions = {
zoom: number,
containerSize?: SizeType,
readOnly?: boolean,
mode: EditorRenderMode,
mapId?: string,
container: string,
persistenceManager?: PersistenceManager,
@ -45,7 +46,7 @@ class OptionsBuilder {
}
const defaultOptions: DesignerOptions = {
readOnly: false,
mode: 'edition',
zoom: 0.85,
saveOnLoad: true,
containerSize,

View File

@ -16,18 +16,27 @@
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import { Point } from '@wisemapping/web2d';
import DesignerModel from './DesignerModel';
import DragTopic from './DragTopic';
import SizeType from './SizeType';
import Topic from './Topic';
import Workspace from './Workspace';
class DragConnector {
constructor(designerModel, workspace) {
private _designerModel: DesignerModel;
private _workspace: Workspace;
constructor(designerModel: DesignerModel, workspace: Workspace) {
$assert(designerModel, 'designerModel can not be null');
$assert(workspace, 'workspace can not be null');
// this._layoutManager = layoutManager;
this._designerModel = designerModel;
this._workspace = workspace;
}
checkConnection(dragTopic) {
checkConnection(dragTopic: DragTopic): void {
// Must be disconnected from their current connection ?.
const candidates = this._searchConnectionCandidates(dragTopic);
const currentConnection = dragTopic.getConnectedToTopic();
@ -42,12 +51,13 @@ class DragConnector {
}
}
_searchConnectionCandidates(dragTopic) {
private _searchConnectionCandidates(dragTopic: DragTopic): Topic[] {
let topics = this._designerModel.getTopics();
const draggedNode = dragTopic.getDraggedTopic();
// Drag node connects to the border ...
const dragTopicWidth = dragTopic.getSize ? dragTopic.getSize().width : 0; // Hack...
// const dragTopicWidth = dragTopic.getSize ? dragTopic.getSize().width : 0; // Hack...
const dragTopicWidth = 0;
const xMouseGap = dragTopic.getPosition().x > 0 ? 0 : dragTopicWidth;
const sPos = { x: dragTopic.getPosition().x - xMouseGap, y: dragTopic.getPosition().y };
@ -56,7 +66,7 @@ class DragConnector {
// - Exclude dragTopic pivot
// - Nodes that are collapsed
// - It's not part of the branch dragged itself
topics = topics.filter((topic) => {
topics = topics.filter((topic: Topic) => {
let result = draggedNode !== topic;
result = result && topic !== draggedNode;
result = result && !topic.areChildrenShrunken() && !topic.isCollapsed();
@ -67,7 +77,7 @@ class DragConnector {
// Filter all the nodes that are outside the vertical boundary:
// * The node is to out of the x scope
// * The x distance greater the vertical tolerated distance
topics = topics.filter((topic) => {
topics = topics.filter((topic: Topic) => {
const tpos = topic.getPosition();
// Center topic has different alignment than the rest of the nodes.
// That's why i need to divide it by two...
@ -95,17 +105,17 @@ class DragConnector {
return topics;
}
_proximityWeight(isAligned, target, sPos, currentConnection) {
private _proximityWeight(isAligned: boolean, target: Topic, sPos: Point, currentConnection: Topic): number {
const tPos = target.getPosition();
return (isAligned ? 0 : 200) + Math.abs(tPos.x - sPos.x)
+ Math.abs(tPos.y - sPos.y) + (currentConnection === target ? 0 : 100);
}
_isVerticallyAligned(targetSize, targetPosition, sourcePosition) {
private _isVerticallyAligned(targetSize: SizeType, targetPosition: Point, sourcePosition: Point): boolean {
return Math.abs(sourcePosition.y - targetPosition.y) < targetSize.height / 2;
}
static MAX_VERTICAL_CONNECTION_TOLERANCE = 80;
}
DragConnector.MAX_VERTICAL_CONNECTION_TOLERANCE = 80;
export default DragConnector;

View File

@ -18,6 +18,7 @@
import { $assert, $defined } from '@wisemapping/core-js';
import DragTopic from './DragTopic';
import EventBusDispatcher from './layout/EventBusDispatcher';
import Topic from './Topic';
import Workspace from './Workspace';
class DragManager {
@ -44,7 +45,7 @@ class DragManager {
DragTopic.init(this._workspace);
}
add(node) {
add(topic: Topic) {
// Add behaviour ...
const workspace = this._workspace;
const screen = workspace.getScreenManager();
@ -57,7 +58,7 @@ class DragManager {
// Set initial position.
const layoutManager = me._eventDispatcher.getLayoutManager();
const dragNode = node.createDragNode(layoutManager);
const dragNode = topic.createDragNode(layoutManager);
// Register mouse move listener ...
const mouseMoveListener = dragManager._buildMouseMoveListener(
@ -67,7 +68,7 @@ class DragManager {
// Register mouse up listeners ...
const mouseUpListener = dragManager._buildMouseUpListener(
workspace, node, dragNode, dragManager,
workspace, topic, dragNode, dragManager,
);
screen.addEvent('mouseup', mouseUpListener);
@ -75,7 +76,7 @@ class DragManager {
window.document.body.style.cursor = 'move';
}
};
node.addEvent('mousedown', mouseDownListener);
topic.addEvent('mousedown', mouseDownListener);
}
remove() {
@ -114,10 +115,10 @@ class DragManager {
return result;
}
protected _buildMouseUpListener(workspace: Workspace, node, dragNode, dragManager: DragManager) {
protected _buildMouseUpListener(workspace: Workspace, topic: Topic, dragNode, dragManager: DragManager) {
const screen = workspace.getScreenManager();
const me = this;
const result = (event) => {
const result = (event: Event) => {
$assert(dragNode.isDragTopic, 'dragNode must be an DragTopic');
// Remove all the events.
@ -154,7 +155,7 @@ class DragManager {
* - dragging
* - enddragging
*/
addEvent(type, listener) {
addEvent(type: string, listener) {
this._listeners[type] = listener;
}
}

View File

@ -19,9 +19,28 @@ import { $assert, $defined } from '@wisemapping/core-js';
import { Point, CurvedLine, Rect } from '@wisemapping/web2d';
import DragTopicConfig from './DragTopicConfig';
import SizeType from './SizeType';
import Topic from './Topic';
import Shape from './util/Shape';
import Workspace from './Workspace';
class DragPivot {
private _position: Point;
private _isVisible: boolean;
private _targetTopic: Topic;
private _connectRect: Rect;
private _dragPivot: Rect;
private _curvedLine: CurvedLine;
private _straightLine: CurvedLine;
private _size: SizeType;
constructor() {
this._position = new Point();
this._size = DragTopicConfig.PIVOT_SIZE;
@ -34,15 +53,15 @@ class DragPivot {
this._isVisible = false;
}
isVisible() {
isVisible(): boolean {
return this._isVisible;
}
getTargetTopic() {
getTargetTopic(): Topic {
return this._targetTopic;
}
_buildStraightLine() {
private _buildStraightLine(): CurvedLine {
const line = new CurvedLine();
line.setStyle(CurvedLine.SIMPLE_LINE);
line.setStroke(1, 'solid', '#CC0033');
@ -51,7 +70,7 @@ class DragPivot {
return line;
}
_buildCurvedLine() {
private _buildCurvedLine(): CurvedLine {
const line = new CurvedLine();
line.setStyle(CurvedLine.SIMPLE_LINE);
line.setStroke(1, 'solid', '#CC0033');
@ -60,7 +79,7 @@ class DragPivot {
return line;
}
_redrawLine() {
private _redrawLine(): void {
// Update line position.
$assert(this.getTargetTopic(), 'Illegal invocation. Target node can not be null');
@ -81,8 +100,8 @@ class DragPivot {
line.setFrom(pivotPoint.x, pivotPoint.y);
// Update rect position
const cx = position.x - parseInt(size.width, 10) / 2;
const cy = position.y - parseInt(size.height, 10) / 2;
const cx = position.x - size.width / 2;
const cy = position.y - size.height / 2;
pivotRect.setPosition(cx, cy);
// Make line visible only when the position has been already changed.
@ -91,16 +110,16 @@ class DragPivot {
line.setTo(targetPoint.x, targetPoint.y);
}
setPosition(point) {
setPosition(point: Point): void {
this._position = point;
this._redrawLine();
}
getPosition() {
getPosition(): Point {
return this._position;
}
_buildRect() {
private _buildRect(): Rect {
const size = this._size;
const rectAttributes = {
fillColor: '#CC0033',
@ -114,16 +133,16 @@ class DragPivot {
return rect;
}
_getPivotRect() {
private _getPivotRect(): Rect {
return this._dragPivot;
}
getSize() {
getSize(): SizeType {
const elem2d = this._getPivotRect();
return elem2d.getSize();
}
setVisibility(value) {
setVisibility(value: boolean) {
if (this.isVisible() !== value) {
const pivotRect = this._getPivotRect();
pivotRect.setVisibility(value);
@ -140,7 +159,7 @@ class DragPivot {
}
// If the node is connected, validate that there is a line connecting both...
_getConnectionLine() {
_getConnectionLine(): CurvedLine {
let result = null;
const parentTopic = this._targetTopic;
if (parentTopic) {
@ -153,7 +172,7 @@ class DragPivot {
return result;
}
addToWorkspace(workspace) {
addToWorkspace(workspace: Workspace) {
const pivotRect = this._getPivotRect();
workspace.append(pivotRect);
@ -179,7 +198,7 @@ class DragPivot {
connectRect.moveToBack();
}
removeFromWorkspace(workspace) {
removeFromWorkspace(workspace: Workspace) {
const shape = this._getPivotRect();
workspace.removeChild(shape);
@ -195,9 +214,7 @@ class DragPivot {
}
}
connectTo(targetTopic, position) {
$assert(!this._outgoingLine, 'Could not connect an already connected node');
$assert(targetTopic !== this, 'Circular connection are not allowed');
connectTo(targetTopic: Topic, position: Point) {
$assert(position, 'position can not be null');
$assert(targetTopic, 'parent can not be null');
@ -227,7 +244,7 @@ class DragPivot {
this._redrawLine();
}
disconnect(workspace) {
disconnect(workspace: Workspace): void {
$assert(workspace, 'workspace can not be null.');
$assert(this._targetTopic, 'There are not connected topic.');

View File

@ -16,13 +16,24 @@
* limitations under the License.
*/
import { $assert, $defined } from '@wisemapping/core-js';
import { Point } from '@wisemapping/web2d';
import { Point, ElementClass } from '@wisemapping/web2d';
import ActionDispatcher from './ActionDispatcher';
import DragPivot from './DragPivot';
import LayoutManager from './layout/LayoutManager';
import NodeGraph from './NodeGraph';
import Topic from './Topic';
import Workspace from './Workspace';
class DragTopic {
constructor(dragShape, draggedNode, layoutManger) {
private _elem2d: ElementClass;
private _order: number | null;
private _draggedNode: NodeGraph;
private _layoutManager: LayoutManager;
private _position: any;
private _isInWorkspace: boolean;
static _dragPivot: any;
constructor(dragShape: ElementClass, draggedNode: NodeGraph, layoutManger: LayoutManager) {
$assert(dragShape, 'Rect can not be null.');
$assert(draggedNode, 'draggedNode can not be null.');
$assert(layoutManger, 'layoutManger can not be null.');
@ -33,26 +44,15 @@ class DragTopic {
this._layoutManager = layoutManger;
this._position = new Point();
this._isInWorkspace = false;
this._isFreeLayoutEnabled = false;
}
setOrder(order) {
setOrder(order: number) {
this._order = order;
}
setPosition(x, y) {
setPosition(x: number, y: number) {
// Update drag shadow position ....
let position = { x, y };
if (this.isFreeLayoutOn() && this.isConnected()) {
const { _layoutManager } = this;
const par = this.getConnectedToTopic();
position = _layoutManager.predict(
par.getId(),
this._draggedNode.getId(),
position,
true,
).position;
}
this._position.setValue(position.x, position.y);
// Elements are positioned in the center.
@ -80,45 +80,35 @@ class DragTopic {
}
}
updateFreeLayout(event) {
const isMac = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0;
const isFreeEnabled = (event.metaKey && isMac) || (event.ctrlKey && !isMac);
if (this.isFreeLayoutOn() !== isFreeEnabled) {
const dragPivot = this._getDragPivot();
dragPivot.setVisibility(!isFreeEnabled);
this._isFreeLayoutEnabled = isFreeEnabled;
}
}
setVisibility(value) {
setVisibility(value: boolean) {
const dragPivot = this._getDragPivot();
dragPivot.setVisibility(value);
}
isVisible() {
isVisible(): boolean {
const dragPivot = this._getDragPivot();
return dragPivot.isVisible();
}
getInnerShape() {
getInnerShape(): ElementClass {
return this._elem2d;
}
disconnect(workspace) {
disconnect(workspace: Workspace) {
// Clear connection line ...
const dragPivot = this._getDragPivot();
dragPivot.disconnect(workspace);
}
connectTo(parent) {
connectTo(parent: Topic) {
$assert(parent, 'Parent connection node can not be null.');
// Where it should be connected ?
// @todo: This is a hack for the access of the editor.
// It's required to review why this is needed forcing the declaration of a global variable.
const predict = designer._eventBussDispatcher._layoutManager.predict(
const predict = global.designer._eventBussDispatcher._layoutManager.predict(
parent.getId(),
this._draggedNode.getId(),
this.getPosition(),
@ -133,11 +123,11 @@ class DragTopic {
this.setOrder(predict.order);
}
getDraggedTopic() {
return this._draggedNode;
getDraggedTopic(): Topic {
return this._draggedNode as Topic;
}
removeFromWorkspace(workspace) {
removeFromWorkspace(workspace: Workspace) {
if (this._isInWorkspace) {
// Remove drag shadow.
workspace.removeChild(this._elem2d);
@ -151,11 +141,11 @@ class DragTopic {
}
}
isInWorkspace() {
isInWorkspace(): boolean {
return this._isInWorkspace;
}
addToWorkspace(workspace) {
addToWorkspace(workspace: Workspace) {
if (!this._isInWorkspace) {
workspace.append(this._elem2d);
const dragPivot = this._getDragPivot();
@ -164,19 +154,19 @@ class DragTopic {
}
}
_getDragPivot() {
_getDragPivot(): DragPivot {
return DragTopic.__getDragPivot();
}
getPosition() {
getPosition(): Point {
return this._position;
}
isDragTopic() {
isDragTopic(): boolean {
return true;
}
applyChanges(workspace) {
applyChanges(workspace: Workspace) {
$assert(workspace, 'workspace can not be null');
const actionDispatcher = ActionDispatcher.getInstance();
@ -201,27 +191,26 @@ class DragTopic {
}
}
getConnectedToTopic() {
getConnectedToTopic(): Topic {
const dragPivot = this._getDragPivot();
return dragPivot.getTargetTopic();
}
isConnected() {
isConnected(): boolean {
return this.getConnectedToTopic() != null;
}
isFreeLayoutOn() {
isFreeLayoutOn(): false {
return false;
}
}
DragTopic.init = function init(workspace) {
static init(workspace: Workspace) {
$assert(workspace, 'workspace can not be null');
const pivot = DragTopic.__getDragPivot();
workspace.append(pivot);
};
DragTopic.__getDragPivot = function __getDragPivot() {
static __getDragPivot() {
let result = DragTopic._dragPivot;
if (!$defined(result)) {
result = new DragPivot();
@ -229,5 +218,6 @@ DragTopic.__getDragPivot = function __getDragPivot() {
}
return result;
};
}
export default DragTopic;

View File

@ -0,0 +1,2 @@
type EditorRenderMode = 'viewonly' | 'edition' | 'showcase';
export default EditorRenderMode;

View File

@ -17,36 +17,43 @@
*/
import { $assert } from '@wisemapping/core-js';
import { Image } from '@wisemapping/web2d';
import IconGroup from './IconGroup';
import { Point } from '@wisemapping/web2d';
import SizeType from './SizeType';
import FeatureModel from './model/FeatureModel';
class Icon {
constructor(url) {
abstract class Icon {
protected _image: Image;
protected _group: IconGroup;
constructor(url: string) {
$assert(url, 'topic can not be null');
this._image = new Image();
this._image.setHref(url);
this._image.setSize(Icon.SIZE, Icon.SIZE);
}
getImage() {
getImage(): Image {
return this._image;
}
setGroup(group) {
setGroup(group: IconGroup) {
this._group = group;
}
getGroup() {
getGroup(): IconGroup {
return this._group;
}
getSize() {
getSize(): SizeType {
return this._image.getSize();
}
getPosition() {
getPosition(): Point {
return this._image.getPosition();
}
addEvent(type, fnc) {
addEvent(type: string, fnc): void {
this._image.addEvent(type, fnc);
}
@ -54,8 +61,10 @@ class Icon {
remove() {
throw new Error('Unsupported operation');
}
abstract getModel(): FeatureModel;
static SIZE = 90;
}
Icon.SIZE = 90;
export default Icon;

View File

@ -22,21 +22,31 @@ import {
} from '@wisemapping/core-js';
import {
Group,
ElementClass,
} from '@wisemapping/web2d';
import IconGroupRemoveTip from './IconGroupRemoveTip';
import { Point } from '@wisemapping/web2d';
import Icon from './Icon';
import SizeType from './SizeType';
import IconModel from './model/IconModel';
import FeatureModel from './model/FeatureModel';
const ORDER_BY_TYPE = new Map();
const ORDER_BY_TYPE = new Map<string, number>();
ORDER_BY_TYPE.set('icon', 0);
ORDER_BY_TYPE.set('note', 1);
ORDER_BY_TYPE.set('link', 2);
class IconGroup {
constructor(topicId, iconSize) {
private _icons: Icon[];
private _group: any;
private _removeTip: IconGroupRemoveTip;
private _iconSize: SizeType;
private _topicId: number;
constructor(topicId: number, iconSize: number) {
$assert($defined(topicId), 'topicId can not be null');
$assert($defined(iconSize), 'iconSize can not be null');
this._topicId = topicId;
this._icons = [];
this._group = new Group({
width: 0,
@ -46,34 +56,31 @@ class IconGroup {
coordSizeWidth: 0,
coordSizeHeight: 100,
});
this._removeTip = new IconGroupRemoveTip(this._group, topicId);
this._removeTip = new IconGroupRemoveTip(this._group);
this.seIconSize(iconSize, iconSize);
this._registerListeners();
}
/** */
setPosition(x, y) {
setPosition(x: number, y: number): void {
this._group.setPosition(x, y);
}
/** */
getPosition() {
getPosition(): Point {
return this._group.getPosition();
}
/** */
getNativeElement() {
getNativeElement(): ElementClass {
return this._group;
}
/** */
getSize() {
getSize(): SizeType {
return this._group.getSize();
}
/** */
seIconSize(width, height) {
seIconSize(width: number, height: number) {
this._iconSize = {
width,
height,
@ -81,12 +88,7 @@ class IconGroup {
this._resize(this._icons.length);
}
/**
* @param icon the icon to be added to the icon group
* @param {Boolean} remove
* @throws will throw an error if icon is not defined
*/
addIcon(icon, remove) {
addIcon(icon: Icon, remove: boolean) {
$defined(icon, 'icon is not defined');
// Order could have change, need to re-add all.
@ -113,7 +115,7 @@ class IconGroup {
}
}
_findIconFromModel(iconModel) {
private _findIconFromModel(iconModel: FeatureModel) {
let result = null;
this._icons.forEach((icon) => {
@ -132,14 +134,14 @@ class IconGroup {
}
/** */
removeIconByModel(featureModel) {
removeIconByModel(featureModel: FeatureModel) {
$assert(featureModel, 'featureModel can not be null');
const icon = this._findIconFromModel(featureModel);
this._removeIcon(icon);
}
_removeIcon(icon) {
private _removeIcon(icon: Icon) {
$assert(icon, 'icon can not be null');
this._removeTip.close(0);
@ -156,11 +158,11 @@ class IconGroup {
}
/** */
moveToFront() {
moveToFront(): void {
this._group.moveToFront();
}
_registerListeners() {
private _registerListeners() {
this._group.addEvent('click', (event) => {
// Avoid node creation ...
event.stopPropagation();
@ -171,21 +173,23 @@ class IconGroup {
});
}
_resize(iconsLength) {
private _resize(iconsLength: number) {
this._group.setSize(iconsLength * this._iconSize.width, this._iconSize.height);
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
this._group.setCoordSize(iconsLength * iconSize, iconSize);
}
_positionIcon(icon, order) {
private _positionIcon(icon: Icon, order: number) {
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
icon.getImage().setPosition(
iconSize * order + IconGroup.ICON_PADDING,
IconGroup.ICON_PADDING,
);
}
static ICON_PADDING = 5;
}
IconGroup.ICON_PADDING = 5;
export default IconGroup;

View File

@ -93,7 +93,7 @@ class ImageIcon extends Icon {
static _getFamilyIcons(iconId) {
$assert(iconId != null, 'id must not be null');
$assert(iconId.indexOf('_') !== -1, "Invalid icon id (it must contain '_')");
$assert(iconId.indexOf('_') !== -1, `Invalid icon id (it must contain '_'). Id: ${iconId}`);
let result = null;
for (let i = 0; i < ImageIcon.prototype.ICON_FAMILIES.length; i++) {

View File

@ -18,7 +18,7 @@
import $ from 'jquery';
class Keyboard {
addShortcut(shortcuts, callback) {
addShortcut(shortcuts: string[] | string, callback) {
const shortcutsArray = Array.isArray(shortcuts) ? shortcuts : [shortcuts];
shortcutsArray.forEach((shortcut) => {
$(document).bind('keydown', shortcut, callback);

View File

@ -20,9 +20,17 @@ import $ from 'jquery';
import Icon from './Icon';
import LinkIconTooltip from './widget/LinkIconTooltip';
import LinksImage from '../../assets/icons/links.svg';
import LinkModel from './model/LinkModel';
import Topic from './Topic';
import FeatureModel from './model/FeatureModel';
class LinkIcon extends Icon {
constructor(topic, linkModel, readOnly) {
private _linksModel: FeatureModel;
private _topic: Topic;
private _readOnly: boolean;
private _tip: LinkIconTooltip;
constructor(topic: Topic, linkModel: LinkModel, readOnly: boolean) {
$assert(topic, 'topic can not be null');
$assert(linkModel, 'linkModel can not be null');
@ -34,7 +42,7 @@ class LinkIcon extends Icon {
this._registerEvents();
}
_registerEvents() {
private _registerEvents() {
this._image.setCursor('pointer');
this._tip = new LinkIconTooltip(this);
@ -62,10 +70,11 @@ class LinkIcon extends Icon {
});
}
getModel() {
getModel(): FeatureModel {
return this._linksModel;
}
static IMAGE_URL = LinksImage;
}
LinkIcon.IMAGE_URL = LinksImage;
export default LinkIcon;

View File

@ -16,7 +16,6 @@
* limitations under the License.
*/
import $ from 'jquery';
import { Mindmap } from '..';
import PersistenceManager from './PersistenceManager';
class LocalStorageManager extends PersistenceManager {
@ -30,7 +29,8 @@ class LocalStorageManager extends PersistenceManager {
this.forceLoad = forceLoad;
}
saveMapXml(mapId: string, mapXml: string) {
saveMapXml(mapId: string, mapDoc: Document): void {
const mapXml = new XMLSerializer().serializeToString(mapDoc);
localStorage.setItem(`${mapId}-xml`, mapXml);
}
@ -43,7 +43,7 @@ class LocalStorageManager extends PersistenceManager {
if (xml == null || this.forceLoad) {
$.ajax({
url: this.documentUrl.replace('{id}', mapId),
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml' },
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml', 'X-CSRF-Token': this.getCSRFToken() },
type: 'get',
dataType: 'text',
async: false,
@ -63,7 +63,7 @@ class LocalStorageManager extends PersistenceManager {
return $.parseXML(xml);
}
unlockMap(mindmap: Mindmap) {
unlockMap(): void {
// Ignore, no implementation required ...
}
}

View File

@ -28,12 +28,6 @@ import SizeType from './SizeType';
class MainTopic extends Topic {
private INNER_RECT_ATTRIBUTES: { stroke: string; };
/**
* @extends mindplot.Topic
* @constructs
* @param model
* @param options
*/
constructor(model: NodeModel, options) {
super(model, options);
this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' };
@ -70,6 +64,11 @@ class MainTopic extends Topic {
const text = this.getText();
textShape.setText(text);
textShape.setOpacity(0.5);
// Copy text position of the topic element ...
const textPosition = this.getTextShape().getPosition();
textShape.setPosition(textPosition.x, textPosition.y);
group.append(textShape);
}
return group;
@ -88,7 +87,6 @@ class MainTopic extends Topic {
}
}
/** */
disconnect(workspace: Workspace) {
super.disconnect(workspace);
const model = this.getModel();

View File

@ -19,21 +19,23 @@ import { $defined } from '@wisemapping/core-js';
import Bundle from './lang/Bundle';
class Messages {
static init(locale) {
public static __bundle;
static init(locale: string) {
console.log(`Init designer message: ${locale}`);
let userLocale = $defined(locale) ? locale : 'en';
let bundle = Bundle[locale];
let bundle = Bundle[userLocale];
if (bundle == null && locale.indexOf('_') !== -1) {
// Try to locate without the specialization ...
userLocale = locale.substring(0, locale.indexOf('_'));
bundle = Bundle[locale];
bundle = Bundle[userLocale];
}
global.locale = userLocale;
Messages.__bundle = bundle || {};
this.__bundle = bundle;
}
}
const $msg = function $msg(key) {
const $msg = function $msg(key: string) {
if (!Messages.__bundle) {
Messages.init('en');
}

View File

@ -0,0 +1,48 @@
/*
* Copyright [2022] [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.
*/
import $ from 'jquery';
import { $assert } from '@wisemapping/core-js';
import PersistenceManager from './PersistenceManager';
class MockPersistenceManager extends PersistenceManager {
private exampleMap: string;
constructor(exampleMapAsXml: string) {
super();
$assert(exampleMapAsXml, 'The test map must be set');
this.exampleMap = exampleMapAsXml;
}
saveMapXml(): void {
// Ignore, no implementation required ...
}
discardChanges() {
// Ignore, no implementation required ...
}
loadMapDom() {
return $.parseXML(this.exampleMap);
}
unlockMap(): void {
// Ignore, no implementation required ...
}
}
export default MockPersistenceManager;

View File

@ -21,58 +21,56 @@ import $ from 'jquery';
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
import Events from './Events';
import ActionDispatcher from './ActionDispatcher';
import Topic from './Topic';
initHotKeyPluggin($);
class MultilineTextEditor extends Events {
private _topic: Topic;
private _containerElem: JQuery;
constructor() {
super();
this._topic = null;
this._timeoutId = -1;
}
static _buildEditor() {
private static _buildEditor() {
const result = $('<div></div>')
.attr('id', 'textContainer')
.css({
display: 'none',
zIndex: '8',
overflow: 'hidden',
border: '0 none',
});
const textareaElem = $('<textarea tabindex="-1" value="" wrap="off" ></textarea>')
.css({
border: '1px gray dashed',
background: 'rgba(98, 135, 167, .3)',
background: 'rgba(98, 135, 167, .4)',
outline: '0 none',
resize: 'none',
overflow: 'hidden',
padding: '2px 0px 2px 4px',
});
result.append(textareaElem);
return result;
}
_registerEvents(containerElem) {
private _registerEvents(containerElem: JQuery) {
const textareaElem = this._getTextareaElem();
const me = this;
let start;
let end;
textareaElem.on('keydown', function keydown(event) {
switch ($.hotkeys.specialKeys[event.keyCode]) {
textareaElem.on('keydown', (event) => {
const j: any = $;
switch (j.hotkeys.specialKeys[event.keyCode]) {
case 'esc':
me.close(false);
this.close(false);
break;
case 'enter':
case 'enter': {
if (event.metaKey || event.ctrlKey) {
// Add return ...
const text = textareaElem.val();
let cursorPosition = text.length;
if (textareaElem.selectionStart) {
cursorPosition = textareaElem.selectionStart;
}
const text = this._getTextAreaText();
const cursorPosition = text.length;
const head = text.substring(0, cursorPosition);
let tail = '';
if (cursorPosition < text.length) {
@ -80,31 +78,12 @@ class MultilineTextEditor extends Events {
}
textareaElem.val(`${head}\n${tail}`);
// Position cursor ...
if (textareaElem[0].setSelectionRange) {
textareaElem.focus();
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
} else if (textareaElem.createTextRange) {
const range = textareaElem.createTextRange();
range.moveStart('character', cursorPosition + 1);
range.select();
}
} else {
me.close(true);
this.close(true);
}
break;
case 'tab': {
event.preventDefault();
start = $(this).get(0).selectionStart;
end = $(this).get(0).selectionEnd;
// set textarea value to: text before caret + tab + text after caret
$(this).val(`${$(this).val().substring(0, start)}\t${$(this).val().substring(end)}`);
// put caret at right position again
$(this).get(0).selectionEnd = start + 1;
$(this).get(0).selectionStart = $(this).get(0).selectionEnd;
break;
}
default:
// No actions...
@ -118,9 +97,9 @@ class MultilineTextEditor extends Events {
});
textareaElem.on('keyup', (event) => {
const text = me._getTextareaElem().val();
me.fireEvent('input', [event, text]);
me._adjustEditorSize();
const text = this._getTextareaElem().val();
this.fireEvent('input', [event, text]);
this._adjustEditorSize();
});
// If the user clicks on the input, all event must be ignored ...
@ -135,33 +114,33 @@ class MultilineTextEditor extends Events {
});
}
_adjustEditorSize() {
private _adjustEditorSize() {
if (this.isVisible()) {
const textElem = this._getTextareaElem();
const lines = textElem.val().split('\n');
const lines = this._getTextAreaText().split('\n');
let maxLineLength = 1;
lines.forEach((line) => {
if (maxLineLength < line.length) maxLineLength = line.length;
lines.forEach((line: string) => {
maxLineLength = Math.max(line.length, maxLineLength);
});
textElem.attr('cols', maxLineLength);
textElem.attr('rows', lines.length);
this._containerElem.css({
width: `${maxLineLength + 3}em`,
width: `${maxLineLength + 2}em`,
height: textElem.height(),
});
}
}
isVisible() {
isVisible(): boolean {
return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
}
_updateModel() {
if (this._topic.getText() !== this._getText()) {
const text = this._getText();
private _updateModel() {
if (this._topic.getText() !== this._getTextAreaText()) {
const text = this._getTextAreaText();
const topicId = this._topic.getId();
const actionDispatcher = ActionDispatcher.getInstance();
@ -169,7 +148,7 @@ class MultilineTextEditor extends Events {
}
}
show(topic, text) {
show(topic: Topic, text: string): void {
// Close a previous node editor if it's opened ...
if (this._topic) {
this.close(false);
@ -187,7 +166,7 @@ class MultilineTextEditor extends Events {
}
}
_showEditor(defaultText) {
private _showEditor(defaultText: string) {
const topic = this._topic;
// Hide topic text ...
@ -199,32 +178,29 @@ class MultilineTextEditor extends Events {
fontStyle.size = nodeText.getHtmlFontSize();
fontStyle.color = nodeText.getColor();
this._setStyle(fontStyle);
const me = this;
// Set editor's initial size
const displayFunc = function displayFunc() {
// Position the editor and set the size...
const textShape = topic.getTextShape();
me._containerElem.css('display', 'block');
this._containerElem.css('display', 'block');
// FIXME: Im not sure if this is best way...
const shapePosition = textShape.getNativePosition();
me._containerElem.offset(shapePosition);
let { top, left } = textShape.getNativePosition();
// Adjust padding top position ...
top -= 4;
left -= 4;
this._containerElem.offset({ top, left });
// Set editor's initial text ...
const text = $defined(defaultText) ? defaultText : topic.getText();
me._setText(text);
this._setText(text);
// Set the element focus and select the current text ...
const inputElem = me._getTextareaElem();
me._positionCursor(inputElem, !$defined(defaultText));
};
this._timeoutId = setTimeout(() => displayFunc(), 10);
const inputElem = this._getTextareaElem();
this._positionCursor(inputElem, !$defined(defaultText));
}
_setStyle(fontStyle) {
private _setStyle(fontStyle) {
const inputField = this._getTextareaElem();
// allowed param reassign to avoid risks of existing code relying in this side-effect
/* eslint-disable no-param-reassign */
@ -252,65 +228,46 @@ class MultilineTextEditor extends Events {
this._containerElem.css(style);
}
_setText(text) {
private _setText(text: string): void {
const textareaElem = this._getTextareaElem();
textareaElem.val(text);
this._adjustEditorSize();
}
_getText() {
return this._getTextareaElem().val();
private _getTextAreaText(): string {
return this._getTextareaElem().val() as string;
}
_getTextareaElem() {
private _getTextareaElem(): JQuery<HTMLTextAreaElement> {
return this._containerElem.find('textarea');
}
_positionCursor(textareaElem, selectText) {
private _positionCursor(textareaElem: JQuery<HTMLTextAreaElement>, selectText: boolean) {
textareaElem.focus();
const lengh = textareaElem.val().length;
const lengh = this._getTextAreaText().length;
if (selectText) {
// Mark text as selected ...
if (textareaElem.createTextRange) {
const rang = textareaElem.createTextRange();
rang.select();
rang.move('character', lengh);
} else {
textareaElem[0].setSelectionRange(0, lengh);
}
} else if (textareaElem.createTextRange) {
const range = textareaElem.createTextRange();
range.move('character', lengh);
} else {
// allowed param reassign to avoid risks of existing code relying in this side-effect
/* eslint-disable no-param-reassign */
textareaElem.selectionStart = lengh;
textareaElem.selectionEnd = lengh;
/* eslint-enable no-param-reassign */
textareaElem.focus();
}
}
close(update) {
if (this.isVisible() && this._topic) {
// Update changes ...
clearTimeout(this._timeoutId);
if (!$defined(update) || update) {
close(update: boolean): void {
if (this.isVisible()) {
if (update) {
this._updateModel();
}
// Let make the visible text in the node visible again ...
this._topic.getTextShape().setVisibility(true);
// Remove it form the screen ...
this._containerElem.remove();
this._containerElem = null;
this._timeoutId = -1;
}
if (this._topic) {
this._topic.getTextShape().setVisibility(true);
this._topic = null;
}
}
}
export default MultilineTextEditor;

View File

@ -16,14 +16,13 @@
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import { ElementClass } from '@wisemapping/web2d';
import { ElementClass, Point } from '@wisemapping/web2d';
import TopicConfig from './TopicConfig';
import NodeModel from './model/NodeModel';
import Workspace from './Workspace';
import DragTopic from './DragTopic';
import LayoutManager from './layout/LayoutManager';
import SizeType from './SizeType';
import PositionType from './PositionType';
abstract class NodeGraph {
private _mouseEvents: boolean;
@ -105,7 +104,7 @@ abstract class NodeGraph {
return this._size;
}
setSize(size: SizeType, force?: boolean) {
setSize(size: SizeType) {
this._size.width = size.width;
this._size.height = size.height;
}
@ -160,15 +159,15 @@ abstract class NodeGraph {
workspace.removeChild(this);
}
/** */
createDragNode(layoutManager: LayoutManager) {
const dragShape = this._buildDragShape();
return new DragTopic(dragShape, this, layoutManager);
}
abstract _buildDragShape();
getPosition(): PositionType {
getPosition(): Point {
const model = this.getModel();
return model.getPosition();
}

View File

@ -21,9 +21,17 @@ import { $msg } from './Messages';
import Icon from './Icon';
import FloatingTip from './widget/FloatingTip';
import NotesImage from '../../assets/icons/notes.svg';
import Topic from './Topic';
import NoteModel from './model/NoteModel';
import FeatureModel from './model/FeatureModel';
class NoteIcon extends Icon {
constructor(topic, noteModel, readOnly) {
private _linksModel: NoteModel;
private _topic: Topic;
private _readOnly: boolean;
private _tip: FloatingTip;
constructor(topic: Topic, noteModel: NoteModel, readOnly: boolean) {
$assert(topic, 'topic can not be null');
super(NoteIcon.IMAGE_URL);
@ -34,7 +42,7 @@ class NoteIcon extends Icon {
this._registerEvents();
}
_registerEvents() {
private _registerEvents(): void {
this._image.setCursor('pointer');
const me = this;
@ -58,7 +66,7 @@ class NoteIcon extends Icon {
});
}
_buildTooltipContent() {
private _buildTooltipContent(): JQuery {
if ($('body').find('#textPopoverNote').length === 1) {
const text = $('body').find('#textPopoverNote');
text.text(this._linksModel.getText());
@ -75,11 +83,12 @@ class NoteIcon extends Icon {
return result;
}
getModel() {
getModel(): FeatureModel {
return this._linksModel;
}
static IMAGE_URL = NotesImage;
}
NoteIcon.IMAGE_URL = NotesImage;
export default NoteIcon;

View File

@ -20,11 +20,21 @@ import { $assert } from '@wisemapping/core-js';
import { Mindmap } from '..';
import XMLSerializerFactory from './persistence/XMLSerializerFactory';
export type PersistenceError = {
severity: string;
message: string;
errorType?: 'session-expired' | 'bad-request' | 'generic';
};
export type PersistenceErrorCallback = (error: PersistenceError) => void;
abstract class PersistenceManager {
// eslint-disable-next-line no-use-before-define
static _instance: PersistenceManager;
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events, sync: boolean) {
private _errorHandlers: PersistenceErrorCallback[] = [];
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events?) {
$assert(mindmap, 'mindmap can not be null');
$assert(editorProperties, 'editorProperties can not be null');
@ -33,30 +43,55 @@ abstract class PersistenceManager {
const serializer = XMLSerializerFactory.createInstanceFromMindmap(mindmap);
const domMap = serializer.toXML(mindmap);
const mapXml = new XMLSerializer().serializeToString(domMap);
const pref = JSON.stringify(editorProperties);
try {
this.saveMapXml(mapId, mapXml, pref, saveHistory, events, sync);
this.saveMapXml(mapId, domMap, pref, saveHistory, events);
} catch (e) {
console.error(e);
events.onError(e);
}
}
protected getCSRFToken(): string | null {
const meta = document.head.querySelector('meta[name="_csrf"]');
let result = null;
if (meta) {
result = meta.getAttribute('content');
}
return result;
}
load(mapId: string) {
$assert(mapId, 'mapId can not be null');
const domDocument = this.loadMapDom(mapId);
return PersistenceManager.loadFromDom(mapId, domDocument);
}
triggerError(error: PersistenceError) {
this._errorHandlers.forEach((handler) => handler(error));
}
addErrorHandler(callback: PersistenceErrorCallback) {
this._errorHandlers.push(callback);
}
removeErrorHandler(callback?: PersistenceErrorCallback) {
if (!callback) {
this._errorHandlers.length = 0;
}
const index = this._errorHandlers.findIndex((handler) => handler === callback);
if (index !== -1) {
this._errorHandlers.splice(index, 1);
}
}
abstract discardChanges(mapId: string): void;
abstract loadMapDom(mapId: string): Document;
abstract saveMapXml(mapId: string, mapXml, pref, saveHistory, events, sync);
abstract saveMapXml(mapId: string, mapXml: Document, pref?, saveHistory?: boolean, events?);
abstract unlockMap(mindmap: Mindmap): void;
abstract unlockMap(mapId: string): void;
static init = (instance: PersistenceManager) => {
this._instance = instance;

View File

@ -20,7 +20,7 @@ import { Arrow, Point, ElementClass } from '@wisemapping/web2d';
import ConnectionLine from './ConnectionLine';
import ControlPoint from './ControlPoint';
import RelationshipModel from './model/RelationshipModel';
import NodeGraph from './NodeGraph';
import Topic from './Topic';
import Shape from './util/Shape';
class Relationship extends ConnectionLine {
@ -38,11 +38,11 @@ class Relationship extends ConnectionLine {
private _endArrow: Arrow;
private _controlPointControllerListener: any;
private _controlPointControllerListener;
private _showStartArrow: Arrow;
constructor(sourceNode: NodeGraph, targetNode: NodeGraph, model: RelationshipModel) {
constructor(sourceNode: Topic, targetNode: Topic, model: RelationshipModel) {
$assert(sourceNode, 'sourceNode can not be null');
$assert(targetNode, 'targetNode can not be null');
@ -349,7 +349,7 @@ class Relationship extends ConnectionLine {
return this._model.getId();
}
fireEvent(type: string, event: any): void {
fireEvent(type: string, event): void {
const elem = this._line2d;
elem.trigger(type, event);
}

View File

@ -36,7 +36,7 @@ class RelationshipPivot {
private _sourceTopic: Topic;
private _pivot: any;
private _pivot: CurvedLine;
private _startArrow: Arrow;

View File

@ -17,9 +17,8 @@
*/
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
import { Mindmap } from '..';
import { $msg } from './Messages';
import PersistenceManager from './PersistenceManager';
import PersistenceManager, { PersistenceError } from './PersistenceManager';
class RESTPersistenceManager extends PersistenceManager {
private documentUrl: string;
@ -51,10 +50,10 @@ class RESTPersistenceManager extends PersistenceManager {
this.session = options.session;
}
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events, sync: boolean): void {
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events): void {
const data = {
id: mapId,
xml: mapXml,
xml: new XMLSerializer().serializeToString(mapXml),
properties: pref,
};
@ -71,73 +70,79 @@ class RESTPersistenceManager extends PersistenceManager {
}, 10000);
const persistence = this;
$.ajax({
type: 'put',
url: `${this.documentUrl.replace('{id}', mapId)}?${query}`,
dataType: 'json',
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
async: !sync,
success(successData) {
persistence.timestamp = successData;
fetch(
`${this.documentUrl.replace('{id}', mapId)}?${query}`,
{
method: 'PUT',
// Blob helps to resuce the memory on large payload.
body: new Blob([JSON.stringify(data)], { type: 'text/plain' }),
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
},
).then(async (response: Response) => {
if (response.ok) {
persistence.timestamp = await response.text();
events.onSuccess();
},
complete() {
// Clear event timeout ...
if (persistence.clearTimeout) {
clearTimeout(persistence.clearTimeout);
}
persistence.onSave = false;
},
error(xhr) {
const { responseText } = xhr;
let userMsg = { severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED') };
const contentType = xhr.getResponseHeader('Content-Type');
} else {
console.log(`Saving error: ${response.status}`);
let userMsg;
if (response.status === 405) {
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED'), errorType: 'session-expired' };
} else {
const responseText = await response.text();
const contentType = response.headers['Content-Type'];
if (contentType != null && contentType.indexOf('application/json') !== -1) {
let serverMsg = null;
try {
serverMsg = $.parseJSON(responseText);
serverMsg = JSON.parse(responseText);
serverMsg = serverMsg.globalSeverity ? serverMsg : null;
} catch (e) {
// Message could not be decoded ...
}
userMsg = persistence._buildError(serverMsg);
} else if (this.status === 405) {
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED') };
}
}
this.triggerError(userMsg);
events.onError(userMsg);
}
// Clear event timeout ...
if (persistence.clearTimeout) {
clearTimeout(persistence.clearTimeout);
}
persistence.onSave = false;
}).catch(() => {
const userMsg: PersistenceError = {
severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED'), errorType: 'generic',
};
this.triggerError(userMsg);
events.onError(userMsg);
// Clear event timeout ...
if (persistence.clearTimeout) {
clearTimeout(persistence.clearTimeout);
}
persistence.onSave = false;
},
});
}
}
discardChanges(mapId: string) {
$.ajax({
url: this.revertUrl.replace('{id}', mapId),
async: false,
method: 'post',
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json' },
error(xhr, ajaxOptions, thrownError) {
console.error(`Request error => status:${xhr.status} ,thrownError: ${thrownError}`);
},
fetch(this.revertUrl.replace('{id}', mapId),
{
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
});
}
unlockMap(mindmap: Mindmap) {
const mapId = mindmap.getId();
$.ajax({
url: this.lockUrl.replace('{id}', mapId),
async: false,
method: 'put',
headers: { 'Content-Type': 'text/plain' },
data: 'false',
error(xhr, ajaxOptions, thrownError) {
console.error(`Request error => status:${xhr.status} ,thrownError: ${thrownError}`);
unlockMap(mapId: string): void {
fetch(
this.lockUrl.replace('{id}', mapId),
{
method: 'PUT',
headers: { 'Content-Type': 'text/plain', 'X-CSRF-Token': this.getCSRFToken() },
body: 'false',
},
});
);
}
private _buildError(jsonSeverResponse) {
@ -155,13 +160,12 @@ class RESTPersistenceManager extends PersistenceManager {
}
loadMapDom(mapId: string): Document {
// Let's try to open one from the local directory ...
let xml: Document;
$.ajax({
url: `${this.documentUrl.replace('{id}', mapId)}/xml`,
method: 'get',
async: false,
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml' },
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml', 'X-CSRF-Token': this.getCSRFToken() },
success(responseText) {
xml = responseText;
},

View File

@ -17,8 +17,6 @@
*/
import { $assert } from '@wisemapping/core-js';
import { Point } from '@wisemapping/web2d';
import Icon from './Icon';
import Topic from './Topic';
class ScreenManager {
private _divContainer: JQuery;
@ -84,59 +82,6 @@ class ScreenManager {
}
}
private _getElementPosition(elem: Topic) {
// Retrieve current element position.
const elementPosition = elem.getPosition();
let { x } = elementPosition;
let { y } = elementPosition;
// Add workspace offset.
x -= this._padding.x;
y -= this._padding.y;
// Scale coordinate in order to be relative to the workspace. That's coord/size;
x /= this._scale;
y /= this._scale;
// Remove decimal part..
return { x, y };
}
getWorkspaceIconPosition(e: Icon) {
// Retrieve current icon position.
const image = e.getImage();
const elementPosition = image.getPosition();
const imageSize = e.getSize();
// Add group offset
const iconGroup = e.getGroup();
const group = iconGroup.getNativeElement();
const coordOrigin = group.getCoordOrigin();
const groupSize = group.getSize();
const coordSize = group.getCoordSize();
const scale = {
x: coordSize.width / parseInt(groupSize.width, 10),
y: coordSize.height / parseInt(groupSize.height, 10),
};
let x = (elementPosition.x - coordOrigin.x - parseInt(imageSize.width, 10) / 2) / scale.x;
let y = (elementPosition.y - coordOrigin.y - parseInt(imageSize.height, 10) / 2) / scale.y;
// Retrieve iconGroup Position
const groupPosition = iconGroup.getPosition();
x += groupPosition.x;
y += groupPosition.y;
// Retrieve topic Position
const topic = iconGroup.getTopic();
const topicPosition = this._getElementPosition(topic);
topicPosition.x -= parseInt(topic.getSize().width, 10) / 2;
// Remove decimal part..
return { x: x + topicPosition.x, y: y + topicPosition.y };
}
getWorkspaceMousePosition(event: MouseEvent) {
// Retrieve current mouse position.
let x = event.clientX;

View File

@ -79,11 +79,11 @@ class StandaloneActionDispatcher extends ActionDispatcher {
$assert($defined(topicId), 'topicsId can not be null');
$assert($defined(position), 'position can not be null');
const commandFunc = (topic, value) => {
const commandFunc = (topic: Topic, pos: Point) => {
const result = topic.getPosition();
EventBus.instance.fireEvent(EventBus.events.NodeMoveEvent, {
node: topic.getModel(),
position: value,
position: pos,
});
return result;
};
@ -114,7 +114,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
changeTextToTopic(topicsIds: number[], text: string) {
$assert($defined(topicsIds), 'topicsIds can not be null');
const commandFunc = (topic: Topic, value: object) => {
const commandFunc = (topic: Topic, value: string) => {
const result = topic.getText();
topic.setText(value);
return result;
@ -163,7 +163,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
$assert(topicsIds, 'topicIds can not be null');
$assert(color, 'color can not be null');
const commandFunc = (topic, commandColor) => {
const commandFunc = (topic: Topic, commandColor: string) => {
const result = topic.getBackgroundColor();
topic.setBackgroundColor(commandColor);
return result;
@ -179,7 +179,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
$assert(topicsIds, 'topicIds can not be null');
$assert(color, 'topicIds can not be null');
const commandFunc = (topic, commandColor) => {
const commandFunc = (topic: Topic, commandColor: string) => {
const result = topic.getBorderColor();
topic.setBorderColor(commandColor);
return result;
@ -195,11 +195,11 @@ class StandaloneActionDispatcher extends ActionDispatcher {
$assert(topicsIds, 'topicIds can not be null');
$assert(size, 'size can not be null');
const commandFunc = (topic, commandSize) => {
const commandFunc = (topic: Topic, commandSize: number) => {
const result = topic.getFontSize();
topic.setFontSize(commandSize, true);
topic._adjustShapes();
topic.adjustShapes();
return result;
};
@ -212,9 +212,9 @@ class StandaloneActionDispatcher extends ActionDispatcher {
$assert(topicsIds, 'topicsIds can not be null');
$assert(shapeType, 'shapeType can not be null');
const commandFunc = (topic, commandShapeType) => {
const commandFunc = (topic: Topic, commandShapeType: string) => {
const result = topic.getShapeType();
topic.setShapeType(commandShapeType, true);
topic.setShapeType(commandShapeType);
return result;
};
@ -226,12 +226,12 @@ class StandaloneActionDispatcher extends ActionDispatcher {
changeFontWeightToTopic(topicsIds: number[]) {
$assert(topicsIds, 'topicsIds can not be null');
const commandFunc = (topic) => {
const commandFunc = (topic: Topic) => {
const result = topic.getFontWeight();
const weight = result === 'bold' ? 'normal' : 'bold';
topic.setFontWeight(weight, true);
topic._adjustShapes();
topic.adjustShapes();
return result;
};

View File

@ -44,6 +44,8 @@ import LayoutManager from './layout/LayoutManager';
import NoteModel from './model/NoteModel';
import LinkModel from './model/LinkModel';
import SizeType from './SizeType';
import FeatureModel from './model/FeatureModel';
import Icon from './Icon';
const ICON_SCALING_FACTOR = 1.3;
@ -104,15 +106,10 @@ abstract class Topic extends NodeGraph {
});
}
/**
* @param {String} type the topic shape type
* @see {@link mindplot.model.INodeModel}
*/
setShapeType(type) {
setShapeType(type: string): void {
this._setShapeType(type, true);
}
/** @return {mindplot.Topic} parent topic */
getParent(): Topic | null {
return this._parent;
}
@ -161,8 +158,7 @@ abstract class Topic extends NodeGraph {
}
}
/** @return {String} topic shape type */
getShapeType() {
getShapeType(): string {
const model = this.getModel();
let result = model.getShapeType();
if (!$defined(result)) {
@ -171,7 +167,7 @@ abstract class Topic extends NodeGraph {
return result;
}
private _removeInnerShape() {
private _removeInnerShape(): ElementClass {
const group = this.get2DElement();
const innerShape = this.getInnerShape();
group.removeChild(innerShape);
@ -308,7 +304,7 @@ abstract class Topic extends NodeGraph {
return this._text;
}
getOrBuildIconGroup() {
getOrBuildIconGroup(): Group {
if (!$defined(this._iconsGroup)) {
this._iconsGroup = this._buildIconGroup();
const group = this.get2DElement();
@ -346,7 +342,7 @@ abstract class Topic extends NodeGraph {
* @param {mindplot.model.FeatureModel} featureModel
* @return {mindplot.Icon} the icon corresponding to the feature model
*/
addFeature(featureModel) {
addFeature(featureModel: FeatureModel): Icon {
const iconGroup = this.getOrBuildIconGroup();
this.closeEditors();
@ -365,13 +361,13 @@ abstract class Topic extends NodeGraph {
}
/** */
findFeatureById(id) {
findFeatureById(id: number) {
const model = this.getModel();
return model.findFeatureById(id);
}
/** */
removeFeature(featureModel) {
removeFeature(featureModel: FeatureModel): void {
$assert(featureModel, 'featureModel could not be null');
// Removing the icon from MODEL
@ -387,21 +383,21 @@ abstract class Topic extends NodeGraph {
}
/** */
addRelationship(relationship) {
addRelationship(relationship: Relationship) {
this._relationships.push(relationship);
}
/** */
deleteRelationship(relationship) {
deleteRelationship(relationship: Rect) {
this._relationships = this._relationships.filter((r) => r !== relationship);
}
/** */
getRelationships() {
getRelationships(): Relationship[] {
return this._relationships;
}
_buildTextShape(readOnly): Text {
protected _buildTextShape(readOnly: boolean): Text {
const result = new Text();
const family = this.getFontFamily();
const size = this.getFontSize();
@ -425,7 +421,7 @@ abstract class Topic extends NodeGraph {
}
/** */
setFontFamily(value, updateModel) {
setFontFamily(value: string, updateModel?: boolean) {
const textShape = this.getTextShape();
textShape.setFontName(value);
if ($defined(updateModel) && updateModel) {
@ -436,7 +432,7 @@ abstract class Topic extends NodeGraph {
}
/** */
setFontSize(value, updateModel) {
setFontSize(value: number, updateModel?: boolean) {
const textShape = this.getTextShape();
textShape.setSize(value);
@ -534,7 +530,7 @@ abstract class Topic extends NodeGraph {
}
}
_setText(text, updateModel) {
_setText(text: string, updateModel: boolean) {
const textShape = this.getTextShape();
textShape.setText(text == null ? TopicStyle.defaultText(this) : text);
@ -545,7 +541,7 @@ abstract class Topic extends NodeGraph {
}
/** */
setText(text) {
setText(text: string) {
// Avoid empty nodes ...
if (!text || $.trim(text).length === 0) {
this._setText(null, true);
@ -557,7 +553,7 @@ abstract class Topic extends NodeGraph {
}
/** */
getText() {
getText(): string {
const model = this.getModel();
let result = model.getText();
if (!$defined(result)) {
@ -567,11 +563,11 @@ abstract class Topic extends NodeGraph {
}
/** */
setBackgroundColor(color) {
setBackgroundColor(color: string) {
this._setBackgroundColor(color, true);
}
_setBackgroundColor(color, updateModel) {
_setBackgroundColor(color: string, updateModel: boolean) {
const innerShape = this.getInnerShape();
innerShape.setFill(color);
@ -587,7 +583,7 @@ abstract class Topic extends NodeGraph {
}
/** */
getBackgroundColor() {
getBackgroundColor(): string {
const model = this.getModel();
let result = model.getBackgroundColor();
if (!$defined(result)) {
@ -597,11 +593,11 @@ abstract class Topic extends NodeGraph {
}
/** */
setBorderColor(color) {
setBorderColor(color: string) {
this._setBorderColor(color, true);
}
_setBorderColor(color, updateModel) {
_setBorderColor(color: string, updateModel: boolean) {
const innerShape = this.getInnerShape();
innerShape.setAttribute('strokeColor', color);
@ -1016,6 +1012,10 @@ abstract class Topic extends NodeGraph {
}
}
// Hide inner shape ...
this.getInnerShape().setVisibility(value);
// Hide text shape ...
const textShape = this.getTextShape();
textShape.setVisibility(this.getShapeType() !== TopicShape.IMAGE ? value : false);
}
@ -1058,7 +1058,7 @@ abstract class Topic extends NodeGraph {
}
}
setSize(size: SizeType, force: boolean): void {
setSize(size: SizeType, force?: boolean): void {
$assert(size, 'size can not be null');
$assert($defined(size.width), 'size seem not to be a valid element');
const roundedSize = {
@ -1272,7 +1272,7 @@ abstract class Topic extends NodeGraph {
}
// If a drag node is create for it, let's hide the editor.
this._getTopicEventDispatcher().close();
this._getTopicEventDispatcher().close(false);
return result;
}
@ -1296,7 +1296,7 @@ abstract class Topic extends NodeGraph {
const topicHeight = Math.max(iconHeight, textHeight) + padding * 2;
const textIconSpacing = Math.round(fontHeight / 4);
const iconGroupWith = iconGroup.getSize().width;
const topicWith = iconGroupWith + textIconSpacing + textWidth + padding * 2;
const topicWith = iconGroupWith + 2 * textIconSpacing + textWidth + padding * 2;
this.setSize({
width: topicWith,
@ -1334,6 +1334,10 @@ abstract class Topic extends NodeGraph {
return result;
}
abstract workoutOutgoingConnectionPoint(position: Point): Point;
abstract workoutIncomingConnectionPoint(position: Point): Point;
isChildTopic(childTopic: Topic): boolean {
let result = this.getId() === childTopic.getId();
if (!result) {
@ -1349,7 +1353,7 @@ abstract class Topic extends NodeGraph {
return result;
}
isCentralTopic() {
isCentralTopic(): boolean {
return this.getModel().getType() === 'CentralTopic';
}
}

View File

@ -19,6 +19,7 @@ import { $assert } from '@wisemapping/core-js';
import Events from './Events';
import MultilineTextEditor from './MultilineTextEditor';
import { TopicShape } from './model/INodeModel';
import Topic from './Topic';
const TopicEvent = {
EDIT: 'editnode',
@ -26,30 +27,39 @@ const TopicEvent = {
};
class TopicEventDispatcher extends Events {
constructor(readOnly) {
private _readOnly: boolean;
private _activeEditor: MultilineTextEditor;
private _multilineEditor: MultilineTextEditor;
// eslint-disable-next-line no-use-before-define
static _instance: TopicEventDispatcher;
constructor(readOnly: boolean) {
super();
this._readOnly = readOnly;
this._activeEditor = null;
this._multilineEditor = new MultilineTextEditor();
}
close(update) {
close(update: boolean): void {
if (this.isVisible()) {
this._activeEditor.close(update);
this._activeEditor = null;
}
}
show(topic, options) {
show(topic: Topic, options?): void {
this.process(TopicEvent.EDIT, topic, options);
}
process(eventType, topic, options) {
process(eventType: string, topic: Topic, options?): void {
$assert(eventType, 'eventType can not be null');
// Close all previous open editor ....
if (this.isVisible()) {
this.close();
this.close(false);
}
// Open the new editor ...
@ -66,20 +76,18 @@ class TopicEventDispatcher extends Events {
}
}
isVisible() {
isVisible(): boolean {
return this._activeEditor != null && this._activeEditor.isVisible();
}
static configure(readOnly: boolean): void {
this._instance = new TopicEventDispatcher(readOnly);
}
TopicEventDispatcher._instance = null;
TopicEventDispatcher.configure = function configure(readOnly) {
this._instance = new TopicEventDispatcher(readOnly);
};
TopicEventDispatcher.getInstance = function getInstance() {
static getInstance(): TopicEventDispatcher {
return this._instance;
};
}
}
export { TopicEvent };
export default TopicEventDispatcher;

View File

@ -18,17 +18,21 @@
import { $assert, $defined } from '@wisemapping/core-js';
import Command from '../Command';
import CommandContext from '../CommandContext';
import NodeModel from '../model/NodeModel';
import RelationshipModel from '../model/RelationshipModel';
import Relationship from '../Relationship';
import Topic from '../Topic';
class DeleteCommand extends Command {
private _relIds: number[];
private _topicIds: number[];
private _deletedTopicModels: any[];
private _deletedTopicModels: NodeModel[];
private _deletedRelModel: any[];
private _deletedRelModel: RelationshipModel[];
private _parentTopicIds: any[];
private _parentTopicIds: number[];
constructor(topicIds: number[], relIds: number[]) {
$assert($defined(relIds), 'topicIds can not be null');
@ -101,11 +105,11 @@ class DeleteCommand extends Command {
// Do they need to be connected ?
this._deletedTopicModels.forEach(((topicModel, index) => {
const topics = commandContext.findTopics(topicModel.getId());
const topics = commandContext.findTopics([topicModel.getId()]);
const parentId = this._parentTopicIds[index];
if (parentId) {
const parentTopics = commandContext.findTopics(parentId);
const parentTopics = commandContext.findTopics([parentId]);
commandContext.connect(topics[0], parentTopics[0]);
}
}));
@ -117,14 +121,14 @@ class DeleteCommand extends Command {
// Finally display the topics ...
this._deletedTopicModels.forEach((topicModel) => {
const topics = commandContext.findTopics(topicModel.getId());
const topics = commandContext.findTopics([topicModel.getId()]);
topics[0].setBranchVisibility(true);
});
// Focus on last recovered topic ..
if (this._deletedTopicModels.length > 0) {
const firstTopic = this._deletedTopicModels[0];
const topic = commandContext.findTopics(firstTopic.getId())[0];
const topic = commandContext.findTopics([firstTopic.getId()])[0];
topic.setOnFocus(true);
}
@ -133,11 +137,11 @@ class DeleteCommand extends Command {
this._deletedRelModel = [];
}
_filterChildren(topicIds, commandContext) {
private _filterChildren(topicIds: number[], commandContext: CommandContext) {
const topics = commandContext.findTopics(topicIds);
const result = [];
topics.forEach((topic) => {
topics.forEach((topic: Topic) => {
let parent = topic.getParent();
let found = false;
while (parent != null && !found) {
@ -156,13 +160,16 @@ class DeleteCommand extends Command {
return result;
}
_collectInDepthRelationships(topic) {
private _collectInDepthRelationships(topic: Topic): Relationship[] {
let result = [];
result = result.concat(topic.getRelationships());
const children = topic.getChildren();
const rels = children.map(((t) => this._collectInDepthRelationships(t)));
result = result.concat(rels.flat());
const rels: (Relationship[])[] = children
.map(((t: Topic) => this._collectInDepthRelationships(t)));
// flatten and concact
result = result.concat(([].concat(...rels)));
if (result.length > 0) {
// Filter for unique ...

View File

@ -62,7 +62,7 @@ class DragTopicCommand extends Command {
const origPosition = topic.getPosition();
// Disconnect topic ..
if ($defined(origParentTopic) && origParentTopic.getId() !== this._parentId) {
if ($defined(origParentTopic)) {
commandContext.disconnect(topic);
}
@ -76,7 +76,6 @@ class DragTopicCommand extends Command {
}
// Finally, connect topic ...
if (!$defined(origParentTopic) || origParentTopic.getId() !== this._parentId) {
if ($defined(this._parentId)) {
const parentTopic = commandContext.findTopics([this._parentId])[0];
commandContext.connect(topic, parentTopic);
@ -87,7 +86,6 @@ class DragTopicCommand extends Command {
if ($defined(origParentTopic)) {
this._parentId = origParentTopic.getId();
}
}
topic.setVisibility(true);
// Store for undo ...

View File

@ -20,18 +20,20 @@ import Command from '../Command';
import CommandContext from '../CommandContext';
import Topic from '../Topic';
type CommandTypes = string | object | boolean | number;
class GenericFunctionCommand extends Command {
private _value: string | object | boolean | number;
private _value: CommandTypes;
private _topicsId: number[];
private _commandFunc: (topic: Topic, value: string | object | boolean | number) => string | object | boolean;
private _commandFunc: (topic: Topic, value: CommandTypes) => CommandTypes;
private _oldValues: any[];
private _oldValues: (CommandTypes)[];
private _applied: boolean;
constructor(commandFunc: (topic: Topic, value: string | object | boolean) => string | object | boolean, topicsIds: number[], value: string | object | boolean | number = undefined) {
constructor(commandFunc: (topic: Topic, value: CommandTypes) => CommandTypes, topicsIds: number[], value: CommandTypes = undefined) {
$assert(commandFunc, 'commandFunc must be defined');
$assert($defined(topicsIds), 'topicsIds must be defined');

View File

@ -15,26 +15,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Mindmap } from '../..';
import Exporter from './Exporter';
import SVGExporter from './SVGExporter';
/**
* Based on https://mybyways.com/blog/convert-svg-to-png-using-your-browser
*/
class BinaryImageExporter extends Exporter {
svgElement: Element;
private svgElement: Element;
mindmap: Mindmap;
private width: number;
width: number;
private height: number;
height: number;
private adjustToFit: boolean;
constructor(mindmap: Mindmap, svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg') {
super(imgFormat.split['/'][0], imgFormat);
constructor(svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg', adjustToFit = true) {
super(imgFormat.split('/')[0], imgFormat);
this.svgElement = svgElement;
this.mindmap = mindmap;
this.adjustToFit = adjustToFit;
this.width = width;
this.height = height;
}
@ -43,21 +41,35 @@ class BinaryImageExporter extends Exporter {
throw new Error('Images can not be exporeted');
}
async exportAndEndcode(): Promise<string> {
const svgExporter = new SVGExporter(this.svgElement);
const svgUrl = await svgExporter.exportAndEncode();
exportAndEncode(): Promise<string> {
const svgExporter = new SVGExporter(this.svgElement, this.adjustToFit);
const svgUrl = svgExporter.exportAndEncode();
return svgUrl.then((value: string) => {
// Get the device pixel ratio, falling back to 1. But, I will double the resolution to look nicer.
const dpr = (window.devicePixelRatio || 1) * 2;
// Create canvas ...
// Create canvas size ...
const canvas = document.createElement('canvas');
canvas.setAttribute('width', (this.width * dpr).toString());
canvas.setAttribute('height', (this.height * dpr).toString());
let width: number;
let height: number;
if (this.adjustToFit) {
// Size must match with SVG image size ...
const size = svgExporter.getImgSize();
width = (size.width * dpr);
height = (size.height * dpr);
} else {
// Use screensize as size ..
width = (this.width * dpr);
height = (this.height * dpr);
}
console.log(`Export size: ${width}:${height}`);
canvas.setAttribute('width', width.toFixed(0));
canvas.setAttribute('height', height.toFixed(0));
// Render the image and wait for the response ...
const img = new Image();
const result = new Promise<string>((resolve, reject) => {
const result = new Promise<string>((resolve) => {
img.onload = () => {
const ctx = canvas.getContext('2d');
// Scale for retina ...
@ -68,12 +80,13 @@ class BinaryImageExporter extends Exporter {
.toDataURL(this.getContentType())
.replace('image/png', 'octet/stream');
URL.revokeObjectURL(svgUrl);
URL.revokeObjectURL(value);
resolve(imgDataUri);
};
});
img.src = svgUrl;
img.src = value;
return result;
});
}
}
export default BinaryImageExporter;

View File

@ -0,0 +1,304 @@
import xmlFormatter from 'xml-formatter';
import { Mindmap } from '../..';
import INodeModel, { TopicShape } from '../model/INodeModel';
import RelationshipModel from '../model/RelationshipModel';
import IconModel from '../model/IconModel';
import FeatureModel from '../model/FeatureModel';
import LinkModel from '../model/LinkModel';
import NoteModel from '../model/NoteModel';
import PositionNodeType from '../PositionType';
import Exporter from './Exporter';
import FreemindConstant from './freemind/FreemindConstant';
import VersionNumber from './freemind/importer/VersionNumber';
import ObjectFactory from './freemind/ObjectFactory';
import FreemindMap from './freemind/Map';
import FreeminNode from './freemind/Node';
import Arrowlink from './freemind/Arrowlink';
import Richcontent from './freemind/Richcontent';
import Icon from './freemind/Icon';
import Edge from './freemind/Edge';
import Font from './freemind/Font';
class FreemindExporter extends Exporter {
private mindmap: Mindmap;
private nodeMap: Map<number, FreeminNode> = null;
private version: VersionNumber = FreemindConstant.SUPPORTED_FREEMIND_VERSION;
private objectFactory: ObjectFactory;
private static wisweToFreeFontSize: Map<number, number> = new Map<number, number>();
constructor(mindmap: Mindmap) {
super(FreemindConstant.SUPPORTED_FREEMIND_VERSION.getVersion(), 'application/xml');
this.mindmap = mindmap;
}
static {
this.wisweToFreeFontSize.set(6, 10);
this.wisweToFreeFontSize.set(8, 12);
this.wisweToFreeFontSize.set(10, 18);
this.wisweToFreeFontSize.set(15, 24);
}
private static parserXMLString(xmlStr: string, mimeType: DOMParserSupportedType): Document {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlStr, mimeType);
// FIXME: Fix error "unclosed tag: p" when exporting bug3 and enc
// Is there any parsing error ?.
/*
if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
const xmmStr = new XMLSerializer().serializeToString(xmlDoc);
console.log(xmmStr);
throw new Error(`Unexpected error parsing: ${xmlStr}. Error: ${xmmStr}`);
}
*/
return xmlDoc;
}
extension(): string {
return 'mm';
}
export(): Promise<string> {
this.objectFactory = new ObjectFactory();
this.nodeMap = new Map();
const freemainMap: FreemindMap = this.objectFactory.createMap();
freemainMap.setVesion(this.getVersionNumber());
const main: FreeminNode = this.objectFactory.createNode();
freemainMap.setNode(main);
const centralTopic: INodeModel = this.mindmap.getCentralTopic();
if (centralTopic) {
this.nodeMap.set(centralTopic.getId(), main);
this.setTopicPropertiesToNode({ freemindNode: main, mindmapTopic: centralTopic, isRoot: true });
this.addNodeFromTopic(centralTopic, main);
}
const relationships: Array<RelationshipModel> = this.mindmap.getRelationships();
relationships.forEach((relationship: RelationshipModel) => {
const srcNode: FreeminNode = this.nodeMap.get(relationship.getFromNode());
const destNode: FreeminNode = this.nodeMap.get(relationship.getToNode());
if (srcNode && destNode) {
const arrowlink: Arrowlink = this.objectFactory.crateArrowlink();
arrowlink.setDestination(destNode.getId());
if (relationship.getEndArrow() && relationship.getEndArrow()) arrowlink.setEndarrow('Default');
if (relationship.getStartArrow() && relationship.getStartArrow()) arrowlink.setStartarrow('Default');
srcNode.setArrowlinkOrCloudOrEdge(arrowlink);
}
});
const freeToXml = freemainMap.toXml();
const xmlToString = new XMLSerializer().serializeToString(freeToXml);
const formatXml = xmlFormatter(xmlToString, {
indentation: ' ',
collapseContent: true,
lineSeparator: '\n',
});
return Promise.resolve(formatXml);
}
private setTopicPropertiesToNode({ freemindNode, mindmapTopic, isRoot }: { freemindNode: FreeminNode; mindmapTopic: INodeModel; isRoot: boolean; }): void {
freemindNode.setId(`ID_${mindmapTopic.getId()}`);
const text = mindmapTopic.getText();
if (text) {
if (!text.includes('\n')) {
freemindNode.setText(text);
} else {
const richcontent: Richcontent = this.buildRichcontent(text, 'NODE');
freemindNode.setArrowlinkOrCloudOrEdge(richcontent);
}
}
const wiseShape: string = mindmapTopic.getShapeType();
if (wiseShape && TopicShape.LINE !== wiseShape) {
freemindNode.setBackgorundColor(this.rgbToHex(mindmapTopic.getBackgroundColor()));
}
if (wiseShape) {
const isRootRoundedRectangle = isRoot && TopicShape.ROUNDED_RECT !== wiseShape;
const notIsRootLine = !isRoot && TopicShape.LINE !== wiseShape;
if (isRootRoundedRectangle || notIsRootLine) {
let style: string = wiseShape;
if (TopicShape.ROUNDED_RECT === style || TopicShape.ELLIPSE === style) {
style = 'bubble';
}
freemindNode.setStyle(style);
}
} else if (!isRoot) freemindNode.setStyle('fork');
this.addFeautreNode(freemindNode, mindmapTopic);
this.addFontNode(freemindNode, mindmapTopic);
this.addEdgeNode(freemindNode, mindmapTopic);
}
private addNodeFromTopic(mainTopic: INodeModel, destNode: FreeminNode): void {
const curretnTopics: Array<INodeModel> = mainTopic.getChildren();
curretnTopics.forEach((currentTopic: INodeModel) => {
const newNode: FreeminNode = this.objectFactory.createNode();
this.nodeMap.set(currentTopic.getId(), newNode);
this.setTopicPropertiesToNode({ freemindNode: newNode, mindmapTopic: currentTopic, isRoot: false });
destNode.setArrowlinkOrCloudOrEdge(newNode);
this.addNodeFromTopic(currentTopic, newNode);
const position: PositionNodeType = currentTopic.getPosition();
if (position) {
const xPos: number = position.x;
newNode.setPosition((xPos < 0 ? 'left' : 'right'));
} else newNode.setPosition('left');
});
}
private buildRichcontent(text: string, type: string): Richcontent {
const richconent: Richcontent = this.objectFactory.createRichcontent();
richconent.setType(type);
const textSplit = text.split('\n');
let html = '<html><head></head><body>';
textSplit.forEach((line: string) => {
html += `<p>${line.trim()}</p>`;
});
html += '</body></html>';
const richconentDocument: Document = FreemindExporter.parserXMLString(html, 'application/xml');
const xmlResult = new XMLSerializer().serializeToString(richconentDocument);
richconent.setHtml(xmlResult);
return richconent;
}
private addFeautreNode(freemindNode: FreeminNode, mindmapTopic: INodeModel): void {
const branches: Array<FeatureModel> = mindmapTopic.getFeatures();
branches
.forEach((feature: FeatureModel) => {
const type = feature.getType();
if (type === 'link') {
const link = feature as LinkModel;
freemindNode.setLink(link.getUrl());
}
if (type === 'note') {
const note = feature as NoteModel;
const richcontent: Richcontent = this.buildRichcontent(note.getText(), 'NOTE');
freemindNode.setArrowlinkOrCloudOrEdge(richcontent);
}
if (type === 'icon') {
const icon = feature as IconModel;
const freemindIcon: Icon = new Icon();
freemindIcon.setBuiltin(icon.getIconType());
freemindNode.setArrowlinkOrCloudOrEdge(freemindIcon);
}
});
}
private addEdgeNode(freemainMap: FreeminNode, mindmapTopic: INodeModel): void {
if (mindmapTopic.getBorderColor()) {
const edgeNode: Edge = this.objectFactory.createEdge();
edgeNode.setColor(this.rgbToHex(mindmapTopic.getBorderColor()));
freemainMap.setArrowlinkOrCloudOrEdge(edgeNode);
}
}
private addFontNode(freemindNode: FreeminNode, mindmapTopic: INodeModel): void {
const fontFamily: string = mindmapTopic.getFontFamily();
const fontSize: number = mindmapTopic.getFontSize();
const fontColor: string = mindmapTopic.getFontColor();
const fontWeigth: string | number | boolean = mindmapTopic.getFontWeight();
const fontStyle: string = mindmapTopic.getFontStyle();
if (fontFamily || fontSize || fontColor || fontWeigth || fontStyle) {
const font: Font = this.objectFactory.createFont();
let fontNodeNeeded = false;
if (fontFamily) {
font.setName(fontFamily);
}
if (fontSize) {
const freeSize = FreemindExporter.wisweToFreeFontSize.get(fontSize);
if (freeSize) {
font.setSize(freeSize.toString());
fontNodeNeeded = true;
}
}
if (fontColor) {
freemindNode.setColor(fontColor);
}
if (fontWeigth) {
if (typeof fontWeigth === 'boolean') {
font.setBold(String(fontWeigth));
} else {
font.setBold(String(true));
}
fontNodeNeeded = true;
}
if (fontStyle === 'italic') {
font.setItalic(String(true));
}
if (fontNodeNeeded) {
if (!font.getSize()) {
font.setSize(FreemindExporter.wisweToFreeFontSize.get(8).toString());
}
freemindNode.setArrowlinkOrCloudOrEdge(font);
}
}
}
private rgbToHex(color: string): string {
let result: string = color;
if (result) {
const isRgb = new RegExp('^rgb\\([0-9]{1,3}, [0-9]{1,3}, [0-9]{1,3}\\)$');
if (isRgb.test(result)) {
const rgb: string[] = color.substring(4, color.length - 1).split(',');
const r: string = rgb[0].trim();
const g: string = rgb[1].trim();
const b: string = rgb[2].trim();
result = `#${r.length === 1 ? `0${r}` : r}${g.length === 1 ? `0${g}` : g}${b.length === 1 ? `0${b}` : b}`;
}
}
return result;
}
private getVersion(): VersionNumber {
return this.version;
}
private getVersionNumber(): string {
return this.getVersion().getVersion();
}
}
export default FreemindExporter;

View File

@ -15,26 +15,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Mindmap } from '../..';
import BinaryImageExporter from './BinaryImageExporter';
import Exporter from './Exporter';
import SVGExporter from './SVGExporter';
type imageType = 'svg' | 'png' | 'jpg';
class ImageExpoterFactory {
static create(type: imageType, mindmap: Mindmap, svgElement: Element, width: number, height: number, isCenter = false): Exporter {
let result;
static create(type: imageType, svgElement: Element, width: number, height: number, adjustToFit = true): Exporter {
let result: Exporter;
switch (type) {
case 'svg': {
result = new SVGExporter(svgElement);
result = new SVGExporter(svgElement, adjustToFit);
break;
}
case 'png': {
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/png');
result = new BinaryImageExporter(svgElement, width, height, 'image/png', adjustToFit);
break;
}
case 'jpg': {
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/jpeg');
result = new BinaryImageExporter(svgElement, width, height, 'image/jpeg', adjustToFit);
break;
}
default:

View File

@ -15,23 +15,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import SizeType from '../SizeType';
import Exporter from './Exporter';
class SVGExporter extends Exporter {
private svgElement: Element;
private prolog = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n';
private static prolog = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n';
constructor(svgElement: Element) {
private static regexpTranslate = /translate\((-?[0-9]+.[0-9]+),(-?[0-9]+.[0-9]+)\)/;
private static padding = 30;
private adjustToFit: boolean;
private static MAX_SUPPORTED_SIZE = 2500;
constructor(svgElement: Element, adjustToFit = true) {
super('svg', 'image/svg+xml');
this.svgElement = svgElement;
this.adjustToFit = adjustToFit;
}
export(): Promise<string> {
// Replace all images for in-line images ...
let svgTxt: string = new XMLSerializer()
.serializeToString(this.svgElement);
svgTxt = this.prolog + svgTxt;
svgTxt = SVGExporter.prolog + svgTxt;
// Are namespace declared ?. Otherwise, force the declaration ...
if (svgTxt.indexOf('xmlns:xlink=') === -1) {
@ -39,15 +49,103 @@ class SVGExporter extends Exporter {
}
// Add white background. This is mainly for PNG export ...
const svgDoc = SVGExporter.parseXMLString(svgTxt, 'application/xml');
let svgDoc = SVGExporter.parseXMLString(svgTxt, 'application/xml');
const svgElement = svgDoc.getElementsByTagName('svg')[0];
svgElement.setAttribute('style', 'background-color:white');
svgElement.setAttribute('focusable', 'false');
// Does need to be adjust ?.
if (this.adjustToFit) {
svgDoc = this._normalizeToFit(svgDoc);
}
const result = new XMLSerializer()
.serializeToString(svgDoc);
return Promise.resolve(result);
}
private _calcualteDimensions(): { minX: number, maxX: number, minY: number, maxY: number } {
// Collect all group elements ...
const rectElems = Array.from(document.querySelectorAll('g>rect'));
const translates: SizeType[] = rectElems
.map((rect: Element) => {
const g = rect.parentElement;
const transformStr = g.getAttribute('transform');
// Looking to parse translate(220.00000,279.00000) scale(1.00000,1.00000)
const match = transformStr.match(SVGExporter.regexpTranslate);
let result: SizeType = { width: 0, height: 0 };
if (match !== null) {
result = { width: Number.parseFloat(match[1]), height: Number.parseFloat(match[2]) };
// Add rect size ...
if (result.width > 0) {
const rectWidth = Number.parseFloat(rect.getAttribute('width'));
result.width += rectWidth;
}
if (result.height > 0) {
const rectHeight = Number.parseFloat(rect.getAttribute('height'));
result.height += rectHeight;
}
}
return result;
});
// Find max and mins ...
const widths = translates.map((t) => t.width).sort((a, b) => a - b);
const heights = translates.map((t) => t.height).sort((a, b) => a - b);
const minX = widths[0] - SVGExporter.padding;
const minY = heights[0] - SVGExporter.padding;
const maxX = widths[widths.length - 1] + SVGExporter.padding;
const maxY = heights[heights.length - 1] + SVGExporter.padding;
return {
minX, maxX, minY, maxY,
};
}
getImgSize(): SizeType {
const {
minX, maxX, minY, maxY,
} = this._calcualteDimensions();
let width: number = maxX + Math.abs(minX);
let height: number = maxY + Math.abs(minY);
// Prevents an image too big. Failures seen during export in couple of browsers
if (Math.max(width, height) > SVGExporter.MAX_SUPPORTED_SIZE) {
const scale = Math.max(width, height) / SVGExporter.MAX_SUPPORTED_SIZE;
width /= scale;
height /= scale;
}
return { width, height };
}
private _normalizeToFit(document: Document): Document {
const {
minX, maxX, minY, maxY,
} = this._calcualteDimensions();
const svgElem = document.firstChild as Element;
const width = maxX + Math.abs(minX);
const height = maxY + Math.abs(minY);
svgElem.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);
svgElem.setAttribute('preserveAspectRatio', 'xMinYMin');
// Get image size ...
const imgSize = this.getImgSize();
svgElem.setAttribute('width', imgSize.width.toFixed(0));
svgElem.setAttribute('height', imgSize.height.toFixed(0));
return document;
}
private static parseXMLString = (xmlStr: string, mimeType: DOMParserSupportedType) => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlStr, mimeType);

View File

@ -20,6 +20,7 @@ import Exporter from './Exporter';
import MDExporter from './MDExporter';
import TxtExporter from './TxtExporter';
import WiseXMLExporter from './WiseXMLExporter';
import FreemindExporter from './FreemindExporter';
type textType = 'wxml' | 'txt' | 'mm' | 'csv' | 'md' | 'mmap';
@ -36,6 +37,9 @@ class TextExporterFactory {
case 'md':
result = new MDExporter(mindmap);
break;
case 'mm':
result = new FreemindExporter(mindmap);
break;
default:
throw new Error(`Unsupported type ${type}`);
}

View File

@ -18,6 +18,7 @@
import { Mindmap } from '../..';
import INodeModel from '../model/INodeModel';
import LinkModel from '../model/LinkModel';
import NoteModel from '../model/NoteModel';
import Exporter from './Exporter';
class TxtExporter extends Exporter {
@ -32,27 +33,27 @@ class TxtExporter extends Exporter {
const { mindmap } = this;
const branches = mindmap.getBranches();
const txtStr = this.traverseBranch('', branches);
const txtStr = this.traverseBranch('', '', branches);
return Promise.resolve(txtStr);
}
private traverseBranch(prefix: string, branches: INodeModel[]) {
private traverseBranch(indent: string, prefix: string, branches: INodeModel[]) {
let result = '';
branches
.filter((n) => n.getText() !== undefined)
.forEach((node, index) => {
result = `${result}${prefix}${index + 1} ${node.getText()}`;
result = `${result}${indent}${prefix}${index + 1} ${node.getText() !== undefined ? node.getText() : ''}`;
node.getFeatures().forEach((f) => {
const type = f.getType();
if (type === 'link') {
result = `${result} [link: ${(f as LinkModel).getUrl()}]`;
result = `${result}\n ${indent} [Link: ${(f as LinkModel).getUrl()}]`;
}
if (type === 'note') {
result = `${result}\n${indent} [Note: ${(f as NoteModel).getText()}]`;
}
});
result = `${result}\n`;
if (node.getChildren().filter((n) => n.getText() !== undefined).length > 0) {
result += this.traverseBranch(`\t${prefix}${index + 1}.`, node.getChildren());
}
result += this.traverseBranch(`\t${indent}`, `${prefix}${index + 1}.`, node.getChildren());
});
return result;
}

View File

@ -0,0 +1,86 @@
export default class Arrowlink {
protected COLOR: string;
protected DESTINATION: string;
protected ENDARROW: string;
protected ENDINCLINATION: string;
protected ID: string;
protected STARTARROW: string;
protected STARTINCLINATION: string;
getColor(): string {
return this.COLOR;
}
getDestination(): string {
return this.DESTINATION;
}
getEndarrow(): string {
return this.ENDARROW;
}
getEndInclination(): string {
return this.ENDINCLINATION;
}
getId(): string {
return this.ID;
}
getStartarrow(): string {
return this.STARTARROW;
}
getStartinclination(): string {
return this.STARTINCLINATION;
}
setColor(value: string): void {
this.COLOR = value;
}
setDestination(value: string): void {
this.DESTINATION = value;
}
setEndarrow(value: string): void {
this.ENDARROW = value;
}
setEndinclination(value: string): void {
this.ENDINCLINATION = value;
}
setId(value: string): void {
this.ID = value;
}
setStartarrow(value: string): void {
this.STARTARROW = value;
}
setStartinclination(value: string): void {
this.STARTINCLINATION = value;
}
toXml(document: Document): HTMLElement {
const arrowlinkElem = document.createElement('arrowlink');
arrowlinkElem.setAttribute('DESTINATION', this.DESTINATION);
arrowlinkElem.setAttribute('STARTARROW', this.STARTARROW);
if (this.COLOR) arrowlinkElem.setAttribute('COLOR', this.COLOR);
if (this.ENDINCLINATION) arrowlinkElem.setAttribute('ENDINCLINATION', this.ENDINCLINATION);
if (this.ENDARROW) arrowlinkElem.setAttribute('ENDARROW', this.ENDARROW);
if (this.ID) arrowlinkElem.setAttribute('ID', this.ID);
if (this.STARTINCLINATION) arrowlinkElem.setAttribute('STARTINCLINATION', this.STARTINCLINATION);
return arrowlinkElem;
}
}

View File

@ -0,0 +1,20 @@
export default class Cloud {
protected COLOR: string;
getColor(): string {
return this.COLOR;
}
setColor(value: string): void {
this.COLOR = value;
}
toXml(document: Document): HTMLElement {
// Set node attributes
const cloudElem = document.createElement('cloud');
cloudElem.setAttribute('COLOR', this.COLOR);
return cloudElem;
}
}

View File

@ -0,0 +1,42 @@
export default class Edge {
protected COLOR: string;
protected STYLE: string;
protected WIDTH: string;
getColor(): string {
return this.COLOR;
}
getStyle(): string {
return this.STYLE;
}
getWidth(): string {
return this.WIDTH;
}
setColor(value: string): void {
this.COLOR = value;
}
setStyle(value: string): void {
this.STYLE = value;
}
setWidth(value: string): void {
this.WIDTH = value;
}
toXml(document: Document): HTMLElement {
// Set node attributes
const edgeElem = document.createElement('edge');
edgeElem.setAttribute('COLOR', this.COLOR);
if (this.STYLE) edgeElem.setAttribute('STYLE', this.STYLE);
if (this.WIDTH) edgeElem.setAttribute('WIDTH', this.WIDTH);
return edgeElem;
}
}

View File

@ -0,0 +1,56 @@
export default class Font {
protected BOLD?: string;
protected ITALIC?: string;
protected NAME?: string;
protected SIZE: string;
getBold(): string {
return this.BOLD;
}
getItalic(): string {
return this.ITALIC;
}
getName(): string {
return this.NAME;
}
getSize(): string {
return this.SIZE;
}
setBold(value: string): void {
this.BOLD = value;
}
setItalic(value: string): void {
this.ITALIC = value;
}
setName(value: string): void {
this.NAME = value;
}
setSize(value: string): void {
this.SIZE = value;
}
toXml(document: Document): HTMLElement {
const fontElem = document.createElement('font');
if (this.SIZE) {
fontElem.setAttribute('SIZE', this.SIZE);
} else {
fontElem.setAttribute('SIZE', '12');
}
if (this.BOLD) fontElem.setAttribute('BOLD', this.BOLD);
if (this.ITALIC) fontElem.setAttribute('ITALIC', this.ITALIC);
if (this.NAME) fontElem.setAttribute('NAME', this.NAME);
return fontElem;
}
}

View File

@ -0,0 +1,39 @@
import VersionNumber from './importer/VersionNumber';
export default {
LAST_SUPPORTED_FREEMIND_VERSION: '1.0.1',
SUPPORTED_FREEMIND_VERSION: new VersionNumber('1.0.1'),
CODE_VERSION: 'tango',
SECOND_LEVEL_TOPIC_HEIGHT: 25,
ROOT_LEVEL_TOPIC_HEIGHT: 25,
CENTRAL_TO_TOPIC_DISTANCE: 200,
TOPIC_TO_TOPIC_DISTANCE: 90,
FONT_SIZE_HUGE: 15,
FONT_SIZE_LARGE: 10,
FONT_SIZE_NORMAL: 8,
FONT_SIZE_SMALL: 6,
NODE_TYPE: 'NODE',
BOLD: 'bold',
ITALIC: 'italic',
EMPTY_FONT_STYLE: ';;;;;',
EMPTY_NOTE: '',
POSITION_LEFT: 'left',
POSITION_RIGHT: 'right',
};

View File

@ -0,0 +1,33 @@
import Parameters from './Parameters';
export default class Hook {
protected PARAMETERS: Parameters;
protected TEXT: string;
protected NAME: string;
getParameters(): Parameters {
return this.PARAMETERS;
}
getText(): string {
return this.TEXT;
}
getName(): string {
return this.NAME;
}
setParameters(value: Parameters): void {
this.PARAMETERS = value;
}
setText(value: string): void {
this.TEXT = value;
}
setName(value: string): void {
this.NAME = value;
}
}

View File

@ -0,0 +1,20 @@
export default class Icon {
protected BUILTIN: string;
getBuiltin(): string {
return this.BUILTIN;
}
setBuiltin(value: string): void {
this.BUILTIN = value;
}
toXml(document: Document): HTMLElement {
// Set node attributes
const iconElem = document.createElement('icon');
iconElem.setAttribute('BUILTIN', this.BUILTIN);
return iconElem;
}
}

View File

@ -0,0 +1,116 @@
import { createDocument } from '@wisemapping/core-js';
import Arrowlink from './Arrowlink';
import Cloud from './Cloud';
import Edge from './Edge';
import Font from './Font';
import Icon from './Icon';
import Node, { Choise } from './Node';
import Richcontent from './Richcontent';
export default class Map {
protected node: Node;
protected version: string;
getNode(): Node {
return this.node;
}
getVersion(): string {
return this.version;
}
setNode(value: Node) {
this.node = value;
}
setVesion(value: string) {
this.version = value;
}
toXml(): Document {
const document = createDocument();
// Set map attributes
const mapElem = document.createElement('map');
mapElem.setAttribute('version', this.version);
document.appendChild(mapElem);
// Create main node
const mainNode: Node = this.node;
mainNode.setCentralTopic(true);
const mainNodeElem = mainNode.toXml(document);
mapElem.appendChild(mainNodeElem);
const childNodes: Array<Choise> = mainNode.getArrowlinkOrCloudOrEdge();
childNodes.forEach((childNode: Choise) => {
const node = this.nodeToXml(childNode, mainNodeElem, document);
mainNodeElem.appendChild(node);
});
return document;
}
private nodeToXml(childNode: Choise, parentNode: HTMLElement, document: Document): HTMLElement {
if (childNode instanceof Node) {
childNode.setCentralTopic(false);
const childNodeXml = childNode.toXml(document);
parentNode.appendChild(childNodeXml);
const childrens = childNode.getArrowlinkOrCloudOrEdge();
if (childrens.length > 0) {
childrens.forEach((node: Choise) => {
const nodeXml = this.nodeToXml(node, childNodeXml, document);
childNodeXml.appendChild(nodeXml);
});
}
return childNodeXml;
}
if (childNode instanceof Font) {
const childNodeXml = childNode.toXml(document);
parentNode.appendChild(childNodeXml);
return childNodeXml;
}
if (childNode instanceof Edge) {
const childNodeXml = childNode.toXml(document);
parentNode.appendChild(childNodeXml);
return childNodeXml;
}
if (childNode instanceof Arrowlink) {
const childNodeXml = childNode.toXml(document);
parentNode.appendChild(childNodeXml);
return childNodeXml;
}
if (childNode instanceof Cloud) {
const childNodeXml = childNode.toXml(document);
parentNode.appendChild(childNodeXml);
return childNodeXml;
}
if (childNode instanceof Icon) {
const childNodeXml = childNode.toXml(document);
parentNode.appendChild(childNodeXml);
return childNodeXml;
}
if (childNode instanceof Richcontent) {
const childNodeXml = childNode.toXml(document);
parentNode.appendChild(childNodeXml);
return childNodeXml;
}
return parentNode;
}
}

View File

@ -0,0 +1,233 @@
import Arrowlink from './Arrowlink';
import Cloud from './Cloud';
import Edge from './Edge';
import Font from './Font';
import Hook from './Hook';
import Icon from './Icon';
import Richcontent from './Richcontent';
class Node {
protected arrowlinkOrCloudOrEdge: Array<Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | this>;
protected BACKGROUND_COLOR: string;
protected COLOR: string;
protected FOLDED: string;
protected ID: string;
protected LINK: string;
protected POSITION: string;
protected STYLE: string;
protected TEXT: string;
protected CREATED: string;
protected MODIFIED: string;
protected HGAP: string;
protected VGAP: string;
protected WCOORDS: string;
protected WORDER: string;
protected VSHIFT: string;
protected ENCRYPTED_CONTENT: string;
private centralTopic: boolean;
getArrowlinkOrCloudOrEdge(): Array<Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | Node> {
if (!this.arrowlinkOrCloudOrEdge) {
this.arrowlinkOrCloudOrEdge = new Array<Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | this>();
}
return this.arrowlinkOrCloudOrEdge;
}
getBackgorundColor(): string {
return this.BACKGROUND_COLOR;
}
getColor(): string {
return this.COLOR;
}
getFolded(): string {
return this.FOLDED;
}
getId(): string {
return this.ID;
}
getLink(): string {
return this.LINK;
}
getPosition(): string {
return this.POSITION;
}
getStyle(): string {
return this.STYLE;
}
getText(): string {
return this.TEXT;
}
getCreated(): string {
return this.CREATED;
}
getModified(): string {
return this.MODIFIED;
}
getHgap(): string {
return this.HGAP;
}
getVgap(): string {
return this.VGAP;
}
getWcoords(): string {
return this.WCOORDS;
}
getWorder(): string {
return this.WORDER;
}
getVshift(): string {
return this.VSHIFT;
}
getEncryptedContent(): string {
return this.ENCRYPTED_CONTENT;
}
getCentralTopic(): boolean {
return this.centralTopic;
}
setArrowlinkOrCloudOrEdge(value: Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | this): void {
this.getArrowlinkOrCloudOrEdge().push(value);
}
setBackgorundColor(value: string): void {
this.BACKGROUND_COLOR = value;
}
setColor(value: string): void {
this.COLOR = value;
}
setFolded(value: string): void {
this.FOLDED = value;
}
setId(value: string): void {
this.ID = value;
}
setLink(value: string): void {
this.LINK = value;
}
setPosition(value: string): void {
this.POSITION = value;
}
setStyle(value: string): void {
this.STYLE = value;
}
setText(value: string): void {
this.TEXT = value;
}
setCreated(value: string): void {
this.CREATED = value;
}
setModified(value: string): void {
this.MODIFIED = value;
}
setHgap(value: string): void {
this.HGAP = value;
}
setVgap(value: string): void {
this.VGAP = value;
}
setWcoords(value: string): void {
this.WCOORDS = value;
}
setWorder(value: string): void {
this.WORDER = value;
}
setVshift(value: string): void {
this.VSHIFT = value;
}
setEncryptedContent(value: string): void {
this.ENCRYPTED_CONTENT = value;
}
setCentralTopic(value: boolean): void {
this.centralTopic = value;
}
toXml(document: Document): HTMLElement {
// Set node attributes
const nodeElem = document.createElement('node');
if (this.centralTopic) {
if (this.ID) nodeElem.setAttribute('ID', this.ID);
if (this.TEXT) nodeElem.setAttribute('TEXT', this.TEXT);
if (this.BACKGROUND_COLOR) nodeElem.setAttribute('BACKGROUND_COLOR', this.BACKGROUND_COLOR);
if (this.COLOR) nodeElem.setAttribute('COLOR', this.COLOR);
if (this.TEXT) {
nodeElem.setAttribute('TEXT', this.TEXT);
} else {
nodeElem.setAttribute('TEXT', '');
}
return nodeElem;
}
if (this.ID) nodeElem.setAttribute('ID', this.ID);
if (this.POSITION) nodeElem.setAttribute('POSITION', this.POSITION);
if (this.STYLE) nodeElem.setAttribute('STYLE', this.STYLE);
if (this.BACKGROUND_COLOR) nodeElem.setAttribute('BACKGROUND_COLOR', this.BACKGROUND_COLOR);
if (this.COLOR) nodeElem.setAttribute('COLOR', this.COLOR);
if (this.TEXT) nodeElem.setAttribute('TEXT', this.TEXT);
if (this.LINK) nodeElem.setAttribute('LINK', this.LINK);
if (this.FOLDED) nodeElem.setAttribute('FOLDED', this.FOLDED);
if (this.CREATED) nodeElem.setAttribute('CREATED', this.CREATED);
if (this.MODIFIED) nodeElem.setAttribute('MODIFIED', this.MODIFIED);
if (this.HGAP) nodeElem.setAttribute('HGAP', this.HGAP);
if (this.VGAP) nodeElem.setAttribute('VGAP', this.VGAP);
if (this.WCOORDS) nodeElem.setAttribute('WCOORDS', this.WCOORDS);
if (this.WORDER) nodeElem.setAttribute('WORDER', this.WORDER);
if (this.VSHIFT) nodeElem.setAttribute('VSHIFT', this.VSHIFT);
if (this.ENCRYPTED_CONTENT) nodeElem.setAttribute('ENCRYPTED_CONTENT', this.ENCRYPTED_CONTENT);
return nodeElem;
}
}
export type Choise = Arrowlink | Cloud | Edge | Font | Hook | Icon | Richcontent | Node
export default Node;

View File

@ -0,0 +1,51 @@
import Arrowlink from './Arrowlink';
import Cloud from './Cloud';
import Edge from './Edge';
import Font from './Font';
import Hook from './Hook';
import Icon from './Icon';
import Richcontent from './Richcontent';
import Map from './Map';
import Node from './Node';
export default class ObjectFactory {
public createParameters(): void {
console.log('parameters');
}
public crateArrowlink(): Arrowlink {
return new Arrowlink();
}
public createCloud(): Cloud {
return new Cloud();
}
public createEdge(): Edge {
return new Edge();
}
public createFont(): Font {
return new Font();
}
public createHook(): Hook {
return new Hook();
}
public createIcon(): Icon {
return new Icon();
}
public createRichcontent(): Richcontent {
return new Richcontent();
}
public createMap(): Map {
return new Map();
}
public createNode(): Node {
return new Node();
}
}

View File

@ -0,0 +1,11 @@
export default class Parameters {
protected REMINDUSERAT: number;
getReminduserat(): number {
return this.REMINDUSERAT;
}
setReminduserat(value: number): void {
this.REMINDUSERAT = value;
}
}

View File

@ -0,0 +1,35 @@
export default class Richcontent {
protected html: string;
protected type: string;
getHtml(): string {
return this.html;
}
getType(): string {
return this.type;
}
setHtml(value: string): void {
this.html = value;
}
setType(value: string): void {
this.type = value;
}
toXml(document: Document): HTMLElement {
// Set node attributes
const richcontentElem = document.createElement('richcontent');
richcontentElem.setAttribute('TYPE', this.type);
if (this.html) {
const htmlElement: DocumentFragment = document.createRange().createContextualFragment(this.html);
richcontentElem.appendChild(htmlElement);
}
return richcontentElem;
}
}

View File

@ -0,0 +1,11 @@
export default class VersionNumber {
protected version: string;
constructor(version: string) {
this.version = version;
}
public getVersion(): string {
return this.version;
}
}

View File

@ -20,12 +20,14 @@ import ES from './es';
import EN from './en';
import DE from './de';
import FR from './fr';
import RU from './ru';
const Bundle = {
es: ES,
en: EN,
de: DE,
fr: FR,
ru: RU,
};
export default Bundle;

View File

@ -1,80 +0,0 @@
ZOOM_IN = 'Ansicht vergrößern',
ZOOM_OUT = 'Ansicht verkleinern',
TOPIC_SHAPE = 'Themen Gestaltung',
TOPIC_ADD = 'Thema hinzufügen',
TOPIC_DELETE = 'Thema löschen',
TOPIC_ICON = 'Symbol hinzufügen',
TOPIC_LINK = 'Verbindung hinzufügen',
TOPIC_RELATIONSHIP = 'Beziehung',
TOPIC_COLOR = 'Themenfarbe',
TOPIC_BORDER_COLOR = 'Thema Randfarbe',
TOPIC_NOTE = 'Notiz hinzufügen',
FONT_FAMILY = 'Schrifttyp',
FONT_SIZE = 'Schriftgröße',
FONT_BOLD = 'Fette Schrift',
FONT_ITALIC = 'Kursive Schrift',
UNDO = 'Rückgängig machen',
REDO = 'Wiederholen',
INSERT = 'Einfügen',
SAVE = 'Sichern',
NOTE = 'Notiz',
ADD_TOPIC = 'Thema hinzufügen',
LOADING = 'Laden ...',
EXPORT = 'Exportieren',
PRINT = 'Drucken',
PUBLISH = 'Publizieren',
COLLABORATE = 'Mitbenutzen',
HISTORY = 'Historie',
DISCARD_CHANGES = 'Änderungen verwerfen',
FONT_COLOR = 'Textfarbe',
SAVING = 'Sichern ...',
SAVE_COMPLETE = 'Sichern abgeschlossen',
ZOOM_IN_ERROR = 'Zoom zu hoch.',
ZOOM_ERROR = 'Es kann nicht weiter vergrößert bzw. verkelinert werden.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED = 'Thema konnte nicht angelegt werden. Bitte wählen Sie nur ein Thema aus.',
ONE_TOPIC_MUST_BE_SELECTED = 'Thema konnte nicht angelegt werden. Es muss ein Thema ausgewählt werden.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED_COLLAPSE = 'Kinderknoten können nicht eingefaltet werden. Es muss ein Thema ausgewäht werden.',
SAVE_COULD_NOT_BE_COMPLETED = 'Sichern wurde nicht abgeschlossen. Versuchen Sie es später nocheinmal.',
UNEXPECTED_ERROR_LOADING = 'E tut uns Leid, ein unerwarteter Fehler ist aufgetreten.\nVersuchen Sie, den Editor neu zu laden. Falls das Problem erneut auftritt, bitte kontaktieren Sie uns unter support@wisemapping.com.',
MAIN_TOPIC = 'Hauptthema',
SUB_TOPIC = 'Unterthema',
ISOLATED_TOPIC = 'Isoliertes Thema',
CENTRAL_TOPIC = 'Zentrales Thema',
SHORTCUTS = 'Tastaturkürzel',
ENTITIES_COULD_NOT_BE_DELETED = 'Konnte das Thema oder die Beziehung nicht löschen. Es muss mindest ein Eintrag ausgewählt sein.',
AT_LEAST_ONE_TOPIC_MUST_BE_SELECTED = 'Es muss mindestens ein Thema ausgewählt sein.',
CLIPBOARD_IS_EMPTY = 'Es gibt nichts zu kopieren. Die Zwischenablage ist leer.',
CENTRAL_TOPIC_CAN_NOT_BE_DELETED = 'Das zentrale Thema kann nicht gelöscht werden.',
RELATIONSHIP_COULD_NOT_BE_CREATED = 'Die Beziehung konnte nicht angelegt werden. Es muss erst ein Vater-Thema ausgewählt werden, um die Beziehung herzustellen.',
SELECTION_COPIED_TO_CLIPBOARD = 'Themen in der Zwischenablage',
WRITE_YOUR_TEXT_HERE = 'Schreiben Sie ihre Notiz hier ...',
REMOVE = 'Entfernen',
ACCEPT = 'Akzeptieren',
CANCEL = 'Abbrechen',
LINK = 'Verbindung',
OPEN_LINK = 'Öffne URL',
SESSION_EXPIRED = 'Ihre Sitzung ist abgelaufen, bitte melden Sie sich erneut an.',
URL_ERROR = 'URL nicht gültig',
ACTION = 'Aktion',
CREATE_SIBLING_TOPIC = 'Erzeuge ein Schwester Thema',
CREATE_CHILD_TOPIC = 'Eryeuge ein Unterthema',
DELETE_TOPIC = 'Lösche Thema',
EDIT_TOPIC_TEXT = 'Editiere Thematext',
JUST_START_TYPING = 'Einfach mit der Eingabe beginnen',
CANCEL_TEXT_CHANGES = 'Textänderungen abbrechen',
TOPIC_NAVIGATION = 'Themen Navigation',
ARROW_KEYS = 'Pfeiltasten',
SELECT_MULTIPLE_NODES = 'Wähle mehrfache Knoten aus',
UNDO_EDITION = 'Änderungen rückgängig machen',
REDO_EDITION = 'Änderung nochmal ausführen',
SELECT_ALL_TOPIC = 'Wähle alle Themen aus',
CHANGE_TEXT_BOLD = 'Ändere Text in fette Schrift',
SAVE_CHANGES = 'Änderungen sichern',
CHANGE_TEXT_ITALIC = 'Ändere Text in kursive Schrift',
DESELECT_ALL_TOPIC = 'Deselektiere alle Themen',
COLLAPSE_CHILDREN = 'Kindknoten zusammenklappen',
KEYBOARD_SHORTCUTS_MSG = 'Tastenkürzel helfen Zeit zu sparen und erlauben die Arbeit nur mit der Tatstatur, s.d. Sie niemals die Hand von der Tastatur nehmen müßen, um die Maus zu bedienen.',
COPY_AND_PASTE_TOPICS = 'Kopieren und Einsetzen von Themen',
MULTIPLE_LINES = 'Füge mehrer Textzeilen hinzu',
BACK_TO_MAP_LIST = 'Zurück zur Kartenliste',
KEYBOARD_SHOTCUTS = 'Tastaturkürzel',

View File

@ -1,80 +0,0 @@
ZOOM_IN = 'Zoom In',
ZOOM_OUT = 'Zoom Out',
TOPIC_SHAPE = 'Topic Shape',
TOPIC_ADD = 'Add Topic',
TOPIC_DELETE = 'Delete Topic',
TOPIC_ICON = 'Add Icon',
TOPIC_LINK = 'Add Link',
TOPIC_RELATIONSHIP = 'Relationship',
TOPIC_COLOR = 'Topic Color',
TOPIC_BORDER_COLOR = 'Topic Border Color',
TOPIC_NOTE = 'Add Note',
FONT_FAMILY = 'Font Type',
FONT_SIZE = 'Text Size',
FONT_BOLD = 'Text Bold',
FONT_ITALIC = 'Text Italic',
UNDO = 'Undo',
REDO = 'Redo',
INSERT = 'Insert',
SAVE = 'Save',
NOTE = 'Note',
ADD_TOPIC = 'Add Topic',
LOADING = 'Loading ...',
EXPORT = 'Export',
PRINT = 'Print',
PUBLISH = 'Publish',
COLLABORATE = 'Share',
HISTORY = 'History',
DISCARD_CHANGES = 'Discard Changes',
FONT_COLOR = 'Text Color',
SAVING = 'Saving ...',
SAVE_COMPLETE = 'Save Complete',
ZOOM_IN_ERROR = 'Zoom too high.',
ZOOM_ERROR = 'No more zoom can be applied.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED = 'Could not create a topic. Only one topic must be selected.',
ONE_TOPIC_MUST_BE_SELECTED = 'Could not create a topic. One topic must be selected.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED_COLLAPSE = 'Children can not be collapsed. One topic must be selected.',
SAVE_COULD_NOT_BE_COMPLETED = 'Save could not be completed, please try again latter.',
UNEXPECTED_ERROR_LOADING = "We're sorry, an unexpected error has occurred.\nTry again reloading the editor.If the problem persists, contact us to support@wisemapping.com.",
MAIN_TOPIC = 'Main Topic',
SUB_TOPIC = 'Sub Topic',
ISOLATED_TOPIC = 'Isolated Topic',
CENTRAL_TOPIC = 'Central Topic',
SHORTCUTS = 'Keyboard Shortcuts',
ENTITIES_COULD_NOT_BE_DELETED = 'Could not delete topic or relation. At least one map entity must be selected.',
AT_LEAST_ONE_TOPIC_MUST_BE_SELECTED = 'At least one topic must be selected.',
CLIPBOARD_IS_EMPTY = 'Nothing to copy. Clipboard is empty.',
CENTRAL_TOPIC_CAN_NOT_BE_DELETED = 'Central topic can not be deleted.',
RELATIONSHIP_COULD_NOT_BE_CREATED = 'Relationship could not be created. A parent relationship topic must be selected first.',
SELECTION_COPIED_TO_CLIPBOARD = 'Topics copied to the clipboard',
WRITE_YOUR_TEXT_HERE = 'Write your note here ...',
REMOVE = 'Remove',
ACCEPT = 'Accept',
CANCEL = 'Cancel',
LINK = 'Link',
OPEN_LINK = 'Open URL',
SESSION_EXPIRED = 'Your session has expired, please log-in again.',
URL_ERROR = 'URL not valid',
ACTION = 'Action',
CREATE_SIBLING_TOPIC = 'Create Sibling Topic',
CREATE_CHILD_TOPIC = 'Create Child Topic',
DELETE_TOPIC = 'Delete Topic',
EDIT_TOPIC_TEXT = 'Edit Topic Text',
JUST_START_TYPING = 'Just start typing',
CANCEL_TEXT_CHANGES = 'Cancel Text Changes',
TOPIC_NAVIGATION = 'Topic Navigation',
ARROW_KEYS = 'Arrow Keys',
SELECT_MULTIPLE_NODES = 'Select Multiple Nodes',
UNDO_EDITION = 'Undo Edition',
REDO_EDITION = 'Redo Edition',
SELECT_ALL_TOPIC = 'Select All Topic',
CHANGE_TEXT_BOLD = 'Change Text Bold Type',
SAVE_CHANGES = 'Save Changes',
CHANGE_TEXT_ITALIC = 'Change Text Italic',
DESELECT_ALL_TOPIC = 'Deselect All Topic',
COLLAPSE_CHILDREN = 'Collapse Children',
KEYBOARD_SHORTCUTS_MSG = 'Keyboard shortcuts can help you save time by allowing you to never take your hands off the keyboard to use the mouse.',
COPY_AND_PASTE_TOPICS = 'Copy and Paste Topics',
MULTIPLE_LINES = 'Add multiple text lines',
BACK_TO_MAP_LIST = 'Back to Maps List',
KEYBOARD_SHOTCUTS = 'Keyboard Shorcuts',

View File

@ -1,80 +0,0 @@
ZOOM_IN = 'Acercar',
ZOOM_OUT = 'Alejar',
TOPIC_SHAPE = 'Forma del Tópico',
TOPIC_ADD = 'Agregar Tópico',
TOPIC_DELETE = 'Borrar Tópico',
TOPIC_ICON = 'Agregar Icono',
TOPIC_LINK = 'Agregar Enlace',
TOPIC_RELATIONSHIP = 'Relación',
TOPIC_COLOR = 'Color Tópico',
TOPIC_BORDER_COLOR = 'Color del Borde',
TOPIC_NOTE = 'Agregar Nota',
FONT_FAMILY = 'Tipo de Fuente',
FONT_SIZE = 'Tamaño de Texto',
FONT_BOLD = 'Negrita',
FONT_ITALIC = 'Italica',
UNDO = 'Rehacer',
REDO = 'Deshacer',
INSERT = 'Insertar',
SAVE = 'Guardar',
NOTE = 'Nota',
ADD_TOPIC = 'Agregar Tópico',
LOADING = 'Cargando ...',
EXPORT = 'Exportar',
PRINT = 'Imprimir',
PUBLISH = 'Publicar',
COLLABORATE = 'Compartir',
HISTORY = 'Historial',
DISCARD_CHANGES = 'Descartar Cambios',
FONT_COLOR = 'Color de Texto',
SAVING = 'Grabando ...',
SAVE_COMPLETE = 'Grabado Completo',
ZOOM_IN_ERROR = 'El zoom es muy alto.',
ZOOM_ERROR = 'No es posible aplicar mas zoom.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED = 'No ha sido posible crear un nuevo tópico. Sólo un tópico debe ser seleccionado.',
ONE_TOPIC_MUST_BE_SELECTED = 'No ha sido posible crear un nuevo tópico. Al menos un tópico debe ser seleccionado.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED_COLLAPSE = 'Tópicos hijos no pueden ser colapsados. Sólo un tópico debe ser seleccionado.',
SAVE_COULD_NOT_BE_COMPLETED = 'Grabación no pudo ser completada. Intentelo mas tarde.',
UNEXPECTED_ERROR_LOADING = 'Lo sentimos, un error inesperado ha ocurrido. Intentelo nuevamente recargando el editor. Si el problema persiste, contactenos a support@wisemapping.com.',
MAIN_TOPIC = 'Tópico Principal',
SUB_TOPIC = 'Tópico Secundario',
ISOLATED_TOPIC = 'Tópico Aislado',
CENTRAL_TOPIC = 'Tópico Central',
SHORTCUTS = 'Accesos directos',
ENTITIES_COULD_NOT_BE_DELETED = 'El tópico o la relación no pudo ser borrada. Debe selecionar al menos una.',
AT_LEAST_ONE_TOPIC_MUST_BE_SELECTED = 'Al menos un tópico debe ser seleccionado.',
CLIPBOARD_IS_EMPTY = 'Nada que copiar. Clipboard está vacio.',
CENTRAL_TOPIC_CAN_NOT_BE_DELETED = 'El tópico central no puede ser borrado.',
RELATIONSHIP_COULD_NOT_BE_CREATED = 'La relación no pudo ser creada. Una relación padre debe ser seleccionada primero.',
SELECTION_COPIED_TO_CLIPBOARD = 'Tópicos copiados al clipboard',
WRITE_YOUR_TEXT_HERE = 'Escribe tu nota aquí ...',
REMOVE = 'Borrar',
ACCEPT = 'Aceptar',
CANCEL = 'Cancelar',
LINK = 'Enlace',
OPEN_LINK = 'Abrir Enlace',
SESSION_EXPIRED = 'Su session ha expirado. Por favor, ingrese nuevamente.',
URL_ERROR = 'URL no válida',
ACTION = 'Acción',
CREATE_SIBLING_TOPIC = 'Agregar Tópico Hermano',
CREATE_CHILD_TOPIC = 'Agregar Tópico Hijo',
DELETE_TOPIC = 'Borrar Tópico',
EDIT_TOPIC_TEXT = 'Editar Texto de Tópico',
JUST_START_TYPING = 'Comenza a escribir',
CANCEL_TEXT_CHANGES = 'Cancelar Edición de Texto',
TOPIC_NAVIGATION = 'Navegación Entre Tópicos',
ARROW_KEYS = 'Flechas Del Cursor',
SELECT_MULTIPLE_NODES = 'Selecciónar Multiples Tópicos',
UNDO_EDITION = 'Revertir Cambios',
REDO_EDITION = 'Rehacer Cambios',
SELECT_ALL_TOPIC = 'Seleccionar Todos los Tópicos',
CHANGE_TEXT_BOLD = 'Cambiar Texto a Negrita',
SAVE_CHANGES = 'Guardar los Cambios',
CHANGE_TEXT_ITALIC = 'Cambiar Texto a Italica',
DESELECT_ALL_TOPIC = 'Revertir Selección de Tópicos',
COLLAPSE_CHILDREN = 'Colapsar Hijos',
KEYBOARD_SHORTCUTS_MSG = 'Los accesos directos pueden ayudarte a salvar tiempo permitiéndote no sacar las manos del teclado para usar el mouse.',
COPY_AND_PASTE_TOPICS = 'Copier et coller les noeuds',
MULTIPLE_LINES = 'Ajouter plusieurs lignes de texte',
BACK_TO_MAP_LIST = 'Volver a la lista de mapas',
KEYBOARD_SHOTCUTS = 'Métodos abreviados de teclado',

View File

@ -1,80 +0,0 @@
ZOOM_IN = 'Agrandir affichage',
ZOOM_OUT = 'Réduire affichage',
TOPIC_SHAPE = 'Forme du noeud',
TOPIC_ADD = 'Ajouter un noeud',
TOPIC_DELETE = 'Supprimer le noeud',
TOPIC_ICON = 'Ajouter une icône',
TOPIC_LINK = 'Ajouter un lien',
TOPIC_RELATIONSHIP = 'Relation du noeud',
TOPIC_COLOR = 'Couleur du noeud',
TOPIC_BORDER_COLOR = 'Couleur de bordure du noeud',
TOPIC_NOTE = 'Ajouter une note',
FONT_FAMILY = 'Type de police',
FONT_SIZE = 'Taille de police',
FONT_BOLD = 'Caractères gras',
FONT_ITALIC = 'Caractères italiques',
UNDO = 'Annuler',
REDO = 'Refaire',
INSERT = 'Insérer',
SAVE = 'Enregistrer',
NOTE = 'Note',
ADD_TOPIC = 'Ajouter un noeud',
LOADING = 'Chargement ...',
EXPORT = 'Exporter',
PRINT = 'Imprimer',
PUBLISH = 'Publier',
COLLABORATE = 'Partager',
HISTORY = 'Historique',
DISCARD_CHANGES = 'Annuler les changements',
FONT_COLOR = 'Couleur de police',
SAVING = 'Enregistrement ...',
SAVE_COMPLETE = 'Enregistrement terminé',
ZOOM_IN_ERROR = 'Zoom trop grand.',
ZOOM_ERROR = 'Impossible de zoomer plus.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED = 'Impossible de créer un noeud. Un seul noeud doit être sélectionné.',
ONE_TOPIC_MUST_BE_SELECTED = 'Impossible de créer un noeud. Un noeud parent doit être sélectionné au préalable.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED_COLLAPSE = 'Un noeud enfant ne peut pas être réduit. Un noeud doit être sélectionné.',
SAVE_COULD_NOT_BE_COMPLETED = 'Enregistrement impossible. Essayer ultérieurement.',
UNEXPECTED_ERROR_LOADING = "Nous sommes désolés, une erreur vient de survenir.\nEssayez de recharger l'éditeur. Si le problème persiste, contactez-nous \= support@wisemapping.com.",
MAIN_TOPIC = 'Noeud titre principal',
SUB_TOPIC = 'Noeud sous-titre',
ISOLATED_TOPIC = 'Noeud isolé',
CENTRAL_TOPIC = 'Noeud racine',
SHORTCUTS = 'Raccourcis clavier',
ENTITIES_COULD_NOT_BE_DELETED = "Impossible d'effacer un noeud ou une relation. Au moins un objet de la carte doit être sélectionné.",
AT_LEAST_ONE_TOPIC_MUST_BE_SELECTED = 'Au moins un objet de la carte doit être sélectionné.',
CLIPBOARD_IS_EMPTY = 'Rien à copier. Presse-papier vide.',
CENTRAL_TOPIC_CAN_NOT_BE_DELETED = 'Le noeud racine ne peut pas être effacé.',
RELATIONSHIP_COULD_NOT_BE_CREATED = 'Impossible de créer relation. Un noeud parent doit être sélectionné au préalable.',
SELECTION_COPIED_TO_CLIPBOARD = 'Noeuds sélectionnés copiés dans le presse-papiers.',
WRITE_YOUR_TEXT_HERE = 'Écrivez votre texte ici ...',
REMOVE = 'Supprimer',
ACCEPT = 'Accepter',
CANCEL = 'Annuler',
LINK = 'Lien',
OPEN_LINK = 'Ouvrir le lien',
SESSION_EXPIRED = 'Votre session a expiré, veuillez vous reconnecter.',
URL_ERROR = 'URL non valide',
ACTION = 'Action',
CREATE_SIBLING_TOPIC = 'Créer noeud même niveau',
CREATE_CHILD_TOPIC = 'Créer noeud enfant',
DELETE_TOPIC = 'Détruire noeud ',
EDIT_TOPIC_TEXT = 'Editer texte du noeud',
JUST_START_TYPING = 'Commencer saisie',
CANCEL_TEXT_CHANGES = 'Annuler changement texte',
TOPIC_NAVIGATION = 'Navigation sur les noeuds',
ARROW_KEYS = 'Touches flèches',
SELECT_MULTIPLE_NODES = 'Selection multiple de noeuds',
UNDO_EDITION = 'Annuler édition',
REDO_EDITION = 'Refaire édition',
SELECT_ALL_TOPIC = 'Sélection tous noeuds',
CHANGE_TEXT_BOLD = 'Caractères en gras',
SAVE_CHANGES = 'Enregistrer changements',
CHANGE_TEXT_ITALIC = 'Caractères en italique',
DESELECT_ALL_TOPIC = 'Deselection tous noeuds',
COLLAPSE_CHILDREN = 'Fermer enfants',
KEYBOARD_SHORTCUTS_MSG = 'Les raccourcis clavier vous font gagner du temps, en vous permettant de garder les mains sur le clavier sans utiliser la souris.',
COPY_AND_PASTE_TOPICS = 'Copier et coller les noeuds',
MULTIPLE_LINES = 'Ajouter plusieurs lignes de texte',
BACK_TO_MAP_LIST = 'Retour à la liste des cartes',
KEYBOARD_SHOTCUTS = "Raccourcis clavier",

View File

@ -0,0 +1,84 @@
const EN = {
ZOOM_IN: 'Zoom In',
ZOOM_OUT: 'Zoom Out',
TOPIC_SHAPE: 'Topic Shape',
TOPIC_ADD: 'Add Topic',
TOPIC_DELETE: 'Delete Topic',
TOPIC_ICON: 'Add Icon',
TOPIC_LINK: 'Add Link',
TOPIC_RELATIONSHIP: 'Relationship',
TOPIC_COLOR: 'Topic Color',
TOPIC_BORDER_COLOR: 'Topic Border Color',
TOPIC_NOTE: 'Add Note',
FONT_FAMILY: 'Font Type',
FONT_SIZE: 'Text Size',
FONT_BOLD: 'Text Bold',
FONT_ITALIC: 'Text Italic',
UNDO: 'Undo',
REDO: 'Redo',
INSERT: 'Insert',
SAVE: 'Save',
NOTE: 'Note',
ADD_TOPIC: 'Add Topic',
LOADING: 'Loading ...',
EXPORT: 'Export',
PRINT: 'Print',
PUBLISH: 'Publish',
COLLABORATE: 'Share',
HISTORY: 'History',
DISCARD_CHANGES: 'Discard Changes',
FONT_COLOR: 'Text Color',
SAVING: 'Saving ...',
SAVE_COMPLETE: 'Save Complete',
ZOOM_IN_ERROR: 'Zoom too high.',
ZOOM_ERROR: 'No more zoom can be applied.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED: 'Could not create a topic. Only one topic must be selected.',
ONE_TOPIC_MUST_BE_SELECTED: 'Could not create a topic. One topic must be selected.',
ONLY_ONE_TOPIC_MUST_BE_SELECTED_COLLAPSE: 'Children can not be collapsed. One topic must be selected.',
SAVE_COULD_NOT_BE_COMPLETED: 'Save could not be completed, please try again latter.',
UNEXPECTED_ERROR_LOADING: "We're sorry, an unexpected error has occurred.\nTry again reloading the editor.If the problem persists, contact us to support@wisemapping.com.",
MAIN_TOPIC: 'Main Topic',
SUB_TOPIC: 'Sub Topic',
ISOLATED_TOPIC: 'Isolated Topic',
CENTRAL_TOPIC: 'Central Topic',
SHORTCUTS: 'Keyboard Shortcuts',
ENTITIES_COULD_NOT_BE_DELETED: 'Could not delete topic or relation. At least one map entity must be selected.',
AT_LEAST_ONE_TOPIC_MUST_BE_SELECTED: 'At least one topic must be selected.',
CLIPBOARD_IS_EMPTY: 'Nothing to copy. Clipboard is empty.',
CENTRAL_TOPIC_CAN_NOT_BE_DELETED: 'Central topic can not be deleted.',
RELATIONSHIP_COULD_NOT_BE_CREATED: 'Relationship could not be created. A parent relationship topic must be selected first.',
SELECTION_COPIED_TO_CLIPBOARD: 'Topics copied to the clipboard',
WRITE_YOUR_TEXT_HERE: 'Write your note here ...',
REMOVE: 'Remove',
ACCEPT: 'Accept',
CANCEL: 'Cancel',
LINK: 'Link',
OPEN_LINK: 'Open URL',
SESSION_EXPIRED: 'Your session has expired, please log-in again.',
URL_ERROR: 'URL not valid',
ACTION: 'Action',
CREATE_SIBLING_TOPIC: 'Create Sibling Topic',
CREATE_CHILD_TOPIC: 'Create Child Topic',
DELETE_TOPIC: 'Delete Topic',
EDIT_TOPIC_TEXT: 'Edit Topic Text',
JUST_START_TYPING: 'Just start typing',
CANCEL_TEXT_CHANGES: 'Cancel Text Changes',
TOPIC_NAVIGATION: 'Topic Navigation',
ARROW_KEYS: 'Arrow Keys',
SELECT_MULTIPLE_NODES: 'Select Multiple Nodes',
UNDO_EDITION: 'Undo Edition',
REDO_EDITION: 'Redo Edition',
SELECT_ALL_TOPIC: 'Select All Topic',
CHANGE_TEXT_BOLD: 'Change Text Bold Type',
SAVE_CHANGES: 'Save Changes',
CHANGE_TEXT_ITALIC: 'Change Text Italic',
DESELECT_ALL_TOPIC: 'Deselect All Topic',
COLLAPSE_CHILDREN: 'Collapse Children',
KEYBOARD_SHORTCUTS_MSG: 'Keyboard shortcuts can help you save time by allowing you to never take your hands off the keyboard to use the mouse.',
COPY_AND_PASTE_TOPICS: 'Copy and Paste Topics',
MULTIPLE_LINES: 'Add multiple text lines',
BACK_TO_MAP_LIST: 'Back to Maps List',
KEYBOARD_SHOTCUTS: 'Keyboard Shorcuts',
};
export default EN;

View File

@ -18,6 +18,7 @@
import { $assert, $defined } from '@wisemapping/core-js';
import PositionType from '../PositionType';
import SizeType from '../SizeType';
import ChildrenSorterStrategy from './ChildrenSorterStrategy';
class Node {
private _id: number;
@ -25,14 +26,14 @@ class Node {
// eslint-disable-next-line no-use-before-define
_parent: Node;
private _sorter: any;
private _sorter: ChildrenSorterStrategy;
private _properties;
// eslint-disable-next-line no-use-before-define
_children: Node[];
constructor(id: number, size: SizeType, position, sorter) {
constructor(id: number, size: SizeType, position: PositionType, sorter: ChildrenSorterStrategy) {
$assert(typeof id === 'number' && Number.isFinite(id), 'id can not be null');
$assert(size, 'size can not be null');
$assert(position, 'position can not be null');

View File

@ -24,7 +24,6 @@ class RootedTreeSet {
protected _children: Node[];
constructor() {
this._rootNodes = [];
}
@ -56,10 +55,9 @@ class RootedTreeSet {
*/
add(node: Node) {
$assert(node, 'node can not be null');
$assert(
!this.find(node.getId(), false),
`node already exits with this id. Id:${node.getId()}: ${this.dump()}`,
);
if (this.find(node.getId(), false)) {
throw new Error(`node already exits with this id. Id:${node.getId()}: ${this.dump()}`);
}
$assert(!node._children, 'node already added');
this._rootNodes.push(this._decodate(node));
}
@ -131,10 +129,11 @@ class RootedTreeSet {
break;
}
}
$assert(
validate ? result : true,
`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`,
);
if (validate && !result) {
throw new Error(`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`);
}
return result;
}
@ -293,7 +292,7 @@ class RootedTreeSet {
}
}
_plot(canvas, node: Node, root?) {
_plot(canvas, node: Node) {
const children = this.getChildren(node);
const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2;
const cy = node.getPosition().y + canvas.height / 2 - node.getSize().height / 2;

View File

@ -70,11 +70,6 @@ class NodeModel extends INodeModel {
return this._features;
}
/**
* @param feature
* @throws will throw an error if feature is null or undefined
* @throws will throw an error if the feature could not be removed
*/
removeFeature(feature: FeatureModel): void {
$assert(feature, 'feature can not be null');
const size = this._features.length;
@ -127,11 +122,8 @@ class NodeModel extends INodeModel {
return this._properties[key];
}
/**
* @return {mindplot.model.NodeModel} an identical clone of the NodeModel
*/
clone(): NodeModel {
const result = new NodeModel(this.getType(), this._mindmap, -1);
const result = new NodeModel(this.getType(), this._mindmap);
result._children = this._children.map((node) => {
const cnode = node.clone() as NodeModel;
cnode._parent = result;
@ -143,12 +135,8 @@ class NodeModel extends INodeModel {
return result;
}
/**
* Similar to clone, assign new id to the elements ...
* @return {mindplot.model.NodeModel}
*/
deepCopy(): NodeModel {
const result = new NodeModel(this.getType(), this._mindmap, -1);
const result = new NodeModel(this.getType(), this._mindmap);
result._children = this._children.map((node) => {
const cnode = (node as NodeModel).deepCopy();
cnode._parent = result;
@ -163,20 +151,12 @@ class NodeModel extends INodeModel {
return result;
}
/**
* @param {mindplot.model.NodeModel} child
* @throws will throw an error if child is null, undefined or not a NodeModel object
*/
append(child: NodeModel): void {
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object');
this._children.push(child);
child._parent = this;
}
/**
* @param {mindplot.model.NodeModel} child
* @throws will throw an error if child is null, undefined or not a NodeModel object
*/
removeChild(child: NodeModel): void {
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.');
this._children = this._children.filter((c) => c !== child);

View File

@ -67,7 +67,7 @@ class RelationshipModel {
return this._id;
}
getLineType() {
getLineType(): number {
return this._lineType;
}

View File

@ -20,7 +20,7 @@ import { Mindmap } from '../..';
import NodeModel from '../model/NodeModel';
import ModelCodeName from './ModelCodeName';
import XMLMindmapSerializer from './XMLMindmapSerializer';
import XMLSerializerPela from './XMLSerializerPela';
import XMLSerializerPela from './XMLSerializerTango';
class Beta2PelaMigrator implements XMLMindmapSerializer {
private _betaSerializer: XMLMindmapSerializer;

View File

@ -72,7 +72,7 @@ class Pela2TangoMigrator implements XMLMindmapSerializer {
}
}
private _fixPosition(mindmap: Mindmap) {
private _fixPosition(mindmap: Mindmap): void {
// Position was not required in previous versions. Try to synthesize one .
const centralNode = mindmap.getBranches()[0];
const children = centralNode.getChildren();
@ -83,7 +83,7 @@ class Pela2TangoMigrator implements XMLMindmapSerializer {
}
}
_fixNodePosition(node: NodeModel, parentPosition: {x:number, y:number}) {
private _fixNodePosition(node: NodeModel, parentPosition: { x: number, y: number }): void {
// Position was not required in previous versions. Try to synthesize one .
let position = node.getPosition();
if (!position) {

View File

@ -20,7 +20,6 @@ import ModelCodeName from './ModelCodeName';
import Beta2PelaMigrator from './Beta2PelaMigrator';
import Pela2TangoMigrator from './Pela2TangoMigrator';
import XMLSerializerBeta from './XMLSerializerBeta';
import XMLSerializerPela from './XMLSerializerPela';
import XMLSerializerTango from './XMLSerializerTango';
import Mindmap from '../model/Mindmap';
import XMLMindmapSerializer from './XMLMindmapSerializer';
@ -35,7 +34,7 @@ const codeToSerializer: { codeName: string, serializer, migrator }[] = [
},
{
codeName: ModelCodeName.PELA,
serializer: XMLSerializerPela,
serializer: XMLSerializerTango,
migrator: Beta2PelaMigrator,
},
{

View File

@ -1,517 +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.
*/
import { $assert, $defined, createDocument } from '@wisemapping/core-js';
import { Point } from '@wisemapping/web2d';
import Mindmap from '../model/Mindmap';
import { TopicShape } from '../model/INodeModel';
import ConnectionLine from '../ConnectionLine';
import FeatureModelFactory from '../model/FeatureModelFactory';
import NodeModel from '../model/NodeModel';
import RelationshipModel from '../model/RelationshipModel';
import XMLMindmapSerializer from './XMLMindmapSerializer';
import FeatureType from '../model/FeatureType';
class XMLSerializerPela implements XMLMindmapSerializer {
private static MAP_ROOT_NODE = 'map';
private _idsMap: Record<number, Element>;
toXML(mindmap: Mindmap): Document {
$assert(mindmap, 'Can not save a null mindmap');
const document = createDocument();
// Store map attributes ...
const mapElem = document.createElement('map');
const name = mindmap.getId();
if ($defined(name)) {
mapElem.setAttribute('name', this._rmXmlInv(name));
}
const version = mindmap.getVersion();
if ($defined(version)) {
mapElem.setAttribute('version', version);
}
document.appendChild(mapElem);
// Create branches ...
const topics = mindmap.getBranches();
topics.forEach((topic) => {
const topicDom = this._topicToXML(document, topic);
mapElem.appendChild(topicDom);
});
// Create Relationships
const relationships = mindmap.getRelationships();
relationships.forEach((relationship) => {
if (
mindmap.findNodeById(relationship.getFromNode()) !== null
&& mindmap.findNodeById(relationship.getToNode()) !== null
) {
// Isolated relationships are not persisted ....
const relationDom = XMLSerializerPela._relationshipToXML(document, relationship);
mapElem.appendChild(relationDom);
}
});
return document;
}
protected _topicToXML(document: Document, topic: NodeModel) {
const parentTopic = document.createElement('topic');
// Set topic attributes...
if (topic.getType() === 'CentralTopic') {
parentTopic.setAttribute('central', 'true');
} else {
const pos = topic.getPosition();
parentTopic.setAttribute('position', `${pos.x},${pos.y}`);
const order = topic.getOrder();
if (typeof order === 'number' && Number.isFinite(order)) {
parentTopic.setAttribute('order', order.toString());
}
}
const text = topic.getText();
if ($defined(text)) {
this._noteTextToXML(document, parentTopic, text);
}
const shape = topic.getShapeType();
if ($defined(shape)) {
parentTopic.setAttribute('shape', shape);
if (shape === TopicShape.IMAGE) {
const size = topic.getImageSize();
parentTopic.setAttribute(
'image',
`${size.width},${size.height}:${topic.getImageUrl()}`,
);
}
}
if (topic.areChildrenShrunken() && topic.getType() !== 'CentralTopic') {
parentTopic.setAttribute('shrink', 'true');
}
// Font properties ...
const id = topic.getId();
parentTopic.setAttribute('id', id.toString());
let font = '';
const fontFamily = topic.getFontFamily();
font += `${fontFamily || ''};`;
const fontSize = topic.getFontSize();
font += `${fontSize || ''};`;
const fontColor = topic.getFontColor();
font += `${fontColor || ''};`;
const fontWeight = topic.getFontWeight();
font += `${fontWeight || ''};`;
const fontStyle = topic.getFontStyle();
font += `${fontStyle || ''};`;
if (
$defined(fontFamily)
|| $defined(fontSize)
|| $defined(fontColor)
|| $defined(fontWeight)
|| $defined(fontStyle)
) {
parentTopic.setAttribute('fontStyle', font);
}
const bgColor = topic.getBackgroundColor();
if ($defined(bgColor)) {
parentTopic.setAttribute('bgColor', bgColor);
}
const brColor = topic.getBorderColor();
if ($defined(brColor)) {
parentTopic.setAttribute('brColor', brColor);
}
const metadata = topic.getMetadata();
if ($defined(metadata)) {
parentTopic.setAttribute('metadata', metadata);
}
// Serialize features ...
const features = topic.getFeatures();
features.forEach((feature) => {
const featureType = feature.getType();
const featureDom = document.createElement(featureType);
const attributes = feature.getAttributes();
const attributesKeys = Object.keys(attributes);
for (let attrIndex = 0; attrIndex < attributesKeys.length; attrIndex++) {
const key = attributesKeys[attrIndex];
const value = attributes[key];
if (key === 'text') {
const cdata = document.createCDATASection(this._rmXmlInv(value));
featureDom.appendChild(cdata);
} else {
featureDom.setAttribute(key, this._rmXmlInv(value));
}
}
parentTopic.appendChild(featureDom);
});
// CHILDREN TOPICS
const childTopics = topic.getChildren();
childTopics.forEach((childTopic) => {
const childDom = this._topicToXML(document, childTopic);
parentTopic.appendChild(childDom);
});
return parentTopic;
}
protected _noteTextToXML(document: Document, elem: Element, text: string) {
if (text.indexOf('\n') === -1) {
elem.setAttribute('text', this._rmXmlInv(text));
} else {
const textDom = document.createElement('text');
const cdata = document.createCDATASection(this._rmXmlInv(text));
textDom.appendChild(cdata);
elem.appendChild(textDom);
}
}
static _relationshipToXML(document: Document, relationship: RelationshipModel) {
const result = document.createElement('relationship');
result.setAttribute('srcTopicId', relationship.getFromNode().toString());
result.setAttribute('destTopicId', relationship.getToNode().toString());
const lineType = relationship.getLineType();
result.setAttribute('lineType', lineType.toString());
if (lineType === ConnectionLine.CURVED || lineType === ConnectionLine.SIMPLE_CURVED) {
if ($defined(relationship.getSrcCtrlPoint())) {
const srcPoint = relationship.getSrcCtrlPoint();
result.setAttribute(
'srcCtrlPoint',
`${Math.round(srcPoint.x)},${Math.round(srcPoint.y)}`,
);
}
if ($defined(relationship.getDestCtrlPoint())) {
const destPoint = relationship.getDestCtrlPoint();
result.setAttribute(
'destCtrlPoint',
`${Math.round(destPoint.x)},${Math.round(destPoint.y)}`,
);
}
}
result.setAttribute('endArrow', String(relationship.getEndArrow()));
result.setAttribute('startArrow', String(relationship.getStartArrow()));
return result;
}
/**
* @param dom
* @param mapId
* @throws will throw an error if dom is null or undefined
* @throws will throw an error if mapId is null or undefined
* @throws will throw an error if the document element is not consistent with a wisemap's root
* element
*/
loadFromDom(dom: Document, mapId: string) {
$assert(dom, 'dom can not be null');
$assert(mapId, 'mapId can not be null');
const rootElem = dom.documentElement;
// Is a wisemap?.
$assert(
rootElem.tagName === XMLSerializerPela.MAP_ROOT_NODE,
`This seem not to be a map document. Found tag: ${rootElem.tagName}`,
);
this._idsMap = {};
// Start the loading process ...
const version = rootElem.getAttribute('version') || 'pela';
const mindmap = new Mindmap(mapId, version);
// Add all the topics nodes ...
const childNodes = Array.from(rootElem.childNodes);
const topicsNodes = childNodes
.filter(
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'topic',
)
.map((c) => c as Element);
topicsNodes.forEach((child) => {
const topic = this._deserializeNode(child, mindmap);
mindmap.addBranch(topic);
});
// Then all relationshops, they are connected to topics ...
const relationshipsNodes = childNodes
.filter(
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'relationship',
)
.map((c) => c as Element);
relationshipsNodes.forEach((child) => {
try {
const relationship = XMLSerializerPela._deserializeRelationship(child, mindmap);
mindmap.addRelationship(relationship);
} catch (e) {
console.error(e);
}
});
// Clean up from the recursion ...
this._idsMap = null;
mindmap.setId(mapId);
return mindmap;
}
protected _deserializeNode(domElem: Element, mindmap: Mindmap) {
const type = domElem.getAttribute('central') != null ? 'CentralTopic' : 'MainTopic';
// Load attributes...
let id: number | null = null;
if ($defined(domElem.getAttribute('id'))) {
id = Number.parseInt(domElem.getAttribute('id'), 10);
}
if (this._idsMap[id]) {
id = null;
} else {
this._idsMap[id] = domElem;
}
const topic = mindmap.createNode(type, id);
// Set text property is it;s defined...
const text = domElem.getAttribute('text');
if ($defined(text) && text) {
topic.setText(text);
}
const fontStyle = domElem.getAttribute('fontStyle');
if ($defined(fontStyle) && fontStyle) {
const font = fontStyle.split(';');
if (font[0]) {
topic.setFontFamily(font[0]);
}
if (font[1]) {
topic.setFontSize(Number.parseInt(font[1], 10));
}
if (font[2]) {
topic.setFontColor(font[2]);
}
if (font[3]) {
topic.setFontWeight(font[3]);
}
if (font[4]) {
topic.setFontStyle(font[4]);
}
}
const shape = domElem.getAttribute('shape');
if ($defined(shape)) {
topic.setShapeType(shape);
if (shape === TopicShape.IMAGE) {
const image = domElem.getAttribute('image');
const size = image.substring(0, image.indexOf(':'));
const url = image.substring(image.indexOf(':') + 1, image.length);
topic.setImageUrl(url);
const split = size.split(',');
topic.setImageSize(Number.parseInt(split[0], 10), Number.parseInt(split[1], 10));
}
}
const bgColor = domElem.getAttribute('bgColor');
if ($defined(bgColor)) {
topic.setBackgroundColor(bgColor);
}
const borderColor = domElem.getAttribute('brColor');
if ($defined(borderColor)) {
topic.setBorderColor(borderColor);
}
const order = domElem.getAttribute('order');
if ($defined(order) && order !== 'NaN') {
// Hack for broken maps ...
topic.setOrder(parseInt(order, 10));
}
const isShrink = domElem.getAttribute('shrink');
// Hack: Some production maps has been stored with the central topic collapsed. This is a bug.
if ($defined(isShrink) && type !== 'CentralTopic') {
topic.setChildrenShrunken(Boolean(isShrink));
}
const position = domElem.getAttribute('position');
if ($defined(position)) {
const pos = position.split(',');
topic.setPosition(Number.parseInt(pos[0], 10), Number.parseInt(pos[1], 10));
}
const metadata = domElem.getAttribute('metadata');
if ($defined(metadata)) {
topic.setMetadata(metadata);
}
// Creating icons and children nodes
const children = Array.from(domElem.childNodes);
children.forEach((child) => {
if (child.nodeType === Node.ELEMENT_NODE) {
const elem = child as Element;
if (elem.tagName === 'topic') {
const childTopic = this._deserializeNode(elem, mindmap);
childTopic.connectTo(topic);
} else if (FeatureModelFactory.isSupported(elem.tagName)) {
// Load attributes ...
const namedNodeMap = elem.attributes;
const attributes: Record<string, string> = {};
for (let j = 0; j < namedNodeMap.length; j++) {
const attribute = namedNodeMap.item(j);
attributes[attribute.name] = attribute.value;
}
// Has text node ?.
const textAttr = XMLSerializerPela._deserializeTextAttr(elem);
if (textAttr) {
attributes.text = textAttr;
}
// Create a new element ....
const featureType = elem.tagName as FeatureType;
const feature = FeatureModelFactory.createModel(featureType, attributes);
topic.addFeature(feature);
} else if (elem.tagName === 'text') {
const nodeText = XMLSerializerPela._deserializeNodeText(child);
topic.setText(nodeText);
}
}
});
return topic;
}
static _deserializeTextAttr(domElem: Element): string {
let value = domElem.getAttribute('text');
if (!$defined(value)) {
const children = domElem.childNodes;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.nodeType === Node.CDATA_SECTION_NODE) {
value = child.nodeValue;
}
}
} else {
// Notes must be decoded ...
value = unescape(value);
// Hack for empty nodes ...
if (value === '') {
value = ' ';
}
}
return value;
}
static _deserializeNodeText(domElem) {
const children = domElem.childNodes;
let value = null;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.nodeType === Node.CDATA_SECTION_NODE) {
value = child.nodeValue;
}
}
return value;
}
static _deserializeRelationship(domElement, mindmap) {
const srcId = Number.parseInt(domElement.getAttribute('srcTopicId'), 10);
const destId = Number.parseInt(domElement.getAttribute('destTopicId'), 10);
const lineType = Number.parseInt(domElement.getAttribute('lineType'), 10);
const srcCtrlPoint = domElement.getAttribute('srcCtrlPoint');
const destCtrlPoint = domElement.getAttribute('destCtrlPoint');
// If for some reason a relationship lines has source and dest nodes the same, don't import it.
if (srcId === destId) {
throw new Error('Invalid relationship, dest and source are equals');
}
// Is the connections points valid ?. If it's not, do not load the relationship ...
if (mindmap.findNodeById(srcId) == null || mindmap.findNodeById(destId) == null) {
throw new Error('Transition could not created, missing node for relationship');
}
const model = mindmap.createRelationship(srcId, destId);
model.setLineType(lineType);
if ($defined(srcCtrlPoint) && srcCtrlPoint !== '') {
model.setSrcCtrlPoint(Point.fromString(srcCtrlPoint));
}
if ($defined(destCtrlPoint) && destCtrlPoint !== '') {
model.setDestCtrlPoint(Point.fromString(destCtrlPoint));
}
model.setEndArrow('false');
model.setStartArrow('true');
return model;
}
/**
* This method ensures that the output String has only
* valid XML unicode characters as specified by the
* XML 1.0 standard. For reference, please see
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
* standard</a>. This method will return an empty
* String if the input is null or empty.
*
* @param in The String whose non-valid characters we want to remove.
* @return The in String, stripped of non-valid characters.
*/
protected _rmXmlInv(str: string) {
if (str == null || str === undefined) return null;
let result = '';
for (let i = 0; i < str.length; i++) {
const c = str.charCodeAt(i);
if (
c === 0x9
|| c === 0xa
|| c === 0xd
|| (c >= 0x20 && c <= 0xd7ff)
|| (c >= 0xe000 && c <= 0xfffd)
|| (c >= 0x10000 && c <= 0x10ffff)
) {
result += str.charAt(i);
}
}
return result;
}
}
export default XMLSerializerPela;

View File

@ -15,13 +15,506 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import XMLSerializerPela from './XMLSerializerPela';
import { $assert, $defined, createDocument } from '@wisemapping/core-js';
import { Point } from '@wisemapping/web2d';
import Mindmap from '../model/Mindmap';
import { TopicShape } from '../model/INodeModel';
import ConnectionLine from '../ConnectionLine';
import FeatureModelFactory from '../model/FeatureModelFactory';
import NodeModel from '../model/NodeModel';
import RelationshipModel from '../model/RelationshipModel';
import XMLMindmapSerializer from './XMLMindmapSerializer';
import FeatureType from '../model/FeatureType';
/**
* This serializer works exactly the same way as for the former version Pela
*/
class XMLSerializerTango extends XMLSerializerPela {
class XMLSerializerTango implements XMLMindmapSerializer {
private static MAP_ROOT_NODE = 'map';
private _idsMap: Record<number, Element>;
toXML(mindmap: Mindmap): Document {
$assert(mindmap, 'Can not save a null mindmap');
const document = createDocument();
// Store map attributes ...
const mapElem = document.createElement('map');
const name = mindmap.getId();
if ($defined(name)) {
mapElem.setAttribute('name', this._rmXmlInv(name));
}
const version = mindmap.getVersion();
if ($defined(version)) {
mapElem.setAttribute('version', version);
}
document.appendChild(mapElem);
// Create branches ...
const topics = mindmap.getBranches();
topics.forEach((topic) => {
const topicDom = this._topicToXML(document, topic);
mapElem.appendChild(topicDom);
});
// Create Relationships
const relationships = mindmap.getRelationships();
relationships.forEach((relationship) => {
if (
mindmap.findNodeById(relationship.getFromNode()) !== null
&& mindmap.findNodeById(relationship.getToNode()) !== null
) {
// Isolated relationships are not persisted ....
const relationDom = XMLSerializerTango._relationshipToXML(document, relationship);
mapElem.appendChild(relationDom);
}
});
return document;
}
protected _topicToXML(document: Document, topic: NodeModel) {
const parentTopic = document.createElement('topic');
// Set topic attributes...
if (topic.getType() === 'CentralTopic') {
parentTopic.setAttribute('central', 'true');
} else {
const pos = topic.getPosition();
parentTopic.setAttribute('position', `${pos.x},${pos.y}`);
const order = topic.getOrder();
if (typeof order === 'number' && Number.isFinite(order)) {
parentTopic.setAttribute('order', order.toString());
}
}
const text = topic.getText();
if ($defined(text)) {
this._noteTextToXML(document, parentTopic, text);
}
const shape = topic.getShapeType();
if ($defined(shape)) {
parentTopic.setAttribute('shape', shape);
if (shape === TopicShape.IMAGE) {
const size = topic.getImageSize();
parentTopic.setAttribute(
'image',
`${size.width},${size.height}:${topic.getImageUrl()}`,
);
}
}
if (topic.areChildrenShrunken() && topic.getType() !== 'CentralTopic') {
parentTopic.setAttribute('shrink', 'true');
}
// Font properties ...
const id = topic.getId();
parentTopic.setAttribute('id', id.toString());
let font = '';
const fontFamily = topic.getFontFamily();
font += `${fontFamily || ''};`;
const fontSize = topic.getFontSize();
font += `${fontSize || ''};`;
const fontColor = topic.getFontColor();
font += `${fontColor || ''};`;
const fontWeight = topic.getFontWeight();
font += `${fontWeight || ''};`;
const fontStyle = topic.getFontStyle();
font += `${fontStyle || ''};`;
if (
$defined(fontFamily)
|| $defined(fontSize)
|| $defined(fontColor)
|| $defined(fontWeight)
|| $defined(fontStyle)
) {
parentTopic.setAttribute('fontStyle', font);
}
const bgColor = topic.getBackgroundColor();
if ($defined(bgColor)) {
parentTopic.setAttribute('bgColor', bgColor);
}
const brColor = topic.getBorderColor();
if ($defined(brColor)) {
parentTopic.setAttribute('brColor', brColor);
}
const metadata = topic.getMetadata();
if ($defined(metadata)) {
parentTopic.setAttribute('metadata', metadata);
}
// Serialize features ...
const features = topic.getFeatures();
features.forEach((feature) => {
const featureType = feature.getType();
const featureDom = document.createElement(featureType);
const attributes = feature.getAttributes();
const attributesKeys = Object.keys(attributes);
for (let attrIndex = 0; attrIndex < attributesKeys.length; attrIndex++) {
const key = attributesKeys[attrIndex];
const value = attributes[key];
if (key === 'text') {
const cdata = document.createCDATASection(this._rmXmlInv(value));
featureDom.appendChild(cdata);
} else {
featureDom.setAttribute(key, this._rmXmlInv(value));
}
}
parentTopic.appendChild(featureDom);
});
// CHILDREN TOPICS
const childTopics = topic.getChildren();
childTopics.forEach((childTopic) => {
const childDom = this._topicToXML(document, childTopic);
parentTopic.appendChild(childDom);
});
return parentTopic;
}
protected _noteTextToXML(document: Document, elem: Element, text: string) {
if (text.indexOf('\n') === -1) {
elem.setAttribute('text', this._rmXmlInv(text));
} else {
const textDom = document.createElement('text');
const cdata = document.createCDATASection(this._rmXmlInv(text));
textDom.appendChild(cdata);
elem.appendChild(textDom);
}
}
static _relationshipToXML(document: Document, relationship: RelationshipModel) {
const result = document.createElement('relationship');
result.setAttribute('srcTopicId', relationship.getFromNode().toString());
result.setAttribute('destTopicId', relationship.getToNode().toString());
const lineType = relationship.getLineType();
result.setAttribute('lineType', lineType.toString());
if (lineType === ConnectionLine.CURVED || lineType === ConnectionLine.SIMPLE_CURVED) {
if ($defined(relationship.getSrcCtrlPoint())) {
const srcPoint = relationship.getSrcCtrlPoint();
result.setAttribute(
'srcCtrlPoint',
`${Math.round(srcPoint.x)},${Math.round(srcPoint.y)}`,
);
}
if ($defined(relationship.getDestCtrlPoint())) {
const destPoint = relationship.getDestCtrlPoint();
result.setAttribute(
'destCtrlPoint',
`${Math.round(destPoint.x)},${Math.round(destPoint.y)}`,
);
}
}
result.setAttribute('endArrow', String(relationship.getEndArrow()));
result.setAttribute('startArrow', String(relationship.getStartArrow()));
return result;
}
loadFromDom(dom: Document, mapId: string) {
$assert(dom, 'dom can not be null');
$assert(mapId, 'mapId can not be null');
const rootElem = dom.documentElement;
// Is a wisemap?.
$assert(
rootElem.tagName === XMLSerializerTango.MAP_ROOT_NODE,
`This seem not to be a map document. Found tag: ${rootElem.tagName}`,
);
this._idsMap = {};
// Start the loading process ...
const version = rootElem.getAttribute('version') || 'pela';
const mindmap = new Mindmap(mapId, version);
// Add all the topics nodes ...
const childNodes = Array.from(rootElem.childNodes);
const topicsNodes = childNodes
.filter(
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'topic',
)
.map((c) => c as Element);
topicsNodes.forEach((child) => {
const topic = this._deserializeNode(child, mindmap);
mindmap.addBranch(topic);
});
// Then all relationshops, they are connected to topics ...
const relationshipsNodes = childNodes
.filter(
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'relationship',
)
.map((c) => c as Element);
relationshipsNodes.forEach((child) => {
try {
const relationship = XMLSerializerTango._deserializeRelationship(child, mindmap);
mindmap.addRelationship(relationship);
} catch (e) {
console.error(e);
}
});
// Clean up from the recursion ...
this._idsMap = null;
mindmap.setId(mapId);
return mindmap;
}
protected _deserializeNode(domElem: Element, mindmap: Mindmap) {
const type = domElem.getAttribute('central') != null ? 'CentralTopic' : 'MainTopic';
// Load attributes...
let id: number | null = null;
if ($defined(domElem.getAttribute('id'))) {
id = Number.parseInt(domElem.getAttribute('id'), 10);
}
if (this._idsMap[id]) {
id = null;
} else {
this._idsMap[id] = domElem;
}
const topic = mindmap.createNode(type, id);
// Set text property is it;s defined...
const text = domElem.getAttribute('text');
if ($defined(text) && text) {
topic.setText(text);
}
const fontStyle = domElem.getAttribute('fontStyle');
if ($defined(fontStyle) && fontStyle) {
const font = fontStyle.split(';');
if (font[0]) {
topic.setFontFamily(font[0]);
}
if (font[1]) {
topic.setFontSize(Number.parseInt(font[1], 10));
}
if (font[2]) {
topic.setFontColor(font[2]);
}
if (font[3]) {
topic.setFontWeight(font[3]);
}
if (font[4]) {
topic.setFontStyle(font[4]);
}
}
const shape = domElem.getAttribute('shape');
if ($defined(shape)) {
topic.setShapeType(shape);
if (shape === TopicShape.IMAGE) {
const image = domElem.getAttribute('image');
const size = image.substring(0, image.indexOf(':'));
const url = image.substring(image.indexOf(':') + 1, image.length);
topic.setImageUrl(url);
const split = size.split(',');
topic.setImageSize(Number.parseInt(split[0], 10), Number.parseInt(split[1], 10));
}
}
const bgColor = domElem.getAttribute('bgColor');
if ($defined(bgColor)) {
topic.setBackgroundColor(bgColor);
}
const borderColor = domElem.getAttribute('brColor');
if ($defined(borderColor)) {
topic.setBorderColor(borderColor);
}
const order = domElem.getAttribute('order');
if ($defined(order) && order !== 'NaN') {
// Hack for broken maps ...
topic.setOrder(parseInt(order, 10));
}
const isShrink = domElem.getAttribute('shrink');
// Hack: Some production maps has been stored with the central topic collapsed. This is a bug.
if ($defined(isShrink) && type !== 'CentralTopic') {
topic.setChildrenShrunken(Boolean(isShrink));
}
const position = domElem.getAttribute('position');
if ($defined(position)) {
const pos = position.split(',');
topic.setPosition(Number.parseInt(pos[0], 10), Number.parseInt(pos[1], 10));
}
const metadata = domElem.getAttribute('metadata');
if ($defined(metadata)) {
topic.setMetadata(metadata);
}
// Creating icons and children nodes
const children = Array.from(domElem.childNodes);
children.forEach((child) => {
if (child.nodeType === Node.ELEMENT_NODE) {
const elem = child as Element;
if (elem.tagName === 'topic') {
const childTopic = this._deserializeNode(elem, mindmap);
childTopic.connectTo(topic);
} else if (FeatureModelFactory.isSupported(elem.tagName)) {
// Load attributes ...
const namedNodeMap = elem.attributes;
const attributes: Record<string, string> = {};
for (let j = 0; j < namedNodeMap.length; j++) {
const attribute = namedNodeMap.item(j);
attributes[attribute.name] = attribute.value;
}
// Has text node ?.
const textAttr = XMLSerializerTango._deserializeTextAttr(elem);
if (textAttr) {
attributes.text = textAttr;
}
// Create a new element ....
const featureType = elem.tagName as FeatureType;
const feature = FeatureModelFactory.createModel(featureType, attributes);
topic.addFeature(feature);
} else if (elem.tagName === 'text') {
const nodeText = XMLSerializerTango._deserializeNodeText(child);
topic.setText(nodeText);
}
}
});
// Workaround: for some reason, some saved maps have holes in the order.
if (topic.getType() !== 'CentralTopic') {
topic
.getChildren()
.forEach((child, index) => {
if (child.getOrder() !== index) {
child.setOrder(index);
console.log('Toppic with order sequence hole. Introducing auto recovery sequence fix.');
}
});
}
return topic;
}
static _deserializeTextAttr(domElem: Element): string {
let value = domElem.getAttribute('text');
if (!$defined(value)) {
const children = domElem.childNodes;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.nodeType === Node.CDATA_SECTION_NODE) {
value = child.nodeValue;
}
}
} else {
// Notes must be decoded ...
value = unescape(value);
// Hack for empty nodes ...
if (value === '') {
value = ' ';
}
}
return value;
}
static _deserializeNodeText(domElem) {
const children = domElem.childNodes;
let value = null;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.nodeType === Node.CDATA_SECTION_NODE) {
value = child.nodeValue;
}
}
return value;
}
static _deserializeRelationship(domElement, mindmap) {
const srcId = Number.parseInt(domElement.getAttribute('srcTopicId'), 10);
const destId = Number.parseInt(domElement.getAttribute('destTopicId'), 10);
const lineType = Number.parseInt(domElement.getAttribute('lineType'), 10);
const srcCtrlPoint = domElement.getAttribute('srcCtrlPoint');
const destCtrlPoint = domElement.getAttribute('destCtrlPoint');
// If for some reason a relationship lines has source and dest nodes the same, don't import it.
if (srcId === destId) {
throw new Error('Invalid relationship, dest and source are equals');
}
// Is the connections points valid ?. If it's not, do not load the relationship ...
if (mindmap.findNodeById(srcId) == null || mindmap.findNodeById(destId) == null) {
throw new Error('Transition could not created, missing node for relationship');
}
const model = mindmap.createRelationship(srcId, destId);
model.setLineType(lineType);
if ($defined(srcCtrlPoint) && srcCtrlPoint !== '') {
model.setSrcCtrlPoint(Point.fromString(srcCtrlPoint));
}
if ($defined(destCtrlPoint) && destCtrlPoint !== '') {
model.setDestCtrlPoint(Point.fromString(destCtrlPoint));
}
model.setEndArrow('false');
model.setStartArrow('true');
return model;
}
/**
* This method ensures that the output String has only
* valid XML unicode characters as specified by the
* XML 1.0 standard. For reference, please see
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
* standard</a>. This method will return an empty
* String if the input is null or empty.
*
* @param in The String whose non-valid characters we want to remove.
* @return The in String, stripped of non-valid characters.
*/
protected _rmXmlInv(str: string) {
if (str == null || str === undefined) return null;
let result = '';
for (let i = 0; i < str.length; i++) {
const c = str.charCodeAt(i);
if (
c === 0x9
|| c === 0xa
|| c === 0xd
|| (c >= 0x20 && c <= 0xd7ff)
|| (c >= 0xe000 && c <= 0xfffd)
|| (c >= 0x10000 && c <= 0x10ffff)
) {
result += str.charAt(i);
}
}
return result;
}
}
// eslint-disable-next-line camelcase
export default XMLSerializerTango;

View File

@ -25,7 +25,8 @@ class AccountSettingsPanel extends ListToolbarPanel {
// Overwite default behaviour ...
},
setValue() {
window.location = '/c/logout';
const elem = document.getElementById('logoutFrom');
elem.submit();
},
};
super(elemId, model);
@ -59,6 +60,7 @@ class AccountSettingsPanel extends ListToolbarPanel {
content[0].innerHTML = `
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
<p>${global.accountEmail}</p>
<form action="/c/logout" method='POST' id="logoutFrom"></form>
<div id="account-logout" model='logout' style='text-align:center'>
Logout
</div>

Some files were not shown because too many files have changed in this diff Show More