Merged in ray-develop (pull request #51)

Ray develop

* reset zoom and toolbar fixes

* lang fixes

* dialog enhancements

* comments

* tooltip for zoom and fit commented

* merge develop into ray develop

* rules for ignoring some linebreaks in eslint

* comments in .eslintrc

* brackets around ifs

* playground fixes, comments removed, semi-colon at bottom of editor removed, root container size now defined with css


Approved-by: Paulo Veiga
This commit is contained in:
Gustavo Fuhr 2022-07-09 01:34:52 +00:00 committed by Paulo Veiga
parent b48d55ebca
commit 0f4a8ee087
47 changed files with 1258 additions and 763 deletions

5
.gitignore vendored
View File

@ -51,4 +51,7 @@ Thumbs.db
**/build/**/* **/build/**/*
.vscode .vscode
*/test/playground/dist */test/playground/dist
# visual code local workspaces
wisemapping-frontend.code-workspace

View File

@ -1,6 +1,6 @@
{ {
"trailingComma": "es5", "trailingComma": "all",
"tabWidth": 4, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 100 "printWidth": 100

View File

@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
e2e: e2e:
image: cypress/included:9.7.0 image: cypress/included:10.2.0
container_name: wisemapping-integration-tests container_name: wisemapping-integration-tests
entrypoint: '/bin/sh -c "yarn install && yarn bootstrap && yarn build && yarn test:integration"' entrypoint: '/bin/sh -c "yarn install && yarn bootstrap && yarn build && yarn test:integration"'
working_dir: /e2e working_dir: /e2e

View File

@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
e2e: e2e:
image: cypress/included:9.7.0 image: cypress/included:10.2.0
container_name: wisemapping-integration-tests container_name: wisemapping-integration-tests
entrypoint: '/bin/sh -c "yarn bootstrap && yarn build && yarn test:integration"' entrypoint: '/bin/sh -c "yarn bootstrap && yarn build && yarn test:integration"'
working_dir: /e2e working_dir: /e2e

View File

@ -4,5 +4,6 @@
], ],
"version": "1.0.0", "version": "1.0.0",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true "useWorkspaces": true,
"useNx": false
} }

View File

