From c60254aca111af0b430ce9917d8689a02c0d3e1b Mon Sep 17 00:00:00 2001 From: Paulo Veiga Date: Mon, 31 Oct 2022 05:17:01 +0000 Subject: [PATCH] Add support for emoji Update core libraries. --- docker-compose.snapshots.update.yml | 2 +- docker-compose.snapshots.yml | 2 +- packages/editor/.eslintrc.json | 35 + packages/editor/package.json | 47 +- packages/editor/src/classes/i18n-msg/index.ts | 23 +- .../editor/src/classes/model/editor/index.ts | 15 +- .../model/node-property-builder/index.ts | 43 +- .../button/undo-and-redo/index.tsx | 6 +- .../{ => image-icon-tab}/iconGroups.json | 0 .../pane/icon-picker/image-icon-tab/index.tsx | 30 + .../action-widget/pane/icon-picker/index.tsx | 105 +- .../pane/keyboard-shortcut-help/index.tsx | 1 - .../editor/src/components/app-bar/index.tsx | 4 +- .../editor-toolbar/configBuilder.tsx | 25 +- packages/editor/src/components/index.tsx | 1 - .../src/components/zoom-panel/index.tsx | 2 +- packages/editor/src/global-styled.css | 4 +- packages/editor/src/index.tsx | 3 +- .../test/playground/map-render/js/editor.tsx | 9 +- .../playground/map-render/js/editorlocked.tsx | 11 +- .../playground/map-render/js/showcase.tsx | 6 +- .../playground/map-render/js/viewmode.tsx | 10 +- packages/editor/webpack.prod.js | 1 - packages/mindplot/package.json | 1 + packages/mindplot/src/components/Designer.ts | 16 +- .../mindplot/src/components/EmojiCharIcon.ts | 99 ++ packages/mindplot/src/components/Icon.ts | 67 +- packages/mindplot/src/components/IconGroup.ts | 22 +- packages/mindplot/src/components/ImageIcon.js | 338 ---- packages/mindplot/src/components/ImageIcon.ts | 71 + packages/mindplot/src/components/LinkIcon.ts | 4 +- packages/mindplot/src/components/NoteIcon.ts | 4 +- .../mindplot/src/components/SvgImageIcon.js | 132 ++ packages/mindplot/src/components/Topic.ts | 24 +- .../mindplot/src/components/TopicFeature.js | 15 +- .../mindplot/src/components/WidgetManager.ts | 8 +- .../src/components/export/FreemindExporter.ts | 4 +- .../import/FreemindIconConverter.ts | 6 +- .../src/components/model/EmojiIconModel.ts | 36 + .../components/model/FeatureModelFactory.ts | 9 +- .../src/components/model/FeatureType.ts | 2 +- .../src/components/model/SvgIconFamily.json | 293 ++++ .../model/{IconModel.ts => SvgIconModel.ts} | 5 +- .../persistence/XMLSerializerTango.ts | 2 +- packages/mindplot/src/index.ts | 16 +- packages/web2d/package.json | 2 +- packages/web2d/test/playground/group.js | 2 +- packages/webapp/.eslintrc.json | 7 +- packages/webapp/lang/es.json | 3 + packages/webapp/lang/fr.json | 3 + packages/webapp/package.json | 35 +- packages/webapp/src/classes/app-i18n/index.ts | 4 +- packages/webapp/src/compiled-lang/es.json | 6 + packages/webapp/src/compiled-lang/fr.json | 6 + .../editor-page/PersistenceManagerUtils.ts | 4 +- .../src/components/editor-page/index.tsx | 16 +- .../src/components/login-page/index.tsx | 27 - .../account-info-dialog/index.tsx | 4 +- .../maps-page/account-menu/index.tsx | 4 +- .../maps-page/action-chooser/index.tsx | 4 +- .../action-dispatcher/delete-dialog/index.tsx | 4 +- .../duplicate-dialog/index.tsx | 4 +- .../action-dispatcher/export-dialog/index.tsx | 4 +- .../maps-page/action-dispatcher/index.tsx | 12 +- .../action-dispatcher/info-dialog/index.tsx | 4 +- .../publish-dialog/index.tsx | 7 +- .../action-dispatcher/rename-dialog/index.tsx | 4 +- .../components/maps-page/maps-list/styled.ts | 20 +- .../webapp/src/components/maps-page/style.ts | 17 +- packages/webapp/src/index.tsx | 6 +- packages/webapp/src/redux/clientSlice.ts | 4 +- packages/webapp/src/redux/store.ts | 7 +- packages/webapp/src/utils.ts | 6 +- yarn.lock | 1463 +++++------------ 74 files changed, 1430 insertions(+), 1818 deletions(-) create mode 100644 packages/editor/.eslintrc.json rename packages/editor/src/components/action-widget/pane/icon-picker/{ => image-icon-tab}/iconGroups.json (100%) create mode 100644 packages/editor/src/components/action-widget/pane/icon-picker/image-icon-tab/index.tsx create mode 100644 packages/mindplot/src/components/EmojiCharIcon.ts delete mode 100644 packages/mindplot/src/components/ImageIcon.js create mode 100644 packages/mindplot/src/components/ImageIcon.ts create mode 100644 packages/mindplot/src/components/SvgImageIcon.js create mode 100644 packages/mindplot/src/components/model/EmojiIconModel.ts create mode 100644 packages/mindplot/src/components/model/SvgIconFamily.json rename packages/mindplot/src/components/model/{IconModel.ts => SvgIconModel.ts} (94%) diff --git a/docker-compose.snapshots.update.yml b/docker-compose.snapshots.update.yml index a990ee29..6d0e4a13 100644 --- a/docker-compose.snapshots.update.yml +++ b/docker-compose.snapshots.update.yml @@ -1,7 +1,7 @@ version: '3' services: e2e: - image: cypress/included:10.8.0 + image: cypress/included:10.11.0 container_name: wisemapping-integration-tests entrypoint: '/bin/sh -c "yarn install && yarn bootstrap && yarn build && yarn test:integration"' working_dir: /e2e diff --git a/docker-compose.snapshots.yml b/docker-compose.snapshots.yml index 7e49edfb..c1622738 100644 --- a/docker-compose.snapshots.yml +++ b/docker-compose.snapshots.yml @@ -1,7 +1,7 @@ version: '3' services: e2e: - image: cypress/included:10.8.0 + image: cypress/included:10.11.0 container_name: wisemapping-integration-tests entrypoint: '/bin/sh -c "yarn bootstrap && yarn build && yarn test:integration"' working_dir: /e2e diff --git a/packages/editor/.eslintrc.json b/packages/editor/.eslintrc.json new file mode 100644 index 00000000..ad8e213d --- /dev/null +++ b/packages/editor/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "prettier", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "ignorePatterns": [ + "**/dist/**/*" + ], + "plugins": [ + "react", + "@typescript-eslint", + "react-hooks" + ], + "rules": { + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/explicit-module-boundary-types": "error", + "@typescript-eslint/no-unused-vars": "error", + "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks + "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies + } +} \ No newline at end of file diff --git a/packages/editor/package.json b/packages/editor/package.json index 8a2207c4..a568e6cb 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -17,52 +17,41 @@ "license": "MIT", "private": false, "devDependencies": { - "@babel/preset-env": "^7.18.6", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.16.5", - "@formatjs/cli": "^4.8.1", + "@babel/preset-env": "^7.19.4", + "@formatjs/cli": "^5.1.3", "@testing-library/react": "^12.0.0", "@types/jest": "^29.0.0", - "@typescript-eslint/eslint-plugin": "^4.8.1", - "@typescript-eslint/parser": "^4.8.1", "babel-plugin-transform-require-context": "^0.1.1", "babel-polyfill": "^6.26.0", "clean-webpack-plugin": "^4.0.0", - "compression-webpack-plugin": "^9.2.0", "copy-webpack-plugin": "^10.2.1", + "css-loader": "^6.7.1", "cypress": "^8.4.1", "cypress-image-snapshot": "^4.0.1", - "eslint": "^7.14.0", - "eslint-config-prettier": "^8.0.0", - "eslint-plugin-cypress": "^2.12.1", - "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-react-hooks": "^4.6.0", "html-webpack-plugin": "^5.5.0", "jest-transform-stub": "^2.0.0", - "prettier": "^2.2.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", + "style-loader": "^3.3.1", "ts-jest": "^27.1.0", - "ts-loader": "^8.0.11", - "ts-node": "^9.0.0", - "typescript": "^4.1.2", + "typescript": "^4.8.4", "webpack": "^5.74.0", - "webpack-dev-server": "^4.7.3", "webpack-merge": "^5.8.0" }, "dependencies": { - "@wisemapping/mindplot": "^5.0.1", - "react-color": "^2.19.3" + "@emotion/styled": "^11.10.5", + "@wisemapping/mindplot": "^5.0.15", + "emoji-picker-react": "^4.4.4", + "react-color": "^2.19.3", + "react-dom": "^18.2.0", + "styled-components": "^5.3.6" }, "peerDependencies": { - "@emotion/react": "^11.10.4", - "@emotion/styled": "^11.10.4", + "@emotion/styled": "^11.10.5", "@mui/icons-material": "^5.9.3", - "@mui/material": "^5.9.3", - "@types/styled-components": "^5.1.26", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-intl": "^5.24.3", - "styled-components": "^5.3.5" + "@mui/material": "^5.10.11", + "react": "^18.2.0", + "react-intl": "^5.25.1", + "styled-components": "^5.3.6" } } diff --git a/packages/editor/src/classes/i18n-msg/index.ts b/packages/editor/src/classes/i18n-msg/index.ts index 4f7fbc35..23679bd8 100644 --- a/packages/editor/src/classes/i18n-msg/index.ts +++ b/packages/editor/src/classes/i18n-msg/index.ts @@ -15,30 +15,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import FR from './../../compiled-lang/fr.json'; -import ES from './../../compiled-lang/es.json'; -import EN from './../../compiled-lang/en.json'; -import DE from './../../compiled-lang/de.json'; -import RU from './../../compiled-lang/ru.json'; -import ZH from './../../compiled-lang/zh.json'; - class I18nMsg { - static loadLocaleData(locale: string) { + static loadLocaleData(locale: string): Record { switch (locale) { case 'fr': - return FR; + return require('./../../compiled-lang/fr.json'); case 'en': - return EN; + return require('./../../compiled-lang/en.json'); case 'es': - return ES; + return require('./../../compiled-lang/es.json'); case 'de': - return DE; + return require('./../../compiled-lang/de.json'); case 'ru': - return RU; + return require('./../../compiled-lang/ru.json'); case 'zh': - return ZH; + return require('./../../compiled-lang/zh.json'); default: - return EN; + return require('./../../compiled-lang/en.json'); } } } diff --git a/packages/editor/src/classes/model/editor/index.ts b/packages/editor/src/classes/model/editor/index.ts index d0f82ce2..96ab0ae4 100644 --- a/packages/editor/src/classes/model/editor/index.ts +++ b/packages/editor/src/classes/model/editor/index.ts @@ -15,7 +15,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Designer, MindplotWebComponent, PersistenceManager } from '@wisemapping/mindplot'; +import { + Designer, + MindplotWebComponent, + PersistenceManager, + DesignerModel, +} from '@wisemapping/mindplot'; import Capability from '../../action/capability'; class Editor { @@ -43,15 +48,19 @@ class Editor { return this.component?.getDesigner(); } + getDesignerModel(): DesignerModel | undefined { + return this.getDesigner().getModel(); + } + loadMindmap(mapId: string, persistenceManager: PersistenceManager, widgetManager): void { this.component.buildDesigner(persistenceManager, widgetManager); this.component.loadMap(mapId); } registerEvents(canvasUpdate: (timestamp: number) => void, capability: Capability) { - const desiger = this.component.getDesigner(); + const designer = this.component.getDesigner(); const onNodeBlurHandler = () => { - if (!desiger.getModel().selectedTopic()) { + if (!designer.getModel().selectedTopic()) { canvasUpdate(Date.now()); } }; diff --git a/packages/editor/src/classes/model/node-property-builder/index.ts b/packages/editor/src/classes/model/node-property-builder/index.ts index a6590ede..b86962a9 100644 --- a/packages/editor/src/classes/model/node-property-builder/index.ts +++ b/packages/editor/src/classes/model/node-property-builder/index.ts @@ -1,4 +1,4 @@ -import { Designer } from '@wisemapping/mindplot'; +import { Designer, Topic } from '@wisemapping/mindplot'; import NodeProperty from '../node-property'; import { getTheUniqueValueOrNull, @@ -8,10 +8,6 @@ import { getNextValue, } from '../../../components/toolbar/ToolbarValueModelBuilder'; -/** - * Given a designer build NodePropertyValueModel instances for the mindplot node properties - */ - class NodePropertyBuilder { designer: Designer; @@ -26,24 +22,20 @@ class NodePropertyBuilder { noteModel: NodeProperty; linkModel: NodeProperty; - /** - * - * @param designer designer to change node properties values - */ constructor(designer: Designer) { this.designer = designer; } private selectedTopic() { - return designer.getModel().selectedTopic(); + return this.designer.getModel().selectedTopic(); } private getFontSize() { - return designer.getModel().selectedTopic()?.getFontSize(); + return this.designer.getModel().selectedTopic()?.getFontSize(); } - private uniqueOrNull(propertyGetter: (Topic) => any | null) { - const nodes = designer.getModel().filterSelectedTopics(); + private uniqueOrNull(propertyGetter: (Topic: Topic) => any | null) { + const nodes = this.designer.getModel().filterSelectedTopics(); return getTheUniqueValueOrNull(nodes, propertyGetter); } @@ -53,8 +45,8 @@ class NodePropertyBuilder { */ fontWeigthModel(): NodeProperty { return { - getValue: () => designer.getModel().selectedTopic()?.getFontWeight(), - switchValue: () => designer.changeFontWeight(), + getValue: () => this.designer.getModel().selectedTopic()?.getFontWeight(), + switchValue: () => this.designer.changeFontWeight(), }; } @@ -74,7 +66,7 @@ class NodePropertyBuilder { if (direction === SwitchValueDirection.up) { newValue = getNextValue(fontSizes, this.getFontSize()); } - designer.changeFontSize(newValue); + this.designer.changeFontSize(newValue); }, }; return this.fontSizeModel; @@ -87,8 +79,8 @@ class NodePropertyBuilder { getSelectedTopicColorModel(): NodeProperty { if (!this.selectedTopicColorModel) this.selectedTopicColorModel = { - getValue: () => designer.getModel().selectedTopic()?.getBackgroundColor(), - setValue: (color) => designer.changeBackgroundColor(color), + getValue: () => this.designer.getModel().selectedTopic()?.getBackgroundColor(), + setValue: (color) => this.designer.changeBackgroundColor(color), }; return this.selectedTopicColorModel; @@ -122,7 +114,7 @@ class NodePropertyBuilder { if (!this.borderColorModel) this.borderColorModel = { getValue: () => this.uniqueOrNull((node) => node.getBorderColor()), - setValue: (hex: string) => designer.changeBorderColor(hex), + setValue: (hex: string) => this.designer.changeBorderColor(hex), }; return this.borderColorModel; } @@ -135,7 +127,7 @@ class NodePropertyBuilder { if (!this.fontColorModel) this.fontColorModel = { getValue: () => this.uniqueOrNull((node) => node.getFontColor()), - setValue: (hex: string) => designer.changeFontColor(hex), + setValue: (hex: string) => this.designer.changeFontColor(hex), }; return this.fontColorModel; } @@ -148,7 +140,10 @@ class NodePropertyBuilder { if (!this.topicIconModel) this.topicIconModel = { getValue: () => null, - setValue: (value: string) => designer.addIconType(value), + setValue: (value: string) => { + const values = value.split(':'); + this.designer.addIconType(values[0] as 'image' | 'emoji', values[1]); + }, }; return this.topicIconModel; } @@ -177,7 +172,7 @@ class NodePropertyBuilder { if (!this.fontFamilyModel) this.fontFamilyModel = { getValue: () => this.uniqueOrNull((node) => node.getFontFamily()), - setValue: (value: string) => designer.changeFontFamily(value), + setValue: (value: string) => this.designer.changeFontFamily(value), }; return this.fontFamilyModel; } @@ -190,7 +185,7 @@ class NodePropertyBuilder { if (!this.fontStyleModel) this.fontStyleModel = { getValue: () => this.selectedTopic()?.getFontStyle(), - switchValue: () => designer.changeFontStyle(), + switchValue: () => this.designer.changeFontStyle(), }; return this.fontStyleModel; } @@ -203,7 +198,7 @@ class NodePropertyBuilder { if (!this.topicShapeModel) this.topicShapeModel = { getValue: () => this.uniqueOrNull((node) => node.getShapeType()), - setValue: (value: string) => designer.changeTopicShape(value), + setValue: (value: string) => this.designer.changeTopicShape(value), }; return this.topicShapeModel; } diff --git a/packages/editor/src/components/action-widget/button/undo-and-redo/index.tsx b/packages/editor/src/components/action-widget/button/undo-and-redo/index.tsx index fc355bb3..f66454c3 100644 --- a/packages/editor/src/components/action-widget/button/undo-and-redo/index.tsx +++ b/packages/editor/src/components/action-widget/button/undo-and-redo/index.tsx @@ -36,12 +36,12 @@ const UndoAndRedo = ({ configuration, disabledCondition, model }: UndoAndRedo) = setDisabled(!isDisabled); return () => { - designer.removeEvent('modelUpdate', handleUpdate); + model.getDesigner().removeEvent('modelUpdate', handleUpdate); }; }; if (model.getDesigner()) { - designer.addEvent('modelUpdate', handleUpdate); + model.getDesigner().addEvent('modelUpdate', handleUpdate); } } }, [model?.isMapLoadded()]); @@ -52,7 +52,7 @@ const UndoAndRedo = ({ configuration, disabledCondition, model }: UndoAndRedo) = ...configuration, disabled: () => disabled, }} - > + /> ); }; export default UndoAndRedo; diff --git a/packages/editor/src/components/action-widget/pane/icon-picker/iconGroups.json b/packages/editor/src/components/action-widget/pane/icon-picker/image-icon-tab/iconGroups.json similarity index 100% rename from packages/editor/src/components/action-widget/pane/icon-picker/iconGroups.json rename to packages/editor/src/components/action-widget/pane/icon-picker/image-icon-tab/iconGroups.json diff --git a/packages/editor/src/components/action-widget/pane/icon-picker/image-icon-tab/index.tsx b/packages/editor/src/components/action-widget/pane/icon-picker/image-icon-tab/index.tsx new file mode 100644 index 00000000..e31655c8 --- /dev/null +++ b/packages/editor/src/components/action-widget/pane/icon-picker/image-icon-tab/index.tsx @@ -0,0 +1,30 @@ +import Box from '@mui/material/Box'; +import React from 'react'; +import iconGroups from './iconGroups.json'; +import { SvgImageIcon } from '@wisemapping/mindplot'; +import NodeProperty from '../../../../../classes/model/node-property'; + +type IconImageTab = { + iconModel: NodeProperty; +}; +const IconImageTab = ({ iconModel }: IconImageTab) => { + return ( + + {iconGroups.map((family, i) => ( + + {family.icons.map((icon) => ( + { + iconModel.setValue(`image:${icon}`); + }} + > + ))} + + ))} + + ); +}; +export default IconImageTab; diff --git a/packages/editor/src/components/action-widget/pane/icon-picker/index.tsx b/packages/editor/src/components/action-widget/pane/icon-picker/index.tsx index c91c1079..94678b5e 100644 --- a/packages/editor/src/components/action-widget/pane/icon-picker/index.tsx +++ b/packages/editor/src/components/action-widget/pane/icon-picker/index.tsx @@ -15,78 +15,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Box from '@mui/material/Box'; -import Tab from '@mui/material/Tab'; -import Tabs from '@mui/material/Tabs'; -import React from 'react'; -import iconGroups from './iconGroups.json'; -import { ImageIcon } from '@wisemapping/mindplot'; +import React, { useEffect } from 'react'; import NodeProperty from '../../../../classes/model/node-property'; +import EmojiPicker, { EmojiClickData } from 'emoji-picker-react'; +import DesignerKeyboard from '@wisemapping/mindplot/src/components/DesignerKeyboard'; +import IconImageTab from './image-icon-tab'; +import Switch from '@mui/material/Switch'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; -/** - * emoji picker for editor toolbar - */ -const IconPicker = (props: { closeModal: () => void; iconModel: NodeProperty }) => { - const [value, setValue] = React.useState(0); +type IconPickerProp = { + closeModal: () => void; + iconModel: NodeProperty; +}; - const handleChange = (event: React.SyntheticEvent, newValue: number) => { - setValue(newValue); +const IconPicker = ({ closeModal, iconModel }: IconPickerProp) => { + const [checked, setChecked] = React.useState(true); + + const handleCheck = (event: React.ChangeEvent) => { + setChecked(!checked); + }; + + // Review ... + useEffect(() => { + DesignerKeyboard.pause(); + return () => { DesignerKeyboard.resume(); } + }, []); + + + const handleEmojiSelect = (emoji: EmojiClickData) => { + const emojiChar = emoji.emoji; + iconModel.setValue(`emoji:${emojiChar}`); + closeModal(); }; return ( - - - - {iconGroups.map((family, i) => ( - } - {...a11yProps(i)} - /> - ))} - - - {iconGroups.map((family, i) => ( - - {family.icons.map((icon) => ( - { - props.iconModel.setValue(icon); - props.closeModal(); - }} - > - ))} - - ))} - - ); -}; +
+ + } /> + -/** - * tab panel used for display icon families in tabs - */ -const TabPanel = (props: { children?: React.ReactNode; index: number; value: number }) => { - const { children, value, index } = props; + {checked && ( + + )} - return ( - ); }; - -const a11yProps = (index: number) => { - return { - id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}`, - }; -}; export default IconPicker; diff --git a/packages/editor/src/components/action-widget/pane/keyboard-shortcut-help/index.tsx b/packages/editor/src/components/action-widget/pane/keyboard-shortcut-help/index.tsx index 46df4bba..59f2ba34 100644 --- a/packages/editor/src/components/action-widget/pane/keyboard-shortcut-help/index.tsx +++ b/packages/editor/src/components/action-widget/pane/keyboard-shortcut-help/index.tsx @@ -15,7 +15,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { $msg } from '@wisemapping/mindplot'; import React from 'react'; import { FormattedMessage } from 'react-intl'; diff --git a/packages/editor/src/components/app-bar/index.tsx b/packages/editor/src/components/app-bar/index.tsx index 5b10ce66..1a81af9c 100644 --- a/packages/editor/src/components/app-bar/index.tsx +++ b/packages/editor/src/components/app-bar/index.tsx @@ -116,7 +116,7 @@ const AppBar = ({ model, mapInfo, capability, onAction, accountConfig }: AppBarP intl.formatMessage({ id: 'appbar.tooltip-undo', defaultMessage: 'Undo' }), 'Z', ), - onClick: () => designer.undo(), + onClick: () => model.getDesigner().undo(), }} disabledCondition={(event) => event.undoSteps > 0} model={model} @@ -134,7 +134,7 @@ const AppBar = ({ model, mapInfo, capability, onAction, accountConfig }: AppBarP intl.formatMessage({ id: 'appbar.tooltip-redo', defaultMessage: 'Redo' }), 'Shift + Z', ), - onClick: () => designer.redo(), + onClick: () => model.getDesigner().redo(), }} disabledCondition={(event) => event.redoSteps > 0} model={model} diff --git a/packages/editor/src/components/editor-toolbar/configBuilder.tsx b/packages/editor/src/components/editor-toolbar/configBuilder.tsx index c69bebf3..ed1ad2d0 100644 --- a/packages/editor/src/components/editor-toolbar/configBuilder.tsx +++ b/packages/editor/src/components/editor-toolbar/configBuilder.tsx @@ -146,7 +146,7 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { ], }, ], - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, }; /** @@ -230,7 +230,7 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { ], }, ], - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, }; /** @@ -243,9 +243,9 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { defaultMessage: 'Add Relationship', }), onClick: (e) => { - designer.showRelPivot(e); + model.getDesigner().showRelPivot(e); }, - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, }; /** @@ -268,7 +268,7 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { ), }, ], - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, }; /** @@ -292,7 +292,7 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { ), }, ], - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, }; /** @@ -304,6 +304,7 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { id: 'editor-panel.tooltip-add-icon', defaultMessage: 'Add Icon', }), + useClickToClose: true, options: [ { tooltip: 'Node icon', @@ -315,7 +316,7 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { ), }, ], - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, }; const addNodeToolbarConfiguration = { @@ -323,9 +324,10 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { tooltip: intl.formatMessage({ id: 'editor-panel.tooltip-add-topic', defaultMessage: 'Add Topic' }) + ' (Enter)', - onClick: () => designer.createSiblingForSelectedNode(), - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + onClick: () => model.getDesigner().createSiblingForSelectedNode(), + disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, }; + const deleteNodeToolbarConfiguration = { icon: , tooltip: @@ -333,9 +335,10 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] { id: 'editor-panel.tooltip-delete-topic', defaultMessage: 'Delete Topic', }) + ' (Delete)', - onClick: () => designer.deleteSelectedEntities(), - disabled: () => designer.getModel().filterSelectedTopics().length === 0, + onClick: () => model.getDesigner().deleteSelectedEntities(), + disabled: () => model.getDesigner().getModel().filterSelectedTopics().length === 0, }; + return [ addNodeToolbarConfiguration, deleteNodeToolbarConfiguration, diff --git a/packages/editor/src/components/index.tsx b/packages/editor/src/components/index.tsx index 83ec5384..63d9a474 100644 --- a/packages/editor/src/components/index.tsx +++ b/packages/editor/src/components/index.tsx @@ -93,7 +93,6 @@ const Editor = ({ // Initialize locate ... const locale = options.locale; const msg = I18nMsg.loadLocaleData(locale); - return ( diff --git a/packages/editor/src/components/zoom-panel/index.tsx b/packages/editor/src/components/zoom-panel/index.tsx index 6a6f7c83..cd35fba3 100644 --- a/packages/editor/src/components/zoom-panel/index.tsx +++ b/packages/editor/src/components/zoom-panel/index.tsx @@ -53,7 +53,7 @@ export function buildZoomToolbarConfig(model: Editor, capability: Capability): A % {!model?.isMapLoadded() ? 100 - : Math.floor((1 / designer.getWorkSpace()?.getZoom()) * 100)} + : Math.floor((1 / model.getDesigner().getWorkSpace()?.getZoom()) * 100)} ), diff --git a/packages/editor/src/global-styled.css b/packages/editor/src/global-styled.css index 705c828b..141d3d4e 100644 --- a/packages/editor/src/global-styled.css +++ b/packages/editor/src/global-styled.css @@ -19,8 +19,8 @@ body { height: 100%; } .panelIcon { - width: 20px; - height: 20px; + width: 25px; + height: 25px; margin-left: 4px; margin-top: 3px; cursor: pointer; diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index 5bade7f6..d9be5b64 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -33,6 +33,7 @@ import { Exporter, Importer, TextImporterFactory, + XMLSerializerFactory, } from '@wisemapping/mindplot'; import Editor from './components'; @@ -41,7 +42,6 @@ import MapInfo from './classes/model/map-info'; declare global { // used in mindplot - var designer: Designer; var accountEmail: string; } @@ -70,6 +70,7 @@ export { TextImporterFactory, EditorOptions, MapInfo, + XMLSerializerFactory, }; export default Editor; diff --git a/packages/editor/test/playground/map-render/js/editor.tsx b/packages/editor/test/playground/map-render/js/editor.tsx index 2568d5ed..59901830 100644 --- a/packages/editor/test/playground/map-render/js/editor.tsx +++ b/packages/editor/test/playground/map-render/js/editor.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import Editor, { EditorOptions } from '../../../../src/index'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import MapInfoImpl from './MapInfoImpl'; @@ -37,13 +37,14 @@ const options: EditorOptions = { enableKeyboardEvents: true, }; -ReactDOM.render( +const container = document.getElementById('root'); +const root = createRoot(container!); +root.render( console.log('action called:', action)} onLoad={initialization} - />, - document.getElementById('root'), + /> ); diff --git a/packages/editor/test/playground/map-render/js/editorlocked.tsx b/packages/editor/test/playground/map-render/js/editorlocked.tsx index b1890ba5..03b3ceda 100644 --- a/packages/editor/test/playground/map-render/js/editorlocked.tsx +++ b/packages/editor/test/playground/map-render/js/editorlocked.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import Editor, { EditorOptions } from '../../../../src/index'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import MapInfoImpl from './MapInfoImpl'; @@ -38,13 +38,14 @@ const options: EditorOptions = { }; const mapInfo = new MapInfoImpl('welcome', 'Develop WiseMapping', true); -ReactDOM.render( - console.log('action called:', action)} onLoad={initialization} - />, - document.getElementById('root'), + /> ); diff --git a/packages/editor/test/playground/map-render/js/showcase.tsx b/packages/editor/test/playground/map-render/js/showcase.tsx index b5b32f6d..fba0a846 100644 --- a/packages/editor/test/playground/map-render/js/showcase.tsx +++ b/packages/editor/test/playground/map-render/js/showcase.tsx @@ -16,8 +16,8 @@ * limitations under the License. */ import React from 'react'; -import ReactDOM from 'react-dom'; import Editor, { EditorOptions } from '../../../../src/index'; +import { createRoot } from 'react-dom/client'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import MapInfoImpl from './MapInfoImpl'; @@ -38,7 +38,9 @@ const options: EditorOptions = { enableKeyboardEvents: true, }; -ReactDOM.render( +const container = document.getElementById('app'); +const root = createRoot(container!); +root.render( { designer.addEvent('loadSuccess', () => { @@ -38,13 +38,13 @@ const options: EditorOptions = { enableKeyboardEvents: true, }; -ReactDOM.render( +const container = document.getElementById('root'); +const root = createRoot(container!); +root.render( console.log('action called:', action)} onLoad={initialization} - />, - document.getElementById('root'), -); + />); diff --git a/packages/editor/webpack.prod.js b/packages/editor/webpack.prod.js index d7d0a84f..98444627 100644 --- a/packages/editor/webpack.prod.js +++ b/packages/editor/webpack.prod.js @@ -9,7 +9,6 @@ const prodConfig = { }, externals: { react: 'react', - 'react-dom': 'react-dom', 'react-intl': 'react-intl', }, plugins: [new CleanWebpackPlugin()], diff --git a/packages/mindplot/package.json b/packages/mindplot/package.json index 0bc9a04d..4384ed4b 100644 --- a/packages/mindplot/package.json +++ b/packages/mindplot/package.json @@ -35,6 +35,7 @@ "@types/jquery": "^3.5.11", "@wisemapping/core-js": "^0.4.0", "@wisemapping/web2d": "^0.4.0", + "emoji-picker-react": "^4.4.3", "jest": "^27.4.5", "jquery": "3.6.0", "lodash": "^4.17.21", diff --git a/packages/mindplot/src/components/Designer.ts b/packages/mindplot/src/components/Designer.ts index a3e9c34a..72b30ec2 100644 --- a/packages/mindplot/src/components/Designer.ts +++ b/packages/mindplot/src/components/Designer.ts @@ -873,16 +873,16 @@ class Designer extends Events { } } - addIconType(iconType: string): void { + addIconType(type: 'image' | 'emoji', iconType: string): void { const topicsIds = this.getModel().filterTopicsIds(); + + const featureType: FeatureType = ( + type === 'emoji' ? TopicFeatureFactory.EmojiIcon.id : TopicFeatureFactory.SvgIcon.id + ) as FeatureType; if (topicsIds.length > 0) { - this._actionDispatcher.addFeatureToTopic( - topicsIds[0], - TopicFeatureFactory.Icon.id as FeatureType, - { - id: iconType, - }, - ); + this._actionDispatcher.addFeatureToTopic(topicsIds[0], featureType, { + id: iconType, + }); } } diff --git a/packages/mindplot/src/components/EmojiCharIcon.ts b/packages/mindplot/src/components/EmojiCharIcon.ts new file mode 100644 index 00000000..990c4a16 --- /dev/null +++ b/packages/mindplot/src/components/EmojiCharIcon.ts @@ -0,0 +1,99 @@ +/* + * 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 { Text, Group, ElementClass, Point } from '@wisemapping/web2d'; +import { $assert } from '@wisemapping/core-js'; + +import Icon from './Icon'; +import IconGroup from './IconGroup'; +import SvgIconModel from './model/SvgIconModel'; +import SizeType from './SizeType'; +import Topic from './Topic'; +import ActionDispatcher from './ActionDispatcher'; + +class EmojiCharIcon implements Icon { + private char: string; + + private element: ElementClass; + + private group: IconGroup; + + private iconModel: SvgIconModel; + + private topic: Topic; + + constructor(topic: Topic, iconModel: SvgIconModel, readOnly: boolean) { + $assert(iconModel, 'iconModel can not be null'); + $assert(topic, 'topic can not be null'); + this.iconModel = iconModel; + this.topic = topic; + + this.element = new Group({ + width: 90, + height: 90, + x: 0, + y: 0, + coordSizeWidth: 15, + coordSizeHeight: 15, + coordOriginY: 2, + }); + const iconText = new Text(); + iconText.setText(iconModel.getIconType()); + this.element.append(iconText); + + // Add events ... + if (!readOnly) { + this.element.setCursor('pointer'); + } + } + + getElement(): ElementClass { + return this.element; + } + + setGroup(group: IconGroup) { + this.group = group; + } + + getGroup(): IconGroup { + return this.group; + } + + getSize(): SizeType { + return this.group.getSize(); + } + + getPosition(): Point { + return this.group.getPosition(); + } + + addEvent(type: string, fnc: any): void { + this.element.addEvent(type, fnc); + } + + remove() { + const actionDispatcher = ActionDispatcher.getInstance(); + const featureId = this.iconModel.getId(); + actionDispatcher.removeFeatureFromTopic(this.topic.getId(), featureId); + } + + getModel(): SvgIconModel { + return this.iconModel; + } +} + +export default EmojiCharIcon; diff --git a/packages/mindplot/src/components/Icon.ts b/packages/mindplot/src/components/Icon.ts index 96922646..995c6a49 100644 --- a/packages/mindplot/src/components/Icon.ts +++ b/packages/mindplot/src/components/Icon.ts @@ -1,70 +1,23 @@ -/* - * 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 } from '@wisemapping/core-js'; -import { Image, Point } from '@wisemapping/web2d'; +import { Point, ElementClass } from '@wisemapping/web2d'; import IconGroup from './IconGroup'; import SizeType from './SizeType'; -import FeatureModel from './model/FeatureModel'; -abstract class Icon { - protected _image: Image; +interface Icon { + getElement(): ElementClass; - protected _group: IconGroup; + setGroup(group: IconGroup); - constructor(url: string) { - $assert(url, 'image url can not be null'); - this._image = new Image(); - this._image.setHref(url); - this._image.setSize(Icon.SIZE, Icon.SIZE); - } + getGroup(): IconGroup; - getImage(): Image { - return this._image; - } + getSize(): SizeType; - setGroup(group: IconGroup) { - this._group = group; - } + getPosition(): Point; - getGroup(): IconGroup { - return this._group; - } + addEvent(type: string, fnc): void; - getSize(): SizeType { - return this._image.getSize(); - } + remove(): void; - getPosition(): Point { - return this._image.getPosition(); - } - - addEvent(type: string, fnc): void { - this._image.addEvent(type, fnc); - } - - // eslint-disable-next-line class-methods-use-this - remove() { - throw new Error('Unsupported operation'); - } - - abstract getModel(): FeatureModel; - - static SIZE = 90; + getModel(); } export default Icon; diff --git a/packages/mindplot/src/components/IconGroup.ts b/packages/mindplot/src/components/IconGroup.ts index d7c62b12..6a2efc6d 100644 --- a/packages/mindplot/src/components/IconGroup.ts +++ b/packages/mindplot/src/components/IconGroup.ts @@ -19,7 +19,7 @@ import { $assert, $defined } from '@wisemapping/core-js'; import { Group, ElementClass, Point } from '@wisemapping/web2d'; import IconGroupRemoveTip from './IconGroupRemoveTip'; -import Icon from './Icon'; +import ImageIcon from './ImageIcon'; import SizeType from './SizeType'; import FeatureModel from './model/FeatureModel'; @@ -29,7 +29,7 @@ ORDER_BY_TYPE.set('note', 1); ORDER_BY_TYPE.set('link', 2); class IconGroup { - private _icons: Icon[]; + private _icons: ImageIcon[]; private _group: any; @@ -84,7 +84,7 @@ class IconGroup { this._resize(this._icons.length); } - addIcon(icon: Icon, remove: boolean) { + addIcon(icon: ImageIcon, remove: boolean) { $defined(icon, 'icon is not defined'); // Order could have change, need to re-add all. @@ -104,7 +104,7 @@ class IconGroup { this._resize(this._icons.length); this._icons.forEach((i, index) => { this._positionIcon(i, index); - const imageShape = i.getImage(); + const imageShape = i.getElement(); this._group.append(imageShape); }); @@ -140,11 +140,11 @@ class IconGroup { this._removeIcon(icon); } - private _removeIcon(icon: Icon) { + private _removeIcon(icon: ImageIcon) { $assert(icon, 'icon can not be null'); this._removeTip.close(0); - this._group.removeChild(icon.getImage()); + this._group.removeChild(icon.getElement()); this._icons = this._icons.filter((i) => i !== icon); this._resize(this._icons.length); @@ -175,13 +175,15 @@ class IconGroup { private _resize(iconsLength: number) { this._group.setSize(iconsLength * this._iconSize.width, this._iconSize.height); - const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2; + const iconSize = ImageIcon.SIZE + IconGroup.ICON_PADDING * 2; this._group.setCoordSize(iconsLength * iconSize, iconSize); } - private _positionIcon(icon: Icon, order: number) { - const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2; - icon.getImage().setPosition(iconSize * order + IconGroup.ICON_PADDING, IconGroup.ICON_PADDING); + private _positionIcon(icon: ImageIcon, order: number) { + const iconSize = ImageIcon.SIZE + IconGroup.ICON_PADDING * 2; + icon + .getElement() + .setPosition(iconSize * order + IconGroup.ICON_PADDING, IconGroup.ICON_PADDING); } static ICON_PADDING = 5; diff --git a/packages/mindplot/src/components/ImageIcon.js b/packages/mindplot/src/components/ImageIcon.js deleted file mode 100644 index 81113864..00000000 --- a/packages/mindplot/src/components/ImageIcon.js +++ /dev/null @@ -1,338 +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 } from '@wisemapping/core-js'; -import Icon from './Icon'; -import ActionDispatcher from './ActionDispatcher'; - -function importAll(r) { - const images = {}; - r.keys().forEach((item) => { - images[item.replace('./', '')] = r(item); - }); - return images; -} - -const images = importAll(require.context('../../assets/icons', false, /\.(png|svg)$/)); - -class ImageIcon extends Icon { - constructor(topic, iconModel, readOnly) { - $assert(iconModel, 'iconModel can not be null'); - $assert(topic, 'topic can not be null'); - - // Build graph image representation ... - let iconType = iconModel.getIconType(); - - // Hack for overwrite wrong icon. Remove in couple of months ... - if (iconType === 'meeetapps_facebook-messenger') { - iconType = 'meetapps_facebook-messenger'; - } - - const imgUrl = ImageIcon.getImageUrl(iconType); - super(imgUrl); - - this._topicId = topic.getId(); - this._featureModel = iconModel; - - if (!readOnly) { - // Icon - const image = this.getImage(); - const me = this; - image.addEvent('click', () => { - const iconTypeClick = iconModel.getIconType(); - const newIconType = ImageIcon._getNextFamilyIconId(iconTypeClick); - iconModel.setIconType(newIconType); - - me._image.setHref(ImageIcon.getImageUrl(newIconType)); - }); - this._image.setCursor('pointer'); - } - } - - static getImageUrl(iconId) { - let result = images[`${iconId}.svg`]; - if (!result) { - result = images[`${iconId}.png`]; - } - return result; - } - - getModel() { - return this._featureModel; - } - - static _getNextFamilyIconId(iconId) { - const familyIcons = ImageIcon._getFamilyIcons(iconId); - $assert(familyIcons !== null, `Family Icon not found: ${iconId}`); - - let result = null; - for (let i = 0; i < familyIcons.length && result == null; i++) { - if (familyIcons[i] === iconId) { - // Is last one? - if (i === familyIcons.length - 1) { - [result] = familyIcons; - } else { - result = familyIcons[i + 1]; - } - break; - } - } - - return result; - } - - static _getFamilyIcons(iconId) { - $assert(iconId != null, 'id must not be null'); - $assert(iconId.indexOf('_') !== -1, `Invalid icon id (it must contain '_'). Id: ${iconId}`); - - let result = null; - for (let i = 0; i < ImageIcon.prototype.ICON_FAMILIES.length; i++) { - const family = ImageIcon.prototype.ICON_FAMILIES[i]; - const iconFamilyId = iconId.substr(0, iconId.indexOf('_')); - - if (family.id === iconFamilyId) { - result = family.icons; - break; - } - } - return result; - } - - remove() { - const actionDispatcher = ActionDispatcher.getInstance(); - const featureId = this._featureModel.getId(); - const topicId = this._topicId; - actionDispatcher.removeFeatureFromTopic(topicId, featureId); - } -} - -ImageIcon.prototype.ICON_FAMILIES = [ - { - id: 'face', - icons: ['face_plain', 'face_sad', 'face_crying', 'face_smile', 'face_surprise', 'face_wink'], - }, - { - id: 'funy', - icons: ['funy_angel', 'funy_devilish', 'funy_glasses', 'funy_grin', 'funy_kiss', 'funy_monkey'], - }, - { - id: 'sport', - icons: [ - 'sport_basketball', - 'sport_football', - 'sport_golf', - 'sport_raquet', - 'sport_shuttlecock', - 'sport_soccer', - 'sport_tennis', - ], - }, - { - id: 'bulb', - icons: ['bulb_light_on', 'bulb_light_off'], - }, - { - id: 'thumb', - icons: ['thumb_thumb_up', 'thumb_thumb_down'], - }, - { - id: 'tick', - icons: ['tick_tick', 'tick_cross'], - }, - { - id: 'onoff', - icons: [ - 'onoff_clock', - 'onoff_clock_red', - 'onoff_add', - 'onoff_delete', - 'onoff_status_offline', - 'onoff_status_online', - ], - }, - { - id: 'money', - icons: [ - 'money_money', - 'money_dollar', - 'money_euro', - 'money_pound', - 'money_yen', - 'money_coins', - 'money_ruby', - ], - }, - { - id: 'time', - icons: ['time_calendar', 'time_clock', 'time_hourglass'], - }, - { - id: 'number', - icons: [ - 'number_1', - 'number_2', - 'number_3', - 'number_4', - 'number_5', - 'number_6', - 'number_7', - 'number_8', - 'number_9', - ], - }, - { - id: 'chart', - icons: ['chart_bar', 'chart_line', 'chart_curve', 'chart_pie', 'chart_organisation'], - }, - { - id: 'sign', - icons: ['sign_warning', 'sign_info', 'sign_stop', 'sign_help', 'sign_cancel'], - }, - { - id: 'hard', - icons: [ - 'hard_cd', - 'hard_computer', - 'hard_controller', - 'hard_driver_disk', - 'hard_ipod', - 'hard_keyboard', - 'hard_mouse', - 'hard_printer', - 'hard_webcam', - 'hard_microphone', - ], - }, - { - id: 'things', - icons: [ - 'things_address_book', - 'things_wrench', - 'things_pin', - 'things_window-layout', - 'things_bubbles', - ], - }, - { - id: 'soft', - icons: [ - 'soft_bug', - 'soft_cursor', - 'soft_database_table', - 'soft_database', - 'soft_feed', - 'soft_folder_explore', - 'soft_rss', - 'soft_penguin', - ], - }, - { - id: 'arrow', - icons: ['arrow_up', 'arrow_down', 'arrow_left', 'arrow_right'], - }, - { - id: 'arrowc', - icons: [ - 'arrowc_rotate_anticlockwise', - 'arrowc_rotate_clockwise', - 'arrowc_turn_left', - 'arrowc_turn_right', - ], - }, - { - id: 'people', - icons: ['people_group', 'people_male1', 'people_male2', 'people_female1', 'people_female2'], - }, - { - id: 'mail', - icons: ['mail_envelop', 'mail_mailbox', 'mail_edit', 'mail_list'], - }, - { - id: 'flag', - icons: ['flag_blue', 'flag_green', 'flag_orange', 'flag_pink', 'flag_purple', 'flag_yellow'], - }, - { - id: 'social', - icons: [ - 'social_facebook', - 'social_twitter', - 'social_redit', - 'social_instagram', - 'social_google-plus', - ], - }, - { - id: 'meetapps', - icons: [ - 'meetapps_slack', - 'meetapps_google-meet', - 'meetapps_whatapp', - 'meetapps_ms-teams', - 'meetapps_zoom', - 'meetapps_facebook-messenger', - ], - }, - { - id: 'appsgoogle', - icons: ['appsgoogle_youtube', 'appsgoogle_gmail', 'appsgoogle_maps'], - }, - { - id: 'tag', - icons: ['tag_blue', 'tag_green', 'tag_orange', 'tag_red', 'tag_pink', 'tag_yellow'], - }, - { - id: 'object', - icons: [ - 'object_bell', - 'object_clanbomber', - 'object_key', - 'object_pencil', - 'object_phone', - 'object_magnifier', - 'object_clip', - 'object_music', - 'object_star', - 'object_wizard', - 'object_house', - 'object_cake', - 'object_camera', - 'object_palette', - 'object_rainbow', - ], - }, - { - id: 'weather', - icons: [ - 'weather_clear-night', - 'weather_clear', - 'weather_few-clouds-night', - 'weather_few-clouds', - 'weather_overcast', - 'weather_severe-alert', - 'weather_showers-scattered', - 'weather_showers', - 'weather_snow', - 'weather_storm', - ], - }, - { - id: 'task', - icons: ['task_0', 'task_25', 'task_50', 'task_75', 'task_100'], - }, -]; - -export default ImageIcon; diff --git a/packages/mindplot/src/components/ImageIcon.ts b/packages/mindplot/src/components/ImageIcon.ts new file mode 100644 index 00000000..a40ffc47 --- /dev/null +++ b/packages/mindplot/src/components/ImageIcon.ts @@ -0,0 +1,71 @@ +/* + * 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 } from '@wisemapping/core-js'; +import { Image, Point, ElementClass } from '@wisemapping/web2d'; +import IconGroup from './IconGroup'; +import SizeType from './SizeType'; +import FeatureModel from './model/FeatureModel'; +import Icon from './Icon'; + +abstract class ImageIcon implements Icon { + protected _image: Image; + + protected _group: IconGroup; + + constructor(url: string) { + $assert(url, 'image url can not be null'); + this._image = new Image(); + this._image.setHref(url); + this._image.setSize(ImageIcon.SIZE, ImageIcon.SIZE); + } + + getElement(): ElementClass { + return this._image; + } + + setGroup(group: IconGroup) { + this._group = group; + } + + getGroup(): IconGroup { + return this._group; + } + + getSize(): SizeType { + return this._image.getSize(); + } + + getPosition(): Point { + return this._image.getPosition(); + } + + addEvent(type: string, fnc): void { + this._image.addEvent(type, fnc); + } + + // eslint-disable-next-line class-methods-use-this + remove() { + throw new Error('Unsupported operation'); + } + + abstract getModel(): FeatureModel; + + static SIZE = 90; +} + +export default ImageIcon; diff --git a/packages/mindplot/src/components/LinkIcon.ts b/packages/mindplot/src/components/LinkIcon.ts index 1765f93b..5ac6925c 100644 --- a/packages/mindplot/src/components/LinkIcon.ts +++ b/packages/mindplot/src/components/LinkIcon.ts @@ -16,14 +16,14 @@ * limitations under the License. */ import { $assert } from '@wisemapping/core-js'; -import Icon from './Icon'; +import ImageIcon from './ImageIcon'; import LinksImage from '../../assets/icons/links.svg'; import LinkModel from './model/LinkModel'; import Topic from './Topic'; import FeatureModel from './model/FeatureModel'; import WidgetManager from './WidgetManager'; -class LinkIcon extends Icon { +class LinkIcon extends ImageIcon { private _linksModel: FeatureModel; private _topic: Topic; diff --git a/packages/mindplot/src/components/NoteIcon.ts b/packages/mindplot/src/components/NoteIcon.ts index 9d030714..9f04cefa 100644 --- a/packages/mindplot/src/components/NoteIcon.ts +++ b/packages/mindplot/src/components/NoteIcon.ts @@ -16,14 +16,14 @@ * limitations under the License. */ import { $assert } from '@wisemapping/core-js'; -import Icon from './Icon'; import NotesImage from '../../assets/icons/notes.svg'; import Topic from './Topic'; import NoteModel from './model/NoteModel'; import FeatureModel from './model/FeatureModel'; import WidgetManager from './WidgetManager'; +import ImageIcon from './ImageIcon'; -class NoteIcon extends Icon { +class NoteIcon extends ImageIcon { private _linksModel: NoteModel; private _topic: Topic; diff --git a/packages/mindplot/src/components/SvgImageIcon.js b/packages/mindplot/src/components/SvgImageIcon.js new file mode 100644 index 00000000..df229b7f --- /dev/null +++ b/packages/mindplot/src/components/SvgImageIcon.js @@ -0,0 +1,132 @@ +/* + * 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 } from '@wisemapping/core-js'; +import ImageIcon from './ImageIcon'; +import ActionDispatcher from './ActionDispatcher'; +import iconFamily from './model/SvgIconFamily.json'; + +function importAll(r) { + const images = {}; + r.keys().forEach((item) => { + images[item.replace('./', '')] = r(item); + }); + return images; +} + +const images = importAll(require.context('../../assets/icons', false, /\.(png|svg)$/)); + +class SvgImageIcon extends ImageIcon { + constructor(topic, iconModel, readOnly) { + $assert(iconModel, 'iconModel can not be null'); + $assert(topic, 'topic can not be null'); + + // Build graph image representation ... + const iconType = iconModel.getIconType(); + const imgUrl = SvgImageIcon.getImageUrl(iconType); + super(imgUrl); + + this._topicId = topic.getId(); + this._featureModel = iconModel; + + if (!readOnly) { + // Icon + const image = this.getElement(); + const me = this; + image.addEvent('click', () => { + const iconTypeClick = iconModel.getIconType(); + const newIconType = SvgImageIcon._getNextFamilyIconId(iconTypeClick); + iconModel.setIconType(newIconType); + + me._image.setHref(SvgImageIcon.getImageUrl(newIconType)); + }); + this._image.setCursor('pointer'); + } + } + + static getImageUrl(iconId) { + let result = images[`${iconId}.svg`]; + if (!result) { + result = images[`${iconId}.png`]; + } + return result; + } + + getModel() { + return this._featureModel; + } + + static _getNextFamilyIconId(iconId) { + const familyIcons = SvgImageIcon._getFamilyIcons(iconId); + $assert(familyIcons !== null, `Family Icon not found: ${iconId}`); + + let result = null; + for (let i = 0; i < familyIcons.length && result == null; i++) { + if (familyIcons[i] === iconId) { + // Is last one? + if (i === familyIcons.length - 1) { + [result] = familyIcons; + } else { + result = familyIcons[i + 1]; + } + break; + } + } + + return result; + } + + static _getNextUnicode(iconId) { + let result = null; + for (let i = 0; i < iconFamily.length; i++) { + const family = iconFamily[i]; + const iconFamilyId = iconId.substr(0, iconId.indexOf('_')); + + if (family.id === iconFamilyId) { + result = family.icons; + break; + } + } + return result; + } + + static _getFamilyIcons(iconId) { + $assert(iconId != null, 'id must not be null'); + $assert(iconId.indexOf('_') !== -1, `Invalid icon id (it must contain '_'). Id: ${iconId}`); + + let result = null; + for (let i = 0; i < iconFamily.length; i++) { + const family = iconFamily[i]; + const iconFamilyId = iconId.substr(0, iconId.indexOf('_')); + + if (family.id === iconFamilyId) { + result = family.icons; + break; + } + } + return result; + } + + remove() { + const actionDispatcher = ActionDispatcher.getInstance(); + const featureId = this._featureModel.getId(); + const topicId = this._topicId; + actionDispatcher.removeFeatureFromTopic(topicId, featureId); + } +} + +export default SvgImageIcon; diff --git a/packages/mindplot/src/components/Topic.ts b/packages/mindplot/src/components/Topic.ts index 071e76dc..e09befde 100644 --- a/packages/mindplot/src/components/Topic.ts +++ b/packages/mindplot/src/components/Topic.ts @@ -40,7 +40,7 @@ import NoteModel from './model/NoteModel'; import LinkModel from './model/LinkModel'; import SizeType from './SizeType'; import FeatureModel from './model/FeatureModel'; -import Icon from './Icon'; +import ImageIcon from './ImageIcon'; const ICON_SCALING_FACTOR = 1.3; @@ -322,13 +322,18 @@ abstract class Topic extends NodeGraph { const featuresModel = model.getFeatures(); featuresModel.forEach((f) => { const icon = TopicFeatureFactory.createIcon(this, f, this.isReadOnly()); - result.addIcon(icon, f.getType() === TopicFeatureFactory.Icon.id && !this.isReadOnly()); + + const type = f.getType(); + const addRemoveAction = + type === TopicFeatureFactory.SvgIcon.id || type === TopicFeatureFactory.EmojiIcon.id; + + result.addIcon(icon, addRemoveAction && !this.isReadOnly()); }); return result; } - addFeature(featureModel: FeatureModel): Icon { + addFeature(featureModel: FeatureModel): ImageIcon { const iconGroup = this.getOrBuildIconGroup(); this.closeEditors(); @@ -336,11 +341,11 @@ abstract class Topic extends NodeGraph { const model = this.getModel(); model.addFeature(featureModel); - const result: Icon = TopicFeatureFactory.createIcon(this, featureModel, this.isReadOnly()); - iconGroup.addIcon( - result, - featureModel.getType() === TopicFeatureFactory.Icon.id && !this.isReadOnly(), - ); + const result: ImageIcon = TopicFeatureFactory.createIcon(this, featureModel, this.isReadOnly()); + const isIcon = + featureModel.getType() === TopicFeatureFactory.SvgIcon.id || + featureModel.getType() === TopicFeatureFactory.EmojiIcon.id; + iconGroup.addIcon(result, isIcon && !this.isReadOnly()); this.adjustShapes(); return result; @@ -533,7 +538,7 @@ abstract class Topic extends NodeGraph { return result; } - setBackgroundColor(color: string) { + setBackgroundColor(color: string): void { this._setBackgroundColor(color, true); } @@ -552,7 +557,6 @@ abstract class Topic extends NodeGraph { } } - /** */ getBackgroundColor(): string { const model = this.getModel(); let result = model.getBackgroundColor(); diff --git a/packages/mindplot/src/components/TopicFeature.js b/packages/mindplot/src/components/TopicFeature.js index cee5f151..a311414b 100644 --- a/packages/mindplot/src/components/TopicFeature.js +++ b/packages/mindplot/src/components/TopicFeature.js @@ -17,15 +17,21 @@ */ import { $assert } from '@wisemapping/core-js'; -import ImageIcon from './ImageIcon'; +import EmojiCharIcon from './EmojiCharIcon'; +import SvgImageIcon from './SvgImageIcon'; import LinkIcon from './LinkIcon'; import NoteIcon from './NoteIcon'; const TopicFeatureFactory = { /** the icon object */ - Icon: { + SvgIcon: { id: 'icon', - icon: ImageIcon, + icon: SvgImageIcon, + }, + + EmojiIcon: { + id: 'eicon', + icon: EmojiCharIcon, }, /** the link object */ @@ -52,7 +58,8 @@ const TopicFeatureFactory = { }; TopicFeatureFactory._featuresMetadataById = [ - TopicFeatureFactory.Icon, + TopicFeatureFactory.SvgIcon, + TopicFeatureFactory.EmojiIcon, TopicFeatureFactory.Link, TopicFeatureFactory.Note, ]; diff --git a/packages/mindplot/src/components/WidgetManager.ts b/packages/mindplot/src/components/WidgetManager.ts index 30a21ef3..d829a705 100644 --- a/packages/mindplot/src/components/WidgetManager.ts +++ b/packages/mindplot/src/components/WidgetManager.ts @@ -72,15 +72,15 @@ class WidgetManager { } createTooltipForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) { - this.createTooltip(linkIcon.getImage().peer, $msg('LINK'), linkModel, undefined); + this.createTooltip(linkIcon.getElement().peer, $msg('LINK'), linkModel, undefined); } createTooltipForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) { - this.createTooltip(noteIcon.getImage().peer, $msg('NOTE'), undefined, noteModel); + this.createTooltip(noteIcon.getElement().peer, $msg('NOTE'), undefined, noteModel); } configureEditorForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) { - const htmlImage = linkIcon.getImage().peer; + const htmlImage = linkIcon.getElement().peer; htmlImage.addEvent('click', (evt) => { this.showEditorForLink(topic, linkModel, linkIcon); evt.stopPropagation(); @@ -88,7 +88,7 @@ class WidgetManager { } configureEditorForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) { - const htmlImage = noteIcon.getImage().peer; + const htmlImage = noteIcon.getElement().peer; htmlImage.addEvent('click', (evt) => { this.showEditorForNote(topic, noteModel, noteIcon); evt.stopPropagation(); diff --git a/packages/mindplot/src/components/export/FreemindExporter.ts b/packages/mindplot/src/components/export/FreemindExporter.ts index 954a0301..c9f669aa 100644 --- a/packages/mindplot/src/components/export/FreemindExporter.ts +++ b/packages/mindplot/src/components/export/FreemindExporter.ts @@ -2,7 +2,7 @@ import xmlFormatter from 'xml-formatter'; import { Mindmap } from '../..'; import INodeModel, { TopicShape } from '../model/INodeModel'; import RelationshipModel from '../model/RelationshipModel'; -import IconModel from '../model/IconModel'; +import SvgIconModel from '../model/SvgIconModel'; import FeatureModel from '../model/FeatureModel'; import LinkModel from '../model/LinkModel'; import NoteModel from '../model/NoteModel'; @@ -228,7 +228,7 @@ class FreemindExporter extends Exporter { } if (type === 'icon') { - const icon = feature as IconModel; + const icon = feature as SvgIconModel; const freemindIcon: Icon = new Icon(); freemindIcon.setBuiltin(icon.getIconType()); freemindNode.setArrowlinkOrCloudOrEdge(freemindIcon); diff --git a/packages/mindplot/src/components/import/FreemindIconConverter.ts b/packages/mindplot/src/components/import/FreemindIconConverter.ts index 4ffc154c..e8761395 100644 --- a/packages/mindplot/src/components/import/FreemindIconConverter.ts +++ b/packages/mindplot/src/components/import/FreemindIconConverter.ts @@ -1,10 +1,10 @@ -import IconModel from '../model/IconModel'; +import SvgIconModel from '../model/SvgIconModel'; export default class FreemindIconConverter { - private static freeIdToIcon: Map = new Map(); + private static freeIdToIcon: Map = new Map(); public static toWiseId(iconId: string): number | null { - const result: IconModel = this.freeIdToIcon.get(iconId); + const result: SvgIconModel = this.freeIdToIcon.get(iconId); return result ? result.getId() : null; } } diff --git a/packages/mindplot/src/components/model/EmojiIconModel.ts b/packages/mindplot/src/components/model/EmojiIconModel.ts new file mode 100644 index 00000000..60dc513d --- /dev/null +++ b/packages/mindplot/src/components/model/EmojiIconModel.ts @@ -0,0 +1,36 @@ +/* + * 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 } from '@wisemapping/core-js'; +import FeatureModel from './FeatureModel'; + +class EmojiIconModel extends FeatureModel { + constructor(attributes) { + super('eicon'); + this.setIconType(attributes.id); + } + + getIconType(): string { + return this.getAttribute('id') as string; + } + + setIconType(iconType: string): void { + $assert(iconType, 'iconType id can not be null'); + this.setAttribute('id', iconType); + } +} +export default EmojiIconModel; diff --git a/packages/mindplot/src/components/model/FeatureModelFactory.ts b/packages/mindplot/src/components/model/FeatureModelFactory.ts index 2fb7487a..467a5305 100644 --- a/packages/mindplot/src/components/model/FeatureModelFactory.ts +++ b/packages/mindplot/src/components/model/FeatureModelFactory.ts @@ -1,9 +1,10 @@ import { $assert } from '@wisemapping/core-js'; -import IconModel from './IconModel'; +import SvgIconModel from './SvgIconModel'; import LinkModel from './LinkModel'; import NoteModel from './NoteModel'; import FeatureModel from './FeatureModel'; import FeatureType from './FeatureType'; +import EmojiIconModel from './EmojiIconModel'; interface NodeById { id: FeatureType; @@ -14,7 +15,11 @@ class FeatureModelFactory { static modelById: Array = [ { id: 'icon', - model: IconModel, + model: SvgIconModel, + }, + { + id: 'eicon', + model: EmojiIconModel, }, { id: 'link', diff --git a/packages/mindplot/src/components/model/FeatureType.ts b/packages/mindplot/src/components/model/FeatureType.ts index 70da4fcc..f5be93cf 100644 --- a/packages/mindplot/src/components/model/FeatureType.ts +++ b/packages/mindplot/src/components/model/FeatureType.ts @@ -15,6 +15,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -type FeatureType = 'note' | 'link' | 'icon'; +type FeatureType = 'note' | 'link' | 'icon' | 'eicon'; export default FeatureType; diff --git a/packages/mindplot/src/components/model/SvgIconFamily.json b/packages/mindplot/src/components/model/SvgIconFamily.json new file mode 100644 index 00000000..efa3ee33 --- /dev/null +++ b/packages/mindplot/src/components/model/SvgIconFamily.json @@ -0,0 +1,293 @@ +[ + { + "id": "face", + "icons": [ + "face_plain", + "face_sad", + "face_crying", + "face_smile", + "face_surprise", + "face_wink" + ] + }, + { + "id": "funy", + "icons": [ + "funy_angel", + "funy_devilish", + "funy_glasses", + "funy_grin", + "funy_kiss", + "funy_monkey" + ] + }, + { + "id": "sport", + "icons": [ + "sport_basketball", + "sport_football", + "sport_golf", + "sport_raquet", + "sport_shuttlecock", + "sport_soccer", + "sport_tennis" + ] + }, + { + "id": "bulb", + "icons": [ + "", + "" + ] + }, + { + "id": "thumb", + "icons": [ + "thumb_thumb_up", + "thumb_thumb_down" + ] + }, + { + "id": "tick", + "icons": [ + "tick_tick", + "tick_cross" + ] + }, + { + "id": "onoff", + "icons": [ + "onoff_clock", + "onoff_clock_red", + "onoff_add", + "onoff_delete", + "onoff_status_offline", + "onoff_status_online" + ] + }, + { + "id": "money", + "icons": [ + "money_money", + "money_dollar", + "money_euro", + "money_pound", + "money_yen", + "money_coins", + "money_ruby" + ] + }, + { + "id": "time", + "icons": [ + "time_calendar", + "time_clock", + "time_hourglass" + ] + }, + { + "id": "number", + "icons": [ + "number_1", + "number_2", + "number_3", + "number_4", + "number_5", + "number_6", + "number_7", + "number_8", + "number_9" + ] + }, + { + "id": "chart", + "icons": [ + "chart_bar", + "chart_line", + "chart_curve", + "chart_pie", + "chart_organisation" + ] + }, + { + "id": "sign", + "icons": [ + "sign_warning", + "sign_info", + "sign_stop", + "sign_help", + "sign_cancel" + ] + }, + { + "id": "hard", + "icons": [ + "hard_cd", + "hard_computer", + "hard_controller", + "hard_driver_disk", + "hard_ipod", + "hard_keyboard", + "hard_mouse", + "hard_printer", + "hard_webcam", + "hard_microphone" + ] + }, + { + "id": "things", + "icons": [ + "things_address_book", + "things_wrench", + "things_pin", + "things_window-layout", + "things_bubbles" + ] + }, + { + "id": "soft", + "icons": [ + "soft_bug", + "soft_cursor", + "soft_database_table", + "soft_database", + "soft_feed", + "soft_folder_explore", + "soft_rss", + "soft_penguin" + ] + }, + { + "id": "arrow", + "icons": [ + "arrow_up", + "arrow_down", + "arrow_left", + "arrow_right" + ] + }, + { + "id": "arrowc", + "icons": [ + "arrowc_rotate_anticlockwise", + "arrowc_rotate_clockwise", + "arrowc_turn_left", + "arrowc_turn_right" + ] + }, + { + "id": "people", + "icons": [ + "people_group", + "people_male1", + "people_male2", + "people_female1", + "people_female2" + ] + }, + { + "id": "mail", + "icons": [ + "mail_envelop", + "mail_mailbox", + "mail_edit", + "mail_list" + ] + }, + { + "id": "flag", + "icons": [ + "flag_blue", + "flag_green", + "flag_orange", + "flag_pink", + "flag_purple", + "flag_yellow" + ] + }, + { + "id": "social", + "icons": [ + "social_facebook", + "social_twitter", + "social_redit", + "social_instagram", + "social_google-plus" + ] + }, + { + "id": "meetapps", + "icons": [ + "meetapps_slack", + "meetapps_google-meet", + "meetapps_whatapp", + "meetapps_ms-teams", + "meetapps_zoom", + "meetapps_facebook-messenger" + ] + }, + { + "id": "appsgoogle", + "icons": [ + "appsgoogle_youtube", + "appsgoogle_gmail", + "appsgoogle_maps" + ] + }, + { + "id": "tag", + "icons": [ + "tag_blue", + "tag_green", + "tag_orange", + "tag_red", + "tag_pink", + "tag_yellow" + ] + }, + { + "id": "object", + "icons": [ + "object_bell", + "object_clanbomber", + "object_key", + "object_pencil", + "object_phone", + "object_magnifier", + "object_clip", + "object_music", + "object_star", + "object_wizard", + "object_house", + "object_cake", + "object_camera", + "object_palette", + "object_rainbow" + ] + }, + { + "id": "weather", + "icons": [ + "weather_clear-night", + "weather_clear", + "weather_few-clouds-night", + "weather_few-clouds", + "weather_overcast", + "weather_severe-alert", + "weather_showers-scattered", + "weather_showers", + "weather_snow", + "weather_storm" + ] + }, + { + "id": "task", + "icons": [ + "task_0", + "task_25", + "task_50", + "task_75", + "task_100" + ] + } +] \ No newline at end of file diff --git a/packages/mindplot/src/components/model/IconModel.ts b/packages/mindplot/src/components/model/SvgIconModel.ts similarity index 94% rename from packages/mindplot/src/components/model/IconModel.ts rename to packages/mindplot/src/components/model/SvgIconModel.ts index 58b35370..28207d07 100644 --- a/packages/mindplot/src/components/model/IconModel.ts +++ b/packages/mindplot/src/components/model/SvgIconModel.ts @@ -18,7 +18,7 @@ import { $assert } from '@wisemapping/core-js'; import FeatureModel from './FeatureModel'; -class IconModel extends FeatureModel { +class SvgIconModel extends FeatureModel { constructor(attributes) { super('icon'); this.setIconType(attributes.id); @@ -33,4 +33,5 @@ class IconModel extends FeatureModel { this.setAttribute('id', iconType); } } -export default IconModel; + +export default SvgIconModel; diff --git a/packages/mindplot/src/components/persistence/XMLSerializerTango.ts b/packages/mindplot/src/components/persistence/XMLSerializerTango.ts index ecc68706..ea734ce7 100644 --- a/packages/mindplot/src/components/persistence/XMLSerializerTango.ts +++ b/packages/mindplot/src/components/persistence/XMLSerializerTango.ts @@ -172,7 +172,7 @@ class XMLSerializerTango implements XMLMindmapSerializer { const cdata = document.createCDATASection(this._rmXmlInv(value)); featureDom.appendChild(cdata); } else { - featureDom.setAttribute(key, this._rmXmlInv(value)); + featureDom.setAttribute(key, value); } } parentTopic.appendChild(featureDom); diff --git a/packages/mindplot/src/index.ts b/packages/mindplot/src/index.ts index 3d073a71..a7454486 100644 --- a/packages/mindplot/src/index.ts +++ b/packages/mindplot/src/index.ts @@ -31,8 +31,10 @@ import Exporter from './components/export/Exporter'; import Importer from './components/import/Importer'; import DesignerKeyboard from './components/DesignerKeyboard'; import EditorRenderMode from './components/EditorRenderMode'; +import DesignerModel from './components/DesignerModel'; + +import SvgImageIcon from './components/SvgImageIcon'; -import ImageIcon from './components/ImageIcon'; import MindplotWebComponent, { MindplotWebComponentInterface, } from './components/MindplotWebComponent'; @@ -48,8 +50,13 @@ import WidgetManager from './components/WidgetManager'; import { buildDesigner } from './components/DesignerBuilder'; import { $notify } from './components/widget/ToolbarNotifier'; +import XMLSerializerFactory from './components/persistence/XMLSerializerFactory'; -import { $msg } from './components/Messages'; +declare global { + // Todo: There are some global references that needs to be removed inside mindplot. + // eslint-disable-next-line vars-on-top, no-var + var designer: Designer; +} const globalAny: any = global; globalAny.jQuery = jquery; @@ -62,6 +69,7 @@ if (!customElements.get('mindplot-component')) { export { Mindmap, Designer, + DesignerModel, DesignerBuilder, PersistenceManager, RESTPersistenceManager, @@ -74,10 +82,9 @@ export { ImageExporterFactory, TextImporterFactory, Exporter, + SvgImageIcon, Importer, - ImageIcon, $notify, - $msg, DesignerKeyboard, MindplotWebComponent, MindplotWebComponentInterface, @@ -87,4 +94,5 @@ export { NoteModel, WidgetManager, Topic, + XMLSerializerFactory, }; diff --git a/packages/web2d/package.json b/packages/web2d/package.json index 34a25481..6d6473e2 100644 --- a/packages/web2d/package.json +++ b/packages/web2d/package.json @@ -29,7 +29,7 @@ "devDependencies": { "@babel/core": "^7.18.13", "@babel/plugin-transform-modules-commonjs": "^7.14.5", - "@babel/preset-env": "^7.14.7", + "@babel/preset-env": "^7.19.4", "babel-loader": "^8.2.2", "clean-webpack-plugin": "^4.0.0", "core-js": "^3.15.2", diff --git a/packages/web2d/test/playground/group.js b/packages/web2d/test/playground/group.js index 90f3f4ac..24f00edd 100644 --- a/packages/web2d/test/playground/group.js +++ b/packages/web2d/test/playground/group.js @@ -1,6 +1,6 @@ /* eslint-disable no-alert */ import $ from 'jquery'; -import { Toolkit, Workspace, Line, Group, Elipse } from '../../src'; +import { Workspace, Line, Group, Elipse } from '../../src'; global.$ = $; diff --git a/packages/webapp/.eslintrc.json b/packages/webapp/.eslintrc.json index e07e788c..fd9fc113 100644 --- a/packages/webapp/.eslintrc.json +++ b/packages/webapp/.eslintrc.json @@ -23,11 +23,14 @@ ], "plugins": [ "react", - "@typescript-eslint" + "@typescript-eslint", + "react-hooks" ], "rules": { "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/explicit-module-boundary-types": "error", - "@typescript-eslint/no-unused-vars": "error" + "@typescript-eslint/no-unused-vars": "error", + "react-hooks/rules-of-hooks": "warn" // Checks rules of Hooks + // "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies } } \ No newline at end of file diff --git a/packages/webapp/lang/es.json b/packages/webapp/lang/es.json index 0c3997eb..17876d6a 100644 --- a/packages/webapp/lang/es.json +++ b/packages/webapp/lang/es.json @@ -11,6 +11,9 @@ "accountinfo.firstname": { "defaultMessage": "Primer nombre" }, + "registration.page-title": { + "defaultMessage": "Registrarse | WiseMapping" + }, "accountinfo.lastname": { "defaultMessage": "Apellido" }, diff --git a/packages/webapp/lang/fr.json b/packages/webapp/lang/fr.json index 7a94d89b..3bb4b62c 100644 --- a/packages/webapp/lang/fr.json +++ b/packages/webapp/lang/fr.json @@ -14,6 +14,9 @@ "accountinfo.lastname": { "defaultMessage": "Nom de famille" }, + "registration.page-title": { + "defaultMessage": "Inscription | WiseMapping" + }, "accountinfo.title": { "defaultMessage": "Informations de compte" }, diff --git a/packages/webapp/package.json b/packages/webapp/package.json index a2f9c960..6ceb04f3 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -25,57 +25,40 @@ "@formatjs/cli": "^2.13.15", "@testing-library/cypress": "^7.0.3", "@types/testing-library__cypress": "^5.0.8", - "@typescript-eslint/eslint-plugin": "^4.8.1", - "@typescript-eslint/parser": "^4.8.1", - "brotli-webpack-plugin": "^1.1.0", - "clean-webpack-plugin": "^3.0.0", - "compression-webpack-plugin": "^7.1.2", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", + "clean-webpack-plugin": "^3.0.05.10.11", "copy-webpack-plugin": "^7.0.0", - "cross-env": "^7.0.3", - "css-loader": "^5.0.1", "cypress": "^8.4.1", "cypress-image-snapshot": "^4.0.1", "eslint": "^7.14.0", "eslint-config-prettier": "^8.0.0", "eslint-plugin-react": "^7.21.5", - "eslint-plugin-react-hooks": "^4.2.0", - "file-loader": "^6.2.0", - "html-webpack-dynamic-env-plugin": "^0.0.2", + "eslint-plugin-react-hooks": "^4.6.0", "html-webpack-plugin": "^5.1.0", "prettier": "^2.2.1", - "sass-loader": "^10.1.0", "start-server-and-test": "^1.12.0", - "style-loader": "^2.0.0", - "ts-loader": "^8.0.11", - "ts-node": "^9.0.0", - "typescript": "^4.1.2", - "url-loader": "^4.1.1", + "typescript": "^4.8.4", "webpack": "^5.74.0", "webpack-bundle-analyzer": "^4.4.0", - "webpack-cli": "^4.2.0", - "webpack-dev-server": "^3.11.0", "webpack-merge": "^5.7.3" }, "dependencies": { "@emotion/react": "^11.10.4", - "@emotion/styled": "^11.10.4", "@mui/icons-material": "^5.9.3", "@mui/lab": "^5.0.0-alpha.98", - "@mui/material": "^5.9.3", + "@mui/material": "^5.10.11", "@mui/styles": "^5.9.3", "@reduxjs/toolkit": "^1.5.0", "@wisemapping/editor": "^0.4.0", "axios": "^0.27.2", "dayjs": "^1.10.7", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", "react-ga4": "^1.4.1", "react-google-recaptcha": "^2.1.0", - "react-intl": "^4.7.6", + "react-intl": "^5.25.1", "react-query": "^3.39.1", "react-redux": "^7.2.2", - "react-router": "^5.1.8", - "react-router-dom": "^5.2.0", - "styled-components": "^5.3.5" + "react-router-dom": "^5.2.0" } } diff --git a/packages/webapp/src/classes/app-i18n/index.ts b/packages/webapp/src/classes/app-i18n/index.ts index e5347532..5a6ed1ea 100644 --- a/packages/webapp/src/classes/app-i18n/index.ts +++ b/packages/webapp/src/classes/app-i18n/index.ts @@ -1,4 +1,4 @@ -import { fetchAccount } from './../../redux/clientSlice'; +import { useFetchAccount } from './../../redux/clientSlice'; import 'dayjs/locale/fr'; import 'dayjs/locale/en'; import 'dayjs/locale/es'; @@ -26,7 +26,7 @@ export default abstract class AppI18n { const isTryPage = window.location.pathname.endsWith('/try'); let result: Locale; if (!isTryPage) { - const account = fetchAccount(); + const account = useFetchAccount(); result = account?.locale ? account.locale : this.getDefaultLocale(); // If the local storage value is different, update ... diff --git a/packages/webapp/src/compiled-lang/es.json b/packages/webapp/src/compiled-lang/es.json index 67fd2bca..b5ec0f4d 100644 --- a/packages/webapp/src/compiled-lang/es.json +++ b/packages/webapp/src/compiled-lang/es.json @@ -873,6 +873,12 @@ "value": "Apellido" } ], + "registration.page-title": [ + { + "type": 0, + "value": "Registrarse | WiseMapping" + } + ], "registration.password": [ { "type": 0, diff --git a/packages/webapp/src/compiled-lang/fr.json b/packages/webapp/src/compiled-lang/fr.json index 48c6db68..292f8d34 100644 --- a/packages/webapp/src/compiled-lang/fr.json +++ b/packages/webapp/src/compiled-lang/fr.json @@ -919,6 +919,12 @@ "value": "Nom de famille" } ], + "registration.page-title": [ + { + "type": 0, + "value": "Inscription | WiseMapping" + } + ], "registration.password": [ { "type": 0, diff --git a/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts b/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts index 002e1c68..d6f04a2e 100644 --- a/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts +++ b/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts @@ -6,7 +6,7 @@ import { LocalStorageManager, Mindmap, MockPersistenceManager, - XMLSerializerTango, + XMLSerializerFactory, } from '@wisemapping/editor'; export const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => { @@ -54,7 +54,7 @@ export const getMindmapFromPersistence = (mapId: string): Mindmap => { 'text/xml', ); - const serializer = new XMLSerializerTango(); + const serializer = XMLSerializerFactory.getSerializer('tango'); mindmap = serializer.loadFromDom(xmlDoc, String(mapId)); } return mindmap; diff --git a/packages/webapp/src/components/editor-page/index.tsx b/packages/webapp/src/components/editor-page/index.tsx index 81e1708a..92d82755 100644 --- a/packages/webapp/src/components/editor-page/index.tsx +++ b/packages/webapp/src/components/editor-page/index.tsx @@ -25,7 +25,7 @@ import AppI18n, { Locales } from '../../classes/app-i18n'; import { useSelector } from 'react-redux'; import { hotkeysEnabled } from '../../redux/editorSlice'; import ReactGA from 'react-ga4'; -import { fetchAccount, fetchMapById } from '../../redux/clientSlice'; +import { useFetchAccount, useFetchMapById } from '../../redux/clientSlice'; import EditorOptionsBuilder from './EditorOptionsBuilder'; import { buildPersistenceManagerForEditor } from './PersistenceManagerUtils'; import { useTheme } from '@mui/material/styles'; @@ -50,14 +50,14 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => { ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: `Map Editor` }); }, []); - const findEditorMode = (isTryMode: boolean, mapId: number): EditorRenderMode | null => { + const useFindEditorMode = (isTryMode: boolean, mapId: number): EditorRenderMode | null => { let result: EditorRenderMode = null; if (isTryMode) { result = 'showcase'; } else if (global.mindmapLocked) { result = 'viewonly'; } else { - const fetchResult = fetchMapById(mapId); + const fetchResult = useFetchMapById(mapId); if (!fetchResult.isLoading) { if (fetchResult.error) { throw new Error(`Map info could not be loaded: ${JSON.stringify(fetchResult.error)}`); @@ -76,11 +76,11 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => { // What is the role ? const mapId = EditorOptionsBuilder.loadMapId(); - const mode = findEditorMode(isTryMode, mapId); + const mode = useFindEditorMode(isTryMode, mapId); // Account settings can be null and editor cannot be initilized multiple times. This creates problems // at the i18n resource loading. - const isAccountLoaded = mode === 'showcase' || fetchAccount; + const isAccountLoaded = mode === 'showcase' || useFetchAccount; const loadCompleted = mode && isAccountLoaded; let options, persistence: PersistenceManager; @@ -99,10 +99,10 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => { } useEffect(() => { - if (options?.mapTitle) { - document.title = `${options.mapTitle} | WiseMapping `; + if (mapInfo) { + document.title = `${mapInfo.getTitle()} | WiseMapping `; } - }, [loadCompleted]); + }, [mapInfo]); return loadCompleted ? ( { - let result; - if (enabled === true) { - result = ( - - ); - } - return result || null; -}; - const LoginError = () => { // @Todo: This must be reviewed to be based on navigation state. // Login error example: http://localhost:8080/c/login?login.error=2 @@ -132,7 +106,6 @@ const LoginPage = (): React.ReactElement => { -