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

This commit is contained in:
Ezequiel-Vega 2022-02-23 15:46:35 -03:00
commit 5bebf75a32
145 changed files with 3405 additions and 2512 deletions

View File

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

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -24,6 +24,7 @@ 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';

View File

@ -1,10 +1,12 @@
@import "compatibility.css";
/********************************************************************************/
/* Header & Toolbar Styles */
/********************************************************************************/
@import "header.css";
@import "../bootstrap/css/bootstrap.min.css";
@import "bootstrap.min.css";
html {
/* avoid bootstrap overriding font-size and breaking Mui */
font-size: initial;
}
body {
-webkit-touch-callout: none;
@ -126,6 +128,7 @@ div.shareModalDialog {
}
.popover {
font-size: 13px;
max-width: none;
}
@ -140,7 +143,7 @@ div.shareModalDialog {
}
div#position {
margin-top: 5px;
margin-top: 5px;
}
#position-button {
@ -205,4 +208,24 @@ div#shotcuts > img{
#keyboardTable th {
background-color: #000000;
color: #ffffff;
}
div#tryInfoPanel {
position: absolute;
margin: auto;
text-align: center;
top: 80px;
right: 20px;
width: 200px;
height: 300px;
padding: 20px;
font-size: 15px;
border-radius: 9px;
background-color: white;
border: solid 2px #ffa800;
}
#tryInfoPanel > p {
justify-content: center;
padding-bottom: 20px;
}

View File

