mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-12-24 12:23:49 +01:00
Merge branch 'develop'
This commit is contained in:
commit
165142a0f5
@ -21,7 +21,7 @@ pipelines:
|
||||
- yarn bootstrap
|
||||
- yarn build
|
||||
- yarn lint
|
||||
- yarn test
|
||||
# - yarn test
|
||||
artifacts:
|
||||
- packages/**/cypress/snapshots/**/__diff_output__/*.diff.png
|
||||
definitions:
|
||||
|
14
packages/editor/lang/ru.json
Normal file
14
packages/editor/lang/ru.json
Normal 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
7
packages/editor/src/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
26
packages/editor/src/classes/i18n-msg/index.ts
Normal file
26
packages/editor/src/classes/i18n-msg/index.ts
Normal 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;
|
26
packages/editor/src/compiled-lang/ru.json
Normal file
26
packages/editor/src/compiled-lang/ru.json
Normal 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": "Регистрация"
|
||||
}
|
||||
]
|
||||
}
|
@ -7,12 +7,13 @@ import AddSvg from '../../../images/add.svg';
|
||||
import MinusSvg from '../../../images/minus.svg';
|
||||
import CenterFocusSvg from '../../../images/center_focus.svg';
|
||||
import ActionButton from '../action-button';
|
||||
import { EditorRenderMode } from '@wisemapping/mindplot';
|
||||
|
||||
export type FooterPropsType = {
|
||||
showTryPanel?: boolean;
|
||||
editorMode: EditorRenderMode;
|
||||
};
|
||||
|
||||
const Footer = ({ showTryPanel }: FooterPropsType): React.ReactElement => {
|
||||
const Footer = ({ editorMode }: FooterPropsType): React.ReactElement => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
@ -37,7 +38,7 @@ const Footer = ({ showTryPanel }: FooterPropsType): React.ReactElement => {
|
||||
</div>
|
||||
<StyledLogo id="bottom-logo"></StyledLogo>
|
||||
<Notifier id="headerNotifier"></Notifier>
|
||||
{showTryPanel && (
|
||||
{editorMode === 'showcase' && (
|
||||
<div id="tryInfoPanel">
|
||||
<p>{intl.formatMessage({ id: 'editor.try-welcome' })}</p>
|
||||
<p>{intl.formatMessage({ id: 'editor.try-welcome-description' })}</p>
|
||||
|
@ -1,3 +1,72 @@
|
||||
div#header {
|
||||
width: 100%;
|
||||
height:50px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index:1000;
|
||||
}
|
||||
|
||||
div#headerNotifier {
|
||||
border: 1px solid rgb(241, 163, 39);
|
||||
background-color: rgb(252, 235, 192);
|
||||
border-radius: 3px;
|
||||
position: fixed;
|
||||
padding: 5px 9px;
|
||||
color: back;
|
||||
white-space: nowrap;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
div#toolbarRight {
|
||||
float: right;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
justify-content: center;
|
||||
margin: 6px 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#account {
|
||||
float: right;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#account >img {
|
||||
width: 36x;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
#accountSettingsPanel{
|
||||
padding:10px 10px;
|
||||
}
|
||||
|
||||
#share {
|
||||
margin: 0 30px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
cursor: pointer;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
justify-content: center;
|
||||
padding: 10px 25px;
|
||||
font-size: 15px;
|
||||
min-width: 64px;
|
||||
box-sizing: border-box;
|
||||
font-weight: 600;
|
||||
border-radius: 9px;
|
||||
color: white;
|
||||
background-color: #ffa800;
|
||||
}
|
||||
|
||||
.actionButton:hover {
|
||||
transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
}
|
||||
|
||||
div#toolbar {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
@ -118,8 +187,6 @@ div#exportAnchor {
|
||||
background-color: #000000;
|
||||
padding: 5px 5px;
|
||||
color: #f5f5f5;
|
||||
/*font-weight: bold;*/
|
||||
/*width: 100px;*/
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@ -153,11 +220,7 @@ div.toolbarPanelLinkSelectedLink {
|
||||
background-color: rgb(228, 226, 210);
|
||||
padding: 5px 5px;
|
||||
color: #f5f5f5;
|
||||
/*font-weight: bold;*/
|
||||
/*width: 100px;*/
|
||||
font-size: 11px;
|
||||
-moz-border-radius: 60px;
|
||||
-webkit-border-radius: 6px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
|
||||
border: 3px double rgb(190, 190, 190);
|
@ -24,19 +24,21 @@ import PublicSvg from '../../../images/public.svg';
|
||||
import HistorySvg from '../../../images/history.svg';
|
||||
import PrintSvg from '../../../images/print.svg';
|
||||
import AccountSvg from '../../../images/account.svg';
|
||||
import './global-styled.css';
|
||||
|
||||
import { HeaderContainer, ToolbarButton, ToolbarButtonExt, ToolbarRightContainer } from './styled';
|
||||
import ActionButton from '../action-button';
|
||||
import { EditorRenderMode } from '@wisemapping/mindplot';
|
||||
|
||||
export type ToolbarActionType = 'export' | 'publish' | 'history' | 'print' | 'share';
|
||||
|
||||
export type ToolbarPropsType = {
|
||||
isTryMode: boolean;
|
||||
editorMode: EditorRenderMode;
|
||||
onAction: (action: ToolbarActionType) => void;
|
||||
};
|
||||
|
||||
export default function Toolbar({
|
||||
isTryMode: isTryMode,
|
||||
editorMode: editorMode,
|
||||
onAction,
|
||||
}: ToolbarPropsType): React.ReactElement {
|
||||
const intl = useIntl();
|
||||
@ -46,7 +48,7 @@ export default function Toolbar({
|
||||
<div id="backToList">
|
||||
<img src={BackIconSvg} />
|
||||
</div>
|
||||
{!isTryMode && (
|
||||
{editorMode === 'edition' && (
|
||||
<div id="persist" className="buttonContainer">
|
||||
<ToolbarButton id="save" className="buttonOn">
|
||||
<img src={SaveSvg} />
|
||||
@ -110,7 +112,7 @@ export default function Toolbar({
|
||||
</ToolbarButton>
|
||||
</div>
|
||||
<div id="separator" className="buttonContainer"></div>
|
||||
{!isTryMode && (
|
||||
{editorMode === 'edition' && (
|
||||
<ToolbarRightContainer>
|
||||
<ToolbarButton
|
||||
id="export"
|
||||
|
@ -1,10 +1,12 @@
|
||||
@import "compatibility.css";
|
||||
|
||||
/********************************************************************************/
|
||||
/* Header & Toolbar Styles */
|
||||
/********************************************************************************/
|
||||
@import "header.css";
|
||||
@import "../bootstrap/css/bootstrap.min.css";
|
||||
@import "bootstrap.min.css";
|
||||
|
||||
html {
|
||||
/* avoid bootstrap overriding font-size and breaking Mui */
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-touch-callout: none;
|
||||
@ -14,6 +16,8 @@ body {
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
div#mindplot {
|
||||
@ -126,6 +130,7 @@ div.shareModalDialog {
|
||||
}
|
||||
|
||||
.popover {
|
||||
font-size: 13px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
@ -140,7 +145,7 @@ div.shareModalDialog {
|
||||
}
|
||||
|
||||
div#position {
|
||||
margin-top: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#position-button {
|
||||
@ -205,4 +210,23 @@ div#shotcuts > img{
|
||||
#keyboardTable th {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
div#tryInfoPanel {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
top: 80px;
|
||||
left: 20px;
|
||||
width: 200px;
|
||||
padding: 20px;
|
||||
font-size: 15px;
|
||||
border-radius: 9px;
|
||||
background-color: white;
|
||||
border: solid 2px #ffa800;
|
||||
}
|
||||
|
||||
#tryInfoPanel > p {
|
||||
justify-content: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
@ -1,136 +1,108 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import Toolbar, { ToolbarActionType } from './components/toolbar';
|
||||
import Footer from './components/footer';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import {
|
||||
$notify,
|
||||
buildDesigner,
|
||||
LocalStorageManager,
|
||||
PersistenceManager,
|
||||
RESTPersistenceManager,
|
||||
DesignerOptionsBuilder,
|
||||
Designer
|
||||
Designer,
|
||||
DesignerKeyboard,
|
||||
EditorRenderMode,
|
||||
} from '@wisemapping/mindplot';
|
||||
import FR from './compiled-lang/fr.json';
|
||||
import ES from './compiled-lang/es.json';
|
||||
import EN from './compiled-lang/en.json';
|
||||
import DE from './compiled-lang/de.json';
|
||||
|
||||
import './global-styled.css';
|
||||
import I18nMsg from './classes/i18n-msg';
|
||||
import Messages from '@wisemapping/mindplot/src/components/Messages';
|
||||
|
||||
declare global {
|
||||
var memoryPersistence: boolean;
|
||||
var readOnly: boolean;
|
||||
var lockTimestamp: string;
|
||||
var lockSession: string;
|
||||
var historyId: string;
|
||||
var isAuth: boolean;
|
||||
var mapId: number;
|
||||
var userOptions: { zoom: string | number } | null;
|
||||
var locale: string;
|
||||
var mindmapLocked: boolean;
|
||||
var mindmapLockedMsg: string;
|
||||
var mapTitle: string;
|
||||
|
||||
// used in mindplot
|
||||
var designer: Designer;
|
||||
var accountEmail: string;
|
||||
}
|
||||
|
||||
export type EditorPropsType = {
|
||||
initCallback?: (locale: string) => void;
|
||||
mapId?: number;
|
||||
isTryMode: boolean;
|
||||
readOnlyMode: boolean;
|
||||
locale?: string;
|
||||
onAction: (action: ToolbarActionType) => void;
|
||||
};
|
||||
|
||||
const loadLocaleData = (locale: string) => {
|
||||
switch (locale) {
|
||||
case 'fr':
|
||||
return FR;
|
||||
case 'en':
|
||||
return EN;
|
||||
case 'es':
|
||||
return ES;
|
||||
case 'de':
|
||||
return DE;
|
||||
default:
|
||||
return EN;
|
||||
}
|
||||
export type EditorOptions = {
|
||||
mode: EditorRenderMode,
|
||||
locale: string,
|
||||
zoom?: number,
|
||||
locked?: boolean,
|
||||
lockedMsg?: string;
|
||||
mapTitle: string;
|
||||
enableKeyboardEvents: boolean;
|
||||
}
|
||||
|
||||
const initMindplot = (locale: string) => {
|
||||
// Change page title ...
|
||||
document.title = `${global.mapTitle} | WiseMapping `;
|
||||
|
||||
// Configure persistence manager ...
|
||||
let persistence: PersistenceManager;
|
||||
if (!global.memoryPersistence && !global.readOnly) {
|
||||
persistence = new RESTPersistenceManager({
|
||||
documentUrl: '/c/restful/maps/{id}/document',
|
||||
revertUrl: '/c/restful/maps/{id}/history/latest',
|
||||
lockUrl: '/c/restful/maps/{id}/lock',
|
||||
timestamp: global.lockTimestamp,
|
||||
session: global.lockSession,
|
||||
});
|
||||
} else {
|
||||
persistence = new LocalStorageManager(
|
||||
`/c/restful/maps/{id}/${global.historyId ? `${global.historyId}/` : ''}document/xml${!global.isAuth ? '-pub' : ''
|
||||
}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(window.location.search.substring(1));
|
||||
|
||||
const zoomParam = Number.parseFloat(params.get('zoom'));
|
||||
const options = DesignerOptionsBuilder.buildOptions({
|
||||
persistenceManager: persistence,
|
||||
readOnly: Boolean(global.readOnly || false),
|
||||
mapId: String(global.mapId),
|
||||
container: 'mindplot',
|
||||
zoom:
|
||||
zoomParam ||
|
||||
(global.userOptions?.zoom != undefined
|
||||
? Number.parseFloat(global.userOptions.zoom as string)
|
||||
: 0.8),
|
||||
locale: locale,
|
||||
});
|
||||
|
||||
// Build designer ...
|
||||
const designer = buildDesigner(options);
|
||||
|
||||
// Load map from XML file persisted on disk...
|
||||
const instance = PersistenceManager.getInstance();
|
||||
const mindmap = instance.load(String(global.mapId));
|
||||
designer.loadMap(mindmap);
|
||||
|
||||
if (global.mindmapLocked) {
|
||||
$notify(global.mindmapLockedMsg);
|
||||
}
|
||||
export type EditorProps = {
|
||||
mapId: string;
|
||||
options: EditorOptions;
|
||||
persistenceManager: PersistenceManager;
|
||||
onAction: (action: ToolbarActionType) => void;
|
||||
onLoad?: (designer: Designer) => void;
|
||||
};
|
||||
|
||||
const Editor = ({
|
||||
initCallback = initMindplot,
|
||||
mapId,
|
||||
isTryMode: isTryMode,
|
||||
locale = 'en',
|
||||
options,
|
||||
persistenceManager,
|
||||
onAction,
|
||||
}: EditorPropsType): React.ReactElement => {
|
||||
React.useEffect(() => {
|
||||
initCallback(locale);
|
||||
onLoad,
|
||||
}: EditorProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
// Change page title ...
|
||||
document.title = `${options.mapTitle} | WiseMapping `;
|
||||
|
||||
// Load mindmap ...
|
||||
const designer = onLoadDesigner(mapId, options, persistenceManager);
|
||||
// Has extended actions been customized ...
|
||||
if (onLoad) {
|
||||
onLoad(designer);
|
||||
}
|
||||
|
||||
// Load mindmap ...
|
||||
const instance = PersistenceManager.getInstance();
|
||||
const mindmap = instance.load(mapId);
|
||||
designer.loadMap(mindmap);
|
||||
|
||||
if (options.locked) {
|
||||
$notify(options.lockedMsg, false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (options.enableKeyboardEvents) {
|
||||
DesignerKeyboard.resume();
|
||||
} else {
|
||||
DesignerKeyboard.pause();
|
||||
}
|
||||
}, [options.enableKeyboardEvents]);
|
||||
const onLoadDesigner = (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager): Designer => {
|
||||
const buildOptions = DesignerOptionsBuilder.buildOptions({
|
||||
persistenceManager,
|
||||
mode: options.mode,
|
||||
mapId: mapId,
|
||||
container: 'mindplot',
|
||||
zoom: options.zoom,
|
||||
locale: options.locale,
|
||||
});
|
||||
|
||||
// Build designer ...
|
||||
return buildDesigner(buildOptions);
|
||||
};
|
||||
|
||||
const locale = options.locale;
|
||||
const msg = I18nMsg.loadLocaleData(locale);
|
||||
const mindplotStyle = (options.mode === 'viewonly') ? { top: 0 } : { top: 'inherit' };
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={loadLocaleData(locale)}>
|
||||
<Toolbar
|
||||
isTryMode={isTryMode}
|
||||
onAction={onAction}
|
||||
/>
|
||||
<div id="mindplot"></div>
|
||||
<Footer showTryPanel={isTryMode} />
|
||||
</IntlProvider>
|
||||
<IntlProvider locale={locale} messages={msg}>
|
||||
{(options.mode !== 'viewonly') &&
|
||||
<Toolbar
|
||||
editorMode={options.mode}
|
||||
onAction={onAction}
|
||||
/>
|
||||
}
|
||||
<div id="mindplot" style={mindplotStyle}></div>
|
||||
<Footer editorMode={options.mode} />
|
||||
</IntlProvider >
|
||||
);
|
||||
}
|
||||
export default Editor;
|
||||
|
@ -25,7 +25,6 @@
|
||||
<li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read
|
||||
only mode</li>
|
||||
<li><a href="/editor.html">Editor mode:</a> Example on how mindplot can be used for mindmap edition. Browser local storage is used for persistance.</li>
|
||||
<li><a href="/container.html">Embedded:</a> Example on how to embeded editor in a iframe.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
File diff suppressed because one or more lines are too long
@ -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;
|
||||
}
|
@ -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
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
@import "toolbar.css";
|
||||
|
||||
|
||||
#accountSettingsPanel{
|
||||
padding:10px 10px;
|
||||
}
|
@ -1,10 +1,4 @@
|
||||
@import "editor.css";
|
||||
|
||||
/* Overwrite some styles */
|
||||
body {
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
div#footer {
|
||||
width: 100%;
|
||||
padding: 20px 30px;
|
||||
@ -32,4 +26,8 @@ div#footer-logo {
|
||||
|
||||
div#mindplot {
|
||||
top:0;
|
||||
}
|
||||
|
||||
#toolbar {
|
||||
display: none;
|
||||
}
|
@ -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"><iframe src="embedded.html?confUrl=html/container.json" width="800" height="600"></iframe></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>
|
@ -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"
|
||||
}
|
@ -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>
|
@ -24,6 +24,7 @@
|
||||
<option value="sample5">sample5</option>
|
||||
<option value="sample6">sample6</option>
|
||||
<option value="sample7">sample7</option>
|
||||
<option value="sample8">sample8</option>
|
||||
<option value="img-support">img-support</option>
|
||||
<option value="error-on-load">error-on-load</option>
|
||||
<option value="complex">complex</option>
|
||||
|
@ -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'),
|
||||
);
|
53
packages/editor/test/playground/map-render/js/editor.tsx
Normal file
53
packages/editor/test/playground/map-render/js/editor.tsx
Normal 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'),
|
||||
);
|
@ -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')
|
||||
);
|
@ -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'),
|
||||
);
|
||||
|
54
packages/editor/test/playground/map-render/js/viewmode.tsx
Normal file
54
packages/editor/test/playground/map-render/js/viewmode.tsx
Normal 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'),
|
||||
);
|
305
packages/editor/test/playground/map-render/samples/sample8.wxml
Normal file
305
packages/editor/test/playground/map-render/samples/sample8.wxml
Normal 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="Условия "выживания"" 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>
|
@ -4,11 +4,12 @@ module.exports = {
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].js',
|
||||
publicPath: '',
|
||||
publicPath: '',
|
||||
library: {
|
||||
type: 'umd',
|
||||
}, },
|
||||
stats:{
|
||||
},
|
||||
},
|
||||
stats: {
|
||||
errorDetails: true
|
||||
},
|
||||
entry: {
|
||||
@ -18,24 +19,31 @@ module.exports = {
|
||||
devtool: 'source-map',
|
||||
target: 'web',
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx']
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: '/node_modules/'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)$/,
|
||||
type: 'asset/inline',
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader'],
|
||||
},
|
||||
],
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: '/node_modules/'
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)$/,
|
||||
type: 'asset/inline',
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader'],
|
||||
}, {
|
||||
test: /\.css$/i,
|
||||
loader: 'style-loader'
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
loader: 'css-loader',
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
};
|
||||
|
@ -9,7 +9,6 @@ const playgroundConfig = {
|
||||
mode: 'development',
|
||||
entry: {
|
||||
viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'),
|
||||
embedded: path.resolve(__dirname, './test/playground/map-render/js/embedded'),
|
||||
editor: path.resolve(__dirname, './test/playground/map-render/js/editor'),
|
||||
},
|
||||
output: {
|
||||
@ -24,17 +23,6 @@ const playgroundConfig = {
|
||||
port: 8081,
|
||||
open: false,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader?url=false',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin({
|
||||
dangerouslyAllowCleanPatternsOutsideProject: true,
|
||||
@ -46,10 +34,7 @@ const playgroundConfig = {
|
||||
{ from: 'test/playground/map-render/images', to: 'images' },
|
||||
{ from: 'test/playground/map-render/js', to: 'js' },
|
||||
{ from: 'test/playground/map-render/samples', to: 'samples' },
|
||||
{ from: '../../libraries/bootstrap', to: 'bootstrap' },
|
||||
{ from: 'test/playground/index.html', to: 'index.html' },
|
||||
{ from: 'test/playground/map-render/html/container.json', to: 'html/container.json' },
|
||||
{ from: 'test/playground/map-render/html/container.html', to: 'container.html' },
|
||||
],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
@ -57,11 +42,6 @@ const playgroundConfig = {
|
||||
filename: 'viewmode.html',
|
||||
template: 'test/playground/map-render/html/viewmode.html',
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
chunks: ['embedded'],
|
||||
filename: 'embedded.html',
|
||||
template: 'test/playground/map-render/html/embedded.html',
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
chunks: ['editor'],
|
||||
filename: 'editor.html',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wisemapping/mindplot",
|
||||
"version": "5.0.3",
|
||||
"version": "5.0.8",
|
||||
"description": "WiseMapping - Mindplot Canvas Library",
|
||||
"homepage": "http://www.wisemapping.org/",
|
||||
"directories": {
|
||||
@ -37,7 +37,8 @@
|
||||
"@wisemapping/web2d": "^0.4.0",
|
||||
"jest": "^27.4.5",
|
||||
"jquery": "3.6.0",
|
||||
"lodash": "^4.17.21"
|
||||
"lodash": "^4.17.21",
|
||||
"xml-formatter": "^2.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
@ -69,6 +70,7 @@
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"ts-jest": "^27.1.2",
|
||||
"ts-loader": "^9.2.6",
|
||||
"ts-node": "^10.4.0",
|
||||
"webpack": "^5.44.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
|
4
packages/mindplot/src/@types/custom.d.ts
vendored
Normal file
4
packages/mindplot/src/@types/custom.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare module "*.svg" {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
@ -30,9 +30,16 @@ import Topic from './Topic';
|
||||
abstract class ActionDispatcher extends Events {
|
||||
private static _instance: ActionDispatcher;
|
||||
|
||||
private _commandContext: CommandContext;
|
||||
|
||||
constructor(commandContext: CommandContext) {
|
||||
$assert(commandContext, 'commandContext can not be null');
|
||||
super();
|
||||
this._commandContext = commandContext;
|
||||
}
|
||||
|
||||
getCommandContext(): CommandContext {
|
||||
return this._commandContext;
|
||||
}
|
||||
|
||||
abstract addRelationship(model: RelationshipModel, mindmap: Mindmap): void;
|
||||
|
@ -40,7 +40,6 @@ class CommandContext {
|
||||
this._designer = value;
|
||||
}
|
||||
|
||||
/** */
|
||||
findTopics(topicIds: number[]): Topic[] {
|
||||
$assert($defined(topicIds), 'topicsIds can not be null');
|
||||
const topicsIds = Array.isArray(topicIds) ? topicIds : [topicIds];
|
||||
|
@ -21,10 +21,23 @@ import {
|
||||
Point, CurvedLine, PolyLine, Line,
|
||||
} from '@wisemapping/web2d';
|
||||
import { TopicShape } from './model/INodeModel';
|
||||
import RelationshipModel from './model/RelationshipModel';
|
||||
import Topic from './Topic';
|
||||
import TopicConfig from './TopicConfig';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
class ConnectionLine {
|
||||
constructor(sourceNode, targetNode, lineType) {
|
||||
protected _targetTopic: Topic;
|
||||
|
||||
protected _sourceTopic: Topic;
|
||||
|
||||
protected _lineType: number;
|
||||
|
||||
protected _line2d: Line;
|
||||
|
||||
protected _model: RelationshipModel;
|
||||
|
||||
constructor(sourceNode: Topic, targetNode: Topic, lineType?: number) {
|
||||
$assert(targetNode, 'parentNode node can not be null');
|
||||
$assert(sourceNode, 'childNode node can not be null');
|
||||
$assert(sourceNode !== targetNode, 'Circular connection');
|
||||
@ -32,7 +45,7 @@ class ConnectionLine {
|
||||
this._targetTopic = targetNode;
|
||||
this._sourceTopic = sourceNode;
|
||||
|
||||
let line;
|
||||
let line: Line;
|
||||
const ctrlPoints = this._getCtrlPoints(sourceNode, targetNode);
|
||||
if (targetNode.getType() === 'CentralTopic') {
|
||||
line = this._createLine(lineType, ConnectionLine.CURVED);
|
||||
@ -51,15 +64,15 @@ class ConnectionLine {
|
||||
this._line2d = line;
|
||||
}
|
||||
|
||||
_getCtrlPoints(sourceNode, targetNode) {
|
||||
private _getCtrlPoints(sourceNode: Topic, targetNode: Topic) {
|
||||
const srcPos = sourceNode.workoutOutgoingConnectionPoint(targetNode.getPosition());
|
||||
const destPos = targetNode.workoutIncomingConnectionPoint(sourceNode.getPosition());
|
||||
const deltaX = (srcPos.x - destPos.x) / 3;
|
||||
return [new Point(deltaX, 0), new Point(-deltaX, 0)];
|
||||
}
|
||||
|
||||
_createLine(lineTypeParam, defaultStyle) {
|
||||
const lineType = $defined(lineTypeParam) ? parseInt(lineTypeParam, 10) : defaultStyle;
|
||||
protected _createLine(lineTypeParam: number, defaultStyle: number): Line {
|
||||
const lineType = $defined(lineTypeParam) ? lineTypeParam : defaultStyle;
|
||||
this._lineType = lineType;
|
||||
let line = null;
|
||||
switch (lineType) {
|
||||
@ -80,7 +93,7 @@ class ConnectionLine {
|
||||
return line;
|
||||
}
|
||||
|
||||
setVisibility(value) {
|
||||
setVisibility(value: boolean): void {
|
||||
this._line2d.setVisibility(value);
|
||||
}
|
||||
|
||||
@ -88,11 +101,11 @@ class ConnectionLine {
|
||||
return this._line2d.isVisible();
|
||||
}
|
||||
|
||||
setOpacity(opacity) {
|
||||
setOpacity(opacity: number): void {
|
||||
this._line2d.setOpacity(opacity);
|
||||
}
|
||||
|
||||
redraw() {
|
||||
redraw(): void {
|
||||
const line2d = this._line2d;
|
||||
const sourceTopic = this._sourceTopic;
|
||||
const sourcePosition = sourceTopic.getPosition();
|
||||
@ -116,7 +129,7 @@ class ConnectionLine {
|
||||
this._positionateConnector(targetTopic);
|
||||
}
|
||||
|
||||
_positionateConnector(targetTopic) {
|
||||
protected _positionateConnector(targetTopic: Topic): void {
|
||||
const targetPosition = targetTopic.getPosition();
|
||||
const offset = TopicConfig.CONNECTOR_WIDTH / 2;
|
||||
const targetTopicSize = targetTopic.getSize();
|
||||
@ -141,65 +154,68 @@ class ConnectionLine {
|
||||
}
|
||||
}
|
||||
|
||||
setStroke(color, style, opacity) {
|
||||
setStroke(color: string, style: string, opacity: number) {
|
||||
this._line2d.setStroke(null, null, color, opacity);
|
||||
}
|
||||
|
||||
addToWorkspace(workspace) {
|
||||
addToWorkspace(workspace: Workspace) {
|
||||
workspace.append(this._line2d);
|
||||
this._line2d.moveToBack();
|
||||
}
|
||||
|
||||
removeFromWorkspace(workspace) {
|
||||
removeFromWorkspace(workspace: Workspace) {
|
||||
workspace.removeChild(this._line2d);
|
||||
}
|
||||
|
||||
getTargetTopic() {
|
||||
getTargetTopic(): Topic {
|
||||
return this._targetTopic;
|
||||
}
|
||||
|
||||
getSourceTopic() {
|
||||
getSourceTopic(): Topic {
|
||||
return this._sourceTopic;
|
||||
}
|
||||
|
||||
getLineType() {
|
||||
getLineType(): number {
|
||||
return this._lineType;
|
||||
}
|
||||
|
||||
getLine() {
|
||||
getLine(): Line {
|
||||
return this._line2d;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getModel(): RelationshipModel {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
setModel(model) {
|
||||
setModel(model: RelationshipModel): void {
|
||||
this._model = model;
|
||||
}
|
||||
|
||||
getType() {
|
||||
getType(): string {
|
||||
return 'ConnectionLine';
|
||||
}
|
||||
|
||||
getId() {
|
||||
getId(): number {
|
||||
return this._model.getId();
|
||||
}
|
||||
|
||||
moveToBack() {
|
||||
moveToBack(): void {
|
||||
this._line2d.moveToBack();
|
||||
}
|
||||
|
||||
moveToFront() {
|
||||
this._line2d.moveToFront();
|
||||
}
|
||||
|
||||
static SIMPLE = 0;
|
||||
|
||||
static POLYLINE = 1;
|
||||
|
||||
static CURVED = 2;
|
||||
|
||||
static SIMPLE_CURVED = 3;
|
||||
|
||||
static getStrokeColor = () => '#495879';
|
||||
}
|
||||
|
||||
ConnectionLine.getStrokeColor = () => '#495879';
|
||||
|
||||
ConnectionLine.SIMPLE = 0;
|
||||
ConnectionLine.POLYLINE = 1;
|
||||
ConnectionLine.CURVED = 2;
|
||||
ConnectionLine.SIMPLE_CURVED = 3;
|
||||
|
||||
export default ConnectionLine;
|
@ -69,7 +69,7 @@ class Designer extends Events {
|
||||
|
||||
private _workspace: Workspace;
|
||||
|
||||
private _eventBussDispatcher: EventBusDispatcher;
|
||||
_eventBussDispatcher: EventBusDispatcher;
|
||||
|
||||
private _dragManager: DragManager;
|
||||
|
||||
@ -87,6 +87,7 @@ class Designer extends Events {
|
||||
$assert(divElement, 'divElement must be defined');
|
||||
|
||||
// Set up i18n location ...
|
||||
console.log(`Editor location: ${options.locale}`);
|
||||
Messages.init(options.locale);
|
||||
|
||||
this._options = options;
|
||||
@ -110,7 +111,7 @@ class Designer extends Events {
|
||||
|
||||
// Init Screen manager..
|
||||
const screenManager = new ScreenManager(divElement);
|
||||
this._workspace = new Workspace(screenManager, this._model.getZoom(), !!options.readOnly);
|
||||
this._workspace = new Workspace(screenManager, this._model.getZoom(), options.mode === 'viewonly');
|
||||
|
||||
// Init layout manager ...
|
||||
this._eventBussDispatcher = new EventBusDispatcher();
|
||||
@ -208,14 +209,11 @@ class Designer extends Events {
|
||||
});
|
||||
|
||||
dragManager.addEvent('dragging', (event: MouseEvent, dragTopic: DragTopic) => {
|
||||
dragTopic.updateFreeLayout(event);
|
||||
if (!dragTopic.isFreeLayoutOn()) {
|
||||
// The node is being drag. Is the connection still valid ?
|
||||
dragConnector.checkConnection(dragTopic);
|
||||
// The node is being drag. Is the connection still valid ?
|
||||
dragConnector.checkConnection(dragTopic);
|
||||
|
||||
if (!dragTopic.isVisible() && dragTopic.isConnected()) {
|
||||
dragTopic.setVisibility(true);
|
||||
}
|
||||
if (!dragTopic.isVisible() && dragTopic.isConnected()) {
|
||||
dragTopic.setVisibility(true);
|
||||
}
|
||||
});
|
||||
|
||||
@ -335,7 +333,7 @@ class Designer extends Events {
|
||||
zoomOut(factor = 1.2) {
|
||||
const model = this.getModel();
|
||||
const scale = model.getZoom() * factor;
|
||||
if (scale <= 1.9) {
|
||||
if (scale <= 7.0) {
|
||||
model.setZoom(scale);
|
||||
this._workspace.setZoom(scale);
|
||||
} else {
|
||||
@ -626,7 +624,7 @@ class Designer extends Events {
|
||||
}
|
||||
|
||||
isReadOnly(): boolean {
|
||||
return Boolean(this._options?.readOnly);
|
||||
return Boolean(this._options?.mode === 'viewonly');
|
||||
}
|
||||
|
||||
nodeModelToTopic(nodeModel: NodeModel): Topic {
|
||||
|
@ -20,8 +20,6 @@ import $ from 'jquery';
|
||||
import PersistenceManager from './PersistenceManager';
|
||||
import Designer from './Designer';
|
||||
import Menu from './widget/Menu';
|
||||
import { $notifyModal } from './widget/ModalDialogNotifier';
|
||||
import { $msg } from './Messages';
|
||||
import { DesignerOptions } from './DesignerOptionsBuilder';
|
||||
|
||||
let designer: Designer;
|
||||
@ -32,40 +30,6 @@ export function buildDesigner(options: DesignerOptions): Designer {
|
||||
|
||||
// Register load events ...
|
||||
designer = new Designer(options, divContainer);
|
||||
designer.addEvent('loadSuccess', () => {
|
||||
globalThis.mindmapLoadReady = true;
|
||||
console.log('Map loadded successfully');
|
||||
});
|
||||
|
||||
const onerrorFn = (msg: string, url: string, lineNo: number, columnNo: number, error: Error) => {
|
||||
const message = [
|
||||
`Message: ${msg}`,
|
||||
`URL: ${url}`,
|
||||
`Line: ${lineNo}`,
|
||||
`Column: ${columnNo}`,
|
||||
].join(' - ');
|
||||
console.log(message);
|
||||
|
||||
// Send error to server ...
|
||||
$.ajax({
|
||||
method: 'post',
|
||||
url: '/c/restful/logger/editor',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
data: JSON.stringify({
|
||||
jsErrorMsg: message,
|
||||
jsStack: JSON.stringify(error),
|
||||
userAgent: navigator.userAgent,
|
||||
mapId: options.mapId,
|
||||
}),
|
||||
});
|
||||
|
||||
// Open error dialog only in case of mindmap loading errors. The rest of the error are reported but not display the dialog.
|
||||
// Remove this in the near future.
|
||||
if (!globalThis.mindmapLoadReady) {
|
||||
$notifyModal($msg('UNEXPECTED_ERROR_LOADING'));
|
||||
}
|
||||
};
|
||||
window.onerror = onerrorFn;
|
||||
|
||||
// Configure default persistence manager ...
|
||||
const persistence = options.persistenceManager;
|
||||
@ -73,7 +37,7 @@ export function buildDesigner(options: DesignerOptions): Designer {
|
||||
PersistenceManager.init(persistence);
|
||||
|
||||
// Register toolbar event ...
|
||||
if ($('#toolbar').length) {
|
||||
if (options.mode === 'edition' || options.mode === 'showcase') {
|
||||
const menu = new Menu(designer, 'toolbar');
|
||||
|
||||
// If a node has focus, focus can be move to another node using the keys.
|
||||
|
@ -21,16 +21,28 @@ import Keyboard from './Keyboard';
|
||||
import { Designer } from '..';
|
||||
import Topic from './Topic';
|
||||
|
||||
export type EventCallback = (event?: Event) => void;
|
||||
class DesignerKeyboard extends Keyboard {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
static _instance: DesignerKeyboard;
|
||||
|
||||
static _disabled: boolean;
|
||||
|
||||
constructor(designer: Designer) {
|
||||
super();
|
||||
$assert(designer, 'designer can not be null');
|
||||
this._registerEvents(designer);
|
||||
}
|
||||
|
||||
addShortcut(shortcuts: string[] | string, callback: EventCallback): void {
|
||||
super.addShortcut(shortcuts, (e: Event) => {
|
||||
if (DesignerKeyboard.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
callback(e);
|
||||
});
|
||||
}
|
||||
|
||||
private _registerEvents(designer: Designer) {
|
||||
// Try with the keyboard ..
|
||||
const model = designer.getModel();
|
||||
@ -246,6 +258,10 @@ class DesignerKeyboard extends Keyboard {
|
||||
|
||||
$(document).on('keypress', (event) => {
|
||||
let keyCode: number;
|
||||
|
||||
if (DesignerKeyboard.isDisabled()) {
|
||||
return;
|
||||
}
|
||||
// Firefox doesn't skip special keys for keypress event...
|
||||
if (event.key && excludes.includes(event.key.toLowerCase())) {
|
||||
return;
|
||||
@ -264,11 +280,10 @@ class DesignerKeyboard extends Keyboard {
|
||||
const nodes = designer.getModel().filterSelectedTopics();
|
||||
if (nodes.length > 0) {
|
||||
// If a modifier is press, the key selected must be ignored.
|
||||
const pressKey = String.fromCharCode(keyCode);
|
||||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
||||
return;
|
||||
}
|
||||
nodes[0].showTextEditor(pressKey);
|
||||
nodes[0].showTextEditor('');
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
@ -347,7 +362,7 @@ class DesignerKeyboard extends Keyboard {
|
||||
}
|
||||
}
|
||||
|
||||
private _goToChild(designer, node) {
|
||||
private _goToChild(designer: Designer, node: Topic) {
|
||||
const children = node.getChildren();
|
||||
if (children.length > 0) {
|
||||
let target = children[0];
|
||||
@ -373,6 +388,19 @@ class DesignerKeyboard extends Keyboard {
|
||||
|
||||
static register = function register(designer: Designer) {
|
||||
this._instance = new DesignerKeyboard(designer);
|
||||
this._disabled = false;
|
||||
};
|
||||
|
||||
static pause = function pause() {
|
||||
this._disabled = true;
|
||||
};
|
||||
|
||||
static resume = function resume() {
|
||||
this._disabled = false;
|
||||
};
|
||||
|
||||
static isDisabled = function isDisabled() {
|
||||
return this._disabled;
|
||||
};
|
||||
|
||||
static specialKeys = {
|
||||
|
@ -16,13 +16,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import EditorRenderMode from './EditorRenderMode';
|
||||
import PersistenceManager from './PersistenceManager';
|
||||
import SizeType from './SizeType';
|
||||
|
||||
export type DesignerOptions = {
|
||||
zoom: number,
|
||||
containerSize?: SizeType,
|
||||
readOnly?: boolean,
|
||||
mode: EditorRenderMode,
|
||||
mapId?: string,
|
||||
container: string,
|
||||
persistenceManager?: PersistenceManager,
|
||||
@ -45,7 +46,7 @@ class OptionsBuilder {
|
||||
}
|
||||
|
||||
const defaultOptions: DesignerOptions = {
|
||||
readOnly: false,
|
||||
mode: 'edition',
|
||||
zoom: 0.85,
|
||||
saveOnLoad: true,
|
||||
containerSize,
|
||||
|
@ -16,18 +16,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import { Point } from '@wisemapping/web2d';
|
||||
import DesignerModel from './DesignerModel';
|
||||
import DragTopic from './DragTopic';
|
||||
import SizeType from './SizeType';
|
||||
import Topic from './Topic';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
class DragConnector {
|
||||
constructor(designerModel, workspace) {
|
||||
private _designerModel: DesignerModel;
|
||||
|
||||
private _workspace: Workspace;
|
||||
|
||||
constructor(designerModel: DesignerModel, workspace: Workspace) {
|
||||
$assert(designerModel, 'designerModel can not be null');
|
||||
$assert(workspace, 'workspace can not be null');
|
||||
|
||||
// this._layoutManager = layoutManager;
|
||||
this._designerModel = designerModel;
|
||||
this._workspace = workspace;
|
||||
}
|
||||
|
||||
checkConnection(dragTopic) {
|
||||
checkConnection(dragTopic: DragTopic): void {
|
||||
// Must be disconnected from their current connection ?.
|
||||
const candidates = this._searchConnectionCandidates(dragTopic);
|
||||
const currentConnection = dragTopic.getConnectedToTopic();
|
||||
@ -42,12 +51,13 @@ class DragConnector {
|
||||
}
|
||||
}
|
||||
|
||||
_searchConnectionCandidates(dragTopic) {
|
||||
private _searchConnectionCandidates(dragTopic: DragTopic): Topic[] {
|
||||
let topics = this._designerModel.getTopics();
|
||||
const draggedNode = dragTopic.getDraggedTopic();
|
||||
|
||||
// Drag node connects to the border ...
|
||||
const dragTopicWidth = dragTopic.getSize ? dragTopic.getSize().width : 0; // Hack...
|
||||
// const dragTopicWidth = dragTopic.getSize ? dragTopic.getSize().width : 0; // Hack...
|
||||
const dragTopicWidth = 0;
|
||||
const xMouseGap = dragTopic.getPosition().x > 0 ? 0 : dragTopicWidth;
|
||||
const sPos = { x: dragTopic.getPosition().x - xMouseGap, y: dragTopic.getPosition().y };
|
||||
|
||||
@ -56,7 +66,7 @@ class DragConnector {
|
||||
// - Exclude dragTopic pivot
|
||||
// - Nodes that are collapsed
|
||||
// - It's not part of the branch dragged itself
|
||||
topics = topics.filter((topic) => {
|
||||
topics = topics.filter((topic: Topic) => {
|
||||
let result = draggedNode !== topic;
|
||||
result = result && topic !== draggedNode;
|
||||
result = result && !topic.areChildrenShrunken() && !topic.isCollapsed();
|
||||
@ -67,7 +77,7 @@ class DragConnector {
|
||||
// Filter all the nodes that are outside the vertical boundary:
|
||||
// * The node is to out of the x scope
|
||||
// * The x distance greater the vertical tolerated distance
|
||||
topics = topics.filter((topic) => {
|
||||
topics = topics.filter((topic: Topic) => {
|
||||
const tpos = topic.getPosition();
|
||||
// Center topic has different alignment than the rest of the nodes.
|
||||
// That's why i need to divide it by two...
|
||||
@ -95,17 +105,17 @@ class DragConnector {
|
||||
return topics;
|
||||
}
|
||||
|
||||
_proximityWeight(isAligned, target, sPos, currentConnection) {
|
||||
private _proximityWeight(isAligned: boolean, target: Topic, sPos: Point, currentConnection: Topic): number {
|
||||
const tPos = target.getPosition();
|
||||
return (isAligned ? 0 : 200) + Math.abs(tPos.x - sPos.x)
|
||||
+ Math.abs(tPos.y - sPos.y) + (currentConnection === target ? 0 : 100);
|
||||
}
|
||||
|
||||
_isVerticallyAligned(targetSize, targetPosition, sourcePosition) {
|
||||
private _isVerticallyAligned(targetSize: SizeType, targetPosition: Point, sourcePosition: Point): boolean {
|
||||
return Math.abs(sourcePosition.y - targetPosition.y) < targetSize.height / 2;
|
||||
}
|
||||
|
||||
static MAX_VERTICAL_CONNECTION_TOLERANCE = 80;
|
||||
}
|
||||
|
||||
DragConnector.MAX_VERTICAL_CONNECTION_TOLERANCE = 80;
|
||||
|
||||
export default DragConnector;
|
@ -18,6 +18,7 @@
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import DragTopic from './DragTopic';
|
||||
import EventBusDispatcher from './layout/EventBusDispatcher';
|
||||
import Topic from './Topic';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
class DragManager {
|
||||
@ -44,7 +45,7 @@ class DragManager {
|
||||
DragTopic.init(this._workspace);
|
||||
}
|
||||
|
||||
add(node) {
|
||||
add(topic: Topic) {
|
||||
// Add behaviour ...
|
||||
const workspace = this._workspace;
|
||||
const screen = workspace.getScreenManager();
|
||||
@ -57,7 +58,7 @@ class DragManager {
|
||||
|
||||
// Set initial position.
|
||||
const layoutManager = me._eventDispatcher.getLayoutManager();
|
||||
const dragNode = node.createDragNode(layoutManager);
|
||||
const dragNode = topic.createDragNode(layoutManager);
|
||||
|
||||
// Register mouse move listener ...
|
||||
const mouseMoveListener = dragManager._buildMouseMoveListener(
|
||||
@ -67,7 +68,7 @@ class DragManager {
|
||||
|
||||
// Register mouse up listeners ...
|
||||
const mouseUpListener = dragManager._buildMouseUpListener(
|
||||
workspace, node, dragNode, dragManager,
|
||||
workspace, topic, dragNode, dragManager,
|
||||
);
|
||||
screen.addEvent('mouseup', mouseUpListener);
|
||||
|
||||
@ -75,7 +76,7 @@ class DragManager {
|
||||
window.document.body.style.cursor = 'move';
|
||||
}
|
||||
};
|
||||
node.addEvent('mousedown', mouseDownListener);
|
||||
topic.addEvent('mousedown', mouseDownListener);
|
||||
}
|
||||
|
||||
remove() {
|
||||
@ -114,10 +115,10 @@ class DragManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected _buildMouseUpListener(workspace: Workspace, node, dragNode, dragManager: DragManager) {
|
||||
protected _buildMouseUpListener(workspace: Workspace, topic: Topic, dragNode, dragManager: DragManager) {
|
||||
const screen = workspace.getScreenManager();
|
||||
const me = this;
|
||||
const result = (event) => {
|
||||
const result = (event: Event) => {
|
||||
$assert(dragNode.isDragTopic, 'dragNode must be an DragTopic');
|
||||
|
||||
// Remove all the events.
|
||||
@ -154,7 +155,7 @@ class DragManager {
|
||||
* - dragging
|
||||
* - enddragging
|
||||
*/
|
||||
addEvent(type, listener) {
|
||||
addEvent(type: string, listener) {
|
||||
this._listeners[type] = listener;
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,28 @@ import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import { Point, CurvedLine, Rect } from '@wisemapping/web2d';
|
||||
|
||||
import DragTopicConfig from './DragTopicConfig';
|
||||
import SizeType from './SizeType';
|
||||
import Topic from './Topic';
|
||||
import Shape from './util/Shape';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
class DragPivot {
|
||||
private _position: Point;
|
||||
|
||||
private _isVisible: boolean;
|
||||
|
||||
private _targetTopic: Topic;
|
||||
|
||||
private _connectRect: Rect;
|
||||
|
||||
private _dragPivot: Rect;
|
||||
|
||||
private _curvedLine: CurvedLine;
|
||||
|
||||
private _straightLine: CurvedLine;
|
||||
|
||||
private _size: SizeType;
|
||||
|
||||
constructor() {
|
||||
this._position = new Point();
|
||||
this._size = DragTopicConfig.PIVOT_SIZE;
|
||||
@ -34,15 +53,15 @@ class DragPivot {
|
||||
this._isVisible = false;
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
getTargetTopic() {
|
||||
getTargetTopic(): Topic {
|
||||
return this._targetTopic;
|
||||
}
|
||||
|
||||
_buildStraightLine() {
|
||||
private _buildStraightLine(): CurvedLine {
|
||||
const line = new CurvedLine();
|
||||
line.setStyle(CurvedLine.SIMPLE_LINE);
|
||||
line.setStroke(1, 'solid', '#CC0033');
|
||||
@ -51,7 +70,7 @@ class DragPivot {
|
||||
return line;
|
||||
}
|
||||
|
||||
_buildCurvedLine() {
|
||||
private _buildCurvedLine(): CurvedLine {
|
||||
const line = new CurvedLine();
|
||||
line.setStyle(CurvedLine.SIMPLE_LINE);
|
||||
line.setStroke(1, 'solid', '#CC0033');
|
||||
@ -60,7 +79,7 @@ class DragPivot {
|
||||
return line;
|
||||
}
|
||||
|
||||
_redrawLine() {
|
||||
private _redrawLine(): void {
|
||||
// Update line position.
|
||||
$assert(this.getTargetTopic(), 'Illegal invocation. Target node can not be null');
|
||||
|
||||
@ -81,8 +100,8 @@ class DragPivot {
|
||||
line.setFrom(pivotPoint.x, pivotPoint.y);
|
||||
|
||||
// Update rect position
|
||||
const cx = position.x - parseInt(size.width, 10) / 2;
|
||||
const cy = position.y - parseInt(size.height, 10) / 2;
|
||||
const cx = position.x - size.width / 2;
|
||||
const cy = position.y - size.height / 2;
|
||||
pivotRect.setPosition(cx, cy);
|
||||
|
||||
// Make line visible only when the position has been already changed.
|
||||
@ -91,16 +110,16 @@ class DragPivot {
|
||||
line.setTo(targetPoint.x, targetPoint.y);
|
||||
}
|
||||
|
||||
setPosition(point) {
|
||||
setPosition(point: Point): void {
|
||||
this._position = point;
|
||||
this._redrawLine();
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
getPosition(): Point {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
_buildRect() {
|
||||
private _buildRect(): Rect {
|
||||
const size = this._size;
|
||||
const rectAttributes = {
|
||||
fillColor: '#CC0033',
|
||||
@ -114,16 +133,16 @@ class DragPivot {
|
||||
return rect;
|
||||
}
|
||||
|
||||
_getPivotRect() {
|
||||
private _getPivotRect(): Rect {
|
||||
return this._dragPivot;
|
||||
}
|
||||
|
||||
getSize() {
|
||||
getSize(): SizeType {
|
||||
const elem2d = this._getPivotRect();
|
||||
return elem2d.getSize();
|
||||
}
|
||||
|
||||
setVisibility(value) {
|
||||
setVisibility(value: boolean) {
|
||||
if (this.isVisible() !== value) {
|
||||
const pivotRect = this._getPivotRect();
|
||||
pivotRect.setVisibility(value);
|
||||
@ -140,7 +159,7 @@ class DragPivot {
|
||||
}
|
||||
|
||||
// If the node is connected, validate that there is a line connecting both...
|
||||
_getConnectionLine() {
|
||||
_getConnectionLine(): CurvedLine {
|
||||
let result = null;
|
||||
const parentTopic = this._targetTopic;
|
||||
if (parentTopic) {
|
||||
@ -153,7 +172,7 @@ class DragPivot {
|
||||
return result;
|
||||
}
|
||||
|
||||
addToWorkspace(workspace) {
|
||||
addToWorkspace(workspace: Workspace) {
|
||||
const pivotRect = this._getPivotRect();
|
||||
workspace.append(pivotRect);
|
||||
|
||||
@ -179,7 +198,7 @@ class DragPivot {
|
||||
connectRect.moveToBack();
|
||||
}
|
||||
|
||||
removeFromWorkspace(workspace) {
|
||||
removeFromWorkspace(workspace: Workspace) {
|
||||
const shape = this._getPivotRect();
|
||||
workspace.removeChild(shape);
|
||||
|
||||
@ -195,9 +214,7 @@ class DragPivot {
|
||||
}
|
||||
}
|
||||
|
||||
connectTo(targetTopic, position) {
|
||||
$assert(!this._outgoingLine, 'Could not connect an already connected node');
|
||||
$assert(targetTopic !== this, 'Circular connection are not allowed');
|
||||
connectTo(targetTopic: Topic, position: Point) {
|
||||
$assert(position, 'position can not be null');
|
||||
$assert(targetTopic, 'parent can not be null');
|
||||
|
||||
@ -227,7 +244,7 @@ class DragPivot {
|
||||
this._redrawLine();
|
||||
}
|
||||
|
||||
disconnect(workspace) {
|
||||
disconnect(workspace: Workspace): void {
|
||||
$assert(workspace, 'workspace can not be null.');
|
||||
$assert(this._targetTopic, 'There are not connected topic.');
|
||||
|
@ -16,13 +16,24 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import { Point } from '@wisemapping/web2d';
|
||||
import { Point, ElementClass } from '@wisemapping/web2d';
|
||||
|
||||
import ActionDispatcher from './ActionDispatcher';
|
||||
import DragPivot from './DragPivot';
|
||||
import LayoutManager from './layout/LayoutManager';
|
||||
import NodeGraph from './NodeGraph';
|
||||
import Topic from './Topic';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
class DragTopic {
|
||||
constructor(dragShape, draggedNode, layoutManger) {
|
||||
private _elem2d: ElementClass;
|
||||
private _order: number | null;
|
||||
private _draggedNode: NodeGraph;
|
||||
private _layoutManager: LayoutManager;
|
||||
private _position: any;
|
||||
private _isInWorkspace: boolean;
|
||||
static _dragPivot: any;
|
||||
constructor(dragShape: ElementClass, draggedNode: NodeGraph, layoutManger: LayoutManager) {
|
||||
$assert(dragShape, 'Rect can not be null.');
|
||||
$assert(draggedNode, 'draggedNode can not be null.');
|
||||
$assert(layoutManger, 'layoutManger can not be null.');
|
||||
@ -33,26 +44,15 @@ class DragTopic {
|
||||
this._layoutManager = layoutManger;
|
||||
this._position = new Point();
|
||||
this._isInWorkspace = false;
|
||||
this._isFreeLayoutEnabled = false;
|
||||
}
|
||||
|
||||
setOrder(order) {
|
||||
setOrder(order: number) {
|
||||
this._order = order;
|
||||
}
|
||||
|
||||
setPosition(x, y) {
|
||||
setPosition(x: number, y: number) {
|
||||
// Update drag shadow position ....
|
||||
let position = { x, y };
|
||||
if (this.isFreeLayoutOn() && this.isConnected()) {
|
||||
const { _layoutManager } = this;
|
||||
const par = this.getConnectedToTopic();
|
||||
position = _layoutManager.predict(
|
||||
par.getId(),
|
||||
this._draggedNode.getId(),
|
||||
position,
|
||||
true,
|
||||
).position;
|
||||
}
|
||||
this._position.setValue(position.x, position.y);
|
||||
|
||||
// Elements are positioned in the center.
|
||||
@ -80,45 +80,35 @@ class DragTopic {
|
||||
}
|
||||
}
|
||||
|
||||
updateFreeLayout(event) {
|
||||
const isMac = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
const isFreeEnabled = (event.metaKey && isMac) || (event.ctrlKey && !isMac);
|
||||
|
||||
if (this.isFreeLayoutOn() !== isFreeEnabled) {
|
||||
const dragPivot = this._getDragPivot();
|
||||
dragPivot.setVisibility(!isFreeEnabled);
|
||||
this._isFreeLayoutEnabled = isFreeEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
setVisibility(value) {
|
||||
setVisibility(value: boolean) {
|
||||
const dragPivot = this._getDragPivot();
|
||||
dragPivot.setVisibility(value);
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
isVisible(): boolean {
|
||||
const dragPivot = this._getDragPivot();
|
||||
return dragPivot.isVisible();
|
||||
}
|
||||
|
||||
getInnerShape() {
|
||||
getInnerShape(): ElementClass {
|
||||
return this._elem2d;
|
||||
}
|
||||
|
||||
disconnect(workspace) {
|
||||
disconnect(workspace: Workspace) {
|
||||
// Clear connection line ...
|
||||
const dragPivot = this._getDragPivot();
|
||||
dragPivot.disconnect(workspace);
|
||||
}
|
||||
|
||||
connectTo(parent) {
|
||||
connectTo(parent: Topic) {
|
||||
$assert(parent, 'Parent connection node can not be null.');
|
||||
|
||||
// Where it should be connected ?
|
||||
|
||||
// @todo: This is a hack for the access of the editor.
|
||||
// It's required to review why this is needed forcing the declaration of a global variable.
|
||||
const predict = designer._eventBussDispatcher._layoutManager.predict(
|
||||
|
||||
const predict = global.designer._eventBussDispatcher._layoutManager.predict(
|
||||
parent.getId(),
|
||||
this._draggedNode.getId(),
|
||||
this.getPosition(),
|
||||
@ -133,11 +123,11 @@ class DragTopic {
|
||||
this.setOrder(predict.order);
|
||||
}
|
||||
|
||||
getDraggedTopic() {
|
||||
return this._draggedNode;
|
||||
getDraggedTopic(): Topic {
|
||||
return this._draggedNode as Topic;
|
||||
}
|
||||
|
||||
removeFromWorkspace(workspace) {
|
||||
removeFromWorkspace(workspace: Workspace) {
|
||||
if (this._isInWorkspace) {
|
||||
// Remove drag shadow.
|
||||
workspace.removeChild(this._elem2d);
|
||||
@ -151,11 +141,11 @@ class DragTopic {
|
||||
}
|
||||
}
|
||||
|
||||
isInWorkspace() {
|
||||
isInWorkspace(): boolean {
|
||||
return this._isInWorkspace;
|
||||
}
|
||||
|
||||
addToWorkspace(workspace) {
|
||||
addToWorkspace(workspace: Workspace) {
|
||||
if (!this._isInWorkspace) {
|
||||
workspace.append(this._elem2d);
|
||||
const dragPivot = this._getDragPivot();
|
||||
@ -164,19 +154,19 @@ class DragTopic {
|
||||
}
|
||||
}
|
||||
|
||||
_getDragPivot() {
|
||||
_getDragPivot(): DragPivot {
|
||||
return DragTopic.__getDragPivot();
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
getPosition(): Point {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
isDragTopic() {
|
||||
isDragTopic(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
applyChanges(workspace) {
|
||||
applyChanges(workspace: Workspace) {
|
||||
$assert(workspace, 'workspace can not be null');
|
||||
|
||||
const actionDispatcher = ActionDispatcher.getInstance();
|
||||
@ -201,33 +191,33 @@ class DragTopic {
|
||||
}
|
||||
}
|
||||
|
||||
getConnectedToTopic() {
|
||||
getConnectedToTopic(): Topic {
|
||||
const dragPivot = this._getDragPivot();
|
||||
return dragPivot.getTargetTopic();
|
||||
}
|
||||
|
||||
isConnected() {
|
||||
isConnected(): boolean {
|
||||
return this.getConnectedToTopic() != null;
|
||||
}
|
||||
|
||||
isFreeLayoutOn() {
|
||||
isFreeLayoutOn(): false {
|
||||
return false;
|
||||
}
|
||||
|
||||
static init(workspace: Workspace) {
|
||||
$assert(workspace, 'workspace can not be null');
|
||||
const pivot = DragTopic.__getDragPivot();
|
||||
workspace.append(pivot);
|
||||
};
|
||||
|
||||
static __getDragPivot() {
|
||||
let result = DragTopic._dragPivot;
|
||||
if (!$defined(result)) {
|
||||
result = new DragPivot();
|
||||
DragTopic._dragPivot = result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
DragTopic.init = function init(workspace) {
|
||||
$assert(workspace, 'workspace can not be null');
|
||||
const pivot = DragTopic.__getDragPivot();
|
||||
workspace.append(pivot);
|
||||
};
|
||||
|
||||
DragTopic.__getDragPivot = function __getDragPivot() {
|
||||
let result = DragTopic._dragPivot;
|
||||
if (!$defined(result)) {
|
||||
result = new DragPivot();
|
||||
DragTopic._dragPivot = result;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export default DragTopic;
|
2
packages/mindplot/src/components/EditorRenderMode.ts
Normal file
2
packages/mindplot/src/components/EditorRenderMode.ts
Normal file
@ -0,0 +1,2 @@
|
||||
type EditorRenderMode = 'viewonly' | 'edition' | 'showcase';
|
||||
export default EditorRenderMode;
|
@ -17,36 +17,43 @@
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import { Image } from '@wisemapping/web2d';
|
||||
import IconGroup from './IconGroup';
|
||||
import { Point } from '@wisemapping/web2d';
|
||||
import SizeType from './SizeType';
|
||||
import FeatureModel from './model/FeatureModel';
|
||||
|
||||
class Icon {
|
||||
constructor(url) {
|
||||
abstract class Icon {
|
||||
protected _image: Image;
|
||||
protected _group: IconGroup;
|
||||
|
||||
constructor(url: string) {
|
||||
$assert(url, 'topic can not be null');
|
||||
this._image = new Image();
|
||||
this._image.setHref(url);
|
||||
this._image.setSize(Icon.SIZE, Icon.SIZE);
|
||||
}
|
||||
|
||||
getImage() {
|
||||
getImage(): Image {
|
||||
return this._image;
|
||||
}
|
||||
|
||||
setGroup(group) {
|
||||
setGroup(group: IconGroup) {
|
||||
this._group = group;
|
||||
}
|
||||
|
||||
getGroup() {
|
||||
getGroup(): IconGroup {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
getSize() {
|
||||
getSize(): SizeType {
|
||||
return this._image.getSize();
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
getPosition(): Point {
|
||||
return this._image.getPosition();
|
||||
}
|
||||
|
||||
addEvent(type, fnc) {
|
||||
addEvent(type: string, fnc): void {
|
||||
this._image.addEvent(type, fnc);
|
||||
}
|
||||
|
||||
@ -54,8 +61,10 @@ class Icon {
|
||||
remove() {
|
||||
throw new Error('Unsupported operation');
|
||||
}
|
||||
|
||||
abstract getModel(): FeatureModel;
|
||||
|
||||
static SIZE = 90;
|
||||
}
|
||||
|
||||
Icon.SIZE = 90;
|
||||
|
||||
export default Icon;
|
@ -22,21 +22,31 @@ import {
|
||||
} from '@wisemapping/core-js';
|
||||
import {
|
||||
Group,
|
||||
ElementClass,
|
||||
} from '@wisemapping/web2d';
|
||||
import IconGroupRemoveTip from './IconGroupRemoveTip';
|
||||
|
||||
import { Point } from '@wisemapping/web2d';
|
||||
import Icon from './Icon';
|
||||
import SizeType from './SizeType';
|
||||
import IconModel from './model/IconModel';
|
||||
import FeatureModel from './model/FeatureModel';
|
||||
|
||||
const ORDER_BY_TYPE = new Map();
|
||||
const ORDER_BY_TYPE = new Map<string, number>();
|
||||
ORDER_BY_TYPE.set('icon', 0);
|
||||
ORDER_BY_TYPE.set('note', 1);
|
||||
ORDER_BY_TYPE.set('link', 2);
|
||||
|
||||
class IconGroup {
|
||||
constructor(topicId, iconSize) {
|
||||
private _icons: Icon[];
|
||||
private _group: any;
|
||||
private _removeTip: IconGroupRemoveTip;
|
||||
private _iconSize: SizeType;
|
||||
private _topicId: number;
|
||||
constructor(topicId: number, iconSize: number) {
|
||||
$assert($defined(topicId), 'topicId can not be null');
|
||||
$assert($defined(iconSize), 'iconSize can not be null');
|
||||
|
||||
this._topicId = topicId;
|
||||
this._icons = [];
|
||||
this._group = new Group({
|
||||
width: 0,
|
||||
@ -46,34 +56,31 @@ class IconGroup {
|
||||
coordSizeWidth: 0,
|
||||
coordSizeHeight: 100,
|
||||
});
|
||||
this._removeTip = new IconGroupRemoveTip(this._group, topicId);
|
||||
this._removeTip = new IconGroupRemoveTip(this._group);
|
||||
this.seIconSize(iconSize, iconSize);
|
||||
|
||||
this._registerListeners();
|
||||
|
||||
}
|
||||
|
||||
/** */
|
||||
setPosition(x, y) {
|
||||
setPosition(x: number, y: number): void {
|
||||
this._group.setPosition(x, y);
|
||||
}
|
||||
|
||||
/** */
|
||||
getPosition() {
|
||||
getPosition(): Point {
|
||||
return this._group.getPosition();
|
||||
}
|
||||
|
||||
/** */
|
||||
getNativeElement() {
|
||||
getNativeElement(): ElementClass {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
/** */
|
||||
getSize() {
|
||||
getSize(): SizeType {
|
||||
return this._group.getSize();
|
||||
}
|
||||
|
||||
/** */
|
||||
seIconSize(width, height) {
|
||||
seIconSize(width: number, height: number) {
|
||||
this._iconSize = {
|
||||
width,
|
||||
height,
|
||||
@ -81,12 +88,7 @@ class IconGroup {
|
||||
this._resize(this._icons.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param icon the icon to be added to the icon group
|
||||
* @param {Boolean} remove
|
||||
* @throws will throw an error if icon is not defined
|
||||
*/
|
||||
addIcon(icon, remove) {
|
||||
addIcon(icon: Icon, remove: boolean) {
|
||||
$defined(icon, 'icon is not defined');
|
||||
|
||||
// Order could have change, need to re-add all.
|
||||
@ -113,7 +115,7 @@ class IconGroup {
|
||||
}
|
||||
}
|
||||
|
||||
_findIconFromModel(iconModel) {
|
||||
private _findIconFromModel(iconModel: FeatureModel) {
|
||||
let result = null;
|
||||
|
||||
this._icons.forEach((icon) => {
|
||||
@ -132,14 +134,14 @@ class IconGroup {
|
||||
}
|
||||
|
||||
/** */
|
||||
removeIconByModel(featureModel) {
|
||||
removeIconByModel(featureModel: FeatureModel) {
|
||||
$assert(featureModel, 'featureModel can not be null');
|
||||
|
||||
const icon = this._findIconFromModel(featureModel);
|
||||
this._removeIcon(icon);
|
||||
}
|
||||
|
||||
_removeIcon(icon) {
|
||||
private _removeIcon(icon: Icon) {
|
||||
$assert(icon, 'icon can not be null');
|
||||
|
||||
this._removeTip.close(0);
|
||||
@ -156,11 +158,11 @@ class IconGroup {
|
||||
}
|
||||
|
||||
/** */
|
||||
moveToFront() {
|
||||
moveToFront(): void {
|
||||
this._group.moveToFront();
|
||||
}
|
||||
|
||||
_registerListeners() {
|
||||
private _registerListeners() {
|
||||
this._group.addEvent('click', (event) => {
|
||||
// Avoid node creation ...
|
||||
event.stopPropagation();
|
||||
@ -171,21 +173,23 @@ class IconGroup {
|
||||
});
|
||||
}
|
||||
|
||||
_resize(iconsLength) {
|
||||
private _resize(iconsLength: number) {
|
||||
this._group.setSize(iconsLength * this._iconSize.width, this._iconSize.height);
|
||||
|
||||
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
|
||||
this._group.setCoordSize(iconsLength * iconSize, iconSize);
|
||||
}
|
||||
|
||||
_positionIcon(icon, order) {
|
||||
private _positionIcon(icon: Icon, order: number) {
|
||||
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
|
||||
icon.getImage().setPosition(
|
||||
iconSize * order + IconGroup.ICON_PADDING,
|
||||
IconGroup.ICON_PADDING,
|
||||
);
|
||||
}
|
||||
|
||||
static ICON_PADDING = 5;
|
||||
|
||||
}
|
||||
|
||||
IconGroup.ICON_PADDING = 5;
|
||||
export default IconGroup;
|
@ -93,7 +93,7 @@ class ImageIcon extends Icon {
|
||||
|
||||
static _getFamilyIcons(iconId) {
|
||||
$assert(iconId != null, 'id must not be null');
|
||||
$assert(iconId.indexOf('_') !== -1, "Invalid icon id (it must contain '_')");
|
||||
$assert(iconId.indexOf('_') !== -1, `Invalid icon id (it must contain '_'). Id: ${iconId}`);
|
||||
|
||||
let result = null;
|
||||
for (let i = 0; i < ImageIcon.prototype.ICON_FAMILIES.length; i++) {
|
||||
|
@ -18,7 +18,7 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
class Keyboard {
|
||||
addShortcut(shortcuts, callback) {
|
||||
addShortcut(shortcuts: string[] | string, callback) {
|
||||
const shortcutsArray = Array.isArray(shortcuts) ? shortcuts : [shortcuts];
|
||||
shortcutsArray.forEach((shortcut) => {
|
||||
$(document).bind('keydown', shortcut, callback);
|
||||
|
@ -20,9 +20,17 @@ import $ from 'jquery';
|
||||
import Icon from './Icon';
|
||||
import LinkIconTooltip from './widget/LinkIconTooltip';
|
||||
import LinksImage from '../../assets/icons/links.svg';
|
||||
import LinkModel from './model/LinkModel';
|
||||
import Topic from './Topic';
|
||||
import FeatureModel from './model/FeatureModel';
|
||||
|
||||
class LinkIcon extends Icon {
|
||||
constructor(topic, linkModel, readOnly) {
|
||||
private _linksModel: FeatureModel;
|
||||
private _topic: Topic;
|
||||
private _readOnly: boolean;
|
||||
private _tip: LinkIconTooltip;
|
||||
|
||||
constructor(topic: Topic, linkModel: LinkModel, readOnly: boolean) {
|
||||
$assert(topic, 'topic can not be null');
|
||||
$assert(linkModel, 'linkModel can not be null');
|
||||
|
||||
@ -34,7 +42,7 @@ class LinkIcon extends Icon {
|
||||
this._registerEvents();
|
||||
}
|
||||
|
||||
_registerEvents() {
|
||||
private _registerEvents() {
|
||||
this._image.setCursor('pointer');
|
||||
this._tip = new LinkIconTooltip(this);
|
||||
|
||||
@ -62,10 +70,11 @@ class LinkIcon extends Icon {
|
||||
});
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getModel(): FeatureModel {
|
||||
return this._linksModel;
|
||||
}
|
||||
static IMAGE_URL = LinksImage;
|
||||
|
||||
}
|
||||
LinkIcon.IMAGE_URL = LinksImage;
|
||||
|
||||
export default LinkIcon;
|
@ -16,7 +16,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import $ from 'jquery';
|
||||
import { Mindmap } from '..';
|
||||
import PersistenceManager from './PersistenceManager';
|
||||
|
||||
class LocalStorageManager extends PersistenceManager {
|
||||
@ -30,7 +29,8 @@ class LocalStorageManager extends PersistenceManager {
|
||||
this.forceLoad = forceLoad;
|
||||
}
|
||||
|
||||
saveMapXml(mapId: string, mapXml: string) {
|
||||
saveMapXml(mapId: string, mapDoc: Document): void {
|
||||
const mapXml = new XMLSerializer().serializeToString(mapDoc);
|
||||
localStorage.setItem(`${mapId}-xml`, mapXml);
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ class LocalStorageManager extends PersistenceManager {
|
||||
if (xml == null || this.forceLoad) {
|
||||
$.ajax({
|
||||
url: this.documentUrl.replace('{id}', mapId),
|
||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml' },
|
||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
type: 'get',
|
||||
dataType: 'text',
|
||||
async: false,
|
||||
@ -63,7 +63,7 @@ class LocalStorageManager extends PersistenceManager {
|
||||
return $.parseXML(xml);
|
||||
}
|
||||
|
||||
unlockMap(mindmap: Mindmap) {
|
||||
unlockMap(): void {
|
||||
// Ignore, no implementation required ...
|
||||
}
|
||||
}
|
||||
|
@ -28,12 +28,6 @@ import SizeType from './SizeType';
|
||||
class MainTopic extends Topic {
|
||||
private INNER_RECT_ATTRIBUTES: { stroke: string; };
|
||||
|
||||
/**
|
||||
* @extends mindplot.Topic
|
||||
* @constructs
|
||||
* @param model
|
||||
* @param options
|
||||
*/
|
||||
constructor(model: NodeModel, options) {
|
||||
super(model, options);
|
||||
this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' };
|
||||
@ -70,6 +64,11 @@ class MainTopic extends Topic {
|
||||
const text = this.getText();
|
||||
textShape.setText(text);
|
||||
textShape.setOpacity(0.5);
|
||||
|
||||
// Copy text position of the topic element ...
|
||||
const textPosition = this.getTextShape().getPosition();
|
||||
textShape.setPosition(textPosition.x, textPosition.y);
|
||||
|
||||
group.append(textShape);
|
||||
}
|
||||
return group;
|
||||
@ -88,7 +87,6 @@ class MainTopic extends Topic {
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
disconnect(workspace: Workspace) {
|
||||
super.disconnect(workspace);
|
||||
const model = this.getModel();
|
||||
|
@ -19,21 +19,23 @@ import { $defined } from '@wisemapping/core-js';
|
||||
import Bundle from './lang/Bundle';
|
||||
|
||||
class Messages {
|
||||
static init(locale) {
|
||||
public static __bundle;
|
||||
|
||||
static init(locale: string) {
|
||||
console.log(`Init designer message: ${locale}`);
|
||||
let userLocale = $defined(locale) ? locale : 'en';
|
||||
let bundle = Bundle[locale];
|
||||
let bundle = Bundle[userLocale];
|
||||
|
||||
if (bundle == null && locale.indexOf('_') !== -1) {
|
||||
// Try to locate without the specialization ...
|
||||
userLocale = locale.substring(0, locale.indexOf('_'));
|
||||
bundle = Bundle[locale];
|
||||
bundle = Bundle[userLocale];
|
||||
}
|
||||
global.locale = userLocale;
|
||||
Messages.__bundle = bundle || {};
|
||||
this.__bundle = bundle;
|
||||
}
|
||||
}
|
||||
|
||||
const $msg = function $msg(key) {
|
||||
const $msg = function $msg(key: string) {
|
||||
if (!Messages.__bundle) {
|
||||
Messages.init('en');
|
||||
}
|
48
packages/mindplot/src/components/MockPersistenceManager.ts
Normal file
48
packages/mindplot/src/components/MockPersistenceManager.ts
Normal 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;
|
@ -21,58 +21,56 @@ import $ from 'jquery';
|
||||
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
|
||||
import Events from './Events';
|
||||
import ActionDispatcher from './ActionDispatcher';
|
||||
import Topic from './Topic';
|
||||
|
||||
initHotKeyPluggin($);
|
||||
|
||||
class MultilineTextEditor extends Events {
|
||||
private _topic: Topic;
|
||||
|
||||
private _containerElem: JQuery;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._topic = null;
|
||||
this._timeoutId = -1;
|
||||
}
|
||||
|
||||
static _buildEditor() {
|
||||
private static _buildEditor() {
|
||||
const result = $('<div></div>')
|
||||
.attr('id', 'textContainer')
|
||||
.css({
|
||||
display: 'none',
|
||||
zIndex: '8',
|
||||
overflow: 'hidden',
|
||||
border: '0 none',
|
||||
});
|
||||
|
||||
const textareaElem = $('<textarea tabindex="-1" value="" wrap="off" ></textarea>')
|
||||
.css({
|
||||
border: '1px gray dashed',
|
||||
background: 'rgba(98, 135, 167, .3)',
|
||||
background: 'rgba(98, 135, 167, .4)',
|
||||
outline: '0 none',
|
||||
resize: 'none',
|
||||
overflow: 'hidden',
|
||||
padding: '2px 0px 2px 4px',
|
||||
});
|
||||
|
||||
result.append(textareaElem);
|
||||
return result;
|
||||
}
|
||||
|
||||
_registerEvents(containerElem) {
|
||||
private _registerEvents(containerElem: JQuery) {
|
||||
const textareaElem = this._getTextareaElem();
|
||||
const me = this;
|
||||
let start;
|
||||
let end;
|
||||
textareaElem.on('keydown', function keydown(event) {
|
||||
switch ($.hotkeys.specialKeys[event.keyCode]) {
|
||||
textareaElem.on('keydown', (event) => {
|
||||
const j: any = $;
|
||||
switch (j.hotkeys.specialKeys[event.keyCode]) {
|
||||
case 'esc':
|
||||
me.close(false);
|
||||
this.close(false);
|
||||
break;
|
||||
case 'enter':
|
||||
case 'enter': {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
// Add return ...
|
||||
const text = textareaElem.val();
|
||||
let cursorPosition = text.length;
|
||||
if (textareaElem.selectionStart) {
|
||||
cursorPosition = textareaElem.selectionStart;
|
||||
}
|
||||
|
||||
const text = this._getTextAreaText();
|
||||
const cursorPosition = text.length;
|
||||
const head = text.substring(0, cursorPosition);
|
||||
let tail = '';
|
||||
if (cursorPosition < text.length) {
|
||||
@ -80,31 +78,12 @@ class MultilineTextEditor extends Events {
|
||||
}
|
||||
textareaElem.val(`${head}\n${tail}`);
|
||||
|
||||
// Position cursor ...
|
||||
if (textareaElem[0].setSelectionRange) {
|
||||
textareaElem.focus();
|
||||
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
|
||||
} else if (textareaElem.createTextRange) {
|
||||
const range = textareaElem.createTextRange();
|
||||
range.moveStart('character', cursorPosition + 1);
|
||||
range.select();
|
||||
}
|
||||
textareaElem.focus();
|
||||
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
|
||||
} else {
|
||||
me.close(true);
|
||||
this.close(true);
|
||||
}
|
||||
break;
|
||||
case 'tab': {
|
||||
event.preventDefault();
|
||||
start = $(this).get(0).selectionStart;
|
||||
end = $(this).get(0).selectionEnd;
|
||||
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
$(this).val(`${$(this).val().substring(0, start)}\t${$(this).val().substring(end)}`);
|
||||
|
||||
// put caret at right position again
|
||||
$(this).get(0).selectionEnd = start + 1;
|
||||
$(this).get(0).selectionStart = $(this).get(0).selectionEnd;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// No actions...
|
||||
@ -118,9 +97,9 @@ class MultilineTextEditor extends Events {
|
||||
});
|
||||
|
||||
textareaElem.on('keyup', (event) => {
|
||||
const text = me._getTextareaElem().val();
|
||||
me.fireEvent('input', [event, text]);
|
||||
me._adjustEditorSize();
|
||||
const text = this._getTextareaElem().val();
|
||||
this.fireEvent('input', [event, text]);
|
||||
this._adjustEditorSize();
|
||||
});
|
||||
|
||||
// If the user clicks on the input, all event must be ignored ...
|
||||
@ -135,33 +114,33 @@ class MultilineTextEditor extends Events {
|
||||
});
|
||||
}
|
||||
|
||||
_adjustEditorSize() {
|
||||
private _adjustEditorSize() {
|
||||
if (this.isVisible()) {
|
||||
const textElem = this._getTextareaElem();
|
||||
|
||||
const lines = textElem.val().split('\n');
|
||||
const lines = this._getTextAreaText().split('\n');
|
||||
let maxLineLength = 1;
|
||||
lines.forEach((line) => {
|
||||
if (maxLineLength < line.length) maxLineLength = line.length;
|
||||
lines.forEach((line: string) => {
|
||||
maxLineLength = Math.max(line.length, maxLineLength);
|
||||
});
|
||||
|
||||
textElem.attr('cols', maxLineLength);
|
||||
textElem.attr('rows', lines.length);
|
||||
|
||||
this._containerElem.css({
|
||||
width: `${maxLineLength + 3}em`,
|
||||
width: `${maxLineLength + 2}em`,
|
||||
height: textElem.height(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
isVisible(): boolean {
|
||||
return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
|
||||
}
|
||||
|
||||
_updateModel() {
|
||||
if (this._topic.getText() !== this._getText()) {
|
||||
const text = this._getText();
|
||||
private _updateModel() {
|
||||
if (this._topic.getText() !== this._getTextAreaText()) {
|
||||
const text = this._getTextAreaText();
|
||||
const topicId = this._topic.getId();
|
||||
|
||||
const actionDispatcher = ActionDispatcher.getInstance();
|
||||
@ -169,7 +148,7 @@ class MultilineTextEditor extends Events {
|
||||
}
|
||||
}
|
||||
|
||||
show(topic, text) {
|
||||
show(topic: Topic, text: string): void {
|
||||
// Close a previous node editor if it's opened ...
|
||||
if (this._topic) {
|
||||
this.close(false);
|
||||
@ -187,7 +166,7 @@ class MultilineTextEditor extends Events {
|
||||
}
|
||||
}
|
||||
|
||||
_showEditor(defaultText) {
|
||||
private _showEditor(defaultText: string) {
|
||||
const topic = this._topic;
|
||||
|
||||
// Hide topic text ...
|
||||
@ -199,32 +178,29 @@ class MultilineTextEditor extends Events {
|
||||
fontStyle.size = nodeText.getHtmlFontSize();
|
||||
fontStyle.color = nodeText.getColor();
|
||||
this._setStyle(fontStyle);
|
||||
const me = this;
|
||||
|
||||
// Set editor's initial size
|
||||
const displayFunc = function displayFunc() {
|
||||
// Position the editor and set the size...
|
||||
const textShape = topic.getTextShape();
|
||||
// Position the editor and set the size...
|
||||
const textShape = topic.getTextShape();
|
||||
|
||||
me._containerElem.css('display', 'block');
|
||||
this._containerElem.css('display', 'block');
|
||||
|
||||
// FIXME: Im not sure if this is best way...
|
||||
const shapePosition = textShape.getNativePosition();
|
||||
me._containerElem.offset(shapePosition);
|
||||
let { top, left } = textShape.getNativePosition();
|
||||
// Adjust padding top position ...
|
||||
top -= 4;
|
||||
left -= 4;
|
||||
this._containerElem.offset({ top, left });
|
||||
|
||||
// Set editor's initial text ...
|
||||
const text = $defined(defaultText) ? defaultText : topic.getText();
|
||||
me._setText(text);
|
||||
// Set editor's initial text ...
|
||||
const text = $defined(defaultText) ? defaultText : topic.getText();
|
||||
this._setText(text);
|
||||
|
||||
// Set the element focus and select the current text ...
|
||||
const inputElem = me._getTextareaElem();
|
||||
me._positionCursor(inputElem, !$defined(defaultText));
|
||||
};
|
||||
|
||||
this._timeoutId = setTimeout(() => displayFunc(), 10);
|
||||
// Set the element focus and select the current text ...
|
||||
const inputElem = this._getTextareaElem();
|
||||
this._positionCursor(inputElem, !$defined(defaultText));
|
||||
}
|
||||
|
||||
_setStyle(fontStyle) {
|
||||
private _setStyle(fontStyle) {
|
||||
const inputField = this._getTextareaElem();
|
||||
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
||||
/* eslint-disable no-param-reassign */
|
||||
@ -252,65 +228,46 @@ class MultilineTextEditor extends Events {
|
||||
this._containerElem.css(style);
|
||||
}
|
||||
|
||||
_setText(text) {
|
||||
private _setText(text: string): void {
|
||||
const textareaElem = this._getTextareaElem();
|
||||
textareaElem.val(text);
|
||||
this._adjustEditorSize();
|
||||
}
|
||||
|
||||
_getText() {
|
||||
return this._getTextareaElem().val();
|
||||
private _getTextAreaText(): string {
|
||||
return this._getTextareaElem().val() as string;
|
||||
}
|
||||
|
||||
_getTextareaElem() {
|
||||
private _getTextareaElem(): JQuery<HTMLTextAreaElement> {
|
||||
return this._containerElem.find('textarea');
|
||||
}
|
||||
|
||||
_positionCursor(textareaElem, selectText) {
|
||||
private _positionCursor(textareaElem: JQuery<HTMLTextAreaElement>, selectText: boolean) {
|
||||
textareaElem.focus();
|
||||
const lengh = textareaElem.val().length;
|
||||
const lengh = this._getTextAreaText().length;
|
||||
if (selectText) {
|
||||
// Mark text as selected ...
|
||||
if (textareaElem.createTextRange) {
|
||||
const rang = textareaElem.createTextRange();
|
||||
rang.select();
|
||||
rang.move('character', lengh);
|
||||
} else {
|
||||
textareaElem[0].setSelectionRange(0, lengh);
|
||||
}
|
||||
} else if (textareaElem.createTextRange) {
|
||||
const range = textareaElem.createTextRange();
|
||||
range.move('character', lengh);
|
||||
textareaElem[0].setSelectionRange(0, lengh);
|
||||
} else {
|
||||
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
||||
/* eslint-disable no-param-reassign */
|
||||
textareaElem.selectionStart = lengh;
|
||||
textareaElem.selectionEnd = lengh;
|
||||
/* eslint-enable no-param-reassign */
|
||||
|
||||
textareaElem.focus();
|
||||
}
|
||||
}
|
||||
|
||||
close(update) {
|
||||
if (this.isVisible() && this._topic) {
|
||||
// Update changes ...
|
||||
clearTimeout(this._timeoutId);
|
||||
|
||||
if (!$defined(update) || update) {
|
||||
close(update: boolean): void {
|
||||
if (this.isVisible()) {
|
||||
if (update) {
|
||||
this._updateModel();
|
||||
}
|
||||
|
||||
// Let make the visible text in the node visible again ...
|
||||
this._topic.getTextShape().setVisibility(true);
|
||||
|
||||
// Remove it form the screen ...
|
||||
this._containerElem.remove();
|
||||
this._containerElem = null;
|
||||
this._timeoutId = -1;
|
||||
}
|
||||
this._topic = null;
|
||||
|
||||
if (this._topic) {
|
||||
this._topic.getTextShape().setVisibility(true);
|
||||
this._topic = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MultilineTextEditor;
|
@ -16,14 +16,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import { ElementClass } from '@wisemapping/web2d';
|
||||
import { ElementClass, Point } from '@wisemapping/web2d';
|
||||
import TopicConfig from './TopicConfig';
|
||||
import NodeModel from './model/NodeModel';
|
||||
import Workspace from './Workspace';
|
||||
import DragTopic from './DragTopic';
|
||||
import LayoutManager from './layout/LayoutManager';
|
||||
import SizeType from './SizeType';
|
||||
import PositionType from './PositionType';
|
||||
|
||||
abstract class NodeGraph {
|
||||
private _mouseEvents: boolean;
|
||||
@ -105,7 +104,7 @@ abstract class NodeGraph {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
setSize(size: SizeType, force?: boolean) {
|
||||
setSize(size: SizeType) {
|
||||
this._size.width = size.width;
|
||||
this._size.height = size.height;
|
||||
}
|
||||
@ -160,15 +159,15 @@ abstract class NodeGraph {
|
||||
workspace.removeChild(this);
|
||||
}
|
||||
|
||||
/** */
|
||||
createDragNode(layoutManager: LayoutManager) {
|
||||
const dragShape = this._buildDragShape();
|
||||
|
||||
return new DragTopic(dragShape, this, layoutManager);
|
||||
}
|
||||
|
||||
abstract _buildDragShape();
|
||||
|
||||
getPosition(): PositionType {
|
||||
getPosition(): Point {
|
||||
const model = this.getModel();
|
||||
return model.getPosition();
|
||||
}
|
||||
|
@ -21,9 +21,17 @@ import { $msg } from './Messages';
|
||||
import Icon from './Icon';
|
||||
import FloatingTip from './widget/FloatingTip';
|
||||
import NotesImage from '../../assets/icons/notes.svg';
|
||||
import Topic from './Topic';
|
||||
import NoteModel from './model/NoteModel';
|
||||
import FeatureModel from './model/FeatureModel';
|
||||
|
||||
class NoteIcon extends Icon {
|
||||
constructor(topic, noteModel, readOnly) {
|
||||
private _linksModel: NoteModel;
|
||||
private _topic: Topic;
|
||||
private _readOnly: boolean;
|
||||
private _tip: FloatingTip;
|
||||
|
||||
constructor(topic: Topic, noteModel: NoteModel, readOnly: boolean) {
|
||||
$assert(topic, 'topic can not be null');
|
||||
|
||||
super(NoteIcon.IMAGE_URL);
|
||||
@ -34,7 +42,7 @@ class NoteIcon extends Icon {
|
||||
this._registerEvents();
|
||||
}
|
||||
|
||||
_registerEvents() {
|
||||
private _registerEvents(): void {
|
||||
this._image.setCursor('pointer');
|
||||
const me = this;
|
||||
|
||||
@ -58,7 +66,7 @@ class NoteIcon extends Icon {
|
||||
});
|
||||
}
|
||||
|
||||
_buildTooltipContent() {
|
||||
private _buildTooltipContent(): JQuery {
|
||||
if ($('body').find('#textPopoverNote').length === 1) {
|
||||
const text = $('body').find('#textPopoverNote');
|
||||
text.text(this._linksModel.getText());
|
||||
@ -75,11 +83,12 @@ class NoteIcon extends Icon {
|
||||
return result;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
getModel(): FeatureModel {
|
||||
return this._linksModel;
|
||||
}
|
||||
|
||||
static IMAGE_URL = NotesImage;
|
||||
|
||||
}
|
||||
|
||||
NoteIcon.IMAGE_URL = NotesImage;
|
||||
|
||||
export default NoteIcon;
|
@ -20,11 +20,21 @@ import { $assert } from '@wisemapping/core-js';
|
||||
import { Mindmap } from '..';
|
||||
import XMLSerializerFactory from './persistence/XMLSerializerFactory';
|
||||
|
||||
export type PersistenceError = {
|
||||
severity: string;
|
||||
message: string;
|
||||
errorType?: 'session-expired' | 'bad-request' | 'generic';
|
||||
};
|
||||
|
||||
export type PersistenceErrorCallback = (error: PersistenceError) => void;
|
||||
|
||||
abstract class PersistenceManager {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
static _instance: PersistenceManager;
|
||||
|
||||
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events, sync: boolean) {
|
||||
private _errorHandlers: PersistenceErrorCallback[] = [];
|
||||
|
||||
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events?) {
|
||||
$assert(mindmap, 'mindmap can not be null');
|
||||
$assert(editorProperties, 'editorProperties can not be null');
|
||||
|
||||
@ -33,30 +43,55 @@ abstract class PersistenceManager {
|
||||
|
||||
const serializer = XMLSerializerFactory.createInstanceFromMindmap(mindmap);
|
||||
const domMap = serializer.toXML(mindmap);
|
||||
const mapXml = new XMLSerializer().serializeToString(domMap);
|
||||
|
||||
const pref = JSON.stringify(editorProperties);
|
||||
try {
|
||||
this.saveMapXml(mapId, mapXml, pref, saveHistory, events, sync);
|
||||
this.saveMapXml(mapId, domMap, pref, saveHistory, events);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
events.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected getCSRFToken(): string | null {
|
||||
const meta = document.head.querySelector('meta[name="_csrf"]');
|
||||
let result = null;
|
||||
if (meta) {
|
||||
result = meta.getAttribute('content');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
load(mapId: string) {
|
||||
$assert(mapId, 'mapId can not be null');
|
||||
const domDocument = this.loadMapDom(mapId);
|
||||
return PersistenceManager.loadFromDom(mapId, domDocument);
|
||||
}
|
||||
|
||||
triggerError(error: PersistenceError) {
|
||||
this._errorHandlers.forEach((handler) => handler(error));
|
||||
}
|
||||
|
||||
addErrorHandler(callback: PersistenceErrorCallback) {
|
||||
this._errorHandlers.push(callback);
|
||||
}
|
||||
|
||||
removeErrorHandler(callback?: PersistenceErrorCallback) {
|
||||
if (!callback) {
|
||||
this._errorHandlers.length = 0;
|
||||
}
|
||||
const index = this._errorHandlers.findIndex((handler) => handler === callback);
|
||||
if (index !== -1) {
|
||||
this._errorHandlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
abstract discardChanges(mapId: string): void;
|
||||
|
||||
abstract loadMapDom(mapId: string): Document;
|
||||
|
||||
abstract saveMapXml(mapId: string, mapXml, pref, saveHistory, events, sync);
|
||||
abstract saveMapXml(mapId: string, mapXml: Document, pref?, saveHistory?: boolean, events?);
|
||||
|
||||
abstract unlockMap(mindmap: Mindmap): void;
|
||||
abstract unlockMap(mapId: string): void;
|
||||
|
||||
static init = (instance: PersistenceManager) => {
|
||||
this._instance = instance;
|
||||
|
@ -20,7 +20,7 @@ import { Arrow, Point, ElementClass } from '@wisemapping/web2d';
|
||||
import ConnectionLine from './ConnectionLine';
|
||||
import ControlPoint from './ControlPoint';
|
||||
import RelationshipModel from './model/RelationshipModel';
|
||||
import NodeGraph from './NodeGraph';
|
||||
import Topic from './Topic';
|
||||
import Shape from './util/Shape';
|
||||
|
||||
class Relationship extends ConnectionLine {
|
||||
@ -38,11 +38,11 @@ class Relationship extends ConnectionLine {
|
||||
|
||||
private _endArrow: Arrow;
|
||||
|
||||
private _controlPointControllerListener: any;
|
||||
private _controlPointControllerListener;
|
||||
|
||||
private _showStartArrow: Arrow;
|
||||
|
||||
constructor(sourceNode: NodeGraph, targetNode: NodeGraph, model: RelationshipModel) {
|
||||
constructor(sourceNode: Topic, targetNode: Topic, model: RelationshipModel) {
|
||||
$assert(sourceNode, 'sourceNode can not be null');
|
||||
$assert(targetNode, 'targetNode can not be null');
|
||||
|
||||
@ -349,7 +349,7 @@ class Relationship extends ConnectionLine {
|
||||
return this._model.getId();
|
||||
}
|
||||
|
||||
fireEvent(type: string, event: any): void {
|
||||
fireEvent(type: string, event): void {
|
||||
const elem = this._line2d;
|
||||
elem.trigger(type, event);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class RelationshipPivot {
|
||||
|
||||
private _sourceTopic: Topic;
|
||||
|
||||
private _pivot: any;
|
||||
private _pivot: CurvedLine;
|
||||
|
||||
private _startArrow: Arrow;
|
||||
|
||||
|
@ -17,9 +17,8 @@
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import $ from 'jquery';
|
||||
import { Mindmap } from '..';
|
||||
import { $msg } from './Messages';
|
||||
import PersistenceManager from './PersistenceManager';
|
||||
import PersistenceManager, { PersistenceError } from './PersistenceManager';
|
||||
|
||||
class RESTPersistenceManager extends PersistenceManager {
|
||||
private documentUrl: string;
|
||||
@ -51,10 +50,10 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
this.session = options.session;
|
||||
}
|
||||
|
||||
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events, sync: boolean): void {
|
||||
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events): void {
|
||||
const data = {
|
||||
id: mapId,
|
||||
xml: mapXml,
|
||||
xml: new XMLSerializer().serializeToString(mapXml),
|
||||
properties: pref,
|
||||
};
|
||||
|
||||
@ -71,73 +70,79 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
}, 10000);
|
||||
|
||||
const persistence = this;
|
||||
$.ajax({
|
||||
type: 'put',
|
||||
url: `${this.documentUrl.replace('{id}', mapId)}?${query}`,
|
||||
dataType: 'json',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
async: !sync,
|
||||
|
||||
success(successData) {
|
||||
persistence.timestamp = successData;
|
||||
fetch(
|
||||
`${this.documentUrl.replace('{id}', mapId)}?${query}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
// Blob helps to resuce the memory on large payload.
|
||||
body: new Blob([JSON.stringify(data)], { type: 'text/plain' }),
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
},
|
||||
).then(async (response: Response) => {
|
||||
if (response.ok) {
|
||||
persistence.timestamp = await response.text();
|
||||
events.onSuccess();
|
||||
},
|
||||
complete() {
|
||||
// Clear event timeout ...
|
||||
if (persistence.clearTimeout) {
|
||||
clearTimeout(persistence.clearTimeout);
|
||||
}
|
||||
persistence.onSave = false;
|
||||
},
|
||||
error(xhr) {
|
||||
const { responseText } = xhr;
|
||||
let userMsg = { severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED') };
|
||||
|
||||
const contentType = xhr.getResponseHeader('Content-Type');
|
||||
if (contentType != null && contentType.indexOf('application/json') !== -1) {
|
||||
let serverMsg = null;
|
||||
try {
|
||||
serverMsg = $.parseJSON(responseText);
|
||||
serverMsg = serverMsg.globalSeverity ? serverMsg : null;
|
||||
} catch (e) {
|
||||
// Message could not be decoded ...
|
||||
} else {
|
||||
console.log(`Saving error: ${response.status}`);
|
||||
let userMsg;
|
||||
if (response.status === 405) {
|
||||
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED'), errorType: 'session-expired' };
|
||||
} else {
|
||||
const responseText = await response.text();
|
||||
const contentType = response.headers['Content-Type'];
|
||||
if (contentType != null && contentType.indexOf('application/json') !== -1) {
|
||||
let serverMsg = null;
|
||||
try {
|
||||
serverMsg = JSON.parse(responseText);
|
||||
serverMsg = serverMsg.globalSeverity ? serverMsg : null;
|
||||
} catch (e) {
|
||||
// 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);
|
||||
persistence.onSave = false;
|
||||
},
|
||||
}
|
||||
|
||||
// Clear event timeout ...
|
||||
if (persistence.clearTimeout) {
|
||||
clearTimeout(persistence.clearTimeout);
|
||||
}
|
||||
persistence.onSave = false;
|
||||
}).catch(() => {
|
||||
const userMsg: PersistenceError = {
|
||||
severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED'), errorType: 'generic',
|
||||
};
|
||||
this.triggerError(userMsg);
|
||||
events.onError(userMsg);
|
||||
|
||||
// Clear event timeout ...
|
||||
if (persistence.clearTimeout) {
|
||||
clearTimeout(persistence.clearTimeout);
|
||||
}
|
||||
persistence.onSave = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
discardChanges(mapId: string) {
|
||||
$.ajax({
|
||||
url: this.revertUrl.replace('{id}', mapId),
|
||||
async: false,
|
||||
method: 'post',
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json' },
|
||||
error(xhr, ajaxOptions, thrownError) {
|
||||
console.error(`Request error => status:${xhr.status} ,thrownError: ${thrownError}`);
|
||||
},
|
||||
});
|
||||
fetch(this.revertUrl.replace('{id}', mapId),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
});
|
||||
}
|
||||
|
||||
unlockMap(mindmap: Mindmap) {
|
||||
const mapId = mindmap.getId();
|
||||
$.ajax({
|
||||
url: this.lockUrl.replace('{id}', mapId),
|
||||
async: false,
|
||||
method: 'put',
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
data: 'false',
|
||||
error(xhr, ajaxOptions, thrownError) {
|
||||
console.error(`Request error => status:${xhr.status} ,thrownError: ${thrownError}`);
|
||||
unlockMap(mapId: string): void {
|
||||
fetch(
|
||||
this.lockUrl.replace('{id}', mapId),
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'text/plain', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
body: 'false',
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
private _buildError(jsonSeverResponse) {
|
||||
@ -155,13 +160,12 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
}
|
||||
|
||||
loadMapDom(mapId: string): Document {
|
||||
// Let's try to open one from the local directory ...
|
||||
let xml: Document;
|
||||
$.ajax({
|
||||
url: `${this.documentUrl.replace('{id}', mapId)}/xml`,
|
||||
method: 'get',
|
||||
async: false,
|
||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml' },
|
||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
success(responseText) {
|
||||
xml = responseText;
|
||||
},
|
||||
|
@ -17,8 +17,6 @@
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import { Point } from '@wisemapping/web2d';
|
||||
import Icon from './Icon';
|
||||
import Topic from './Topic';
|
||||
|
||||
class ScreenManager {
|
||||
private _divContainer: JQuery;
|
||||
@ -84,59 +82,6 @@ class ScreenManager {
|
||||
}
|
||||
}
|
||||
|
||||
private _getElementPosition(elem: Topic) {
|
||||
// Retrieve current element position.
|
||||
const elementPosition = elem.getPosition();
|
||||
let { x } = elementPosition;
|
||||
let { y } = elementPosition;
|
||||
|
||||
// Add workspace offset.
|
||||
x -= this._padding.x;
|
||||
y -= this._padding.y;
|
||||
|
||||
// Scale coordinate in order to be relative to the workspace. That's coord/size;
|
||||
x /= this._scale;
|
||||
y /= this._scale;
|
||||
|
||||
// Remove decimal part..
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getWorkspaceIconPosition(e: Icon) {
|
||||
// Retrieve current icon position.
|
||||
const image = e.getImage();
|
||||
const elementPosition = image.getPosition();
|
||||
const imageSize = e.getSize();
|
||||
|
||||
// Add group offset
|
||||
const iconGroup = e.getGroup();
|
||||
const group = iconGroup.getNativeElement();
|
||||
const coordOrigin = group.getCoordOrigin();
|
||||
const groupSize = group.getSize();
|
||||
const coordSize = group.getCoordSize();
|
||||
|
||||
const scale = {
|
||||
x: coordSize.width / parseInt(groupSize.width, 10),
|
||||
y: coordSize.height / parseInt(groupSize.height, 10),
|
||||
};
|
||||
|
||||
let x = (elementPosition.x - coordOrigin.x - parseInt(imageSize.width, 10) / 2) / scale.x;
|
||||
let y = (elementPosition.y - coordOrigin.y - parseInt(imageSize.height, 10) / 2) / scale.y;
|
||||
|
||||
// Retrieve iconGroup Position
|
||||
const groupPosition = iconGroup.getPosition();
|
||||
x += groupPosition.x;
|
||||
y += groupPosition.y;
|
||||
|
||||
// Retrieve topic Position
|
||||
const topic = iconGroup.getTopic();
|
||||
const topicPosition = this._getElementPosition(topic);
|
||||
topicPosition.x -= parseInt(topic.getSize().width, 10) / 2;
|
||||
|
||||
// Remove decimal part..
|
||||
return { x: x + topicPosition.x, y: y + topicPosition.y };
|
||||
}
|
||||
|
||||
getWorkspaceMousePosition(event: MouseEvent) {
|
||||
// Retrieve current mouse position.
|
||||
let x = event.clientX;
|
||||
|
@ -79,11 +79,11 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
||||
$assert($defined(topicId), 'topicsId can not be null');
|
||||
$assert($defined(position), 'position can not be null');
|
||||
|
||||
const commandFunc = (topic, value) => {
|
||||
const commandFunc = (topic: Topic, pos: Point) => {
|
||||
const result = topic.getPosition();
|
||||
EventBus.instance.fireEvent(EventBus.events.NodeMoveEvent, {
|
||||
node: topic.getModel(),
|
||||
position: value,
|
||||
position: pos,
|
||||
});
|
||||
return result;
|
||||
};
|
||||
@ -114,7 +114,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
||||
changeTextToTopic(topicsIds: number[], text: string) {
|
||||
$assert($defined(topicsIds), 'topicsIds can not be null');
|
||||
|
||||
const commandFunc = (topic: Topic, value: object) => {
|
||||
const commandFunc = (topic: Topic, value: string) => {
|
||||
const result = topic.getText();
|
||||
topic.setText(value);
|
||||
return result;
|
||||
@ -163,7 +163,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
||||
$assert(topicsIds, 'topicIds can not be null');
|
||||
$assert(color, 'color can not be null');
|
||||
|
||||
const commandFunc = (topic, commandColor) => {
|
||||
const commandFunc = (topic: Topic, commandColor: string) => {
|
||||
const result = topic.getBackgroundColor();
|
||||
topic.setBackgroundColor(commandColor);
|
||||
return result;
|
||||
@ -179,7 +179,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
||||
$assert(topicsIds, 'topicIds can not be null');
|
||||
$assert(color, 'topicIds can not be null');
|
||||
|
||||
const commandFunc = (topic, commandColor) => {
|
||||
const commandFunc = (topic: Topic, commandColor: string) => {
|
||||
const result = topic.getBorderColor();
|
||||
topic.setBorderColor(commandColor);
|
||||
return result;
|
||||
@ -195,11 +195,11 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
||||
$assert(topicsIds, 'topicIds can not be null');
|
||||
$assert(size, 'size can not be null');
|
||||
|
||||
const commandFunc = (topic, commandSize) => {
|
||||
const commandFunc = (topic: Topic, commandSize: number) => {
|
||||
const result = topic.getFontSize();
|
||||
topic.setFontSize(commandSize, true);
|
||||
|
||||
topic._adjustShapes();
|
||||
topic.adjustShapes();
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -212,9 +212,9 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
||||
$assert(topicsIds, 'topicsIds can not be null');
|
||||
$assert(shapeType, 'shapeType can not be null');
|
||||
|
||||
const commandFunc = (topic, commandShapeType) => {
|
||||
const commandFunc = (topic: Topic, commandShapeType: string) => {
|
||||
const result = topic.getShapeType();
|
||||
topic.setShapeType(commandShapeType, true);
|
||||
topic.setShapeType(commandShapeType);
|
||||
return result;
|
||||
};
|
||||
|
||||
@ -226,12 +226,12 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
||||
changeFontWeightToTopic(topicsIds: number[]) {
|
||||
$assert(topicsIds, 'topicsIds can not be null');
|
||||
|
||||
const commandFunc = (topic) => {
|
||||
const commandFunc = (topic: Topic) => {
|
||||
const result = topic.getFontWeight();
|
||||
const weight = result === 'bold' ? 'normal' : 'bold';
|
||||
topic.setFontWeight(weight, true);
|
||||
|
||||
topic._adjustShapes();
|
||||
topic.adjustShapes();
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -44,6 +44,8 @@ import LayoutManager from './layout/LayoutManager';
|
||||
import NoteModel from './model/NoteModel';
|
||||
import LinkModel from './model/LinkModel';
|
||||
import SizeType from './SizeType';
|
||||
import FeatureModel from './model/FeatureModel';
|
||||
import Icon from './Icon';
|
||||
|
||||
const ICON_SCALING_FACTOR = 1.3;
|
||||
|
||||
@ -104,15 +106,10 @@ abstract class Topic extends NodeGraph {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} type the topic shape type
|
||||
* @see {@link mindplot.model.INodeModel}
|
||||
*/
|
||||
setShapeType(type) {
|
||||
setShapeType(type: string): void {
|
||||
this._setShapeType(type, true);
|
||||
}
|
||||
|
||||
/** @return {mindplot.Topic} parent topic */
|
||||
getParent(): Topic | null {
|
||||
return this._parent;
|
||||
}
|
||||
@ -161,8 +158,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/** @return {String} topic shape type */
|
||||
getShapeType() {
|
||||
getShapeType(): string {
|
||||
const model = this.getModel();
|
||||
let result = model.getShapeType();
|
||||
if (!$defined(result)) {
|
||||
@ -171,7 +167,7 @@ abstract class Topic extends NodeGraph {
|
||||
return result;
|
||||
}
|
||||
|
||||
private _removeInnerShape() {
|
||||
private _removeInnerShape(): ElementClass {
|
||||
const group = this.get2DElement();
|
||||
const innerShape = this.getInnerShape();
|
||||
group.removeChild(innerShape);
|
||||
@ -308,7 +304,7 @@ abstract class Topic extends NodeGraph {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
getOrBuildIconGroup() {
|
||||
getOrBuildIconGroup(): Group {
|
||||
if (!$defined(this._iconsGroup)) {
|
||||
this._iconsGroup = this._buildIconGroup();
|
||||
const group = this.get2DElement();
|
||||
@ -346,7 +342,7 @@ abstract class Topic extends NodeGraph {
|
||||
* @param {mindplot.model.FeatureModel} featureModel
|
||||
* @return {mindplot.Icon} the icon corresponding to the feature model
|
||||
*/
|
||||
addFeature(featureModel) {
|
||||
addFeature(featureModel: FeatureModel): Icon {
|
||||
const iconGroup = this.getOrBuildIconGroup();
|
||||
this.closeEditors();
|
||||
|
||||
@ -365,13 +361,13 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
findFeatureById(id) {
|
||||
findFeatureById(id: number) {
|
||||
const model = this.getModel();
|
||||
return model.findFeatureById(id);
|
||||
}
|
||||
|
||||
/** */
|
||||
removeFeature(featureModel) {
|
||||
removeFeature(featureModel: FeatureModel): void {
|
||||
$assert(featureModel, 'featureModel could not be null');
|
||||
|
||||
// Removing the icon from MODEL
|
||||
@ -387,21 +383,21 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
addRelationship(relationship) {
|
||||
addRelationship(relationship: Relationship) {
|
||||
this._relationships.push(relationship);
|
||||
}
|
||||
|
||||
/** */
|
||||
deleteRelationship(relationship) {
|
||||
deleteRelationship(relationship: Rect) {
|
||||
this._relationships = this._relationships.filter((r) => r !== relationship);
|
||||
}
|
||||
|
||||
/** */
|
||||
getRelationships() {
|
||||
getRelationships(): Relationship[] {
|
||||
return this._relationships;
|
||||
}
|
||||
|
||||
_buildTextShape(readOnly): Text {
|
||||
protected _buildTextShape(readOnly: boolean): Text {
|
||||
const result = new Text();
|
||||
const family = this.getFontFamily();
|
||||
const size = this.getFontSize();
|
||||
@ -425,7 +421,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
setFontFamily(value, updateModel) {
|
||||
setFontFamily(value: string, updateModel?: boolean) {
|
||||
const textShape = this.getTextShape();
|
||||
textShape.setFontName(value);
|
||||
if ($defined(updateModel) && updateModel) {
|
||||
@ -436,7 +432,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
setFontSize(value, updateModel) {
|
||||
setFontSize(value: number, updateModel?: boolean) {
|
||||
const textShape = this.getTextShape();
|
||||
textShape.setSize(value);
|
||||
|
||||
@ -534,7 +530,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
}
|
||||
|
||||
_setText(text, updateModel) {
|
||||
_setText(text: string, updateModel: boolean) {
|
||||
const textShape = this.getTextShape();
|
||||
textShape.setText(text == null ? TopicStyle.defaultText(this) : text);
|
||||
|
||||
@ -545,7 +541,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
setText(text) {
|
||||
setText(text: string) {
|
||||
// Avoid empty nodes ...
|
||||
if (!text || $.trim(text).length === 0) {
|
||||
this._setText(null, true);
|
||||
@ -557,7 +553,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
getText() {
|
||||
getText(): string {
|
||||
const model = this.getModel();
|
||||
let result = model.getText();
|
||||
if (!$defined(result)) {
|
||||
@ -567,11 +563,11 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
setBackgroundColor(color) {
|
||||
setBackgroundColor(color: string) {
|
||||
this._setBackgroundColor(color, true);
|
||||
}
|
||||
|
||||
_setBackgroundColor(color, updateModel) {
|
||||
_setBackgroundColor(color: string, updateModel: boolean) {
|
||||
const innerShape = this.getInnerShape();
|
||||
innerShape.setFill(color);
|
||||
|
||||
@ -587,7 +583,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
getBackgroundColor() {
|
||||
getBackgroundColor(): string {
|
||||
const model = this.getModel();
|
||||
let result = model.getBackgroundColor();
|
||||
if (!$defined(result)) {
|
||||
@ -597,11 +593,11 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
/** */
|
||||
setBorderColor(color) {
|
||||
setBorderColor(color: string) {
|
||||
this._setBorderColor(color, true);
|
||||
}
|
||||
|
||||
_setBorderColor(color, updateModel) {
|
||||
_setBorderColor(color: string, updateModel: boolean) {
|
||||
const innerShape = this.getInnerShape();
|
||||
innerShape.setAttribute('strokeColor', color);
|
||||
|
||||
@ -1016,6 +1012,10 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
}
|
||||
|
||||
// Hide inner shape ...
|
||||
this.getInnerShape().setVisibility(value);
|
||||
|
||||
// Hide text shape ...
|
||||
const textShape = this.getTextShape();
|
||||
textShape.setVisibility(this.getShapeType() !== TopicShape.IMAGE ? value : false);
|
||||
}
|
||||
@ -1058,7 +1058,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
}
|
||||
|
||||
setSize(size: SizeType, force: boolean): void {
|
||||
setSize(size: SizeType, force?: boolean): void {
|
||||
$assert(size, 'size can not be null');
|
||||
$assert($defined(size.width), 'size seem not to be a valid element');
|
||||
const roundedSize = {
|
||||
@ -1272,7 +1272,7 @@ abstract class Topic extends NodeGraph {
|
||||
}
|
||||
|
||||
// If a drag node is create for it, let's hide the editor.
|
||||
this._getTopicEventDispatcher().close();
|
||||
this._getTopicEventDispatcher().close(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -1296,7 +1296,7 @@ abstract class Topic extends NodeGraph {
|
||||
const topicHeight = Math.max(iconHeight, textHeight) + padding * 2;
|
||||
const textIconSpacing = Math.round(fontHeight / 4);
|
||||
const iconGroupWith = iconGroup.getSize().width;
|
||||
const topicWith = iconGroupWith + textIconSpacing + textWidth + padding * 2;
|
||||
const topicWith = iconGroupWith + 2 * textIconSpacing + textWidth + padding * 2;
|
||||
|
||||
this.setSize({
|
||||
width: topicWith,
|
||||
@ -1334,6 +1334,10 @@ abstract class Topic extends NodeGraph {
|
||||
return result;
|
||||
}
|
||||
|
||||
abstract workoutOutgoingConnectionPoint(position: Point): Point;
|
||||
|
||||
abstract workoutIncomingConnectionPoint(position: Point): Point;
|
||||
|
||||
isChildTopic(childTopic: Topic): boolean {
|
||||
let result = this.getId() === childTopic.getId();
|
||||
if (!result) {
|
||||
@ -1349,7 +1353,7 @@ abstract class Topic extends NodeGraph {
|
||||
return result;
|
||||
}
|
||||
|
||||
isCentralTopic() {
|
||||
isCentralTopic(): boolean {
|
||||
return this.getModel().getType() === 'CentralTopic';
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import { $assert } from '@wisemapping/core-js';
|
||||
import Events from './Events';
|
||||
import MultilineTextEditor from './MultilineTextEditor';
|
||||
import { TopicShape } from './model/INodeModel';
|
||||
import Topic from './Topic';
|
||||
|
||||
const TopicEvent = {
|
||||
EDIT: 'editnode',
|
||||
@ -26,30 +27,39 @@ const TopicEvent = {
|
||||
};
|
||||
|
||||
class TopicEventDispatcher extends Events {
|
||||
constructor(readOnly) {
|
||||
private _readOnly: boolean;
|
||||
|
||||
private _activeEditor: MultilineTextEditor;
|
||||
|
||||
private _multilineEditor: MultilineTextEditor;
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
static _instance: TopicEventDispatcher;
|
||||
|
||||
constructor(readOnly: boolean) {
|
||||
super();
|
||||
this._readOnly = readOnly;
|
||||
this._activeEditor = null;
|
||||
this._multilineEditor = new MultilineTextEditor();
|
||||
}
|
||||
|
||||
close(update) {
|
||||
close(update: boolean): void {
|
||||
if (this.isVisible()) {
|
||||
this._activeEditor.close(update);
|
||||
this._activeEditor = null;
|
||||
}
|
||||
}
|
||||
|
||||
show(topic, options) {
|
||||
show(topic: Topic, options?): void {
|
||||
this.process(TopicEvent.EDIT, topic, options);
|
||||
}
|
||||
|
||||
process(eventType, topic, options) {
|
||||
process(eventType: string, topic: Topic, options?): void {
|
||||
$assert(eventType, 'eventType can not be null');
|
||||
|
||||
// Close all previous open editor ....
|
||||
if (this.isVisible()) {
|
||||
this.close();
|
||||
this.close(false);
|
||||
}
|
||||
|
||||
// Open the new editor ...
|
||||
@ -66,20 +76,18 @@ class TopicEventDispatcher extends Events {
|
||||
}
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
isVisible(): boolean {
|
||||
return this._activeEditor != null && this._activeEditor.isVisible();
|
||||
}
|
||||
|
||||
static configure(readOnly: boolean): void {
|
||||
this._instance = new TopicEventDispatcher(readOnly);
|
||||
}
|
||||
|
||||
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 default TopicEventDispatcher;
|
@ -18,17 +18,21 @@
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import Command from '../Command';
|
||||
import CommandContext from '../CommandContext';
|
||||
import NodeModel from '../model/NodeModel';
|
||||
import RelationshipModel from '../model/RelationshipModel';
|
||||
import Relationship from '../Relationship';
|
||||
import Topic from '../Topic';
|
||||
|
||||
class DeleteCommand extends Command {
|
||||
private _relIds: number[];
|
||||
|
||||
private _topicIds: number[];
|
||||
|
||||
private _deletedTopicModels: any[];
|
||||
private _deletedTopicModels: NodeModel[];
|
||||
|
||||
private _deletedRelModel: any[];
|
||||
private _deletedRelModel: RelationshipModel[];
|
||||
|
||||
private _parentTopicIds: any[];
|
||||
private _parentTopicIds: number[];
|
||||
|
||||
constructor(topicIds: number[], relIds: number[]) {
|
||||
$assert($defined(relIds), 'topicIds can not be null');
|
||||
@ -101,11 +105,11 @@ class DeleteCommand extends Command {
|
||||
|
||||
// Do they need to be connected ?
|
||||
this._deletedTopicModels.forEach(((topicModel, index) => {
|
||||
const topics = commandContext.findTopics(topicModel.getId());
|
||||
const topics = commandContext.findTopics([topicModel.getId()]);
|
||||
|
||||
const parentId = this._parentTopicIds[index];
|
||||
if (parentId) {
|
||||
const parentTopics = commandContext.findTopics(parentId);
|
||||
const parentTopics = commandContext.findTopics([parentId]);
|
||||
commandContext.connect(topics[0], parentTopics[0]);
|
||||
}
|
||||
}));
|
||||
@ -117,14 +121,14 @@ class DeleteCommand extends Command {
|
||||
|
||||
// Finally display the topics ...
|
||||
this._deletedTopicModels.forEach((topicModel) => {
|
||||
const topics = commandContext.findTopics(topicModel.getId());
|
||||
const topics = commandContext.findTopics([topicModel.getId()]);
|
||||
topics[0].setBranchVisibility(true);
|
||||
});
|
||||
|
||||
// Focus on last recovered topic ..
|
||||
if (this._deletedTopicModels.length > 0) {
|
||||
const firstTopic = this._deletedTopicModels[0];
|
||||
const topic = commandContext.findTopics(firstTopic.getId())[0];
|
||||
const topic = commandContext.findTopics([firstTopic.getId()])[0];
|
||||
topic.setOnFocus(true);
|
||||
}
|
||||
|
||||
@ -133,11 +137,11 @@ class DeleteCommand extends Command {
|
||||
this._deletedRelModel = [];
|
||||
}
|
||||
|
||||
_filterChildren(topicIds, commandContext) {
|
||||
private _filterChildren(topicIds: number[], commandContext: CommandContext) {
|
||||
const topics = commandContext.findTopics(topicIds);
|
||||
|
||||
const result = [];
|
||||
topics.forEach((topic) => {
|
||||
topics.forEach((topic: Topic) => {
|
||||
let parent = topic.getParent();
|
||||
let found = false;
|
||||
while (parent != null && !found) {
|
||||
@ -156,13 +160,16 @@ class DeleteCommand extends Command {
|
||||
return result;
|
||||
}
|
||||
|
||||
_collectInDepthRelationships(topic) {
|
||||
private _collectInDepthRelationships(topic: Topic): Relationship[] {
|
||||
let result = [];
|
||||
result = result.concat(topic.getRelationships());
|
||||
|
||||
const children = topic.getChildren();
|
||||
const rels = children.map(((t) => this._collectInDepthRelationships(t)));
|
||||
result = result.concat(rels.flat());
|
||||
const rels: (Relationship[])[] = children
|
||||
.map(((t: Topic) => this._collectInDepthRelationships(t)));
|
||||
|
||||
// flatten and concact
|
||||
result = result.concat(([].concat(...rels)));
|
||||
|
||||
if (result.length > 0) {
|
||||
// Filter for unique ...
|
||||
|
@ -62,7 +62,7 @@ class DragTopicCommand extends Command {
|
||||
const origPosition = topic.getPosition();
|
||||
|
||||
// Disconnect topic ..
|
||||
if ($defined(origParentTopic) && origParentTopic.getId() !== this._parentId) {
|
||||
if ($defined(origParentTopic)) {
|
||||
commandContext.disconnect(topic);
|
||||
}
|
||||
|
||||
@ -76,17 +76,15 @@ class DragTopicCommand extends Command {
|
||||
}
|
||||
|
||||
// Finally, connect topic ...
|
||||
if (!$defined(origParentTopic) || origParentTopic.getId() !== this._parentId) {
|
||||
if ($defined(this._parentId)) {
|
||||
const parentTopic = commandContext.findTopics([this._parentId])[0];
|
||||
commandContext.connect(topic, parentTopic);
|
||||
}
|
||||
if ($defined(this._parentId)) {
|
||||
const parentTopic = commandContext.findTopics([this._parentId])[0];
|
||||
commandContext.connect(topic, parentTopic);
|
||||
}
|
||||
|
||||
// Backup old parent id ...
|
||||
this._parentId = null;
|
||||
if ($defined(origParentTopic)) {
|
||||
this._parentId = origParentTopic.getId();
|
||||
}
|
||||
// Backup old parent id ...
|
||||
this._parentId = null;
|
||||
if ($defined(origParentTopic)) {
|
||||
this._parentId = origParentTopic.getId();
|
||||
}
|
||||
topic.setVisibility(true);
|
||||
|
||||
|
@ -20,18 +20,20 @@ import Command from '../Command';
|
||||
import CommandContext from '../CommandContext';
|
||||
import Topic from '../Topic';
|
||||
|
||||
type CommandTypes = string | object | boolean | number;
|
||||
|
||||
class GenericFunctionCommand extends Command {
|
||||
private _value: string | object | boolean | number;
|
||||
private _value: CommandTypes;
|
||||
|
||||
private _topicsId: number[];
|
||||
|
||||
private _commandFunc: (topic: Topic, value: string | object | boolean | number) => string | object | boolean;
|
||||
private _commandFunc: (topic: Topic, value: CommandTypes) => CommandTypes;
|
||||
|
||||
private _oldValues: any[];
|
||||
private _oldValues: (CommandTypes)[];
|
||||
|
||||
private _applied: boolean;
|
||||
|
||||
constructor(commandFunc: (topic: Topic, value: string | object | boolean) => string | object | boolean, topicsIds: number[], value: string | object | boolean | number = undefined) {
|
||||
constructor(commandFunc: (topic: Topic, value: CommandTypes) => CommandTypes, topicsIds: number[], value: CommandTypes = undefined) {
|
||||
$assert(commandFunc, 'commandFunc must be defined');
|
||||
$assert($defined(topicsIds), 'topicsIds must be defined');
|
||||
|
||||
|
@ -15,26 +15,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Mindmap } from '../..';
|
||||
import Exporter from './Exporter';
|
||||
import SVGExporter from './SVGExporter';
|
||||
/**
|
||||
* Based on https://mybyways.com/blog/convert-svg-to-png-using-your-browser
|
||||
*/
|
||||
class BinaryImageExporter extends Exporter {
|
||||
svgElement: Element;
|
||||
private svgElement: Element;
|
||||
|
||||
mindmap: Mindmap;
|
||||
private width: number;
|
||||
|
||||
width: number;
|
||||
private height: number;
|
||||
|
||||
height: number;
|
||||
private adjustToFit: boolean;
|
||||
|
||||
constructor(mindmap: Mindmap, svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg') {
|
||||
super(imgFormat.split['/'][0], imgFormat);
|
||||
constructor(svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg', adjustToFit = true) {
|
||||
super(imgFormat.split('/')[0], imgFormat);
|
||||
this.svgElement = svgElement;
|
||||
this.mindmap = mindmap;
|
||||
|
||||
this.adjustToFit = adjustToFit;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
@ -43,37 +41,52 @@ class BinaryImageExporter extends Exporter {
|
||||
throw new Error('Images can not be exporeted');
|
||||
}
|
||||
|
||||
async exportAndEndcode(): Promise<string> {
|
||||
const svgExporter = new SVGExporter(this.svgElement);
|
||||
const svgUrl = await svgExporter.exportAndEncode();
|
||||
exportAndEncode(): Promise<string> {
|
||||
const svgExporter = new SVGExporter(this.svgElement, this.adjustToFit);
|
||||
const svgUrl = svgExporter.exportAndEncode();
|
||||
return svgUrl.then((value: string) => {
|
||||
// Get the device pixel ratio, falling back to 1. But, I will double the resolution to look nicer.
|
||||
const dpr = (window.devicePixelRatio || 1) * 2;
|
||||
|
||||
// Get the device pixel ratio, falling back to 1. But, I will double the resolution to look nicer.
|
||||
const dpr = (window.devicePixelRatio || 1) * 2;
|
||||
// Create canvas size ...
|
||||
const canvas = document.createElement('canvas');
|
||||
let width: number;
|
||||
let height: number;
|
||||
if (this.adjustToFit) {
|
||||
// Size must match with SVG image size ...
|
||||
const size = svgExporter.getImgSize();
|
||||
width = (size.width * dpr);
|
||||
height = (size.height * dpr);
|
||||
} else {
|
||||
// Use screensize as size ..
|
||||
width = (this.width * dpr);
|
||||
height = (this.height * dpr);
|
||||
}
|
||||
|
||||
// Create canvas ...
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.setAttribute('width', (this.width * dpr).toString());
|
||||
canvas.setAttribute('height', (this.height * dpr).toString());
|
||||
console.log(`Export size: ${width}:${height}`);
|
||||
canvas.setAttribute('width', width.toFixed(0));
|
||||
canvas.setAttribute('height', height.toFixed(0));
|
||||
|
||||
// Render the image and wait for the response ...
|
||||
const img = new Image();
|
||||
const result = new Promise<string>((resolve, reject) => {
|
||||
img.onload = () => {
|
||||
const ctx = canvas.getContext('2d');
|
||||
// Scale for retina ...
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.drawImage(img, 0, 0);
|
||||
// Render the image and wait for the response ...
|
||||
const img = new Image();
|
||||
const result = new Promise<string>((resolve) => {
|
||||
img.onload = () => {
|
||||
const ctx = canvas.getContext('2d');
|
||||
// Scale for retina ...
|
||||
ctx.scale(dpr, dpr);
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
const imgDataUri = canvas
|
||||
.toDataURL(this.getContentType())
|
||||
.replace('image/png', 'octet/stream');
|
||||
const imgDataUri = canvas
|
||||
.toDataURL(this.getContentType())
|
||||
.replace('image/png', 'octet/stream');
|
||||
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
resolve(imgDataUri);
|
||||
};
|
||||
URL.revokeObjectURL(value);
|
||||
resolve(imgDataUri);
|
||||
};
|
||||
});
|
||||
img.src = value;
|
||||
return result;
|
||||
});
|
||||
img.src = svgUrl;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
export default BinaryImageExporter;
|
||||
|
304
packages/mindplot/src/components/export/FreemindExporter.ts
Normal file
304
packages/mindplot/src/components/export/FreemindExporter.ts
Normal 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;
|
@ -15,26 +15,25 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Mindmap } from '../..';
|
||||
import BinaryImageExporter from './BinaryImageExporter';
|
||||
import Exporter from './Exporter';
|
||||
import SVGExporter from './SVGExporter';
|
||||
|
||||
type imageType = 'svg' | 'png' | 'jpg';
|
||||
class ImageExpoterFactory {
|
||||
static create(type: imageType, mindmap: Mindmap, svgElement: Element, width: number, height: number, isCenter = false): Exporter {
|
||||
let result;
|
||||
static create(type: imageType, svgElement: Element, width: number, height: number, adjustToFit = true): Exporter {
|
||||
let result: Exporter;
|
||||
switch (type) {
|
||||
case 'svg': {
|
||||
result = new SVGExporter(svgElement);
|
||||
result = new SVGExporter(svgElement, adjustToFit);
|
||||
break;
|
||||
}
|
||||
case 'png': {
|
||||
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/png');
|
||||
result = new BinaryImageExporter(svgElement, width, height, 'image/png', adjustToFit);
|
||||
break;
|
||||
}
|
||||
case 'jpg': {
|
||||
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/jpeg');
|
||||
result = new BinaryImageExporter(svgElement, width, height, 'image/jpeg', adjustToFit);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -15,23 +15,33 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import SizeType from '../SizeType';
|
||||
import Exporter from './Exporter';
|
||||
|
||||
class SVGExporter extends Exporter {
|
||||
private svgElement: Element;
|
||||
|
||||
private prolog = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n';
|
||||
private static prolog = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n';
|
||||
|
||||
constructor(svgElement: Element) {
|
||||
private static regexpTranslate = /translate\((-?[0-9]+.[0-9]+),(-?[0-9]+.[0-9]+)\)/;
|
||||
|
||||
private static padding = 30;
|
||||
|
||||
private adjustToFit: boolean;
|
||||
|
||||
private static MAX_SUPPORTED_SIZE = 2500;
|
||||
|
||||
constructor(svgElement: Element, adjustToFit = true) {
|
||||
super('svg', 'image/svg+xml');
|
||||
this.svgElement = svgElement;
|
||||
this.adjustToFit = adjustToFit;
|
||||
}
|
||||
|
||||
export(): Promise<string> {
|
||||
// Replace all images for in-line images ...
|
||||
let svgTxt: string = new XMLSerializer()
|
||||
.serializeToString(this.svgElement);
|
||||
svgTxt = this.prolog + svgTxt;
|
||||
svgTxt = SVGExporter.prolog + svgTxt;
|
||||
|
||||
// Are namespace declared ?. Otherwise, force the declaration ...
|
||||
if (svgTxt.indexOf('xmlns:xlink=') === -1) {
|
||||
@ -39,15 +49,103 @@ class SVGExporter extends Exporter {
|
||||
}
|
||||
|
||||
// Add white background. This is mainly for PNG export ...
|
||||
const svgDoc = SVGExporter.parseXMLString(svgTxt, 'application/xml');
|
||||
let svgDoc = SVGExporter.parseXMLString(svgTxt, 'application/xml');
|
||||
const svgElement = svgDoc.getElementsByTagName('svg')[0];
|
||||
svgElement.setAttribute('style', 'background-color:white');
|
||||
svgElement.setAttribute('focusable', 'false');
|
||||
|
||||
// Does need to be adjust ?.
|
||||
if (this.adjustToFit) {
|
||||
svgDoc = this._normalizeToFit(svgDoc);
|
||||
}
|
||||
|
||||
const result = new XMLSerializer()
|
||||
.serializeToString(svgDoc);
|
||||
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private _calcualteDimensions(): { minX: number, maxX: number, minY: number, maxY: number } {
|
||||
// Collect all group elements ...
|
||||
const rectElems = Array.from(document.querySelectorAll('g>rect'));
|
||||
const translates: SizeType[] = rectElems
|
||||
.map((rect: Element) => {
|
||||
const g = rect.parentElement;
|
||||
const transformStr = g.getAttribute('transform');
|
||||
|
||||
// Looking to parse translate(220.00000,279.00000) scale(1.00000,1.00000)
|
||||
const match = transformStr.match(SVGExporter.regexpTranslate);
|
||||
let result: SizeType = { width: 0, height: 0 };
|
||||
if (match !== null) {
|
||||
result = { width: Number.parseFloat(match[1]), height: Number.parseFloat(match[2]) };
|
||||
|
||||
// Add rect size ...
|
||||
if (result.width > 0) {
|
||||
const rectWidth = Number.parseFloat(rect.getAttribute('width'));
|
||||
result.width += rectWidth;
|
||||
}
|
||||
|
||||
if (result.height > 0) {
|
||||
const rectHeight = Number.parseFloat(rect.getAttribute('height'));
|
||||
result.height += rectHeight;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
// Find max and mins ...
|
||||
const widths = translates.map((t) => t.width).sort((a, b) => a - b);
|
||||
const heights = translates.map((t) => t.height).sort((a, b) => a - b);
|
||||
|
||||
const minX = widths[0] - SVGExporter.padding;
|
||||
const minY = heights[0] - SVGExporter.padding;
|
||||
|
||||
const maxX = widths[widths.length - 1] + SVGExporter.padding;
|
||||
const maxY = heights[heights.length - 1] + SVGExporter.padding;
|
||||
|
||||
return {
|
||||
minX, maxX, minY, maxY,
|
||||
};
|
||||
}
|
||||
|
||||
getImgSize(): SizeType {
|
||||
const {
|
||||
minX, maxX, minY, maxY,
|
||||
} = this._calcualteDimensions();
|
||||
|
||||
let width: number = maxX + Math.abs(minX);
|
||||
let height: number = maxY + Math.abs(minY);
|
||||
|
||||
// Prevents an image too big. Failures seen during export in couple of browsers
|
||||
if (Math.max(width, height) > SVGExporter.MAX_SUPPORTED_SIZE) {
|
||||
const scale = Math.max(width, height) / SVGExporter.MAX_SUPPORTED_SIZE;
|
||||
width /= scale;
|
||||
height /= scale;
|
||||
}
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
private _normalizeToFit(document: Document): Document {
|
||||
const {
|
||||
minX, maxX, minY, maxY,
|
||||
} = this._calcualteDimensions();
|
||||
const svgElem = document.firstChild as Element;
|
||||
|
||||
const width = maxX + Math.abs(minX);
|
||||
const height = maxY + Math.abs(minY);
|
||||
|
||||
svgElem.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);
|
||||
svgElem.setAttribute('preserveAspectRatio', 'xMinYMin');
|
||||
|
||||
// Get image size ...
|
||||
const imgSize = this.getImgSize();
|
||||
svgElem.setAttribute('width', imgSize.width.toFixed(0));
|
||||
svgElem.setAttribute('height', imgSize.height.toFixed(0));
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
private static parseXMLString = (xmlStr: string, mimeType: DOMParserSupportedType) => {
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(xmlStr, mimeType);
|
||||
@ -56,7 +154,7 @@ class SVGExporter extends Exporter {
|
||||
if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
|
||||
const xmmStr = new XMLSerializer().serializeToString(xmlDoc);
|
||||
console.log(xmmStr);
|
||||
throw new Error(`Unexpected error parsing: ${xmlStr}. Error: ${xmmStr}`);
|
||||
throw new Error(`Unexpected error parsing: ${xmlStr}.Error: ${xmmStr}`);
|
||||
}
|
||||
|
||||
return xmlDoc;
|
||||
|
@ -20,6 +20,7 @@ import Exporter from './Exporter';
|
||||
import MDExporter from './MDExporter';
|
||||
import TxtExporter from './TxtExporter';
|
||||
import WiseXMLExporter from './WiseXMLExporter';
|
||||
import FreemindExporter from './FreemindExporter';
|
||||
|
||||
type textType = 'wxml' | 'txt' | 'mm' | 'csv' | 'md' | 'mmap';
|
||||
|
||||
@ -36,6 +37,9 @@ class TextExporterFactory {
|
||||
case 'md':
|
||||
result = new MDExporter(mindmap);
|
||||
break;
|
||||
case 'mm':
|
||||
result = new FreemindExporter(mindmap);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported type ${type}`);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
import { Mindmap } from '../..';
|
||||
import INodeModel from '../model/INodeModel';
|
||||
import LinkModel from '../model/LinkModel';
|
||||
import NoteModel from '../model/NoteModel';
|
||||
import Exporter from './Exporter';
|
||||
|
||||
class TxtExporter extends Exporter {
|
||||
@ -32,27 +33,27 @@ class TxtExporter extends Exporter {
|
||||
const { mindmap } = this;
|
||||
|
||||
const branches = mindmap.getBranches();
|
||||
const txtStr = this.traverseBranch('', branches);
|
||||
const txtStr = this.traverseBranch('', '', branches);
|
||||
return Promise.resolve(txtStr);
|
||||
}
|
||||
|
||||
private traverseBranch(prefix: string, branches: INodeModel[]) {
|
||||
private traverseBranch(indent: string, prefix: string, branches: INodeModel[]) {
|
||||
let result = '';
|
||||
branches
|
||||
.filter((n) => n.getText() !== undefined)
|
||||
.forEach((node, index) => {
|
||||
result = `${result}${prefix}${index + 1} ${node.getText()}`;
|
||||
result = `${result}${indent}${prefix}${index + 1} ${node.getText() !== undefined ? node.getText() : ''}`;
|
||||
node.getFeatures().forEach((f) => {
|
||||
const type = f.getType();
|
||||
if (type === 'link') {
|
||||
result = `${result} [link: ${(f as LinkModel).getUrl()}]`;
|
||||
result = `${result}\n ${indent} [Link: ${(f as LinkModel).getUrl()}]`;
|
||||
}
|
||||
if (type === 'note') {
|
||||
result = `${result}\n${indent} [Note: ${(f as NoteModel).getText()}]`;
|
||||
}
|
||||
});
|
||||
result = `${result}\n`;
|
||||
|
||||
if (node.getChildren().filter((n) => n.getText() !== undefined).length > 0) {
|
||||
result += this.traverseBranch(`\t${prefix}${index + 1}.`, node.getChildren());
|
||||
}
|
||||
result += this.traverseBranch(`\t${indent}`, `${prefix}${index + 1}.`, node.getChildren());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
20
packages/mindplot/src/components/export/freemind/Cloud.ts
Normal file
20
packages/mindplot/src/components/export/freemind/Cloud.ts
Normal 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;
|
||||
}
|
||||
}
|
42
packages/mindplot/src/components/export/freemind/Edge.ts
Normal file
42
packages/mindplot/src/components/export/freemind/Edge.ts
Normal 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;
|
||||
}
|
||||
}
|
56
packages/mindplot/src/components/export/freemind/Font.ts
Normal file
56
packages/mindplot/src/components/export/freemind/Font.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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',
|
||||
};
|
33
packages/mindplot/src/components/export/freemind/Hook.ts
Normal file
33
packages/mindplot/src/components/export/freemind/Hook.ts
Normal 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;
|
||||
}
|
||||
}
|
20
packages/mindplot/src/components/export/freemind/Icon.ts
Normal file
20
packages/mindplot/src/components/export/freemind/Icon.ts
Normal 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;
|
||||
}
|
||||
}
|
116
packages/mindplot/src/components/export/freemind/Map.ts
Normal file
116
packages/mindplot/src/components/export/freemind/Map.ts
Normal 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;
|
||||
}
|
||||
}
|
233
packages/mindplot/src/components/export/freemind/Node.ts
Normal file
233
packages/mindplot/src/components/export/freemind/Node.ts
Normal 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;
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export default class Parameters {
|
||||
protected REMINDUSERAT: number;
|
||||
|
||||
getReminduserat(): number {
|
||||
return this.REMINDUSERAT;
|
||||
}
|
||||
|
||||
setReminduserat(value: number): void {
|
||||
this.REMINDUSERAT = value;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
export default class VersionNumber {
|
||||
protected version: string;
|
||||
|
||||
constructor(version: string) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public getVersion(): string {
|
||||
return this.version;
|
||||
}
|
||||
}
|
@ -20,12 +20,14 @@ import ES from './es';
|
||||
import EN from './en';
|
||||
import DE from './de';
|
||||
import FR from './fr';
|
||||
import RU from './ru';
|
||||
|
||||
const Bundle = {
|
||||
es: ES,
|
||||
en: EN,
|
||||
de: DE,
|
||||
fr: FR,
|
||||
ru: RU,
|
||||
};
|
||||
|
||||
export default Bundle;
|
||||
|
@ -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',
|
@ -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',
|
@ -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',
|
@ -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",
|
84
packages/mindplot/src/components/lang/ru.js
Normal file
84
packages/mindplot/src/components/lang/ru.js
Normal 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;
|
@ -18,6 +18,7 @@
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import PositionType from '../PositionType';
|
||||
import SizeType from '../SizeType';
|
||||
import ChildrenSorterStrategy from './ChildrenSorterStrategy';
|
||||
|
||||
class Node {
|
||||
private _id: number;
|
||||
@ -25,14 +26,14 @@ class Node {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
_parent: Node;
|
||||
|
||||
private _sorter: any;
|
||||
private _sorter: ChildrenSorterStrategy;
|
||||
|
||||
private _properties;
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
_children: Node[];
|
||||
|
||||
constructor(id: number, size: SizeType, position, sorter) {
|
||||
constructor(id: number, size: SizeType, position: PositionType, sorter: ChildrenSorterStrategy) {
|
||||
$assert(typeof id === 'number' && Number.isFinite(id), 'id can not be null');
|
||||
$assert(size, 'size can not be null');
|
||||
$assert(position, 'position can not be null');
|
||||
|
@ -24,7 +24,6 @@ class RootedTreeSet {
|
||||
|
||||
protected _children: Node[];
|
||||
|
||||
|
||||
constructor() {
|
||||
this._rootNodes = [];
|
||||
}
|
||||
@ -56,10 +55,9 @@ class RootedTreeSet {
|
||||
*/
|
||||
add(node: Node) {
|
||||
$assert(node, 'node can not be null');
|
||||
$assert(
|
||||
!this.find(node.getId(), false),
|
||||
`node already exits with this id. Id:${node.getId()}: ${this.dump()}`,
|
||||
);
|
||||
if (this.find(node.getId(), false)) {
|
||||
throw new Error(`node already exits with this id. Id:${node.getId()}: ${this.dump()}`);
|
||||
}
|
||||
$assert(!node._children, 'node already added');
|
||||
this._rootNodes.push(this._decodate(node));
|
||||
}
|
||||
@ -131,10 +129,11 @@ class RootedTreeSet {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$assert(
|
||||
validate ? result : true,
|
||||
`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`,
|
||||
);
|
||||
|
||||
if (validate && !result) {
|
||||
throw new Error(`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -259,8 +258,8 @@ class RootedTreeSet {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return result
|
||||
*/
|
||||
* @return result
|
||||
*/
|
||||
dump() {
|
||||
const branches = this._rootNodes;
|
||||
let result = '';
|
||||
@ -293,7 +292,7 @@ class RootedTreeSet {
|
||||
}
|
||||
}
|
||||
|
||||
_plot(canvas, node: Node, root?) {
|
||||
_plot(canvas, node: Node) {
|
||||
const children = this.getChildren(node);
|
||||
const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2;
|
||||
const cy = node.getPosition().y + canvas.height / 2 - node.getSize().height / 2;
|
||||
|
@ -70,11 +70,6 @@ class NodeModel extends INodeModel {
|
||||
return this._features;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param feature
|
||||
* @throws will throw an error if feature is null or undefined
|
||||
* @throws will throw an error if the feature could not be removed
|
||||
*/
|
||||
removeFeature(feature: FeatureModel): void {
|
||||
$assert(feature, 'feature can not be null');
|
||||
const size = this._features.length;
|
||||
@ -127,11 +122,8 @@ class NodeModel extends INodeModel {
|
||||
return this._properties[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {mindplot.model.NodeModel} an identical clone of the NodeModel
|
||||
*/
|
||||
clone(): NodeModel {
|
||||
const result = new NodeModel(this.getType(), this._mindmap, -1);
|
||||
const result = new NodeModel(this.getType(), this._mindmap);
|
||||
result._children = this._children.map((node) => {
|
||||
const cnode = node.clone() as NodeModel;
|
||||
cnode._parent = result;
|
||||
@ -143,12 +135,8 @@ class NodeModel extends INodeModel {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to clone, assign new id to the elements ...
|
||||
* @return {mindplot.model.NodeModel}
|
||||
*/
|
||||
deepCopy(): NodeModel {
|
||||
const result = new NodeModel(this.getType(), this._mindmap, -1);
|
||||
const result = new NodeModel(this.getType(), this._mindmap);
|
||||
result._children = this._children.map((node) => {
|
||||
const cnode = (node as NodeModel).deepCopy();
|
||||
cnode._parent = result;
|
||||
@ -163,20 +151,12 @@ class NodeModel extends INodeModel {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {mindplot.model.NodeModel} child
|
||||
* @throws will throw an error if child is null, undefined or not a NodeModel object
|
||||
*/
|
||||
append(child: NodeModel): void {
|
||||
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object');
|
||||
this._children.push(child);
|
||||
child._parent = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {mindplot.model.NodeModel} child
|
||||
* @throws will throw an error if child is null, undefined or not a NodeModel object
|
||||
*/
|
||||
removeChild(child: NodeModel): void {
|
||||
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.');
|
||||
this._children = this._children.filter((c) => c !== child);
|
||||
|
@ -67,7 +67,7 @@ class RelationshipModel {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
getLineType() {
|
||||
getLineType(): number {
|
||||
return this._lineType;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import { Mindmap } from '../..';
|
||||
import NodeModel from '../model/NodeModel';
|
||||
import ModelCodeName from './ModelCodeName';
|
||||
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
||||
import XMLSerializerPela from './XMLSerializerPela';
|
||||
import XMLSerializerPela from './XMLSerializerTango';
|
||||
|
||||
class Beta2PelaMigrator implements XMLMindmapSerializer {
|
||||
private _betaSerializer: XMLMindmapSerializer;
|
||||
|
@ -72,7 +72,7 @@ class Pela2TangoMigrator implements XMLMindmapSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
private _fixPosition(mindmap: Mindmap) {
|
||||
private _fixPosition(mindmap: Mindmap): void {
|
||||
// Position was not required in previous versions. Try to synthesize one .
|
||||
const centralNode = mindmap.getBranches()[0];
|
||||
const children = centralNode.getChildren();
|
||||
@ -83,7 +83,7 @@ class Pela2TangoMigrator implements XMLMindmapSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
_fixNodePosition(node: NodeModel, parentPosition: {x:number, y:number}) {
|
||||
private _fixNodePosition(node: NodeModel, parentPosition: { x: number, y: number }): void {
|
||||
// Position was not required in previous versions. Try to synthesize one .
|
||||
let position = node.getPosition();
|
||||
if (!position) {
|
||||
|
@ -20,7 +20,6 @@ import ModelCodeName from './ModelCodeName';
|
||||
import Beta2PelaMigrator from './Beta2PelaMigrator';
|
||||
import Pela2TangoMigrator from './Pela2TangoMigrator';
|
||||
import XMLSerializerBeta from './XMLSerializerBeta';
|
||||
import XMLSerializerPela from './XMLSerializerPela';
|
||||
import XMLSerializerTango from './XMLSerializerTango';
|
||||
import Mindmap from '../model/Mindmap';
|
||||
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
||||
@ -35,7 +34,7 @@ const codeToSerializer: { codeName: string, serializer, migrator }[] = [
|
||||
},
|
||||
{
|
||||
codeName: ModelCodeName.PELA,
|
||||
serializer: XMLSerializerPela,
|
||||
serializer: XMLSerializerTango,
|
||||
migrator: Beta2PelaMigrator,
|
||||
},
|
||||
{
|
||||
|
@ -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;
|
@ -15,13 +15,506 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import XMLSerializerPela from './XMLSerializerPela';
|
||||
import { $assert, $defined, createDocument } from '@wisemapping/core-js';
|
||||
import { Point } from '@wisemapping/web2d';
|
||||
import Mindmap from '../model/Mindmap';
|
||||
import { TopicShape } from '../model/INodeModel';
|
||||
import ConnectionLine from '../ConnectionLine';
|
||||
import FeatureModelFactory from '../model/FeatureModelFactory';
|
||||
import NodeModel from '../model/NodeModel';
|
||||
import RelationshipModel from '../model/RelationshipModel';
|
||||
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
||||
import FeatureType from '../model/FeatureType';
|
||||
|
||||
/**
|
||||
* This serializer works exactly the same way as for the former version Pela
|
||||
*/
|
||||
class XMLSerializerTango extends XMLSerializerPela {
|
||||
class XMLSerializerTango implements XMLMindmapSerializer {
|
||||
private static MAP_ROOT_NODE = 'map';
|
||||
|
||||
private _idsMap: Record<number, Element>;
|
||||
|
||||
toXML(mindmap: Mindmap): Document {
|
||||
$assert(mindmap, 'Can not save a null mindmap');
|
||||
|
||||
const document = createDocument();
|
||||
|
||||
// Store map attributes ...
|
||||
const mapElem = document.createElement('map');
|
||||
const name = mindmap.getId();
|
||||
if ($defined(name)) {
|
||||
mapElem.setAttribute('name', this._rmXmlInv(name));
|
||||
}
|
||||
const version = mindmap.getVersion();
|
||||
if ($defined(version)) {
|
||||
mapElem.setAttribute('version', version);
|
||||
}
|
||||
|
||||
document.appendChild(mapElem);
|
||||
|
||||
// Create branches ...
|
||||
const topics = mindmap.getBranches();
|
||||
topics.forEach((topic) => {
|
||||
const topicDom = this._topicToXML(document, topic);
|
||||
mapElem.appendChild(topicDom);
|
||||
});
|
||||
|
||||
// Create Relationships
|
||||
const relationships = mindmap.getRelationships();
|
||||
relationships.forEach((relationship) => {
|
||||
if (
|
||||
mindmap.findNodeById(relationship.getFromNode()) !== null
|
||||
&& mindmap.findNodeById(relationship.getToNode()) !== null
|
||||
) {
|
||||
// Isolated relationships are not persisted ....
|
||||
const relationDom = XMLSerializerTango._relationshipToXML(document, relationship);
|
||||
mapElem.appendChild(relationDom);
|
||||
}
|
||||
});
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
protected _topicToXML(document: Document, topic: NodeModel) {
|
||||
const parentTopic = document.createElement('topic');
|
||||
|
||||
// Set topic attributes...
|
||||
if (topic.getType() === 'CentralTopic') {
|
||||
parentTopic.setAttribute('central', 'true');
|
||||
} else {
|
||||
const pos = topic.getPosition();
|
||||
parentTopic.setAttribute('position', `${pos.x},${pos.y}`);
|
||||
|
||||
const order = topic.getOrder();
|
||||
if (typeof order === 'number' && Number.isFinite(order)) {
|
||||
parentTopic.setAttribute('order', order.toString());
|
||||
}
|
||||
}
|
||||
|
||||
const text = topic.getText();
|
||||
if ($defined(text)) {
|
||||
this._noteTextToXML(document, parentTopic, text);
|
||||
}
|
||||
|
||||
const shape = topic.getShapeType();
|
||||
if ($defined(shape)) {
|
||||
parentTopic.setAttribute('shape', shape);
|
||||
|
||||
if (shape === TopicShape.IMAGE) {
|
||||
const size = topic.getImageSize();
|
||||
parentTopic.setAttribute(
|
||||
'image',
|
||||
`${size.width},${size.height}:${topic.getImageUrl()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (topic.areChildrenShrunken() && topic.getType() !== 'CentralTopic') {
|
||||
parentTopic.setAttribute('shrink', 'true');
|
||||
}
|
||||
|
||||
// Font properties ...
|
||||
const id = topic.getId();
|
||||
parentTopic.setAttribute('id', id.toString());
|
||||
|
||||
let font = '';
|
||||
|
||||
const fontFamily = topic.getFontFamily();
|
||||
font += `${fontFamily || ''};`;
|
||||
|
||||
const fontSize = topic.getFontSize();
|
||||
font += `${fontSize || ''};`;
|
||||
|
||||
const fontColor = topic.getFontColor();
|
||||
font += `${fontColor || ''};`;
|
||||
|
||||
const fontWeight = topic.getFontWeight();
|
||||
font += `${fontWeight || ''};`;
|
||||
|
||||
const fontStyle = topic.getFontStyle();
|
||||
font += `${fontStyle || ''};`;
|
||||
|
||||
if (
|
||||
$defined(fontFamily)
|
||||
|| $defined(fontSize)
|
||||
|| $defined(fontColor)
|
||||
|| $defined(fontWeight)
|
||||
|| $defined(fontStyle)
|
||||
) {
|
||||
parentTopic.setAttribute('fontStyle', font);
|
||||
}
|
||||
|
||||
const bgColor = topic.getBackgroundColor();
|
||||
if ($defined(bgColor)) {
|
||||
parentTopic.setAttribute('bgColor', bgColor);
|
||||
}
|
||||
|
||||
const brColor = topic.getBorderColor();
|
||||
if ($defined(brColor)) {
|
||||
parentTopic.setAttribute('brColor', brColor);
|
||||
}
|
||||
|
||||
const metadata = topic.getMetadata();
|
||||
if ($defined(metadata)) {
|
||||
parentTopic.setAttribute('metadata', metadata);
|
||||
}
|
||||
|
||||
// Serialize features ...
|
||||
const features = topic.getFeatures();
|
||||
features.forEach((feature) => {
|
||||
const featureType = feature.getType();
|
||||
const featureDom = document.createElement(featureType);
|
||||
const attributes = feature.getAttributes();
|
||||
|
||||
const attributesKeys = Object.keys(attributes);
|
||||
for (let attrIndex = 0; attrIndex < attributesKeys.length; attrIndex++) {
|
||||
const key = attributesKeys[attrIndex];
|
||||
const value = attributes[key];
|
||||
if (key === 'text') {
|
||||
const cdata = document.createCDATASection(this._rmXmlInv(value));
|
||||
featureDom.appendChild(cdata);
|
||||
} else {
|
||||
featureDom.setAttribute(key, this._rmXmlInv(value));
|
||||
}
|
||||
}
|
||||
parentTopic.appendChild(featureDom);
|
||||
});
|
||||
|
||||
// CHILDREN TOPICS
|
||||
const childTopics = topic.getChildren();
|
||||
childTopics.forEach((childTopic) => {
|
||||
const childDom = this._topicToXML(document, childTopic);
|
||||
parentTopic.appendChild(childDom);
|
||||
});
|
||||
|
||||
return parentTopic;
|
||||
}
|
||||
|
||||
protected _noteTextToXML(document: Document, elem: Element, text: string) {
|
||||
if (text.indexOf('\n') === -1) {
|
||||
elem.setAttribute('text', this._rmXmlInv(text));
|
||||
} else {
|
||||
const textDom = document.createElement('text');
|
||||
const cdata = document.createCDATASection(this._rmXmlInv(text));
|
||||
textDom.appendChild(cdata);
|
||||
elem.appendChild(textDom);
|
||||
}
|
||||
}
|
||||
|
||||
static _relationshipToXML(document: Document, relationship: RelationshipModel) {
|
||||
const result = document.createElement('relationship');
|
||||
result.setAttribute('srcTopicId', relationship.getFromNode().toString());
|
||||
result.setAttribute('destTopicId', relationship.getToNode().toString());
|
||||
|
||||
const lineType = relationship.getLineType();
|
||||
result.setAttribute('lineType', lineType.toString());
|
||||
if (lineType === ConnectionLine.CURVED || lineType === ConnectionLine.SIMPLE_CURVED) {
|
||||
if ($defined(relationship.getSrcCtrlPoint())) {
|
||||
const srcPoint = relationship.getSrcCtrlPoint();
|
||||
result.setAttribute(
|
||||
'srcCtrlPoint',
|
||||
`${Math.round(srcPoint.x)},${Math.round(srcPoint.y)}`,
|
||||
);
|
||||
}
|
||||
if ($defined(relationship.getDestCtrlPoint())) {
|
||||
const destPoint = relationship.getDestCtrlPoint();
|
||||
result.setAttribute(
|
||||
'destCtrlPoint',
|
||||
`${Math.round(destPoint.x)},${Math.round(destPoint.y)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
result.setAttribute('endArrow', String(relationship.getEndArrow()));
|
||||
result.setAttribute('startArrow', String(relationship.getStartArrow()));
|
||||
return result;
|
||||
}
|
||||
|
||||
loadFromDom(dom: Document, mapId: string) {
|
||||
$assert(dom, 'dom can not be null');
|
||||
$assert(mapId, 'mapId can not be null');
|
||||
|
||||
const rootElem = dom.documentElement;
|
||||
|
||||
// Is a wisemap?.
|
||||
$assert(
|
||||
rootElem.tagName === XMLSerializerTango.MAP_ROOT_NODE,
|
||||
`This seem not to be a map document. Found tag: ${rootElem.tagName}`,
|
||||
);
|
||||
|
||||
this._idsMap = {};
|
||||
// Start the loading process ...
|
||||
const version = rootElem.getAttribute('version') || 'pela';
|
||||
const mindmap = new Mindmap(mapId, version);
|
||||
|
||||
// Add all the topics nodes ...
|
||||
const childNodes = Array.from(rootElem.childNodes);
|
||||
const topicsNodes = childNodes
|
||||
.filter(
|
||||
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'topic',
|
||||
)
|
||||
.map((c) => c as Element);
|
||||
topicsNodes.forEach((child) => {
|
||||
const topic = this._deserializeNode(child, mindmap);
|
||||
mindmap.addBranch(topic);
|
||||
});
|
||||
|
||||
// Then all relationshops, they are connected to topics ...
|
||||
const relationshipsNodes = childNodes
|
||||
.filter(
|
||||
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'relationship',
|
||||
)
|
||||
.map((c) => c as Element);
|
||||
relationshipsNodes.forEach((child) => {
|
||||
try {
|
||||
const relationship = XMLSerializerTango._deserializeRelationship(child, mindmap);
|
||||
mindmap.addRelationship(relationship);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up from the recursion ...
|
||||
this._idsMap = null;
|
||||
mindmap.setId(mapId);
|
||||
return mindmap;
|
||||
}
|
||||
|
||||
protected _deserializeNode(domElem: Element, mindmap: Mindmap) {
|
||||
const type = domElem.getAttribute('central') != null ? 'CentralTopic' : 'MainTopic';
|
||||
|
||||
// Load attributes...
|
||||
let id: number | null = null;
|
||||
if ($defined(domElem.getAttribute('id'))) {
|
||||
id = Number.parseInt(domElem.getAttribute('id'), 10);
|
||||
}
|
||||
|
||||
if (this._idsMap[id]) {
|
||||
id = null;
|
||||
} else {
|
||||
this._idsMap[id] = domElem;
|
||||
}
|
||||
|
||||
const topic = mindmap.createNode(type, id);
|
||||
|
||||
// Set text property is it;s defined...
|
||||
const text = domElem.getAttribute('text');
|
||||
if ($defined(text) && text) {
|
||||
topic.setText(text);
|
||||
}
|
||||
|
||||
const fontStyle = domElem.getAttribute('fontStyle');
|
||||
if ($defined(fontStyle) && fontStyle) {
|
||||
const font = fontStyle.split(';');
|
||||
|
||||
if (font[0]) {
|
||||
topic.setFontFamily(font[0]);
|
||||
}
|
||||
|
||||
if (font[1]) {
|
||||
topic.setFontSize(Number.parseInt(font[1], 10));
|
||||
}
|
||||
|
||||
if (font[2]) {
|
||||
topic.setFontColor(font[2]);
|
||||
}
|
||||
|
||||
if (font[3]) {
|
||||
topic.setFontWeight(font[3]);
|
||||
}
|
||||
|
||||
if (font[4]) {
|
||||
topic.setFontStyle(font[4]);
|
||||
}
|
||||
}
|
||||
|
||||
const shape = domElem.getAttribute('shape');
|
||||
if ($defined(shape)) {
|
||||
topic.setShapeType(shape);
|
||||
|
||||
if (shape === TopicShape.IMAGE) {
|
||||
const image = domElem.getAttribute('image');
|
||||
const size = image.substring(0, image.indexOf(':'));
|
||||
const url = image.substring(image.indexOf(':') + 1, image.length);
|
||||
topic.setImageUrl(url);
|
||||
|
||||
const split = size.split(',');
|
||||
topic.setImageSize(Number.parseInt(split[0], 10), Number.parseInt(split[1], 10));
|
||||
}
|
||||
}
|
||||
|
||||
const bgColor = domElem.getAttribute('bgColor');
|
||||
if ($defined(bgColor)) {
|
||||
topic.setBackgroundColor(bgColor);
|
||||
}
|
||||
|
||||
const borderColor = domElem.getAttribute('brColor');
|
||||
if ($defined(borderColor)) {
|
||||
topic.setBorderColor(borderColor);
|
||||
}
|
||||
|
||||
const order = domElem.getAttribute('order');
|
||||
if ($defined(order) && order !== 'NaN') {
|
||||
// Hack for broken maps ...
|
||||
topic.setOrder(parseInt(order, 10));
|
||||
}
|
||||
|
||||
const isShrink = domElem.getAttribute('shrink');
|
||||
// Hack: Some production maps has been stored with the central topic collapsed. This is a bug.
|
||||
if ($defined(isShrink) && type !== 'CentralTopic') {
|
||||
topic.setChildrenShrunken(Boolean(isShrink));
|
||||
}
|
||||
|
||||
const position = domElem.getAttribute('position');
|
||||
if ($defined(position)) {
|
||||
const pos = position.split(',');
|
||||
topic.setPosition(Number.parseInt(pos[0], 10), Number.parseInt(pos[1], 10));
|
||||
}
|
||||
|
||||
const metadata = domElem.getAttribute('metadata');
|
||||
if ($defined(metadata)) {
|
||||
topic.setMetadata(metadata);
|
||||
}
|
||||
|
||||
// Creating icons and children nodes
|
||||
const children = Array.from(domElem.childNodes);
|
||||
children.forEach((child) => {
|
||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
||||
const elem = child as Element;
|
||||
if (elem.tagName === 'topic') {
|
||||
const childTopic = this._deserializeNode(elem, mindmap);
|
||||
childTopic.connectTo(topic);
|
||||
} else if (FeatureModelFactory.isSupported(elem.tagName)) {
|
||||
// Load attributes ...
|
||||
const namedNodeMap = elem.attributes;
|
||||
const attributes: Record<string, string> = {};
|
||||
|
||||
for (let j = 0; j < namedNodeMap.length; j++) {
|
||||
const attribute = namedNodeMap.item(j);
|
||||
attributes[attribute.name] = attribute.value;
|
||||
}
|
||||
|
||||
// Has text node ?.
|
||||
const textAttr = XMLSerializerTango._deserializeTextAttr(elem);
|
||||
if (textAttr) {
|
||||
attributes.text = textAttr;
|
||||
}
|
||||
|
||||
// Create a new element ....
|
||||
const featureType = elem.tagName as FeatureType;
|
||||
const feature = FeatureModelFactory.createModel(featureType, attributes);
|
||||
topic.addFeature(feature);
|
||||
} else if (elem.tagName === 'text') {
|
||||
const nodeText = XMLSerializerTango._deserializeNodeText(child);
|
||||
topic.setText(nodeText);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Workaround: for some reason, some saved maps have holes in the order.
|
||||
if (topic.getType() !== 'CentralTopic') {
|
||||
topic
|
||||
.getChildren()
|
||||
.forEach((child, index) => {
|
||||
if (child.getOrder() !== index) {
|
||||
child.setOrder(index);
|
||||
console.log('Toppic with order sequence hole. Introducing auto recovery sequence fix.');
|
||||
}
|
||||
});
|
||||
}
|
||||
return topic;
|
||||
}
|
||||
|
||||
static _deserializeTextAttr(domElem: Element): string {
|
||||
let value = domElem.getAttribute('text');
|
||||
if (!$defined(value)) {
|
||||
const children = domElem.childNodes;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
if (child.nodeType === Node.CDATA_SECTION_NODE) {
|
||||
value = child.nodeValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Notes must be decoded ...
|
||||
value = unescape(value);
|
||||
|
||||
// Hack for empty nodes ...
|
||||
if (value === '') {
|
||||
value = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static _deserializeNodeText(domElem) {
|
||||
const children = domElem.childNodes;
|
||||
let value = null;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
if (child.nodeType === Node.CDATA_SECTION_NODE) {
|
||||
value = child.nodeValue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static _deserializeRelationship(domElement, mindmap) {
|
||||
const srcId = Number.parseInt(domElement.getAttribute('srcTopicId'), 10);
|
||||
const destId = Number.parseInt(domElement.getAttribute('destTopicId'), 10);
|
||||
const lineType = Number.parseInt(domElement.getAttribute('lineType'), 10);
|
||||
const srcCtrlPoint = domElement.getAttribute('srcCtrlPoint');
|
||||
const destCtrlPoint = domElement.getAttribute('destCtrlPoint');
|
||||
|
||||
// If for some reason a relationship lines has source and dest nodes the same, don't import it.
|
||||
if (srcId === destId) {
|
||||
throw new Error('Invalid relationship, dest and source are equals');
|
||||
}
|
||||
// Is the connections points valid ?. If it's not, do not load the relationship ...
|
||||
if (mindmap.findNodeById(srcId) == null || mindmap.findNodeById(destId) == null) {
|
||||
throw new Error('Transition could not created, missing node for relationship');
|
||||
}
|
||||
|
||||
const model = mindmap.createRelationship(srcId, destId);
|
||||
model.setLineType(lineType);
|
||||
if ($defined(srcCtrlPoint) && srcCtrlPoint !== '') {
|
||||
model.setSrcCtrlPoint(Point.fromString(srcCtrlPoint));
|
||||
}
|
||||
if ($defined(destCtrlPoint) && destCtrlPoint !== '') {
|
||||
model.setDestCtrlPoint(Point.fromString(destCtrlPoint));
|
||||
}
|
||||
model.setEndArrow('false');
|
||||
model.setStartArrow('true');
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method ensures that the output String has only
|
||||
* valid XML unicode characters as specified by the
|
||||
* XML 1.0 standard. For reference, please see
|
||||
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
|
||||
* standard</a>. This method will return an empty
|
||||
* String if the input is null or empty.
|
||||
*
|
||||
* @param in The String whose non-valid characters we want to remove.
|
||||
* @return The in String, stripped of non-valid characters.
|
||||
*/
|
||||
protected _rmXmlInv(str: string) {
|
||||
if (str == null || str === undefined) return null;
|
||||
|
||||
let result = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const c = str.charCodeAt(i);
|
||||
if (
|
||||
c === 0x9
|
||||
|| c === 0xa
|
||||
|| c === 0xd
|
||||
|| (c >= 0x20 && c <= 0xd7ff)
|
||||
|| (c >= 0xe000 && c <= 0xfffd)
|
||||
|| (c >= 0x10000 && c <= 0x10ffff)
|
||||
) {
|
||||
result += str.charAt(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export default XMLSerializerTango;
|
||||
|
@ -25,7 +25,8 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
||||
// Overwite default behaviour ...
|
||||
},
|
||||
setValue() {
|
||||
window.location = '/c/logout';
|
||||
const elem = document.getElementById('logoutFrom');
|
||||
elem.submit();
|
||||
},
|
||||
};
|
||||
super(elemId, model);
|
||||
@ -59,6 +60,7 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
||||
content[0].innerHTML = `
|
||||
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
||||
<p>${global.accountEmail}</p>
|
||||
<form action="/c/logout" method='POST' id="logoutFrom"></form>
|
||||
<div id="account-logout" model='logout' style='text-align:center'>
|
||||
Logout
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user