@ -45,5 +45,20 @@
"homepage": "http://localhost:8080/react", "homepage": "http://localhost:8080/react",
"license": "https://wisemapping.atlassian.net/wiki/spaces/WS/pages/524357/WiseMapping+Public+License+Version+1.0+WPL", "license": "https://wisemapping.atlassian.net/wiki/spaces/WS/pages/524357/WiseMapping+Public+License+Version+1.0+WPL",
"version": "0.4.0", "version": "0.4.0",
"dependencies": {} "husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "yarn lint && yarn test:unit"
}
},
"lint-staged": {
"**/*.{ts,tsx}": [
"prettier --write"
]
},
"eslintConfig": {
"rules": {
"implicit-arrow-linebreak": "off"
}
}
} }

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 26 26" style="enable-background:new 0 0 26 26;" xml:space="preserve">
<g>
<path style="fill:#030104;" d="M21.125,0H4.875C2.182,0,0,2.182,0,4.875v16.25C0,23.818,2.182,26,4.875,26h16.25
C23.818,26,26,23.818,26,21.125V4.875C26,2.182,23.818,0,21.125,0z M18.78,17.394l-1.388,1.387c-0.254,0.255-0.67,0.255-0.924,0
L13,15.313L9.533,18.78c-0.255,0.255-0.67,0.255-0.925-0.002L7.22,17.394c-0.253-0.256-0.253-0.669,0-0.926l3.468-3.467
L7.221,9.534c-0.254-0.256-0.254-0.672,0-0.925l1.388-1.388c0.255-0.257,0.671-0.257,0.925,0L13,10.689l3.468-3.468
c0.255-0.257,0.671-0.257,0.924,0l1.388,1.386c0.254,0.255,0.254,0.671,0.001,0.927l-3.468,3.467l3.468,3.467
C19.033,16.725,19.033,17.138,18.78,17.394z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -4,6 +4,18 @@
}, },
"editor.try-welcome-description": { "editor.try-welcome-description": {
"defaultMessage": "Melden Sie sich an, um kostenlos eine unbegrenzte Anzahl von Mindmaps zu erstellen, zu teilen und zu veröffentlichen." "defaultMessage": "Melden Sie sich an, um kostenlos eine unbegrenzte Anzahl von Mindmaps zu erstellen, zu teilen und zu veröffentlichen."
},
"editor.try-welcome-mobile": {
"defaultMessage": "Diese Edition zeigt einige der Mindmap-Funktionen!"
},
"editor.try-welcome-description-mobile": {
"defaultMessage": "Melden Sie sich an, um kostenlos eine unbegrenzte Anzahl von Mindmaps zu erstellen, zu teilen und zu veröffentlichen. Eingeschränkte Funktionen der Mindmap-Edition werden auf Mobilgeräten unterstützt. Verwenden Sie den Desktop-Browser für vollständige Editorfunktionen."
},
"editor.edit-mobile": {
"defaultMessage": "Hinweis für Mobilgeräte."
},
"editor.edit-description-mobile": {
"defaultMessage": "Eingeschränkte Funktionen der Mindmap-Edition werden auf Mobilgeräten unterstützt. Verwenden Sie den Desktop-Browser für vollständige Editorfunktionen."
}, },
"login.signup": { "login.signup": {
"defaultMessage": "Anmeldung" "defaultMessage": "Anmeldung"

View File

@ -1,9 +1,21 @@
{ {
"editor.try-welcome": { "editor.try-welcome": {
"defaultMessage": "This edition space showcases some of the mindmap editor capabilities !" "defaultMessage": "This edition space showcases some of the mindmap editor capabilities!"
}, },
"editor.try-welcome-description": { "editor.try-welcome-description": {
"defaultMessage": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free." "defaultMessage": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free."
},
"editor.try-welcome-mobile": {
"defaultMessage": "This edition space showcases some of the mindmap capabilities!"
},
"editor.try-welcome-description-mobile": {
"defaultMessage": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free. Limited mindmap edition capabilties are supported in Mobile devices. Use Desktop browser for full editor capabilies."
},
"editor.edit-mobile": {
"defaultMessage": "Note for mobile devices."
},
"editor.edit-description-mobile": {
"defaultMessage": "Limited mindmap edition capabilities are supported in Mobile devices. Use Desktop browser for full editor capabilities."
}, },
"login.signup": { "login.signup": {
"defaultMessage": "Sign Up" "defaultMessage": "Sign Up"

View File

@ -4,6 +4,18 @@
}, },
"editor.try-welcome-description": { "editor.try-welcome-description": {
"defaultMessage": "Registrate para comenzar a crear, compartir y publicar una cantidad ilimitada de mapas mentales de forma gratuita." "defaultMessage": "Registrate para comenzar a crear, compartir y publicar una cantidad ilimitada de mapas mentales de forma gratuita."
},
"editor.try-welcome-mobile": {
"defaultMessage": "¡Este espacio de edición muestra algunas de las capacidades de mapas mentales!"
},
"editor.try-welcome-description-mobile": {
"defaultMessage": "Registrate para comenzar a crear, compartir y publicar una cantidad ilimitada de mapas mentales de forma gratuita. En dispositivos móbiles las funciones son limitadas. Use la versión de escritorio para tener las funciones completas."
},
"editor.edit-mobile": {
"defaultMessage": "Nota para dispositivos móbiles."
},
"editor.edit-description-mobile": {
"defaultMessage": "En dispositivos móbiles las funciones son limitadas. Use la versión de escritorio para tener las funciones completas."
}, },
"login.signup": { "login.signup": {
"defaultMessage": "Crear cuenta" "defaultMessage": "Crear cuenta"

View File

@ -1,9 +1,21 @@
{ {
"editor.try-welcome": { "editor.try-welcome": {
"defaultMessage": "Cet espace d'édition présente certaines des fonctionnalités de l'éditeur de cartes mentales !" "defaultMessage": "Cet espace d'édition présente certaines des fonctionnalités de l'éditeur de cartes mentales!"
}, },
"editor.try-welcome-description": { "editor.try-welcome-description": {
"defaultMessage": "Inscrivez-vous pour commencer à créer, partager et publier gratuitement un nombre illimité de cartes mentales." "defaultMessage": "Inscrivez-vous pour commencer à créer, partager et publier gratuitement un nombre illimité de cartes mentales."
},
"editor.try-welcome-mobile": {
"defaultMessage": "Cet espace d'édition présente certaines des fonctionnalités des cartes mentales!"
},
"editor.try-welcome-description-mobile": {
"defaultMessage": "Inscrivez-vous pour commencer à créer, partager et publier gratuitement un nombre illimité de cartes mentales. Les capacités d'édition limitées de mindmap sont prises en charge dans les appareils mobiles. Utilisez le navigateur de bureau pour bénéficier de toutes les fonctionnalités de l'éditeur."
},
"editor.edit-mobile": {
"defaultMessage": "Remarque pour les appareils mobiles."
},
"editor.edit-description-mobile": {
"defaultMessage": "Les capacités d'édition limitées de mindmap sont prises en charge dans les appareils mobiles. Utilisez le navigateur de bureau pour bénéficier de toutes les fonctionnalités de l'éditeur."
}, },
"login.signup": { "login.signup": {
"defaultMessage": "S'inscrire" "defaultMessage": "S'inscrire"

View File

@ -4,6 +4,18 @@
}, },
"editor.try-welcome-description": { "editor.try-welcome-description": {
"defaultMessage": "Чтобы получить бесплатный неограниченный доступ — нужна только регистрация." "defaultMessage": "Чтобы получить бесплатный неограниченный доступ — нужна только регистрация."
},
"editor.try-welcome-mobile": {
"defaultMessage": "В этом издании демонстрируются некоторые возможности ментальных карт!"
},
"editor.try-welcome-description-mobile": {
"defaultMessage": "Зарегистрируйтесь, чтобы начать создавать, делиться и публиковать неограниченное количество ментальных карт бесплатно. Возможности ограниченной версии Mindmap поддерживаются на мобильных устройствах. Используйте настольный браузер для полных возможностей редактора."
},
"editor.edit-mobile": {
"defaultMessage": "Примечание для мобильных устройств."
},
"editor.edit-description-mobile": {
"defaultMessage": "Возможности ограниченной версии Mindmap поддерживаются на мобильных устройствах. Используйте настольный браузер для полных возможностей редактора."
}, },
"login.signup": { "login.signup": {
"defaultMessage": "Регистрация" "defaultMessage": "Регистрация"

View File

@ -1,14 +1,26 @@
{ {
"editor.try-welcome": { "editor.try-welcome": {
"defaultMessage": "这个编辑区域展示了一些思维导图编辑器的功能!" "defaultMessage": "这个编辑区域展示了一些思维导图编辑器的功能!"
}, },
"editor.try-welcome-description": { "editor.try-welcome-description": {
"defaultMessage": "注册后可以免费创建、分享和发布无限数量的思维导图。" "defaultMessage": "注册后可以免费创建、分享和发布无限数量的思维导图。"
}, },
"login.signup": { "editor.try-welcome-mobile": {
"defaultMessage": "注册" "defaultMessage": "这个版本空间展示了一些思维导图功能!"
}, },
"action.share": { "editor.try-welcome-description-mobile": {
"defaultMessage": "分享" "defaultMessage": "注册以开始免费创建、共享和发布无限数量的思维导图。 移动设备支持有限的思维导图编辑功能。 使用桌面浏览器获得完整的编辑器功能。"
} },
"editor.edit-mobile": {
"defaultMessage": "移动设备注意事项."
},
"editor.edit-description-mobile": {
"defaultMessage": "移动设备支持有限的思维导图编辑功能。 使用桌面浏览器获得完整的编辑器功能。"
},
"login.signup": {
"defaultMessage": "注册"
},
"action.share": {
"defaultMessage": "分享"
}
} }

View File

@ -61,7 +61,9 @@ class Menu extends IMenu {
this._addButton('position', false, false, () => { this._addButton('position', false, false, () => {
designer.zoomToFit(); designer.zoomToFit();
}); });
Menu._registerTooltip('position', $msg('CENTER_POSITION')); // Disabled because this tooltip overflows the screen and makes the button un-clickeable
// This should be enabled when migrating to material-ui
//Menu._registerTooltip('position', $msg('CENTER_POSITION'));
// Edition actions ... // Edition actions ...
if (!readOnly) { if (!readOnly) {
@ -181,7 +183,9 @@ class Menu extends IMenu {
designer.changeBorderColor(hex); designer.changeBorderColor(hex);
}, },
}; };
this._toolbarElems.push(new ColorPalettePanel('topicBorder', borderColorModel, widgetsBaseUrl)); this._toolbarElems.push(
new ColorPalettePanel('topicBorder', borderColorModel, widgetsBaseUrl),
);
Menu._registerTooltip('topicBorder', $msg('TOPIC_BORDER_COLOR')); Menu._registerTooltip('topicBorder', $msg('TOPIC_BORDER_COLOR'));
const fontColorModel = { const fontColorModel = {
@ -271,14 +275,12 @@ class Menu extends IMenu {
}); });
Menu._registerTooltip('fontItalic', $msg('FONT_ITALIC'), $msg('CTRL') + ' + I'); Menu._registerTooltip('fontItalic', $msg('FONT_ITALIC'), $msg('CTRL') + ' + I');
if (!readOnly) { if (!readOnly) {
// Register action on save ... // Register action on save ...
const saveElem = $('#save'); const saveElem = $('#save');
this._addButton('save', false, false, this._addButton('save', false, false, () => {
() => { this.save(saveElem, designer, true);
this.save(saveElem, designer, true); });
});
Menu._registerTooltip('save', $msg('SAVE'), $msg('CTRL') + ' + S'); Menu._registerTooltip('save', $msg('SAVE'), $msg('CTRL') + ' + S');
// Register unload save ... // Register unload save ...
@ -290,13 +292,11 @@ class Menu extends IMenu {
}); });
// Autosave on a fixed period of time ... // Autosave on a fixed period of time ...
setInterval( setInterval(() => {
() => { if (this.isSaveRequired()) {
if (this.isSaveRequired()) { this.save(saveElem, designer, false);
this.save(saveElem, designer, false); }
} }, 10000);
}, 10000,
);
} }
} }
@ -394,10 +394,14 @@ class Menu extends IMenu {
// Register Events ... // Register Events ...
let result = null; let result = null;
if ($(`#${buttonId}`)) { if ($(`#${buttonId}`)) {
const button = new ToolbarItem(buttonId, ((event) => { const button = new ToolbarItem(
fn(event); buttonId,
this.clear(); (event) => {
}), { topicAction: isTopic, relAction: isRelationship }); fn(event);
this.clear();
},
{ topicAction: isTopic, relAction: isRelationship },
);
this._toolbarElems.push(button); this._toolbarElems.push(button);
result = button; result = button;
@ -409,9 +413,10 @@ class Menu extends IMenu {
if ($(`#${buttonId}`)) { if ($(`#${buttonId}`)) {
let tooltip = text; let tooltip = text;
if (shortcut) { if (shortcut) {
const platformedShortcut = navigator.appVersion.indexOf('Mac') !== -1 const platformedShortcut =
? shortcut.replace('meta+', '⌘') navigator.appVersion.indexOf('Mac') !== -1
: shortcut.replace('meta+', 'ctrl+'); ? shortcut.replace('meta+', '⌘')
: shortcut.replace('meta+', 'ctrl+');
tooltip = `${tooltip} (${platformedShortcut})`; tooltip = `${tooltip} (${platformedShortcut})`;
} }
return new KeyboardShortcutTooltip($(`#${buttonId}`), tooltip); return new KeyboardShortcutTooltip($(`#${buttonId}`), tooltip);

View File

@ -5,6 +5,18 @@
"value": "Teilen" "value": "Teilen"
} }
], ],
"editor.edit-description-mobile": [
{
"type": 0,
"value": "Eingeschränkte Funktionen der Mindmap-Edition werden auf Mobilgeräten unterstützt. Verwenden Sie den Desktop-Browser für vollständige Editorfunktionen."
}
],
"editor.edit-mobile": [
{
"type": 0,
"value": "Hinweis für Mobilgeräte."
}
],
"editor.try-welcome": [ "editor.try-welcome": [
{ {
"type": 0, "type": 0,
@ -17,6 +29,18 @@
"value": "Melden Sie sich an, um kostenlos eine unbegrenzte Anzahl von Mindmaps zu erstellen, zu teilen und zu veröffentlichen." "value": "Melden Sie sich an, um kostenlos eine unbegrenzte Anzahl von Mindmaps zu erstellen, zu teilen und zu veröffentlichen."
} }
], ],
"editor.try-welcome-description-mobile": [
{
"type": 0,
"value": "Melden Sie sich an, um kostenlos eine unbegrenzte Anzahl von Mindmaps zu erstellen, zu teilen und zu veröffentlichen. Eingeschränkte Funktionen der Mindmap-Edition werden auf Mobilgeräten unterstützt. Verwenden Sie den Desktop-Browser für vollständige Editorfunktionen."
}
],
"editor.try-welcome-mobile": [
{
"type": 0,
"value": "Diese Edition zeigt einige der Mindmap-Funktionen!"
}
],
"login.signup": [ "login.signup": [
{ {
"type": 0, "type": 0,

View File

@ -5,10 +5,22 @@
"value": "Share" "value": "Share"
} }
], ],
"editor.edit-description-mobile": [
{
"type": 0,
"value": "Limited mindmap edition capabilities are supported in Mobile devices. Use Desktop browser for full editor capabilities."
}
],
"editor.edit-mobile": [
{
"type": 0,
"value": "Note for mobile devices."
}
],
"editor.try-welcome": [ "editor.try-welcome": [
{ {
"type": 0, "type": 0,
"value": "This edition space showcases some of the mindmap editor capabilities !" "value": "This edition space showcases some of the mindmap editor capabilities!"
} }
], ],
"editor.try-welcome-description": [ "editor.try-welcome-description": [
@ -17,6 +29,18 @@
"value": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free." "value": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free."
} }
], ],
"editor.try-welcome-description-mobile": [
{
"type": 0,
"value": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free. Limited mindmap edition capabilties are supported in Mobile devices. Use Desktop browser for full editor capabilies."
}
],
"editor.try-welcome-mobile": [
{
"type": 0,
"value": "This edition space showcases some of the mindmap capabilities!"
}
],
"login.signup": [ "login.signup": [
{ {
"type": 0, "type": 0,

View File

@ -5,6 +5,18 @@
"value": "Compartir" "value": "Compartir"
} }
], ],
"editor.edit-description-mobile": [
{
"type": 0,
"value": "En dispositivos móbiles las funciones son limitadas. Use la versión de escritorio para tener las funciones completas."
}
],
"editor.edit-mobile": [
{
"type": 0,
"value": "Nota para dispositivos móbiles."
}
],
"editor.try-welcome": [ "editor.try-welcome": [
{ {
"type": 0, "type": 0,
@ -17,6 +29,18 @@
"value": "Registrate para comenzar a crear, compartir y publicar una cantidad ilimitada de mapas mentales de forma gratuita." "value": "Registrate para comenzar a crear, compartir y publicar una cantidad ilimitada de mapas mentales de forma gratuita."
} }
], ],
"editor.try-welcome-description-mobile": [
{
"type": 0,
"value": "Registrate para comenzar a crear, compartir y publicar una cantidad ilimitada de mapas mentales de forma gratuita. En dispositivos móbiles las funciones son limitadas. Use la versión de escritorio para tener las funciones completas."
}
],
"editor.try-welcome-mobile": [
{
"type": 0,
"value": "¡Este espacio de edición muestra algunas de las capacidades de mapas mentales!"
}
],
"login.signup": [ "login.signup": [
{ {
"type": 0, "type": 0,

View File

@ -5,10 +5,22 @@
"value": "Partager" "value": "Partager"
} }
], ],
"editor.edit-description-mobile": [
{
"type": 0,
"value": "Les capacités d'édition limitées de mindmap sont prises en charge dans les appareils mobiles. Utilisez le navigateur de bureau pour bénéficier de toutes les fonctionnalités de l'éditeur."
}
],
"editor.edit-mobile": [
{
"type": 0,
"value": "Remarque pour les appareils mobiles."
}
],
"editor.try-welcome": [ "editor.try-welcome": [
{ {
"type": 0, "type": 0,
"value": "Cet espace d'édition présente certaines des fonctionnalités de l'éditeur de cartes mentales !" "value": "Cet espace d'édition présente certaines des fonctionnalités de l'éditeur de cartes mentales!"
} }
], ],
"editor.try-welcome-description": [ "editor.try-welcome-description": [
@ -17,6 +29,18 @@
"value": "Inscrivez-vous pour commencer à créer, partager et publier gratuitement un nombre illimité de cartes mentales." "value": "Inscrivez-vous pour commencer à créer, partager et publier gratuitement un nombre illimité de cartes mentales."
} }
], ],
"editor.try-welcome-description-mobile": [
{
"type": 0,
"value": "Inscrivez-vous pour commencer à créer, partager et publier gratuitement un nombre illimité de cartes mentales. Les capacités d'édition limitées de mindmap sont prises en charge dans les appareils mobiles. Utilisez le navigateur de bureau pour bénéficier de toutes les fonctionnalités de l'éditeur."
}
],
"editor.try-welcome-mobile": [
{
"type": 0,
"value": "Cet espace d'édition présente certaines des fonctionnalités des cartes mentales!"
}
],
"login.signup": [ "login.signup": [
{ {
"type": 0, "type": 0,

View File

@ -5,6 +5,18 @@
"value": "Поделиться" "value": "Поделиться"
} }
], ],
"editor.edit-description-mobile": [
{
"type": 0,
"value": "Возможности ограниченной версии Mindmap поддерживаются на мобильных устройствах. Используйте настольный браузер для полных возможностей редактора."
}
],
"editor.edit-mobile": [
{
"type": 0,
"value": "Примечание для мобильных устройств."
}
],
"editor.try-welcome": [ "editor.try-welcome": [
{ {
"type": 0, "type": 0,
@ -17,6 +29,18 @@
"value": "Чтобы получить бесплатный неограниченный доступ — нужна только регистрация." "value": "Чтобы получить бесплатный неограниченный доступ — нужна только регистрация."
} }
], ],
"editor.try-welcome-description-mobile": [
{
"type": 0,
"value": "Зарегистрируйтесь, чтобы начать создавать, делиться и публиковать неограниченное количество ментальных карт бесплатно. Возможности ограниченной версии Mindmap поддерживаются на мобильных устройствах. Используйте настольный браузер для полных возможностей редактора."
}
],
"editor.try-welcome-mobile": [
{
"type": 0,
"value": "В этом издании демонстрируются некоторые возможности ментальных карт!"
}
],
"login.signup": [ "login.signup": [
{ {
"type": 0, "type": 0,

View File

@ -5,6 +5,18 @@
"value": "分享" "value": "分享"
} }
], ],
"editor.edit-description-mobile": [
{
"type": 0,
"value": "移动设备支持有限的思维导图编辑功能。 使用桌面浏览器获得完整的编辑器功能。"
}
],
"editor.edit-mobile": [
{
"type": 0,
"value": "移动设备注意事项."
}
],
"editor.try-welcome": [ "editor.try-welcome": [
{ {
"type": 0, "type": 0,
@ -17,6 +29,18 @@
"value": "注册后可以免费创建、分享和发布无限数量的思维导图。" "value": "注册后可以免费创建、分享和发布无限数量的思维导图。"
} }
], ],
"editor.try-welcome-description-mobile": [
{
"type": 0,
"value": "注册以开始免费创建、共享和发布无限数量的思维导图。 移动设备支持有限的思维导图编辑功能。 使用桌面浏览器获得完整的编辑器功能。"
}
],
"editor.try-welcome-mobile": [
{
"type": 0,
"value": "这个版本空间展示了一些思维导图功能!"
}
],
"login.signup": [ "login.signup": [
{ {
"type": 0, "type": 0,

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import { StyledLogo, Notifier } from './styled'; import { StyledLogo, Notifier } from './styled';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -6,51 +6,99 @@ import KeyboardSvg from '../../../images/keyboard.svg';
import AddSvg from '../../../images/add.svg'; import AddSvg from '../../../images/add.svg';
import MinusSvg from '../../../images/minus.svg'; import MinusSvg from '../../../images/minus.svg';
import CenterFocusSvg from '../../../images/center_focus.svg'; import CenterFocusSvg from '../../../images/center_focus.svg';
import CloseDialogSvg from '../../../images/close-dialog-icon.svg';
import ActionButton from '../action-button'; import ActionButton from '../action-button';
import { EditorRenderMode } from '@wisemapping/mindplot'; import { EditorRenderMode } from '@wisemapping/mindplot';
export type FooterPropsType = { export type FooterPropsType = {
editorMode: EditorRenderMode; editorMode: EditorRenderMode;
isMobile: boolean;
}; };
const Footer = ({ editorMode }: FooterPropsType): React.ReactElement => { const Footer = ({ editorMode, isMobile }: FooterPropsType): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const [dialogClass, setDialogClass] = useState('tryInfoPanel');
return ( var titleKey = undefined;
<> var descriptionKey = undefined;
<div id="floating-panel"> var showSignupButton = undefined;
<div id="keyboardShortcuts" className="buttonExtOn">
<img src={KeyboardSvg} /> if (editorMode !== 'viewonly' && editorMode !== 'showcase' && isMobile) {
</div> titleKey = 'editor.edit-mobile';
<div id="zoom-button"> descriptionKey = 'editor.edit-description-mobile';
<button id="zoom-plus"> showSignupButton = false;
<img src={AddSvg} /> }
</button> if (editorMode === 'showcase' && isMobile) {
<button id="zoom-minus"> titleKey = 'editor.try-welcome-mobile';
<img src={MinusSvg} /> descriptionKey = 'editor.edit-description-mobile';
</button> showSignupButton = true;
</div> }
<div id="position"> if (editorMode === 'showcase' && !isMobile) {
<button id="position-button"> titleKey = 'editor.try-welcome';
<img src={CenterFocusSvg} /> descriptionKey = 'editor.try-welcome-description';
</button> showSignupButton = true;
</div> }
// if the toolbar is present, the alert must not overlap
var alertTopAdjustmentStyle =
editorMode !== 'viewonly' && !isMobile
? 'tryInfoPanelWithToolbar'
: 'tryInfoPanelWithoutToolbar';
return (
<>
<div id="floating-panel">
{!isMobile && (
<div id="keyboardShortcuts" className="buttonExtOn">
<img src={KeyboardSvg} />
</div>
)}
<div id="zoom-button">
<button id="zoom-plus">
<img src={AddSvg} />
</button>
<button id="zoom-minus">
<img src={MinusSvg} />
</button>
</div>
<div id="position">
<button id="position-button">
<img src={CenterFocusSvg} />
</button>
</div>
</div>
<StyledLogo id="bottom-logo"></StyledLogo>
<Notifier id="headerNotifier"></Notifier>
{titleKey && (
<div className={dialogClass + ' ' + alertTopAdjustmentStyle}>
<div className="tryInfoPanelInner">
<div className="closeButton">
<button
onClick={(e) => {
setDialogClass('tryInfoPanelClosed');
e.preventDefault();
e.stopPropagation();
}}
>
<img src={CloseDialogSvg} />
</button>
</div> </div>
<StyledLogo id="bottom-logo"></StyledLogo> <p>
<Notifier id="headerNotifier"></Notifier> {intl.formatMessage({ id: titleKey })} {intl.formatMessage({ id: descriptionKey })}
{editorMode === 'showcase' && ( </p>
<div id="tryInfoPanel"> {showSignupButton && (
<p>{intl.formatMessage({ id: 'editor.try-welcome' })}</p> <a href="/c/registration">
<p>{intl.formatMessage({ id: 'editor.try-welcome-description' })}</p> <ActionButton>
<a href="/c/registration"> {intl.formatMessage({ id: 'login.signup', defaultMessage: 'Sign Up' })}
<ActionButton> </ActionButton>
{intl.formatMessage({ id: 'login.signup', defaultMessage: 'Sign Up' })} </a>
</ActionButton>
</a>
</div>
)} )}
</> </div>
); </div>
)}
</>
);
}; };
export default Footer; export default Footer;

View File

@ -9,6 +9,19 @@ html {
font-size: initial; font-size: initial;
} }
body {
width: 100vw;
height: 100vh;
min-width: 100vw;
min-height: 100vh;
margin: 0px;
}
.mindplot-root {
width: 100%;
height: 100%;
}
div#mindplot { div#mindplot {
position: relative; position: relative;
top: 50px; top: 50px;
@ -201,21 +214,69 @@ div#shotcuts > img{
color: #ffffff; color: #ffffff;
} }
div#tryInfoPanel { .tryInfoPanel {
position: absolute; position: absolute;
margin: auto; text-align: center;
text-align: center; left: 0;
top: 80px; right: 0;
left: 20px; background-color: white;
width: 200px; border: solid 2px #ffa800;
padding: 20px; margin: auto;
font-size: 15px; width: 99%;
border-radius: 9px; border-radius: 9px;
background-color: white; width: 96%;
border: solid 2px #ffa800;
} }
#tryInfoPanel > p { @media (min-width: 600px) {
.tryInfoPanel {
font-size: 15px;
}
}
@media (max-width: 600px) {
.tryInfoPanel {
font-size: 13px;
}
}
.tryInfoPanel .tryInfoPanelInner {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 5px;
padding-right: 5px;
}
.tryInfoPanel .tryInfoPanelInner .closeButton {
position: absolute;
top: 5px;
right: 5px;
}
.tryInfoPanel .tryInfoPanelInner .closeButton button {
cursor: pointer;
border-style: hidden;
background-color: transparent;
padding: 0px;
}
.tryInfoPanel .tryInfoPanelInner .closeButton button img {
width: 18px;
height: 18px;
filter: invert(73%) sepia(21%) saturate(4699%) hue-rotate(357deg) brightness(98%) contrast(108%);
}
.tryInfoPanelWithToolbar {
top: 55px;
}
.tryInfoPanelWithoutToolbar {
top: 5px;
}
.tryInfoPanelClosed {
display: none;
}
.tryInfoPanel > p {
justify-content: center; justify-content: center;
padding-bottom: 20px;
} }

