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 bootstrap
- yarn build - yarn build
- yarn lint - yarn lint
- yarn test # - yarn test
artifacts: artifacts:
- packages/**/cypress/snapshots/**/__diff_output__/*.diff.png - packages/**/cypress/snapshots/**/__diff_output__/*.diff.png
definitions: 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 MinusSvg from '../../../images/minus.svg';
import CenterFocusSvg from '../../../images/center_focus.svg'; import CenterFocusSvg from '../../../images/center_focus.svg';
import ActionButton from '../action-button'; import ActionButton from '../action-button';
import { EditorRenderMode } from '@wisemapping/mindplot';
export type FooterPropsType = { export type FooterPropsType = {
showTryPanel?: boolean; editorMode: EditorRenderMode;
}; };
const Footer = ({ showTryPanel }: FooterPropsType): React.ReactElement => { const Footer = ({ editorMode }: FooterPropsType): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
return ( return (
@ -37,7 +38,7 @@ const Footer = ({ showTryPanel }: FooterPropsType): React.ReactElement => {
</div> </div>
<StyledLogo id="bottom-logo"></StyledLogo> <StyledLogo id="bottom-logo"></StyledLogo>
<Notifier id="headerNotifier"></Notifier> <Notifier id="headerNotifier"></Notifier>
{showTryPanel && ( {editorMode === 'showcase' && (
<div id="tryInfoPanel"> <div id="tryInfoPanel">
<p>{intl.formatMessage({ id: 'editor.try-welcome' })}</p> <p>{intl.formatMessage({ id: 'editor.try-welcome' })}</p>
<p>{intl.formatMessage({ id: 'editor.try-welcome-description' })}</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 { div#toolbar {
width: 100%; width: 100%;
height: 50px; height: 50px;
@ -118,8 +187,6 @@ div#exportAnchor {
background-color: #000000; background-color: #000000;
padding: 5px 5px; padding: 5px 5px;
color: #f5f5f5; color: #f5f5f5;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 11px; font-size: 11px;
} }
@ -153,11 +220,7 @@ div.toolbarPanelLinkSelectedLink {
background-color: rgb(228, 226, 210); background-color: rgb(228, 226, 210);
padding: 5px 5px; padding: 5px 5px;
color: #f5f5f5; color: #f5f5f5;
/*font-weight: bold;*/
/*width: 100px;*/
font-size: 11px; font-size: 11px;
-moz-border-radius: 60px;
-webkit-border-radius: 6px;
border-radius: 6px; border-radius: 6px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2); box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
border: 3px double rgb(190, 190, 190); 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 HistorySvg from '../../../images/history.svg';
import PrintSvg from '../../../images/print.svg'; import PrintSvg from '../../../images/print.svg';
import AccountSvg from '../../../images/account.svg'; import AccountSvg from '../../../images/account.svg';
import './global-styled.css';
import { HeaderContainer, ToolbarButton, ToolbarButtonExt, ToolbarRightContainer } from './styled'; import { HeaderContainer, ToolbarButton, ToolbarButtonExt, ToolbarRightContainer } from './styled';
import ActionButton from '../action-button'; import ActionButton from '../action-button';
import { EditorRenderMode } from '@wisemapping/mindplot';
export type ToolbarActionType = 'export' | 'publish' | 'history' | 'print' | 'share'; export type ToolbarActionType = 'export' | 'publish' | 'history' | 'print' | 'share';
export type ToolbarPropsType = { export type ToolbarPropsType = {
isTryMode: boolean; editorMode: EditorRenderMode;
onAction: (action: ToolbarActionType) => void; onAction: (action: ToolbarActionType) => void;
}; };
export default function Toolbar({ export default function Toolbar({
isTryMode: isTryMode, editorMode: editorMode,
onAction, onAction,
}: ToolbarPropsType): React.ReactElement { }: ToolbarPropsType): React.ReactElement {
const intl = useIntl(); const intl = useIntl();
@ -46,7 +48,7 @@ export default function Toolbar({
<div id="backToList"> <div id="backToList">
<img src={BackIconSvg} /> <img src={BackIconSvg} />
</div> </div>
{!isTryMode && ( {editorMode === 'edition' && (
<div id="persist" className="buttonContainer"> <div id="persist" className="buttonContainer">
<ToolbarButton id="save" className="buttonOn"> <ToolbarButton id="save" className="buttonOn">
<img src={SaveSvg} /> <img src={SaveSvg} />
@ -110,7 +112,7 @@ export default function Toolbar({
</ToolbarButton> </ToolbarButton>
</div> </div>
<div id="separator" className="buttonContainer"></div> <div id="separator" className="buttonContainer"></div>
{!isTryMode && ( {editorMode === 'edition' && (
<ToolbarRightContainer> <ToolbarRightContainer>
<ToolbarButton <ToolbarButton
id="export" id="export"

View File

@ -1,10 +1,12 @@
@import "compatibility.css";
/********************************************************************************/ /********************************************************************************/
/* Header & Toolbar Styles */ /* Header & Toolbar Styles */
/********************************************************************************/ /********************************************************************************/
@import "header.css"; @import "bootstrap.min.css";
@import "../bootstrap/css/bootstrap.min.css";
html {
/* avoid bootstrap overriding font-size and breaking Mui */
font-size: initial;
}
body { body {
-webkit-touch-callout: none; -webkit-touch-callout: none;
@ -14,6 +16,8 @@ body {
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
overflow: hidden; overflow: hidden;
margin: 0;
font-family: Arial, Helvetica, sans-serif;
} }
div#mindplot { div#mindplot {
@ -126,6 +130,7 @@ div.shareModalDialog {
} }
.popover { .popover {
font-size: 13px;
max-width: none; max-width: none;
} }
@ -206,3 +211,22 @@ div#shotcuts > img{
background-color: #000000; background-color: #000000;
color: #ffffff; 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,136 +1,108 @@
import React from 'react'; import React, { useEffect } from 'react';
import Toolbar, { ToolbarActionType } from './components/toolbar'; import Toolbar, { ToolbarActionType } from './components/toolbar';
import Footer from './components/footer'; import Footer from './components/footer';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
import { import {
$notify, $notify,
buildDesigner, buildDesigner,
LocalStorageManager,
PersistenceManager, PersistenceManager,
RESTPersistenceManager,
DesignerOptionsBuilder, DesignerOptionsBuilder,
Designer Designer,
DesignerKeyboard,
EditorRenderMode,
} from '@wisemapping/mindplot'; } from '@wisemapping/mindplot';
import FR from './compiled-lang/fr.json'; import './global-styled.css';
import ES from './compiled-lang/es.json'; import I18nMsg from './classes/i18n-msg';
import EN from './compiled-lang/en.json'; import Messages from '@wisemapping/mindplot/src/components/Messages';
import DE from './compiled-lang/de.json';
declare global { 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 // used in mindplot
var designer: Designer; var designer: Designer;
var accountEmail: string; var accountEmail: string;
} }
export type EditorPropsType = { export type EditorOptions = {
initCallback?: (locale: string) => void; mode: EditorRenderMode,
mapId?: number; locale: string,
isTryMode: boolean; zoom?: number,
readOnlyMode: boolean; locked?: boolean,
locale?: string; lockedMsg?: string;
onAction: (action: ToolbarActionType) => void; mapTitle: string;
}; enableKeyboardEvents: boolean;
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) => { export type EditorProps = {
// Change page title ... mapId: string;
document.title = `${global.mapTitle} | WiseMapping `; options: EditorOptions;
persistenceManager: PersistenceManager;
// Configure persistence manager ... onAction: (action: ToolbarActionType) => void;
let persistence: PersistenceManager; onLoad?: (designer: Designer) => void;
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);
}
}; };
const Editor = ({ const Editor = ({
initCallback = initMindplot,
mapId, mapId,
isTryMode: isTryMode, options,
locale = 'en', persistenceManager,
onAction, onAction,
}: EditorPropsType): React.ReactElement => { onLoad,
React.useEffect(() => { }: EditorProps) => {
initCallback(locale);
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 ( return (
<IntlProvider locale={locale} messages={loadLocaleData(locale)}> <IntlProvider locale={locale} messages={msg}>
{(options.mode !== 'viewonly') &&
<Toolbar <Toolbar
isTryMode={isTryMode} editorMode={options.mode}
onAction={onAction} onAction={onAction}
/> />
<div id="mindplot"></div> }
<Footer showTryPanel={isTryMode} /> <div id="mindplot" style={mindplotStyle}></div>
</IntlProvider> <Footer editorMode={options.mode} />
</IntlProvider >
); );
} }
export default Editor; export default Editor;

View File

@ -25,7 +25,6 @@
<li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read <li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read
only mode</li> 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="/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> </ul>
</div> </div>
</body> </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 */ /* Overwrite some styles */
body {
position: inherit;
}
div#footer { div#footer {
width: 100%; width: 100%;
padding: 20px 30px; padding: 20px 30px;
@ -33,3 +27,7 @@ div#footer-logo {
div#mindplot { div#mindplot {
top:0; 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="sample5">sample5</option>
<option value="sample6">sample6</option> <option value="sample6">sample6</option>
<option value="sample7">sample7</option> <option value="sample7">sample7</option>
<option value="sample8">sample8</option>
<option value="img-support">img-support</option> <option value="img-support">img-support</option>
<option value="error-on-load">error-on-load</option> <option value="error-on-load">error-on-load</option>
<option value="complex">complex</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,8 +7,9 @@ module.exports = {
publicPath: '', publicPath: '',
library: { library: {
type: 'umd', type: 'umd',
}, }, },
stats:{ },
stats: {
errorDetails: true errorDetails: true
}, },
entry: { entry: {
@ -35,7 +36,14 @@ module.exports = {
test: /\.(js|jsx)$/, test: /\.(js|jsx)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ['babel-loader'], use: ['babel-loader'],
}, {
test: /\.css$/i,
loader: 'style-loader'
}, },
{
test: /\.css$/,
loader: 'css-loader',
}
], ],
}, },
} };

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@wisemapping/mindplot", "name": "@wisemapping/mindplot",
"version": "5.0.3", "version": "5.0.8",
"description": "WiseMapping - Mindplot Canvas Library", "description": "WiseMapping - Mindplot Canvas Library",
"homepage": "http://www.wisemapping.org/", "homepage": "http://www.wisemapping.org/",
"directories": { "directories": {
@ -37,7 +37,8 @@
"@wisemapping/web2d": "^0.4.0", "@wisemapping/web2d": "^0.4.0",
"jest": "^27.4.5", "jest": "^27.4.5",
"jquery": "3.6.0", "jquery": "3.6.0",
"lodash": "^4.17.21" "lodash": "^4.17.21",
"xml-formatter": "^2.6.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.14.6", "@babel/core": "^7.14.6",
@ -69,6 +70,7 @@
"start-server-and-test": "^1.14.0", "start-server-and-test": "^1.14.0",
"ts-jest": "^27.1.2", "ts-jest": "^27.1.2",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"webpack": "^5.44.0", "webpack": "^5.44.0",
"webpack-bundle-analyzer": "^4.5.0", "webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.7.2", "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 { abstract class ActionDispatcher extends Events {
private static _instance: ActionDispatcher; private static _instance: ActionDispatcher;
private _commandContext: CommandContext;
constructor(commandContext: CommandContext) { constructor(commandContext: CommandContext) {
$assert(commandContext, 'commandContext can not be null'); $assert(commandContext, 'commandContext can not be null');
super(); super();
this._commandContext = commandContext;
}
getCommandContext(): CommandContext {
return this._commandContext;
} }
abstract addRelationship(model: RelationshipModel, mindmap: Mindmap): void; abstract addRelationship(model: RelationshipModel, mindmap: Mindmap): void;

View File

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

View File

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

View File

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

View File

@ -20,8 +20,6 @@ import $ from 'jquery';
import PersistenceManager from './PersistenceManager'; import PersistenceManager from './PersistenceManager';
import Designer from './Designer'; import Designer from './Designer';
import Menu from './widget/Menu'; import Menu from './widget/Menu';
import { $notifyModal } from './widget/ModalDialogNotifier';
import { $msg } from './Messages';
import { DesignerOptions } from './DesignerOptionsBuilder'; import { DesignerOptions } from './DesignerOptionsBuilder';
let designer: Designer; let designer: Designer;
@ -32,40 +30,6 @@ export function buildDesigner(options: DesignerOptions): Designer {
// Register load events ... // Register load events ...
designer = new Designer(options, divContainer); 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 ... // Configure default persistence manager ...
const persistence = options.persistenceManager; const persistence = options.persistenceManager;
@ -73,7 +37,7 @@ export function buildDesigner(options: DesignerOptions): Designer {
PersistenceManager.init(persistence); PersistenceManager.init(persistence);
// Register toolbar event ... // Register toolbar event ...
if ($('#toolbar').length) { if (options.mode === 'edition' || options.mode === 'showcase') {
const menu = new Menu(designer, 'toolbar'); const menu = new Menu(designer, 'toolbar');
// If a node has focus, focus can be move to another node using the keys. // If a node has focus, focus can be move to another node using the keys.

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import DragTopic from './DragTopic'; import DragTopic from './DragTopic';
import EventBusDispatcher from './layout/EventBusDispatcher'; import EventBusDispatcher from './layout/EventBusDispatcher';
import Topic from './Topic';
import Workspace from './Workspace'; import Workspace from './Workspace';
class DragManager { class DragManager {
@ -44,7 +45,7 @@ class DragManager {
DragTopic.init(this._workspace); DragTopic.init(this._workspace);
} }
add(node) { add(topic: Topic) {
// Add behaviour ... // Add behaviour ...
const workspace = this._workspace; const workspace = this._workspace;
const screen = workspace.getScreenManager(); const screen = workspace.getScreenManager();
@ -57,7 +58,7 @@ class DragManager {
// Set initial position. // Set initial position.
const layoutManager = me._eventDispatcher.getLayoutManager(); const layoutManager = me._eventDispatcher.getLayoutManager();
const dragNode = node.createDragNode(layoutManager); const dragNode = topic.createDragNode(layoutManager);
// Register mouse move listener ... // Register mouse move listener ...
const mouseMoveListener = dragManager._buildMouseMoveListener( const mouseMoveListener = dragManager._buildMouseMoveListener(
@ -67,7 +68,7 @@ class DragManager {
// Register mouse up listeners ... // Register mouse up listeners ...
const mouseUpListener = dragManager._buildMouseUpListener( const mouseUpListener = dragManager._buildMouseUpListener(
workspace, node, dragNode, dragManager, workspace, topic, dragNode, dragManager,
); );
screen.addEvent('mouseup', mouseUpListener); screen.addEvent('mouseup', mouseUpListener);
@ -75,7 +76,7 @@ class DragManager {
window.document.body.style.cursor = 'move'; window.document.body.style.cursor = 'move';
} }
}; };
node.addEvent('mousedown', mouseDownListener); topic.addEvent('mousedown', mouseDownListener);
} }
remove() { remove() {
@ -114,10 +115,10 @@ class DragManager {
return result; return result;
} }
protected _buildMouseUpListener(workspace: Workspace, node, dragNode, dragManager: DragManager) { protected _buildMouseUpListener(workspace: Workspace, topic: Topic, dragNode, dragManager: DragManager) {
const screen = workspace.getScreenManager(); const screen = workspace.getScreenManager();
const me = this; const me = this;
const result = (event) => { const result = (event: Event) => {
$assert(dragNode.isDragTopic, 'dragNode must be an DragTopic'); $assert(dragNode.isDragTopic, 'dragNode must be an DragTopic');
// Remove all the events. // Remove all the events.
@ -154,7 +155,7 @@ class DragManager {
* - dragging * - dragging
* - enddragging * - enddragging
*/ */
addEvent(type, listener) { addEvent(type: string, listener) {
this._listeners[type] = 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 { Point, CurvedLine, Rect } from '@wisemapping/web2d';
import DragTopicConfig from './DragTopicConfig'; import DragTopicConfig from './DragTopicConfig';
import SizeType from './SizeType';
import Topic from './Topic';
import Shape from './util/Shape'; import Shape from './util/Shape';
import Workspace from './Workspace';
class DragPivot { 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() { constructor() {
this._position = new Point(); this._position = new Point();
this._size = DragTopicConfig.PIVOT_SIZE; this._size = DragTopicConfig.PIVOT_SIZE;
@ -34,15 +53,15 @@ class DragPivot {
this._isVisible = false; this._isVisible = false;
} }
isVisible() { isVisible(): boolean {
return this._isVisible; return this._isVisible;
} }
getTargetTopic() { getTargetTopic(): Topic {
return this._targetTopic; return this._targetTopic;
} }
_buildStraightLine() { private _buildStraightLine(): CurvedLine {
const line = new CurvedLine(); const line = new CurvedLine();
line.setStyle(CurvedLine.SIMPLE_LINE); line.setStyle(CurvedLine.SIMPLE_LINE);
line.setStroke(1, 'solid', '#CC0033'); line.setStroke(1, 'solid', '#CC0033');
@ -51,7 +70,7 @@ class DragPivot {
return line; return line;
} }
_buildCurvedLine() { private _buildCurvedLine(): CurvedLine {
const line = new CurvedLine(); const line = new CurvedLine();
line.setStyle(CurvedLine.SIMPLE_LINE); line.setStyle(CurvedLine.SIMPLE_LINE);
line.setStroke(1, 'solid', '#CC0033'); line.setStroke(1, 'solid', '#CC0033');
@ -60,7 +79,7 @@ class DragPivot {
return line; return line;
} }
_redrawLine() { private _redrawLine(): void {
// Update line position. // Update line position.
$assert(this.getTargetTopic(), 'Illegal invocation. Target node can not be null'); $assert(this.getTargetTopic(), 'Illegal invocation. Target node can not be null');
@ -81,8 +100,8 @@ class DragPivot {
line.setFrom(pivotPoint.x, pivotPoint.y); line.setFrom(pivotPoint.x, pivotPoint.y);
// Update rect position // Update rect position
const cx = position.x - parseInt(size.width, 10) / 2; const cx = position.x - size.width / 2;
const cy = position.y - parseInt(size.height, 10) / 2; const cy = position.y - size.height / 2;
pivotRect.setPosition(cx, cy); pivotRect.setPosition(cx, cy);
// Make line visible only when the position has been already changed. // Make line visible only when the position has been already changed.
@ -91,16 +110,16 @@ class DragPivot {
line.setTo(targetPoint.x, targetPoint.y); line.setTo(targetPoint.x, targetPoint.y);
} }
setPosition(point) { setPosition(point: Point): void {
this._position = point; this._position = point;
this._redrawLine(); this._redrawLine();
} }
getPosition() { getPosition(): Point {
return this._position; return this._position;
} }
_buildRect() { private _buildRect(): Rect {
const size = this._size; const size = this._size;
const rectAttributes = { const rectAttributes = {
fillColor: '#CC0033', fillColor: '#CC0033',
@ -114,16 +133,16 @@ class DragPivot {
return rect; return rect;
} }
_getPivotRect() { private _getPivotRect(): Rect {
return this._dragPivot; return this._dragPivot;
} }
getSize() { getSize(): SizeType {
const elem2d = this._getPivotRect(); const elem2d = this._getPivotRect();
return elem2d.getSize(); return elem2d.getSize();
} }
setVisibility(value) { setVisibility(value: boolean) {
if (this.isVisible() !== value) { if (this.isVisible() !== value) {
const pivotRect = this._getPivotRect(); const pivotRect = this._getPivotRect();
pivotRect.setVisibility(value); pivotRect.setVisibility(value);
@ -140,7 +159,7 @@ class DragPivot {
} }
// If the node is connected, validate that there is a line connecting both... // If the node is connected, validate that there is a line connecting both...
_getConnectionLine() { _getConnectionLine(): CurvedLine {
let result = null; let result = null;
const parentTopic = this._targetTopic; const parentTopic = this._targetTopic;
if (parentTopic) { if (parentTopic) {
@ -153,7 +172,7 @@ class DragPivot {
return result; return result;
} }
addToWorkspace(workspace) { addToWorkspace(workspace: Workspace) {
const pivotRect = this._getPivotRect(); const pivotRect = this._getPivotRect();
workspace.append(pivotRect); workspace.append(pivotRect);
@ -179,7 +198,7 @@ class DragPivot {
connectRect.moveToBack(); connectRect.moveToBack();
} }
removeFromWorkspace(workspace) { removeFromWorkspace(workspace: Workspace) {
const shape = this._getPivotRect(); const shape = this._getPivotRect();
workspace.removeChild(shape); workspace.removeChild(shape);
@ -195,9 +214,7 @@ class DragPivot {
} }
} }
connectTo(targetTopic, position) { connectTo(targetTopic: Topic, position: Point) {
$assert(!this._outgoingLine, 'Could not connect an already connected node');
$assert(targetTopic !== this, 'Circular connection are not allowed');
$assert(position, 'position can not be null'); $assert(position, 'position can not be null');
$assert(targetTopic, 'parent can not be null'); $assert(targetTopic, 'parent can not be null');
@ -227,7 +244,7 @@ class DragPivot {
this._redrawLine(); this._redrawLine();
} }
disconnect(workspace) { disconnect(workspace: Workspace): void {
$assert(workspace, 'workspace can not be null.'); $assert(workspace, 'workspace can not be null.');
$assert(this._targetTopic, 'There are not connected topic.'); $assert(this._targetTopic, 'There are not connected topic.');

View File

@ -16,13 +16,24 @@
* limitations under the License. * limitations under the License.
*/ */
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import { Point } from '@wisemapping/web2d'; import { Point, ElementClass } from '@wisemapping/web2d';
import ActionDispatcher from './ActionDispatcher'; import ActionDispatcher from './ActionDispatcher';
import DragPivot from './DragPivot'; import DragPivot from './DragPivot';
import LayoutManager from './layout/LayoutManager';
import NodeGraph from './NodeGraph';
import Topic from './Topic';
import Workspace from './Workspace';
class DragTopic { 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(dragShape, 'Rect can not be null.');
$assert(draggedNode, 'draggedNode can not be null.'); $assert(draggedNode, 'draggedNode can not be null.');
$assert(layoutManger, 'layoutManger can not be null.'); $assert(layoutManger, 'layoutManger can not be null.');
@ -33,26 +44,15 @@ class DragTopic {
this._layoutManager = layoutManger; this._layoutManager = layoutManger;
this._position = new Point(); this._position = new Point();
this._isInWorkspace = false; this._isInWorkspace = false;
this._isFreeLayoutEnabled = false;
} }
setOrder(order) { setOrder(order: number) {
this._order = order; this._order = order;
} }
setPosition(x, y) { setPosition(x: number, y: number) {
// Update drag shadow position .... // Update drag shadow position ....
let position = { x, y }; 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); this._position.setValue(position.x, position.y);
// Elements are positioned in the center. // Elements are positioned in the center.
@ -80,45 +80,35 @@ class DragTopic {
} }
} }
updateFreeLayout(event) { setVisibility(value: boolean) {
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) {
const dragPivot = this._getDragPivot(); const dragPivot = this._getDragPivot();
dragPivot.setVisibility(value); dragPivot.setVisibility(value);
} }
isVisible() { isVisible(): boolean {
const dragPivot = this._getDragPivot(); const dragPivot = this._getDragPivot();
return dragPivot.isVisible(); return dragPivot.isVisible();
} }
getInnerShape() { getInnerShape(): ElementClass {
return this._elem2d; return this._elem2d;
} }
disconnect(workspace) { disconnect(workspace: Workspace) {
// Clear connection line ... // Clear connection line ...
const dragPivot = this._getDragPivot(); const dragPivot = this._getDragPivot();
dragPivot.disconnect(workspace); dragPivot.disconnect(workspace);
} }
connectTo(parent) { connectTo(parent: Topic) {
$assert(parent, 'Parent connection node can not be null.'); $assert(parent, 'Parent connection node can not be null.');
// Where it should be connected ? // Where it should be connected ?
// @todo: This is a hack for the access of the editor. // @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. // 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(), parent.getId(),
this._draggedNode.getId(), this._draggedNode.getId(),
this.getPosition(), this.getPosition(),
@ -133,11 +123,11 @@ class DragTopic {
this.setOrder(predict.order); this.setOrder(predict.order);
} }
getDraggedTopic() { getDraggedTopic(): Topic {
return this._draggedNode; return this._draggedNode as Topic;
} }
removeFromWorkspace(workspace) { removeFromWorkspace(workspace: Workspace) {
if (this._isInWorkspace) { if (this._isInWorkspace) {
// Remove drag shadow. // Remove drag shadow.
workspace.removeChild(this._elem2d); workspace.removeChild(this._elem2d);
@ -151,11 +141,11 @@ class DragTopic {
} }
} }
isInWorkspace() { isInWorkspace(): boolean {
return this._isInWorkspace; return this._isInWorkspace;
} }
addToWorkspace(workspace) { addToWorkspace(workspace: Workspace) {
if (!this._isInWorkspace) { if (!this._isInWorkspace) {
workspace.append(this._elem2d); workspace.append(this._elem2d);
const dragPivot = this._getDragPivot(); const dragPivot = this._getDragPivot();
@ -164,19 +154,19 @@ class DragTopic {
} }
} }
_getDragPivot() { _getDragPivot(): DragPivot {
return DragTopic.__getDragPivot(); return DragTopic.__getDragPivot();
} }
getPosition() { getPosition(): Point {
return this._position; return this._position;
} }
isDragTopic() { isDragTopic(): boolean {
return true; return true;
} }
applyChanges(workspace) { applyChanges(workspace: Workspace) {
$assert(workspace, 'workspace can not be null'); $assert(workspace, 'workspace can not be null');
const actionDispatcher = ActionDispatcher.getInstance(); const actionDispatcher = ActionDispatcher.getInstance();
@ -201,33 +191,33 @@ class DragTopic {
} }
} }
getConnectedToTopic() { getConnectedToTopic(): Topic {
const dragPivot = this._getDragPivot(); const dragPivot = this._getDragPivot();
return dragPivot.getTargetTopic(); return dragPivot.getTargetTopic();
} }
isConnected() { isConnected(): boolean {
return this.getConnectedToTopic() != null; return this.getConnectedToTopic() != null;
} }
isFreeLayoutOn() { isFreeLayoutOn(): false {
return false; return false;
} }
}
DragTopic.init = function init(workspace) { static init(workspace: Workspace) {
$assert(workspace, 'workspace can not be null'); $assert(workspace, 'workspace can not be null');
const pivot = DragTopic.__getDragPivot(); const pivot = DragTopic.__getDragPivot();
workspace.append(pivot); workspace.append(pivot);
}; };
DragTopic.__getDragPivot = function __getDragPivot() { static __getDragPivot() {
let result = DragTopic._dragPivot; let result = DragTopic._dragPivot;
if (!$defined(result)) { if (!$defined(result)) {
result = new DragPivot(); result = new DragPivot();
DragTopic._dragPivot = result; DragTopic._dragPivot = result;
} }
return result; return result;
}; };
}
export default DragTopic; 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 { $assert } from '@wisemapping/core-js';
import { Image } from '@wisemapping/web2d'; 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 { abstract class Icon {
constructor(url) { protected _image: Image;
protected _group: IconGroup;
constructor(url: string) {
$assert(url, 'topic can not be null'); $assert(url, 'topic can not be null');
this._image = new Image(); this._image = new Image();
this._image.setHref(url); this._image.setHref(url);
this._image.setSize(Icon.SIZE, Icon.SIZE); this._image.setSize(Icon.SIZE, Icon.SIZE);
} }
getImage() { getImage(): Image {
return this._image; return this._image;
} }
setGroup(group) { setGroup(group: IconGroup) {
this._group = group; this._group = group;
} }
getGroup() { getGroup(): IconGroup {
return this._group; return this._group;
} }
getSize() { getSize(): SizeType {
return this._image.getSize(); return this._image.getSize();
} }
getPosition() { getPosition(): Point {
return this._image.getPosition(); return this._image.getPosition();
} }
addEvent(type, fnc) { addEvent(type: string, fnc): void {
this._image.addEvent(type, fnc); this._image.addEvent(type, fnc);
} }
@ -54,8 +61,10 @@ class Icon {
remove() { remove() {
throw new Error('Unsupported operation'); throw new Error('Unsupported operation');
} }
abstract getModel(): FeatureModel;
static SIZE = 90;
} }
Icon.SIZE = 90;
export default Icon; export default Icon;

View File

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

View File

@ -93,7 +93,7 @@ class ImageIcon extends Icon {
static _getFamilyIcons(iconId) { static _getFamilyIcons(iconId) {
$assert(iconId != null, 'id must not be null'); $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; let result = null;
for (let i = 0; i < ImageIcon.prototype.ICON_FAMILIES.length; i++) { for (let i = 0; i < ImageIcon.prototype.ICON_FAMILIES.length; i++) {

View File

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

View File

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

View File

@ -16,7 +16,6 @@
* limitations under the License. * limitations under the License.
*/ */
import $ from 'jquery'; import $ from 'jquery';
import { Mindmap } from '..';
import PersistenceManager from './PersistenceManager'; import PersistenceManager from './PersistenceManager';
class LocalStorageManager extends PersistenceManager { class LocalStorageManager extends PersistenceManager {
@ -30,7 +29,8 @@ class LocalStorageManager extends PersistenceManager {
this.forceLoad = forceLoad; 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); localStorage.setItem(`${mapId}-xml`, mapXml);
} }
@ -43,7 +43,7 @@ class LocalStorageManager extends PersistenceManager {
if (xml == null || this.forceLoad) { if (xml == null || this.forceLoad) {
$.ajax({ $.ajax({
url: this.documentUrl.replace('{id}', mapId), 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', type: 'get',
dataType: 'text', dataType: 'text',
async: false, async: false,
@ -63,7 +63,7 @@ class LocalStorageManager extends PersistenceManager {
return $.parseXML(xml); return $.parseXML(xml);
} }
unlockMap(mindmap: Mindmap) { unlockMap(): void {
// Ignore, no implementation required ... // Ignore, no implementation required ...
} }
} }

View File

@ -28,12 +28,6 @@ import SizeType from './SizeType';
class MainTopic extends Topic { class MainTopic extends Topic {
private INNER_RECT_ATTRIBUTES: { stroke: string; }; private INNER_RECT_ATTRIBUTES: { stroke: string; };
/**
* @extends mindplot.Topic
* @constructs
* @param model
* @param options
*/
constructor(model: NodeModel, options) { constructor(model: NodeModel, options) {
super(model, options); super(model, options);
this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' }; this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' };
@ -70,6 +64,11 @@ class MainTopic extends Topic {
const text = this.getText(); const text = this.getText();
textShape.setText(text); textShape.setText(text);
textShape.setOpacity(0.5); 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); group.append(textShape);
} }
return group; return group;
@ -88,7 +87,6 @@ class MainTopic extends Topic {
} }
} }
/** */
disconnect(workspace: Workspace) { disconnect(workspace: Workspace) {
super.disconnect(workspace); super.disconnect(workspace);
const model = this.getModel(); const model = this.getModel();

View File

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

View File

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

View File

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

View File

@ -20,11 +20,21 @@ import { $assert } from '@wisemapping/core-js';
import { Mindmap } from '..'; import { Mindmap } from '..';
import XMLSerializerFactory from './persistence/XMLSerializerFactory'; 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 { abstract class PersistenceManager {
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
static _instance: PersistenceManager; 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(mindmap, 'mindmap can not be null');
$assert(editorProperties, 'editorProperties can not be null'); $assert(editorProperties, 'editorProperties can not be null');
@ -33,30 +43,55 @@ abstract class PersistenceManager {
const serializer = XMLSerializerFactory.createInstanceFromMindmap(mindmap); const serializer = XMLSerializerFactory.createInstanceFromMindmap(mindmap);
const domMap = serializer.toXML(mindmap); const domMap = serializer.toXML(mindmap);
const mapXml = new XMLSerializer().serializeToString(domMap);
const pref = JSON.stringify(editorProperties); const pref = JSON.stringify(editorProperties);
try { try {
this.saveMapXml(mapId, mapXml, pref, saveHistory, events, sync); this.saveMapXml(mapId, domMap, pref, saveHistory, events);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
events.onError(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) { load(mapId: string) {
$assert(mapId, 'mapId can not be null'); $assert(mapId, 'mapId can not be null');
const domDocument = this.loadMapDom(mapId); const domDocument = this.loadMapDom(mapId);
return PersistenceManager.loadFromDom(mapId, domDocument); 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 discardChanges(mapId: string): void;
abstract loadMapDom(mapId: string): Document; 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) => { static init = (instance: PersistenceManager) => {
this._instance = instance; this._instance = instance;

View File

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

View File

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

View File

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

View File

@ -17,8 +17,6 @@
*/ */
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import { Point } from '@wisemapping/web2d'; import { Point } from '@wisemapping/web2d';
import Icon from './Icon';
import Topic from './Topic';
class ScreenManager { class ScreenManager {
private _divContainer: JQuery; 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) { getWorkspaceMousePosition(event: MouseEvent) {
// Retrieve current mouse position. // Retrieve current mouse position.
let x = event.clientX; let x = event.clientX;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,18 +20,20 @@ import Command from '../Command';
import CommandContext from '../CommandContext'; import CommandContext from '../CommandContext';
import Topic from '../Topic'; import Topic from '../Topic';
type CommandTypes = string | object | boolean | number;
class GenericFunctionCommand extends Command { class GenericFunctionCommand extends Command {
private _value: string | object | boolean | number; private _value: CommandTypes;
private _topicsId: number[]; 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; 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(commandFunc, 'commandFunc must be defined');
$assert($defined(topicsIds), 'topicsIds 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Mindmap } from '../..';
import Exporter from './Exporter'; import Exporter from './Exporter';
import SVGExporter from './SVGExporter'; import SVGExporter from './SVGExporter';
/** /**
* Based on https://mybyways.com/blog/convert-svg-to-png-using-your-browser * Based on https://mybyways.com/blog/convert-svg-to-png-using-your-browser
*/ */
class BinaryImageExporter extends Exporter { 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') { constructor(svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg', adjustToFit = true) {
super(imgFormat.split['/'][0], imgFormat); super(imgFormat.split('/')[0], imgFormat);
this.svgElement = svgElement; this.svgElement = svgElement;
this.mindmap = mindmap; this.adjustToFit = adjustToFit;
this.width = width; this.width = width;
this.height = height; this.height = height;
} }
@ -43,21 +41,35 @@ class BinaryImageExporter extends Exporter {
throw new Error('Images can not be exporeted'); throw new Error('Images can not be exporeted');
} }
async exportAndEndcode(): Promise<string> { exportAndEncode(): Promise<string> {
const svgExporter = new SVGExporter(this.svgElement); const svgExporter = new SVGExporter(this.svgElement, this.adjustToFit);
const svgUrl = await svgExporter.exportAndEncode(); 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. // Get the device pixel ratio, falling back to 1. But, I will double the resolution to look nicer.
const dpr = (window.devicePixelRatio || 1) * 2; const dpr = (window.devicePixelRatio || 1) * 2;
// Create canvas ... // Create canvas size ...
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.setAttribute('width', (this.width * dpr).toString()); let width: number;
canvas.setAttribute('height', (this.height * dpr).toString()); 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 ... // Render the image and wait for the response ...
const img = new Image(); const img = new Image();
const result = new Promise<string>((resolve, reject) => { const result = new Promise<string>((resolve) => {
img.onload = () => { img.onload = () => {
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
// Scale for retina ... // Scale for retina ...
@ -68,12 +80,13 @@ class BinaryImageExporter extends Exporter {
.toDataURL(this.getContentType()) .toDataURL(this.getContentType())
.replace('image/png', 'octet/stream'); .replace('image/png', 'octet/stream');
URL.revokeObjectURL(svgUrl); URL.revokeObjectURL(value);
resolve(imgDataUri); resolve(imgDataUri);
}; };
}); });
img.src = svgUrl; img.src = value;
return result; return result;
});
} }
} }
export default BinaryImageExporter; 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Mindmap } from '../..';
import BinaryImageExporter from './BinaryImageExporter'; import BinaryImageExporter from './BinaryImageExporter';
import Exporter from './Exporter'; import Exporter from './Exporter';
import SVGExporter from './SVGExporter'; import SVGExporter from './SVGExporter';
type imageType = 'svg' | 'png' | 'jpg'; type imageType = 'svg' | 'png' | 'jpg';
class ImageExpoterFactory { class ImageExpoterFactory {
static create(type: imageType, mindmap: Mindmap, svgElement: Element, width: number, height: number, isCenter = false): Exporter { static create(type: imageType, svgElement: Element, width: number, height: number, adjustToFit = true): Exporter {
let result; let result: Exporter;
switch (type) { switch (type) {
case 'svg': { case 'svg': {
result = new SVGExporter(svgElement); result = new SVGExporter(svgElement, adjustToFit);
break; break;
} }
case 'png': { case 'png': {
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/png'); result = new BinaryImageExporter(svgElement, width, height, 'image/png', adjustToFit);
break; break;
} }
case 'jpg': { case 'jpg': {
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/jpeg'); result = new BinaryImageExporter(svgElement, width, height, 'image/jpeg', adjustToFit);
break; break;
} }
default: default:

View File

@ -15,23 +15,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import SizeType from '../SizeType';
import Exporter from './Exporter'; import Exporter from './Exporter';
class SVGExporter extends Exporter { class SVGExporter extends Exporter {
private svgElement: Element; 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'); super('svg', 'image/svg+xml');
this.svgElement = svgElement; this.svgElement = svgElement;
this.adjustToFit = adjustToFit;
} }
export(): Promise<string> { export(): Promise<string> {
// Replace all images for in-line images ... // Replace all images for in-line images ...
let svgTxt: string = new XMLSerializer() let svgTxt: string = new XMLSerializer()
.serializeToString(this.svgElement); .serializeToString(this.svgElement);
svgTxt = this.prolog + svgTxt; svgTxt = SVGExporter.prolog + svgTxt;
// Are namespace declared ?. Otherwise, force the declaration ... // Are namespace declared ?. Otherwise, force the declaration ...
if (svgTxt.indexOf('xmlns:xlink=') === -1) { if (svgTxt.indexOf('xmlns:xlink=') === -1) {
@ -39,15 +49,103 @@ class SVGExporter extends Exporter {
} }
// Add white background. This is mainly for PNG export ... // 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]; const svgElement = svgDoc.getElementsByTagName('svg')[0];
svgElement.setAttribute('style', 'background-color:white'); 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() const result = new XMLSerializer()
.serializeToString(svgDoc); .serializeToString(svgDoc);
return Promise.resolve(result); 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) => { private static parseXMLString = (xmlStr: string, mimeType: DOMParserSupportedType) => {
const parser = new DOMParser(); const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlStr, mimeType); const xmlDoc = parser.parseFromString(xmlStr, mimeType);
@ -56,7 +154,7 @@ class SVGExporter extends Exporter {
if (xmlDoc.getElementsByTagName('parsererror').length > 0) { if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
const xmmStr = new XMLSerializer().serializeToString(xmlDoc); const xmmStr = new XMLSerializer().serializeToString(xmlDoc);
console.log(xmmStr); console.log(xmmStr);
throw new Error(`Unexpected error parsing: ${xmlStr}. Error: ${xmmStr}`); throw new Error(`Unexpected error parsing: ${xmlStr}.Error: ${xmmStr}`);
} }
return xmlDoc; return xmlDoc;

View File

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

View File

@ -18,6 +18,7 @@
import { Mindmap } from '../..'; import { Mindmap } from '../..';
import INodeModel from '../model/INodeModel'; import INodeModel from '../model/INodeModel';
import LinkModel from '../model/LinkModel'; import LinkModel from '../model/LinkModel';
import NoteModel from '../model/NoteModel';
import Exporter from './Exporter'; import Exporter from './Exporter';
class TxtExporter extends Exporter { class TxtExporter extends Exporter {
@ -32,27 +33,27 @@ class TxtExporter extends Exporter {
const { mindmap } = this; const { mindmap } = this;
const branches = mindmap.getBranches(); const branches = mindmap.getBranches();
const txtStr = this.traverseBranch('', branches); const txtStr = this.traverseBranch('', '', branches);
return Promise.resolve(txtStr); return Promise.resolve(txtStr);
} }
private traverseBranch(prefix: string, branches: INodeModel[]) { private traverseBranch(indent: string, prefix: string, branches: INodeModel[]) {
let result = ''; let result = '';
branches branches
.filter((n) => n.getText() !== undefined)
.forEach((node, index) => { .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) => { node.getFeatures().forEach((f) => {
const type = f.getType(); const type = f.getType();
if (type === 'link') { 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`; result = `${result}\n`;
if (node.getChildren().filter((n) => n.getText() !== undefined).length > 0) { result += this.traverseBranch(`\t${indent}`, `${prefix}${index + 1}.`, node.getChildren());
result += this.traverseBranch(`\t${prefix}${index + 1}.`, node.getChildren());
}
}); });
return result; 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 EN from './en';
import DE from './de'; import DE from './de';
import FR from './fr'; import FR from './fr';
import RU from './ru';
const Bundle = { const Bundle = {
es: ES, es: ES,
en: EN, en: EN,
de: DE, de: DE,
fr: FR, fr: FR,
ru: RU,
}; };
export default Bundle; 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 { $assert, $defined } from '@wisemapping/core-js';
import PositionType from '../PositionType'; import PositionType from '../PositionType';
import SizeType from '../SizeType'; import SizeType from '../SizeType';
import ChildrenSorterStrategy from './ChildrenSorterStrategy';
class Node { class Node {
private _id: number; private _id: number;
@ -25,14 +26,14 @@ class Node {
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
_parent: Node; _parent: Node;
private _sorter: any; private _sorter: ChildrenSorterStrategy;
private _properties; private _properties;
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
_children: Node[]; _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(typeof id === 'number' && Number.isFinite(id), 'id can not be null');
$assert(size, 'size can not be null'); $assert(size, 'size can not be null');
$assert(position, 'position can not be null'); $assert(position, 'position can not be null');

View File

@ -24,7 +24,6 @@ class RootedTreeSet {
protected _children: Node[]; protected _children: Node[];
constructor() { constructor() {
this._rootNodes = []; this._rootNodes = [];
} }
@ -56,10 +55,9 @@ class RootedTreeSet {
*/ */
add(node: Node) { add(node: Node) {
$assert(node, 'node can not be null'); $assert(node, 'node can not be null');
$assert( if (this.find(node.getId(), false)) {
!this.find(node.getId(), false), throw new Error(`node already exits with this id. Id:${node.getId()}: ${this.dump()}`);
`node already exits with this id. Id:${node.getId()}: ${this.dump()}`, }
);
$assert(!node._children, 'node already added'); $assert(!node._children, 'node already added');
this._rootNodes.push(this._decodate(node)); this._rootNodes.push(this._decodate(node));
} }
@ -131,10 +129,11 @@ class RootedTreeSet {
break; break;
} }
} }
$assert(
validate ? result : true, if (validate && !result) {
`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`, throw new Error(`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`);
); }
return result; return result;
} }
@ -293,7 +292,7 @@ class RootedTreeSet {
} }
} }
_plot(canvas, node: Node, root?) { _plot(canvas, node: Node) {
const children = this.getChildren(node); const children = this.getChildren(node);
const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2; const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2;
const cy = node.getPosition().y + canvas.height / 2 - node.getSize().height / 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; 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 { removeFeature(feature: FeatureModel): void {
$assert(feature, 'feature can not be null'); $assert(feature, 'feature can not be null');
const size = this._features.length; const size = this._features.length;
@ -127,11 +122,8 @@ class NodeModel extends INodeModel {
return this._properties[key]; return this._properties[key];
} }
/**
* @return {mindplot.model.NodeModel} an identical clone of the NodeModel
*/
clone(): 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) => { result._children = this._children.map((node) => {
const cnode = node.clone() as NodeModel; const cnode = node.clone() as NodeModel;
cnode._parent = result; cnode._parent = result;
@ -143,12 +135,8 @@ class NodeModel extends INodeModel {
return result; return result;
} }
/**
* Similar to clone, assign new id to the elements ...
* @return {mindplot.model.NodeModel}
*/
deepCopy(): 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) => { result._children = this._children.map((node) => {
const cnode = (node as NodeModel).deepCopy(); const cnode = (node as NodeModel).deepCopy();
cnode._parent = result; cnode._parent = result;
@ -163,20 +151,12 @@ class NodeModel extends INodeModel {
return result; 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 { append(child: NodeModel): void {
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object'); $assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object');
this._children.push(child); this._children.push(child);
child._parent = this; 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 { removeChild(child: NodeModel): void {
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.'); $assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.');
this._children = this._children.filter((c) => c !== child); this._children = this._children.filter((c) => c !== child);

View File

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

View File

@ -20,7 +20,7 @@ import { Mindmap } from '../..';
import NodeModel from '../model/NodeModel'; import NodeModel from '../model/NodeModel';
import ModelCodeName from './ModelCodeName'; import ModelCodeName from './ModelCodeName';
import XMLMindmapSerializer from './XMLMindmapSerializer'; import XMLMindmapSerializer from './XMLMindmapSerializer';
import XMLSerializerPela from './XMLSerializerPela'; import XMLSerializerPela from './XMLSerializerTango';
class Beta2PelaMigrator implements XMLMindmapSerializer { class Beta2PelaMigrator implements XMLMindmapSerializer {
private _betaSerializer: 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 . // Position was not required in previous versions. Try to synthesize one .
const centralNode = mindmap.getBranches()[0]; const centralNode = mindmap.getBranches()[0];
const children = centralNode.getChildren(); 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 . // Position was not required in previous versions. Try to synthesize one .
let position = node.getPosition(); let position = node.getPosition();
if (!position) { if (!position) {

View File

@ -20,7 +20,6 @@ import ModelCodeName from './ModelCodeName';
import Beta2PelaMigrator from './Beta2PelaMigrator'; import Beta2PelaMigrator from './Beta2PelaMigrator';
import Pela2TangoMigrator from './Pela2TangoMigrator'; import Pela2TangoMigrator from './Pela2TangoMigrator';
import XMLSerializerBeta from './XMLSerializerBeta'; import XMLSerializerBeta from './XMLSerializerBeta';
import XMLSerializerPela from './XMLSerializerPela';
import XMLSerializerTango from './XMLSerializerTango'; import XMLSerializerTango from './XMLSerializerTango';
import Mindmap from '../model/Mindmap'; import Mindmap from '../model/Mindmap';
import XMLMindmapSerializer from './XMLMindmapSerializer'; import XMLMindmapSerializer from './XMLMindmapSerializer';
@ -35,7 +34,7 @@ const codeToSerializer: { codeName: string, serializer, migrator }[] = [
}, },
{ {
codeName: ModelCodeName.PELA, codeName: ModelCodeName.PELA,
serializer: XMLSerializerPela, serializer: XMLSerializerTango,
migrator: Beta2PelaMigrator, 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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';
/** class XMLSerializerTango implements XMLMindmapSerializer {
* This serializer works exactly the same way as for the former version Pela 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.
*/ */
class XMLSerializerTango extends XMLSerializerPela { 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; export default XMLSerializerTango;

View File

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

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