@ -1,48 +1,45 @@
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,
} 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 { EditorModeType } from '@wisemapping/mindplot/src/components/DesignerOptionsBuilder';
declare global {
var memoryPersistence: boolean;
var readOnly: boolean;
var lockTimestamp: string;
var lockSession: string;
var historyId: string;
var isAuth: boolean;
var mapId: number;
var userOptions: { zoom: string | number } | null;
var locale: string;
var mindmapLocked: boolean;
var mindmapLockedMsg: string;
var mapTitle: string;
// used in mindplot
var designer: Designer;
var accountEmail: string;
}
export type EditorPropsType = {
initCallback?: (locale: string) => void;
mapId?: number;
isTryMode: boolean;
readOnlyMode: boolean;
locale?: string;
export type EditorOptions = {
mode: EditorModeType,
locale: string,
zoom?: number,
locked?: boolean,
lockedMsg?: string;
mapTitle: string;
enableKeyboardEvents: boolean;
}
export type EditorProps = {
mapId: string;
options: EditorOptions;
onAction: (action: ToolbarActionType) => void;
persistenceManager: PersistenceManager;
initCallback?: (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager) => void;
};
const loadLocaleData = (locale: string) => {
@ -60,76 +57,59 @@ const loadLocaleData = (locale: string) => {
}
}
const initMindplot = (locale: string) => {
const defaultCallback = (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager) => {
// Change page title ...
document.title = `${global.mapTitle} | WiseMapping `;
document.title = `${options.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),
const buildOptions = DesignerOptionsBuilder.buildOptions({
persistenceManager,
mode: options.mode,
mapId: mapId,
container: 'mindplot',
zoom:
zoomParam ||
(global.userOptions?.zoom != undefined
? Number.parseFloat(global.userOptions.zoom as string)
: 0.8),
locale: locale,
zoom: options.zoom,
locale: options.locale,
});
// Build designer ...
const designer = buildDesigner(options);
const designer = buildDesigner(buildOptions);
// Load map from XML file persisted on disk...
const instance = PersistenceManager.getInstance();
const mindmap = instance.load(String(global.mapId));
const mindmap = instance.load(mapId);
designer.loadMap(mindmap);
if (global.mindmapLocked) {
$notify(global.mindmapLockedMsg);
if (options.locked) {
$notify(options.lockedMsg);
}
};
const Editor = ({
initCallback = initMindplot,
mapId,
isTryMode: isTryMode,
locale = 'en',
options,
persistenceManager,
initCallback = defaultCallback,
onAction,
}: EditorPropsType): React.ReactElement => {
React.useEffect(() => {
initCallback(locale);
}: EditorProps) => {
useEffect(() => {
initCallback(mapId, options, persistenceManager);
}, []);
useEffect(() => {
if (options.enableKeyboardEvents) {
DesignerKeyboard.resume();
} else {
DesignerKeyboard.pause();
}
}, [options.enableKeyboardEvents]);
return (
<IntlProvider locale={locale} messages={loadLocaleData(locale)}>
<IntlProvider locale={options.locale} messages={loadLocaleData(options.locale)}>
<Toolbar
isTryMode={isTryMode}
isTryMode={options.mode === 'showcase'}
onAction={onAction}
/>
<div id="mindplot"></div>
<Footer showTryPanel={isTryMode} />
<Footer showTryPanel={options.mode === 'showcase'} />
</IntlProvider>
);
}

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
@import "editor.css";
/* Overwrite some styles */
body {
position: inherit;
@ -32,4 +30,8 @@ div#footer-logo {
div#mindplot {
top:0;
}
#toolbar {
display: none;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,49 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { EditorOptions } from '../../../../src/index';
import { buildDesigner, LocalStorageManager, PersistenceManager, DesignerOptionsBuilder } from '@wisemapping/mindplot';
const initialization = (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager) => {
const designerOptions = DesignerOptionsBuilder.buildOptions({
persistenceManager: persistenceManager,
zoom: options.zoom ? options.zoom : 0.8,
mode: options.mode,
container: 'mindplot'
});
const designer = buildDesigner(designerOptions);
designer.addEvent('loadSuccess', () => {
const elem = document.getElementById('mindplot');
if (elem) {
elem.classList.add('ready');
}
});
// Load map from XML file persisted on disk...
const persistence = PersistenceManager.getInstance();
const mindmap = persistence.load(mapId);
designer.loadMap(mindmap);
};
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)}
initCallback={initialization}
/>,
document.getElementById('root'),
);

View File

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

View File

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

View File

@ -0,0 +1,67 @@
import '../css/viewmode.css';
import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { EditorOptions } from '../../../../src/index';
import { buildDesigner, LocalStorageManager, PersistenceManager, DesignerOptionsBuilder } from '@wisemapping/mindplot';
const initialization = (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager) => {
const designerOptions = DesignerOptionsBuilder.buildOptions({
persistenceManager: persistenceManager,
zoom: options.zoom ? options.zoom : 0.8,
mode: options.mode,
container: 'mindplot'
});
const designer = buildDesigner(designerOptions);
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;
});
}
});
// Load map from XML file persisted on disk...
const persistence = PersistenceManager.getInstance();
const mindmap = persistence.load(mapId);
designer.loadMap(mindmap);
};
// 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)}
initCallback={initialization}
/>,
document.getElementById('root'),
);

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@wisemapping/mindplot",
"version": "5.0.3",
"version": "5.0.7",
"description": "WiseMapping - Mindplot Canvas Library",
"homepage": "http://www.wisemapping.org/",
"directories": {

View File

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

View File

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

View File

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

View File

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

View File

@ -110,7 +110,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();
@ -335,7 +335,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 +626,7 @@ class Designer extends Events {
}
isReadOnly(): boolean {
return Boolean(this._options?.readOnly);
return Boolean(this._options?.mode === 'viewonly');
}
nodeModelToTopic(nodeModel: NodeModel): Topic {

View File

@ -20,8 +20,6 @@ import $ from 'jquery';
import PersistenceManager from './PersistenceManager';
import Designer from './Designer';
import Menu from './widget/Menu';
import { $notifyModal } from './widget/ModalDialogNotifier';
import { $msg } from './Messages';
import { DesignerOptions } from './DesignerOptionsBuilder';
let designer: Designer;
@ -37,36 +35,6 @@ export function buildDesigner(options: DesignerOptions): Designer {
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;
$assert(persistence, 'persistence must be defined');

View File

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

View File

@ -19,10 +19,12 @@ import { $assert } from '@wisemapping/core-js';
import PersistenceManager from './PersistenceManager';
import SizeType from './SizeType';
export type EditorModeType = 'viewonly' | 'edition' | 'showcase';
export type DesignerOptions = {
zoom: number,
containerSize?: SizeType,
readOnly?: boolean,
mode: EditorModeType,
mapId?: string,
container: string,
persistenceManager?: PersistenceManager,
@ -45,7 +47,7 @@ class OptionsBuilder {
}
const defaultOptions: DesignerOptions = {
readOnly: false,
mode: 'edition',
zoom: 0.85,
saveOnLoad: true,
containerSize,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,12 +28,6 @@ import SizeType from './SizeType';
class MainTopic extends Topic {
private INNER_RECT_ATTRIBUTES: { stroke: string; };
/**
* @extends mindplot.Topic
* @constructs
* @param model
* @param options
*/
constructor(model: NodeModel, options) {
super(model, options);
this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' };
@ -88,7 +82,6 @@ class MainTopic extends Topic {
}
}
/** */
disconnect(workspace: Workspace) {
super.disconnect(workspace);
const model = this.getModel();

View File

@ -0,0 +1,48 @@
/*
* Copyright [2022] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import $ from 'jquery';
import { $assert } from '@wisemapping/core-js';
import PersistenceManager from './PersistenceManager';
class MockPersistenceManager extends PersistenceManager {
private exampleMap: string;
constructor(exampleMapAsXml: string) {
super();
$assert(exampleMapAsXml, 'The test map must be set');
this.exampleMap = exampleMapAsXml;
}
saveMapXml(): void {
// Ignore, no implementation required ...
}
discardChanges() {
// Ignore, no implementation required ...
}
loadMapDom() {
return $.parseXML(this.exampleMap);
}
unlockMap(): void {
// Ignore, no implementation required ...
}
}
export default MockPersistenceManager;

View File

@ -21,58 +21,56 @@ import $ from 'jquery';
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
import Events from './Events';
import ActionDispatcher from './ActionDispatcher';
import Topic from './Topic';
initHotKeyPluggin($);
class MultilineTextEditor extends Events {
private _topic: Topic;
private _containerElem: JQuery;
constructor() {
super();
this._topic = null;
this._timeoutId = -1;
}
static _buildEditor() {
private static _buildEditor() {
const result = $('<div></div>')
.attr('id', 'textContainer')
.css({
display: 'none',
zIndex: '8',
overflow: 'hidden',
border: '0 none',
});
const textareaElem = $('<textarea tabindex="-1" value="" wrap="off" ></textarea>')
.css({
border: '1px gray dashed',
background: 'rgba(98, 135, 167, .3)',
background: 'rgba(98, 135, 167, .4)',
outline: '0 none',
resize: 'none',
overflow: 'hidden',
padding: '2px 0px 2px 4px',
});
result.append(textareaElem);
return result;
}
_registerEvents(containerElem) {
private _registerEvents(containerElem: JQuery) {
const textareaElem = this._getTextareaElem();
const me = this;
let start;
let end;
textareaElem.on('keydown', function keydown(event) {
switch ($.hotkeys.specialKeys[event.keyCode]) {
textareaElem.on('keydown', (event) => {
const j: any = $;
switch (j.hotkeys.specialKeys[event.keyCode]) {
case 'esc':
me.close(false);
this.close(false);
break;
case 'enter':
case 'enter': {
if (event.metaKey || event.ctrlKey) {
// Add return ...
const text = textareaElem.val();
let cursorPosition = text.length;
if (textareaElem.selectionStart) {
cursorPosition = textareaElem.selectionStart;
}
const text = this._getTextAreaText();
const cursorPosition = text.length;
const head = text.substring(0, cursorPosition);
let tail = '';
if (cursorPosition < text.length) {
@ -80,31 +78,12 @@ class MultilineTextEditor extends Events {
}
textareaElem.val(`${head}\n${tail}`);
// Position cursor ...
if (textareaElem[0].setSelectionRange) {
textareaElem.focus();
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
} else if (textareaElem.createTextRange) {
const range = textareaElem.createTextRange();
range.moveStart('character', cursorPosition + 1);
range.select();
}
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,9 +17,8 @@
*/
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
import { Mindmap } from '..';
import { $msg } from './Messages';
import PersistenceManager from './PersistenceManager';
import PersistenceManager, { PersistenceError } from './PersistenceManager';
class RESTPersistenceManager extends PersistenceManager {
private documentUrl: string;
@ -51,10 +50,10 @@ class RESTPersistenceManager extends PersistenceManager {
this.session = options.session;
}
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events, sync: boolean): void {
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events): void {
const data = {
id: mapId,
xml: mapXml,
xml: new XMLSerializer().serializeToString(mapXml),
properties: pref,
};
@ -71,73 +70,79 @@ class RESTPersistenceManager extends PersistenceManager {
}, 10000);
const persistence = this;
$.ajax({
type: 'put',
url: `${this.documentUrl.replace('{id}', mapId)}?${query}`,
dataType: 'json',
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
async: !sync,
success(successData) {
persistence.timestamp = successData;
fetch(
`${this.documentUrl.replace('{id}', mapId)}?${query}`,
{
method: 'PUT',
// Blob helps to resuce the memory on large payload.
body: new Blob([JSON.stringify(data)], { type: 'text/plain' }),
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
},
).then(async (response: Response) => {
if (response.ok) {
persistence.timestamp = await response.text();
events.onSuccess();
},
complete() {
// Clear event timeout ...
if (persistence.clearTimeout) {
clearTimeout(persistence.clearTimeout);
}
persistence.onSave = false;
},
error(xhr) {
const { responseText } = xhr;
let userMsg = { severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED') };
const contentType = xhr.getResponseHeader('Content-Type');
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;
},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,7 +62,7 @@ class DragTopicCommand extends Command {
const origPosition = topic.getPosition();
// Disconnect topic ..
if ($defined(origParentTopic) && origParentTopic.getId() !== this._parentId) {
if ($defined(origParentTopic)) {
commandContext.disconnect(topic);
}
@ -76,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);

View File

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

View File

@ -15,26 +15,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Mindmap } from '../..';
import Exporter from './Exporter';
import SVGExporter from './SVGExporter';
/**
* Based on https://mybyways.com/blog/convert-svg-to-png-using-your-browser
*/
class BinaryImageExporter extends Exporter {
svgElement: Element;
private svgElement: Element;
mindmap: Mindmap;
private width: number;
width: number;
private height: number;
height: number;
private adjustToFit: boolean;
constructor(mindmap: Mindmap, svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg') {
super(imgFormat.split['/'][0], imgFormat);
constructor(svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg', adjustToFit = true) {
super(imgFormat.split('/')[0], imgFormat);
this.svgElement = svgElement;
this.mindmap = mindmap;
this.adjustToFit = adjustToFit;
this.width = width;
this.height = height;
}
@ -43,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;

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,6 @@ class RootedTreeSet {
protected _children: Node[];
constructor() {
this._rootNodes = [];
}
@ -56,10 +55,9 @@ class RootedTreeSet {
*/
add(node: Node) {
$assert(node, 'node can not be null');
$assert(
!this.find(node.getId(), false),
`node already exits with this id. Id:${node.getId()}: ${this.dump()}`,
);
if (this.find(node.getId(), false)) {
throw new Error(`node already exits with this id. Id:${node.getId()}: ${this.dump()}`);
}
$assert(!node._children, 'node already added');
this._rootNodes.push(this._decodate(node));
}
@ -131,10 +129,11 @@ class RootedTreeSet {
break;
}
}
$assert(
validate ? result : true,
`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`,
);
if (validate && !result) {
throw new Error(`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`);
}
return result;
}
@ -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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -70,10 +70,14 @@ class IMenu {
unlockMap(designer: Designer) {
const mindmap = designer.getMindmap();
const persistenceManager = PersistenceManager.getInstance();
persistenceManager.unlockMap(mindmap);
// If the map could not be loaded, partial map load could happen.
if (mindmap) {
persistenceManager.unlockMap(mindmap.getId());
}
}
save(saveElem: JQuery, designer: Designer, saveHistory: boolean, sync?: boolean) {
save(saveElem: JQuery, designer: Designer, saveHistory: boolean) {
// Load map content ...
const mindmap = designer.getMindmap();
const mindmapProp = designer.getMindmapProperties();
@ -106,7 +110,7 @@ class IMenu {
}
}
},
}, sync);
});
}
isSaveRequired(): boolean {

View File

@ -57,7 +57,7 @@ class Menu extends IMenu {
return result;
},
setValue(value) {
setValue(value: string) {
designer.changeFontFamily(value);
},
};
@ -68,7 +68,7 @@ class Menu extends IMenu {
const fontSizeBtn = $('#fontSize');
if (fontSizeBtn) {
const fontSizeModel = {
getValue() {
getValue(): number {
const nodes = designerModel.filterSelectedTopics();
let result = null;
@ -82,7 +82,7 @@ class Menu extends IMenu {
}
return result;
},
setValue(value) {
setValue(value: number) {
designer.changeFontSize(value);
},
};
@ -297,9 +297,9 @@ class Menu extends IMenu {
Menu._registerTooltip('save', $msg('SAVE'), 'meta+S');
if (!readOnly) {
$(window).bind('beforeunload', () => {
window.addEventListener('beforeunload', () => {
if (this.isSaveRequired()) {
this.save(saveElem, designer, false, true);
this.save(saveElem, designer, false);
}
this.unlockMap(designer);
});
@ -310,7 +310,7 @@ class Menu extends IMenu {
if (this.isSaveRequired()) {
this.save(saveElem, designer, false);
}
}, 30000,
}, 10000,
);
}
}

View File

@ -22,11 +22,13 @@ import PersistenceManager from './components/PersistenceManager';
import Designer from './components/Designer';
import LocalStorageManager from './components/LocalStorageManager';
import RESTPersistenceManager from './components/RestPersistenceManager';
import MockPersistenceManager from './components/MockPersistenceManager';
import Menu from './components/widget/Menu';
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
import ImageExporterFactory from './components/export/ImageExporterFactory';
import TextExporterFactory from './components/export/TextExporterFactory';
import Exporter from './components/export/Exporter';
import DesignerKeyboard from './components/DesignerKeyboard';
import {
buildDesigner,
@ -47,6 +49,7 @@ export {
DesignerBuilder,
PersistenceManager,
RESTPersistenceManager,
MockPersistenceManager,
LocalStorageManager,
DesignerOptionsBuilder,
buildDesigner,
@ -54,4 +57,5 @@ export {
ImageExporterFactory,
Exporter,
$notify,
DesignerKeyboard,
};

View File

@ -57,7 +57,7 @@ const zoomParam = Number.parseFloat(params.get('zoom'));
const options = DesignerOptionsBuilder.buildOptions(
{
persistenceManager: persistence,
readOnly: Boolean(global.readOnly || false),
mode: 'viewonly',
mapId: global.mapId,
container: 'mindplot',
zoom: zoomParam || global.userOptions.zoom,

View File

@ -1,8 +1,6 @@
import path from 'path';
import fs from 'fs';
import { expect, test } from '@jest/globals'; // Workaround for cypress conflict
import Mindmap from '../../../src/components/model/Mindmap';
import XMLSerializerFactory from '../../../src/components/persistence/XMLSerializerFactory';
import SVGExporter from '../../../src/components/export/SVGExporter';
import { parseXMLFile, setupBlob, exporterAssert } from './Helper';
@ -12,14 +10,6 @@ describe('SVG export test execution', () => {
test.each(fs.readdirSync(path.resolve(__dirname, './input/'))
.filter((f) => f.endsWith('.wxml'))
.map((filename: string) => filename.split('.')[0]))('Exporting %p suite', async (testName: string) => {
// Load mindmap DOM ...
const mindmapPath = path.resolve(__dirname, `./input/${testName}.wxml`);
const mapDocument = parseXMLFile(mindmapPath, 'text/xml');
// Convert to mindmap ...
const serializer = XMLSerializerFactory.createInstanceFromDocument(mapDocument);
const mindmap: Mindmap = serializer.loadFromDom(mapDocument, testName);
// Load SVG ...
const svgPath = path.resolve(__dirname, `./input/${testName}.svg`);
expect(fs.existsSync(svgPath)).toEqual(true);

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-609.4499999999999 -272.425 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M191.00,208.00 C200.60,208 210.20,333.00 219.80,333.00"/>
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M191.00,208.00 C200.77,208 210.53,301.00 220.30,301.00"/>
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M191.00,208.00 C200.60,208 210.20,269.00 219.80,269.00"/>

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -24,6 +24,12 @@
1.12 Actividades centradas en el contexto cercano
1.13 Flexibilidad en el uso de las lenguas de trabajo (inglés, castellano, esukara?)
1.14 Complementamos el trabajo de la escuela
[Note: Todos los contenidos de los talleres están relacionados con el currículo de la enseñanza básica.
A diferencia de la práctica tradicional, pretendemos ahondar en el conocimiento partiendo de lo que realmente interesa al niño o niña,
ayudándole a que encuentre respuesta a las preguntas que él o ella se plantea.
Por ese motivo, SaberMás proyecta estar al lado de los niños que necesitan una motivación extra para entender la escuela y fluir en ella,
y también al lado de aquellos a quienes la curiosidad y las ganas de saber les lleva más allá.]
1.14.1 Cada uno va a su ritmo, y cada cual pone sus límites
1.14.2 Aprendemos todos de todos
1.14.3 Valoramos lo que hemos aprendido

View File

@ -1,6 +1,6 @@
<map name="bug2" version="tango"><topic central="true" text="SaberMás" id="1"><topic position="271,-39" order="8" text="Utilización de medios de expresión artística, digitales y analógicos" id="5"/><topic position="-181,-17" order="5" text="Precio también limitado: 100-120?" id="9"/><topic position="132,165" order="14" text="Talleres temáticos" id="2"><topic position="242,57" order="0" text="Naturaleza" id="13"><topic position="362,57" order="0" text="Animales, Plantas, Piedras" id="17"/></topic><topic position="245,84" order="1" text="Arqueología" id="21"/><topic position="236,138" order="3" text="Energía" id="18"/><topic position="244,192" order="5" text="Astronomía" id="16"/><topic position="245,219" order="6" text="Arquitectura" id="20"/><topic position="234,246" order="7" text="Cocina" id="11"/><topic position="234,273" order="8" text="Poesía" id="24"/><topic position="256,111" order="2" text="Culturas Antiguas" id="25"><topic position="378,111" order="0" text="Egipto, Grecia, China..." id="26"/></topic><topic position="248,165" order="4" text="Paleontología" id="38"/></topic><topic position="-168,-49" order="3" text="Duración limitada: 5-6 semanas" id="6"/><topic position="-181,16" order="7" text="Niños y niñas que quieren saber más" id="7"/><topic position="-184,-81" order="1" text="Alternativa a otras actividades de ocio" id="8"/><topic position="255,-6" order="10" text="Uso de la tecnología durante todo el proceso de aprendizaje" id="23"/><topic position="336,-137" order="2" text="Estructura PBL: aprendemos cuando buscamos respuestas a nuestras propias preguntas " id="3"/><topic position="238,-105" order="4" text="Trabajo basado en la experimentación y en la investigación" id="4"/><topic position="-201,48" order="9" text="De 8 a 12 años, sin separación por edades" id="10"/><topic position="-146,81" order="11" text="Máximo 10/1 por taller" id="19"/><topic position="211,-72" order="6" text="Actividades centradas en el contexto cercano" id="37"/><topic position="303,27" order="12" text="Flexibilidad en el uso de las lenguas de trabajo (inglés, castellano, esukara?)" id="22"/><topic position="206,-220" order="0" text="Complementamos el trabajo de la escuela" shape="rounded rectagle" id="27"><note><![CDATA[Todos los contenidos de los talleres están relacionados con el currículo de la enseñanza básica.
<map name="bug2" version="tango"><topic central="true" text="SaberMás" id="1"><topic position="271,-39" order="8" text="Utilización de medios de expresión artística, digitales y analógicos" id="5"/><topic position="-181,-17" order="5" text="Precio también limitado: 100-120?" id="9"/><topic position="132,165" order="14" text="Talleres temáticos" id="2"><topic position="242,57" order="0" text="Naturaleza" id="13"><topic position="362,57" order="0" text="Animales, Plantas, Piedras" id="17"/></topic><topic position="245,84" order="1" text="Arqueología" id="21"/><topic position="236,138" order="2" text="Energía" id="18"/><topic position="244,192" order="3" text="Astronomía" id="16"/><topic position="245,219" order="4" text="Arquitectura" id="20"/><topic position="234,246" order="5" text="Cocina" id="11"/><topic position="234,273" order="6" text="Poesía" id="24"/><topic position="256,111" order="7" text="Culturas Antiguas" id="25"><topic position="378,111" order="0" text="Egipto, Grecia, China..." id="26"/></topic><topic position="248,165" order="8" text="Paleontología" id="38"/></topic><topic position="-168,-49" order="3" text="Duración limitada: 5-6 semanas" id="6"/><topic position="-181,16" order="7" text="Niños y niñas que quieren saber más" id="7"/><topic position="-184,-81" order="1" text="Alternativa a otras actividades de ocio" id="8"/><topic position="255,-6" order="10" text="Uso de la tecnología durante todo el proceso de aprendizaje" id="23"/><topic position="336,-137" order="2" text="Estructura PBL: aprendemos cuando buscamos respuestas a nuestras propias preguntas " id="3"/><topic position="238,-105" order="4" text="Trabajo basado en la experimentación y en la investigación" id="4"/><topic position="-201,48" order="9" text="De 8 a 12 años, sin separación por edades" id="10"/><topic position="-146,81" order="11" text="Máximo 10/1 por taller" id="19"/><topic position="211,-72" order="6" text="Actividades centradas en el contexto cercano" id="37"/><topic position="303,27" order="12" text="Flexibilidad en el uso de las lenguas de trabajo (inglés, castellano, esukara?)" id="22"/><topic position="206,-220" order="0" text="Complementamos el trabajo de la escuela" shape="rounded rectagle" id="27"><note><![CDATA[Todos los contenidos de los talleres están relacionados con el currículo de la enseñanza básica.
A diferencia de la práctica tradicional, pretendemos ahondar en el conocimiento partiendo de lo que realmente interesa al niño o niña,
ayudándole a que encuentre respuesta a las preguntas que él o ella se plantea.
Por ese motivo, SaberMás proyecta estar al lado de los niños que necesitan una motivación extra para entender la escuela y fluir en ella,
y también al lado de aquellos a quienes la curiosidad y las ganas de saber les lleva más allá.]]></note><topic position="477,-220" order="2" text="Cada uno va a su ritmo, y cada cual pone sus límites" id="30"/><topic position="425,-193" order="3" text="Aprendemos todos de todos" id="31"/><topic position="440,-167" order="4" text="Valoramos lo que hemos aprendido" id="33"/><topic position="468,-273" order="0" text="SaberMás trabaja con, desde y para la motivación" shape="line" id="28"/><topic position="458,-247" order="1" text="Trabajamos en equipo en nuestros proyectos " id="32"/></topic></topic></map>
y también al lado de aquellos a quienes la curiosidad y las ganas de saber les lleva más allá.]]></note><topic position="477,-220" order="0" text="Cada uno va a su ritmo, y cada cual pone sus límites" id="30"/><topic position="425,-193" order="1" text="Aprendemos todos de todos" id="31"/><topic position="440,-167" order="2" text="Valoramos lo que hemos aprendido" id="33"/><topic position="468,-273" order="3" text="SaberMás trabaja con, desde y para la motivación" shape="line" id="28"/><topic position="458,-247" order="4" text="Trabajamos en equipo en nuestros proyectos " id="32"/></topic></topic></map>

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-277.95 -272.425 1284.00071 802.50044" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>
<path stroke-width="2px" stroke="#9b74e6" stroke-opacity="1" visibility="visible" d="M607.8536585365854,-1362 L613.2957123430759,-1359.4733321612723 M607.8536585365854,-1362 L605.3269906978576,-1356.5579461935095"/>
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>

Before

Width:  |  Height:  |  Size: 443 KiB

After

Width:  |  Height:  |  Size: 443 KiB

View File

@ -1,13 +1,16 @@
1 Indicator needs
1.1 Which new measures
[Note: Identifying new measures or investments that should be implemented.]
1.1.1 Landscape of measures
1.1.1.1 Diversity index of innovation support instruments in the region
[Note: Number of different innovations policy instruments existing in the region as a share of a total number representing a full typology of instruments]
1.1.1.2 Existing investments in measures
1.1.2 What other regions do differently
1.1.2.1 Balance of measure index
1.1.2.2 Profile comparison with other regions
1.1.2.3 Number of specific types of measures per capita
1.2 How to design & implement measures
[Note: Understanding how to design the details of a particular measure and how to implement them.]
1.2.1 Good practices
1.2.2 Diagnostics
1.2.2.1 Internal business innovation factors
@ -34,7 +37,9 @@ highly cited scientific article in the whole Federation)
1.2.2.2.13 Number of innovative companies to the number of researchers
1.2.2.2.14 Volume of license agreements to the volume of R&D support from the regional budget
1.3 How much effort: where & how
[Note: Understanding the level of effort the region needs to take to compete on innovation and where to put this effort]
1.3.1 The bottom-line
[Note: This is what policy makers care about in the end]
1.3.1.1 Wages
1.3.1.1.1 Dynamics of real wages
1.3.1.1.2 Average wage (compare to the Fed)
@ -54,13 +59,17 @@ highly cited scientific article in the whole Federation)
1.3.2.1.3 Manufacturing value added per capita (non-natural resource-based)
1.3.2.2 The enabling environment
1.3.2.2.1 Ease of doing business
[Note: WB]
1.3.2.2.1.1 Level of administrative barriers (number and cost of administrative procedures)
1.3.2.2.2 Competition index
[Note: GCR]
1.3.2.2.3 Workforce
1.3.2.2.3.1 Quality of education
[Note: GCR]
1.3.2.2.3.1.1 Inrease in the number of International students
1.3.2.2.3.2 Quantity of education
1.3.2.2.3.2.1 Participation in life-long learning
[Note: per 100 population aged 25-64]
1.3.2.2.3.2.2 Increase in literarecy
1.3.2.2.3.2.3 Amount of university and colleague
students per 10 thousands population
@ -71,6 +80,7 @@ the total amount of population at the working age
1.3.2.2.3.2.7 Access to training, information, and consulting support
1.3.2.2.3.3 Science & engineering workforce
1.3.2.2.3.3.1 Availability of scientists and engineers
[Note: GCR]
1.3.2.2.3.3.2 Amount of researches per 10 thousands population
1.3.2.2.3.3.3 Average wage of researches per average wage in the region
1.3.2.2.3.3.4 Share of researchers in the total number of employees in the region
@ -89,6 +99,7 @@ the total amount of population at the working age
1.3.2.2.5.2.2 Number of Business Angels
1.3.2.2.6 ICT
1.3.2.2.6.1 ICT use
[Note: GCR]
1.3.2.2.6.2 Broadband penetration
1.3.2.2.6.3 Internet penetration
1.3.2.2.6.4 Computer literacy
@ -98,11 +109,13 @@ the total amount of population at the working age
1.3.2.3.1.1.1 foreign JVs
1.3.2.3.1.1.2 Inflow of foreign direct investments in high-technology industries
1.3.2.3.1.1.3 Foreign direct investment jobs
[Note: : the percentage of the workforce employed by foreign companies [%]. ]
1.3.2.3.1.1.4 FDI as a share of regional non natural resource-based GRP
1.3.2.3.1.1.5 Number of foreign subsidiaries operating in the region
1.3.2.3.1.1.6 Share of foreign controlled enterprises
1.3.2.3.1.2 Exports
1.3.2.3.1.2.1 Export intensity in manufacturing and services
[Note: : exports as a share of total output in manufacturing and services [%].]
1.3.2.3.1.2.2 Share of high-technology export in the total volume
of production of goods, works and services
1.3.2.3.1.2.3 Share of innovation production/serivces that goes for export,
@ -110,12 +123,17 @@ by zones (EU, US, CIS, other countries
1.3.2.3.1.3 Share of high-technology products in government procurements
1.3.2.3.2 Entrepreneurship culture
1.3.2.3.2.1 Fear of failure rate
[Note: GEM]
1.3.2.3.2.2 Entrepreneurship as desirable career choice
[Note: GEM]
1.3.2.3.2.3 High Status Successful Entrepreneurship
[Note: GEM]
1.3.2.3.3 Collaboration & partnerships
1.3.2.3.3.1 Number of business contracts with foreign partners for R&D collaboration
1.3.2.3.3.2 Share of R&D financed from foreign sources
[Note: UNESCO]
1.3.2.3.3.3 Firms collaborating on innovation with organizations in other countries
[Note: CIS]
1.3.2.3.3.4 Share of Innovative companies collaborating
with research institutions on innovation
1.3.2.3.3.5 Number of joint projects conducted by the local comapnies
@ -123,6 +141,7 @@ with research institutions on innovation
1.3.2.3.3.6 science and industry links
1.3.2.3.4 Technology absorption
1.3.2.3.4.1 Local supplier quality
[Note: GCR]
1.3.2.3.4.2 Share of expenditures on technological innovations
in the amount of sales
1.3.2.3.4.3 Number of purchased new technologies
@ -137,6 +156,7 @@ in the amount of sales
1.3.2.3.5.1 Share of innovative companies
1.3.2.3.5.2 Business R&D expenditures per GRP
1.3.2.3.5.3 Factors hampering innovation
[Note: CIS, BEEPS]
1.3.2.3.5.4 Expenditure on innovation by firm size
1.3.2.3.5.5 R&D and other intellectl property products
1.3.2.3.5.6 Growth of the number of innovative companies
@ -147,7 +167,9 @@ in the amount of sales
1.3.2.3.5.7.4 Volume of innovation production per capita
1.3.2.3.6 Entrepreneurial activities
1.3.2.3.6.1 New business density
[Note: Number of new organizations per thousand working age population (WBI)]
1.3.2.3.6.2 Volume of newly registered corporations
[Note: (as a percentage of all registered corporations)]
1.3.2.3.6.3 Share of gazelle companies in the total number of businesses
1.3.2.3.7 R&D production
1.3.2.3.7.1 Outputs
@ -185,6 +207,7 @@ and large companies by university size
1.3.2.4.1.3.2 Number of foreign patents granted per staff
1.3.2.4.1.4 Supportive measures
1.3.2.4.1.4.1 Diversity index of university entrepreneurship support measures
[Note: Number of measures offered by the unversity within a preset range (NCET2 survey)]
1.3.2.4.1.5 Commercialization
1.3.2.4.1.5.1 Licensing
1.3.2.4.1.5.1.1 Academic licenses: Number of licenses
@ -203,7 +226,9 @@ of total institutional budget (up to a cap)
1.3.2.4.1.5.3.5 Difficulties faced by research organization in collaborating with SMEs
1.3.2.4.2 Private market
1.3.2.4.2.1 Number of innovation & IP services organizations
[Note: (design firms, IP consultants, etc.)]
1.3.2.4.2.2 Number of private innovation infrastructure organizations
[Note: (e.g. accelerators, incubators)]
1.3.2.4.2.3 Access to certification and licensing for specific activities
1.3.2.4.2.4 Access to suppliers of equipment, production and engineering services
1.3.2.4.3 Innovation infrastructure
@ -215,11 +240,14 @@ of total institutional budget (up to a cap)
1.3.2.4.3.1.5 Volume of venture financing from the regional budget
1.3.2.4.3.2 Volume of state support per one company
1.4 What to do about existing measures
[Note: Understanding which measures should be strengthened, dropped or improved, and how.]
1.4.1 Demand for measure
1.4.1.1 Quality of beneficiaries
1.4.1.1.1 Growth rates of employment in supported innovative firms
1.4.1.1.2 Growth rates of employment in supported innovative firms
1.4.1.1.3 Role of IP for tenants/clients
[Note: WIPO SURVEY OF INTELLECTUAL PROPERTY SERVICES OF
EUROPEAN TECHNOLOGY INCUBATORS]
1.4.1.1.4 Share of tenants with innovation activities
1.4.1.1.5 Gazelle tenant: Share of tenants with
annual revenue growth of more than 20%
@ -247,6 +275,7 @@ select and apply for regional and federal support schemes
1.4.1.4.4 Increase in the number of start-ups applying for a place in the incubators
1.4.2 Inputs of measures
1.4.2.1 Qualified staff
[Note: JL: not sure how this would be measured]
1.4.2.2 Budget per beneficiary
1.4.3 Performance of measure
1.4.3.1 Implementation of measure
@ -275,6 +304,7 @@ several programs with different leverage)
1.4.4.5 Volume of attracted money per one ruble
of regional budget expenditures on innovation projects
1.5 What investments in innovative projects
[Note: Understanding what investments should be made in innovative projects.]
1.5.1 Competitive niches
1.5.1.1 Clusters behavior
1.5.1.1.1 Cluster EU star rating

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-277.95 -272.425 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>
<path stroke-width="2px" stroke="#9b74e6" stroke-opacity="1" visibility="visible" d="M-277.25396825396825,-31 L-271.352776723551,-32.084406990632225 M-277.25396825396825,-31 L-276.16956126333605,-25.098808469582778"/>
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,11 +1,22 @@
1 Observation
[Note: Always ask ]
2 Data Analysis
[Note: You always check your data to see if it is correct and then you check it and organize the data that you have to make sure that it is right ]
3 Organizing Data
[Note: Organize your data when you are doing an experiment ]
4 Questions
[Note: Always ask your self a question when analysis the data it is a good idea to do.]
5 Hypothesis
[Note: You make your hypothesis when you are making your observation.]
6 Experiment
[Note: Always analysis your data and keep it in order when you are doing an experiment.]
7 Variable
[Note: A major factor that can change the outcome in an experiment.]
8 Independent Variable
[Note: When you change it you the see affect or the aftermath of what happened ]
9 Control Group
[Note: A test That can be compared ]
10 Dependent Variable
[Note: Changes the outcome of the other variables]
11 Constant
[Note: Doesnt Change at all maybe once and a while but never that often]

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-204.6358490802649 -386.67631966381197 1583.04984 989.40615" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>
<path stroke-width="2px" stroke="#9b74e6" stroke-opacity="1" visibility="visible" d="M581.7268292682927,-45 L582.3818502546526,-50.96413845475004 M581.7268292682927,-45 L587.6909677230427,-44.34497901364017"/>
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 328 KiB

View File

@ -1,55 +1,112 @@
1 PPM Plan
1.1 Business Development
1.2 Backlog Management [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
1.2 Backlog Management
[Link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
1.3 Freeform IT
1.4 Client Project Management
1.5 Governance & Executive
1.6 Finance
1.7 Administration
1.8 Human Resources
[Note: HR Vision: Freeform Solutions is successful at its mission, sustainable as an organization AND is a great place to work.
HR Mission: To provide a positive HR service experience for applicants and employees, and collaborate with departments to recruit, develop, support, and retain diverse and talented employees who are the key to Freeforms reputation and success.]
1.9 Freeform Hosting
1.10 Community Outreach
1.11 R&D
1.11.1 Goals
1.11.2 Formulize
1.12 Probono
1.12.1
2 Strategy 2: Talent Development
[Note: Strategy #2: Support the talent development of our employees through professional development and learning and through improved performance management.]
2.1 Strategic Priority 2a: Personal Plans
[Note: Each employee will have a personal Professional Development Plan. ]
2.2 Strategic Priority 2b: External learning matches organ. goals
[Note: Each department of Freeform will identify areas that need development to meet overall FS goals. Eg. Project Manager may identify needed improvement in a development tool. Or... Bus. Dev. may identify a new need in NFP that FS could fill within mandate, if training were provided. Professional Dev. priority will be given to proposals for development with clear ROIs.]
2.3 Strategic Priority 2c: Learning Environment
[Note: Learning and innovation are an essential part of providing the best solutions to NFPs. Cost effective internal learning and time to explore innovation will be encouraged, provided they conform with organization goal and clear ROI is demonstrated.]
2.4 So That...
[Note: (So that... our employees have improved skills and knowledge, So that... they are highly competent and can work well in agile teams and feel fulfilled and self actualized... So that we can so the best work possible, for the least cost, in the shortest time for other NFPs, So that... NFPs can help those who need it.)]
3 Strategy 4: Inclusive, Positive Environment
[Note: Strategy #4: Foster a diverse, inclusive community with a positive work environment.]
3.1 Strategic Priority 4a:Feedback
[Note: Conduct regular organizational feedback assessments and collaborate to improve the work climate]
3.2 Strategic Priority 4b: Anti Harassment
[Note: Educate employees on the prevention of harassment and discrimination and productive ways to resolve conflict]
3.3 Strategic Priority 4c: Diversity
3.4 So That...
[Note: Insure we promote our commitment to diversity and non-discrimination through our actions and in our outreach and employee recruitment efforts]
3.4
3.5 So That...
[Note: (So that... we can reflect the diverse populations we serve AND ensure everyone feels safe, respected and included, So that... we better serve our diverse client organizations AND we are a great place to work )]
4 Strategy 1: Recruit & Retain
[Note: Recruit and retain top talent commensurate with identified organizational capacity requirements ]
4.1 So that...
[Note: (So that... we find and keep good people, So that... they are highly competent and can work well in agile teams... So that we can so the best work possible, for the least cost, in the shortest time for other NFPs, So that... NFPs can help those who need it.)]
4.2 Strategic Priority 1a: Recruitment
[Note: 1. Identify and use proactive and effective recruitment strategies, ]
4.2.1 Modify App Form
[Note: Recently, I saw a few job posts sent through different community
groups and they seem to be taking our idea of screening candidates
to a next level. Not only they ask candidates to provide resume and
cover letter + some project related information but also request
written answers to questions like "Why are you interested in this
position" etc. That allows to screen out people who just submit
multiple resumes to multiple organizations without really applying
for that particular job and it would make our interview process more
straightforward, knowing answers to some questions.
For example, we may want to always include in the screening
questions:
- Why do you want to work for Freeform Solutions?
- Why are you interested in this position?
- What's your experience working with NFP?
- What's your experience working with Open Source software?
etc.
I also saw that people ask for references up front, in that
submissions form. We could include the HR requirement that Heather
brought recently for "permissions to ask your references about you"
in the online form so that we don't have to follow up with this
later.
Attached below a sample of such screening questions]
4.2.2 Strategy integrated with hiring plan
[Note: Hiring plan should be comprehensive... not Agile or Iterative, in the sense that staff capacity and skill needs should be met within at least a six month plan. If three Drupal developers are needed, the hiring should be done concurrently to minimize HR costs and time.]
4.3 Strategic Priority 1b: Hiring
[Note: 2. Continue to practice our unique Freeform hiring process that balances fit with the Freeform culture and best talent ]
4.4 Strategic Priority 1c: Onboarding
[Note: ]
4.4.1 3 Month Onboarding Process
4.4.2 Tools & Guidelines
4.4.3 Mentoring
4.5 Strategic Priority 1d: Incentives
[Note: 5. Explore incentives - monetary, benefits, fulfilment - needed to encourage top talent to work for and remain working for an NFP]
4.5.1 Raises
4.5.2 Benefits
4.5.3 Rewards Message
[Note: Create a total rewards message to encourage prospective and current employees to understand the full value of working for Freeform]
4.6 Strategic Priority 1e: Offboarding
[Note: Assess and address reasons why talented people leave Freeform]
5 Business Development Plan
5.1 Goals
5.1.1 Increase new clients
5.1.1.1 Academic Research
5.1.1.2
5.1.2 Support New Products
5.1.2.1 Formulize
5.1.2.2
5.1.2.3
5.1.3 Support CiviCRM
5.1.4 Identify Opportunites
5.1.4.1
5.1.4.2
5.1.4.3
5.1.4.4
6 Hosting NG Plan
7 Freeform IT Plan
7.1 Fragile
7.2 Tools
7.3
8 Project Teams
8.1 Projects 1-3
8.2 Projects 4-6
@ -62,17 +119,45 @@
9.3 Supportive Systems Plan
10 Board and C Planning
10.1 Mission Statements
[Note: In the absence of one clearly defined mission statement, we have reviewed various expressed mission statement as following
Objects of Incorporation in Letters Patent
The objects of the Corporation are:To provide solutions that facilitate the effective use of information technology in not-for-profit, non-governmental, and charitable organizations throughout Canada, to support and improve their mission delivery.
2006 Strategic Business Plan - Brand Positioning
We are a nonprofit dedicated to helping other nonprofits understand and employ technology appropriately and effectively to support their mission. Our mission is to provide flexible consulting, website development, and Internet hosting solutions that give you peace of mind and help you stay focused on your mission.
Freeform One Page (Freeform Wiki)
About Freeform Solutions: Freeform Solutions is a not-for-profit organization. Our mission is help (sic) other not-for-profits organizations to build their capacity and increase their effectiveness.
Result of Google search for “Freeform Solutions mission”
Freeform Solutions is a not-for-profit organization. Our mission is to help other not-for-profits use technology to build their capacity and increase their effectiveness. (www.freeformsolutions.ca/en/files/AboutFreeformSolutions.pdf
Freeform Solutions (www.freeformsolutions.ca) is a non profit organization. Our mission is to help other non-profit organizations to realize their missions through the appropriate deployment of information and knowledge management systems.www.freeformsolutions.ca/en/sites/default/.../virtual.volunteering.pdf
We are a not-for-profit organization (NFP) that helps other NFPs use IT to achieve their organizational goals and better serve their communities. Freeform Solutions is a nonprofit organization dedicated to helping other nonprofit organizations understand and employ technology appropriately and effectively to support their missions. Our mission is to provide flexible consulting, website development, and hosting solutions http://socialinnovation.ca/community/organizations/freeform-solutions
Freeform Solutions is a not-for-profit organization, with a mission to help other not-for-profits use technology more effectively to meet their own missions. http://timreview.ca/article/387
Freeform Solutions their mission: “we help not-for-profit organisations use technology to build their capacity and increase their effectiveness. http://www.warnerlaw.ca/links/community-organisations-and-local-businesses/
Freeform Solutions is a not-for-profit organization with a mission to help other not-for-profits use technology to meet their goals. http://xoops.org/modules/news/article.php?storyid=3860
At Freeform Solutions, we have a mission to help not-for-profit and public sector organizations use technology more effectively. http://osbrca.blogspot.ca/2010/07/development-commons-approach.html
Freeform Solutions Facebook Page
Mission: Freeform Solutions is a not-for-profit organization (NFP) that helps other NFPs use IT to achieve their organizational goals and better serve their communities.
The current Freeform Solutions website
We started Freeform to help NFPs use IT to achieve their organizational goals and better serve their communities - to support and improve their mission delivery. Our mission is to strengthen the capacity of NFPs and the voluntary sector, and to help build a civil society.]
10.2 Values
10.3 Bylaw Review
10.4 Policies
10.5 Business Plan
11 Strategy 3: Safety and Wellness
[Note: Strategy # 3: Promote the achievement of safety and wellness in our virtual employee community.]
11.1 Strategic Priority 3a: H&S Policies & Practices
[Note: Continuing improvement in Health and Safety policies and practices & compliance with OHSC legislation]
11.1.1
11.2 Strategic Priority 3b: Health Promotion
[Note: Promoting safety, work-life balance, self-care, ergonomics and other factors for wellness and productivity in a virtual workplace environment]
11.2.1 Health and Wellness Committee
11.2.2 Work-life Balance Initiative [link: http://hrcouncil.ca/hr-toolkit/workplaces-health-safety.cfm]
[Note: The Freeform H&S rep will lead a Health and Wellness Committee to responsible for recognizing health and safety concerns and identifying solutions.]
11.2.2 Work-life Balance Initiative
[Link: http://hrcouncil.ca/hr-toolkit/workplaces-health-safety.cfm]
11.3 So that...
[Note: (So that... our employees remain well and safe, So that... they are highly competent and can work well in agile teams and feel fulfilled and self actualized... So that we can so the best work possible, for the least cost, in the shortest time for other NFPs, So that... NFPs can help those who need it.)]
12 Benefits
[Note: As Freeform Employees we will have benefits reviewed in light of our priorities and cost to Freeform]
12.1 As Freeform Staff
12.2 Responsibility: HZ, JC
12.3 Release 3
@ -81,54 +166,90 @@
12.6 Have JC & HZ consult with staff
12.7 Have best benefits we can afford
12.8 So that...
[Note: so that our efforts to excel are rewarded.]
13 Community Outreach Plan
13.1 Goals
13.2 CSI
13.3 Drupal Community
13.4 CiviCRM
13.5 Other
14 Backlog Plan [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
14.1 Go To Backlog Plan [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
14 Backlog Plan
[Link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
14.1 Go To Backlog Plan
[Link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
15 Strategy Prospecting
15.1
15.2
15.3
16 Stategies: Forecasting
16.1
16.2
16.3
17 Strategies Marketing
18 Exit Interviews
18.1 As Freeform
18.2 Responsiblity: HZ, KS
18.3 Release
18.4 Have Heather write procedures for exit interview process
18.5 So that
19 3 Month Onboarding Process
20 Human Resources Plan
20.1 Related Org Objectives
20.1.1 1
20.1.2 2
20.1.3 3
20.1.4 4
20.2 Related Documents
20.3 Goals
20.3.1 Goal:Staff=Optimal Bus. Growth
20.3.1.1 So that...
20.3.1.2 Related Strategic Priorities:
20.3.1.3 KPI: HR Level equals Planned Growth
20.3.1.4 Methodology
20.3.1.4.1 Target
20.3.2 Goal: Increase Job Satisfaction
20.3.2.1 So That
20.3.2.2 Related Strategic Priorities
20.3.2.3 KPI: Employee Satisfaction
20.3.2.4 Methodology
20.3.2.4.1 Target
20.3.3 Goal: Improve Performance
20.3.3.1 So That
20.3.3.2 Related Strategic Priorities
20.3.3.3 KPI: Employee Performance
20.3.3.4 Methodology
20.3.3.4.1 Target
20.3.4 Goal: Reduce Turnover
20.3.4.1 So That
20.3.4.2 Related Strategic Priorities
20.3.4.3 KPI: Retention Rate
20.3.4.4 Methodology
20.3.4.4.1 Target
20.3.5 Risk & Compliance
18
19 Exit Interviews
19.1 As Freeform
19.2 Responsiblity: HZ, KS
19.3 Release
19.4 Have Heather write procedures for exit interview process
19.5 So that
[Note: We learn from our mistakes and missed opportunities in future with the goal of keeping the best talent.]
20 3 Month Onboarding Process
21 Human Resources Plan
21.1 Related Org Objectives
21.1.1 1
[Note: Attract, build and retain a motivated, agile, knowledgeable team of Top Talent that loves to “come” to work]
21.1.2 2
[Note: Maintain level of human resource capacity and skill to meet planned growth and client contractual commitments]
21.1.3 3
[Note: Conform to all legislated requirements]
21.1.4 4
[Note: Minimize and mitigate risk to the organization]
21.2 Related Documents
[Note: MIssion, Values, Principles, Org Business Plan, Human Resources Policy Manual]
21.3 Goals
21.3.1 Goal:Staff=Optimal Bus. Growth
[Note: Human resource capacity will remain at a level to meet planned growth growth objectives and client contractual commitments]
21.3.1.1 So that...
21.3.1.2 Related Strategic Priorities:
21.3.1.3 KPI: HR Level equals Planned Growth
21.3.1.4 Methodology
[Note: Schedule of required HR capacity vs. actual HR capacity. Variance + or - 1]
21.3.1.4.1 Target
[Note: = + or - 1]
21.3.2 Goal: Increase Job Satisfaction
21.3.2.1 So That
[Note: Establish better relationships.
Identify with the new employer.
Build a great attitude with the company.]
21.3.2.2 Related Strategic Priorities
21.3.2.2.1
21.3.2.3 KPI: Employee Satisfaction
21.3.2.3.1
21.3.2.4 Methodology
[Note: Percentage of improvement in employee reported job satisfaction based on survey vs previous year. Base level to be established in first year. ]
21.3.2.4.1 Target
[Note: Base level 1st year]
21.3.3 Goal: Improve Performance
[Note: To increase knowledge, skills and experience of the Freeform staff relevant to organizational priorities.]
21.3.3.1 So That
[Note: Clarify expectations.
Understand values and priorities.
Decrease the learning curve.]
21.3.3.2 Related Strategic Priorities
[Note: 1]
21.3.3.3 KPI: Employee Performance
21.3.3.4 Methodology
21.3.3.4.1 Target
21.3.4 Goal: Reduce Turnover
[Note: To reduce turnover of Top Talent.]
21.3.4.1 So That
[Note: Provide support through feedback.
Help the employee feel valued.
Again, decrease the learning curve.]
21.3.4.2 Related Strategic Priorities
21.3.4.3 KPI: Retention Rate
21.3.4.4 Methodology
21.3.4.4.1 Target
21.3.5 Risk & Compliance
[Note: To eliminate or minimize risk and to comply with all legislated requirements. ]

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-277.95 -272.425 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M463.00,660.00 C472.77,660 482.53,1017.00 492.30,1017.00"/>
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M463.00,660.00 C472.93,660 482.87,1007.00 492.80,1007.00"/>
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M463.00,660.00 C472.93,660 482.87,975.00 492.80,975.00"/>

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -1,4 +1,5 @@
1
[Note: ]
1.1 objectifs journée
1.1.1 "business plan" associatif ?
1.1.2 modèle / activités responsabilités

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="294.95 -265.625 1231.34400 769.59000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M-764.00,19.00 C-773.93,19 -783.87,251.50 -793.80,251.50"/>
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M-764.00,19.00 C-773.93,19 -783.87,208.00 -793.80,208.00"/>
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M-764.00,19.00 C-773.93,19 -783.87,140.00 -793.80,140.00"/>

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -35,9 +35,11 @@ the other hand, strategies alternative to BRIDGE clearly
failed to accurately estimate the variance of trait values. This
indicates that in situations where accurate estimation of plotlevel
variance is desired, complete censuses are essential.
[Note: Isso significa que estudos de característica de história de vida compensam? Ver nos m&m.]
1.1.8 We suggest that, in these studies,
the investment in complete sampling may be worthwhile
for at least some traits.
[Note: Falar que isso corrobora nossa sugestão de utilizar poucas medidas, mas que elas sejam confiáveis.]
1.2 Chazdon 2010. Biotropica. 42(1): 3140
1.2.1 Here, we develop a new approach that links functional attributes
of tree species with studies of forest recovery and regional

View File

@ -28,18 +28,18 @@ failed to accurately estimate the variance of trait values. This
indicates that in situations where accurate estimation of plotlevel
variance is desired, complete censuses are essential.]]></text><note><![CDATA[Isso significa que estudos de característica de história de vida compensam? Ver nos m&m.]]></note></topic><topic position="-915,219" order="7" id="15"><text><![CDATA[We suggest that, in these studies,
the investment in complete sampling may be worthwhile
for at least some traits.]]></text><note><![CDATA[Falar que isso corrobora nossa sugestão de utilizar poucas medidas, mas que elas sejam confiáveis.]]></note></topic></topic><topic position="297,0" order="0" text="Chazdon 2010. Biotropica. 42(1): 3140" shape="rectagle" id="17" fontStyle=";;#000000;;;" bgColor="#cccccc" brColor="#cccccc"><topic position="586,-383" order="1" id="22"><text><![CDATA[Here, we develop a new approach that links functional attributes
for at least some traits.]]></text><note><![CDATA[Falar que isso corrobora nossa sugestão de utilizar poucas medidas, mas que elas sejam confiáveis.]]></note></topic></topic><topic position="297,0" order="0" text="Chazdon 2010. Biotropica. 42(1): 3140" shape="rectagle" id="17" fontStyle=";;#000000;;;" bgColor="#cccccc" brColor="#cccccc"><topic position="586,-383" order="0" id="22"><text><![CDATA[Here, we develop a new approach that links functional attributes
of tree species with studies of forest recovery and regional
land-use transitions (Chazdon et al. 2007). Grouping species according
to their functional attributes or demographic rates provides
insight into both applied and theoretical questions, such as selecting
species for reforestation programs, assessing ecosystem services, and
understanding community assembly processes in tropical forests
(Diaz et al. 2007, Kraft et al. 2008).]]></text></topic><topic position="583,-313" order="2" id="23"><text><![CDATA[Since we have data on leaf
(Diaz et al. 2007, Kraft et al. 2008).]]></text></topic><topic position="583,-313" order="1" id="23"><text><![CDATA[Since we have data on leaf
and wood functional traits for only a subset of the species in our
study sites, we based our functional type classification on information
for a large number of tree species obtained through vegetation
monitoring studies.]]></text></topic><topic position="2883,-437" order="0" text="Falar no artigo que esse trabalho fala que é inadequada a divisão entre pioneira e não pioneira devido a grande variação que há entre elas. Além de terem descoberto que durante a ontogenia a resposta a luminosidade muda dentro de uma mesma espécie. Porém recomendar que essa classificação continue sendo usada em curto prazo enquanto não há informações confiáveis suficiente para esta simples classificação. Outras classificações como esta do artigo são bem vinda, contanto que tenham dados confiáveis. Porém dados estáticos já são difíceis de se obter, dados temporais, como taxa de crescimento em diâmetro ou altura, são mais difíceis ainda. Falar que vários tipos de classificações podem ser utilizadas e quanto mais detalhe melhor, porém os dados é que são mais limitantes. Se focarmos em dados de germinação e crescimento limitantes, como sugerem sainete e whitmore, da uma idéia maismrápida e a curto prazo da classificação destas espécies. Depois com o tempo conseguiremos construir classificações mais detalhadas e com mais dados confiáveis. " id="24"/><topic position="720,-239" order="3" id="25"><text><![CDATA[Our approach avoided preconceived notions of successional
monitoring studies.]]></text></topic><topic position="2883,-437" order="2" text="Falar no artigo que esse trabalho fala que é inadequada a divisão entre pioneira e não pioneira devido a grande variação que há entre elas. Além de terem descoberto que durante a ontogenia a resposta a luminosidade muda dentro de uma mesma espécie. Porém recomendar que essa classificação continue sendo usada em curto prazo enquanto não há informações confiáveis suficiente para esta simples classificação. Outras classificações como esta do artigo são bem vinda, contanto que tenham dados confiáveis. Porém dados estáticos já são difíceis de se obter, dados temporais, como taxa de crescimento em diâmetro ou altura, são mais difíceis ainda. Falar que vários tipos de classificações podem ser utilizadas e quanto mais detalhe melhor, porém os dados é que são mais limitantes. Se focarmos em dados de germinação e crescimento limitantes, como sugerem sainete e whitmore, da uma idéia maismrápida e a curto prazo da classificação destas espécies. Depois com o tempo conseguiremos construir classificações mais detalhadas e com mais dados confiáveis. " id="24"/><topic position="720,-239" order="3" id="25"><text><![CDATA[Our approach avoided preconceived notions of successional
behavior or shade tolerance of tree species by developing an objective
and independent classification of functional types based on vegetation
monitoring data from permanent sample plots in mature and

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-277.95 -272.425 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:#495879 " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M0.00,0.00 C19.77,0 39.53,52.00 59.30,52.00 39.53,55.00 19.77,5.00 0.00,7.00 Z"/>
<path style="fill:#495879 " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M0.00,0.00 C19.77,0 39.53,-24.00 59.30,-24.00 39.53,-21.00 19.77,5.00 0.00,7.00 Z"/>
<path style="fill:#495879 " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M0.00,0.00 C19.60,0 39.20,14.00 58.80,14.00 39.20,17.00 19.60,5.00 0.00,7.00 Z"/>

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-277.95 -272.425 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:#495879 " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M0.00,0.00 C26.77,0 53.53,-5.00 80.30,-5.00 53.53,-2.00 26.77,5.00 0.00,7.00 Z"/>
<path style="fill:#495879 " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M0.00,0.00 C26.93,0 53.87,41.00 80.80,41.00 53.87,44.00 26.93,5.00 0.00,7.00 Z"/>
<g preserveAspectRatio="none" focusable="true" width="100" height="100" transform="translate(-50.00000,-23.00000) scale(1.00000,1.00000)" visibility="visible">

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,4 +1,5 @@
1 أَبْجَدِيَّة عَرَبِيَّة
1.1 أَبْجَدِيَّة عَرَبِ
[Note: This is a not in languange أَبْجَدِيَّة عَرَبِ]
1.2 Long text node:
أَبْجَدِيَّة عَرَب

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-277.95 -272.425 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>
<path stroke-width="2px" stroke="#9b74e6" stroke-opacity="1" visibility="visible" d="M83.54929577464789,-187 L88.28751685656984,-183.31906791982922 M83.54929577464789,-187 L79.86836369447711,-182.26177891807805"/>
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-264.99255421004466 -310.83776307154034 1554.89349 971.80843" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>
<path stroke-width="2px" stroke="#9b74e6" stroke-opacity="1" visibility="visible" d="M326,104 L320.00018895554825,103.95238245202816 M326,104 L326.0476175479718,98.00018895554825"/>
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -1,13 +1,19 @@
1 NIF (NORMAS DE INFORMACIÓN FINANCIERA)
2 NIF D
[Note: Beneficios a los empleados,impuestos a la utilidad, arrendamientos y capitalización de resultado integral .]
2.1 Normas aplicables a problemas de determinación de resultados
3 CIRCULANTES
[Note: Tratamiento contable de los gastos de registro, colocación, unidades de inversión, aplicación supletoria etc.]
3.1 Adquisición temporal de acciones propias
4 NIF A [link: http://www.youtube.com/watch?v=7YN-sOlkQp0]
4 NIF A
[Link: http://www.youtube.com/watch?v=7YN-sOlkQp0]
4.1 Marco conceptual
5 NIF C [link: https://sites.google.com/site/contabilidadimcpnif/estructura-de-las-nif]
5 NIF C
[Link: https://sites.google.com/site/contabilidadimcpnif/estructura-de-las-nif]
5.1 Normas aplicables a conceptos específicos de los estados financieros
6 NIF E
[Note: Agricultura y donativos recibidos u otorgados con propósitos no lucrativos.]
6.1 Normas aplicables alas actividades especializadas de distintos sectores
7 NIF B [link: http://www.contaduria.uady.mx/files/cuerpo-acad/caef/aief/resumen_NIF_marco_conceptual.pdf]
7 NIF B
[Link: http://www.contaduria.uady.mx/files/cuerpo-acad/caef/aief/resumen_NIF_marco_conceptual.pdf]
7.1 Normas aplicables a los estados financieros en su conjunto

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-100.29999999999998 -120.275 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>
<path stroke-width="2px" stroke="#9b74e6" stroke-opacity="1" visibility="visible" d="M-296,-173 L-292.3650603359595,-168.2264045375854 M-296,-173 L-300.7735954624146,-169.36506033595953"/>
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -29,7 +29,8 @@
1.5.1.1 Orange County Eye and Transplant Bank
1.5.1.2 Northern California Transplant Bank
1.5.1.2.1 In 2010, 2,500 referrals forwarded to OneLegacy
1.5.1.3 Doheny Eye and Tissue Transplant Bank [link: http://www.dohenyeyebank.org/]
1.5.1.3 Doheny Eye and Tissue Transplant Bank
[Link: http://www.dohenyeyebank.org/]
1.5.2 OneLegacy
1.5.2.1 In 2010, 11,828 referrals
1.5.3 San Diego Eye Bank

View File

@ -1,4 +1,4 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="true" preserveAspectRatio="none" width="1440" height="900" viewBox="-277.95 -272.425 1224.00000 765.00000" style="background-color:white">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" focusable="false" preserveAspectRatio="xMinYMin" width="NaN" height="NaN" viewBox="NaN NaN NaN NaN" style="background-color:white">
<path style="fill:none " stroke-width="2px" stroke="#3f96ff" stroke-opacity="1" visibility="hidden"/>
<path stroke-width="2px" stroke="#9b74e6" stroke-opacity="1" visibility="visible" d="M210.85714285714286,-166.5 L204.94844150423523,-165.45728799654572 M210.85714285714286,-166.5 L209.81443085368858,-172.40870135290763"/>
<path style="fill:none " stroke-width="1px" stroke="#495879" stroke-opacity="1" fill="#495879" fill-opacity="1" visibility="visible" d="M-254.00,161.00 C-263.77,161 -273.53,190.00 -283.30,190.00"/>

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

@ -1,13 +1,15 @@
1 Welcome To WiseMapping
1.1 5 min tutorial video ?
Follow the link ! [link: https://www.youtube.com/tv?vq=medium#/watch?v=rKxZwNKs9cE]
Follow the link !
[Link: https://www.youtube.com/tv?vq=medium#/watch?v=rKxZwNKs9cE]
1.2 Try it Now!
1.2.1 Double Click
1.2.2 Press "enter" to add a
Sibling
1.2.3 Drag map to move
1.3 Features
1.3.1 Links to Sites [link: http://www.digg.com]
1.3.1 Links to Sites
[Link: http://www.digg.com]
1.3.2 Styles
1.3.2.1 Fonts
1.3.2.2 Topic Shapes
@ -24,8 +26,10 @@ Sibling
1.5.2 Brainstorming
1.5.3 Visual
1.6 Install In Your Server
1.6.1 Open Source [link: http://www.wisemapping.org/]
1.6.2 Download [link: http://www.wisemapping.com/inyourserver.html]
1.6.1 Open Source
[Link: http://www.wisemapping.org/]
1.6.2 Download
[Link: http://www.wisemapping.com/inyourserver.html]
1.7 Collaborate
1.7.1 Embed
1.7.2 Publish

View File

@ -5,7 +5,7 @@
"noImplicitAny": false,
"module": "amd",
"moduleResolution": "node",
"target": "es6",
"target": "ES2020",
"allowJs": true,
"esModuleInterop": true,
"declaration": true,

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