View File

@ -1,122 +1,141 @@
import React, { useEffect } from 'react'; import React, { useEffect, useState } 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,
PersistenceManager, PersistenceManager,
DesignerOptionsBuilder, DesignerOptionsBuilder,
Designer, Designer,
DesignerKeyboard, DesignerKeyboard,
EditorRenderMode, EditorRenderMode,
} from '@wisemapping/mindplot'; } from '@wisemapping/mindplot';
import './global-styled.css'; import './global-styled.css';
import I18nMsg from './classes/i18n-msg'; import I18nMsg from './classes/i18n-msg';
import Menu from './classes/menu/Menu'; import Menu from './classes/menu/Menu';
declare global { declare global {
// used in mindplot // used in mindplot
var designer: Designer; var designer: Designer;
var accountEmail: string; var accountEmail: string;
} }
export type EditorOptions = { export type EditorOptions = {
mode: EditorRenderMode, mode: EditorRenderMode;
locale: string, locale: string;
zoom?: number, zoom?: number;
locked?: boolean, locked?: boolean;
lockedMsg?: string; lockedMsg?: string;
mapTitle: string; mapTitle: string;
enableKeyboardEvents: boolean; enableKeyboardEvents: boolean;
}
export type EditorProps = {
mapId: string;
options: EditorOptions;
persistenceManager: PersistenceManager;
onAction: (action: ToolbarActionType) => void;
onLoad?: (designer: Designer) => void;
}; };
const Editor = ({ export type EditorProps = {
mapId, mapId: string;
options, options: EditorOptions;
persistenceManager, persistenceManager: PersistenceManager;
onAction, onAction: (action: ToolbarActionType) => void;
onLoad, onLoad?: (designer: Designer) => void;
}: EditorProps) => { };
useEffect(() => { const Editor = ({ mapId, options, persistenceManager, onAction, onLoad }: EditorProps) => {
// Change page title ... const [isMobile, setIsMobile] = useState(undefined);
document.title = `${options.mapTitle} | WiseMapping `;
// Load mindmap ... useEffect(() => {
const designer = onLoadDesigner(mapId, options, persistenceManager); // Change page title ...
// Has extended actions been customized ... document.title = `${options.mapTitle} | WiseMapping `;
if (onLoad) {
onLoad(designer);
}
// Load mindmap ... // Load mindmap ...
const instance = PersistenceManager.getInstance(); const designer = onLoadDesigner(mapId, options, persistenceManager);
const mindmap = instance.load(mapId); // Has extended actions been customized ...
designer.loadMap(mindmap); if (onLoad) {
onLoad(designer);
}
if (options.locked) { // Load mindmap ...
$notify(options.lockedMsg, false); const instance = PersistenceManager.getInstance();
} const mindmap = instance.load(mapId);
}, []); designer.loadMap(mindmap);
useEffect(() => { setIsMobile(checkMobile());
if (options.enableKeyboardEvents) {
DesignerKeyboard.resume();
} else {
DesignerKeyboard.pause();
}
}, [options.enableKeyboardEvents]);
const onLoadDesigner = (mapId: string, options: EditorOptions, persistenceManager: PersistenceManager): Designer => { if (options.locked) {
const buildOptions = DesignerOptionsBuilder.buildOptions({ $notify(options.lockedMsg, false);
persistenceManager, }
mode: options.mode, }, []);
mapId: mapId,
container: 'mindplot',
zoom: options.zoom,
locale: options.locale,
});
// Build designer ... useEffect(() => {
const result = buildDesigner(buildOptions); if (options.enableKeyboardEvents) {
DesignerKeyboard.resume();
} else {
DesignerKeyboard.pause();
}
}, [options.enableKeyboardEvents]);
// Register toolbar event ... const checkMobile = () => {
if (options.mode === 'edition-owner' || options.mode === 'edition-editor' || options.mode === 'edition-viewer' || options.mode === 'showcase') { const check =
const menu = new Menu(designer, 'toolbar'); /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
navigator.userAgent.toLowerCase(),
) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
navigator.userAgent.toLowerCase().substring(0, 4),
);
return check;
};
// If a node has focus, focus can be move to another node using the keys. const onLoadDesigner = (
designer.cleanScreen = () => { mapId: string,
menu.clear(); options: EditorOptions,
}; persistenceManager: PersistenceManager,
} ): Designer => {
return result; const buildOptions = DesignerOptionsBuilder.buildOptions({
persistenceManager,
mode: options.mode,
mapId: mapId,
container: 'mindplot',
zoom: options.zoom,
locale: options.locale,
});
}; // Build designer ...
const result = buildDesigner(buildOptions);
const locale = options.locale; // Register toolbar event ...
const msg = I18nMsg.loadLocaleData(locale); if (
const mindplotStyle = (options.mode === 'viewonly') ? { top: 0 } : { top: 'inherit' }; options.mode === 'edition-owner' ||
return ( options.mode === 'edition-editor' ||
<IntlProvider locale={locale} messages={msg}> options.mode === 'edition-viewer' ||
{(options.mode !== 'viewonly') && options.mode === 'showcase'
<Toolbar ) {
editorMode={options.mode} const menu = new Menu(designer, 'toolbar');
onAction={onAction}
/> // If a node has focus, focus can be move to another node using the keys.
} designer.cleanScreen = () => {
<div id="mindplot" style={mindplotStyle} className="wise-editor"></div> menu.clear();
<div id="mindplot-tooltips" className="wise-editor"></div> };
<Footer editorMode={options.mode} /> }
</IntlProvider > return result;
); };
}
const locale = options.locale;
const msg = I18nMsg.loadLocaleData(locale);
const mindplotStyle = options.mode === 'viewonly' ? { top: 0 } : { top: 'inherit' };
// if the Toolbar is not hidden before the variable 'isMobile' is defined, it appears intermittently when the page loads
// if the Toolbar is not rendered, Menu.ts cant find buttons for create event listeners
// so, with this hack the Toolbar is rendered but no visible until the variable 'isMobile' is defined
const toolbarContainerStyle = isMobile === undefined ? { display: 'none' } : { display: 'block' };
return (
<IntlProvider locale={locale} messages={msg}>
<div style={toolbarContainerStyle}>
{options.mode !== 'viewonly' && !isMobile && (
<Toolbar editorMode={options.mode} onAction={onAction} />
)}
</div>
<div id="mindplot" style={mindplotStyle} className="wise-editor"></div>
<div id="mindplot-tooltips" className="wise-editor"></div>
<Footer editorMode={options.mode} isMobile={isMobile} />
</IntlProvider>
);
};
export default Editor; export default Editor;

