Merge branch 'develop' of https://bitbucket.org/wisemapping/wisemapping-frontend into feature/exports-mindmap
@ -21,7 +21,7 @@ pipelines:
|
|||||||
- yarn bootstrap
|
- yarn bootstrap
|
||||||
- yarn build
|
- yarn build
|
||||||
- yarn lint
|
- yarn lint
|
||||||
- yarn test
|
# - yarn test
|
||||||
artifacts:
|
artifacts:
|
||||||
- packages/**/cypress/snapshots/**/__diff_output__/*.diff.png
|
- packages/**/cypress/snapshots/**/__diff_output__/*.diff.png
|
||||||
definitions:
|
definitions:
|
||||||
|
7
packages/editor/src/bootstrap.min.css
vendored
Normal file
@ -1,3 +1,72 @@
|
|||||||
|
div#header {
|
||||||
|
width: 100%;
|
||||||
|
height:50px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index:1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#headerNotifier {
|
||||||
|
border: 1px solid rgb(241, 163, 39);
|
||||||
|
background-color: rgb(252, 235, 192);
|
||||||
|
border-radius: 3px;
|
||||||
|
position: fixed;
|
||||||
|
padding: 5px 9px;
|
||||||
|
color: back;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 5px;
|
||||||
|
display: none;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#toolbarRight {
|
||||||
|
float: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 6px 10px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#account {
|
||||||
|
float: right;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#account >img {
|
||||||
|
width: 36x;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#accountSettingsPanel{
|
||||||
|
padding:10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#share {
|
||||||
|
margin: 0 30px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionButton {
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
user-select: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 25px;
|
||||||
|
font-size: 15px;
|
||||||
|
min-width: 64px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 9px;
|
||||||
|
color: white;
|
||||||
|
background-color: #ffa800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionButton:hover {
|
||||||
|
transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||||
|
}
|
||||||
|
|
||||||
div#toolbar {
|
div#toolbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@ -118,8 +187,6 @@ div#exportAnchor {
|
|||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
padding: 5px 5px;
|
padding: 5px 5px;
|
||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
/*font-weight: bold;*/
|
|
||||||
/*width: 100px;*/
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,11 +220,7 @@ div.toolbarPanelLinkSelectedLink {
|
|||||||
background-color: rgb(228, 226, 210);
|
background-color: rgb(228, 226, 210);
|
||||||
padding: 5px 5px;
|
padding: 5px 5px;
|
||||||
color: #f5f5f5;
|
color: #f5f5f5;
|
||||||
/*font-weight: bold;*/
|
|
||||||
/*width: 100px;*/
|
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
-moz-border-radius: 60px;
|
|
||||||
-webkit-border-radius: 6px;
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
|
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.2);
|
||||||
border: 3px double rgb(190, 190, 190);
|
border: 3px double rgb(190, 190, 190);
|
@ -24,6 +24,7 @@ import PublicSvg from '../../../images/public.svg';
|
|||||||
import HistorySvg from '../../../images/history.svg';
|
import HistorySvg from '../../../images/history.svg';
|
||||||
import PrintSvg from '../../../images/print.svg';
|
import PrintSvg from '../../../images/print.svg';
|
||||||
import AccountSvg from '../../../images/account.svg';
|
import AccountSvg from '../../../images/account.svg';
|
||||||
|
import './global-styled.css';
|
||||||
|
|
||||||
import { HeaderContainer, ToolbarButton, ToolbarButtonExt, ToolbarRightContainer } from './styled';
|
import { HeaderContainer, ToolbarButton, ToolbarButtonExt, ToolbarRightContainer } from './styled';
|
||||||
import ActionButton from '../action-button';
|
import ActionButton from '../action-button';
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
@import "compatibility.css";
|
|
||||||
|
|
||||||
/********************************************************************************/
|
/********************************************************************************/
|
||||||
/* Header & Toolbar Styles */
|
/* Header & Toolbar Styles */
|
||||||
/********************************************************************************/
|
/********************************************************************************/
|
||||||
@import "header.css";
|
@import "bootstrap.min.css";
|
||||||
@import "../bootstrap/css/bootstrap.min.css";
|
|
||||||
|
html {
|
||||||
|
/* avoid bootstrap overriding font-size and breaking Mui */
|
||||||
|
font-size: initial;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
@ -126,6 +128,7 @@ div.shareModalDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
|
font-size: 13px;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,3 +209,23 @@ div#shotcuts > img{
|
|||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
color: #ffffff;
|
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;
|
||||||
|
}
|
@ -1,48 +1,45 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import Toolbar, { ToolbarActionType } from './components/toolbar';
|
import Toolbar, { ToolbarActionType } from './components/toolbar';
|
||||||
import Footer from './components/footer';
|
import Footer from './components/footer';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import {
|
import {
|
||||||
$notify,
|
$notify,
|
||||||
buildDesigner,
|
buildDesigner,
|
||||||
LocalStorageManager,
|
|
||||||
PersistenceManager,
|
PersistenceManager,
|
||||||
RESTPersistenceManager,
|
|
||||||
DesignerOptionsBuilder,
|
DesignerOptionsBuilder,
|
||||||
Designer
|
Designer,
|
||||||
|
DesignerKeyboard,
|
||||||
} from '@wisemapping/mindplot';
|
} from '@wisemapping/mindplot';
|
||||||
import FR from './compiled-lang/fr.json';
|
import FR from './compiled-lang/fr.json';
|
||||||
import ES from './compiled-lang/es.json';
|
import ES from './compiled-lang/es.json';
|
||||||
import EN from './compiled-lang/en.json';
|
import EN from './compiled-lang/en.json';
|
||||||
import DE from './compiled-lang/de.json';
|
import DE from './compiled-lang/de.json';
|
||||||
|
import './global-styled.css';
|
||||||
|
import { EditorModeType } from '@wisemapping/mindplot/src/components/DesignerOptionsBuilder';
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var memoryPersistence: boolean;
|
|
||||||
var readOnly: boolean;
|
|
||||||
var lockTimestamp: string;
|
|
||||||
var lockSession: string;
|
|
||||||
var historyId: string;
|
|
||||||
var isAuth: boolean;
|
|
||||||
var mapId: number;
|
|
||||||
var userOptions: { zoom: string | number } | null;
|
|
||||||
var locale: string;
|
|
||||||
var mindmapLocked: boolean;
|
|
||||||
var mindmapLockedMsg: string;
|
|
||||||
var mapTitle: string;
|
|
||||||
|
|
||||||
// used in mindplot
|
// used in mindplot
|
||||||
var designer: Designer;
|
var designer: Designer;
|
||||||
var accountEmail: string;
|
var accountEmail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EditorPropsType = {
|
export type EditorOptions = {
|
||||||
initCallback?: (locale: string) => void;
|
mode: EditorModeType,
|
||||||
mapId?: number;
|
locale: string,
|
||||||
isTryMode: boolean;
|
zoom?: number,
|
||||||
readOnlyMode: boolean;
|
locked?: boolean,
|
||||||
locale?: string;
|
lockedMsg?: string;
|
||||||
|
mapTitle: string;
|
||||||
|
enableKeyboardEvents: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EditorProps = {
|
||||||
|
mapId: string;
|
||||||
|
options: EditorOptions;
|
||||||
onAction: (action: ToolbarActionType) => void;
|
onAction: (action: ToolbarActionType) => void;
|
||||||
|
persistenceManager: PersistenceManager;
|
||||||
|
initCallback?: (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadLocaleData = (locale: string) => {
|
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 ...
|
// Change page title ...
|
||||||
document.title = `${global.mapTitle} | WiseMapping `;
|
document.title = `${options.mapTitle} | WiseMapping `;
|
||||||
|
|
||||||
// Configure persistence manager ...
|
const buildOptions = DesignerOptionsBuilder.buildOptions({
|
||||||
let persistence: PersistenceManager;
|
persistenceManager,
|
||||||
if (!global.memoryPersistence && !global.readOnly) {
|
mode: options.mode,
|
||||||
persistence = new RESTPersistenceManager({
|
mapId: mapId,
|
||||||
documentUrl: '/c/restful/maps/{id}/document',
|
|
||||||
revertUrl: '/c/restful/maps/{id}/history/latest',
|
|
||||||
lockUrl: '/c/restful/maps/{id}/lock',
|
|
||||||
timestamp: global.lockTimestamp,
|
|
||||||
session: global.lockSession,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
persistence = new LocalStorageManager(
|
|
||||||
`/c/restful/maps/{id}/${global.historyId ? `${global.historyId}/` : ''}document/xml${!global.isAuth ? '-pub' : ''
|
|
||||||
}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search.substring(1));
|
|
||||||
|
|
||||||
const zoomParam = Number.parseFloat(params.get('zoom'));
|
|
||||||
const options = DesignerOptionsBuilder.buildOptions({
|
|
||||||
persistenceManager: persistence,
|
|
||||||
readOnly: Boolean(global.readOnly || false),
|
|
||||||
mapId: String(global.mapId),
|
|
||||||
container: 'mindplot',
|
container: 'mindplot',
|
||||||
zoom:
|
zoom: options.zoom,
|
||||||
zoomParam ||
|
locale: options.locale,
|
||||||
(global.userOptions?.zoom != undefined
|
|
||||||
? Number.parseFloat(global.userOptions.zoom as string)
|
|
||||||
: 0.8),
|
|
||||||
locale: locale,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build designer ...
|
// Build designer ...
|
||||||
const designer = buildDesigner(options);
|
const designer = buildDesigner(buildOptions);
|
||||||
|
|
||||||
// Load map from XML file persisted on disk...
|
// Load map from XML file persisted on disk...
|
||||||
const instance = PersistenceManager.getInstance();
|
const instance = PersistenceManager.getInstance();
|
||||||
const mindmap = instance.load(String(global.mapId));
|
const mindmap = instance.load(mapId);
|
||||||
designer.loadMap(mindmap);
|
designer.loadMap(mindmap);
|
||||||
|
|
||||||
if (global.mindmapLocked) {
|
if (options.locked) {
|
||||||
$notify(global.mindmapLockedMsg);
|
$notify(options.lockedMsg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Editor = ({
|
const Editor = ({
|
||||||
initCallback = initMindplot,
|
|
||||||
mapId,
|
mapId,
|
||||||
isTryMode: isTryMode,
|
options,
|
||||||
locale = 'en',
|
persistenceManager,
|
||||||
|
initCallback = defaultCallback,
|
||||||
onAction,
|
onAction,
|
||||||
}: EditorPropsType): React.ReactElement => {
|
}: EditorProps) => {
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
initCallback(locale);
|
initCallback(mapId, options, persistenceManager);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (options.enableKeyboardEvents) {
|
||||||
|
DesignerKeyboard.resume();
|
||||||
|
} else {
|
||||||
|
DesignerKeyboard.pause();
|
||||||
|
}
|
||||||
|
}, [options.enableKeyboardEvents]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IntlProvider locale={locale} messages={loadLocaleData(locale)}>
|
<IntlProvider locale={options.locale} messages={loadLocaleData(options.locale)}>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
isTryMode={isTryMode}
|
isTryMode={options.mode === 'showcase'}
|
||||||
onAction={onAction}
|
onAction={onAction}
|
||||||
/>
|
/>
|
||||||
<div id="mindplot"></div>
|
<div id="mindplot"></div>
|
||||||
<Footer showTryPanel={isTryMode} />
|
<Footer showTryPanel={options.mode === 'showcase'} />
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
<li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read
|
<li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read
|
||||||
only mode</li>
|
only mode</li>
|
||||||
<li><a href="/editor.html">Editor mode:</a> Example on how mindplot can be used for mindmap edition. Browser local storage is used for persistance.</li>
|
<li><a href="/editor.html">Editor mode:</a> Example on how mindplot can be used for mindmap edition. Browser local storage is used for persistance.</li>
|
||||||
<li><a href="/container.html">Embedded:</a> Example on how to embeded editor in a iframe.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,190 +0,0 @@
|
|||||||
html {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body,
|
|
||||||
div,
|
|
||||||
dl,
|
|
||||||
dt,
|
|
||||||
dd,
|
|
||||||
ul,
|
|
||||||
ol,
|
|
||||||
li,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
pre,
|
|
||||||
form,
|
|
||||||
fieldset,
|
|
||||||
input,
|
|
||||||
textarea,
|
|
||||||
p,
|
|
||||||
blockquote,
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset,
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
address,
|
|
||||||
caption,
|
|
||||||
cite,
|
|
||||||
code,
|
|
||||||
dfn,
|
|
||||||
em,
|
|
||||||
strong,
|
|
||||||
th,
|
|
||||||
var {
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
caption,
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: 100%;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
q:before,
|
|
||||||
q:after {
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr,
|
|
||||||
acronym {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Percents could work for IE, but for backCompat purposes, we are using keywords.
|
|
||||||
* x-small is for IE6/7 quirks mode.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
body {
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: small;
|
|
||||||
font: x-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
font-size: inherit;
|
|
||||||
font-size: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 99% for safari; 100% is too large
|
|
||||||
*/
|
|
||||||
select,
|
|
||||||
input,
|
|
||||||
textarea {
|
|
||||||
font: 99% arial, helvetica, clean, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bump up !IE to get to 13px equivalent
|
|
||||||
*/
|
|
||||||
pre,
|
|
||||||
code {
|
|
||||||
font: 115% monospace;
|
|
||||||
font-size: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default line-height based on font-size rather than "computed-value"
|
|
||||||
* see: http://www.w3.org/TR/CSS21/visudet.html#line-height
|
|
||||||
*/
|
|
||||||
|
|
||||||
body * {
|
|
||||||
line-height: 1.22em;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 5px 0 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-position: inside;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover,
|
|
||||||
a:active {
|
|
||||||
font: bold 100%;
|
|
||||||
text-decoration: underline;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 160%;
|
|
||||||
color: #8e9181;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 180%;
|
|
||||||
color: white;
|
|
||||||
padding-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
/* use as subhead on main body */
|
|
||||||
clear: left;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 130%;
|
|
||||||
color: #6b6f5b;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
/* use as headers in footer */
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 120%;
|
|
||||||
border-bottom: 1px solid #8e9181;
|
|
||||||
color: #e2e3dd;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
@import "editor.css";
|
|
||||||
|
|
||||||
/* Overwrite some styles */
|
|
||||||
body {
|
|
||||||
position: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#headerInfo {
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#header {
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#headerMapTitle,
|
|
||||||
#headerActions,
|
|
||||||
#headerLogo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer Styles */
|
|
||||||
div#footer {
|
|
||||||
position: absolute;
|
|
||||||
height: 0px;
|
|
||||||
width: 100%;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#zoomIn {
|
|
||||||
background: url(../images/zoom-in.png) no-repeat left top;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#zoomOut {
|
|
||||||
background: url(../images/zoom-out.png) no-repeat left top;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
float: left;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
float: left;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid black;
|
|
||||||
border-top-color: white;
|
|
||||||
border-left-color: white;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#mapDetails {
|
|
||||||
float: right;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin-right: 130px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div#mapDetails .title {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#infoPanel {
|
|
||||||
border: 2px black solid;
|
|
||||||
position: absolute;
|
|
||||||
background: gray;
|
|
||||||
width: 100px;
|
|
||||||
height: 300px;
|
|
||||||
z-index: 100;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 8px;
|
|
||||||
top: 150px;
|
|
||||||
right: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div#infoPanel .textNode {
|
|
||||||
background-color: #E0E5EF;
|
|
||||||
height: 20px;
|
|
||||||
width: 80px;
|
|
||||||
border: 3px #023BB9 solid;
|
|
||||||
padding: 4px;
|
|
||||||
cursor: move
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
@import "toolbar.css";
|
|
||||||
|
|
||||||
|
|
||||||
#accountSettingsPanel{
|
|
||||||
padding:10px 10px;
|
|
||||||
}
|
|
@ -1,5 +1,3 @@
|
|||||||
@import "editor.css";
|
|
||||||
|
|
||||||
/* Overwrite some styles */
|
/* Overwrite some styles */
|
||||||
body {
|
body {
|
||||||
position: inherit;
|
position: inherit;
|
||||||
@ -33,3 +31,7 @@ div#footer-logo {
|
|||||||
div#mindplot {
|
div#mindplot {
|
||||||
top:0;
|
top:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toolbar {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -1,68 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>WiseMapping - Embedded Sample </title>
|
|
||||||
<meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, Tahoma;
|
|
||||||
background: #a9a9a9;
|
|
||||||
padding: 10px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#code {
|
|
||||||
border: 1px dashed #f5f5dc;
|
|
||||||
padding: 10px;
|
|
||||||
background: #838383;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
|
||||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<div id="doc">
|
|
||||||
<h1>Embedded editor sample</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This is a simple example of how WiseMapping can be embedded in a page.
|
|
||||||
Embedding WiseMapping editor is such simple as copying this line in your page:
|
|
||||||
</p>
|
|
||||||
<div id="code"><iframe src="embedded.html?confUrl=html/container.json" width="800" height="600"></iframe></div>
|
|
||||||
<p>
|
|
||||||
The container.json file contains a set of properties that must be configured. Properties:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>size: Must match with the size of the iframe</li>
|
|
||||||
<li>zoom: Scale to be applied to the map</li>
|
|
||||||
<li>readOnly: If the map could be modified.</li>
|
|
||||||
<li>persistenceManager: Persistence managers to be used. By default, local browser storage is used.</li>
|
|
||||||
<li>mapId: UUID of the map to be loaded.</li>
|
|
||||||
<li>container: div element where the mindmap will be embedded..</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
It's important to point out that embedded.html is a static html page that it's mean to be a template page
|
|
||||||
for advanced customization. In few words, go ahead and modify it.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div id="editor">
|
|
||||||
<iframe src="embedded.html?confUrl=html/container.json" width="800" height="400"></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"readOnly":false,
|
|
||||||
"zoom":1.3,
|
|
||||||
"size":{
|
|
||||||
"width":800,
|
|
||||||
"height":400
|
|
||||||
},
|
|
||||||
"viewPort":
|
|
||||||
{
|
|
||||||
"width":800,
|
|
||||||
"height":400
|
|
||||||
},
|
|
||||||
"persistenceManager": "mindplot.LocalStorageManager",
|
|
||||||
"mapId": "welcome",
|
|
||||||
"container":"mindplot",
|
|
||||||
"locale": "en"
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>WiseMapping - Editor </title>
|
|
||||||
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
|
|
||||||
<link rel="icon" href="images/favicon.ico" type="image/x-icon">
|
|
||||||
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="root" onselectstart="return false;"></div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -24,6 +24,7 @@
|
|||||||
<option value="sample5">sample5</option>
|
<option value="sample5">sample5</option>
|
||||||
<option value="sample6">sample6</option>
|
<option value="sample6">sample6</option>
|
||||||
<option value="sample7">sample7</option>
|
<option value="sample7">sample7</option>
|
||||||
|
<option value="sample8">sample8</option>
|
||||||
<option value="img-support">img-support</option>
|
<option value="img-support">img-support</option>
|
||||||
<option value="error-on-load">error-on-load</option>
|
<option value="error-on-load">error-on-load</option>
|
||||||
<option value="complex">complex</option>
|
<option value="complex">complex</option>
|
||||||
|
@ -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'),
|
|
||||||
);
|
|
49
packages/editor/test/playground/map-render/js/editor.tsx
Normal 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'),
|
||||||
|
);
|
@ -1,35 +0,0 @@
|
|||||||
import '../css/embedded.css';
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Editor from '../../../../src/index';
|
|
||||||
import { buildDesigner, LocalStorageManager, PersistenceManager, DesignerOptionsBuilder } from '@wisemapping/mindplot';
|
|
||||||
|
|
||||||
|
|
||||||
const initialization = () => {
|
|
||||||
// Options has been defined in by a external file ?
|
|
||||||
const p = new LocalStorageManager('samples/{id}.wxml');
|
|
||||||
const options = DesignerOptionsBuilder.buildOptions({ persistenceManager: p });
|
|
||||||
const designer = buildDesigner(options);
|
|
||||||
|
|
||||||
designer.addEvent('loadSuccess', () => {
|
|
||||||
document.getElementById('mindplot').classList.add('ready');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load map from XML file persisted on disk...
|
|
||||||
const mapId = 'welcome';
|
|
||||||
const persistence = PersistenceManager.getInstance();
|
|
||||||
const mindmap = persistence.load(mapId);
|
|
||||||
designer.loadMap(mindmap);
|
|
||||||
};
|
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
<Editor
|
|
||||||
mapId={global.mapId}
|
|
||||||
memoryPersistence={global.memoryPersistence}
|
|
||||||
readOnlyMode={global.readOnly}
|
|
||||||
locale={global.locale}
|
|
||||||
onAction={(action) => console.log('action called:', action)}
|
|
||||||
initCallback={initialization}
|
|
||||||
/>,
|
|
||||||
document.getElementById('root')
|
|
||||||
);
|
|
@ -1,49 +0,0 @@
|
|||||||
import '../css/viewmode.css';
|
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import Editor from '../../../../src/index';
|
|
||||||
import { buildDesigner, LocalStorageManager, PersistenceManager, DesignerOptionsBuilder } from '@wisemapping/mindplot';
|
|
||||||
|
|
||||||
|
|
||||||
const initialization = () => {
|
|
||||||
const p = new LocalStorageManager('samples/{id}.wxml');
|
|
||||||
const options = DesignerOptionsBuilder.buildOptions({ persistenceManager: p, readOnly: true, saveOnLoad: false });
|
|
||||||
|
|
||||||
// Obtain map id from query param
|
|
||||||
const params = new URLSearchParams(window.location.search.substring(1));
|
|
||||||
const mapId = params.get('id') || 'welcome';
|
|
||||||
|
|
||||||
const designer = buildDesigner(options);
|
|
||||||
designer.addEvent('loadSuccess', () => {
|
|
||||||
document.getElementById('mindplot').classList.add('ready');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load map from XML file persisted on disk...
|
|
||||||
const persistence = PersistenceManager.getInstance();
|
|
||||||
const mindmap = persistence.load(mapId);
|
|
||||||
designer.loadMap(mindmap);
|
|
||||||
|
|
||||||
// Code for selector of map.
|
|
||||||
const mapSelectElem = document.getElementById('map-select');
|
|
||||||
mapSelectElem.addEventListener('change', (e) => {
|
|
||||||
const selectMap = e.target.value;
|
|
||||||
window.location = `${window.location.pathname}?id=${selectMap}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
Array.from(mapSelectElem.options).forEach((option) => {
|
|
||||||
option.selected = option.value === mapId;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
<Editor
|
|
||||||
mapId={global.mapId}
|
|
||||||
memoryPersistence={global.memoryPersistence}
|
|
||||||
readOnlyMode={global.readOnly}
|
|
||||||
locale={global.locale}
|
|
||||||
onAction={(action) => console.log('action called:', action)}
|
|
||||||
initCallback={initialization}
|
|
||||||
/>,
|
|
||||||
document.getElementById('root'),
|
|
||||||
);
|
|
||||||
|
|
67
packages/editor/test/playground/map-render/js/viewmode.tsx
Normal 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'),
|
||||||
|
);
|
305
packages/editor/test/playground/map-render/samples/sample8.wxml
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
<map name="844354" version="tango">
|
||||||
|
<topic central="true" text="Школа Интернет Профессий" id="1" fontStyle=";15;;;;">
|
||||||
|
<topic position="281,-3696" order="0" text="Идеология" id="7" fontStyle=";15;;;;">
|
||||||
|
<topic position="441,-3828" order="0" text="Видение" id="14" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="660,-4554" order="0" text="Условия "выживания"" id="172" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="892,-4620" order="0" text="Автономия" id="174" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="915,-4554" order="1" text="Использование" id="176" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="872,-4488" order="2" text="Польза" id="175" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="588,-4356" order="1" text="Ресурсы" id="171" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="732,-4422" order="0" text="Личные" id="177" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="758,-4356" order="1" text="Добываемые" id="178" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="761,-4290" order="2" text="Создаваемые" id="179" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="579,-4158" order="2" text="Обмен" id="173" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="766,-4224" order="0" text="Война за ресурсы" id="180" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="764,-4158" order="1" text="Обмен ресурсами" id="181" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="794,-4092" order="2" text="Объединение ресурсов" id="182" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="594,-3960" order="3" text="Ценность" id="183" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="752,-4026" order="0" text="Создание" id="184" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="773,-3960" order="1" text="Предложение" id="185" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="736,-3894" order="2" text="Обмен" id="186" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="569,-3432" order="4" text="Роль" id="187" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="720,-3762" order="0" text="Собственник" id="188" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="942,-3828" order="0" text="Постановка целей" id="211" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="966,-3762" order="1" text="Инвестиции в системы" id="210" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1030,-3696" order="2" text="Контроль распределения ресурсов" id="217" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="747,-3366" order="2" text="Предприниматель" id="189" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1105,-3432" order="0" text="Создание систем повышения ценности" id="208" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1060,-3366" order="1" text="Поиск выгодных форм обмена" id="209" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="997,-3300" order="2" text="Масштабирование" id="216" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="716,-3234" order="3" text="Управленец" shrink="true" id="190" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="964,-3300" order="0" text="Организация процессов" id="206" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="954,-3234" order="1" text="Контроль результатов" id="207" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1080,-3168" order="2" text="Карьера от наемного сотрудника до партнера" id="215" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="704,-3168" order="4" text="Продавец" shrink="true" id="191" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="891,-3234" order="0" text="Коммуникации" id="204" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="848,-3168" order="1" text="Обмен" id="205" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1119,-3102" order="2" text="Карьера от ставки с премиями к распределению прибыли" id="214" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="715,-3102" order="5" text="Специалист" shrink="true" id="192" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="937,-3168" order="0" text="Создание ценности" id="200" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="954,-3102" order="1" text="Переработка ресурсов" id="203" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1008,-3036" order="2" text="Карьера от зарплаты к гонорару" id="213" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="730,-3036" order="6" text="Обслуживание" shrink="true" id="193" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="982,-3102" order="0" text="Обеспечение условий" id="201" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1002,-3036" order="1" text="Обслуживание процессов" id="202" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1089,-2970" order="2" text="Карьера от внутреннего найма к аутсорсу" id="212" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="702,-3564" order="1" text="Инвестор" id="257" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="977,-3630" order="1" text="Инвестиции в людей и проекты" id="258" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1004,-3498" order="2" text="Управление финансами и ресурсами" id="259" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1173,-3564" order="1" text="Создание множественных источников ресурсов, плодов и ценностей" id="260" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="446,-2970" order="1" text="Ценности" shrink="true" id="12" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="706,-3267" order="0" text="Социальная ответственность" id="223" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="671,-3201" order="1" text="Предпринимательство" id="226" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="638,-3135" order="2" text="Компетентность" id="227" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="750,-3069" order="3" text="Равные возможности на рынке труда" id="219" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="699,-3003" order="4" text="Финансовая независимость" id="220" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="694,-2937" order="5" text="Право на самореализацию" id="218" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="695,-2871" order="6" text="Свобода самоопределения" id="221" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="674,-2805" order="7" text="Социальное равенство" id="222" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="709,-2739" order="8" text="Уважение к любому возрасту" id="225" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="678,-2673" order="9" text="Обеспеченная старость" id="224" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="451,-2904" order="2" text="Принципы" shrink="true" id="13" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="678,-3003" order="0" text="Честность отношений" id="228" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="722,-2937" order="1" text="Ответственность за результат" id="230" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="698,-2871" order="2" text="Эффективность действий" id="231" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="685,-2805" order="3" text="Личная вовлеченность" id="229" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="453,-2838" order="3" text="Стандарты" shrink="true" id="15" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="664,-2970" order="0" text="Профессионализм" id="236" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="663,-2904" order="1" text="Высокое качество" id="232" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="639,-2838" order="2" text="Четкие сроки" id="234" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="695,-2772" order="3" text="Стабильные результаты" id="233" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="714,-2706" order="4" text="Достойное вознаграждение" id="235" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="436,-2772" order="4" text="Методы" shrink="true" id="16" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="644,-3003" order="0" text="Доступное обучение" id="237" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="788,-2937" order="1" text="Помощь в трудоустройстве/поиске сотрудников" id="238" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="725,-2871" order="2" text="Профессиональная инфраструктура" id="240" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="692,-2805" order="3" text="Консалтинг и Сопровождение" id="241" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="689,-2739" order="4" text="Аутсорсинг задач и проектов" id="242" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="742,-2673" order="5" text="Аутстаффинг сотрудников и процессов" id="239" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="597,-2607" order="6" text="Нетворкинг" id="243" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="627,-2541" order="7" text="Арбитраж споров" id="244" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="264,-2706" order="2" text="Миссия" shrink="true" id="8" fontStyle=";15;;;;">
|
||||||
|
<topic position="500,-2772" order="0" text="На что мы хотим повлиять" shrink="true" id="17" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="939,-2805" order="0" text="Положение людей предпенсионного возраста" id="245" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="981,-2739" order="1" text="Уровень диджитализации в малом и среднем бизнесе" id="246" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="484,-2706" order="1" text="Что мы хотим изменить" shrink="true" id="18" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="869,-2772" order="0" text="Подход к поиску и найму сотрудников" id="247" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="897,-2706" order="1" text="Ситуацию на рынке интернет специалистов" id="248" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="909,-2640" order="2" text="Степень доверия и взаимной ответственности" id="249" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="476,-2640" order="2" text="Что мы хотим создать" shrink="true" id="19" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="870,-2838" order="0" text="Центр подготовки интернет специалистов" id="250" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="837,-2772" order="1" text="Центр обучения предпринимателей" id="251" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="934,-2706" order="2" text="Систему профессиональных стандартов и требований" id="256" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="993,-2640" order="3" text="Площадку для коммуникации специалистов и предпринимателей" id="252" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="880,-2574" order="4" text="Агентство трудоустройства и аутстаффинга" id="253" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="959,-2508" order="5" text="Диджитал агентство по аутсорсу и управлению проектами" id="254" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1093,-2442" order="6" text="Профессиональное комьюнити интернет специалистов и онлайн предпринимателей" id="255" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="283,-2640" order="4" text="Концепция" shrink="true" id="6" fontStyle=";15;;;;">
|
||||||
|
<topic position="524,-2871" order="0" text="Формулировка проблем" id="20" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="511,-2805" order="1" text="Определение причин" id="21" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="495,-2739" order="2" text="Постановка целей" id="22" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="491,-2673" order="3" text="План достижения" id="23" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="601,-2607" order="4" text="Список необходимых преобразований" id="24" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="546,-2541" order="5" text="Список необходимых шагов" id="25" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="562,-2475" order="6" text="Список необходимых ресурсов" id="26" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="586,-2409" order="7" text="Список необходимых инструментов" id="27" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="354,-1617" order="6" text="Путь Героя (клиента)" id="9" fontStyle=";15;;;;">
|
||||||
|
<note><![CDATA[Описывается для каждой целевой группы (соискатели/работодатели)]]></note>
|
||||||
|
<topic position="584,-2244" order="0" text="Точка А" id="28" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="845,-2541" order="0" text="Главная неудовлетворенность" id="80" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1200,-2574" order="0" text="Существующие проблемы" id="85" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1240,-2508" order="1" text="Неудовлетворенные потребности" id="86" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="824,-2442" order="1" text="Причины глазами клиента " id="81" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="740,-2343" order="2" text="Состояния" id="82" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="937,-2376" order="0" text="Эмоциональное" id="98" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="916,-2310" order="1" text="Физическое" id="99" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="722,-2211" order="3" text="Страхи" id="83" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1010,-2244" order="0" text="Связанные с неудовлетворенностью" id="100" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1371,-2244" order="0" text="Как продукт избавит" id="101" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="941,-2178" order="1" text="Связанные с решением" id="102" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1301,-2178" order="0" text="Какие свойства продукта избавят" id="103" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="733,-2013" order="4" text="Желания" id="84" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="981,-2112" order="0" text="Главное внешнее желание" id="107" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="988,-2046" order="1" text="Скрытые мотивы и желания" id="108" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="894,-1947" order="2" text="Ожидания" id="109" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1074,-1980" order="0" text="Если решить" id="110" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1089,-1914" order="1" text="Если не решать" id="111" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="584,-1518" order="1" text="Точка Б" id="29" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="759,-1782" order="0" text="Приобретения" id="90" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1097,-1782" order="0" text="Чем станет обладать из того что хотел" id="91" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1418,-1848" order="0" text="Первичные" id="92" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1417,-1782" order="1" text="Вторичные" id="93" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1406,-1716" order="2" text="Скрытые" id="94" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="725,-1584" order="1" text="Выгоды" id="87" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="877,-1650" order="0" text="В усилиях" id="95" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="885,-1584" order="1" text="Во времени" id="96" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="876,-1518" order="2" text="В деньгах" id="97" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="739,-1419" order="2" text="Состояния" id="88" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="936,-1452" order="0" text="Эмоциональное" id="104" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="915,-1386" order="1" text="Физическое" id="105" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="724,-1254" order="3" text="Оценки" id="89" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="886,-1320" order="0" text="Самооценка" id="106" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="879,-1254" order="1" text="Признание" id="112" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="922,-1188" order="2" text="Изменение статуса" id="113" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="575,-891" order="2" text="Этапы" id="30" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="785,-1122" order="0" text="Необходимые условия" id="31" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="786,-1056" order="1" text="Необходимые ресурсы" id="32" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="779,-990" order="2" text="Необходимые знания" id="34" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="782,-924" order="3" text="Необходимые навыки" id="35" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="705,-858" order="4" text="Задачи" id="36" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="790,-792" order="5" text="Необходимые действия" id="33" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="815,-726" order="6" text="Промежуточные результаты" id="37" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="875,-660" order="7" text="Условия перехода к следующему этапу" id="38" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="328,1056" order="8" text="Матрица продуктов" id="10" fontStyle=";15;;;;">
|
||||||
|
<topic position="660,-561" order="0" text="Комплексное решение под ключ" id="39" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="963,-594" order="0" text="Для учеников" id="114" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1299,-594" order="0" text="Обучение+карьера в нашей структуре" id="116" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="1014,-528" order="1" text="Для предпринимателей" id="115" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1338,-528" order="0" text="Интернет проект под ключ" id="117" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="695,-429" order="1" text="Комплексное решение руками клиента" id="40" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1275,-462" order="0" text="Обучение+инструменты трудоустройства и продуктивности" shrink="true" id="118" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1375,-396" order="1" text="Обучение управлению интернет проектами+инструменты найма и управления" id="119" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="689,231" order="2" text="Решение конкретного этапа под ключ" id="41" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1044,-330" order="0" text="Постановка целей" id="141" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1023,-231" order="1" text="Анализ рынка" id="120" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1265,-264" order="0" text="На уровне компании" id="133" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1280,-198" order="1" text="На уровне специалиста" id="134" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="1019,-132" order="2" text="SWOT анализ" id="132" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1072,0" order="3" text="Упаковка предложения" id="121" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1376,-66" order="0" text="Ценностная концепция" id="137" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1390,0" order="1" text="Ценностное предложение" id="138" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1376,66" order="2" text="Торговое предложение" id="139" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="1110,132" order="4" text="Разработка структуры проекта" id="122" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1163,198" order="5" text="Разработка стратегии достижения целей" id="123" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1115,264" order="6" text="Разработка тактического плана" id="140" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1081,330" order="7" text="Техническая реализация" id="126" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1072,396" order="8" text="Производство контента" id="124" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1067,462" order="9" text="Настройка интеграций " id="125" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1051,528" order="10" text="Настройка трафика" id="127" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1067,594" order="11" text="Обработка обращений" id="130" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1101,660" order="12" text="Техническое сопровождение" id="128" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1089,726" order="13" text="Сопровождение процессов" id="131" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1070,792" order="14" text="Клиентская поддержка" id="129" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="723,858" order="3" text="Решение конкретного этапа руками клиента" id="42" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="692,924" order="4" text="Решение конкретной задачи под ключ" id="43" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="726,990" order="5" text="Решение конкретной задачи руками клиента" id="44" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="663,1155" order="6" text="Источник необходимых ресурсов" id="45" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="996,1056" order="0" text="База специалистов" id="142" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="998,1122" order="1" text="Штат специалистов" id="143" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1002,1188" order="2" text="База работодателей" id="144" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1042,1254" order="3" text="База партнерских проектов" id="167" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="671,1452" order="7" text="Набор необходимых инструментов" id="46" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="954,1320" order="0" text="Хостинг" id="145" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="958,1386" order="1" text="Сервисы" id="146" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="962,1452" order="2" text="Шаблоны" id="147" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="982,1518" order="3" text="Инструменты" id="148" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="939,1584" order="4" text="Софт" id="149" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="634,2079" order="9" text="Способ получения навыков" id="48" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="917,1914" order="0" text="Мастер-классы" id="150" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="893,1980" order="1" text="Воркшопы" id="151" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="896,2046" order="2" text="Интенсивы" id="158" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="888,2112" order="3" text="Тренинги" id="152" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="937,2178" order="4" text="Программы курсов" id="159" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="902,2244" order="5" text="Стажировки" id="153" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="724,2508" order="10" text="Создание среды с необходимыми условиями" id="49" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="1081,2310" order="0" text="Сообщество" id="160" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1149,2376" order="1" text="Платформа для общения" id="161" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1074,2442" order="2" text="Коворкинг" id="162" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1080,2508" order="3" text="Нетворкинг" id="163" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1267,2574" order="4" text="Платформа для поиска партнеров/соискателей" id="164" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1152,2640" order="5" text="Платформа для обучения" id="165" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="1141,2706" order="6" text="Платформа для работы" id="166" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="628,1749" order="8" text="База необходимых знаний" id="47" fontStyle=";15;;;;" bgColor="rgb(224,229,239)">
|
||||||
|
<topic position="890,1650" order="0" text="Публикации" id="154" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="920,1716" order="1" text="Открытые лекции" id="155" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="962,1782" order="2" text="Образовательные каналы" id="156" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="923,1848" order="3" text="Платное обучение" id="157" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
<topic position="303,3036" order="10" text="Бизнес модель" id="2" fontStyle=";15;;;;">
|
||||||
|
<topic position="553,2772" order="0" text="Клиентские сегменты" id="71" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="575,2838" order="1" text="Ценностное предложение" id="72" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="565,2904" order="2" text="Способы коммуникации" id="73" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="573,2970" order="3" text="Способы предоставления" id="74" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="598,3036" order="4" text="Ключевые виды деятельности" id="75" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="540,3102" order="5" text="Ключевые ресурсы" id="76" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="547,3168" order="6" text="Ключевые партнеры" id="77" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="545,3234" order="7" text="Структура расходов" id="78" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="541,3300" order="8" text="Источники доходов" id="79" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="333,3630" order="12" text="Ключевые процессы" id="5" fontStyle=";15;;;;">
|
||||||
|
<topic position="560,3366" order="0" text="Консалтинг" id="57" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="676,3432" order="1" text="Разработка обучающих программ" id="50" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="551,3498" order="2" text="Обучение" id="53" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="625,3564" order="3" text="Создание инструментов" id="51" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="654,3630" order="4" text="Систематизация информации" id="52" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="595,3696" order="5" text="Создание условий" id="54" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="621,3762" order="6" text="Управление проектами" id="55" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="582,3828" order="7" text="Оказание услуг" id="170" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="616,3894" order="8" text="Поддержка процессов" id="56" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
<topic position="374,3960" order="14" text="Организационная структура" id="3" fontStyle=";15;;;;"/>
|
||||||
|
<topic position="258,4323" order="16" text="Этапы" id="60" fontStyle=";15;;;;">
|
||||||
|
<topic position="495,4026" order="0" text="Определение потребностей" id="61" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="425,4092" order="1" text="Анализ рынка" id="62" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="552,4158" order="2" text="Разработка ценностного предложения" id="63" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="425,4224" order="3" text="Создание MVP" id="64" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="439,4290" order="4" text="Тестовый запуск" id="68" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="388,4356" order="5" text="Анализ" id="65" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="518,4422" order="6" text="Разработка основного продукта" id="66" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="381,4488" order="7" text="Релиз" id="69" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="544,4554" order="8" text="Систематизация ключевых процесов" id="70" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
<topic position="446,4620" order="9" text="Масштабирование" id="67" fontStyle=";15;;;;" bgColor="rgb(224,229,239)"/>
|
||||||
|
</topic>
|
||||||
|
</topic>
|
||||||
|
</map>
|
@ -7,8 +7,9 @@ module.exports = {
|
|||||||
publicPath: '',
|
publicPath: '',
|
||||||
library: {
|
library: {
|
||||||
type: 'umd',
|
type: 'umd',
|
||||||
}, },
|
},
|
||||||
stats:{
|
},
|
||||||
|
stats: {
|
||||||
errorDetails: true
|
errorDetails: true
|
||||||
},
|
},
|
||||||
entry: {
|
entry: {
|
||||||
@ -35,7 +36,14 @@ module.exports = {
|
|||||||
test: /\.(js|jsx)$/,
|
test: /\.(js|jsx)$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: ['babel-loader'],
|
use: ['babel-loader'],
|
||||||
|
}, {
|
||||||
|
test: /\.css$/i,
|
||||||
|
loader: 'style-loader'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
loader: 'css-loader',
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
@ -9,7 +9,6 @@ const playgroundConfig = {
|
|||||||
mode: 'development',
|
mode: 'development',
|
||||||
entry: {
|
entry: {
|
||||||
viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'),
|
viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'),
|
||||||
embedded: path.resolve(__dirname, './test/playground/map-render/js/embedded'),
|
|
||||||
editor: path.resolve(__dirname, './test/playground/map-render/js/editor'),
|
editor: path.resolve(__dirname, './test/playground/map-render/js/editor'),
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
@ -24,17 +23,6 @@ const playgroundConfig = {
|
|||||||
port: 8081,
|
port: 8081,
|
||||||
open: false,
|
open: false,
|
||||||
},
|
},
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.css$/i,
|
|
||||||
use: [
|
|
||||||
'style-loader',
|
|
||||||
'css-loader?url=false',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin({
|
new CleanWebpackPlugin({
|
||||||
dangerouslyAllowCleanPatternsOutsideProject: true,
|
dangerouslyAllowCleanPatternsOutsideProject: true,
|
||||||
@ -46,10 +34,7 @@ const playgroundConfig = {
|
|||||||
{ from: 'test/playground/map-render/images', to: 'images' },
|
{ from: 'test/playground/map-render/images', to: 'images' },
|
||||||
{ from: 'test/playground/map-render/js', to: 'js' },
|
{ from: 'test/playground/map-render/js', to: 'js' },
|
||||||
{ from: 'test/playground/map-render/samples', to: 'samples' },
|
{ from: 'test/playground/map-render/samples', to: 'samples' },
|
||||||
{ from: '../../libraries/bootstrap', to: 'bootstrap' },
|
|
||||||
{ from: 'test/playground/index.html', to: 'index.html' },
|
{ from: 'test/playground/index.html', to: 'index.html' },
|
||||||
{ from: 'test/playground/map-render/html/container.json', to: 'html/container.json' },
|
|
||||||
{ from: 'test/playground/map-render/html/container.html', to: 'container.html' },
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
@ -57,11 +42,6 @@ const playgroundConfig = {
|
|||||||
filename: 'viewmode.html',
|
filename: 'viewmode.html',
|
||||||
template: 'test/playground/map-render/html/viewmode.html',
|
template: 'test/playground/map-render/html/viewmode.html',
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
chunks: ['embedded'],
|
|
||||||
filename: 'embedded.html',
|
|
||||||
template: 'test/playground/map-render/html/embedded.html',
|
|
||||||
}),
|
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
chunks: ['editor'],
|
chunks: ['editor'],
|
||||||
filename: 'editor.html',
|
filename: 'editor.html',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wisemapping/mindplot",
|
"name": "@wisemapping/mindplot",
|
||||||
"version": "5.0.3",
|
"version": "5.0.7",
|
||||||
"description": "WiseMapping - Mindplot Canvas Library",
|
"description": "WiseMapping - Mindplot Canvas Library",
|
||||||
"homepage": "http://www.wisemapping.org/",
|
"homepage": "http://www.wisemapping.org/",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
4
packages/mindplot/src/@types/custom.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module "*.svg" {
|
||||||
|
const content: any;
|
||||||
|
export default content;
|
||||||
|
}
|
@ -30,9 +30,16 @@ import Topic from './Topic';
|
|||||||
abstract class ActionDispatcher extends Events {
|
abstract class ActionDispatcher extends Events {
|
||||||
private static _instance: ActionDispatcher;
|
private static _instance: ActionDispatcher;
|
||||||
|
|
||||||
|
private _commandContext: CommandContext;
|
||||||
|
|
||||||
constructor(commandContext: CommandContext) {
|
constructor(commandContext: CommandContext) {
|
||||||
$assert(commandContext, 'commandContext can not be null');
|
$assert(commandContext, 'commandContext can not be null');
|
||||||
super();
|
super();
|
||||||
|
this._commandContext = commandContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCommandContext(): CommandContext {
|
||||||
|
return this._commandContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract addRelationship(model: RelationshipModel, mindmap: Mindmap): void;
|
abstract addRelationship(model: RelationshipModel, mindmap: Mindmap): void;
|
||||||
|
@ -40,7 +40,6 @@ class CommandContext {
|
|||||||
this._designer = value;
|
this._designer = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
|
||||||
findTopics(topicIds: number[]): Topic[] {
|
findTopics(topicIds: number[]): Topic[] {
|
||||||
$assert($defined(topicIds), 'topicsIds can not be null');
|
$assert($defined(topicIds), 'topicsIds can not be null');
|
||||||
const topicsIds = Array.isArray(topicIds) ? topicIds : [topicIds];
|
const topicsIds = Array.isArray(topicIds) ? topicIds : [topicIds];
|
||||||
|
@ -21,10 +21,23 @@ import {
|
|||||||
Point, CurvedLine, PolyLine, Line,
|
Point, CurvedLine, PolyLine, Line,
|
||||||
} from '@wisemapping/web2d';
|
} from '@wisemapping/web2d';
|
||||||
import { TopicShape } from './model/INodeModel';
|
import { TopicShape } from './model/INodeModel';
|
||||||
|
import RelationshipModel from './model/RelationshipModel';
|
||||||
|
import Topic from './Topic';
|
||||||
import TopicConfig from './TopicConfig';
|
import TopicConfig from './TopicConfig';
|
||||||
|
import Workspace from './Workspace';
|
||||||
|
|
||||||
class ConnectionLine {
|
class ConnectionLine {
|
||||||
constructor(sourceNode, targetNode, lineType) {
|
protected _targetTopic: Topic;
|
||||||
|
|
||||||
|
protected _sourceTopic: Topic;
|
||||||
|
|
||||||
|
protected _lineType: number;
|
||||||
|
|
||||||
|
protected _line2d: Line;
|
||||||
|
|
||||||
|
protected _model: RelationshipModel;
|
||||||
|
|
||||||
|
constructor(sourceNode: Topic, targetNode: Topic, lineType?: number) {
|
||||||
$assert(targetNode, 'parentNode node can not be null');
|
$assert(targetNode, 'parentNode node can not be null');
|
||||||
$assert(sourceNode, 'childNode node can not be null');
|
$assert(sourceNode, 'childNode node can not be null');
|
||||||
$assert(sourceNode !== targetNode, 'Circular connection');
|
$assert(sourceNode !== targetNode, 'Circular connection');
|
||||||
@ -32,7 +45,7 @@ class ConnectionLine {
|
|||||||
this._targetTopic = targetNode;
|
this._targetTopic = targetNode;
|
||||||
this._sourceTopic = sourceNode;
|
this._sourceTopic = sourceNode;
|
||||||
|
|
||||||
let line;
|
let line: Line;
|
||||||
const ctrlPoints = this._getCtrlPoints(sourceNode, targetNode);
|
const ctrlPoints = this._getCtrlPoints(sourceNode, targetNode);
|
||||||
if (targetNode.getType() === 'CentralTopic') {
|
if (targetNode.getType() === 'CentralTopic') {
|
||||||
line = this._createLine(lineType, ConnectionLine.CURVED);
|
line = this._createLine(lineType, ConnectionLine.CURVED);
|
||||||
@ -51,15 +64,15 @@ class ConnectionLine {
|
|||||||
this._line2d = line;
|
this._line2d = line;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCtrlPoints(sourceNode, targetNode) {
|
private _getCtrlPoints(sourceNode: Topic, targetNode: Topic) {
|
||||||
const srcPos = sourceNode.workoutOutgoingConnectionPoint(targetNode.getPosition());
|
const srcPos = sourceNode.workoutOutgoingConnectionPoint(targetNode.getPosition());
|
||||||
const destPos = targetNode.workoutIncomingConnectionPoint(sourceNode.getPosition());
|
const destPos = targetNode.workoutIncomingConnectionPoint(sourceNode.getPosition());
|
||||||
const deltaX = (srcPos.x - destPos.x) / 3;
|
const deltaX = (srcPos.x - destPos.x) / 3;
|
||||||
return [new Point(deltaX, 0), new Point(-deltaX, 0)];
|
return [new Point(deltaX, 0), new Point(-deltaX, 0)];
|
||||||
}
|
}
|
||||||
|
|
||||||
_createLine(lineTypeParam, defaultStyle) {
|
protected _createLine(lineTypeParam: number, defaultStyle: number): Line {
|
||||||
const lineType = $defined(lineTypeParam) ? parseInt(lineTypeParam, 10) : defaultStyle;
|
const lineType = $defined(lineTypeParam) ? lineTypeParam : defaultStyle;
|
||||||
this._lineType = lineType;
|
this._lineType = lineType;
|
||||||
let line = null;
|
let line = null;
|
||||||
switch (lineType) {
|
switch (lineType) {
|
||||||
@ -80,7 +93,7 @@ class ConnectionLine {
|
|||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisibility(value) {
|
setVisibility(value: boolean): void {
|
||||||
this._line2d.setVisibility(value);
|
this._line2d.setVisibility(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,11 +101,11 @@ class ConnectionLine {
|
|||||||
return this._line2d.isVisible();
|
return this._line2d.isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
setOpacity(opacity) {
|
setOpacity(opacity: number): void {
|
||||||
this._line2d.setOpacity(opacity);
|
this._line2d.setOpacity(opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
redraw() {
|
redraw(): void {
|
||||||
const line2d = this._line2d;
|
const line2d = this._line2d;
|
||||||
const sourceTopic = this._sourceTopic;
|
const sourceTopic = this._sourceTopic;
|
||||||
const sourcePosition = sourceTopic.getPosition();
|
const sourcePosition = sourceTopic.getPosition();
|
||||||
@ -116,7 +129,7 @@ class ConnectionLine {
|
|||||||
this._positionateConnector(targetTopic);
|
this._positionateConnector(targetTopic);
|
||||||
}
|
}
|
||||||
|
|
||||||
_positionateConnector(targetTopic) {
|
protected _positionateConnector(targetTopic: Topic): void {
|
||||||
const targetPosition = targetTopic.getPosition();
|
const targetPosition = targetTopic.getPosition();
|
||||||
const offset = TopicConfig.CONNECTOR_WIDTH / 2;
|
const offset = TopicConfig.CONNECTOR_WIDTH / 2;
|
||||||
const targetTopicSize = targetTopic.getSize();
|
const targetTopicSize = targetTopic.getSize();
|
||||||
@ -141,65 +154,68 @@ class ConnectionLine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setStroke(color, style, opacity) {
|
setStroke(color: string, style: string, opacity: number) {
|
||||||
this._line2d.setStroke(null, null, color, opacity);
|
this._line2d.setStroke(null, null, color, opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
addToWorkspace(workspace) {
|
addToWorkspace(workspace: Workspace) {
|
||||||
workspace.append(this._line2d);
|
workspace.append(this._line2d);
|
||||||
this._line2d.moveToBack();
|
this._line2d.moveToBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromWorkspace(workspace) {
|
removeFromWorkspace(workspace: Workspace) {
|
||||||
workspace.removeChild(this._line2d);
|
workspace.removeChild(this._line2d);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetTopic() {
|
getTargetTopic(): Topic {
|
||||||
return this._targetTopic;
|
return this._targetTopic;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSourceTopic() {
|
getSourceTopic(): Topic {
|
||||||
return this._sourceTopic;
|
return this._sourceTopic;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLineType() {
|
getLineType(): number {
|
||||||
return this._lineType;
|
return this._lineType;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLine() {
|
getLine(): Line {
|
||||||
return this._line2d;
|
return this._line2d;
|
||||||
}
|
}
|
||||||
|
|
||||||
getModel() {
|
getModel(): RelationshipModel {
|
||||||
return this._model;
|
return this._model;
|
||||||
}
|
}
|
||||||
|
|
||||||
setModel(model) {
|
setModel(model: RelationshipModel): void {
|
||||||
this._model = model;
|
this._model = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
getType() {
|
getType(): string {
|
||||||
return 'ConnectionLine';
|
return 'ConnectionLine';
|
||||||
}
|
}
|
||||||
|
|
||||||
getId() {
|
getId(): number {
|
||||||
return this._model.getId();
|
return this._model.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToBack() {
|
moveToBack(): void {
|
||||||
this._line2d.moveToBack();
|
this._line2d.moveToBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToFront() {
|
moveToFront() {
|
||||||
this._line2d.moveToFront();
|
this._line2d.moveToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SIMPLE = 0;
|
||||||
|
|
||||||
|
static POLYLINE = 1;
|
||||||
|
|
||||||
|
static CURVED = 2;
|
||||||
|
|
||||||
|
static SIMPLE_CURVED = 3;
|
||||||
|
|
||||||
|
static getStrokeColor = () => '#495879';
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionLine.getStrokeColor = () => '#495879';
|
|
||||||
|
|
||||||
ConnectionLine.SIMPLE = 0;
|
|
||||||
ConnectionLine.POLYLINE = 1;
|
|
||||||
ConnectionLine.CURVED = 2;
|
|
||||||
ConnectionLine.SIMPLE_CURVED = 3;
|
|
||||||
|
|
||||||
export default ConnectionLine;
|
export default ConnectionLine;
|
@ -110,7 +110,7 @@ class Designer extends Events {
|
|||||||
|
|
||||||
// Init Screen manager..
|
// Init Screen manager..
|
||||||
const screenManager = new ScreenManager(divElement);
|
const screenManager = new ScreenManager(divElement);
|
||||||
this._workspace = new Workspace(screenManager, this._model.getZoom(), !!options.readOnly);
|
this._workspace = new Workspace(screenManager, this._model.getZoom(), options.mode === 'viewonly');
|
||||||
|
|
||||||
// Init layout manager ...
|
// Init layout manager ...
|
||||||
this._eventBussDispatcher = new EventBusDispatcher();
|
this._eventBussDispatcher = new EventBusDispatcher();
|
||||||
@ -335,7 +335,7 @@ class Designer extends Events {
|
|||||||
zoomOut(factor = 1.2) {
|
zoomOut(factor = 1.2) {
|
||||||
const model = this.getModel();
|
const model = this.getModel();
|
||||||
const scale = model.getZoom() * factor;
|
const scale = model.getZoom() * factor;
|
||||||
if (scale <= 1.9) {
|
if (scale <= 7.0) {
|
||||||
model.setZoom(scale);
|
model.setZoom(scale);
|
||||||
this._workspace.setZoom(scale);
|
this._workspace.setZoom(scale);
|
||||||
} else {
|
} else {
|
||||||
@ -626,7 +626,7 @@ class Designer extends Events {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isReadOnly(): boolean {
|
isReadOnly(): boolean {
|
||||||
return Boolean(this._options?.readOnly);
|
return Boolean(this._options?.mode === 'viewonly');
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeModelToTopic(nodeModel: NodeModel): Topic {
|
nodeModelToTopic(nodeModel: NodeModel): Topic {
|
||||||
|
@ -20,8 +20,6 @@ import $ from 'jquery';
|
|||||||
import PersistenceManager from './PersistenceManager';
|
import PersistenceManager from './PersistenceManager';
|
||||||
import Designer from './Designer';
|
import Designer from './Designer';
|
||||||
import Menu from './widget/Menu';
|
import Menu from './widget/Menu';
|
||||||
import { $notifyModal } from './widget/ModalDialogNotifier';
|
|
||||||
import { $msg } from './Messages';
|
|
||||||
import { DesignerOptions } from './DesignerOptionsBuilder';
|
import { DesignerOptions } from './DesignerOptionsBuilder';
|
||||||
|
|
||||||
let designer: Designer;
|
let designer: Designer;
|
||||||
@ -37,36 +35,6 @@ export function buildDesigner(options: DesignerOptions): Designer {
|
|||||||
console.log('Map loadded successfully');
|
console.log('Map loadded successfully');
|
||||||
});
|
});
|
||||||
|
|
||||||
const onerrorFn = (msg: string, url: string, lineNo: number, columnNo: number, error: Error) => {
|
|
||||||
const message = [
|
|
||||||
`Message: ${msg}`,
|
|
||||||
`URL: ${url}`,
|
|
||||||
`Line: ${lineNo}`,
|
|
||||||
`Column: ${columnNo}`,
|
|
||||||
].join(' - ');
|
|
||||||
console.log(message);
|
|
||||||
|
|
||||||
// Send error to server ...
|
|
||||||
$.ajax({
|
|
||||||
method: 'post',
|
|
||||||
url: '/c/restful/logger/editor',
|
|
||||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
||||||
data: JSON.stringify({
|
|
||||||
jsErrorMsg: message,
|
|
||||||
jsStack: JSON.stringify(error),
|
|
||||||
userAgent: navigator.userAgent,
|
|
||||||
mapId: options.mapId,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Open error dialog only in case of mindmap loading errors. The rest of the error are reported but not display the dialog.
|
|
||||||
// Remove this in the near future.
|
|
||||||
if (!globalThis.mindmapLoadReady) {
|
|
||||||
$notifyModal($msg('UNEXPECTED_ERROR_LOADING'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.onerror = onerrorFn;
|
|
||||||
|
|
||||||
// Configure default persistence manager ...
|
// Configure default persistence manager ...
|
||||||
const persistence = options.persistenceManager;
|
const persistence = options.persistenceManager;
|
||||||
$assert(persistence, 'persistence must be defined');
|
$assert(persistence, 'persistence must be defined');
|
||||||
|
@ -21,16 +21,28 @@ import Keyboard from './Keyboard';
|
|||||||
import { Designer } from '..';
|
import { Designer } from '..';
|
||||||
import Topic from './Topic';
|
import Topic from './Topic';
|
||||||
|
|
||||||
|
export type EventCallback = (event?: Event) => void;
|
||||||
class DesignerKeyboard extends Keyboard {
|
class DesignerKeyboard extends Keyboard {
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
static _instance: DesignerKeyboard;
|
static _instance: DesignerKeyboard;
|
||||||
|
|
||||||
|
static _disabled: boolean;
|
||||||
|
|
||||||
constructor(designer: Designer) {
|
constructor(designer: Designer) {
|
||||||
super();
|
super();
|
||||||
$assert(designer, 'designer can not be null');
|
$assert(designer, 'designer can not be null');
|
||||||
this._registerEvents(designer);
|
this._registerEvents(designer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addShortcut(shortcuts: string[] | string, callback: EventCallback): void {
|
||||||
|
super.addShortcut(shortcuts, (e: Event) => {
|
||||||
|
if (DesignerKeyboard.isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _registerEvents(designer: Designer) {
|
private _registerEvents(designer: Designer) {
|
||||||
// Try with the keyboard ..
|
// Try with the keyboard ..
|
||||||
const model = designer.getModel();
|
const model = designer.getModel();
|
||||||
@ -246,6 +258,10 @@ class DesignerKeyboard extends Keyboard {
|
|||||||
|
|
||||||
$(document).on('keypress', (event) => {
|
$(document).on('keypress', (event) => {
|
||||||
let keyCode: number;
|
let keyCode: number;
|
||||||
|
|
||||||
|
if (DesignerKeyboard.isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Firefox doesn't skip special keys for keypress event...
|
// Firefox doesn't skip special keys for keypress event...
|
||||||
if (event.key && excludes.includes(event.key.toLowerCase())) {
|
if (event.key && excludes.includes(event.key.toLowerCase())) {
|
||||||
return;
|
return;
|
||||||
@ -264,11 +280,10 @@ class DesignerKeyboard extends Keyboard {
|
|||||||
const nodes = designer.getModel().filterSelectedTopics();
|
const nodes = designer.getModel().filterSelectedTopics();
|
||||||
if (nodes.length > 0) {
|
if (nodes.length > 0) {
|
||||||
// If a modifier is press, the key selected must be ignored.
|
// If a modifier is press, the key selected must be ignored.
|
||||||
const pressKey = String.fromCharCode(keyCode);
|
|
||||||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
if (event.ctrlKey || event.altKey || event.metaKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
nodes[0].showTextEditor(pressKey);
|
nodes[0].showTextEditor('');
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,7 +362,7 @@ class DesignerKeyboard extends Keyboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _goToChild(designer, node) {
|
private _goToChild(designer: Designer, node: Topic) {
|
||||||
const children = node.getChildren();
|
const children = node.getChildren();
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
let target = children[0];
|
let target = children[0];
|
||||||
@ -373,6 +388,19 @@ class DesignerKeyboard extends Keyboard {
|
|||||||
|
|
||||||
static register = function register(designer: Designer) {
|
static register = function register(designer: Designer) {
|
||||||
this._instance = new DesignerKeyboard(designer);
|
this._instance = new DesignerKeyboard(designer);
|
||||||
|
this._disabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static pause = function pause() {
|
||||||
|
this._disabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
static resume = function resume() {
|
||||||
|
this._disabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static isDisabled = function isDisabled() {
|
||||||
|
return this._disabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
static specialKeys = {
|
static specialKeys = {
|
||||||
|
@ -19,10 +19,12 @@ import { $assert } from '@wisemapping/core-js';
|
|||||||
import PersistenceManager from './PersistenceManager';
|
import PersistenceManager from './PersistenceManager';
|
||||||
import SizeType from './SizeType';
|
import SizeType from './SizeType';
|
||||||
|
|
||||||
|
export type EditorModeType = 'viewonly' | 'edition' | 'showcase';
|
||||||
|
|
||||||
export type DesignerOptions = {
|
export type DesignerOptions = {
|
||||||
zoom: number,
|
zoom: number,
|
||||||
containerSize?: SizeType,
|
containerSize?: SizeType,
|
||||||
readOnly?: boolean,
|
mode: EditorModeType,
|
||||||
mapId?: string,
|
mapId?: string,
|
||||||
container: string,
|
container: string,
|
||||||
persistenceManager?: PersistenceManager,
|
persistenceManager?: PersistenceManager,
|
||||||
@ -45,7 +47,7 @@ class OptionsBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: DesignerOptions = {
|
const defaultOptions: DesignerOptions = {
|
||||||
readOnly: false,
|
mode: 'edition',
|
||||||
zoom: 0.85,
|
zoom: 0.85,
|
||||||
saveOnLoad: true,
|
saveOnLoad: true,
|
||||||
containerSize,
|
containerSize,
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import { $assert, $defined } from '@wisemapping/core-js';
|
import { $assert, $defined } from '@wisemapping/core-js';
|
||||||
import DragTopic from './DragTopic';
|
import DragTopic from './DragTopic';
|
||||||
import EventBusDispatcher from './layout/EventBusDispatcher';
|
import EventBusDispatcher from './layout/EventBusDispatcher';
|
||||||
|
import Topic from './Topic';
|
||||||
import Workspace from './Workspace';
|
import Workspace from './Workspace';
|
||||||
|
|
||||||
class DragManager {
|
class DragManager {
|
||||||
@ -44,7 +45,7 @@ class DragManager {
|
|||||||
DragTopic.init(this._workspace);
|
DragTopic.init(this._workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
add(node) {
|
add(topic: Topic) {
|
||||||
// Add behaviour ...
|
// Add behaviour ...
|
||||||
const workspace = this._workspace;
|
const workspace = this._workspace;
|
||||||
const screen = workspace.getScreenManager();
|
const screen = workspace.getScreenManager();
|
||||||
@ -57,7 +58,7 @@ class DragManager {
|
|||||||
|
|
||||||
// Set initial position.
|
// Set initial position.
|
||||||
const layoutManager = me._eventDispatcher.getLayoutManager();
|
const layoutManager = me._eventDispatcher.getLayoutManager();
|
||||||
const dragNode = node.createDragNode(layoutManager);
|
const dragNode = topic.createDragNode(layoutManager);
|
||||||
|
|
||||||
// Register mouse move listener ...
|
// Register mouse move listener ...
|
||||||
const mouseMoveListener = dragManager._buildMouseMoveListener(
|
const mouseMoveListener = dragManager._buildMouseMoveListener(
|
||||||
@ -67,7 +68,7 @@ class DragManager {
|
|||||||
|
|
||||||
// Register mouse up listeners ...
|
// Register mouse up listeners ...
|
||||||
const mouseUpListener = dragManager._buildMouseUpListener(
|
const mouseUpListener = dragManager._buildMouseUpListener(
|
||||||
workspace, node, dragNode, dragManager,
|
workspace, topic, dragNode, dragManager,
|
||||||
);
|
);
|
||||||
screen.addEvent('mouseup', mouseUpListener);
|
screen.addEvent('mouseup', mouseUpListener);
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ class DragManager {
|
|||||||
window.document.body.style.cursor = 'move';
|
window.document.body.style.cursor = 'move';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
node.addEvent('mousedown', mouseDownListener);
|
topic.addEvent('mousedown', mouseDownListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
@ -114,10 +115,10 @@ class DragManager {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _buildMouseUpListener(workspace: Workspace, node, dragNode, dragManager: DragManager) {
|
protected _buildMouseUpListener(workspace: Workspace, topic: Topic, dragNode, dragManager: DragManager) {
|
||||||
const screen = workspace.getScreenManager();
|
const screen = workspace.getScreenManager();
|
||||||
const me = this;
|
const me = this;
|
||||||
const result = (event) => {
|
const result = (event: Event) => {
|
||||||
$assert(dragNode.isDragTopic, 'dragNode must be an DragTopic');
|
$assert(dragNode.isDragTopic, 'dragNode must be an DragTopic');
|
||||||
|
|
||||||
// Remove all the events.
|
// Remove all the events.
|
||||||
@ -154,7 +155,7 @@ class DragManager {
|
|||||||
* - dragging
|
* - dragging
|
||||||
* - enddragging
|
* - enddragging
|
||||||
*/
|
*/
|
||||||
addEvent(type, listener) {
|
addEvent(type: string, listener) {
|
||||||
this._listeners[type] = listener;
|
this._listeners[type] = listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,36 +17,43 @@
|
|||||||
*/
|
*/
|
||||||
import { $assert } from '@wisemapping/core-js';
|
import { $assert } from '@wisemapping/core-js';
|
||||||
import { Image } from '@wisemapping/web2d';
|
import { Image } from '@wisemapping/web2d';
|
||||||
|
import IconGroup from './IconGroup';
|
||||||
|
import { Point } from '@wisemapping/web2d';
|
||||||
|
import SizeType from './SizeType';
|
||||||
|
import FeatureModel from './model/FeatureModel';
|
||||||
|
|
||||||
class Icon {
|
abstract class Icon {
|
||||||
constructor(url) {
|
protected _image: Image;
|
||||||
|
protected _group: IconGroup;
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
$assert(url, 'topic can not be null');
|
$assert(url, 'topic can not be null');
|
||||||
this._image = new Image();
|
this._image = new Image();
|
||||||
this._image.setHref(url);
|
this._image.setHref(url);
|
||||||
this._image.setSize(Icon.SIZE, Icon.SIZE);
|
this._image.setSize(Icon.SIZE, Icon.SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
getImage() {
|
getImage(): Image {
|
||||||
return this._image;
|
return this._image;
|
||||||
}
|
}
|
||||||
|
|
||||||
setGroup(group) {
|
setGroup(group: IconGroup) {
|
||||||
this._group = group;
|
this._group = group;
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroup() {
|
getGroup(): IconGroup {
|
||||||
return this._group;
|
return this._group;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSize() {
|
getSize(): SizeType {
|
||||||
return this._image.getSize();
|
return this._image.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
getPosition() {
|
getPosition(): Point {
|
||||||
return this._image.getPosition();
|
return this._image.getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
addEvent(type, fnc) {
|
addEvent(type: string, fnc): void {
|
||||||
this._image.addEvent(type, fnc);
|
this._image.addEvent(type, fnc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +61,10 @@ class Icon {
|
|||||||
remove() {
|
remove() {
|
||||||
throw new Error('Unsupported operation');
|
throw new Error('Unsupported operation');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract getModel(): FeatureModel;
|
||||||
|
|
||||||
|
static SIZE = 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon.SIZE = 90;
|
|
||||||
|
|
||||||
export default Icon;
|
export default Icon;
|
@ -22,21 +22,31 @@ import {
|
|||||||
} from '@wisemapping/core-js';
|
} from '@wisemapping/core-js';
|
||||||
import {
|
import {
|
||||||
Group,
|
Group,
|
||||||
|
ElementClass,
|
||||||
} from '@wisemapping/web2d';
|
} from '@wisemapping/web2d';
|
||||||
import IconGroupRemoveTip from './IconGroupRemoveTip';
|
import IconGroupRemoveTip from './IconGroupRemoveTip';
|
||||||
|
import { Point } from '@wisemapping/web2d';
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
|
import SizeType from './SizeType';
|
||||||
|
import IconModel from './model/IconModel';
|
||||||
|
import FeatureModel from './model/FeatureModel';
|
||||||
|
|
||||||
const ORDER_BY_TYPE = new Map();
|
const ORDER_BY_TYPE = new Map<string, number>();
|
||||||
ORDER_BY_TYPE.set('icon', 0);
|
ORDER_BY_TYPE.set('icon', 0);
|
||||||
ORDER_BY_TYPE.set('note', 1);
|
ORDER_BY_TYPE.set('note', 1);
|
||||||
ORDER_BY_TYPE.set('link', 2);
|
ORDER_BY_TYPE.set('link', 2);
|
||||||
|
|
||||||
class IconGroup {
|
class IconGroup {
|
||||||
constructor(topicId, iconSize) {
|
private _icons: Icon[];
|
||||||
|
private _group: any;
|
||||||
|
private _removeTip: IconGroupRemoveTip;
|
||||||
|
private _iconSize: SizeType;
|
||||||
|
private _topicId: number;
|
||||||
|
constructor(topicId: number, iconSize: number) {
|
||||||
$assert($defined(topicId), 'topicId can not be null');
|
$assert($defined(topicId), 'topicId can not be null');
|
||||||
$assert($defined(iconSize), 'iconSize can not be null');
|
$assert($defined(iconSize), 'iconSize can not be null');
|
||||||
|
|
||||||
|
this._topicId = topicId;
|
||||||
this._icons = [];
|
this._icons = [];
|
||||||
this._group = new Group({
|
this._group = new Group({
|
||||||
width: 0,
|
width: 0,
|
||||||
@ -46,34 +56,31 @@ class IconGroup {
|
|||||||
coordSizeWidth: 0,
|
coordSizeWidth: 0,
|
||||||
coordSizeHeight: 100,
|
coordSizeHeight: 100,
|
||||||
});
|
});
|
||||||
this._removeTip = new IconGroupRemoveTip(this._group, topicId);
|
this._removeTip = new IconGroupRemoveTip(this._group);
|
||||||
this.seIconSize(iconSize, iconSize);
|
this.seIconSize(iconSize, iconSize);
|
||||||
|
|
||||||
this._registerListeners();
|
this._registerListeners();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
setPosition(x: number, y: number): void {
|
||||||
setPosition(x, y) {
|
|
||||||
this._group.setPosition(x, y);
|
this._group.setPosition(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
getPosition(): Point {
|
||||||
getPosition() {
|
|
||||||
return this._group.getPosition();
|
return this._group.getPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
getNativeElement(): ElementClass {
|
||||||
getNativeElement() {
|
|
||||||
return this._group;
|
return this._group;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
getSize() {
|
getSize(): SizeType {
|
||||||
return this._group.getSize();
|
return this._group.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
seIconSize(width, height) {
|
seIconSize(width: number, height: number) {
|
||||||
this._iconSize = {
|
this._iconSize = {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -81,12 +88,7 @@ class IconGroup {
|
|||||||
this._resize(this._icons.length);
|
this._resize(this._icons.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
addIcon(icon: Icon, remove: boolean) {
|
||||||
* @param icon the icon to be added to the icon group
|
|
||||||
* @param {Boolean} remove
|
|
||||||
* @throws will throw an error if icon is not defined
|
|
||||||
*/
|
|
||||||
addIcon(icon, remove) {
|
|
||||||
$defined(icon, 'icon is not defined');
|
$defined(icon, 'icon is not defined');
|
||||||
|
|
||||||
// Order could have change, need to re-add all.
|
// Order could have change, need to re-add all.
|
||||||
@ -113,7 +115,7 @@ class IconGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_findIconFromModel(iconModel) {
|
private _findIconFromModel(iconModel: FeatureModel) {
|
||||||
let result = null;
|
let result = null;
|
||||||
|
|
||||||
this._icons.forEach((icon) => {
|
this._icons.forEach((icon) => {
|
||||||
@ -132,14 +134,14 @@ class IconGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
removeIconByModel(featureModel) {
|
removeIconByModel(featureModel: FeatureModel) {
|
||||||
$assert(featureModel, 'featureModel can not be null');
|
$assert(featureModel, 'featureModel can not be null');
|
||||||
|
|
||||||
const icon = this._findIconFromModel(featureModel);
|
const icon = this._findIconFromModel(featureModel);
|
||||||
this._removeIcon(icon);
|
this._removeIcon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeIcon(icon) {
|
private _removeIcon(icon: Icon) {
|
||||||
$assert(icon, 'icon can not be null');
|
$assert(icon, 'icon can not be null');
|
||||||
|
|
||||||
this._removeTip.close(0);
|
this._removeTip.close(0);
|
||||||
@ -156,11 +158,11 @@ class IconGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
moveToFront() {
|
moveToFront(): void {
|
||||||
this._group.moveToFront();
|
this._group.moveToFront();
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerListeners() {
|
private _registerListeners() {
|
||||||
this._group.addEvent('click', (event) => {
|
this._group.addEvent('click', (event) => {
|
||||||
// Avoid node creation ...
|
// Avoid node creation ...
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -171,21 +173,23 @@ class IconGroup {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_resize(iconsLength) {
|
private _resize(iconsLength: number) {
|
||||||
this._group.setSize(iconsLength * this._iconSize.width, this._iconSize.height);
|
this._group.setSize(iconsLength * this._iconSize.width, this._iconSize.height);
|
||||||
|
|
||||||
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
|
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
|
||||||
this._group.setCoordSize(iconsLength * iconSize, iconSize);
|
this._group.setCoordSize(iconsLength * iconSize, iconSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
_positionIcon(icon, order) {
|
private _positionIcon(icon: Icon, order: number) {
|
||||||
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
|
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2;
|
||||||
icon.getImage().setPosition(
|
icon.getImage().setPosition(
|
||||||
iconSize * order + IconGroup.ICON_PADDING,
|
iconSize * order + IconGroup.ICON_PADDING,
|
||||||
IconGroup.ICON_PADDING,
|
IconGroup.ICON_PADDING,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ICON_PADDING = 5;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IconGroup.ICON_PADDING = 5;
|
|
||||||
export default IconGroup;
|
export default IconGroup;
|
@ -93,7 +93,7 @@ class ImageIcon extends Icon {
|
|||||||
|
|
||||||
static _getFamilyIcons(iconId) {
|
static _getFamilyIcons(iconId) {
|
||||||
$assert(iconId != null, 'id must not be null');
|
$assert(iconId != null, 'id must not be null');
|
||||||
$assert(iconId.indexOf('_') !== -1, "Invalid icon id (it must contain '_')");
|
$assert(iconId.indexOf('_') !== -1, `Invalid icon id (it must contain '_'). Id: ${iconId}`);
|
||||||
|
|
||||||
let result = null;
|
let result = null;
|
||||||
for (let i = 0; i < ImageIcon.prototype.ICON_FAMILIES.length; i++) {
|
for (let i = 0; i < ImageIcon.prototype.ICON_FAMILIES.length; i++) {
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
class Keyboard {
|
class Keyboard {
|
||||||
addShortcut(shortcuts, callback) {
|
addShortcut(shortcuts: string[] | string, callback) {
|
||||||
const shortcutsArray = Array.isArray(shortcuts) ? shortcuts : [shortcuts];
|
const shortcutsArray = Array.isArray(shortcuts) ? shortcuts : [shortcuts];
|
||||||
shortcutsArray.forEach((shortcut) => {
|
shortcutsArray.forEach((shortcut) => {
|
||||||
$(document).bind('keydown', shortcut, callback);
|
$(document).bind('keydown', shortcut, callback);
|
||||||
|
@ -20,9 +20,17 @@ import $ from 'jquery';
|
|||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
import LinkIconTooltip from './widget/LinkIconTooltip';
|
import LinkIconTooltip from './widget/LinkIconTooltip';
|
||||||
import LinksImage from '../../assets/icons/links.svg';
|
import LinksImage from '../../assets/icons/links.svg';
|
||||||
|
import LinkModel from './model/LinkModel';
|
||||||
|
import Topic from './Topic';
|
||||||
|
import FeatureModel from './model/FeatureModel';
|
||||||
|
|
||||||
class LinkIcon extends Icon {
|
class LinkIcon extends Icon {
|
||||||
constructor(topic, linkModel, readOnly) {
|
private _linksModel: FeatureModel;
|
||||||
|
private _topic: Topic;
|
||||||
|
private _readOnly: boolean;
|
||||||
|
private _tip: LinkIconTooltip;
|
||||||
|
|
||||||
|
constructor(topic: Topic, linkModel: LinkModel, readOnly: boolean) {
|
||||||
$assert(topic, 'topic can not be null');
|
$assert(topic, 'topic can not be null');
|
||||||
$assert(linkModel, 'linkModel can not be null');
|
$assert(linkModel, 'linkModel can not be null');
|
||||||
|
|
||||||
@ -34,7 +42,7 @@ class LinkIcon extends Icon {
|
|||||||
this._registerEvents();
|
this._registerEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerEvents() {
|
private _registerEvents() {
|
||||||
this._image.setCursor('pointer');
|
this._image.setCursor('pointer');
|
||||||
this._tip = new LinkIconTooltip(this);
|
this._tip = new LinkIconTooltip(this);
|
||||||
|
|
||||||
@ -62,10 +70,11 @@ class LinkIcon extends Icon {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getModel() {
|
getModel(): FeatureModel {
|
||||||
return this._linksModel;
|
return this._linksModel;
|
||||||
}
|
}
|
||||||
|
static IMAGE_URL = LinksImage;
|
||||||
|
|
||||||
}
|
}
|
||||||
LinkIcon.IMAGE_URL = LinksImage;
|
|
||||||
|
|
||||||
export default LinkIcon;
|
export default LinkIcon;
|
@ -16,7 +16,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { Mindmap } from '..';
|
|
||||||
import PersistenceManager from './PersistenceManager';
|
import PersistenceManager from './PersistenceManager';
|
||||||
|
|
||||||
class LocalStorageManager extends PersistenceManager {
|
class LocalStorageManager extends PersistenceManager {
|
||||||
@ -30,7 +29,8 @@ class LocalStorageManager extends PersistenceManager {
|
|||||||
this.forceLoad = forceLoad;
|
this.forceLoad = forceLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMapXml(mapId: string, mapXml: string) {
|
saveMapXml(mapId: string, mapDoc: Document): void {
|
||||||
|
const mapXml = new XMLSerializer().serializeToString(mapDoc);
|
||||||
localStorage.setItem(`${mapId}-xml`, mapXml);
|
localStorage.setItem(`${mapId}-xml`, mapXml);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ class LocalStorageManager extends PersistenceManager {
|
|||||||
if (xml == null || this.forceLoad) {
|
if (xml == null || this.forceLoad) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: this.documentUrl.replace('{id}', mapId),
|
url: this.documentUrl.replace('{id}', mapId),
|
||||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml' },
|
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml', 'X-CSRF-Token': this.getCSRFToken() },
|
||||||
type: 'get',
|
type: 'get',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
async: false,
|
async: false,
|
||||||
@ -63,7 +63,7 @@ class LocalStorageManager extends PersistenceManager {
|
|||||||
return $.parseXML(xml);
|
return $.parseXML(xml);
|
||||||
}
|
}
|
||||||
|
|
||||||
unlockMap(mindmap: Mindmap) {
|
unlockMap(): void {
|
||||||
// Ignore, no implementation required ...
|
// Ignore, no implementation required ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,6 @@ import SizeType from './SizeType';
|
|||||||
class MainTopic extends Topic {
|
class MainTopic extends Topic {
|
||||||
private INNER_RECT_ATTRIBUTES: { stroke: string; };
|
private INNER_RECT_ATTRIBUTES: { stroke: string; };
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends mindplot.Topic
|
|
||||||
* @constructs
|
|
||||||
* @param model
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
constructor(model: NodeModel, options) {
|
constructor(model: NodeModel, options) {
|
||||||
super(model, options);
|
super(model, options);
|
||||||
this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' };
|
this.INNER_RECT_ATTRIBUTES = { stroke: '0.5 solid #009900' };
|
||||||
@ -88,7 +82,6 @@ class MainTopic extends Topic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
|
||||||
disconnect(workspace: Workspace) {
|
disconnect(workspace: Workspace) {
|
||||||
super.disconnect(workspace);
|
super.disconnect(workspace);
|
||||||
const model = this.getModel();
|
const model = this.getModel();
|
||||||
|
48
packages/mindplot/src/components/MockPersistenceManager.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright [2022] [wisemapping]
|
||||||
|
*
|
||||||
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
||||||
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
||||||
|
* "powered by wisemapping" text requirement on every single page;
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the license at
|
||||||
|
*
|
||||||
|
* http://www.wisemapping.org/license
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import $ from 'jquery';
|
||||||
|
import { $assert } from '@wisemapping/core-js';
|
||||||
|
import PersistenceManager from './PersistenceManager';
|
||||||
|
|
||||||
|
class MockPersistenceManager extends PersistenceManager {
|
||||||
|
private exampleMap: string;
|
||||||
|
|
||||||
|
constructor(exampleMapAsXml: string) {
|
||||||
|
super();
|
||||||
|
$assert(exampleMapAsXml, 'The test map must be set');
|
||||||
|
this.exampleMap = exampleMapAsXml;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveMapXml(): void {
|
||||||
|
// Ignore, no implementation required ...
|
||||||
|
}
|
||||||
|
|
||||||
|
discardChanges() {
|
||||||
|
// Ignore, no implementation required ...
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMapDom() {
|
||||||
|
return $.parseXML(this.exampleMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockMap(): void {
|
||||||
|
// Ignore, no implementation required ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MockPersistenceManager;
|
@ -21,58 +21,56 @@ import $ from 'jquery';
|
|||||||
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
|
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
|
||||||
import Events from './Events';
|
import Events from './Events';
|
||||||
import ActionDispatcher from './ActionDispatcher';
|
import ActionDispatcher from './ActionDispatcher';
|
||||||
|
import Topic from './Topic';
|
||||||
|
|
||||||
initHotKeyPluggin($);
|
initHotKeyPluggin($);
|
||||||
|
|
||||||
class MultilineTextEditor extends Events {
|
class MultilineTextEditor extends Events {
|
||||||
|
private _topic: Topic;
|
||||||
|
|
||||||
|
private _containerElem: JQuery;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._topic = null;
|
this._topic = null;
|
||||||
this._timeoutId = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static _buildEditor() {
|
private static _buildEditor() {
|
||||||
const result = $('<div></div>')
|
const result = $('<div></div>')
|
||||||
.attr('id', 'textContainer')
|
.attr('id', 'textContainer')
|
||||||
.css({
|
.css({
|
||||||
display: 'none',
|
display: 'none',
|
||||||
zIndex: '8',
|
zIndex: '8',
|
||||||
overflow: 'hidden',
|
|
||||||
border: '0 none',
|
border: '0 none',
|
||||||
});
|
});
|
||||||
|
|
||||||
const textareaElem = $('<textarea tabindex="-1" value="" wrap="off" ></textarea>')
|
const textareaElem = $('<textarea tabindex="-1" value="" wrap="off" ></textarea>')
|
||||||
.css({
|
.css({
|
||||||
border: '1px gray dashed',
|
border: '1px gray dashed',
|
||||||
background: 'rgba(98, 135, 167, .3)',
|
background: 'rgba(98, 135, 167, .4)',
|
||||||
outline: '0 none',
|
outline: '0 none',
|
||||||
resize: 'none',
|
resize: 'none',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
padding: '2px 0px 2px 4px',
|
||||||
});
|
});
|
||||||
|
|
||||||
result.append(textareaElem);
|
result.append(textareaElem);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerEvents(containerElem) {
|
private _registerEvents(containerElem: JQuery) {
|
||||||
const textareaElem = this._getTextareaElem();
|
const textareaElem = this._getTextareaElem();
|
||||||
const me = this;
|
textareaElem.on('keydown', (event) => {
|
||||||
let start;
|
const j: any = $;
|
||||||
let end;
|
switch (j.hotkeys.specialKeys[event.keyCode]) {
|
||||||
textareaElem.on('keydown', function keydown(event) {
|
|
||||||
switch ($.hotkeys.specialKeys[event.keyCode]) {
|
|
||||||
case 'esc':
|
case 'esc':
|
||||||
me.close(false);
|
this.close(false);
|
||||||
break;
|
break;
|
||||||
case 'enter':
|
case 'enter': {
|
||||||
if (event.metaKey || event.ctrlKey) {
|
if (event.metaKey || event.ctrlKey) {
|
||||||
// Add return ...
|
// Add return ...
|
||||||
const text = textareaElem.val();
|
const text = this._getTextAreaText();
|
||||||
let cursorPosition = text.length;
|
const cursorPosition = text.length;
|
||||||
if (textareaElem.selectionStart) {
|
|
||||||
cursorPosition = textareaElem.selectionStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
const head = text.substring(0, cursorPosition);
|
const head = text.substring(0, cursorPosition);
|
||||||
let tail = '';
|
let tail = '';
|
||||||
if (cursorPosition < text.length) {
|
if (cursorPosition < text.length) {
|
||||||
@ -80,31 +78,12 @@ class MultilineTextEditor extends Events {
|
|||||||
}
|
}
|
||||||
textareaElem.val(`${head}\n${tail}`);
|
textareaElem.val(`${head}\n${tail}`);
|
||||||
|
|
||||||
// Position cursor ...
|
|
||||||
if (textareaElem[0].setSelectionRange) {
|
|
||||||
textareaElem.focus();
|
textareaElem.focus();
|
||||||
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
|
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
|
||||||
} else if (textareaElem.createTextRange) {
|
|
||||||
const range = textareaElem.createTextRange();
|
|
||||||
range.moveStart('character', cursorPosition + 1);
|
|
||||||
range.select();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
me.close(true);
|
this.close(true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'tab': {
|
|
||||||
event.preventDefault();
|
|
||||||
start = $(this).get(0).selectionStart;
|
|
||||||
end = $(this).get(0).selectionEnd;
|
|
||||||
|
|
||||||
// set textarea value to: text before caret + tab + text after caret
|
|
||||||
$(this).val(`${$(this).val().substring(0, start)}\t${$(this).val().substring(end)}`);
|
|
||||||
|
|
||||||
// put caret at right position again
|
|
||||||
$(this).get(0).selectionEnd = start + 1;
|
|
||||||
$(this).get(0).selectionStart = $(this).get(0).selectionEnd;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// No actions...
|
// No actions...
|
||||||
@ -118,9 +97,9 @@ class MultilineTextEditor extends Events {
|
|||||||
});
|
});
|
||||||
|
|
||||||
textareaElem.on('keyup', (event) => {
|
textareaElem.on('keyup', (event) => {
|
||||||
const text = me._getTextareaElem().val();
|
const text = this._getTextareaElem().val();
|
||||||
me.fireEvent('input', [event, text]);
|
this.fireEvent('input', [event, text]);
|
||||||
me._adjustEditorSize();
|
this._adjustEditorSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the user clicks on the input, all event must be ignored ...
|
// If the user clicks on the input, all event must be ignored ...
|
||||||
@ -135,33 +114,33 @@ class MultilineTextEditor extends Events {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_adjustEditorSize() {
|
private _adjustEditorSize() {
|
||||||
if (this.isVisible()) {
|
if (this.isVisible()) {
|
||||||
const textElem = this._getTextareaElem();
|
const textElem = this._getTextareaElem();
|
||||||
|
|
||||||
const lines = textElem.val().split('\n');
|
const lines = this._getTextAreaText().split('\n');
|
||||||
let maxLineLength = 1;
|
let maxLineLength = 1;
|
||||||
lines.forEach((line) => {
|
lines.forEach((line: string) => {
|
||||||
if (maxLineLength < line.length) maxLineLength = line.length;
|
maxLineLength = Math.max(line.length, maxLineLength);
|
||||||
});
|
});
|
||||||
|
|
||||||
textElem.attr('cols', maxLineLength);
|
textElem.attr('cols', maxLineLength);
|
||||||
textElem.attr('rows', lines.length);
|
textElem.attr('rows', lines.length);
|
||||||
|
|
||||||
this._containerElem.css({
|
this._containerElem.css({
|
||||||
width: `${maxLineLength + 3}em`,
|
width: `${maxLineLength + 2}em`,
|
||||||
height: textElem.height(),
|
height: textElem.height(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isVisible() {
|
isVisible(): boolean {
|
||||||
return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
|
return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateModel() {
|
private _updateModel() {
|
||||||
if (this._topic.getText() !== this._getText()) {
|
if (this._topic.getText() !== this._getTextAreaText()) {
|
||||||
const text = this._getText();
|
const text = this._getTextAreaText();
|
||||||
const topicId = this._topic.getId();
|
const topicId = this._topic.getId();
|
||||||
|
|
||||||
const actionDispatcher = ActionDispatcher.getInstance();
|
const actionDispatcher = ActionDispatcher.getInstance();
|
||||||
@ -169,7 +148,7 @@ class MultilineTextEditor extends Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
show(topic, text) {
|
show(topic: Topic, text: string): void {
|
||||||
// Close a previous node editor if it's opened ...
|
// Close a previous node editor if it's opened ...
|
||||||
if (this._topic) {
|
if (this._topic) {
|
||||||
this.close(false);
|
this.close(false);
|
||||||
@ -187,7 +166,7 @@ class MultilineTextEditor extends Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_showEditor(defaultText) {
|
private _showEditor(defaultText: string) {
|
||||||
const topic = this._topic;
|
const topic = this._topic;
|
||||||
|
|
||||||
// Hide topic text ...
|
// Hide topic text ...
|
||||||
@ -199,32 +178,29 @@ class MultilineTextEditor extends Events {
|
|||||||
fontStyle.size = nodeText.getHtmlFontSize();
|
fontStyle.size = nodeText.getHtmlFontSize();
|
||||||
fontStyle.color = nodeText.getColor();
|
fontStyle.color = nodeText.getColor();
|
||||||
this._setStyle(fontStyle);
|
this._setStyle(fontStyle);
|
||||||
const me = this;
|
|
||||||
|
|
||||||
// Set editor's initial size
|
// Set editor's initial size
|
||||||
const displayFunc = function displayFunc() {
|
|
||||||
// Position the editor and set the size...
|
// Position the editor and set the size...
|
||||||
const textShape = topic.getTextShape();
|
const textShape = topic.getTextShape();
|
||||||
|
|
||||||
me._containerElem.css('display', 'block');
|
this._containerElem.css('display', 'block');
|
||||||
|
|
||||||
// FIXME: Im not sure if this is best way...
|
let { top, left } = textShape.getNativePosition();
|
||||||
const shapePosition = textShape.getNativePosition();
|
// Adjust padding top position ...
|
||||||
me._containerElem.offset(shapePosition);
|
top -= 4;
|
||||||
|
left -= 4;
|
||||||
|
this._containerElem.offset({ top, left });
|
||||||
|
|
||||||
// Set editor's initial text ...
|
// Set editor's initial text ...
|
||||||
const text = $defined(defaultText) ? defaultText : topic.getText();
|
const text = $defined(defaultText) ? defaultText : topic.getText();
|
||||||
me._setText(text);
|
this._setText(text);
|
||||||
|
|
||||||
// Set the element focus and select the current text ...
|
// Set the element focus and select the current text ...
|
||||||
const inputElem = me._getTextareaElem();
|
const inputElem = this._getTextareaElem();
|
||||||
me._positionCursor(inputElem, !$defined(defaultText));
|
this._positionCursor(inputElem, !$defined(defaultText));
|
||||||
};
|
|
||||||
|
|
||||||
this._timeoutId = setTimeout(() => displayFunc(), 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setStyle(fontStyle) {
|
private _setStyle(fontStyle) {
|
||||||
const inputField = this._getTextareaElem();
|
const inputField = this._getTextareaElem();
|
||||||
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
@ -252,65 +228,46 @@ class MultilineTextEditor extends Events {
|
|||||||
this._containerElem.css(style);
|
this._containerElem.css(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setText(text) {
|
private _setText(text: string): void {
|
||||||
const textareaElem = this._getTextareaElem();
|
const textareaElem = this._getTextareaElem();
|
||||||
textareaElem.val(text);
|
textareaElem.val(text);
|
||||||
this._adjustEditorSize();
|
this._adjustEditorSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
_getText() {
|
private _getTextAreaText(): string {
|
||||||
return this._getTextareaElem().val();
|
return this._getTextareaElem().val() as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTextareaElem() {
|
private _getTextareaElem(): JQuery<HTMLTextAreaElement> {
|
||||||
return this._containerElem.find('textarea');
|
return this._containerElem.find('textarea');
|
||||||
}
|
}
|
||||||
|
|
||||||
_positionCursor(textareaElem, selectText) {
|
private _positionCursor(textareaElem: JQuery<HTMLTextAreaElement>, selectText: boolean) {
|
||||||
textareaElem.focus();
|
textareaElem.focus();
|
||||||
const lengh = textareaElem.val().length;
|
const lengh = this._getTextAreaText().length;
|
||||||
if (selectText) {
|
if (selectText) {
|
||||||
// Mark text as selected ...
|
// Mark text as selected ...
|
||||||
if (textareaElem.createTextRange) {
|
|
||||||
const rang = textareaElem.createTextRange();
|
|
||||||
rang.select();
|
|
||||||
rang.move('character', lengh);
|
|
||||||
} else {
|
|
||||||
textareaElem[0].setSelectionRange(0, lengh);
|
textareaElem[0].setSelectionRange(0, lengh);
|
||||||
}
|
|
||||||
} else if (textareaElem.createTextRange) {
|
|
||||||
const range = textareaElem.createTextRange();
|
|
||||||
range.move('character', lengh);
|
|
||||||
} else {
|
} else {
|
||||||
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
textareaElem.selectionStart = lengh;
|
|
||||||
textareaElem.selectionEnd = lengh;
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
||||||
|
|
||||||
textareaElem.focus();
|
textareaElem.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(update) {
|
close(update: boolean): void {
|
||||||
if (this.isVisible() && this._topic) {
|
if (this.isVisible()) {
|
||||||
// Update changes ...
|
if (update) {
|
||||||
clearTimeout(this._timeoutId);
|
|
||||||
|
|
||||||
if (!$defined(update) || update) {
|
|
||||||
this._updateModel();
|
this._updateModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let make the visible text in the node visible again ...
|
|
||||||
this._topic.getTextShape().setVisibility(true);
|
|
||||||
|
|
||||||
// Remove it form the screen ...
|
// Remove it form the screen ...
|
||||||
this._containerElem.remove();
|
this._containerElem.remove();
|
||||||
this._containerElem = null;
|
this._containerElem = null;
|
||||||
this._timeoutId = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._topic) {
|
||||||
|
this._topic.getTextShape().setVisibility(true);
|
||||||
this._topic = null;
|
this._topic = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MultilineTextEditor;
|
export default MultilineTextEditor;
|
@ -16,14 +16,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { $assert } from '@wisemapping/core-js';
|
import { $assert } from '@wisemapping/core-js';
|
||||||
import { ElementClass } from '@wisemapping/web2d';
|
import { ElementClass, Point } from '@wisemapping/web2d';
|
||||||
import TopicConfig from './TopicConfig';
|
import TopicConfig from './TopicConfig';
|
||||||
import NodeModel from './model/NodeModel';
|
import NodeModel from './model/NodeModel';
|
||||||
import Workspace from './Workspace';
|
import Workspace from './Workspace';
|
||||||
import DragTopic from './DragTopic';
|
import DragTopic from './DragTopic';
|
||||||
import LayoutManager from './layout/LayoutManager';
|
import LayoutManager from './layout/LayoutManager';
|
||||||
import SizeType from './SizeType';
|
import SizeType from './SizeType';
|
||||||
import PositionType from './PositionType';
|
|
||||||
|
|
||||||
abstract class NodeGraph {
|
abstract class NodeGraph {
|
||||||
private _mouseEvents: boolean;
|
private _mouseEvents: boolean;
|
||||||
@ -105,7 +104,7 @@ abstract class NodeGraph {
|
|||||||
return this._size;
|
return this._size;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(size: SizeType, force?: boolean) {
|
setSize(size: SizeType) {
|
||||||
this._size.width = size.width;
|
this._size.width = size.width;
|
||||||
this._size.height = size.height;
|
this._size.height = size.height;
|
||||||
}
|
}
|
||||||
@ -168,7 +167,7 @@ abstract class NodeGraph {
|
|||||||
|
|
||||||
abstract _buildDragShape();
|
abstract _buildDragShape();
|
||||||
|
|
||||||
getPosition(): PositionType {
|
getPosition(): Point {
|
||||||
const model = this.getModel();
|
const model = this.getModel();
|
||||||
return model.getPosition();
|
return model.getPosition();
|
||||||
}
|
}
|
||||||
|
@ -21,9 +21,17 @@ import { $msg } from './Messages';
|
|||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
import FloatingTip from './widget/FloatingTip';
|
import FloatingTip from './widget/FloatingTip';
|
||||||
import NotesImage from '../../assets/icons/notes.svg';
|
import NotesImage from '../../assets/icons/notes.svg';
|
||||||
|
import Topic from './Topic';
|
||||||
|
import NoteModel from './model/NoteModel';
|
||||||
|
import FeatureModel from './model/FeatureModel';
|
||||||
|
|
||||||
class NoteIcon extends Icon {
|
class NoteIcon extends Icon {
|
||||||
constructor(topic, noteModel, readOnly) {
|
private _linksModel: NoteModel;
|
||||||
|
private _topic: Topic;
|
||||||
|
private _readOnly: boolean;
|
||||||
|
private _tip: FloatingTip;
|
||||||
|
|
||||||
|
constructor(topic: Topic, noteModel: NoteModel, readOnly: boolean) {
|
||||||
$assert(topic, 'topic can not be null');
|
$assert(topic, 'topic can not be null');
|
||||||
|
|
||||||
super(NoteIcon.IMAGE_URL);
|
super(NoteIcon.IMAGE_URL);
|
||||||
@ -34,7 +42,7 @@ class NoteIcon extends Icon {
|
|||||||
this._registerEvents();
|
this._registerEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerEvents() {
|
private _registerEvents(): void {
|
||||||
this._image.setCursor('pointer');
|
this._image.setCursor('pointer');
|
||||||
const me = this;
|
const me = this;
|
||||||
|
|
||||||
@ -58,7 +66,7 @@ class NoteIcon extends Icon {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildTooltipContent() {
|
private _buildTooltipContent(): JQuery {
|
||||||
if ($('body').find('#textPopoverNote').length === 1) {
|
if ($('body').find('#textPopoverNote').length === 1) {
|
||||||
const text = $('body').find('#textPopoverNote');
|
const text = $('body').find('#textPopoverNote');
|
||||||
text.text(this._linksModel.getText());
|
text.text(this._linksModel.getText());
|
||||||
@ -75,11 +83,12 @@ class NoteIcon extends Icon {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
getModel() {
|
getModel(): FeatureModel {
|
||||||
return this._linksModel;
|
return this._linksModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static IMAGE_URL = NotesImage;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteIcon.IMAGE_URL = NotesImage;
|
|
||||||
|
|
||||||
export default NoteIcon;
|
export default NoteIcon;
|
@ -20,11 +20,21 @@ import { $assert } from '@wisemapping/core-js';
|
|||||||
import { Mindmap } from '..';
|
import { Mindmap } from '..';
|
||||||
import XMLSerializerFactory from './persistence/XMLSerializerFactory';
|
import XMLSerializerFactory from './persistence/XMLSerializerFactory';
|
||||||
|
|
||||||
|
export type PersistenceError = {
|
||||||
|
severity: string;
|
||||||
|
message: string;
|
||||||
|
errorType?: 'session-expired' | 'bad-request' | 'generic';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PersistenceErrorCallback = (error: PersistenceError) => void;
|
||||||
|
|
||||||
abstract class PersistenceManager {
|
abstract class PersistenceManager {
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
static _instance: PersistenceManager;
|
static _instance: PersistenceManager;
|
||||||
|
|
||||||
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events, sync: boolean) {
|
private _errorHandlers: PersistenceErrorCallback[] = [];
|
||||||
|
|
||||||
|
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events?) {
|
||||||
$assert(mindmap, 'mindmap can not be null');
|
$assert(mindmap, 'mindmap can not be null');
|
||||||
$assert(editorProperties, 'editorProperties can not be null');
|
$assert(editorProperties, 'editorProperties can not be null');
|
||||||
|
|
||||||
@ -33,30 +43,55 @@ abstract class PersistenceManager {
|
|||||||
|
|
||||||
const serializer = XMLSerializerFactory.createInstanceFromMindmap(mindmap);
|
const serializer = XMLSerializerFactory.createInstanceFromMindmap(mindmap);
|
||||||
const domMap = serializer.toXML(mindmap);
|
const domMap = serializer.toXML(mindmap);
|
||||||
const mapXml = new XMLSerializer().serializeToString(domMap);
|
|
||||||
|
|
||||||
const pref = JSON.stringify(editorProperties);
|
const pref = JSON.stringify(editorProperties);
|
||||||
try {
|
try {
|
||||||
this.saveMapXml(mapId, mapXml, pref, saveHistory, events, sync);
|
this.saveMapXml(mapId, domMap, pref, saveHistory, events);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
events.onError(e);
|
events.onError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getCSRFToken(): string | null {
|
||||||
|
const meta = document.head.querySelector('meta[name="_csrf"]');
|
||||||
|
let result = null;
|
||||||
|
if (meta) {
|
||||||
|
result = meta.getAttribute('content');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
load(mapId: string) {
|
load(mapId: string) {
|
||||||
$assert(mapId, 'mapId can not be null');
|
$assert(mapId, 'mapId can not be null');
|
||||||
const domDocument = this.loadMapDom(mapId);
|
const domDocument = this.loadMapDom(mapId);
|
||||||
return PersistenceManager.loadFromDom(mapId, domDocument);
|
return PersistenceManager.loadFromDom(mapId, domDocument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
triggerError(error: PersistenceError) {
|
||||||
|
this._errorHandlers.forEach((handler) => handler(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
addErrorHandler(callback: PersistenceErrorCallback) {
|
||||||
|
this._errorHandlers.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeErrorHandler(callback?: PersistenceErrorCallback) {
|
||||||
|
if (!callback) {
|
||||||
|
this._errorHandlers.length = 0;
|
||||||
|
}
|
||||||
|
const index = this._errorHandlers.findIndex((handler) => handler === callback);
|
||||||
|
if (index !== -1) {
|
||||||
|
this._errorHandlers.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract discardChanges(mapId: string): void;
|
abstract discardChanges(mapId: string): void;
|
||||||
|
|
||||||
abstract loadMapDom(mapId: string): Document;
|
abstract loadMapDom(mapId: string): Document;
|
||||||
|
|
||||||
abstract saveMapXml(mapId: string, mapXml, pref, saveHistory, events, sync);
|
abstract saveMapXml(mapId: string, mapXml: Document, pref?, saveHistory?: boolean, events?);
|
||||||
|
|
||||||
abstract unlockMap(mindmap: Mindmap): void;
|
abstract unlockMap(mapId: string): void;
|
||||||
|
|
||||||
static init = (instance: PersistenceManager) => {
|
static init = (instance: PersistenceManager) => {
|
||||||
this._instance = instance;
|
this._instance = instance;
|
||||||
|
@ -20,7 +20,7 @@ import { Arrow, Point, ElementClass } from '@wisemapping/web2d';
|
|||||||
import ConnectionLine from './ConnectionLine';
|
import ConnectionLine from './ConnectionLine';
|
||||||
import ControlPoint from './ControlPoint';
|
import ControlPoint from './ControlPoint';
|
||||||
import RelationshipModel from './model/RelationshipModel';
|
import RelationshipModel from './model/RelationshipModel';
|
||||||
import NodeGraph from './NodeGraph';
|
import Topic from './Topic';
|
||||||
import Shape from './util/Shape';
|
import Shape from './util/Shape';
|
||||||
|
|
||||||
class Relationship extends ConnectionLine {
|
class Relationship extends ConnectionLine {
|
||||||
@ -38,11 +38,11 @@ class Relationship extends ConnectionLine {
|
|||||||
|
|
||||||
private _endArrow: Arrow;
|
private _endArrow: Arrow;
|
||||||
|
|
||||||
private _controlPointControllerListener: any;
|
private _controlPointControllerListener;
|
||||||
|
|
||||||
private _showStartArrow: Arrow;
|
private _showStartArrow: Arrow;
|
||||||
|
|
||||||
constructor(sourceNode: NodeGraph, targetNode: NodeGraph, model: RelationshipModel) {
|
constructor(sourceNode: Topic, targetNode: Topic, model: RelationshipModel) {
|
||||||
$assert(sourceNode, 'sourceNode can not be null');
|
$assert(sourceNode, 'sourceNode can not be null');
|
||||||
$assert(targetNode, 'targetNode can not be null');
|
$assert(targetNode, 'targetNode can not be null');
|
||||||
|
|
||||||
@ -349,7 +349,7 @@ class Relationship extends ConnectionLine {
|
|||||||
return this._model.getId();
|
return this._model.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(type: string, event: any): void {
|
fireEvent(type: string, event): void {
|
||||||
const elem = this._line2d;
|
const elem = this._line2d;
|
||||||
elem.trigger(type, event);
|
elem.trigger(type, event);
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ class RelationshipPivot {
|
|||||||
|
|
||||||
private _sourceTopic: Topic;
|
private _sourceTopic: Topic;
|
||||||
|
|
||||||
private _pivot: any;
|
private _pivot: CurvedLine;
|
||||||
|
|
||||||
private _startArrow: Arrow;
|
private _startArrow: Arrow;
|
||||||
|
|
||||||
|
@ -17,9 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
import { $assert } from '@wisemapping/core-js';
|
import { $assert } from '@wisemapping/core-js';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { Mindmap } from '..';
|
|
||||||
import { $msg } from './Messages';
|
import { $msg } from './Messages';
|
||||||
import PersistenceManager from './PersistenceManager';
|
import PersistenceManager, { PersistenceError } from './PersistenceManager';
|
||||||
|
|
||||||
class RESTPersistenceManager extends PersistenceManager {
|
class RESTPersistenceManager extends PersistenceManager {
|
||||||
private documentUrl: string;
|
private documentUrl: string;
|
||||||
@ -51,10 +50,10 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
this.session = options.session;
|
this.session = options.session;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events, sync: boolean): void {
|
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events): void {
|
||||||
const data = {
|
const data = {
|
||||||
id: mapId,
|
id: mapId,
|
||||||
xml: mapXml,
|
xml: new XMLSerializer().serializeToString(mapXml),
|
||||||
properties: pref,
|
properties: pref,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,73 +70,79 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
const persistence = this;
|
const persistence = this;
|
||||||
$.ajax({
|
fetch(
|
||||||
type: 'put',
|
`${this.documentUrl.replace('{id}', mapId)}?${query}`,
|
||||||
url: `${this.documentUrl.replace('{id}', mapId)}?${query}`,
|
{
|
||||||
dataType: 'json',
|
method: 'PUT',
|
||||||
data: JSON.stringify(data),
|
// Blob helps to resuce the memory on large payload.
|
||||||
contentType: 'application/json; charset=utf-8',
|
body: new Blob([JSON.stringify(data)], { type: 'text/plain' }),
|
||||||
async: !sync,
|
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
|
||||||
|
},
|
||||||
success(successData) {
|
).then(async (response: Response) => {
|
||||||
persistence.timestamp = successData;
|
if (response.ok) {
|
||||||
|
persistence.timestamp = await response.text();
|
||||||
events.onSuccess();
|
events.onSuccess();
|
||||||
},
|
} else {
|
||||||
complete() {
|
console.log(`Saving error: ${response.status}`);
|
||||||
// Clear event timeout ...
|
let userMsg;
|
||||||
if (persistence.clearTimeout) {
|
if (response.status === 405) {
|
||||||
clearTimeout(persistence.clearTimeout);
|
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED'), errorType: 'session-expired' };
|
||||||
}
|
} else {
|
||||||
persistence.onSave = false;
|
const responseText = await response.text();
|
||||||
},
|
const contentType = response.headers['Content-Type'];
|
||||||
error(xhr) {
|
|
||||||
const { responseText } = xhr;
|
|
||||||
let userMsg = { severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED') };
|
|
||||||
|
|
||||||
const contentType = xhr.getResponseHeader('Content-Type');
|
|
||||||
if (contentType != null && contentType.indexOf('application/json') !== -1) {
|
if (contentType != null && contentType.indexOf('application/json') !== -1) {
|
||||||
let serverMsg = null;
|
let serverMsg = null;
|
||||||
try {
|
try {
|
||||||
serverMsg = $.parseJSON(responseText);
|
serverMsg = JSON.parse(responseText);
|
||||||
serverMsg = serverMsg.globalSeverity ? serverMsg : null;
|
serverMsg = serverMsg.globalSeverity ? serverMsg : null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Message could not be decoded ...
|
// Message could not be decoded ...
|
||||||
}
|
}
|
||||||
userMsg = persistence._buildError(serverMsg);
|
userMsg = persistence._buildError(serverMsg);
|
||||||
} else if (this.status === 405) {
|
|
||||||
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED') };
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
this.triggerError(userMsg);
|
||||||
events.onError(userMsg);
|
events.onError(userMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear event timeout ...
|
||||||
|
if (persistence.clearTimeout) {
|
||||||
|
clearTimeout(persistence.clearTimeout);
|
||||||
|
}
|
||||||
|
persistence.onSave = false;
|
||||||
|
}).catch(() => {
|
||||||
|
const userMsg: PersistenceError = {
|
||||||
|
severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED'), errorType: 'generic',
|
||||||
|
};
|
||||||
|
this.triggerError(userMsg);
|
||||||
|
events.onError(userMsg);
|
||||||
|
|
||||||
|
// Clear event timeout ...
|
||||||
|
if (persistence.clearTimeout) {
|
||||||
|
clearTimeout(persistence.clearTimeout);
|
||||||
|
}
|
||||||
persistence.onSave = false;
|
persistence.onSave = false;
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
discardChanges(mapId: string) {
|
discardChanges(mapId: string) {
|
||||||
$.ajax({
|
fetch(this.revertUrl.replace('{id}', mapId),
|
||||||
url: this.revertUrl.replace('{id}', mapId),
|
{
|
||||||
async: false,
|
method: 'POST',
|
||||||
method: 'post',
|
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
|
||||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json' },
|
|
||||||
error(xhr, ajaxOptions, thrownError) {
|
|
||||||
console.error(`Request error => status:${xhr.status} ,thrownError: ${thrownError}`);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unlockMap(mindmap: Mindmap) {
|
unlockMap(mapId: string): void {
|
||||||
const mapId = mindmap.getId();
|
fetch(
|
||||||
$.ajax({
|
this.lockUrl.replace('{id}', mapId),
|
||||||
url: this.lockUrl.replace('{id}', mapId),
|
{
|
||||||
async: false,
|
method: 'PUT',
|
||||||
method: 'put',
|
headers: { 'Content-Type': 'text/plain', 'X-CSRF-Token': this.getCSRFToken() },
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
body: 'false',
|
||||||
data: 'false',
|
|
||||||
error(xhr, ajaxOptions, thrownError) {
|
|
||||||
console.error(`Request error => status:${xhr.status} ,thrownError: ${thrownError}`);
|
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _buildError(jsonSeverResponse) {
|
private _buildError(jsonSeverResponse) {
|
||||||
@ -155,13 +160,12 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadMapDom(mapId: string): Document {
|
loadMapDom(mapId: string): Document {
|
||||||
// Let's try to open one from the local directory ...
|
|
||||||
let xml: Document;
|
let xml: Document;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `${this.documentUrl.replace('{id}', mapId)}/xml`,
|
url: `${this.documentUrl.replace('{id}', mapId)}/xml`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
async: false,
|
async: false,
|
||||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml' },
|
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml', 'X-CSRF-Token': this.getCSRFToken() },
|
||||||
success(responseText) {
|
success(responseText) {
|
||||||
xml = responseText;
|
xml = responseText;
|
||||||
},
|
},
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
import { $assert } from '@wisemapping/core-js';
|
import { $assert } from '@wisemapping/core-js';
|
||||||
import { Point } from '@wisemapping/web2d';
|
import { Point } from '@wisemapping/web2d';
|
||||||
import Icon from './Icon';
|
|
||||||
import Topic from './Topic';
|
|
||||||
|
|
||||||
class ScreenManager {
|
class ScreenManager {
|
||||||
private _divContainer: JQuery;
|
private _divContainer: JQuery;
|
||||||
@ -84,59 +82,6 @@ class ScreenManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getElementPosition(elem: Topic) {
|
|
||||||
// Retrieve current element position.
|
|
||||||
const elementPosition = elem.getPosition();
|
|
||||||
let { x } = elementPosition;
|
|
||||||
let { y } = elementPosition;
|
|
||||||
|
|
||||||
// Add workspace offset.
|
|
||||||
x -= this._padding.x;
|
|
||||||
y -= this._padding.y;
|
|
||||||
|
|
||||||
// Scale coordinate in order to be relative to the workspace. That's coord/size;
|
|
||||||
x /= this._scale;
|
|
||||||
y /= this._scale;
|
|
||||||
|
|
||||||
// Remove decimal part..
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
|
|
||||||
getWorkspaceIconPosition(e: Icon) {
|
|
||||||
// Retrieve current icon position.
|
|
||||||
const image = e.getImage();
|
|
||||||
const elementPosition = image.getPosition();
|
|
||||||
const imageSize = e.getSize();
|
|
||||||
|
|
||||||
// Add group offset
|
|
||||||
const iconGroup = e.getGroup();
|
|
||||||
const group = iconGroup.getNativeElement();
|
|
||||||
const coordOrigin = group.getCoordOrigin();
|
|
||||||
const groupSize = group.getSize();
|
|
||||||
const coordSize = group.getCoordSize();
|
|
||||||
|
|
||||||
const scale = {
|
|
||||||
x: coordSize.width / parseInt(groupSize.width, 10),
|
|
||||||
y: coordSize.height / parseInt(groupSize.height, 10),
|
|
||||||
};
|
|
||||||
|
|
||||||
let x = (elementPosition.x - coordOrigin.x - parseInt(imageSize.width, 10) / 2) / scale.x;
|
|
||||||
let y = (elementPosition.y - coordOrigin.y - parseInt(imageSize.height, 10) / 2) / scale.y;
|
|
||||||
|
|
||||||
// Retrieve iconGroup Position
|
|
||||||
const groupPosition = iconGroup.getPosition();
|
|
||||||
x += groupPosition.x;
|
|
||||||
y += groupPosition.y;
|
|
||||||
|
|
||||||
// Retrieve topic Position
|
|
||||||
const topic = iconGroup.getTopic();
|
|
||||||
const topicPosition = this._getElementPosition(topic);
|
|
||||||
topicPosition.x -= parseInt(topic.getSize().width, 10) / 2;
|
|
||||||
|
|
||||||
// Remove decimal part..
|
|
||||||
return { x: x + topicPosition.x, y: y + topicPosition.y };
|
|
||||||
}
|
|
||||||
|
|
||||||
getWorkspaceMousePosition(event: MouseEvent) {
|
getWorkspaceMousePosition(event: MouseEvent) {
|
||||||
// Retrieve current mouse position.
|
// Retrieve current mouse position.
|
||||||
let x = event.clientX;
|
let x = event.clientX;
|
||||||
|
@ -79,11 +79,11 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
|||||||
$assert($defined(topicId), 'topicsId can not be null');
|
$assert($defined(topicId), 'topicsId can not be null');
|
||||||
$assert($defined(position), 'position can not be null');
|
$assert($defined(position), 'position can not be null');
|
||||||
|
|
||||||
const commandFunc = (topic, value) => {
|
const commandFunc = (topic: Topic, pos: Point) => {
|
||||||
const result = topic.getPosition();
|
const result = topic.getPosition();
|
||||||
EventBus.instance.fireEvent(EventBus.events.NodeMoveEvent, {
|
EventBus.instance.fireEvent(EventBus.events.NodeMoveEvent, {
|
||||||
node: topic.getModel(),
|
node: topic.getModel(),
|
||||||
position: value,
|
position: pos,
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@ -114,7 +114,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
|||||||
changeTextToTopic(topicsIds: number[], text: string) {
|
changeTextToTopic(topicsIds: number[], text: string) {
|
||||||
$assert($defined(topicsIds), 'topicsIds can not be null');
|
$assert($defined(topicsIds), 'topicsIds can not be null');
|
||||||
|
|
||||||
const commandFunc = (topic: Topic, value: object) => {
|
const commandFunc = (topic: Topic, value: string) => {
|
||||||
const result = topic.getText();
|
const result = topic.getText();
|
||||||
topic.setText(value);
|
topic.setText(value);
|
||||||
return result;
|
return result;
|
||||||
@ -163,7 +163,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
|||||||
$assert(topicsIds, 'topicIds can not be null');
|
$assert(topicsIds, 'topicIds can not be null');
|
||||||
$assert(color, 'color can not be null');
|
$assert(color, 'color can not be null');
|
||||||
|
|
||||||
const commandFunc = (topic, commandColor) => {
|
const commandFunc = (topic: Topic, commandColor: string) => {
|
||||||
const result = topic.getBackgroundColor();
|
const result = topic.getBackgroundColor();
|
||||||
topic.setBackgroundColor(commandColor);
|
topic.setBackgroundColor(commandColor);
|
||||||
return result;
|
return result;
|
||||||
@ -179,7 +179,7 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
|||||||
$assert(topicsIds, 'topicIds can not be null');
|
$assert(topicsIds, 'topicIds can not be null');
|
||||||
$assert(color, 'topicIds can not be null');
|
$assert(color, 'topicIds can not be null');
|
||||||
|
|
||||||
const commandFunc = (topic, commandColor) => {
|
const commandFunc = (topic: Topic, commandColor: string) => {
|
||||||
const result = topic.getBorderColor();
|
const result = topic.getBorderColor();
|
||||||
topic.setBorderColor(commandColor);
|
topic.setBorderColor(commandColor);
|
||||||
return result;
|
return result;
|
||||||
@ -195,11 +195,11 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
|||||||
$assert(topicsIds, 'topicIds can not be null');
|
$assert(topicsIds, 'topicIds can not be null');
|
||||||
$assert(size, 'size can not be null');
|
$assert(size, 'size can not be null');
|
||||||
|
|
||||||
const commandFunc = (topic, commandSize) => {
|
const commandFunc = (topic: Topic, commandSize: number) => {
|
||||||
const result = topic.getFontSize();
|
const result = topic.getFontSize();
|
||||||
topic.setFontSize(commandSize, true);
|
topic.setFontSize(commandSize, true);
|
||||||
|
|
||||||
topic._adjustShapes();
|
topic.adjustShapes();
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -212,9 +212,9 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
|||||||
$assert(topicsIds, 'topicsIds can not be null');
|
$assert(topicsIds, 'topicsIds can not be null');
|
||||||
$assert(shapeType, 'shapeType can not be null');
|
$assert(shapeType, 'shapeType can not be null');
|
||||||
|
|
||||||
const commandFunc = (topic, commandShapeType) => {
|
const commandFunc = (topic: Topic, commandShapeType: string) => {
|
||||||
const result = topic.getShapeType();
|
const result = topic.getShapeType();
|
||||||
topic.setShapeType(commandShapeType, true);
|
topic.setShapeType(commandShapeType);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -226,12 +226,12 @@ class StandaloneActionDispatcher extends ActionDispatcher {
|
|||||||
changeFontWeightToTopic(topicsIds: number[]) {
|
changeFontWeightToTopic(topicsIds: number[]) {
|
||||||
$assert(topicsIds, 'topicsIds can not be null');
|
$assert(topicsIds, 'topicsIds can not be null');
|
||||||
|
|
||||||
const commandFunc = (topic) => {
|
const commandFunc = (topic: Topic) => {
|
||||||
const result = topic.getFontWeight();
|
const result = topic.getFontWeight();
|
||||||
const weight = result === 'bold' ? 'normal' : 'bold';
|
const weight = result === 'bold' ? 'normal' : 'bold';
|
||||||
topic.setFontWeight(weight, true);
|
topic.setFontWeight(weight, true);
|
||||||
|
|
||||||
topic._adjustShapes();
|
topic.adjustShapes();
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ import LayoutManager from './layout/LayoutManager';
|
|||||||
import NoteModel from './model/NoteModel';
|
import NoteModel from './model/NoteModel';
|
||||||
import LinkModel from './model/LinkModel';
|
import LinkModel from './model/LinkModel';
|
||||||
import SizeType from './SizeType';
|
import SizeType from './SizeType';
|
||||||
|
import FeatureModel from './model/FeatureModel';
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
const ICON_SCALING_FACTOR = 1.3;
|
const ICON_SCALING_FACTOR = 1.3;
|
||||||
|
|
||||||
@ -104,15 +106,10 @@ abstract class Topic extends NodeGraph {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
setShapeType(type: string): void {
|
||||||
* @param {String} type the topic shape type
|
|
||||||
* @see {@link mindplot.model.INodeModel}
|
|
||||||
*/
|
|
||||||
setShapeType(type) {
|
|
||||||
this._setShapeType(type, true);
|
this._setShapeType(type, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return {mindplot.Topic} parent topic */
|
|
||||||
getParent(): Topic | null {
|
getParent(): Topic | null {
|
||||||
return this._parent;
|
return this._parent;
|
||||||
}
|
}
|
||||||
@ -161,8 +158,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return {String} topic shape type */
|
getShapeType(): string {
|
||||||
getShapeType() {
|
|
||||||
const model = this.getModel();
|
const model = this.getModel();
|
||||||
let result = model.getShapeType();
|
let result = model.getShapeType();
|
||||||
if (!$defined(result)) {
|
if (!$defined(result)) {
|
||||||
@ -171,7 +167,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _removeInnerShape() {
|
private _removeInnerShape(): ElementClass {
|
||||||
const group = this.get2DElement();
|
const group = this.get2DElement();
|
||||||
const innerShape = this.getInnerShape();
|
const innerShape = this.getInnerShape();
|
||||||
group.removeChild(innerShape);
|
group.removeChild(innerShape);
|
||||||
@ -308,7 +304,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
return this._text;
|
return this._text;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrBuildIconGroup() {
|
getOrBuildIconGroup(): Group {
|
||||||
if (!$defined(this._iconsGroup)) {
|
if (!$defined(this._iconsGroup)) {
|
||||||
this._iconsGroup = this._buildIconGroup();
|
this._iconsGroup = this._buildIconGroup();
|
||||||
const group = this.get2DElement();
|
const group = this.get2DElement();
|
||||||
@ -346,7 +342,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
* @param {mindplot.model.FeatureModel} featureModel
|
* @param {mindplot.model.FeatureModel} featureModel
|
||||||
* @return {mindplot.Icon} the icon corresponding to the feature model
|
* @return {mindplot.Icon} the icon corresponding to the feature model
|
||||||
*/
|
*/
|
||||||
addFeature(featureModel) {
|
addFeature(featureModel: FeatureModel): Icon {
|
||||||
const iconGroup = this.getOrBuildIconGroup();
|
const iconGroup = this.getOrBuildIconGroup();
|
||||||
this.closeEditors();
|
this.closeEditors();
|
||||||
|
|
||||||
@ -365,13 +361,13 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
findFeatureById(id) {
|
findFeatureById(id: number) {
|
||||||
const model = this.getModel();
|
const model = this.getModel();
|
||||||
return model.findFeatureById(id);
|
return model.findFeatureById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
removeFeature(featureModel) {
|
removeFeature(featureModel: FeatureModel): void {
|
||||||
$assert(featureModel, 'featureModel could not be null');
|
$assert(featureModel, 'featureModel could not be null');
|
||||||
|
|
||||||
// Removing the icon from MODEL
|
// Removing the icon from MODEL
|
||||||
@ -387,21 +383,21 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
addRelationship(relationship) {
|
addRelationship(relationship: Relationship) {
|
||||||
this._relationships.push(relationship);
|
this._relationships.push(relationship);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
deleteRelationship(relationship) {
|
deleteRelationship(relationship: Rect) {
|
||||||
this._relationships = this._relationships.filter((r) => r !== relationship);
|
this._relationships = this._relationships.filter((r) => r !== relationship);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
getRelationships() {
|
getRelationships(): Relationship[] {
|
||||||
return this._relationships;
|
return this._relationships;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildTextShape(readOnly): Text {
|
protected _buildTextShape(readOnly: boolean): Text {
|
||||||
const result = new Text();
|
const result = new Text();
|
||||||
const family = this.getFontFamily();
|
const family = this.getFontFamily();
|
||||||
const size = this.getFontSize();
|
const size = this.getFontSize();
|
||||||
@ -425,7 +421,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
setFontFamily(value, updateModel) {
|
setFontFamily(value: string, updateModel?: boolean) {
|
||||||
const textShape = this.getTextShape();
|
const textShape = this.getTextShape();
|
||||||
textShape.setFontName(value);
|
textShape.setFontName(value);
|
||||||
if ($defined(updateModel) && updateModel) {
|
if ($defined(updateModel) && updateModel) {
|
||||||
@ -436,7 +432,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
setFontSize(value, updateModel) {
|
setFontSize(value: number, updateModel?: boolean) {
|
||||||
const textShape = this.getTextShape();
|
const textShape = this.getTextShape();
|
||||||
textShape.setSize(value);
|
textShape.setSize(value);
|
||||||
|
|
||||||
@ -534,7 +530,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_setText(text, updateModel) {
|
_setText(text: string, updateModel: boolean) {
|
||||||
const textShape = this.getTextShape();
|
const textShape = this.getTextShape();
|
||||||
textShape.setText(text == null ? TopicStyle.defaultText(this) : text);
|
textShape.setText(text == null ? TopicStyle.defaultText(this) : text);
|
||||||
|
|
||||||
@ -545,7 +541,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
setText(text) {
|
setText(text: string) {
|
||||||
// Avoid empty nodes ...
|
// Avoid empty nodes ...
|
||||||
if (!text || $.trim(text).length === 0) {
|
if (!text || $.trim(text).length === 0) {
|
||||||
this._setText(null, true);
|
this._setText(null, true);
|
||||||
@ -557,7 +553,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
getText() {
|
getText(): string {
|
||||||
const model = this.getModel();
|
const model = this.getModel();
|
||||||
let result = model.getText();
|
let result = model.getText();
|
||||||
if (!$defined(result)) {
|
if (!$defined(result)) {
|
||||||
@ -567,11 +563,11 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
setBackgroundColor(color) {
|
setBackgroundColor(color: string) {
|
||||||
this._setBackgroundColor(color, true);
|
this._setBackgroundColor(color, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setBackgroundColor(color, updateModel) {
|
_setBackgroundColor(color: string, updateModel: boolean) {
|
||||||
const innerShape = this.getInnerShape();
|
const innerShape = this.getInnerShape();
|
||||||
innerShape.setFill(color);
|
innerShape.setFill(color);
|
||||||
|
|
||||||
@ -587,7 +583,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
getBackgroundColor() {
|
getBackgroundColor(): string {
|
||||||
const model = this.getModel();
|
const model = this.getModel();
|
||||||
let result = model.getBackgroundColor();
|
let result = model.getBackgroundColor();
|
||||||
if (!$defined(result)) {
|
if (!$defined(result)) {
|
||||||
@ -597,11 +593,11 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
setBorderColor(color) {
|
setBorderColor(color: string) {
|
||||||
this._setBorderColor(color, true);
|
this._setBorderColor(color, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setBorderColor(color, updateModel) {
|
_setBorderColor(color: string, updateModel: boolean) {
|
||||||
const innerShape = this.getInnerShape();
|
const innerShape = this.getInnerShape();
|
||||||
innerShape.setAttribute('strokeColor', color);
|
innerShape.setAttribute('strokeColor', color);
|
||||||
|
|
||||||
@ -1016,6 +1012,10 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide inner shape ...
|
||||||
|
this.getInnerShape().setVisibility(value);
|
||||||
|
|
||||||
|
// Hide text shape ...
|
||||||
const textShape = this.getTextShape();
|
const textShape = this.getTextShape();
|
||||||
textShape.setVisibility(this.getShapeType() !== TopicShape.IMAGE ? value : false);
|
textShape.setVisibility(this.getShapeType() !== TopicShape.IMAGE ? value : false);
|
||||||
}
|
}
|
||||||
@ -1058,7 +1058,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSize(size: SizeType, force: boolean): void {
|
setSize(size: SizeType, force?: boolean): void {
|
||||||
$assert(size, 'size can not be null');
|
$assert(size, 'size can not be null');
|
||||||
$assert($defined(size.width), 'size seem not to be a valid element');
|
$assert($defined(size.width), 'size seem not to be a valid element');
|
||||||
const roundedSize = {
|
const roundedSize = {
|
||||||
@ -1272,7 +1272,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If a drag node is create for it, let's hide the editor.
|
// If a drag node is create for it, let's hide the editor.
|
||||||
this._getTopicEventDispatcher().close();
|
this._getTopicEventDispatcher().close(false);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -1296,7 +1296,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
const topicHeight = Math.max(iconHeight, textHeight) + padding * 2;
|
const topicHeight = Math.max(iconHeight, textHeight) + padding * 2;
|
||||||
const textIconSpacing = Math.round(fontHeight / 4);
|
const textIconSpacing = Math.round(fontHeight / 4);
|
||||||
const iconGroupWith = iconGroup.getSize().width;
|
const iconGroupWith = iconGroup.getSize().width;
|
||||||
const topicWith = iconGroupWith + textIconSpacing + textWidth + padding * 2;
|
const topicWith = iconGroupWith + 2 * textIconSpacing + textWidth + padding * 2;
|
||||||
|
|
||||||
this.setSize({
|
this.setSize({
|
||||||
width: topicWith,
|
width: topicWith,
|
||||||
@ -1334,6 +1334,10 @@ abstract class Topic extends NodeGraph {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract workoutOutgoingConnectionPoint(position: Point): Point;
|
||||||
|
|
||||||
|
abstract workoutIncomingConnectionPoint(position: Point): Point;
|
||||||
|
|
||||||
isChildTopic(childTopic: Topic): boolean {
|
isChildTopic(childTopic: Topic): boolean {
|
||||||
let result = this.getId() === childTopic.getId();
|
let result = this.getId() === childTopic.getId();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -1349,7 +1353,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCentralTopic() {
|
isCentralTopic(): boolean {
|
||||||
return this.getModel().getType() === 'CentralTopic';
|
return this.getModel().getType() === 'CentralTopic';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { $assert } from '@wisemapping/core-js';
|
|||||||
import Events from './Events';
|
import Events from './Events';
|
||||||
import MultilineTextEditor from './MultilineTextEditor';
|
import MultilineTextEditor from './MultilineTextEditor';
|
||||||
import { TopicShape } from './model/INodeModel';
|
import { TopicShape } from './model/INodeModel';
|
||||||
|
import Topic from './Topic';
|
||||||
|
|
||||||
const TopicEvent = {
|
const TopicEvent = {
|
||||||
EDIT: 'editnode',
|
EDIT: 'editnode',
|
||||||
@ -26,30 +27,39 @@ const TopicEvent = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class TopicEventDispatcher extends Events {
|
class TopicEventDispatcher extends Events {
|
||||||
constructor(readOnly) {
|
private _readOnly: boolean;
|
||||||
|
|
||||||
|
private _activeEditor: MultilineTextEditor;
|
||||||
|
|
||||||
|
private _multilineEditor: MultilineTextEditor;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
static _instance: TopicEventDispatcher;
|
||||||
|
|
||||||
|
constructor(readOnly: boolean) {
|
||||||
super();
|
super();
|
||||||
this._readOnly = readOnly;
|
this._readOnly = readOnly;
|
||||||
this._activeEditor = null;
|
this._activeEditor = null;
|
||||||
this._multilineEditor = new MultilineTextEditor();
|
this._multilineEditor = new MultilineTextEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
close(update) {
|
close(update: boolean): void {
|
||||||
if (this.isVisible()) {
|
if (this.isVisible()) {
|
||||||
this._activeEditor.close(update);
|
this._activeEditor.close(update);
|
||||||
this._activeEditor = null;
|
this._activeEditor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
show(topic, options) {
|
show(topic: Topic, options?): void {
|
||||||
this.process(TopicEvent.EDIT, topic, options);
|
this.process(TopicEvent.EDIT, topic, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
process(eventType, topic, options) {
|
process(eventType: string, topic: Topic, options?): void {
|
||||||
$assert(eventType, 'eventType can not be null');
|
$assert(eventType, 'eventType can not be null');
|
||||||
|
|
||||||
// Close all previous open editor ....
|
// Close all previous open editor ....
|
||||||
if (this.isVisible()) {
|
if (this.isVisible()) {
|
||||||
this.close();
|
this.close(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the new editor ...
|
// Open the new editor ...
|
||||||
@ -66,20 +76,18 @@ class TopicEventDispatcher extends Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isVisible() {
|
isVisible(): boolean {
|
||||||
return this._activeEditor != null && this._activeEditor.isVisible();
|
return this._activeEditor != null && this._activeEditor.isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static configure(readOnly: boolean): void {
|
||||||
|
this._instance = new TopicEventDispatcher(readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInstance(): TopicEventDispatcher {
|
||||||
|
return this._instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TopicEventDispatcher._instance = null;
|
|
||||||
|
|
||||||
TopicEventDispatcher.configure = function configure(readOnly) {
|
|
||||||
this._instance = new TopicEventDispatcher(readOnly);
|
|
||||||
};
|
|
||||||
|
|
||||||
TopicEventDispatcher.getInstance = function getInstance() {
|
|
||||||
return this._instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { TopicEvent };
|
export { TopicEvent };
|
||||||
export default TopicEventDispatcher;
|
export default TopicEventDispatcher;
|
@ -18,17 +18,21 @@
|
|||||||
import { $assert, $defined } from '@wisemapping/core-js';
|
import { $assert, $defined } from '@wisemapping/core-js';
|
||||||
import Command from '../Command';
|
import Command from '../Command';
|
||||||
import CommandContext from '../CommandContext';
|
import CommandContext from '../CommandContext';
|
||||||
|
import NodeModel from '../model/NodeModel';
|
||||||
|
import RelationshipModel from '../model/RelationshipModel';
|
||||||
|
import Relationship from '../Relationship';
|
||||||
|
import Topic from '../Topic';
|
||||||
|
|
||||||
class DeleteCommand extends Command {
|
class DeleteCommand extends Command {
|
||||||
private _relIds: number[];
|
private _relIds: number[];
|
||||||
|
|
||||||
private _topicIds: number[];
|
private _topicIds: number[];
|
||||||
|
|
||||||
private _deletedTopicModels: any[];
|
private _deletedTopicModels: NodeModel[];
|
||||||
|
|
||||||
private _deletedRelModel: any[];
|
private _deletedRelModel: RelationshipModel[];
|
||||||
|
|
||||||
private _parentTopicIds: any[];
|
private _parentTopicIds: number[];
|
||||||
|
|
||||||
constructor(topicIds: number[], relIds: number[]) {
|
constructor(topicIds: number[], relIds: number[]) {
|
||||||
$assert($defined(relIds), 'topicIds can not be null');
|
$assert($defined(relIds), 'topicIds can not be null');
|
||||||
@ -101,11 +105,11 @@ class DeleteCommand extends Command {
|
|||||||
|
|
||||||
// Do they need to be connected ?
|
// Do they need to be connected ?
|
||||||
this._deletedTopicModels.forEach(((topicModel, index) => {
|
this._deletedTopicModels.forEach(((topicModel, index) => {
|
||||||
const topics = commandContext.findTopics(topicModel.getId());
|
const topics = commandContext.findTopics([topicModel.getId()]);
|
||||||
|
|
||||||
const parentId = this._parentTopicIds[index];
|
const parentId = this._parentTopicIds[index];
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
const parentTopics = commandContext.findTopics(parentId);
|
const parentTopics = commandContext.findTopics([parentId]);
|
||||||
commandContext.connect(topics[0], parentTopics[0]);
|
commandContext.connect(topics[0], parentTopics[0]);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -117,14 +121,14 @@ class DeleteCommand extends Command {
|
|||||||
|
|
||||||
// Finally display the topics ...
|
// Finally display the topics ...
|
||||||
this._deletedTopicModels.forEach((topicModel) => {
|
this._deletedTopicModels.forEach((topicModel) => {
|
||||||
const topics = commandContext.findTopics(topicModel.getId());
|
const topics = commandContext.findTopics([topicModel.getId()]);
|
||||||
topics[0].setBranchVisibility(true);
|
topics[0].setBranchVisibility(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus on last recovered topic ..
|
// Focus on last recovered topic ..
|
||||||
if (this._deletedTopicModels.length > 0) {
|
if (this._deletedTopicModels.length > 0) {
|
||||||
const firstTopic = this._deletedTopicModels[0];
|
const firstTopic = this._deletedTopicModels[0];
|
||||||
const topic = commandContext.findTopics(firstTopic.getId())[0];
|
const topic = commandContext.findTopics([firstTopic.getId()])[0];
|
||||||
topic.setOnFocus(true);
|
topic.setOnFocus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,11 +137,11 @@ class DeleteCommand extends Command {
|
|||||||
this._deletedRelModel = [];
|
this._deletedRelModel = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_filterChildren(topicIds, commandContext) {
|
private _filterChildren(topicIds: number[], commandContext: CommandContext) {
|
||||||
const topics = commandContext.findTopics(topicIds);
|
const topics = commandContext.findTopics(topicIds);
|
||||||
|
|
||||||
const result = [];
|
const result = [];
|
||||||
topics.forEach((topic) => {
|
topics.forEach((topic: Topic) => {
|
||||||
let parent = topic.getParent();
|
let parent = topic.getParent();
|
||||||
let found = false;
|
let found = false;
|
||||||
while (parent != null && !found) {
|
while (parent != null && !found) {
|
||||||
@ -156,13 +160,16 @@ class DeleteCommand extends Command {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_collectInDepthRelationships(topic) {
|
private _collectInDepthRelationships(topic: Topic): Relationship[] {
|
||||||
let result = [];
|
let result = [];
|
||||||
result = result.concat(topic.getRelationships());
|
result = result.concat(topic.getRelationships());
|
||||||
|
|
||||||
const children = topic.getChildren();
|
const children = topic.getChildren();
|
||||||
const rels = children.map(((t) => this._collectInDepthRelationships(t)));
|
const rels: (Relationship[])[] = children
|
||||||
result = result.concat(rels.flat());
|
.map(((t: Topic) => this._collectInDepthRelationships(t)));
|
||||||
|
|
||||||
|
// flatten and concact
|
||||||
|
result = result.concat(([].concat(...rels)));
|
||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
// Filter for unique ...
|
// Filter for unique ...
|
||||||
|
@ -62,7 +62,7 @@ class DragTopicCommand extends Command {
|
|||||||
const origPosition = topic.getPosition();
|
const origPosition = topic.getPosition();
|
||||||
|
|
||||||
// Disconnect topic ..
|
// Disconnect topic ..
|
||||||
if ($defined(origParentTopic) && origParentTopic.getId() !== this._parentId) {
|
if ($defined(origParentTopic)) {
|
||||||
commandContext.disconnect(topic);
|
commandContext.disconnect(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +76,6 @@ class DragTopicCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally, connect topic ...
|
// Finally, connect topic ...
|
||||||
if (!$defined(origParentTopic) || origParentTopic.getId() !== this._parentId) {
|
|
||||||
if ($defined(this._parentId)) {
|
if ($defined(this._parentId)) {
|
||||||
const parentTopic = commandContext.findTopics([this._parentId])[0];
|
const parentTopic = commandContext.findTopics([this._parentId])[0];
|
||||||
commandContext.connect(topic, parentTopic);
|
commandContext.connect(topic, parentTopic);
|
||||||
@ -87,7 +86,6 @@ class DragTopicCommand extends Command {
|
|||||||
if ($defined(origParentTopic)) {
|
if ($defined(origParentTopic)) {
|
||||||
this._parentId = origParentTopic.getId();
|
this._parentId = origParentTopic.getId();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
topic.setVisibility(true);
|
topic.setVisibility(true);
|
||||||
|
|
||||||
// Store for undo ...
|
// Store for undo ...
|
||||||
|
@ -20,18 +20,20 @@ import Command from '../Command';
|
|||||||
import CommandContext from '../CommandContext';
|
import CommandContext from '../CommandContext';
|
||||||
import Topic from '../Topic';
|
import Topic from '../Topic';
|
||||||
|
|
||||||
|
type CommandTypes = string | object | boolean | number;
|
||||||
|
|
||||||
class GenericFunctionCommand extends Command {
|
class GenericFunctionCommand extends Command {
|
||||||
private _value: string | object | boolean | number;
|
private _value: CommandTypes;
|
||||||
|
|
||||||
private _topicsId: number[];
|
private _topicsId: number[];
|
||||||
|
|
||||||
private _commandFunc: (topic: Topic, value: string | object | boolean | number) => string | object | boolean;
|
private _commandFunc: (topic: Topic, value: CommandTypes) => CommandTypes;
|
||||||
|
|
||||||
private _oldValues: any[];
|
private _oldValues: (CommandTypes)[];
|
||||||
|
|
||||||
private _applied: boolean;
|
private _applied: boolean;
|
||||||
|
|
||||||
constructor(commandFunc: (topic: Topic, value: string | object | boolean) => string | object | boolean, topicsIds: number[], value: string | object | boolean | number = undefined) {
|
constructor(commandFunc: (topic: Topic, value: CommandTypes) => CommandTypes, topicsIds: number[], value: CommandTypes = undefined) {
|
||||||
$assert(commandFunc, 'commandFunc must be defined');
|
$assert(commandFunc, 'commandFunc must be defined');
|
||||||
$assert($defined(topicsIds), 'topicsIds must be defined');
|
$assert($defined(topicsIds), 'topicsIds must be defined');
|
||||||
|
|
||||||
|
@ -15,26 +15,24 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Mindmap } from '../..';
|
|
||||||
import Exporter from './Exporter';
|
import Exporter from './Exporter';
|
||||||
import SVGExporter from './SVGExporter';
|
import SVGExporter from './SVGExporter';
|
||||||
/**
|
/**
|
||||||
* Based on https://mybyways.com/blog/convert-svg-to-png-using-your-browser
|
* Based on https://mybyways.com/blog/convert-svg-to-png-using-your-browser
|
||||||
*/
|
*/
|
||||||
class BinaryImageExporter extends Exporter {
|
class BinaryImageExporter extends Exporter {
|
||||||
svgElement: Element;
|
private svgElement: Element;
|
||||||
|
|
||||||
mindmap: Mindmap;
|
private width: number;
|
||||||
|
|
||||||
width: number;
|
private height: number;
|
||||||
|
|
||||||
height: number;
|
private adjustToFit: boolean;
|
||||||
|
|
||||||
constructor(mindmap: Mindmap, svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg') {
|
constructor(svgElement: Element, width: number, height: number, imgFormat: 'image/png' | 'image/jpeg', adjustToFit = true) {
|
||||||
super(imgFormat.split['/'][0], imgFormat);
|
super(imgFormat.split('/')[0], imgFormat);
|
||||||
this.svgElement = svgElement;
|
this.svgElement = svgElement;
|
||||||
this.mindmap = mindmap;
|
this.adjustToFit = adjustToFit;
|
||||||
|
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
@ -43,21 +41,35 @@ class BinaryImageExporter extends Exporter {
|
|||||||
throw new Error('Images can not be exporeted');
|
throw new Error('Images can not be exporeted');
|
||||||
}
|
}
|
||||||
|
|
||||||
async exportAndEndcode(): Promise<string> {
|
exportAndEncode(): Promise<string> {
|
||||||
const svgExporter = new SVGExporter(this.svgElement);
|
const svgExporter = new SVGExporter(this.svgElement, this.adjustToFit);
|
||||||
const svgUrl = await svgExporter.exportAndEncode();
|
const svgUrl = svgExporter.exportAndEncode();
|
||||||
|
return svgUrl.then((value: string) => {
|
||||||
// Get the device pixel ratio, falling back to 1. But, I will double the resolution to look nicer.
|
// Get the device pixel ratio, falling back to 1. But, I will double the resolution to look nicer.
|
||||||
const dpr = (window.devicePixelRatio || 1) * 2;
|
const dpr = (window.devicePixelRatio || 1) * 2;
|
||||||
|
|
||||||
// Create canvas ...
|
// Create canvas size ...
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.setAttribute('width', (this.width * dpr).toString());
|
let width: number;
|
||||||
canvas.setAttribute('height', (this.height * dpr).toString());
|
let height: number;
|
||||||
|
if (this.adjustToFit) {
|
||||||
|
// Size must match with SVG image size ...
|
||||||
|
const size = svgExporter.getImgSize();
|
||||||
|
width = (size.width * dpr);
|
||||||
|
height = (size.height * dpr);
|
||||||
|
} else {
|
||||||
|
// Use screensize as size ..
|
||||||
|
width = (this.width * dpr);
|
||||||
|
height = (this.height * dpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Export size: ${width}:${height}`);
|
||||||
|
canvas.setAttribute('width', width.toFixed(0));
|
||||||
|
canvas.setAttribute('height', height.toFixed(0));
|
||||||
|
|
||||||
// Render the image and wait for the response ...
|
// Render the image and wait for the response ...
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const result = new Promise<string>((resolve, reject) => {
|
const result = new Promise<string>((resolve) => {
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
// Scale for retina ...
|
// Scale for retina ...
|
||||||
@ -68,12 +80,13 @@ class BinaryImageExporter extends Exporter {
|
|||||||
.toDataURL(this.getContentType())
|
.toDataURL(this.getContentType())
|
||||||
.replace('image/png', 'octet/stream');
|
.replace('image/png', 'octet/stream');
|
||||||
|
|
||||||
URL.revokeObjectURL(svgUrl);
|
URL.revokeObjectURL(value);
|
||||||
resolve(imgDataUri);
|
resolve(imgDataUri);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
img.src = svgUrl;
|
img.src = value;
|
||||||
return result;
|
return result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default BinaryImageExporter;
|
export default BinaryImageExporter;
|
||||||
|
@ -15,26 +15,25 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Mindmap } from '../..';
|
|
||||||
import BinaryImageExporter from './BinaryImageExporter';
|
import BinaryImageExporter from './BinaryImageExporter';
|
||||||
import Exporter from './Exporter';
|
import Exporter from './Exporter';
|
||||||
import SVGExporter from './SVGExporter';
|
import SVGExporter from './SVGExporter';
|
||||||
|
|
||||||
type imageType = 'svg' | 'png' | 'jpg';
|
type imageType = 'svg' | 'png' | 'jpg';
|
||||||
class ImageExpoterFactory {
|
class ImageExpoterFactory {
|
||||||
static create(type: imageType, mindmap: Mindmap, svgElement: Element, width: number, height: number, isCenter = false): Exporter {
|
static create(type: imageType, svgElement: Element, width: number, height: number, adjustToFit = true): Exporter {
|
||||||
let result;
|
let result: Exporter;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'svg': {
|
case 'svg': {
|
||||||
result = new SVGExporter(svgElement);
|
result = new SVGExporter(svgElement, adjustToFit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'png': {
|
case 'png': {
|
||||||
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/png');
|
result = new BinaryImageExporter(svgElement, width, height, 'image/png', adjustToFit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'jpg': {
|
case 'jpg': {
|
||||||
result = new BinaryImageExporter(mindmap, svgElement, width, height, 'image/jpeg');
|
result = new BinaryImageExporter(svgElement, width, height, 'image/jpeg', adjustToFit);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -15,23 +15,33 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import SizeType from '../SizeType';
|
||||||
import Exporter from './Exporter';
|
import Exporter from './Exporter';
|
||||||
|
|
||||||
class SVGExporter extends Exporter {
|
class SVGExporter extends Exporter {
|
||||||
private svgElement: Element;
|
private svgElement: Element;
|
||||||
|
|
||||||
private prolog = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n';
|
private static prolog = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>\n';
|
||||||
|
|
||||||
constructor(svgElement: Element) {
|
private static regexpTranslate = /translate\((-?[0-9]+.[0-9]+),(-?[0-9]+.[0-9]+)\)/;
|
||||||
|
|
||||||
|
private static padding = 30;
|
||||||
|
|
||||||
|
private adjustToFit: boolean;
|
||||||
|
|
||||||
|
private static MAX_SUPPORTED_SIZE = 2500;
|
||||||
|
|
||||||
|
constructor(svgElement: Element, adjustToFit = true) {
|
||||||
super('svg', 'image/svg+xml');
|
super('svg', 'image/svg+xml');
|
||||||
this.svgElement = svgElement;
|
this.svgElement = svgElement;
|
||||||
|
this.adjustToFit = adjustToFit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export(): Promise<string> {
|
export(): Promise<string> {
|
||||||
// Replace all images for in-line images ...
|
// Replace all images for in-line images ...
|
||||||
let svgTxt: string = new XMLSerializer()
|
let svgTxt: string = new XMLSerializer()
|
||||||
.serializeToString(this.svgElement);
|
.serializeToString(this.svgElement);
|
||||||
svgTxt = this.prolog + svgTxt;
|
svgTxt = SVGExporter.prolog + svgTxt;
|
||||||
|
|
||||||
// Are namespace declared ?. Otherwise, force the declaration ...
|
// Are namespace declared ?. Otherwise, force the declaration ...
|
||||||
if (svgTxt.indexOf('xmlns:xlink=') === -1) {
|
if (svgTxt.indexOf('xmlns:xlink=') === -1) {
|
||||||
@ -39,15 +49,103 @@ class SVGExporter extends Exporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add white background. This is mainly for PNG export ...
|
// Add white background. This is mainly for PNG export ...
|
||||||
const svgDoc = SVGExporter.parseXMLString(svgTxt, 'application/xml');
|
let svgDoc = SVGExporter.parseXMLString(svgTxt, 'application/xml');
|
||||||
const svgElement = svgDoc.getElementsByTagName('svg')[0];
|
const svgElement = svgDoc.getElementsByTagName('svg')[0];
|
||||||
svgElement.setAttribute('style', 'background-color:white');
|
svgElement.setAttribute('style', 'background-color:white');
|
||||||
|
svgElement.setAttribute('focusable', 'false');
|
||||||
|
|
||||||
|
// Does need to be adjust ?.
|
||||||
|
if (this.adjustToFit) {
|
||||||
|
svgDoc = this._normalizeToFit(svgDoc);
|
||||||
|
}
|
||||||
|
|
||||||
const result = new XMLSerializer()
|
const result = new XMLSerializer()
|
||||||
.serializeToString(svgDoc);
|
.serializeToString(svgDoc);
|
||||||
|
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _calcualteDimensions(): { minX: number, maxX: number, minY: number, maxY: number } {
|
||||||
|
// Collect all group elements ...
|
||||||
|
const rectElems = Array.from(document.querySelectorAll('g>rect'));
|
||||||
|
const translates: SizeType[] = rectElems
|
||||||
|
.map((rect: Element) => {
|
||||||
|
const g = rect.parentElement;
|
||||||
|
const transformStr = g.getAttribute('transform');
|
||||||
|
|
||||||
|
// Looking to parse translate(220.00000,279.00000) scale(1.00000,1.00000)
|
||||||
|
const match = transformStr.match(SVGExporter.regexpTranslate);
|
||||||
|
let result: SizeType = { width: 0, height: 0 };
|
||||||
|
if (match !== null) {
|
||||||
|
result = { width: Number.parseFloat(match[1]), height: Number.parseFloat(match[2]) };
|
||||||
|
|
||||||
|
// Add rect size ...
|
||||||
|
if (result.width > 0) {
|
||||||
|
const rectWidth = Number.parseFloat(rect.getAttribute('width'));
|
||||||
|
result.width += rectWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.height > 0) {
|
||||||
|
const rectHeight = Number.parseFloat(rect.getAttribute('height'));
|
||||||
|
result.height += rectHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find max and mins ...
|
||||||
|
const widths = translates.map((t) => t.width).sort((a, b) => a - b);
|
||||||
|
const heights = translates.map((t) => t.height).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const minX = widths[0] - SVGExporter.padding;
|
||||||
|
const minY = heights[0] - SVGExporter.padding;
|
||||||
|
|
||||||
|
const maxX = widths[widths.length - 1] + SVGExporter.padding;
|
||||||
|
const maxY = heights[heights.length - 1] + SVGExporter.padding;
|
||||||
|
|
||||||
|
return {
|
||||||
|
minX, maxX, minY, maxY,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getImgSize(): SizeType {
|
||||||
|
const {
|
||||||
|
minX, maxX, minY, maxY,
|
||||||
|
} = this._calcualteDimensions();
|
||||||
|
|
||||||
|
let width: number = maxX + Math.abs(minX);
|
||||||
|
let height: number = maxY + Math.abs(minY);
|
||||||
|
|
||||||
|
// Prevents an image too big. Failures seen during export in couple of browsers
|
||||||
|
if (Math.max(width, height) > SVGExporter.MAX_SUPPORTED_SIZE) {
|
||||||
|
const scale = Math.max(width, height) / SVGExporter.MAX_SUPPORTED_SIZE;
|
||||||
|
width /= scale;
|
||||||
|
height /= scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _normalizeToFit(document: Document): Document {
|
||||||
|
const {
|
||||||
|
minX, maxX, minY, maxY,
|
||||||
|
} = this._calcualteDimensions();
|
||||||
|
const svgElem = document.firstChild as Element;
|
||||||
|
|
||||||
|
const width = maxX + Math.abs(minX);
|
||||||
|
const height = maxY + Math.abs(minY);
|
||||||
|
|
||||||
|
svgElem.setAttribute('viewBox', `${minX} ${minY} ${width} ${height}`);
|
||||||
|
svgElem.setAttribute('preserveAspectRatio', 'xMinYMin');
|
||||||
|
|
||||||
|
// Get image size ...
|
||||||
|
const imgSize = this.getImgSize();
|
||||||
|
svgElem.setAttribute('width', imgSize.width.toFixed(0));
|
||||||
|
svgElem.setAttribute('height', imgSize.height.toFixed(0));
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
private static parseXMLString = (xmlStr: string, mimeType: DOMParserSupportedType) => {
|
private static parseXMLString = (xmlStr: string, mimeType: DOMParserSupportedType) => {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const xmlDoc = parser.parseFromString(xmlStr, mimeType);
|
const xmlDoc = parser.parseFromString(xmlStr, mimeType);
|
||||||
@ -56,7 +154,7 @@ class SVGExporter extends Exporter {
|
|||||||
if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
|
if (xmlDoc.getElementsByTagName('parsererror').length > 0) {
|
||||||
const xmmStr = new XMLSerializer().serializeToString(xmlDoc);
|
const xmmStr = new XMLSerializer().serializeToString(xmlDoc);
|
||||||
console.log(xmmStr);
|
console.log(xmmStr);
|
||||||
throw new Error(`Unexpected error parsing: ${xmlStr}. Error: ${xmmStr}`);
|
throw new Error(`Unexpected error parsing: ${xmlStr}.Error: ${xmmStr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return xmlDoc;
|
return xmlDoc;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import { Mindmap } from '../..';
|
import { Mindmap } from '../..';
|
||||||
import INodeModel from '../model/INodeModel';
|
import INodeModel from '../model/INodeModel';
|
||||||
import LinkModel from '../model/LinkModel';
|
import LinkModel from '../model/LinkModel';
|
||||||
|
import NoteModel from '../model/NoteModel';
|
||||||
import Exporter from './Exporter';
|
import Exporter from './Exporter';
|
||||||
|
|
||||||
class TxtExporter extends Exporter {
|
class TxtExporter extends Exporter {
|
||||||
@ -32,27 +33,27 @@ class TxtExporter extends Exporter {
|
|||||||
const { mindmap } = this;
|
const { mindmap } = this;
|
||||||
|
|
||||||
const branches = mindmap.getBranches();
|
const branches = mindmap.getBranches();
|
||||||
const txtStr = this.traverseBranch('', branches);
|
const txtStr = this.traverseBranch('', '', branches);
|
||||||
return Promise.resolve(txtStr);
|
return Promise.resolve(txtStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private traverseBranch(prefix: string, branches: INodeModel[]) {
|
private traverseBranch(indent: string, prefix: string, branches: INodeModel[]) {
|
||||||
let result = '';
|
let result = '';
|
||||||
branches
|
branches
|
||||||
.filter((n) => n.getText() !== undefined)
|
|
||||||
.forEach((node, index) => {
|
.forEach((node, index) => {
|
||||||
result = `${result}${prefix}${index + 1} ${node.getText()}`;
|
result = `${result}${indent}${prefix}${index + 1} ${node.getText() !== undefined ? node.getText() : ''}`;
|
||||||
node.getFeatures().forEach((f) => {
|
node.getFeatures().forEach((f) => {
|
||||||
const type = f.getType();
|
const type = f.getType();
|
||||||
if (type === 'link') {
|
if (type === 'link') {
|
||||||
result = `${result} [link: ${(f as LinkModel).getUrl()}]`;
|
result = `${result}\n ${indent} [Link: ${(f as LinkModel).getUrl()}]`;
|
||||||
|
}
|
||||||
|
if (type === 'note') {
|
||||||
|
result = `${result}\n${indent} [Note: ${(f as NoteModel).getText()}]`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
result = `${result}\n`;
|
result = `${result}\n`;
|
||||||
|
|
||||||
if (node.getChildren().filter((n) => n.getText() !== undefined).length > 0) {
|
result += this.traverseBranch(`\t${indent}`, `${prefix}${index + 1}.`, node.getChildren());
|
||||||
result += this.traverseBranch(`\t${prefix}${index + 1}.`, node.getChildren());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import { $assert, $defined } from '@wisemapping/core-js';
|
import { $assert, $defined } from '@wisemapping/core-js';
|
||||||
import PositionType from '../PositionType';
|
import PositionType from '../PositionType';
|
||||||
import SizeType from '../SizeType';
|
import SizeType from '../SizeType';
|
||||||
|
import ChildrenSorterStrategy from './ChildrenSorterStrategy';
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
private _id: number;
|
private _id: number;
|
||||||
@ -25,14 +26,14 @@ class Node {
|
|||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
_parent: Node;
|
_parent: Node;
|
||||||
|
|
||||||
private _sorter: any;
|
private _sorter: ChildrenSorterStrategy;
|
||||||
|
|
||||||
private _properties;
|
private _properties;
|
||||||
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
_children: Node[];
|
_children: Node[];
|
||||||
|
|
||||||
constructor(id: number, size: SizeType, position, sorter) {
|
constructor(id: number, size: SizeType, position: PositionType, sorter: ChildrenSorterStrategy) {
|
||||||
$assert(typeof id === 'number' && Number.isFinite(id), 'id can not be null');
|
$assert(typeof id === 'number' && Number.isFinite(id), 'id can not be null');
|
||||||
$assert(size, 'size can not be null');
|
$assert(size, 'size can not be null');
|
||||||
$assert(position, 'position can not be null');
|
$assert(position, 'position can not be null');
|
||||||
|
@ -24,7 +24,6 @@ class RootedTreeSet {
|
|||||||
|
|
||||||
protected _children: Node[];
|
protected _children: Node[];
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._rootNodes = [];
|
this._rootNodes = [];
|
||||||
}
|
}
|
||||||
@ -56,10 +55,9 @@ class RootedTreeSet {
|
|||||||
*/
|
*/
|
||||||
add(node: Node) {
|
add(node: Node) {
|
||||||
$assert(node, 'node can not be null');
|
$assert(node, 'node can not be null');
|
||||||
$assert(
|
if (this.find(node.getId(), false)) {
|
||||||
!this.find(node.getId(), false),
|
throw new Error(`node already exits with this id. Id:${node.getId()}: ${this.dump()}`);
|
||||||
`node already exits with this id. Id:${node.getId()}: ${this.dump()}`,
|
}
|
||||||
);
|
|
||||||
$assert(!node._children, 'node already added');
|
$assert(!node._children, 'node already added');
|
||||||
this._rootNodes.push(this._decodate(node));
|
this._rootNodes.push(this._decodate(node));
|
||||||
}
|
}
|
||||||
@ -131,10 +129,11 @@ class RootedTreeSet {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$assert(
|
|
||||||
validate ? result : true,
|
if (validate && !result) {
|
||||||
`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`,
|
throw new Error(`node could not be found id:${id}\n,RootedTreeSet${this.dump()}`);
|
||||||
);
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +292,7 @@ class RootedTreeSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_plot(canvas, node: Node, root?) {
|
_plot(canvas, node: Node) {
|
||||||
const children = this.getChildren(node);
|
const children = this.getChildren(node);
|
||||||
const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2;
|
const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2;
|
||||||
const cy = node.getPosition().y + canvas.height / 2 - node.getSize().height / 2;
|
const cy = node.getPosition().y + canvas.height / 2 - node.getSize().height / 2;
|
||||||
|
@ -70,11 +70,6 @@ class NodeModel extends INodeModel {
|
|||||||
return this._features;
|
return this._features;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param feature
|
|
||||||
* @throws will throw an error if feature is null or undefined
|
|
||||||
* @throws will throw an error if the feature could not be removed
|
|
||||||
*/
|
|
||||||
removeFeature(feature: FeatureModel): void {
|
removeFeature(feature: FeatureModel): void {
|
||||||
$assert(feature, 'feature can not be null');
|
$assert(feature, 'feature can not be null');
|
||||||
const size = this._features.length;
|
const size = this._features.length;
|
||||||
@ -127,11 +122,8 @@ class NodeModel extends INodeModel {
|
|||||||
return this._properties[key];
|
return this._properties[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {mindplot.model.NodeModel} an identical clone of the NodeModel
|
|
||||||
*/
|
|
||||||
clone(): NodeModel {
|
clone(): NodeModel {
|
||||||
const result = new NodeModel(this.getType(), this._mindmap, -1);
|
const result = new NodeModel(this.getType(), this._mindmap);
|
||||||
result._children = this._children.map((node) => {
|
result._children = this._children.map((node) => {
|
||||||
const cnode = node.clone() as NodeModel;
|
const cnode = node.clone() as NodeModel;
|
||||||
cnode._parent = result;
|
cnode._parent = result;
|
||||||
@ -143,12 +135,8 @@ class NodeModel extends INodeModel {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to clone, assign new id to the elements ...
|
|
||||||
* @return {mindplot.model.NodeModel}
|
|
||||||
*/
|
|
||||||
deepCopy(): NodeModel {
|
deepCopy(): NodeModel {
|
||||||
const result = new NodeModel(this.getType(), this._mindmap, -1);
|
const result = new NodeModel(this.getType(), this._mindmap);
|
||||||
result._children = this._children.map((node) => {
|
result._children = this._children.map((node) => {
|
||||||
const cnode = (node as NodeModel).deepCopy();
|
const cnode = (node as NodeModel).deepCopy();
|
||||||
cnode._parent = result;
|
cnode._parent = result;
|
||||||
@ -163,20 +151,12 @@ class NodeModel extends INodeModel {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {mindplot.model.NodeModel} child
|
|
||||||
* @throws will throw an error if child is null, undefined or not a NodeModel object
|
|
||||||
*/
|
|
||||||
append(child: NodeModel): void {
|
append(child: NodeModel): void {
|
||||||
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object');
|
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object');
|
||||||
this._children.push(child);
|
this._children.push(child);
|
||||||
child._parent = this;
|
child._parent = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {mindplot.model.NodeModel} child
|
|
||||||
* @throws will throw an error if child is null, undefined or not a NodeModel object
|
|
||||||
*/
|
|
||||||
removeChild(child: NodeModel): void {
|
removeChild(child: NodeModel): void {
|
||||||
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.');
|
$assert(child && child.isNodeModel(), 'Only NodeModel can be appended to Mindmap object.');
|
||||||
this._children = this._children.filter((c) => c !== child);
|
this._children = this._children.filter((c) => c !== child);
|
||||||
|
@ -67,7 +67,7 @@ class RelationshipModel {
|
|||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLineType() {
|
getLineType(): number {
|
||||||
return this._lineType;
|
return this._lineType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import { Mindmap } from '../..';
|
|||||||
import NodeModel from '../model/NodeModel';
|
import NodeModel from '../model/NodeModel';
|
||||||
import ModelCodeName from './ModelCodeName';
|
import ModelCodeName from './ModelCodeName';
|
||||||
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
||||||
import XMLSerializerPela from './XMLSerializerPela';
|
import XMLSerializerPela from './XMLSerializerTango';
|
||||||
|
|
||||||
class Beta2PelaMigrator implements XMLMindmapSerializer {
|
class Beta2PelaMigrator implements XMLMindmapSerializer {
|
||||||
private _betaSerializer: XMLMindmapSerializer;
|
private _betaSerializer: XMLMindmapSerializer;
|
||||||
|
@ -72,7 +72,7 @@ class Pela2TangoMigrator implements XMLMindmapSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _fixPosition(mindmap: Mindmap) {
|
private _fixPosition(mindmap: Mindmap): void {
|
||||||
// Position was not required in previous versions. Try to synthesize one .
|
// Position was not required in previous versions. Try to synthesize one .
|
||||||
const centralNode = mindmap.getBranches()[0];
|
const centralNode = mindmap.getBranches()[0];
|
||||||
const children = centralNode.getChildren();
|
const children = centralNode.getChildren();
|
||||||
@ -83,7 +83,7 @@ class Pela2TangoMigrator implements XMLMindmapSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_fixNodePosition(node: NodeModel, parentPosition: {x:number, y:number}) {
|
private _fixNodePosition(node: NodeModel, parentPosition: { x: number, y: number }): void {
|
||||||
// Position was not required in previous versions. Try to synthesize one .
|
// Position was not required in previous versions. Try to synthesize one .
|
||||||
let position = node.getPosition();
|
let position = node.getPosition();
|
||||||
if (!position) {
|
if (!position) {
|
||||||
|
@ -20,7 +20,6 @@ import ModelCodeName from './ModelCodeName';
|
|||||||
import Beta2PelaMigrator from './Beta2PelaMigrator';
|
import Beta2PelaMigrator from './Beta2PelaMigrator';
|
||||||
import Pela2TangoMigrator from './Pela2TangoMigrator';
|
import Pela2TangoMigrator from './Pela2TangoMigrator';
|
||||||
import XMLSerializerBeta from './XMLSerializerBeta';
|
import XMLSerializerBeta from './XMLSerializerBeta';
|
||||||
import XMLSerializerPela from './XMLSerializerPela';
|
|
||||||
import XMLSerializerTango from './XMLSerializerTango';
|
import XMLSerializerTango from './XMLSerializerTango';
|
||||||
import Mindmap from '../model/Mindmap';
|
import Mindmap from '../model/Mindmap';
|
||||||
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
||||||
@ -35,7 +34,7 @@ const codeToSerializer: { codeName: string, serializer, migrator }[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
codeName: ModelCodeName.PELA,
|
codeName: ModelCodeName.PELA,
|
||||||
serializer: XMLSerializerPela,
|
serializer: XMLSerializerTango,
|
||||||
migrator: Beta2PelaMigrator,
|
migrator: Beta2PelaMigrator,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,517 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright [2021] [wisemapping]
|
|
||||||
*
|
|
||||||
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
|
||||||
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
|
||||||
* "powered by wisemapping" text requirement on every single page;
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the license at
|
|
||||||
*
|
|
||||||
* http://www.wisemapping.org/license
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
import { $assert, $defined, createDocument } from '@wisemapping/core-js';
|
|
||||||
import { Point } from '@wisemapping/web2d';
|
|
||||||
import Mindmap from '../model/Mindmap';
|
|
||||||
import { TopicShape } from '../model/INodeModel';
|
|
||||||
import ConnectionLine from '../ConnectionLine';
|
|
||||||
import FeatureModelFactory from '../model/FeatureModelFactory';
|
|
||||||
import NodeModel from '../model/NodeModel';
|
|
||||||
import RelationshipModel from '../model/RelationshipModel';
|
|
||||||
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
|
||||||
import FeatureType from '../model/FeatureType';
|
|
||||||
|
|
||||||
class XMLSerializerPela implements XMLMindmapSerializer {
|
|
||||||
private static MAP_ROOT_NODE = 'map';
|
|
||||||
|
|
||||||
private _idsMap: Record<number, Element>;
|
|
||||||
|
|
||||||
toXML(mindmap: Mindmap): Document {
|
|
||||||
$assert(mindmap, 'Can not save a null mindmap');
|
|
||||||
|
|
||||||
const document = createDocument();
|
|
||||||
|
|
||||||
// Store map attributes ...
|
|
||||||
const mapElem = document.createElement('map');
|
|
||||||
const name = mindmap.getId();
|
|
||||||
if ($defined(name)) {
|
|
||||||
mapElem.setAttribute('name', this._rmXmlInv(name));
|
|
||||||
}
|
|
||||||
const version = mindmap.getVersion();
|
|
||||||
if ($defined(version)) {
|
|
||||||
mapElem.setAttribute('version', version);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.appendChild(mapElem);
|
|
||||||
|
|
||||||
// Create branches ...
|
|
||||||
const topics = mindmap.getBranches();
|
|
||||||
topics.forEach((topic) => {
|
|
||||||
const topicDom = this._topicToXML(document, topic);
|
|
||||||
mapElem.appendChild(topicDom);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create Relationships
|
|
||||||
const relationships = mindmap.getRelationships();
|
|
||||||
relationships.forEach((relationship) => {
|
|
||||||
if (
|
|
||||||
mindmap.findNodeById(relationship.getFromNode()) !== null
|
|
||||||
&& mindmap.findNodeById(relationship.getToNode()) !== null
|
|
||||||
) {
|
|
||||||
// Isolated relationships are not persisted ....
|
|
||||||
const relationDom = XMLSerializerPela._relationshipToXML(document, relationship);
|
|
||||||
mapElem.appendChild(relationDom);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _topicToXML(document: Document, topic: NodeModel) {
|
|
||||||
const parentTopic = document.createElement('topic');
|
|
||||||
|
|
||||||
// Set topic attributes...
|
|
||||||
if (topic.getType() === 'CentralTopic') {
|
|
||||||
parentTopic.setAttribute('central', 'true');
|
|
||||||
} else {
|
|
||||||
const pos = topic.getPosition();
|
|
||||||
parentTopic.setAttribute('position', `${pos.x},${pos.y}`);
|
|
||||||
|
|
||||||
const order = topic.getOrder();
|
|
||||||
if (typeof order === 'number' && Number.isFinite(order)) {
|
|
||||||
parentTopic.setAttribute('order', order.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = topic.getText();
|
|
||||||
if ($defined(text)) {
|
|
||||||
this._noteTextToXML(document, parentTopic, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
const shape = topic.getShapeType();
|
|
||||||
if ($defined(shape)) {
|
|
||||||
parentTopic.setAttribute('shape', shape);
|
|
||||||
|
|
||||||
if (shape === TopicShape.IMAGE) {
|
|
||||||
const size = topic.getImageSize();
|
|
||||||
parentTopic.setAttribute(
|
|
||||||
'image',
|
|
||||||
`${size.width},${size.height}:${topic.getImageUrl()}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topic.areChildrenShrunken() && topic.getType() !== 'CentralTopic') {
|
|
||||||
parentTopic.setAttribute('shrink', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font properties ...
|
|
||||||
const id = topic.getId();
|
|
||||||
parentTopic.setAttribute('id', id.toString());
|
|
||||||
|
|
||||||
let font = '';
|
|
||||||
|
|
||||||
const fontFamily = topic.getFontFamily();
|
|
||||||
font += `${fontFamily || ''};`;
|
|
||||||
|
|
||||||
const fontSize = topic.getFontSize();
|
|
||||||
font += `${fontSize || ''};`;
|
|
||||||
|
|
||||||
const fontColor = topic.getFontColor();
|
|
||||||
font += `${fontColor || ''};`;
|
|
||||||
|
|
||||||
const fontWeight = topic.getFontWeight();
|
|
||||||
font += `${fontWeight || ''};`;
|
|
||||||
|
|
||||||
const fontStyle = topic.getFontStyle();
|
|
||||||
font += `${fontStyle || ''};`;
|
|
||||||
|
|
||||||
if (
|
|
||||||
$defined(fontFamily)
|
|
||||||
|| $defined(fontSize)
|
|
||||||
|| $defined(fontColor)
|
|
||||||
|| $defined(fontWeight)
|
|
||||||
|| $defined(fontStyle)
|
|
||||||
) {
|
|
||||||
parentTopic.setAttribute('fontStyle', font);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bgColor = topic.getBackgroundColor();
|
|
||||||
if ($defined(bgColor)) {
|
|
||||||
parentTopic.setAttribute('bgColor', bgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
const brColor = topic.getBorderColor();
|
|
||||||
if ($defined(brColor)) {
|
|
||||||
parentTopic.setAttribute('brColor', brColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = topic.getMetadata();
|
|
||||||
if ($defined(metadata)) {
|
|
||||||
parentTopic.setAttribute('metadata', metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize features ...
|
|
||||||
const features = topic.getFeatures();
|
|
||||||
features.forEach((feature) => {
|
|
||||||
const featureType = feature.getType();
|
|
||||||
const featureDom = document.createElement(featureType);
|
|
||||||
const attributes = feature.getAttributes();
|
|
||||||
|
|
||||||
const attributesKeys = Object.keys(attributes);
|
|
||||||
for (let attrIndex = 0; attrIndex < attributesKeys.length; attrIndex++) {
|
|
||||||
const key = attributesKeys[attrIndex];
|
|
||||||
const value = attributes[key];
|
|
||||||
if (key === 'text') {
|
|
||||||
const cdata = document.createCDATASection(this._rmXmlInv(value));
|
|
||||||
featureDom.appendChild(cdata);
|
|
||||||
} else {
|
|
||||||
featureDom.setAttribute(key, this._rmXmlInv(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parentTopic.appendChild(featureDom);
|
|
||||||
});
|
|
||||||
|
|
||||||
// CHILDREN TOPICS
|
|
||||||
const childTopics = topic.getChildren();
|
|
||||||
childTopics.forEach((childTopic) => {
|
|
||||||
const childDom = this._topicToXML(document, childTopic);
|
|
||||||
parentTopic.appendChild(childDom);
|
|
||||||
});
|
|
||||||
|
|
||||||
return parentTopic;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _noteTextToXML(document: Document, elem: Element, text: string) {
|
|
||||||
if (text.indexOf('\n') === -1) {
|
|
||||||
elem.setAttribute('text', this._rmXmlInv(text));
|
|
||||||
} else {
|
|
||||||
const textDom = document.createElement('text');
|
|
||||||
const cdata = document.createCDATASection(this._rmXmlInv(text));
|
|
||||||
textDom.appendChild(cdata);
|
|
||||||
elem.appendChild(textDom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static _relationshipToXML(document: Document, relationship: RelationshipModel) {
|
|
||||||
const result = document.createElement('relationship');
|
|
||||||
result.setAttribute('srcTopicId', relationship.getFromNode().toString());
|
|
||||||
result.setAttribute('destTopicId', relationship.getToNode().toString());
|
|
||||||
|
|
||||||
const lineType = relationship.getLineType();
|
|
||||||
result.setAttribute('lineType', lineType.toString());
|
|
||||||
if (lineType === ConnectionLine.CURVED || lineType === ConnectionLine.SIMPLE_CURVED) {
|
|
||||||
if ($defined(relationship.getSrcCtrlPoint())) {
|
|
||||||
const srcPoint = relationship.getSrcCtrlPoint();
|
|
||||||
result.setAttribute(
|
|
||||||
'srcCtrlPoint',
|
|
||||||
`${Math.round(srcPoint.x)},${Math.round(srcPoint.y)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if ($defined(relationship.getDestCtrlPoint())) {
|
|
||||||
const destPoint = relationship.getDestCtrlPoint();
|
|
||||||
result.setAttribute(
|
|
||||||
'destCtrlPoint',
|
|
||||||
`${Math.round(destPoint.x)},${Math.round(destPoint.y)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.setAttribute('endArrow', String(relationship.getEndArrow()));
|
|
||||||
result.setAttribute('startArrow', String(relationship.getStartArrow()));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dom
|
|
||||||
* @param mapId
|
|
||||||
* @throws will throw an error if dom is null or undefined
|
|
||||||
* @throws will throw an error if mapId is null or undefined
|
|
||||||
* @throws will throw an error if the document element is not consistent with a wisemap's root
|
|
||||||
* element
|
|
||||||
*/
|
|
||||||
loadFromDom(dom: Document, mapId: string) {
|
|
||||||
$assert(dom, 'dom can not be null');
|
|
||||||
$assert(mapId, 'mapId can not be null');
|
|
||||||
|
|
||||||
const rootElem = dom.documentElement;
|
|
||||||
|
|
||||||
// Is a wisemap?.
|
|
||||||
$assert(
|
|
||||||
rootElem.tagName === XMLSerializerPela.MAP_ROOT_NODE,
|
|
||||||
`This seem not to be a map document. Found tag: ${rootElem.tagName}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
this._idsMap = {};
|
|
||||||
// Start the loading process ...
|
|
||||||
const version = rootElem.getAttribute('version') || 'pela';
|
|
||||||
const mindmap = new Mindmap(mapId, version);
|
|
||||||
|
|
||||||
// Add all the topics nodes ...
|
|
||||||
const childNodes = Array.from(rootElem.childNodes);
|
|
||||||
const topicsNodes = childNodes
|
|
||||||
.filter(
|
|
||||||
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'topic',
|
|
||||||
)
|
|
||||||
.map((c) => c as Element);
|
|
||||||
topicsNodes.forEach((child) => {
|
|
||||||
const topic = this._deserializeNode(child, mindmap);
|
|
||||||
mindmap.addBranch(topic);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then all relationshops, they are connected to topics ...
|
|
||||||
const relationshipsNodes = childNodes
|
|
||||||
.filter(
|
|
||||||
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'relationship',
|
|
||||||
)
|
|
||||||
.map((c) => c as Element);
|
|
||||||
relationshipsNodes.forEach((child) => {
|
|
||||||
try {
|
|
||||||
const relationship = XMLSerializerPela._deserializeRelationship(child, mindmap);
|
|
||||||
mindmap.addRelationship(relationship);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up from the recursion ...
|
|
||||||
this._idsMap = null;
|
|
||||||
mindmap.setId(mapId);
|
|
||||||
return mindmap;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _deserializeNode(domElem: Element, mindmap: Mindmap) {
|
|
||||||
const type = domElem.getAttribute('central') != null ? 'CentralTopic' : 'MainTopic';
|
|
||||||
|
|
||||||
// Load attributes...
|
|
||||||
let id: number | null = null;
|
|
||||||
if ($defined(domElem.getAttribute('id'))) {
|
|
||||||
id = Number.parseInt(domElem.getAttribute('id'), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._idsMap[id]) {
|
|
||||||
id = null;
|
|
||||||
} else {
|
|
||||||
this._idsMap[id] = domElem;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topic = mindmap.createNode(type, id);
|
|
||||||
|
|
||||||
// Set text property is it;s defined...
|
|
||||||
const text = domElem.getAttribute('text');
|
|
||||||
if ($defined(text) && text) {
|
|
||||||
topic.setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fontStyle = domElem.getAttribute('fontStyle');
|
|
||||||
if ($defined(fontStyle) && fontStyle) {
|
|
||||||
const font = fontStyle.split(';');
|
|
||||||
|
|
||||||
if (font[0]) {
|
|
||||||
topic.setFontFamily(font[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font[1]) {
|
|
||||||
topic.setFontSize(Number.parseInt(font[1], 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font[2]) {
|
|
||||||
topic.setFontColor(font[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font[3]) {
|
|
||||||
topic.setFontWeight(font[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font[4]) {
|
|
||||||
topic.setFontStyle(font[4]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const shape = domElem.getAttribute('shape');
|
|
||||||
if ($defined(shape)) {
|
|
||||||
topic.setShapeType(shape);
|
|
||||||
|
|
||||||
if (shape === TopicShape.IMAGE) {
|
|
||||||
const image = domElem.getAttribute('image');
|
|
||||||
const size = image.substring(0, image.indexOf(':'));
|
|
||||||
const url = image.substring(image.indexOf(':') + 1, image.length);
|
|
||||||
topic.setImageUrl(url);
|
|
||||||
|
|
||||||
const split = size.split(',');
|
|
||||||
topic.setImageSize(Number.parseInt(split[0], 10), Number.parseInt(split[1], 10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bgColor = domElem.getAttribute('bgColor');
|
|
||||||
if ($defined(bgColor)) {
|
|
||||||
topic.setBackgroundColor(bgColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
const borderColor = domElem.getAttribute('brColor');
|
|
||||||
if ($defined(borderColor)) {
|
|
||||||
topic.setBorderColor(borderColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = domElem.getAttribute('order');
|
|
||||||
if ($defined(order) && order !== 'NaN') {
|
|
||||||
// Hack for broken maps ...
|
|
||||||
topic.setOrder(parseInt(order, 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
const isShrink = domElem.getAttribute('shrink');
|
|
||||||
// Hack: Some production maps has been stored with the central topic collapsed. This is a bug.
|
|
||||||
if ($defined(isShrink) && type !== 'CentralTopic') {
|
|
||||||
topic.setChildrenShrunken(Boolean(isShrink));
|
|
||||||
}
|
|
||||||
|
|
||||||
const position = domElem.getAttribute('position');
|
|
||||||
if ($defined(position)) {
|
|
||||||
const pos = position.split(',');
|
|
||||||
topic.setPosition(Number.parseInt(pos[0], 10), Number.parseInt(pos[1], 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = domElem.getAttribute('metadata');
|
|
||||||
if ($defined(metadata)) {
|
|
||||||
topic.setMetadata(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating icons and children nodes
|
|
||||||
const children = Array.from(domElem.childNodes);
|
|
||||||
children.forEach((child) => {
|
|
||||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
const elem = child as Element;
|
|
||||||
if (elem.tagName === 'topic') {
|
|
||||||
const childTopic = this._deserializeNode(elem, mindmap);
|
|
||||||
childTopic.connectTo(topic);
|
|
||||||
} else if (FeatureModelFactory.isSupported(elem.tagName)) {
|
|
||||||
// Load attributes ...
|
|
||||||
const namedNodeMap = elem.attributes;
|
|
||||||
const attributes: Record<string, string> = {};
|
|
||||||
|
|
||||||
for (let j = 0; j < namedNodeMap.length; j++) {
|
|
||||||
const attribute = namedNodeMap.item(j);
|
|
||||||
attributes[attribute.name] = attribute.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has text node ?.
|
|
||||||
const textAttr = XMLSerializerPela._deserializeTextAttr(elem);
|
|
||||||
if (textAttr) {
|
|
||||||
attributes.text = textAttr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new element ....
|
|
||||||
const featureType = elem.tagName as FeatureType;
|
|
||||||
const feature = FeatureModelFactory.createModel(featureType, attributes);
|
|
||||||
topic.addFeature(feature);
|
|
||||||
} else if (elem.tagName === 'text') {
|
|
||||||
const nodeText = XMLSerializerPela._deserializeNodeText(child);
|
|
||||||
topic.setText(nodeText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return topic;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _deserializeTextAttr(domElem: Element): string {
|
|
||||||
let value = domElem.getAttribute('text');
|
|
||||||
if (!$defined(value)) {
|
|
||||||
const children = domElem.childNodes;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const child = children[i];
|
|
||||||
if (child.nodeType === Node.CDATA_SECTION_NODE) {
|
|
||||||
value = child.nodeValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Notes must be decoded ...
|
|
||||||
value = unescape(value);
|
|
||||||
|
|
||||||
// Hack for empty nodes ...
|
|
||||||
if (value === '') {
|
|
||||||
value = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _deserializeNodeText(domElem) {
|
|
||||||
const children = domElem.childNodes;
|
|
||||||
let value = null;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const child = children[i];
|
|
||||||
if (child.nodeType === Node.CDATA_SECTION_NODE) {
|
|
||||||
value = child.nodeValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _deserializeRelationship(domElement, mindmap) {
|
|
||||||
const srcId = Number.parseInt(domElement.getAttribute('srcTopicId'), 10);
|
|
||||||
const destId = Number.parseInt(domElement.getAttribute('destTopicId'), 10);
|
|
||||||
const lineType = Number.parseInt(domElement.getAttribute('lineType'), 10);
|
|
||||||
const srcCtrlPoint = domElement.getAttribute('srcCtrlPoint');
|
|
||||||
const destCtrlPoint = domElement.getAttribute('destCtrlPoint');
|
|
||||||
|
|
||||||
// If for some reason a relationship lines has source and dest nodes the same, don't import it.
|
|
||||||
if (srcId === destId) {
|
|
||||||
throw new Error('Invalid relationship, dest and source are equals');
|
|
||||||
}
|
|
||||||
// Is the connections points valid ?. If it's not, do not load the relationship ...
|
|
||||||
if (mindmap.findNodeById(srcId) == null || mindmap.findNodeById(destId) == null) {
|
|
||||||
throw new Error('Transition could not created, missing node for relationship');
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = mindmap.createRelationship(srcId, destId);
|
|
||||||
model.setLineType(lineType);
|
|
||||||
if ($defined(srcCtrlPoint) && srcCtrlPoint !== '') {
|
|
||||||
model.setSrcCtrlPoint(Point.fromString(srcCtrlPoint));
|
|
||||||
}
|
|
||||||
if ($defined(destCtrlPoint) && destCtrlPoint !== '') {
|
|
||||||
model.setDestCtrlPoint(Point.fromString(destCtrlPoint));
|
|
||||||
}
|
|
||||||
model.setEndArrow('false');
|
|
||||||
model.setStartArrow('true');
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method ensures that the output String has only
|
|
||||||
* valid XML unicode characters as specified by the
|
|
||||||
* XML 1.0 standard. For reference, please see
|
|
||||||
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
|
|
||||||
* standard</a>. This method will return an empty
|
|
||||||
* String if the input is null or empty.
|
|
||||||
*
|
|
||||||
* @param in The String whose non-valid characters we want to remove.
|
|
||||||
* @return The in String, stripped of non-valid characters.
|
|
||||||
*/
|
|
||||||
protected _rmXmlInv(str: string) {
|
|
||||||
if (str == null || str === undefined) return null;
|
|
||||||
|
|
||||||
let result = '';
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
const c = str.charCodeAt(i);
|
|
||||||
if (
|
|
||||||
c === 0x9
|
|
||||||
|| c === 0xa
|
|
||||||
|| c === 0xd
|
|
||||||
|| (c >= 0x20 && c <= 0xd7ff)
|
|
||||||
|| (c >= 0xe000 && c <= 0xfffd)
|
|
||||||
|| (c >= 0x10000 && c <= 0x10ffff)
|
|
||||||
) {
|
|
||||||
result += str.charAt(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default XMLSerializerPela;
|
|
@ -15,13 +15,506 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import XMLSerializerPela from './XMLSerializerPela';
|
import { $assert, $defined, createDocument } from '@wisemapping/core-js';
|
||||||
|
import { Point } from '@wisemapping/web2d';
|
||||||
|
import Mindmap from '../model/Mindmap';
|
||||||
|
import { TopicShape } from '../model/INodeModel';
|
||||||
|
import ConnectionLine from '../ConnectionLine';
|
||||||
|
import FeatureModelFactory from '../model/FeatureModelFactory';
|
||||||
|
import NodeModel from '../model/NodeModel';
|
||||||
|
import RelationshipModel from '../model/RelationshipModel';
|
||||||
|
import XMLMindmapSerializer from './XMLMindmapSerializer';
|
||||||
|
import FeatureType from '../model/FeatureType';
|
||||||
|
|
||||||
/**
|
class XMLSerializerTango implements XMLMindmapSerializer {
|
||||||
* This serializer works exactly the same way as for the former version Pela
|
private static MAP_ROOT_NODE = 'map';
|
||||||
|
|
||||||
|
private _idsMap: Record<number, Element>;
|
||||||
|
|
||||||
|
toXML(mindmap: Mindmap): Document {
|
||||||
|
$assert(mindmap, 'Can not save a null mindmap');
|
||||||
|
|
||||||
|
const document = createDocument();
|
||||||
|
|
||||||
|
// Store map attributes ...
|
||||||
|
const mapElem = document.createElement('map');
|
||||||
|
const name = mindmap.getId();
|
||||||
|
if ($defined(name)) {
|
||||||
|
mapElem.setAttribute('name', this._rmXmlInv(name));
|
||||||
|
}
|
||||||
|
const version = mindmap.getVersion();
|
||||||
|
if ($defined(version)) {
|
||||||
|
mapElem.setAttribute('version', version);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.appendChild(mapElem);
|
||||||
|
|
||||||
|
// Create branches ...
|
||||||
|
const topics = mindmap.getBranches();
|
||||||
|
topics.forEach((topic) => {
|
||||||
|
const topicDom = this._topicToXML(document, topic);
|
||||||
|
mapElem.appendChild(topicDom);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create Relationships
|
||||||
|
const relationships = mindmap.getRelationships();
|
||||||
|
relationships.forEach((relationship) => {
|
||||||
|
if (
|
||||||
|
mindmap.findNodeById(relationship.getFromNode()) !== null
|
||||||
|
&& mindmap.findNodeById(relationship.getToNode()) !== null
|
||||||
|
) {
|
||||||
|
// Isolated relationships are not persisted ....
|
||||||
|
const relationDom = XMLSerializerTango._relationshipToXML(document, relationship);
|
||||||
|
mapElem.appendChild(relationDom);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _topicToXML(document: Document, topic: NodeModel) {
|
||||||
|
const parentTopic = document.createElement('topic');
|
||||||
|
|
||||||
|
// Set topic attributes...
|
||||||
|
if (topic.getType() === 'CentralTopic') {
|
||||||
|
parentTopic.setAttribute('central', 'true');
|
||||||
|
} else {
|
||||||
|
const pos = topic.getPosition();
|
||||||
|
parentTopic.setAttribute('position', `${pos.x},${pos.y}`);
|
||||||
|
|
||||||
|
const order = topic.getOrder();
|
||||||
|
if (typeof order === 'number' && Number.isFinite(order)) {
|
||||||
|
parentTopic.setAttribute('order', order.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = topic.getText();
|
||||||
|
if ($defined(text)) {
|
||||||
|
this._noteTextToXML(document, parentTopic, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const shape = topic.getShapeType();
|
||||||
|
if ($defined(shape)) {
|
||||||
|
parentTopic.setAttribute('shape', shape);
|
||||||
|
|
||||||
|
if (shape === TopicShape.IMAGE) {
|
||||||
|
const size = topic.getImageSize();
|
||||||
|
parentTopic.setAttribute(
|
||||||
|
'image',
|
||||||
|
`${size.width},${size.height}:${topic.getImageUrl()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topic.areChildrenShrunken() && topic.getType() !== 'CentralTopic') {
|
||||||
|
parentTopic.setAttribute('shrink', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Font properties ...
|
||||||
|
const id = topic.getId();
|
||||||
|
parentTopic.setAttribute('id', id.toString());
|
||||||
|
|
||||||
|
let font = '';
|
||||||
|
|
||||||
|
const fontFamily = topic.getFontFamily();
|
||||||
|
font += `${fontFamily || ''};`;
|
||||||
|
|
||||||
|
const fontSize = topic.getFontSize();
|
||||||
|
font += `${fontSize || ''};`;
|
||||||
|
|
||||||
|
const fontColor = topic.getFontColor();
|
||||||
|
font += `${fontColor || ''};`;
|
||||||
|
|
||||||
|
const fontWeight = topic.getFontWeight();
|
||||||
|
font += `${fontWeight || ''};`;
|
||||||
|
|
||||||
|
const fontStyle = topic.getFontStyle();
|
||||||
|
font += `${fontStyle || ''};`;
|
||||||
|
|
||||||
|
if (
|
||||||
|
$defined(fontFamily)
|
||||||
|
|| $defined(fontSize)
|
||||||
|
|| $defined(fontColor)
|
||||||
|
|| $defined(fontWeight)
|
||||||
|
|| $defined(fontStyle)
|
||||||
|
) {
|
||||||
|
parentTopic.setAttribute('fontStyle', font);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bgColor = topic.getBackgroundColor();
|
||||||
|
if ($defined(bgColor)) {
|
||||||
|
parentTopic.setAttribute('bgColor', bgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const brColor = topic.getBorderColor();
|
||||||
|
if ($defined(brColor)) {
|
||||||
|
parentTopic.setAttribute('brColor', brColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = topic.getMetadata();
|
||||||
|
if ($defined(metadata)) {
|
||||||
|
parentTopic.setAttribute('metadata', metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize features ...
|
||||||
|
const features = topic.getFeatures();
|
||||||
|
features.forEach((feature) => {
|
||||||
|
const featureType = feature.getType();
|
||||||
|
const featureDom = document.createElement(featureType);
|
||||||
|
const attributes = feature.getAttributes();
|
||||||
|
|
||||||
|
const attributesKeys = Object.keys(attributes);
|
||||||
|
for (let attrIndex = 0; attrIndex < attributesKeys.length; attrIndex++) {
|
||||||
|
const key = attributesKeys[attrIndex];
|
||||||
|
const value = attributes[key];
|
||||||
|
if (key === 'text') {
|
||||||
|
const cdata = document.createCDATASection(this._rmXmlInv(value));
|
||||||
|
featureDom.appendChild(cdata);
|
||||||
|
} else {
|
||||||
|
featureDom.setAttribute(key, this._rmXmlInv(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parentTopic.appendChild(featureDom);
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHILDREN TOPICS
|
||||||
|
const childTopics = topic.getChildren();
|
||||||
|
childTopics.forEach((childTopic) => {
|
||||||
|
const childDom = this._topicToXML(document, childTopic);
|
||||||
|
parentTopic.appendChild(childDom);
|
||||||
|
});
|
||||||
|
|
||||||
|
return parentTopic;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _noteTextToXML(document: Document, elem: Element, text: string) {
|
||||||
|
if (text.indexOf('\n') === -1) {
|
||||||
|
elem.setAttribute('text', this._rmXmlInv(text));
|
||||||
|
} else {
|
||||||
|
const textDom = document.createElement('text');
|
||||||
|
const cdata = document.createCDATASection(this._rmXmlInv(text));
|
||||||
|
textDom.appendChild(cdata);
|
||||||
|
elem.appendChild(textDom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static _relationshipToXML(document: Document, relationship: RelationshipModel) {
|
||||||
|
const result = document.createElement('relationship');
|
||||||
|
result.setAttribute('srcTopicId', relationship.getFromNode().toString());
|
||||||
|
result.setAttribute('destTopicId', relationship.getToNode().toString());
|
||||||
|
|
||||||
|
const lineType = relationship.getLineType();
|
||||||
|
result.setAttribute('lineType', lineType.toString());
|
||||||
|
if (lineType === ConnectionLine.CURVED || lineType === ConnectionLine.SIMPLE_CURVED) {
|
||||||
|
if ($defined(relationship.getSrcCtrlPoint())) {
|
||||||
|
const srcPoint = relationship.getSrcCtrlPoint();
|
||||||
|
result.setAttribute(
|
||||||
|
'srcCtrlPoint',
|
||||||
|
`${Math.round(srcPoint.x)},${Math.round(srcPoint.y)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($defined(relationship.getDestCtrlPoint())) {
|
||||||
|
const destPoint = relationship.getDestCtrlPoint();
|
||||||
|
result.setAttribute(
|
||||||
|
'destCtrlPoint',
|
||||||
|
`${Math.round(destPoint.x)},${Math.round(destPoint.y)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.setAttribute('endArrow', String(relationship.getEndArrow()));
|
||||||
|
result.setAttribute('startArrow', String(relationship.getStartArrow()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFromDom(dom: Document, mapId: string) {
|
||||||
|
$assert(dom, 'dom can not be null');
|
||||||
|
$assert(mapId, 'mapId can not be null');
|
||||||
|
|
||||||
|
const rootElem = dom.documentElement;
|
||||||
|
|
||||||
|
// Is a wisemap?.
|
||||||
|
$assert(
|
||||||
|
rootElem.tagName === XMLSerializerTango.MAP_ROOT_NODE,
|
||||||
|
`This seem not to be a map document. Found tag: ${rootElem.tagName}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this._idsMap = {};
|
||||||
|
// Start the loading process ...
|
||||||
|
const version = rootElem.getAttribute('version') || 'pela';
|
||||||
|
const mindmap = new Mindmap(mapId, version);
|
||||||
|
|
||||||
|
// Add all the topics nodes ...
|
||||||
|
const childNodes = Array.from(rootElem.childNodes);
|
||||||
|
const topicsNodes = childNodes
|
||||||
|
.filter(
|
||||||
|
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'topic',
|
||||||
|
)
|
||||||
|
.map((c) => c as Element);
|
||||||
|
topicsNodes.forEach((child) => {
|
||||||
|
const topic = this._deserializeNode(child, mindmap);
|
||||||
|
mindmap.addBranch(topic);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then all relationshops, they are connected to topics ...
|
||||||
|
const relationshipsNodes = childNodes
|
||||||
|
.filter(
|
||||||
|
(child: ChildNode) => child.nodeType === 1 && (child as Element).tagName === 'relationship',
|
||||||
|
)
|
||||||
|
.map((c) => c as Element);
|
||||||
|
relationshipsNodes.forEach((child) => {
|
||||||
|
try {
|
||||||
|
const relationship = XMLSerializerTango._deserializeRelationship(child, mindmap);
|
||||||
|
mindmap.addRelationship(relationship);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up from the recursion ...
|
||||||
|
this._idsMap = null;
|
||||||
|
mindmap.setId(mapId);
|
||||||
|
return mindmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _deserializeNode(domElem: Element, mindmap: Mindmap) {
|
||||||
|
const type = domElem.getAttribute('central') != null ? 'CentralTopic' : 'MainTopic';
|
||||||
|
|
||||||
|
// Load attributes...
|
||||||
|
let id: number | null = null;
|
||||||
|
if ($defined(domElem.getAttribute('id'))) {
|
||||||
|
id = Number.parseInt(domElem.getAttribute('id'), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._idsMap[id]) {
|
||||||
|
id = null;
|
||||||
|
} else {
|
||||||
|
this._idsMap[id] = domElem;
|
||||||
|
}
|
||||||
|
|
||||||
|
const topic = mindmap.createNode(type, id);
|
||||||
|
|
||||||
|
// Set text property is it;s defined...
|
||||||
|
const text = domElem.getAttribute('text');
|
||||||
|
if ($defined(text) && text) {
|
||||||
|
topic.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fontStyle = domElem.getAttribute('fontStyle');
|
||||||
|
if ($defined(fontStyle) && fontStyle) {
|
||||||
|
const font = fontStyle.split(';');
|
||||||
|
|
||||||
|
if (font[0]) {
|
||||||
|
topic.setFontFamily(font[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font[1]) {
|
||||||
|
topic.setFontSize(Number.parseInt(font[1], 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font[2]) {
|
||||||
|
topic.setFontColor(font[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font[3]) {
|
||||||
|
topic.setFontWeight(font[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font[4]) {
|
||||||
|
topic.setFontStyle(font[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shape = domElem.getAttribute('shape');
|
||||||
|
if ($defined(shape)) {
|
||||||
|
topic.setShapeType(shape);
|
||||||
|
|
||||||
|
if (shape === TopicShape.IMAGE) {
|
||||||
|
const image = domElem.getAttribute('image');
|
||||||
|
const size = image.substring(0, image.indexOf(':'));
|
||||||
|
const url = image.substring(image.indexOf(':') + 1, image.length);
|
||||||
|
topic.setImageUrl(url);
|
||||||
|
|
||||||
|
const split = size.split(',');
|
||||||
|
topic.setImageSize(Number.parseInt(split[0], 10), Number.parseInt(split[1], 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bgColor = domElem.getAttribute('bgColor');
|
||||||
|
if ($defined(bgColor)) {
|
||||||
|
topic.setBackgroundColor(bgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderColor = domElem.getAttribute('brColor');
|
||||||
|
if ($defined(borderColor)) {
|
||||||
|
topic.setBorderColor(borderColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = domElem.getAttribute('order');
|
||||||
|
if ($defined(order) && order !== 'NaN') {
|
||||||
|
// Hack for broken maps ...
|
||||||
|
topic.setOrder(parseInt(order, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
const isShrink = domElem.getAttribute('shrink');
|
||||||
|
// Hack: Some production maps has been stored with the central topic collapsed. This is a bug.
|
||||||
|
if ($defined(isShrink) && type !== 'CentralTopic') {
|
||||||
|
topic.setChildrenShrunken(Boolean(isShrink));
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = domElem.getAttribute('position');
|
||||||
|
if ($defined(position)) {
|
||||||
|
const pos = position.split(',');
|
||||||
|
topic.setPosition(Number.parseInt(pos[0], 10), Number.parseInt(pos[1], 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = domElem.getAttribute('metadata');
|
||||||
|
if ($defined(metadata)) {
|
||||||
|
topic.setMetadata(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating icons and children nodes
|
||||||
|
const children = Array.from(domElem.childNodes);
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (child.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
const elem = child as Element;
|
||||||
|
if (elem.tagName === 'topic') {
|
||||||
|
const childTopic = this._deserializeNode(elem, mindmap);
|
||||||
|
childTopic.connectTo(topic);
|
||||||
|
} else if (FeatureModelFactory.isSupported(elem.tagName)) {
|
||||||
|
// Load attributes ...
|
||||||
|
const namedNodeMap = elem.attributes;
|
||||||
|
const attributes: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (let j = 0; j < namedNodeMap.length; j++) {
|
||||||
|
const attribute = namedNodeMap.item(j);
|
||||||
|
attributes[attribute.name] = attribute.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has text node ?.
|
||||||
|
const textAttr = XMLSerializerTango._deserializeTextAttr(elem);
|
||||||
|
if (textAttr) {
|
||||||
|
attributes.text = textAttr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new element ....
|
||||||
|
const featureType = elem.tagName as FeatureType;
|
||||||
|
const feature = FeatureModelFactory.createModel(featureType, attributes);
|
||||||
|
topic.addFeature(feature);
|
||||||
|
} else if (elem.tagName === 'text') {
|
||||||
|
const nodeText = XMLSerializerTango._deserializeNodeText(child);
|
||||||
|
topic.setText(nodeText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Workaround: for some reason, some saved maps have holes in the order.
|
||||||
|
if (topic.getType() !== 'CentralTopic') {
|
||||||
|
topic
|
||||||
|
.getChildren()
|
||||||
|
.forEach((child, index) => {
|
||||||
|
if (child.getOrder() !== index) {
|
||||||
|
child.setOrder(index);
|
||||||
|
console.log('Toppic with order sequence hole. Introducing auto recovery sequence fix.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _deserializeTextAttr(domElem: Element): string {
|
||||||
|
let value = domElem.getAttribute('text');
|
||||||
|
if (!$defined(value)) {
|
||||||
|
const children = domElem.childNodes;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
if (child.nodeType === Node.CDATA_SECTION_NODE) {
|
||||||
|
value = child.nodeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notes must be decoded ...
|
||||||
|
value = unescape(value);
|
||||||
|
|
||||||
|
// Hack for empty nodes ...
|
||||||
|
if (value === '') {
|
||||||
|
value = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _deserializeNodeText(domElem) {
|
||||||
|
const children = domElem.childNodes;
|
||||||
|
let value = null;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
if (child.nodeType === Node.CDATA_SECTION_NODE) {
|
||||||
|
value = child.nodeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _deserializeRelationship(domElement, mindmap) {
|
||||||
|
const srcId = Number.parseInt(domElement.getAttribute('srcTopicId'), 10);
|
||||||
|
const destId = Number.parseInt(domElement.getAttribute('destTopicId'), 10);
|
||||||
|
const lineType = Number.parseInt(domElement.getAttribute('lineType'), 10);
|
||||||
|
const srcCtrlPoint = domElement.getAttribute('srcCtrlPoint');
|
||||||
|
const destCtrlPoint = domElement.getAttribute('destCtrlPoint');
|
||||||
|
|
||||||
|
// If for some reason a relationship lines has source and dest nodes the same, don't import it.
|
||||||
|
if (srcId === destId) {
|
||||||
|
throw new Error('Invalid relationship, dest and source are equals');
|
||||||
|
}
|
||||||
|
// Is the connections points valid ?. If it's not, do not load the relationship ...
|
||||||
|
if (mindmap.findNodeById(srcId) == null || mindmap.findNodeById(destId) == null) {
|
||||||
|
throw new Error('Transition could not created, missing node for relationship');
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = mindmap.createRelationship(srcId, destId);
|
||||||
|
model.setLineType(lineType);
|
||||||
|
if ($defined(srcCtrlPoint) && srcCtrlPoint !== '') {
|
||||||
|
model.setSrcCtrlPoint(Point.fromString(srcCtrlPoint));
|
||||||
|
}
|
||||||
|
if ($defined(destCtrlPoint) && destCtrlPoint !== '') {
|
||||||
|
model.setDestCtrlPoint(Point.fromString(destCtrlPoint));
|
||||||
|
}
|
||||||
|
model.setEndArrow('false');
|
||||||
|
model.setStartArrow('true');
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method ensures that the output String has only
|
||||||
|
* valid XML unicode characters as specified by the
|
||||||
|
* XML 1.0 standard. For reference, please see
|
||||||
|
* <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
|
||||||
|
* standard</a>. This method will return an empty
|
||||||
|
* String if the input is null or empty.
|
||||||
|
*
|
||||||
|
* @param in The String whose non-valid characters we want to remove.
|
||||||
|
* @return The in String, stripped of non-valid characters.
|
||||||
*/
|
*/
|
||||||
class XMLSerializerTango extends XMLSerializerPela {
|
protected _rmXmlInv(str: string) {
|
||||||
|
if (str == null || str === undefined) return null;
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const c = str.charCodeAt(i);
|
||||||
|
if (
|
||||||
|
c === 0x9
|
||||||
|
|| c === 0xa
|
||||||
|
|| c === 0xd
|
||||||
|
|| (c >= 0x20 && c <= 0xd7ff)
|
||||||
|
|| (c >= 0xe000 && c <= 0xfffd)
|
||||||
|
|| (c >= 0x10000 && c <= 0x10ffff)
|
||||||
|
) {
|
||||||
|
result += str.charAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
export default XMLSerializerTango;
|
export default XMLSerializerTango;
|
||||||
|
@ -25,7 +25,8 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
|||||||
// Overwite default behaviour ...
|
// Overwite default behaviour ...
|
||||||
},
|
},
|
||||||
setValue() {
|
setValue() {
|
||||||
window.location = '/c/logout';
|
const elem = document.getElementById('logoutFrom');
|
||||||
|
elem.submit();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
super(elemId, model);
|
super(elemId, model);
|
||||||
@ -59,6 +60,7 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
|||||||
content[0].innerHTML = `
|
content[0].innerHTML = `
|
||||||
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
||||||
<p>${global.accountEmail}</p>
|
<p>${global.accountEmail}</p>
|
||||||
|
<form action="/c/logout" method='POST' id="logoutFrom"></form>
|
||||||
<div id="account-logout" model='logout' style='text-align:center'>
|
<div id="account-logout" model='logout' style='text-align:center'>
|
||||||
Logout
|
Logout
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,10 +70,14 @@ class IMenu {
|
|||||||
unlockMap(designer: Designer) {
|
unlockMap(designer: Designer) {
|
||||||
const mindmap = designer.getMindmap();
|
const mindmap = designer.getMindmap();
|
||||||
const persistenceManager = PersistenceManager.getInstance();
|
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 ...
|
// Load map content ...
|
||||||
const mindmap = designer.getMindmap();
|
const mindmap = designer.getMindmap();
|
||||||
const mindmapProp = designer.getMindmapProperties();
|
const mindmapProp = designer.getMindmapProperties();
|
||||||
@ -106,7 +110,7 @@ class IMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}, sync);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isSaveRequired(): boolean {
|
isSaveRequired(): boolean {
|
||||||
|
@ -57,7 +57,7 @@ class Menu extends IMenu {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
setValue(value) {
|
setValue(value: string) {
|
||||||
designer.changeFontFamily(value);
|
designer.changeFontFamily(value);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -68,7 +68,7 @@ class Menu extends IMenu {
|
|||||||
const fontSizeBtn = $('#fontSize');
|
const fontSizeBtn = $('#fontSize');
|
||||||
if (fontSizeBtn) {
|
if (fontSizeBtn) {
|
||||||
const fontSizeModel = {
|
const fontSizeModel = {
|
||||||
getValue() {
|
getValue(): number {
|
||||||
const nodes = designerModel.filterSelectedTopics();
|
const nodes = designerModel.filterSelectedTopics();
|
||||||
|
|
||||||
let result = null;
|
let result = null;
|
||||||
@ -82,7 +82,7 @@ class Menu extends IMenu {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
setValue(value) {
|
setValue(value: number) {
|
||||||
designer.changeFontSize(value);
|
designer.changeFontSize(value);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -297,9 +297,9 @@ class Menu extends IMenu {
|
|||||||
Menu._registerTooltip('save', $msg('SAVE'), 'meta+S');
|
Menu._registerTooltip('save', $msg('SAVE'), 'meta+S');
|
||||||
|
|
||||||
if (!readOnly) {
|
if (!readOnly) {
|
||||||
$(window).bind('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
if (this.isSaveRequired()) {
|
if (this.isSaveRequired()) {
|
||||||
this.save(saveElem, designer, false, true);
|
this.save(saveElem, designer, false);
|
||||||
}
|
}
|
||||||
this.unlockMap(designer);
|
this.unlockMap(designer);
|
||||||
});
|
});
|
||||||
@ -310,7 +310,7 @@ class Menu extends IMenu {
|
|||||||
if (this.isSaveRequired()) {
|
if (this.isSaveRequired()) {
|
||||||
this.save(saveElem, designer, false);
|
this.save(saveElem, designer, false);
|
||||||
}
|
}
|
||||||
}, 30000,
|
}, 10000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,13 @@ import PersistenceManager from './components/PersistenceManager';
|
|||||||
import Designer from './components/Designer';
|
import Designer from './components/Designer';
|
||||||
import LocalStorageManager from './components/LocalStorageManager';
|
import LocalStorageManager from './components/LocalStorageManager';
|
||||||
import RESTPersistenceManager from './components/RestPersistenceManager';
|
import RESTPersistenceManager from './components/RestPersistenceManager';
|
||||||
|
import MockPersistenceManager from './components/MockPersistenceManager';
|
||||||
import Menu from './components/widget/Menu';
|
import Menu from './components/widget/Menu';
|
||||||
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
|
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
|
||||||
import ImageExporterFactory from './components/export/ImageExporterFactory';
|
import ImageExporterFactory from './components/export/ImageExporterFactory';
|
||||||
import TextExporterFactory from './components/export/TextExporterFactory';
|
import TextExporterFactory from './components/export/TextExporterFactory';
|
||||||
import Exporter from './components/export/Exporter';
|
import Exporter from './components/export/Exporter';
|
||||||
|
import DesignerKeyboard from './components/DesignerKeyboard';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildDesigner,
|
buildDesigner,
|
||||||
@ -47,6 +49,7 @@ export {
|
|||||||
DesignerBuilder,
|
DesignerBuilder,
|
||||||
PersistenceManager,
|
PersistenceManager,
|
||||||
RESTPersistenceManager,
|
RESTPersistenceManager,
|
||||||
|
MockPersistenceManager,
|
||||||
LocalStorageManager,
|
LocalStorageManager,
|
||||||
DesignerOptionsBuilder,
|
DesignerOptionsBuilder,
|
||||||
buildDesigner,
|
buildDesigner,
|
||||||
@ -54,4 +57,5 @@ export {
|
|||||||
ImageExporterFactory,
|
ImageExporterFactory,
|
||||||
Exporter,
|
Exporter,
|
||||||
$notify,
|
$notify,
|
||||||
|
DesignerKeyboard,
|
||||||
};
|
};
|
||||||
|
@ -57,7 +57,7 @@ const zoomParam = Number.parseFloat(params.get('zoom'));
|
|||||||
const options = DesignerOptionsBuilder.buildOptions(
|
const options = DesignerOptionsBuilder.buildOptions(
|
||||||
{
|
{
|
||||||
persistenceManager: persistence,
|
persistenceManager: persistence,
|
||||||
readOnly: Boolean(global.readOnly || false),
|
mode: 'viewonly',
|
||||||
mapId: global.mapId,
|
mapId: global.mapId,
|
||||||
container: 'mindplot',
|
container: 'mindplot',
|
||||||
zoom: zoomParam || global.userOptions.zoom,
|
zoom: zoomParam || global.userOptions.zoom,
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { expect, test } from '@jest/globals'; // Workaround for cypress conflict
|
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 SVGExporter from '../../../src/components/export/SVGExporter';
|
||||||
import { parseXMLFile, setupBlob, exporterAssert } from './Helper';
|
import { parseXMLFile, setupBlob, exporterAssert } from './Helper';
|
||||||
|
|
||||||
@ -12,14 +10,6 @@ describe('SVG export test execution', () => {
|
|||||||
test.each(fs.readdirSync(path.resolve(__dirname, './input/'))
|
test.each(fs.readdirSync(path.resolve(__dirname, './input/'))
|
||||||
.filter((f) => f.endsWith('.wxml'))
|
.filter((f) => f.endsWith('.wxml'))
|
||||||
.map((filename: string) => filename.split('.')[0]))('Exporting %p suite', async (testName: string) => {
|
.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 ...
|
// Load SVG ...
|
||||||
const svgPath = path.resolve(__dirname, `./input/${testName}.svg`);
|
const svgPath = path.resolve(__dirname, `./input/${testName}.svg`);
|
||||||
expect(fs.existsSync(svgPath)).toEqual(true);
|
expect(fs.existsSync(svgPath)).toEqual(true);
|
||||||
|
@ -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.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.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"/>
|
<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 |
@ -24,6 +24,12 @@
|
|||||||
1.12 Actividades centradas en el contexto cercano
|
1.12 Actividades centradas en el contexto cercano
|
||||||
1.13 Flexibilidad en el uso de las lenguas de trabajo (inglés, castellano, esukara?)
|
1.13 Flexibilidad en el uso de las lenguas de trabajo (inglés, castellano, esukara?)
|
||||||
1.14 Complementamos el trabajo de la escuela
|
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.1 Cada uno va a su ritmo, y cada cual pone sus límites
|
||||||
1.14.2 Aprendemos todos de todos
|
1.14.2 Aprendemos todos de todos
|
||||||
1.14.3 Valoramos lo que hemos aprendido
|
1.14.3 Valoramos lo que hemos aprendido
|
||||||
|
@ -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,
|
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.
|
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,
|
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>
|
@ -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 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 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"/>
|
<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 |
@ -1,13 +1,16 @@
|
|||||||
1 Indicator needs
|
1 Indicator needs
|
||||||
1.1 Which new measures
|
1.1 Which new measures
|
||||||
|
[Note: Identifying new measures or investments that should be implemented.]
|
||||||
1.1.1 Landscape of measures
|
1.1.1 Landscape of measures
|
||||||
1.1.1.1 Diversity index of innovation support instruments in the region
|
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.1.2 Existing investments in measures
|
||||||
1.1.2 What other regions do differently
|
1.1.2 What other regions do differently
|
||||||
1.1.2.1 Balance of measure index
|
1.1.2.1 Balance of measure index
|
||||||
1.1.2.2 Profile comparison with other regions
|
1.1.2.2 Profile comparison with other regions
|
||||||
1.1.2.3 Number of specific types of measures per capita
|
1.1.2.3 Number of specific types of measures per capita
|
||||||
1.2 How to design & implement measures
|
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.1 Good practices
|
||||||
1.2.2 Diagnostics
|
1.2.2 Diagnostics
|
||||||
1.2.2.1 Internal business innovation factors
|
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.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.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
|
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
|
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 Wages
|
||||||
1.3.1.1.1 Dynamics of real wages
|
1.3.1.1.1 Dynamics of real wages
|
||||||
1.3.1.1.2 Average wage (compare to the Fed)
|
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.1.3 Manufacturing value added per capita (non-natural resource-based)
|
||||||
1.3.2.2 The enabling environment
|
1.3.2.2 The enabling environment
|
||||||
1.3.2.2.1 Ease of doing business
|
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.1.1 Level of administrative barriers (number and cost of administrative procedures)
|
||||||
1.3.2.2.2 Competition index
|
1.3.2.2.2 Competition index
|
||||||
|
[Note: GCR]
|
||||||
1.3.2.2.3 Workforce
|
1.3.2.2.3 Workforce
|
||||||
1.3.2.2.3.1 Quality of education
|
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.1.1 Inrease in the number of International students
|
||||||
1.3.2.2.3.2 Quantity of education
|
1.3.2.2.3.2 Quantity of education
|
||||||
1.3.2.2.3.2.1 Participation in life-long learning
|
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.2 Increase in literarecy
|
||||||
1.3.2.2.3.2.3 Amount of university and colleague
|
1.3.2.2.3.2.3 Amount of university and colleague
|
||||||
students per 10 thousands population
|
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.2.7 Access to training, information, and consulting support
|
||||||
1.3.2.2.3.3 Science & engineering workforce
|
1.3.2.2.3.3 Science & engineering workforce
|
||||||
1.3.2.2.3.3.1 Availability of scientists and engineers
|
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.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.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
|
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.5.2.2 Number of Business Angels
|
||||||
1.3.2.2.6 ICT
|
1.3.2.2.6 ICT
|
||||||
1.3.2.2.6.1 ICT use
|
1.3.2.2.6.1 ICT use
|
||||||
|
[Note: GCR]
|
||||||
1.3.2.2.6.2 Broadband penetration
|
1.3.2.2.6.2 Broadband penetration
|
||||||
1.3.2.2.6.3 Internet penetration
|
1.3.2.2.6.3 Internet penetration
|
||||||
1.3.2.2.6.4 Computer literacy
|
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.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.2 Inflow of foreign direct investments in high-technology industries
|
||||||
1.3.2.3.1.1.3 Foreign direct investment jobs
|
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.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.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.1.6 Share of foreign controlled enterprises
|
||||||
1.3.2.3.1.2 Exports
|
1.3.2.3.1.2 Exports
|
||||||
1.3.2.3.1.2.1 Export intensity in manufacturing and services
|
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
|
1.3.2.3.1.2.2 Share of high-technology export in the total volume
|
||||||
of production of goods, works and services
|
of production of goods, works and services
|
||||||
1.3.2.3.1.2.3 Share of innovation production/serivces that goes for export,
|
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.1.3 Share of high-technology products in government procurements
|
||||||
1.3.2.3.2 Entrepreneurship culture
|
1.3.2.3.2 Entrepreneurship culture
|
||||||
1.3.2.3.2.1 Fear of failure rate
|
1.3.2.3.2.1 Fear of failure rate
|
||||||
|
[Note: GEM]
|
||||||
1.3.2.3.2.2 Entrepreneurship as desirable career choice
|
1.3.2.3.2.2 Entrepreneurship as desirable career choice
|
||||||
|
[Note: GEM]
|
||||||
1.3.2.3.2.3 High Status Successful Entrepreneurship
|
1.3.2.3.2.3 High Status Successful Entrepreneurship
|
||||||
|
[Note: GEM]
|
||||||
1.3.2.3.3 Collaboration & partnerships
|
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.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
|
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
|
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
|
1.3.2.3.3.4 Share of Innovative companies collaborating
|
||||||
with research institutions on innovation
|
with research institutions on innovation
|
||||||
1.3.2.3.3.5 Number of joint projects conducted by the local comapnies
|
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.3.6 science and industry links
|
||||||
1.3.2.3.4 Technology absorption
|
1.3.2.3.4 Technology absorption
|
||||||
1.3.2.3.4.1 Local supplier quality
|
1.3.2.3.4.1 Local supplier quality
|
||||||
|
[Note: GCR]
|
||||||
1.3.2.3.4.2 Share of expenditures on technological innovations
|
1.3.2.3.4.2 Share of expenditures on technological innovations
|
||||||
in the amount of sales
|
in the amount of sales
|
||||||
1.3.2.3.4.3 Number of purchased new technologies
|
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.1 Share of innovative companies
|
||||||
1.3.2.3.5.2 Business R&D expenditures per GRP
|
1.3.2.3.5.2 Business R&D expenditures per GRP
|
||||||
1.3.2.3.5.3 Factors hampering innovation
|
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.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.5 R&D and other intellectl property products
|
||||||
1.3.2.3.5.6 Growth of the number of innovative companies
|
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.5.7.4 Volume of innovation production per capita
|
||||||
1.3.2.3.6 Entrepreneurial activities
|
1.3.2.3.6 Entrepreneurial activities
|
||||||
1.3.2.3.6.1 New business density
|
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
|
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.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 R&D production
|
||||||
1.3.2.3.7.1 Outputs
|
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.3.2 Number of foreign patents granted per staff
|
||||||
1.3.2.4.1.4 Supportive measures
|
1.3.2.4.1.4 Supportive measures
|
||||||
1.3.2.4.1.4.1 Diversity index of university entrepreneurship support 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 Commercialization
|
||||||
1.3.2.4.1.5.1 Licensing
|
1.3.2.4.1.5.1 Licensing
|
||||||
1.3.2.4.1.5.1.1 Academic licenses: Number of licenses
|
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.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 Private market
|
||||||
1.3.2.4.2.1 Number of innovation & IP services organizations
|
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
|
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.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.2.4 Access to suppliers of equipment, production and engineering services
|
||||||
1.3.2.4.3 Innovation infrastructure
|
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.1.5 Volume of venture financing from the regional budget
|
||||||
1.3.2.4.3.2 Volume of state support per one company
|
1.3.2.4.3.2 Volume of state support per one company
|
||||||
1.4 What to do about existing measures
|
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 Demand for measure
|
||||||
1.4.1.1 Quality of beneficiaries
|
1.4.1.1 Quality of beneficiaries
|
||||||
1.4.1.1.1 Growth rates of employment in supported innovative firms
|
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.2 Growth rates of employment in supported innovative firms
|
||||||
1.4.1.1.3 Role of IP for tenants/clients
|
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.4 Share of tenants with innovation activities
|
||||||
1.4.1.1.5 Gazelle tenant: Share of tenants with
|
1.4.1.1.5 Gazelle tenant: Share of tenants with
|
||||||
annual revenue growth of more than 20%
|
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.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 Inputs of measures
|
||||||
1.4.2.1 Qualified staff
|
1.4.2.1 Qualified staff
|
||||||
|
[Note: JL: not sure how this would be measured]
|
||||||
1.4.2.2 Budget per beneficiary
|
1.4.2.2 Budget per beneficiary
|
||||||
1.4.3 Performance of measure
|
1.4.3 Performance of measure
|
||||||
1.4.3.1 Implementation 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
|
1.4.4.5 Volume of attracted money per one ruble
|
||||||
of regional budget expenditures on innovation projects
|
of regional budget expenditures on innovation projects
|
||||||
1.5 What investments in innovative 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 Competitive niches
|
||||||
1.5.1.1 Clusters behavior
|
1.5.1.1 Clusters behavior
|
||||||
1.5.1.1.1 Cluster EU star rating
|
1.5.1.1.1 Cluster EU star rating
|
||||||
|
@ -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 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 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"/>
|
<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 |
@ -1,11 +1,22 @@
|
|||||||
1 Observation
|
1 Observation
|
||||||
|
[Note: Always ask ]
|
||||||
2 Data Analysis
|
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
|
3 Organizing Data
|
||||||
|
[Note: Organize your data when you are doing an experiment ]
|
||||||
4 Questions
|
4 Questions
|
||||||
|
[Note: Always ask your self a question when analysis the data it is a good idea to do.]
|
||||||
5 Hypothesis
|
5 Hypothesis
|
||||||
|
[Note: You make your hypothesis when you are making your observation.]
|
||||||
6 Experiment
|
6 Experiment
|
||||||
|
[Note: Always analysis your data and keep it in order when you are doing an experiment.]
|
||||||
7 Variable
|
7 Variable
|
||||||
|
[Note: A major factor that can change the outcome in an experiment.]
|
||||||
8 Independent Variable
|
8 Independent Variable
|
||||||
|
[Note: When you change it you the see affect or the aftermath of what happened ]
|
||||||
9 Control Group
|
9 Control Group
|
||||||
|
[Note: A test That can be compared ]
|
||||||
10 Dependent Variable
|
10 Dependent Variable
|
||||||
|
[Note: Changes the outcome of the other variables]
|
||||||
11 Constant
|
11 Constant
|
||||||
|
[Note: Doesnt Change at all maybe once and a while but never that often]
|
||||||
|
@ -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 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 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"/>
|
<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 |
@ -1,55 +1,112 @@
|
|||||||
1 PPM Plan
|
1 PPM Plan
|
||||||
1.1 Business Development
|
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.3 Freeform IT
|
||||||
1.4 Client Project Management
|
1.4 Client Project Management
|
||||||
1.5 Governance & Executive
|
1.5 Governance & Executive
|
||||||
1.6 Finance
|
1.6 Finance
|
||||||
1.7 Administration
|
1.7 Administration
|
||||||
1.8 Human Resources
|
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 Freeform’s reputation and success.]
|
||||||
1.9 Freeform Hosting
|
1.9 Freeform Hosting
|
||||||
1.10 Community Outreach
|
1.10 Community Outreach
|
||||||
1.11 R&D
|
1.11 R&D
|
||||||
1.11.1 Goals
|
1.11.1 Goals
|
||||||
1.11.2 Formulize
|
1.11.2 Formulize
|
||||||
1.12 Probono
|
1.12 Probono
|
||||||
|
1.12.1
|
||||||
2 Strategy 2: Talent Development
|
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
|
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
|
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
|
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...
|
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
|
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
|
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
|
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.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
|
4 Strategy 1: Recruit & Retain
|
||||||
|
[Note: Recruit and retain top talent commensurate with identified organizational capacity requirements ]
|
||||||
4.1 So that...
|
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
|
4.2 Strategic Priority 1a: Recruitment
|
||||||
|
[Note: 1. Identify and use proactive and effective recruitment strategies, ]
|
||||||
4.2.1 Modify App Form
|
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
|
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
|
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
|
4.4 Strategic Priority 1c: Onboarding
|
||||||
|
[Note: ]
|
||||||
4.4.1 3 Month Onboarding Process
|
4.4.1 3 Month Onboarding Process
|
||||||
4.4.2 Tools & Guidelines
|
4.4.2 Tools & Guidelines
|
||||||
4.4.3 Mentoring
|
4.4.3 Mentoring
|
||||||
4.5 Strategic Priority 1d: Incentives
|
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.1 Raises
|
||||||
4.5.2 Benefits
|
4.5.2 Benefits
|
||||||
4.5.3 Rewards Message
|
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
|
4.6 Strategic Priority 1e: Offboarding
|
||||||
|
[Note: Assess and address reasons why talented people leave Freeform]
|
||||||
5 Business Development Plan
|
5 Business Development Plan
|
||||||
5.1 Goals
|
5.1 Goals
|
||||||
5.1.1 Increase new clients
|
5.1.1 Increase new clients
|
||||||
5.1.1.1 Academic Research
|
5.1.1.1 Academic Research
|
||||||
|
5.1.1.2
|
||||||
5.1.2 Support New Products
|
5.1.2 Support New Products
|
||||||
5.1.2.1 Formulize
|
5.1.2.1 Formulize
|
||||||
|
5.1.2.2
|
||||||
|
5.1.2.3
|
||||||
5.1.3 Support CiviCRM
|
5.1.3 Support CiviCRM
|
||||||
5.1.4 Identify Opportunites
|
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
|
6 Hosting NG Plan
|
||||||
7 Freeform IT Plan
|
7 Freeform IT Plan
|
||||||
7.1 Fragile
|
7.1 Fragile
|
||||||
7.2 Tools
|
7.2 Tools
|
||||||
|
7.3
|
||||||
8 Project Teams
|
8 Project Teams
|
||||||
8.1 Projects 1-3
|
8.1 Projects 1-3
|
||||||
8.2 Projects 4-6
|
8.2 Projects 4-6
|
||||||
@ -62,17 +119,45 @@
|
|||||||
9.3 Supportive Systems Plan
|
9.3 Supportive Systems Plan
|
||||||
10 Board and C Planning
|
10 Board and C Planning
|
||||||
10.1 Mission Statements
|
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.2 Values
|
||||||
10.3 Bylaw Review
|
10.3 Bylaw Review
|
||||||
10.4 Policies
|
10.4 Policies
|
||||||
10.5 Business Plan
|
10.5 Business Plan
|
||||||
11 Strategy 3: Safety and Wellness
|
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
|
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
|
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.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...
|
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
|
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.1 As Freeform Staff
|
||||||
12.2 Responsibility: HZ, JC
|
12.2 Responsibility: HZ, JC
|
||||||
12.3 Release 3
|
12.3 Release 3
|
||||||
@ -81,54 +166,90 @@
|
|||||||
12.6 Have JC & HZ consult with staff
|
12.6 Have JC & HZ consult with staff
|
||||||
12.7 Have best benefits we can afford
|
12.7 Have best benefits we can afford
|
||||||
12.8 So that...
|
12.8 So that...
|
||||||
|
[Note: so that our efforts to excel are rewarded.]
|
||||||
13 Community Outreach Plan
|
13 Community Outreach Plan
|
||||||
13.1 Goals
|
13.1 Goals
|
||||||
13.2 CSI
|
13.2 CSI
|
||||||
13.3 Drupal Community
|
13.3 Drupal Community
|
||||||
13.4 CiviCRM
|
13.4 CiviCRM
|
||||||
13.5 Other
|
13.5 Other
|
||||||
14 Backlog Plan [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
|
14 Backlog Plan
|
||||||
14.1 Go To Backlog Plan [link: https://docs.google.com/a/freeform.ca/drawings/d/1mrtkVAN3_XefJJCgfxw4Va6xk9TVDBKXDt_uzyIF4Us/edit]
|
[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 Strategy Prospecting
|
||||||
|
15.1
|
||||||
|
15.2
|
||||||
|
15.3
|
||||||
16 Stategies: Forecasting
|
16 Stategies: Forecasting
|
||||||
|
16.1
|
||||||
|
16.2
|
||||||
|
16.3
|
||||||
17 Strategies Marketing
|
17 Strategies Marketing
|
||||||
18 Exit Interviews
|
18
|
||||||
18.1 As Freeform
|
19 Exit Interviews
|
||||||
18.2 Responsiblity: HZ, KS
|
19.1 As Freeform
|
||||||
18.3 Release
|
19.2 Responsiblity: HZ, KS
|
||||||
18.4 Have Heather write procedures for exit interview process
|
19.3 Release
|
||||||
18.5 So that
|
19.4 Have Heather write procedures for exit interview process
|
||||||
19 3 Month Onboarding Process
|
19.5 So that
|
||||||
20 Human Resources Plan
|
[Note: We learn from our mistakes and missed opportunities in future with the goal of keeping the best talent.]
|
||||||
20.1 Related Org Objectives
|
20 3 Month Onboarding Process
|
||||||
20.1.1 1
|
21 Human Resources Plan
|
||||||
20.1.2 2
|
21.1 Related Org Objectives
|
||||||
20.1.3 3
|
21.1.1 1
|
||||||
20.1.4 4
|
[Note: Attract, build and retain a motivated, agile, knowledgeable team of Top Talent that loves to “come” to work]
|
||||||
20.2 Related Documents
|
21.1.2 2
|
||||||
20.3 Goals
|
[Note: Maintain level of human resource capacity and skill to meet planned growth and client contractual commitments]
|
||||||
20.3.1 Goal:Staff=Optimal Bus. Growth
|
21.1.3 3
|
||||||
20.3.1.1 So that...
|
[Note: Conform to all legislated requirements]
|
||||||
20.3.1.2 Related Strategic Priorities:
|
21.1.4 4
|
||||||
20.3.1.3 KPI: HR Level equals Planned Growth
|
[Note: Minimize and mitigate risk to the organization]
|
||||||
20.3.1.4 Methodology
|
21.2 Related Documents
|
||||||
20.3.1.4.1 Target
|
[Note: MIssion, Values, Principles, Org Business Plan, Human Resources Policy Manual]
|
||||||
20.3.2 Goal: Increase Job Satisfaction
|
21.3 Goals
|
||||||
20.3.2.1 So That
|
21.3.1 Goal:Staff=Optimal Bus. Growth
|
||||||
20.3.2.2 Related Strategic Priorities
|
[Note: Human resource capacity will remain at a level to meet planned growth growth objectives and client contractual commitments]
|
||||||
20.3.2.3 KPI: Employee Satisfaction
|
21.3.1.1 So that...
|
||||||
20.3.2.4 Methodology
|
21.3.1.2 Related Strategic Priorities:
|
||||||
20.3.2.4.1 Target
|
21.3.1.3 KPI: HR Level equals Planned Growth
|
||||||
20.3.3 Goal: Improve Performance
|
21.3.1.4 Methodology
|
||||||
20.3.3.1 So That
|
[Note: Schedule of required HR capacity vs. actual HR capacity. Variance + or - 1]
|
||||||
20.3.3.2 Related Strategic Priorities
|
21.3.1.4.1 Target
|
||||||
20.3.3.3 KPI: Employee Performance
|
[Note: = + or - 1]
|
||||||
20.3.3.4 Methodology
|
21.3.2 Goal: Increase Job Satisfaction
|
||||||
20.3.3.4.1 Target
|
21.3.2.1 So That
|
||||||
20.3.4 Goal: Reduce Turnover
|
[Note: Establish better relationships.
|
||||||
20.3.4.1 So That
|
Identify with the new employer.
|
||||||
20.3.4.2 Related Strategic Priorities
|
Build a great attitude with the company.]
|
||||||
20.3.4.3 KPI: Retention Rate
|
21.3.2.2 Related Strategic Priorities
|
||||||
20.3.4.4 Methodology
|
21.3.2.2.1
|
||||||
20.3.4.4.1 Target
|
21.3.2.3 KPI: Employee Satisfaction
|
||||||
20.3.5 Risk & Compliance
|
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. ]
|
||||||
|
@ -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.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,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"/>
|
<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 |
@ -1,4 +1,5 @@
|
|||||||
1
|
1
|
||||||
|
[Note: ]
|
||||||
1.1 objectifs journée
|
1.1 objectifs journée
|
||||||
1.1.1 "business plan" associatif ?
|
1.1.1 "business plan" associatif ?
|
||||||
1.1.2 modèle / activités responsabilités
|
1.1.2 modèle / activités responsabilités
|
||||||
|
@ -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,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,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"/>
|
<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 |
@ -35,9 +35,11 @@ the other hand, strategies alternative to BRIDGE clearly
|
|||||||
failed to accurately estimate the variance of trait values. This
|
failed to accurately estimate the variance of trait values. This
|
||||||
indicates that in situations where accurate estimation of plotlevel
|
indicates that in situations where accurate estimation of plotlevel
|
||||||
variance is desired, complete censuses are essential.
|
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,
|
1.1.8 We suggest that, in these studies,
|
||||||
the investment in complete sampling may be worthwhile
|
the investment in complete sampling may be worthwhile
|
||||||
for at least some traits.
|
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): 31–40
|
1.2 Chazdon 2010. Biotropica. 42(1): 31–40
|
||||||
1.2.1 Here, we develop a new approach that links functional attributes
|
1.2.1 Here, we develop a new approach that links functional attributes
|
||||||
of tree species with studies of forest recovery and regional
|
of tree species with studies of forest recovery and regional
|
||||||
|
@ -28,18 +28,18 @@ failed to accurately estimate the variance of trait values. This
|
|||||||
indicates that in situations where accurate estimation of plotlevel
|
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,
|
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
|
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): 31–40" 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): 31–40" 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
|
of tree species with studies of forest recovery and regional
|
||||||
land-use transitions (Chazdon et al. 2007). Grouping species according
|
land-use transitions (Chazdon et al. 2007). Grouping species according
|
||||||
to their functional attributes or demographic rates provides
|
to their functional attributes or demographic rates provides
|
||||||
insight into both applied and theoretical questions, such as selecting
|
insight into both applied and theoretical questions, such as selecting
|
||||||
species for reforestation programs, assessing ecosystem services, and
|
species for reforestation programs, assessing ecosystem services, and
|
||||||
understanding community assembly processes in tropical forests
|
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
|
and wood functional traits for only a subset of the species in our
|
||||||
study sites, we based our functional type classification on information
|
study sites, we based our functional type classification on information
|
||||||
for a large number of tree species obtained through vegetation
|
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
|
behavior or shade tolerance of tree species by developing an objective
|
||||||
and independent classification of functional types based on vegetation
|
and independent classification of functional types based on vegetation
|
||||||
monitoring data from permanent sample plots in mature and
|
monitoring data from permanent sample plots in mature and
|
||||||
|
@ -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,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.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"/>
|
<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 |
@ -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.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"/>
|
<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">
|
<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 |
@ -1,4 +1,5 @@
|
|||||||
1 أَبْجَدِيَّة عَرَبِيَّة
|
1 أَبْجَدِيَّة عَرَبِيَّة
|
||||||
1.1 أَبْجَدِيَّة عَرَبِ
|
1.1 أَبْجَدِيَّة عَرَبِ
|
||||||
|
[Note: This is a not in languange أَبْجَدِيَّة عَرَبِ]
|
||||||
1.2 Long text node:
|
1.2 Long text node:
|
||||||
أَبْجَدِيَّة عَرَب
|
أَبْجَدِيَّة عَرَب
|
||||||
|
@ -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 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 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"/>
|
<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 |
@ -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 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 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"/>
|
<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 |
@ -1,13 +1,19 @@
|
|||||||
1 NIF (NORMAS DE INFORMACIÓN FINANCIERA)
|
1 NIF (NORMAS DE INFORMACIÓN FINANCIERA)
|
||||||
2 NIF D
|
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
|
2.1 Normas aplicables a problemas de determinación de resultados
|
||||||
3 CIRCULANTES
|
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
|
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
|
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
|
5.1 Normas aplicables a conceptos específicos de los estados financieros
|
||||||
6 NIF E
|
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
|
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
|
7.1 Normas aplicables a los estados financieros en su conjunto
|
||||||
|
@ -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 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 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"/>
|
<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 |
@ -29,7 +29,8 @@
|
|||||||
1.5.1.1 Orange County Eye and Transplant Bank
|
1.5.1.1 Orange County Eye and Transplant Bank
|
||||||
1.5.1.2 Northern California 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.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 OneLegacy
|
||||||
1.5.2.1 In 2010, 11,828 referrals
|
1.5.2.1 In 2010, 11,828 referrals
|
||||||
1.5.3 San Diego Eye Bank
|
1.5.3 San Diego Eye Bank
|
||||||
|
@ -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 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 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"/>
|
<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 |
@ -1,13 +1,15 @@
|
|||||||
1 Welcome To WiseMapping
|
1 Welcome To WiseMapping
|
||||||
1.1 5 min tutorial video ?
|
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 Try it Now!
|
||||||
1.2.1 Double Click
|
1.2.1 Double Click
|
||||||
1.2.2 Press "enter" to add a
|
1.2.2 Press "enter" to add a
|
||||||
Sibling
|
Sibling
|
||||||
1.2.3 Drag map to move
|
1.2.3 Drag map to move
|
||||||
1.3 Features
|
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 Styles
|
||||||
1.3.2.1 Fonts
|
1.3.2.1 Fonts
|
||||||
1.3.2.2 Topic Shapes
|
1.3.2.2 Topic Shapes
|
||||||
@ -24,8 +26,10 @@ Sibling
|
|||||||
1.5.2 Brainstorming
|
1.5.2 Brainstorming
|
||||||
1.5.3 Visual
|
1.5.3 Visual
|
||||||
1.6 Install In Your Server
|
1.6 Install In Your Server
|
||||||
1.6.1 Open Source [link: http://www.wisemapping.org/]
|
1.6.1 Open Source
|
||||||
1.6.2 Download [link: http://www.wisemapping.com/inyourserver.html]
|
[Link: http://www.wisemapping.org/]
|
||||||
|
1.6.2 Download
|
||||||
|
[Link: http://www.wisemapping.com/inyourserver.html]
|
||||||
1.7 Collaborate
|
1.7 Collaborate
|
||||||
1.7.1 Embed
|
1.7.1 Embed
|
||||||
1.7.2 Publish
|
1.7.2 Publish
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"module": "amd",
|
"module": "amd",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"target": "es6",
|
"target": "ES2020",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|