View File

@ -2,31 +2,55 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@wisemapping/editor - Playground</title> <title>@wisemapping/editor - Playground</title>
<style> <style>
html * { html * {
font-family: Arial !important; font-family: Arial !important;
} }
tbody tr td:first-child { .section {
width: 20%; font-weight: bold;
} }
</style>
tbody tr td:first-child {
width: 20%;
}
</style>
</head> </head>
<body> <body>
<h1>@wisemapping/editor - Playground</h1> <h1>@wisemapping/editor - Playground</h1>
<p>You will find here a set of examples that shows how you can use integrate WiseMapping Editor</p> <p>You will find here a set of examples that shows how you can use integrate WiseMapping Editor</p>
<div> <div>
<ul> <p><span class="section">View Mode:</span>Simple integration to load and render mindaps in read only mode.</p>
<li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read <ul>
only mode</li> <li><a href="/viewmode.html?id=welcome">Welcome</a></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="/viewmode.html?id=sample1">Sample 1</a></li>
</ul> <li><a href="/viewmode.html?id=sample2">Sample 2</a></li>
</div> <li><a href="/viewmode.html?id=sample3">Sample 3</a></li>
<li><a href="/viewmode.html?id=sample4">Sample 4</a></li>
<li><a href="/viewmode.html?id=sample5">Sample 5</a></li>
<li><a href="/viewmode.html?id=sample6">Sample 6</a></li>
<li><a href="/viewmode.html?id=sample7">Sample 7</a></li>
<li><a href="/viewmode.html?id=sample8">Sample 8</a></li>
<li><a href="/viewmode.html?id=img-support">Image support</a></li>
<li><a href="/viewmode.html?id=error-on-load">Error on load</a></li>
<li><a href="/viewmode.html?id=complex">Complex</a></li>
<li><a href="/viewmode.html?id=huge">Huge</a></li>
<li><a href="/viewmode.html?id=icon-sample">Icon Sample</a></li>
</ul>
<p><span class="section">Editor Mode:</span>Example on how mindplot can be used for mindmap edition. Browser local storage is used for persistance.</p>
<ul>
<li><a href="/editor.html">Sample</a></li>
</ul>
<p><span class="section">Showcase Mode:</span>When an user wants to try the editor without creating an account.</p>
<ul>
<li><a href="/showcase.html">Sample</a></li>
</ul>
</div>
</body> </body>
</html> </html>

View File

@ -20,7 +20,7 @@ div#footer-logo {
} }
#floating-panel { #floating-panel {
bottom: 80px; bottom: 20px;
align-items: stretch; align-items: stretch;
} }

View File

@ -10,8 +10,8 @@
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root" class="mindplot-root"></div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WiseMapping - Editor </title>
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
<div id="root" class="mindplot-root"></div>
</body>
</html>

View File

@ -4,35 +4,14 @@
<head> <head>
<title>WiseMapping - View Mode </title> <title>WiseMapping - View Mode </title>
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
<link rel="icon" href="images/favicon.ico" type="image/x-icon"> <link rel="icon" href="images/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
</head> </head>
<body> <body>
<div id="root" onselectstart="return false;"></div> <div id="root" class="mindplot-root" onselectstart="return false;"></div>
<div id="footer">
<div id="footer-logo"><img src="../images/logo-small.svg" /></div>
<div id="footer-desc">
<p>The following example showcase rendering of mindmaps in read-only.</p>
<p>Select one map to render from the gallery: <select id="map-select">
<option value="welcome">welcome</option>
<option value="sample1">sample1</option>
<option value="sample2">sample2</option>
<option value="sample3">sample3</option>
<option value="sample4">sample4</option>
<option value="sample5">sample5</option>
<option value="sample6">sample6</option>
<option value="sample7">sample7</option>
<option value="sample8">sample8</option>
<option value="img-support">img-support</option>
<option value="error-on-load">error-on-load</option>
<option value="complex">complex</option>
<option value="huge">huge</option>
<option value="icon-sample">icon-sample</option>
</select>
</p>
</div>
</div> </div>
</body> </body>

View File

@ -0,0 +1,52 @@
/*
* Copyright [2021] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => {
const elem = document.getElementById('mindplot');
if (elem) {
elem.classList.add('ready');
}
});
};
const persistence = new LocalStorageManager('samples/{id}.wxml', false, false);
const mapId = 'welcome';
const options: EditorOptions = {
zoom: 0.8,
locked: false,
mapTitle: 'Develop WiseMapping',
mode: 'showcase',
locale: 'en',
enableKeyboardEvents: true,
};
ReactDOM.render(
<Editor
mapId={mapId}
options={options}
persistenceManager={persistence}
onAction={(action) => console.log('action called:', action)}
onLoad={initialization}
/>,
document.getElementById('root'),
);

View File

@ -10,6 +10,7 @@ const playgroundConfig = {
entry: { entry: {
viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'), viewmode: path.resolve(__dirname, './test/playground/map-render/js/viewmode'),
editor: path.resolve(__dirname, './test/playground/map-render/js/editor'), editor: path.resolve(__dirname, './test/playground/map-render/js/editor'),
showcase: path.resolve(__dirname, './test/playground/map-render/js/showcase'),
}, },
output: { output: {
path: path.resolve(__dirname, 'test/playground/dist'), path: path.resolve(__dirname, 'test/playground/dist'),
@ -43,11 +44,16 @@ const playgroundConfig = {
template: 'test/playground/map-render/html/viewmode.html', template: 'test/playground/map-render/html/viewmode.html',
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
chunks: ['editor'], chunks: ['editor'],
filename: 'editor.html', filename: 'editor.html',
template: 'test/playground/map-render/html/editor.html', template: 'test/playground/map-render/html/editor.html',
}), }),
], new HtmlWebpackPlugin({
chunks: ['showcase'],
filename: 'showcase.html',
template: 'test/playground/map-render/html/showcase.html',
}),
],
}; };
module.exports = merge(common, playgroundConfig); module.exports = merge(common, playgroundConfig);

View File

@ -22,6 +22,21 @@
"@typescript-eslint" "@typescript-eslint"
], ],
"rules": { "rules": {
// ignore errores when a line finishes with (setting this value to 0 ignores all errors)
"operator-linebreak": [
"error", "after", {
"overrides": {
"+": "ignore",
"-": "ignore",
":": "ignore",
"*": "ignore",
"?": "ignore",
">": "ignore",
"||": "ignore",
"&&": "ignore"
}
}
],
"no-underscore-dangle": "off", "no-underscore-dangle": "off",
"no-plusplus": "off", "no-plusplus": "off",
"no-param-reassign": "off", "no-param-reassign": "off",
@ -44,7 +59,8 @@
"ts": "never", "ts": "never",
"tsx": "never" "tsx": "never"
} }
] ],
"implicit-arrow-linebreak": "off"
}, },
"settings": { "settings": {
"import/resolver": { "import/resolver": {
@ -59,4 +75,4 @@
} }
} }
} }
} }

View File

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

View File

@ -82,7 +82,6 @@ class Designer extends Events {
super(); super();
$assert(options, 'options must be defined'); $assert(options, 'options must be defined');
$assert(options.zoom, 'zoom must be defined'); $assert(options.zoom, 'zoom must be defined');
$assert(options.containerSize, 'size must be defined');
$assert(divElement, 'divElement must be defined'); $assert(divElement, 'divElement must be defined');
// Set up i18n location ... // Set up i18n location ...
@ -91,10 +90,10 @@ class Designer extends Events {
this._options = options; this._options = options;
// Set full div elem render area ... // Set full div elem render area.The component must fill container size
if (options.containerSize) { // container is responsible for location and size
divElement.css(options.containerSize); divElement.css('width', '100%');
} divElement.css('height', '100%');
// Dispatcher manager ... // Dispatcher manager ...
const commandContext = new CommandContext(this); const commandContext = new CommandContext(this);
@ -138,14 +137,18 @@ class Designer extends Events {
private _registerWheelEvents(): void { private _registerWheelEvents(): void {
const zoomFactor = 1.02; const zoomFactor = 1.02;
document.addEventListener('wheel', (event: WheelEvent) => { document.addEventListener(
if (event.deltaX > 0 || event.deltaY > 0) { 'wheel',
this.zoomOut(zoomFactor); (event: WheelEvent) => {
} else { if (event.deltaX > 0 || event.deltaY > 0) {
this.zoomIn(zoomFactor); this.zoomOut(zoomFactor);
} } else {
event.preventDefault(); this.zoomIn(zoomFactor);
}, { passive: false }); }
event.preventDefault();
},
{ passive: false },
);
} }
getActionDispatcher(): StandaloneActionDispatcher { getActionDispatcher(): StandaloneActionDispatcher {
@ -187,8 +190,7 @@ class Designer extends Events {
screenManager.addEvent('dblclick', (event: MouseEvent) => { screenManager.addEvent('dblclick', (event: MouseEvent) => {
if (workspace.isWorkspaceEventsEnabled()) { if (workspace.isWorkspaceEventsEnabled()) {
const mousePos = screenManager.getWorkspaceMousePosition(event); const mousePos = screenManager.getWorkspaceMousePosition(event);
const centralTopic: CentralTopic = me.getModel() const centralTopic: CentralTopic = me.getModel().getCentralTopic();
.getCentralTopic();
const model = me._createChildModel(centralTopic, mousePos); const model = me._createChildModel(centralTopic, mousePos);
this._actionDispatcher.addTopics([model], [centralTopic.getId()]); this._actionDispatcher.addTopics([model], [centralTopic.getId()]);
@ -568,9 +570,9 @@ class Designer extends Events {
} }
/** /**
* @param {mindplot.Mindmap} mindmap * @param {mindplot.Mindmap} mindmap
* @throws will throw an error if mindmapModel is null or undefined * @throws will throw an error if mindmapModel is null or undefined
*/ */
loadMap(mindmap: Mindmap): void { loadMap(mindmap: Mindmap): void {
$assert(mindmap, 'mindmapModel can not be null'); $assert(mindmap, 'mindmapModel can not be null');
this._mindmap = mindmap; this._mindmap = mindmap;
@ -644,11 +646,11 @@ class Designer extends Events {
} }
/** /**
* @private * @private
* @param {mindplot.model.RelationshipModel} model * @param {mindplot.model.RelationshipModel} model
* @return {mindplot.Relationship} the relationship created to the model * @return {mindplot.Relationship} the relationship created to the model
* @throws will throw an error if model is null or undefined * @throws will throw an error if model is null or undefined
*/ */
private _relationshipModelToRelationship(model: RelationshipModel): Relationship { private _relationshipModelToRelationship(model: RelationshipModel): Relationship {
$assert(model, 'Node model can not be null'); $assert(model, 'Node model can not be null');
@ -667,9 +669,9 @@ class Designer extends Events {
} }
/** /**
* @param {mindplot.model.RelationshipModel} model * @param {mindplot.model.RelationshipModel} model
* @return {mindplot.Relationship} the relationship added to the mindmap * @return {mindplot.Relationship} the relationship added to the mindmap
*/ */
addRelationship(model: RelationshipModel): Relationship { addRelationship(model: RelationshipModel): Relationship {
const mindmap = this.getMindmap(); const mindmap = this.getMindmap();
mindmap.addRelationship(model); mindmap.addRelationship(model);
@ -677,9 +679,9 @@ class Designer extends Events {
} }
/** /**
* deletes the relationship from the linked topics, DesignerModel, Workspace and Mindmap * deletes the relationship from the linked topics, DesignerModel, Workspace and Mindmap
* @param {mindplot.Relationship} rel the relationship to delete * @param {mindplot.Relationship} rel the relationship to delete
*/ */
deleteRelationship(rel: Relationship): void { deleteRelationship(rel: Relationship): void {
const sourceTopic = rel.getSourceTopic(); const sourceTopic = rel.getSourceTopic();
sourceTopic.deleteRelationship(rel); sourceTopic.deleteRelationship(rel);
@ -704,9 +706,7 @@ class Designer extends Events {
const targetTopic = dmodel.findTopicById(targetTopicId); const targetTopic = dmodel.findTopicById(targetTopicId);
$assert( $assert(
targetTopic, targetTopic,
`targetTopic could not be found:${targetTopicId},${dmodel `targetTopic could not be found:${targetTopicId},${dmodel.getTopics().map((e) => e.getId())}`,
.getTopics()
.map((e) => e.getId())}`,
); );
// Build relationship line .... // Build relationship line ....
@ -797,16 +797,14 @@ class Designer extends Events {
} }
changeFontFamily(font: string) { changeFontFamily(font: string) {
const topicsIds = this.getModel() const topicsIds = this.getModel().filterTopicsIds();
.filterTopicsIds();
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.changeFontFamilyToTopic(topicsIds, font); this._actionDispatcher.changeFontFamilyToTopic(topicsIds, font);
} }
} }
changeFontStyle(): void { changeFontStyle(): void {
const topicsIds = this.getModel() const topicsIds = this.getModel().filterTopicsIds();
.filterTopicsIds();
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.changeFontStyleToTopic(topicsIds); this._actionDispatcher.changeFontStyleToTopic(topicsIds);
} }
@ -815,8 +813,7 @@ class Designer extends Events {
changeFontColor(color: string) { changeFontColor(color: string) {
$assert(color, 'color can not be null'); $assert(color, 'color can not be null');
const topicsIds = this.getModel() const topicsIds = this.getModel().filterTopicsIds();
.filterTopicsIds();
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.changeFontColorToTopic(topicsIds, color); this._actionDispatcher.changeFontColorToTopic(topicsIds, color);
@ -851,9 +848,8 @@ class Designer extends Events {
} }
changeTopicShape(shape: string) { changeTopicShape(shape: string) {
const validateFunc = (topic: Topic) => !( const validateFunc = (topic: Topic) =>
topic.getType() === 'CentralTopic' && shape === TopicShape.LINE !(topic.getType() === 'CentralTopic' && shape === TopicShape.LINE);
);
const validateError = 'Central Topic shape can not be changed to line figure.'; const validateError = 'Central Topic shape can not be changed to line figure.';
const topicsIds = this.getModel().filterTopicsIds(validateFunc, validateError); const topicsIds = this.getModel().filterTopicsIds(validateFunc, validateError);
@ -872,9 +868,13 @@ class Designer extends Events {
addIconType(iconType: string): void { addIconType(iconType: string): void {
const topicsIds = this.getModel().filterTopicsIds(); const topicsIds = this.getModel().filterTopicsIds();
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.addFeatureToTopic(topicsIds[0], TopicFeatureFactory.Icon.id as FeatureType, { this._actionDispatcher.addFeatureToTopic(
id: iconType, topicsIds[0],
}); TopicFeatureFactory.Icon.id as FeatureType,
{
id: iconType,
},
);
} }
} }

View File

@ -18,37 +18,25 @@
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import EditorRenderMode from './EditorRenderMode'; import EditorRenderMode from './EditorRenderMode';
import PersistenceManager from './PersistenceManager'; import PersistenceManager from './PersistenceManager';
import SizeType from './SizeType';
export type DesignerOptions = { export type DesignerOptions = {
zoom: number, zoom: number;
containerSize?: SizeType, mode: EditorRenderMode;
mode: EditorRenderMode, mapId?: string;
mapId?: string, container: string;
container: string, persistenceManager?: PersistenceManager;
persistenceManager?: PersistenceManager, saveOnLoad?: boolean;
saveOnLoad?: boolean, locale?: string;
locale?: string,
}; };
class OptionsBuilder { class OptionsBuilder {
static buildOptions(options: DesignerOptions): DesignerOptions { static buildOptions(options: DesignerOptions): DesignerOptions {
$assert(options.persistenceManager, 'persistence must be defined'); $assert(options.persistenceManager, 'persistence must be defined');
let { containerSize } = options;
if (options.containerSize == null) {
// If it has not been defined, use browser size ...
containerSize = {
width: window.screen.width,
height: window.screen.height,
};
}
const defaultOptions: DesignerOptions = { const defaultOptions: DesignerOptions = {
mode: 'edition-owner', mode: 'edition-owner',
zoom: 0.85, zoom: 0.85,
saveOnLoad: true, saveOnLoad: true,
containerSize,
container: 'mindplot', container: 'mindplot',
locale: 'en', locale: 'en',
}; };

View File

@ -16,15 +16,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { import { $assert, $defined } from '@wisemapping/core-js';
$assert, import { Group, ElementClass, Point } from '@wisemapping/web2d';
$defined,
} from '@wisemapping/core-js';
import {
Group,
ElementClass,
Point,
} from '@wisemapping/web2d';
import IconGroupRemoveTip from './IconGroupRemoveTip'; import IconGroupRemoveTip from './IconGroupRemoveTip';
import Icon from './Icon'; import Icon from './Icon';
import SizeType from './SizeType'; import SizeType from './SizeType';
@ -102,7 +95,10 @@ class IconGroup {
icon.setGroup(this); icon.setGroup(this);
icons.push(icon); icons.push(icon);
this._icons = icons.sort((a, b) => ORDER_BY_TYPE.get(a.getModel().getType()) - ORDER_BY_TYPE.get(b.getModel().getType())); this._icons = icons.sort(
(a, b) =>
ORDER_BY_TYPE.get(a.getModel().getType()) - ORDER_BY_TYPE.get(b.getModel().getType()),
);
// Add all the nodes back ... // Add all the nodes back ...
this._resize(this._icons.length); this._resize(this._icons.length);
@ -185,10 +181,7 @@ class IconGroup {
private _positionIcon(icon: Icon, order: number) { 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, IconGroup.ICON_PADDING);
iconSize * order + IconGroup.ICON_PADDING,
IconGroup.ICON_PADDING,
);
} }
static ICON_PADDING = 5; static ICON_PADDING = 5;

View File

@ -21,7 +21,7 @@ import { Point } from '@wisemapping/web2d';
class ScreenManager { class ScreenManager {
private _divContainer: JQuery; private _divContainer: JQuery;
private _padding: { x: number; y: number; }; private _padding: { x: number; y: number };
private _clickEvents; private _clickEvents;
@ -34,20 +34,23 @@ class ScreenManager {
// Ignore default click event propagation. Prevent 'click' event on drag. // Ignore default click event propagation. Prevent 'click' event on drag.
this._clickEvents = []; this._clickEvents = [];
this._divContainer.bind('click', (event: { stopPropagation: () => void; }) => { this._divContainer.bind('click', (event: { stopPropagation: () => void }) => {
event.stopPropagation(); event.stopPropagation();
}); });
this._divContainer.bind('dblclick', (event: { stopPropagation: () => void; preventDefault: () => void; }) => { this._divContainer.bind(
event.stopPropagation(); 'dblclick',
event.preventDefault(); (event: { stopPropagation: () => void; preventDefault: () => void }) => {
}); event.stopPropagation();
event.preventDefault();
},
);
} }
/** /**
* Return the current visibile area in the browser. * Return the current visibile area in the browser.
*/ */
getVisibleBrowserSize(): { width: number, height: number } { getVisibleBrowserSize(): { width: number; height: number } {
return { return {
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight - Number.parseInt(this._divContainer.css('top'), 10), height: window.innerHeight - Number.parseInt(this._divContainer.css('top'), 10),
@ -82,10 +85,31 @@ class ScreenManager {
} }
} }
getWorkspaceMousePosition(event: MouseEvent) { private mouseEvents = ['mousedown', 'mouseup', 'mousemove', 'dblclick', 'click'];
// Retrieve current mouse position.
let x = event.clientX; private tocuchEvents = ['touchstart', 'touchend', 'touchmove'];
let y = event.clientY;
// the received type was changed from MouseEvent to "any", because we must support touch events
getWorkspaceMousePosition(event: any) {
let x;
let y;
if (this.mouseEvents.includes(event.type)) {
// Retrieve current mouse position.
x = event.clientX;
y = event.clientY;
} else if (this.tocuchEvents.includes(event.type)) {
x = event.touches[0].clientX;
y = event.touches[0].clientY;
}
// if value is zero assert throws error
if (x !== 0) {
$assert(x, `clientX can not be null, eventType= ${event.type}`);
}
if (y !== 0) {
$assert(y, `clientY can not be null, eventType= ${event.type}`);
}
// Adjust the deviation of the container positioning ... // Adjust the deviation of the container positioning ...
const containerPosition = this.getContainer().position(); const containerPosition = this.getContainer().position();

View File

@ -130,11 +130,19 @@ class Workspace {
setZoom(zoom: number, center = false): void { setZoom(zoom: number, center = false): void {
this._zoom = zoom; this._zoom = zoom;
const workspace = this._workspace; const workspace = this._workspace;
const newVisibleAreaSize = this._screenManager.getVisibleBrowserSize();
// Update coord scale... const divContainer = this._screenManager.getContainer();
const newCoordWidth = zoom * this._containerSize.width; const containerWidth = divContainer.width();
const newCoordHeight = zoom * this._containerSize.height; const containerHeight = divContainer.height();
const newVisibleAreaSize = { width: containerWidth, height: containerHeight };
// - svg must fit container size
const svgElement = divContainer.find('svg');
svgElement.attr('width', containerWidth);
svgElement.attr('height', containerHeight);
// - svg viewPort must fit container size with zoom adjustment
const newCoordWidth = containerWidth * this._zoom;
const newCoordHeight = containerHeight * this._zoom;
let coordOriginX: number; let coordOriginX: number;
let coordOriginY: number; let coordOriginY: number;
@ -222,11 +230,14 @@ class Workspace {
wasDragged = true; wasDragged = true;
}; };
screenManager.addEvent('mousemove', workspace._mouseMoveListener); screenManager.addEvent('mousemove', workspace._mouseMoveListener);
screenManager.addEvent('touchmove', workspace._mouseMoveListener);
// Register mouse up listeners ... // Register mouse up listeners ...
workspace._mouseUpListener = () => { workspace._mouseUpListener = () => {
screenManager.removeEvent('mousemove', workspace._mouseMoveListener); screenManager.removeEvent('mousemove', workspace._mouseMoveListener);
screenManager.removeEvent('mouseup', workspace._mouseUpListener); screenManager.removeEvent('mouseup', workspace._mouseUpListener);
screenManager.removeEvent('touchmove', workspace._mouseUpListener);
screenManager.removeEvent('touchend', workspace._mouseMoveListener);
workspace._mouseUpListener = null; workspace._mouseUpListener = null;
workspace._mouseMoveListener = null; workspace._mouseMoveListener = null;
window.document.body.style.cursor = 'default'; window.document.body.style.cursor = 'default';
@ -241,12 +252,14 @@ class Workspace {
} }
}; };
screenManager.addEvent('mouseup', workspace._mouseUpListener); screenManager.addEvent('mouseup', workspace._mouseUpListener);
screenManager.addEvent('touchend', workspace._mouseUpListener);
} }
} else { } else {
workspace._mouseUpListener(); workspace._mouseUpListener();
} }
}; };
screenManager.addEvent('mousedown', mouseDownListener); screenManager.addEvent('mousedown', mouseDownListener);
screenManager.addEvent('touchstart', mouseDownListener);
} }
} }

View File

@ -42,8 +42,8 @@ class MoveControlPointCommand extends Command {
* @classdesc This command handles do/undo of changing the control points of a relationship * @classdesc This command handles do/undo of changing the control points of a relationship
* arrow. These are the two points that appear when the relationship is on focus. They * arrow. These are the two points that appear when the relationship is on focus. They
* influence how the arrow is drawn (not the source or the destination topic nor the arrow * influence how the arrow is drawn (not the source or the destination topic nor the arrow
* direction) * direction)
*/ */
constructor(ctrlPointController: ControlPoint, point: number) { constructor(ctrlPointController: ControlPoint, point: number) {
$assert(ctrlPointController, 'line can not be null'); $assert(ctrlPointController, 'line can not be null');
$assert($defined(point), 'point can not be null'); $assert($defined(point), 'point can not be null');

View File

@ -45,9 +45,9 @@ abstract class AbstractBasicSorter extends ChildrenSorterStrategy {
} else { } else {
let childrenHeight = 0; let childrenHeight = 0;
children.forEach(((child) => { children.forEach((child) => {
childrenHeight += this._computeChildrenHeight(treeSet, child, heightCache); childrenHeight += this._computeChildrenHeight(treeSet, child, heightCache);
})); });
result = Math.max(height, childrenHeight); result = Math.max(height, childrenHeight);
} }

View File

@ -34,14 +34,8 @@ class BalancedSorter extends AbstractBasicSorter {
// If it is a dragged node... // If it is a dragged node...
if (node) { if (node) {
$assert($defined(position), 'position cannot be null for predict in dragging'); $assert($defined(position), 'position cannot be null for predict in dragging');
const nodeDirection = this._getRelativeDirection( const nodeDirection = this._getRelativeDirection(rootNode.getPosition(), node.getPosition());
rootNode.getPosition(), const positionDirection = this._getRelativeDirection(rootNode.getPosition(), position);
node.getPosition(),
);
const positionDirection = this._getRelativeDirection(
rootNode.getPosition(),
position,
);
const siblings = graph.getSiblings(node); const siblings = graph.getSiblings(node);
const sameParent = parent === graph.getParent(node); const sameParent = parent === graph.getParent(node);
@ -50,7 +44,8 @@ class BalancedSorter extends AbstractBasicSorter {
} }
} }
let right; let left; let right;
let left;
if (!position) { if (!position) {
right = this._getChildrenForOrder(parent, graph, 0); right = this._getChildrenForOrder(parent, graph, 0);
left = this._getChildrenForOrder(parent, graph, 1); left = this._getChildrenForOrder(parent, graph, 1);
@ -58,19 +53,16 @@ class BalancedSorter extends AbstractBasicSorter {
// Filter nodes on one side.. // Filter nodes on one side..
let order; let order;
if (position) { if (position) {
order = position.x > rootNode.getPosition().x order = position.x > rootNode.getPosition().x ? 0 : 1;
? 0
: 1;
} else { } else {
order = right.length - left.length > 0 order = right.length - left.length > 0 ? 1 : 0;
? 1
: 0;
} }
const direction = order % 2 === 0 ? 1 : -1; const direction = order % 2 === 0 ? 1 : -1;
// Exclude the dragged node (if set) // Exclude the dragged node (if set)
const children = this._getChildrenForOrder(parent, graph, order) const children = this._getChildrenForOrder(parent, graph, order).filter(
.filter((child) => child !== node); (child) => child !== node,
);
// No children? // No children?
if (children.length === 0) { if (children.length === 0) {
@ -78,10 +70,9 @@ class BalancedSorter extends AbstractBasicSorter {
order, order,
{ {
x: x:
parent.getPosition().x parent.getPosition().x +
+ direction direction *
* (parent.getSize().width / 2 (parent.getSize().width / 2 + BalancedSorter.INTERNODE_HORIZONTAL_PADDING * 2),
+ BalancedSorter.INTERNODE_HORIZONTAL_PADDING * 2),
y: parent.getPosition().y, y: parent.getPosition().y,
}, },
]; ];
@ -94,9 +85,10 @@ class BalancedSorter extends AbstractBasicSorter {
children.forEach((child, index) => { children.forEach((child, index) => {
const cpos = child.getPosition(); const cpos = child.getPosition();
if (newestPosition.y > cpos.y) { if (newestPosition.y > cpos.y) {
const yOffset = child === last const yOffset =
? child.getSize().height + BalancedSorter.INTERNODE_VERTICAL_PADDING * 2 child === last
: (children[index + 1].getPosition().y - child.getPosition().y) / 2; ? child.getSize().height + BalancedSorter.INTERNODE_VERTICAL_PADDING * 2
: (children[index + 1].getPosition().y - child.getPosition().y) / 2;
result = [child.getOrder() + 2, { x: cpos.x, y: cpos.y + yOffset }]; result = [child.getOrder() + 2, { x: cpos.x, y: cpos.y + yOffset }];
} }
}); });
@ -109,9 +101,9 @@ class BalancedSorter extends AbstractBasicSorter {
{ {
x: first.getPosition().x, x: first.getPosition().x,
y: y:
first.getPosition().y first.getPosition().y -
- first.getSize().height first.getSize().height -
- BalancedSorter.INTERNODE_VERTICAL_PADDING * 2, BalancedSorter.INTERNODE_VERTICAL_PADDING * 2,
}, },
]; ];
} }
@ -202,10 +194,11 @@ class BalancedSorter extends AbstractBasicSorter {
} }
const yOffset = ysum + heights[i].height / 2; const yOffset = ysum + heights[i].height / 2;
const xOffset = direction const xOffset =
* (node.getSize().width / 2 direction *
+ heights[i].width / 2 (node.getSize().width / 2 +
+ +BalancedSorter.INTERNODE_HORIZONTAL_PADDING); heights[i].width / 2 +
+BalancedSorter.INTERNODE_HORIZONTAL_PADDING);
$assert(!Number.isNaN(xOffset), 'xOffset can not be null'); $assert(!Number.isNaN(xOffset), 'xOffset can not be null');
$assert(!Number.isNaN(yOffset), 'yOffset can not be null'); $assert(!Number.isNaN(yOffset), 'yOffset can not be null');
@ -226,9 +219,9 @@ class BalancedSorter extends AbstractBasicSorter {
const order = i === 0 && factor === 1 ? 1 : factor * i; const order = i === 0 && factor === 1 ? 1 : factor * i;
$assert( $assert(
children[i].getOrder() === order, children[i].getOrder() === order,
`Missing order elements. Missing order: ${i * factor `Missing order elements. Missing order: ${
}. Parent:${node.getId() i * factor
},Node:${children[i].getId()}`, }. Parent:${node.getId()},Node:${children[i].getId()}`,
); );
} }
} }
@ -242,8 +235,9 @@ class BalancedSorter extends AbstractBasicSorter {
} }
protected _getChildrenForOrder(parent: Node, graph: RootedTreeSet, order: number) { protected _getChildrenForOrder(parent: Node, graph: RootedTreeSet, order: number) {
return this._getSortedChildren(graph, parent) return this._getSortedChildren(graph, parent).filter(
.filter((child) => child.getOrder() % 2 === order % 2); (child) => child.getOrder() % 2 === order % 2,
);
} }
protected _getVerticalPadding(): number { protected _getVerticalPadding(): number {

View File

@ -119,8 +119,9 @@ class LinkEditor extends BootstrapDialog {
* @return {Boolean} true if the url is valid * @return {Boolean} true if the url is valid
*/ */
private checkURL(url: string): boolean { private checkURL(url: string): boolean {
const regex = /^(http|https):\/\/[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i; const regex =
return (regex.test(url)); /^(http|https):\/\/[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i;
return regex.test(url);
} }
/** /**
@ -129,7 +130,7 @@ class LinkEditor extends BootstrapDialog {
*/ */
private hasProtocol(url: string): boolean { private hasProtocol(url: string): boolean {
const regex = /^(http|https):\/\//i; const regex = /^(http|https):\/\//i;
return (regex.test(url)); return regex.test(url);
} }
/** /**

View File

@ -35,7 +35,8 @@ class LinkIconTooltip extends FloatingTip {
placement: 'bottom', placement: 'bottom',
title: $msg('LINK'), title: $msg('LINK'),
trigger: 'manual', trigger: 'manual',
template: '<div id="linkPopover" class="popover" onmouseover="jQuery(this).mouseleave(function() {jQuery(this).fadeOut(200); });" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>', template:
'<div id="linkPopover" class="popover" onmouseover="jQuery(this).mouseleave(function() {jQuery(this).fadeOut(200); });" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>',
destroyOnExit: true, destroyOnExit: true,
}); });
} }

View File

@ -33,17 +33,11 @@ import DesignerKeyboard from './components/DesignerKeyboard';
import EditorRenderMode from './components/EditorRenderMode'; import EditorRenderMode from './components/EditorRenderMode';
import ImageIcon from './components/ImageIcon'; import ImageIcon from './components/ImageIcon';
import { import { buildDesigner } from './components/DesignerBuilder';
buildDesigner,
} from './components/DesignerBuilder';
import { import { $notify } from './components/widget/ToolbarNotifier';
$notify,
} from './components/widget/ToolbarNotifier';
import { import { $msg } from './components/Messages';
$msg,
} from './components/Messages';
// This hack is required to initialize Bootstrap. In future, this should be removed. // This hack is required to initialize Bootstrap. In future, this should be removed.
const globalAny: any = global; const globalAny: any = global;

View File

@ -16,11 +16,8 @@
* limitations under the License. * limitations under the License.
*/ */
import jquery from 'jquery'; import jquery from 'jquery';
import { import {} from './components/widget/ToolbarNotifier';
} from './components/widget/ToolbarNotifier'; import { buildDesigner } from './components/DesignerBuilder';
import {
buildDesigner,
} from './components/DesignerBuilder';
import PersistenceManager from './components/PersistenceManager'; import PersistenceManager from './components/PersistenceManager';
import LocalStorageManager from './components/LocalStorageManager'; import LocalStorageManager from './components/LocalStorageManager';
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder'; import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
@ -43,16 +40,14 @@ const persistence: PersistenceManager = new LocalStorageManager(
const params = new URLSearchParams(window.location.search.substring(1)); const params = new URLSearchParams(window.location.search.substring(1));
const zoomParam = Number.parseFloat(params.get('zoom')); const zoomParam = Number.parseFloat(params.get('zoom'));
const options = DesignerOptionsBuilder.buildOptions( const options = DesignerOptionsBuilder.buildOptions({
{ persistenceManager: persistence,
persistenceManager: persistence, mode: 'viewonly',
mode: 'viewonly', mapId: global.mapId,
mapId: global.mapId, container: 'mindplot',
container: 'mindplot', zoom: zoomParam || global.userOptions.zoom,
zoom: zoomParam || global.userOptions.zoom, locale: global.locale,
locale: global.locale, });
},
);
// Build designer ... // Build designer ...
const designer = buildDesigner(options); const designer = buildDesigner(options);

View File

@ -19,88 +19,73 @@ import EditorPage from './components/editor-page';
import AppConfig from './classes/app-config'; import AppConfig from './classes/app-config';
import withSessionExpirationHandling from './components/HOCs/withSessionExpirationHandling'; import withSessionExpirationHandling from './components/HOCs/withSessionExpirationHandling';
declare module '@mui/styles/defaultTheme' { declare module '@mui/styles/defaultTheme' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface // eslint-disable-next-line @typescript-eslint/no-empty-interface
interface DefaultTheme extends Theme { } interface DefaultTheme extends Theme {}
} }
// Google Analytics Initialization. // Google Analytics Initialization.
ReactGA.initialize([ ReactGA.initialize([
{ {
trackingId: AppConfig.getGoogleAnalyticsAccount(), trackingId: AppConfig.getGoogleAnalyticsAccount(),
} },
]); ]);
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
refetchIntervalInBackground: false, refetchIntervalInBackground: false,
staleTime: 5 * 1000 * 60, // 10 minutes staleTime: 5 * 1000 * 60, // 10 minutes
},
}, },
},
}); });
const App = (): ReactElement => { const App = (): ReactElement => {
const locale = AppI18n.getDefaultLocale(); const locale = AppI18n.getDefaultLocale();
const EnhacedEditorPage = withSessionExpirationHandling(EditorPage); const EnhacedEditorPage = withSessionExpirationHandling(EditorPage);
return locale.message ? ( return locale.message ? (
<Provider store={store}> <Provider store={store}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<IntlProvider <IntlProvider
locale={locale.code} locale={locale.code}
defaultLocale={Locales.EN.code} defaultLocale={Locales.EN.code}
messages={locale.message as Record<string, string>} messages={locale.message as Record<string, string>}
> >
<StyledEngineProvider injectFirst> <StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<Router> <Router>
<Switch> <Switch>
<Route exact path="/"> <Route exact path="/">
<Redirect to="/c/login" /> <Redirect to="/c/login" />
</Route> </Route>
<Route path="/c/login" <Route path="/c/login" component={LoginPage} />
component={LoginPage} <Route path="/c/registration" component={RegistationPage} />
/> <Route path="/c/registration-success" component={RegistrationSuccessPage} />
<Route <Route path="/c/forgot-password" component={ForgotPasswordPage} />
path="/c/registration" <Route path="/c/forgot-password-success" component={ForgotPasswordSuccessPage} />
component={RegistationPage} <Route
/> exact
<Route path="/c/maps/"
path="/c/registration-success" component={withSessionExpirationHandling(MapsPage)}
component={RegistrationSuccessPage} />
/> <Route exact path="/c/maps/:id/edit">
<Route <EnhacedEditorPage isTryMode={false} />
path="/c/forgot-password" </Route>
component={ForgotPasswordPage} <Route exact path="/c/maps/:id/try">
/> <EnhacedEditorPage isTryMode={true} />
<Route </Route>
path="/c/forgot-password-success" </Switch>
component={ForgotPasswordSuccessPage} </Router>
/> </ThemeProvider>
<Route </StyledEngineProvider>
exact path="/c/maps/" </IntlProvider>
component={withSessionExpirationHandling(MapsPage)} </QueryClientProvider>
/> </Provider>
<Route exact path="/c/maps/:id/edit"> ) : (
<EnhacedEditorPage isTryMode={false} /> <div>Loading ... </div>
</Route> );
<Route exact path="/c/maps/:id/try">
<EnhacedEditorPage isTryMode={true} />
</Route>
</Switch>
</Router>
</ThemeProvider>
</StyledEngineProvider>
</IntlProvider>
</QueryClientProvider>
</Provider>
) : (
<div>Loading ... </div>
);
}; };
export default App; export default App;

View File

@ -6,27 +6,27 @@ import ClientHealthSentinel from '../../classes/client/client-health-sentinel';
import { activeInstance, sessionExpired } from '../../redux/clientSlice'; import { activeInstance, sessionExpired } from '../../redux/clientSlice';
function withSessionExpirationHandling<T>(Component: ComponentType<T>) { function withSessionExpirationHandling<T>(Component: ComponentType<T>) {
return (hocProps: T): React.ReactElement => { return (hocProps: T): React.ReactElement => {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
if (client) { if (client) {
client.onSessionExpired(() => { client.onSessionExpired(() => {
dispatch(sessionExpired()); dispatch(sessionExpired());
}); });
} else { } else {
console.warn('Session expiration wont be handled because could not find client'); console.warn('Session expiration wont be handled because could not find client');
} }
}, []); }, []);
return ( return (
<> <>
<ClientHealthSentinel /> <ClientHealthSentinel />
<Component {...hocProps} />; <Component {...hocProps} />
</> </>
); );
}; };
} }
export default withSessionExpirationHandling; export default withSessionExpirationHandling;

View File

@ -43,313 +43,315 @@ import LabelDeleteConfirm from './maps-list/label-delete-confirm';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import { withStyles } from '@mui/styles'; import { withStyles } from '@mui/styles';
export type Filter = GenericFilter | LabelFilter; export type Filter = GenericFilter | LabelFilter;
export interface GenericFilter { export interface GenericFilter {
type: 'public' | 'all' | 'starred' | 'shared' | 'label' | 'owned'; type: 'public' | 'all' | 'starred' | 'shared' | 'label' | 'owned';
} }
export interface LabelFilter { export interface LabelFilter {
type: 'label'; type: 'label';
label: Label; label: Label;
} }
interface ToolbarButtonInfo { interface ToolbarButtonInfo {
filter: GenericFilter | LabelFilter; filter: GenericFilter | LabelFilter;
label: string; label: string;
icon: React.ReactElement; icon: React.ReactElement;
} }
const MapsPage = (): ReactElement => { const MapsPage = (): ReactElement => {
const classes = useStyles(); const classes = useStyles();
const [filter, setFilter] = React.useState<Filter>({ type: 'all' }); const [filter, setFilter] = React.useState<Filter>({ type: 'all' });
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined); const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined);
const [labelToDelete, setLabelToDelete] = React.useState<number | null>(null); const [labelToDelete, setLabelToDelete] = React.useState<number | null>(null);
// Reload based on user preference ... // Reload based on user preference ...
const userLocale = AppI18n.getUserLocale(); const userLocale = AppI18n.getUserLocale();
const cache = createIntlCache(); const cache = createIntlCache();
const intl = createIntl({ const intl = createIntl(
defaultLocale: userLocale.code, {
locale: Locales.EN.code, defaultLocale: userLocale.code,
messages: userLocale.message locale: Locales.EN.code,
}, cache) messages: userLocale.message,
},
cache,
);
useEffect(() => { useEffect(() => {
document.title = intl.formatMessage({ document.title = intl.formatMessage({
id: 'maps.page-title', id: 'maps.page-title',
defaultMessage: 'My Maps | WiseMapping', defaultMessage: 'My Maps | WiseMapping',
});
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Maps List' });
}, []);
const mutation = useMutation((id: number) => client.deleteLabel(id), {
onSuccess: () => {
queryClient.invalidateQueries('labels');
queryClient.invalidateQueries('maps');
},
onError: (error) => {
console.error(`Unexpected error ${error}`);
},
}); });
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Maps List' });
}, []);
const handleMenuClick = (filter: Filter) => { const mutation = useMutation((id: number) => client.deleteLabel(id), {
queryClient.invalidateQueries('maps'); onSuccess: () => {
setFilter(filter); queryClient.invalidateQueries('labels');
}; queryClient.invalidateQueries('maps');
},
onError: (error) => {
console.error(`Unexpected error ${error}`);
},
});
const handleLabelDelete = (id: number) => { const handleMenuClick = (filter: Filter) => {
mutation.mutate(id); queryClient.invalidateQueries('maps');
}; setFilter(filter);
};
const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', () => { const handleLabelDelete = (id: number) => {
return client.fetchLabels(); mutation.mutate(id);
}); };
const labels: Label[] = data ? data : []; const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', () => {
const filterButtons: ToolbarButtonInfo[] = [ return client.fetchLabels();
{ });
filter: { type: 'all' },
label: intl.formatMessage({ id: 'maps.nav-all', defaultMessage: 'All' }),
icon: <ScatterPlotTwoTone color="secondary" />,
},
{
filter: { type: 'owned' },
label: intl.formatMessage({ id: 'maps.nav-onwned', defaultMessage: 'Owned' }),
icon: <PersonOutlineTwoTone color="secondary" />,
},
{
filter: { type: 'starred' },
label: intl.formatMessage({ id: 'maps.nav-starred', defaultMessage: 'Starred' }),
icon: <StarTwoTone color="secondary" />,
},
{
filter: { type: 'shared' },
label: intl.formatMessage({ id: 'maps.nav-shared', defaultMessage: 'Shared with me' }),
icon: <ShareTwoTone color="secondary" />,
},
{
filter: { type: 'public' },
label: intl.formatMessage({ id: 'maps.nav-public', defaultMessage: 'Public' }),
icon: <PublicTwoTone color="secondary" />,
},
];
labels.forEach((l) => const labels: Label[] = data ? data : [];
filterButtons.push({ const filterButtons: ToolbarButtonInfo[] = [
filter: { type: 'label', label: l }, {
label: l.title, filter: { type: 'all' },
icon: <LabelTwoTone style={{ color: l.color ? l.color : 'inherit' }} />, label: intl.formatMessage({ id: 'maps.nav-all', defaultMessage: 'All' }),
}) icon: <ScatterPlotTwoTone color="secondary" />,
); },
{
filter: { type: 'owned' },
label: intl.formatMessage({ id: 'maps.nav-onwned', defaultMessage: 'Owned' }),
icon: <PersonOutlineTwoTone color="secondary" />,
},
{
filter: { type: 'starred' },
label: intl.formatMessage({ id: 'maps.nav-starred', defaultMessage: 'Starred' }),
icon: <StarTwoTone color="secondary" />,
},
{
filter: { type: 'shared' },
label: intl.formatMessage({ id: 'maps.nav-shared', defaultMessage: 'Shared with me' }),
icon: <ShareTwoTone color="secondary" />,
},
{
filter: { type: 'public' },
label: intl.formatMessage({ id: 'maps.nav-public', defaultMessage: 'Public' }),
icon: <PublicTwoTone color="secondary" />,
},
];
return ( labels.forEach((l) =>
<IntlProvider filterButtons.push({
locale={userLocale.code} filter: { type: 'label', label: l },
defaultLocale={Locales.EN.code} label: l.title,
messages={userLocale.message} icon: <LabelTwoTone style={{ color: l.color ? l.color : 'inherit' }} />,
}),
);
return (
<IntlProvider
locale={userLocale.code}
defaultLocale={Locales.EN.code}
messages={userLocale.message}
>
<div className={classes.root}>
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open,
})}
variant="outlined"
elevation={0}
> >
<div className={classes.root}> <Toolbar>
<AppBar <Tooltip
position="fixed" arrow={true}
className={clsx(classes.appBar, { title={intl.formatMessage({
[classes.appBarShift]: open, id: 'maps.create-tooltip',
})} defaultMessage: 'Create a new mindmap',
variant="outlined" })}
elevation={0} >
> <Button
<Toolbar> color="primary"
<Tooltip data-testid="create"
arrow={true} size="medium"
title={intl.formatMessage({ variant="contained"
id: 'maps.create-tooltip', type="button"
defaultMessage: 'Create a new mindmap', disableElevation={true}
})} startIcon={<AddCircleTwoTone />}
> className={classes.newMapButton}
<Button onClick={() => setActiveDialog('create')}
color="primary" >
data-testid="create" <FormattedMessage id="action.new" defaultMessage="New map" />
size="medium" </Button>
variant="contained" </Tooltip>
type="button"
disableElevation={true}
startIcon={<AddCircleTwoTone />}
className={classes.newMapButton}
onClick={() => setActiveDialog('create')}
>
<FormattedMessage id="action.new" defaultMessage="New map" />
</Button>
</Tooltip>
<Tooltip <Tooltip
arrow={true} arrow={true}
title={intl.formatMessage({ title={intl.formatMessage({
id: 'maps.import-desc', id: 'maps.import-desc',
defaultMessage: 'Import from other tools', defaultMessage: 'Import from other tools',
})} })}
> >
<Button <Button
color="primary" color="primary"
size="medium" size="medium"
variant="outlined" variant="outlined"
type="button" type="button"
disableElevation={true} disableElevation={true}
startIcon={<CloudUploadTwoTone />} startIcon={<CloudUploadTwoTone />}
className={classes.importButton} className={classes.importButton}
onClick={() => setActiveDialog('import')} onClick={() => setActiveDialog('import')}
> >
<FormattedMessage id="action.import" defaultMessage="Import" /> <FormattedMessage id="action.import" defaultMessage="Import" />
</Button> </Button>
</Tooltip> </Tooltip>
<ActionDispatcher <ActionDispatcher
action={activeDialog} action={activeDialog}
onClose={() => setActiveDialog(undefined)} onClose={() => setActiveDialog(undefined)}
mapsId={[]} mapsId={[]}
fromEditor fromEditor
/> />
<div className={classes.rightButtonGroup}> <div className={classes.rightButtonGroup}>
<LanguageMenu /> <LanguageMenu />
<HelpMenu /> <HelpMenu />
<AccountMenu /> <AccountMenu />
</div>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
}),
}}
>
<div style={{ padding: '20px 0 20px 15px' }} key="logo">
<img src={logoIcon} alt="logo" />
</div>
<List component="nav">
{filterButtons.map((buttonInfo) => {
return (
<StyleListItem
icon={buttonInfo.icon}
label={buttonInfo.label}
filter={buttonInfo.filter}
active={filter}
onClick={handleMenuClick}
onDelete={setLabelToDelete}
key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
/>
);
})}
</List>
<div
style={{ position: 'absolute', bottom: '10px', left: '20px' }}
key="power-by"
>
<Link href="http://www.wisemapping.org/">
<img src={poweredByIcon} alt="Powered By WiseMapping" />
</Link>
</div>
</Drawer>
<main className={classes.content}>
<div className={classes.toolbar} />
<MapsList filter={filter} />
</main>
</div> </div>
{labelToDelete && <LabelDeleteConfirm </Toolbar>
onClose={() => setLabelToDelete(null)} </AppBar>
onConfirm={() => { <Drawer
handleLabelDelete(labelToDelete); variant="permanent"
setLabelToDelete(null); className={clsx(classes.drawer, {
}} [classes.drawerOpen]: open,
label={labels.find(l => l.id === labelToDelete)} })}
/>} classes={{
</IntlProvider> paper: clsx({
); [classes.drawerOpen]: open,
}),
}}
>
<div style={{ padding: '20px 0 20px 15px' }} key="logo">
<img src={logoIcon} alt="logo" />
</div>
<List component="nav">
{filterButtons.map((buttonInfo) => {
return (
<StyleListItem
icon={buttonInfo.icon}
label={buttonInfo.label}
filter={buttonInfo.filter}
active={filter}
onClick={handleMenuClick}
onDelete={setLabelToDelete}
key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
/>
);
})}
</List>
<div style={{ position: 'absolute', bottom: '10px', left: '20px' }} key="power-by">
<Link href="http://www.wisemapping.org/">
<img src={poweredByIcon} alt="Powered By WiseMapping" />
</Link>
</div>
</Drawer>
<main className={classes.content}>
<div className={classes.toolbar} />
<MapsList filter={filter} />
</main>
</div>
{labelToDelete && (
<LabelDeleteConfirm
onClose={() => setLabelToDelete(null)}
onConfirm={() => {
handleLabelDelete(labelToDelete);
setLabelToDelete(null);
}}
label={labels.find((l) => l.id === labelToDelete)}
/>
)}
</IntlProvider>
);
}; };
interface ListItemProps { interface ListItemProps {
icon: React.ReactElement; icon: React.ReactElement;
label: string; label: string;
filter: Filter; filter: Filter;
active?: Filter; active?: Filter;
onClick: (filter: Filter) => void; onClick: (filter: Filter) => void;
onDelete?: (id: number) => void; onDelete?: (id: number) => void;
} }
// https://stackoverflow.com/questions/61486061/how-to-set-selected-and-hover-color-of-listitem-in-mui // https://stackoverflow.com/questions/61486061/how-to-set-selected-and-hover-color-of-listitem-in-mui
const CustomListItem = withStyles({ const CustomListItem = withStyles({
root: { root: {
"&$selected": { '&$selected': {
backgroundColor: "rgb(210, 140, 5)", backgroundColor: 'rgb(210, 140, 5)',
color: "white", color: 'white',
"& .MuiListItemIcon-root": { '& .MuiListItemIcon-root': {
color: "white" color: 'white',
} },
},
"&$selected:hover": {
backgroundColor: "rgb(210, 140, 5)",
color: "white",
"& .MuiListItemIcon-root": {
color: "white"
}
},
}, },
selected: {} '&$selected:hover': {
backgroundColor: 'rgb(210, 140, 5)',
color: 'white',
'& .MuiListItemIcon-root': {
color: 'white',
},
},
},
selected: {},
})(ListItemButton); })(ListItemButton);
const StyleListItem = (props: ListItemProps) => { const StyleListItem = (props: ListItemProps) => {
const icon = props.icon; const icon = props.icon;
const label = props.label; const label = props.label;
const filter = props.filter; const filter = props.filter;
const activeFilter = props.active; const activeFilter = props.active;
const onClick = props.onClick; const onClick = props.onClick;
const onDeleteLabel = props.onDelete; const onDeleteLabel = props.onDelete;
const isSelected = const isSelected =
activeFilter && activeFilter &&
activeFilter.type == filter.type && activeFilter.type == filter.type &&
(activeFilter.type != 'label' || (activeFilter.type != 'label' ||
(activeFilter as LabelFilter).label == (filter as LabelFilter).label); (activeFilter as LabelFilter).label == (filter as LabelFilter).label);
const handleOnClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, filter: Filter) => { const handleOnClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, filter: Filter) => {
event.stopPropagation(); event.stopPropagation();
onClick(filter); onClick(filter);
}; };
const handleOnDelete = ( const handleOnDelete = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>, event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
filter: Filter filter: Filter,
) => { ) => {
event.stopPropagation(); event.stopPropagation();
if (!onDeleteLabel) { if (!onDeleteLabel) {
throw 'Illegal state exeption'; throw 'Illegal state exeption';
} }
onDeleteLabel((filter as LabelFilter).label.id); onDeleteLabel((filter as LabelFilter).label.id);
}; };
return ( return (
<CustomListItem selected={isSelected} onClick={(e) => handleOnClick(e, filter)}> <CustomListItem selected={isSelected} onClick={(e) => handleOnClick(e, filter)}>
<ListItemIcon>{icon}</ListItemIcon> <ListItemIcon>{icon}</ListItemIcon>
<ListItemText style={{ color: 'white' }} primary={label} /> <ListItemText style={{ color: 'white' }} primary={label} />
{filter.type == 'label' && ( {filter.type == 'label' && (
<ListItemSecondaryAction> <ListItemSecondaryAction>
<IconButton <IconButton
edge="end" edge="end"
aria-label="delete" aria-label="delete"
onClick={(e) => handleOnDelete(e, filter)} onClick={(e) => handleOnDelete(e, filter)}
size="large"> size="large"
<DeleteOutlineTwoTone color="secondary" /> >
</IconButton> <DeleteOutlineTwoTone color="secondary" />
</ListItemSecondaryAction> </IconButton>
)} </ListItemSecondaryAction>
</CustomListItem> )}
); </CustomListItem>
);
}; };
export default MapsPage; export default MapsPage;