Add support for emoji

Update core libraries.
This commit is contained in:
Paulo Veiga 2022-10-31 05:17:01 +00:00
parent 906c284c24
commit c60254aca1
74 changed files with 1430 additions and 1818 deletions

View File

@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
e2e: e2e:
image: cypress/included:10.8.0 image: cypress/included:10.11.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:10.8.0 image: cypress/included:10.11.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

@ -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
}
}

View File

@ -17,52 +17,41 @@
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.18.6", "@babel/preset-env": "^7.19.4",
"@babel/preset-react": "^7.18.6", "@formatjs/cli": "^5.1.3",
"@babel/preset-typescript": "^7.16.5",
"@formatjs/cli": "^4.8.1",
"@testing-library/react": "^12.0.0", "@testing-library/react": "^12.0.0",
"@types/jest": "^29.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-plugin-transform-require-context": "^0.1.1",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"compression-webpack-plugin": "^9.2.0",
"copy-webpack-plugin": "^10.2.1", "copy-webpack-plugin": "^10.2.1",
"css-loader": "^6.7.1",
"cypress": "^8.4.1", "cypress": "^8.4.1",
"cypress-image-snapshot": "^4.0.1", "cypress-image-snapshot": "^4.0.1",
"eslint": "^7.14.0", "eslint-plugin-react-hooks": "^4.6.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",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"jest-transform-stub": "^2.0.0", "jest-transform-stub": "^2.0.0",
"prettier": "^2.2.1", "react": "^18.2.0",
"react": "^17.0.2", "style-loader": "^3.3.1",
"react-dom": "^17.0.2",
"ts-jest": "^27.1.0", "ts-jest": "^27.1.0",
"ts-loader": "^8.0.11", "typescript": "^4.8.4",
"ts-node": "^9.0.0",
"typescript": "^4.1.2",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-dev-server": "^4.7.3",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },
"dependencies": { "dependencies": {
"@wisemapping/mindplot": "^5.0.1", "@emotion/styled": "^11.10.5",
"react-color": "^2.19.3" "@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": { "peerDependencies": {
"@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.5",
"@emotion/styled": "^11.10.4",
"@mui/icons-material": "^5.9.3", "@mui/icons-material": "^5.9.3",
"@mui/material": "^5.9.3", "@mui/material": "^5.10.11",
"@types/styled-components": "^5.1.26", "react": "^18.2.0",
"react": "^17.0.2", "react-intl": "^5.25.1",
"react-dom": "^17.0.2", "styled-components": "^5.3.6"
"react-intl": "^5.24.3",
"styled-components": "^5.3.5"
} }
} }

View File

@ -15,30 +15,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import 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 { class I18nMsg {
static loadLocaleData(locale: string) { static loadLocaleData(locale: string): Record<string, string> {
switch (locale) { switch (locale) {
case 'fr': case 'fr':
return FR; return require('./../../compiled-lang/fr.json');
case 'en': case 'en':
return EN; return require('./../../compiled-lang/en.json');
case 'es': case 'es':
return ES; return require('./../../compiled-lang/es.json');
case 'de': case 'de':
return DE; return require('./../../compiled-lang/de.json');
case 'ru': case 'ru':
return RU; return require('./../../compiled-lang/ru.json');
case 'zh': case 'zh':
return ZH; return require('./../../compiled-lang/zh.json');
default: default:
return EN; return require('./../../compiled-lang/en.json');
} }
} }
} }

View File

@ -15,7 +15,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Designer, MindplotWebComponent, PersistenceManager } from '@wisemapping/mindplot'; import {
Designer,
MindplotWebComponent,
PersistenceManager,
DesignerModel,
} from '@wisemapping/mindplot';
import Capability from '../../action/capability'; import Capability from '../../action/capability';
class Editor { class Editor {
@ -43,15 +48,19 @@ class Editor {
return this.component?.getDesigner(); return this.component?.getDesigner();
} }
getDesignerModel(): DesignerModel | undefined {
return this.getDesigner().getModel();
}
loadMindmap(mapId: string, persistenceManager: PersistenceManager, widgetManager): void { loadMindmap(mapId: string, persistenceManager: PersistenceManager, widgetManager): void {
this.component.buildDesigner(persistenceManager, widgetManager); this.component.buildDesigner(persistenceManager, widgetManager);
this.component.loadMap(mapId); this.component.loadMap(mapId);
} }
registerEvents(canvasUpdate: (timestamp: number) => void, capability: Capability) { registerEvents(canvasUpdate: (timestamp: number) => void, capability: Capability) {
const desiger = this.component.getDesigner(); const designer = this.component.getDesigner();
const onNodeBlurHandler = () => { const onNodeBlurHandler = () => {
if (!desiger.getModel().selectedTopic()) { if (!designer.getModel().selectedTopic()) {
canvasUpdate(Date.now()); canvasUpdate(Date.now());
} }
}; };

View File

@ -1,4 +1,4 @@
import { Designer } from '@wisemapping/mindplot'; import { Designer, Topic } from '@wisemapping/mindplot';
import NodeProperty from '../node-property'; import NodeProperty from '../node-property';
import { import {
getTheUniqueValueOrNull, getTheUniqueValueOrNull,
@ -8,10 +8,6 @@ import {
getNextValue, getNextValue,
} from '../../../components/toolbar/ToolbarValueModelBuilder'; } from '../../../components/toolbar/ToolbarValueModelBuilder';
/**
* Given a designer build NodePropertyValueModel instances for the mindplot node properties
*/
class NodePropertyBuilder { class NodePropertyBuilder {
designer: Designer; designer: Designer;
@ -26,24 +22,20 @@ class NodePropertyBuilder {
noteModel: NodeProperty; noteModel: NodeProperty;
linkModel: NodeProperty; linkModel: NodeProperty;
/**
*
* @param designer designer to change node properties values
*/
constructor(designer: Designer) { constructor(designer: Designer) {
this.designer = designer; this.designer = designer;
} }
private selectedTopic() { private selectedTopic() {
return designer.getModel().selectedTopic(); return this.designer.getModel().selectedTopic();
} }
private getFontSize() { private getFontSize() {
return designer.getModel().selectedTopic()?.getFontSize(); return this.designer.getModel().selectedTopic()?.getFontSize();
} }
private uniqueOrNull(propertyGetter: (Topic) => any | null) { private uniqueOrNull(propertyGetter: (Topic: Topic) => any | null) {
const nodes = designer.getModel().filterSelectedTopics(); const nodes = this.designer.getModel().filterSelectedTopics();
return getTheUniqueValueOrNull(nodes, propertyGetter); return getTheUniqueValueOrNull(nodes, propertyGetter);
} }
@ -53,8 +45,8 @@ class NodePropertyBuilder {
*/ */
fontWeigthModel(): NodeProperty { fontWeigthModel(): NodeProperty {
return { return {
getValue: () => designer.getModel().selectedTopic()?.getFontWeight(), getValue: () => this.designer.getModel().selectedTopic()?.getFontWeight(),
switchValue: () => designer.changeFontWeight(), switchValue: () => this.designer.changeFontWeight(),
}; };
} }
@ -74,7 +66,7 @@ class NodePropertyBuilder {
if (direction === SwitchValueDirection.up) { if (direction === SwitchValueDirection.up) {
newValue = getNextValue(fontSizes, this.getFontSize()); newValue = getNextValue(fontSizes, this.getFontSize());
} }
designer.changeFontSize(newValue); this.designer.changeFontSize(newValue);
}, },
}; };
return this.fontSizeModel; return this.fontSizeModel;
@ -87,8 +79,8 @@ class NodePropertyBuilder {
getSelectedTopicColorModel(): NodeProperty { getSelectedTopicColorModel(): NodeProperty {
if (!this.selectedTopicColorModel) if (!this.selectedTopicColorModel)
this.selectedTopicColorModel = { this.selectedTopicColorModel = {
getValue: () => designer.getModel().selectedTopic()?.getBackgroundColor(), getValue: () => this.designer.getModel().selectedTopic()?.getBackgroundColor(),
setValue: (color) => designer.changeBackgroundColor(color), setValue: (color) => this.designer.changeBackgroundColor(color),
}; };
return this.selectedTopicColorModel; return this.selectedTopicColorModel;
@ -122,7 +114,7 @@ class NodePropertyBuilder {
if (!this.borderColorModel) if (!this.borderColorModel)
this.borderColorModel = { this.borderColorModel = {
getValue: () => this.uniqueOrNull((node) => node.getBorderColor()), getValue: () => this.uniqueOrNull((node) => node.getBorderColor()),
setValue: (hex: string) => designer.changeBorderColor(hex), setValue: (hex: string) => this.designer.changeBorderColor(hex),
}; };
return this.borderColorModel; return this.borderColorModel;
} }
@ -135,7 +127,7 @@ class NodePropertyBuilder {
if (!this.fontColorModel) if (!this.fontColorModel)
this.fontColorModel = { this.fontColorModel = {
getValue: () => this.uniqueOrNull((node) => node.getFontColor()), getValue: () => this.uniqueOrNull((node) => node.getFontColor()),
setValue: (hex: string) => designer.changeFontColor(hex), setValue: (hex: string) => this.designer.changeFontColor(hex),
}; };
return this.fontColorModel; return this.fontColorModel;
} }
@ -148,7 +140,10 @@ class NodePropertyBuilder {
if (!this.topicIconModel) if (!this.topicIconModel)
this.topicIconModel = { this.topicIconModel = {
getValue: () => null, 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; return this.topicIconModel;
} }
@ -177,7 +172,7 @@ class NodePropertyBuilder {
if (!this.fontFamilyModel) if (!this.fontFamilyModel)
this.fontFamilyModel = { this.fontFamilyModel = {
getValue: () => this.uniqueOrNull((node) => node.getFontFamily()), getValue: () => this.uniqueOrNull((node) => node.getFontFamily()),
setValue: (value: string) => designer.changeFontFamily(value), setValue: (value: string) => this.designer.changeFontFamily(value),
}; };
return this.fontFamilyModel; return this.fontFamilyModel;
} }
@ -190,7 +185,7 @@ class NodePropertyBuilder {
if (!this.fontStyleModel) if (!this.fontStyleModel)
this.fontStyleModel = { this.fontStyleModel = {
getValue: () => this.selectedTopic()?.getFontStyle(), getValue: () => this.selectedTopic()?.getFontStyle(),
switchValue: () => designer.changeFontStyle(), switchValue: () => this.designer.changeFontStyle(),
}; };
return this.fontStyleModel; return this.fontStyleModel;
} }
@ -203,7 +198,7 @@ class NodePropertyBuilder {
if (!this.topicShapeModel) if (!this.topicShapeModel)
this.topicShapeModel = { this.topicShapeModel = {
getValue: () => this.uniqueOrNull((node) => node.getShapeType()), getValue: () => this.uniqueOrNull((node) => node.getShapeType()),
setValue: (value: string) => designer.changeTopicShape(value), setValue: (value: string) => this.designer.changeTopicShape(value),
}; };
return this.topicShapeModel; return this.topicShapeModel;
} }

View File

@ -36,12 +36,12 @@ const UndoAndRedo = ({ configuration, disabledCondition, model }: UndoAndRedo) =
setDisabled(!isDisabled); setDisabled(!isDisabled);
return () => { return () => {
designer.removeEvent('modelUpdate', handleUpdate); model.getDesigner().removeEvent('modelUpdate', handleUpdate);
}; };
}; };
if (model.getDesigner()) { if (model.getDesigner()) {
designer.addEvent('modelUpdate', handleUpdate); model.getDesigner().addEvent('modelUpdate', handleUpdate);
} }
} }
}, [model?.isMapLoadded()]); }, [model?.isMapLoadded()]);
@ -52,7 +52,7 @@ const UndoAndRedo = ({ configuration, disabledCondition, model }: UndoAndRedo) =
...configuration, ...configuration,
disabled: () => disabled, disabled: () => disabled,
}} }}
></ToolbarMenuItem> />
); );
}; };
export default UndoAndRedo; export default UndoAndRedo;

View File

@ -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 (
<Box sx={{ width: '350px' }}>
{iconGroups.map((family, i) => (
<span>
{family.icons.map((icon) => (
<img
className="panelIcon"
key={icon}
src={SvgImageIcon.getImageUrl(icon)}
onClick={() => {
iconModel.setValue(`image:${icon}`);
}}
></img>
))}
</span>
))}
</Box>
);
};
export default IconImageTab;

View File

@ -15,78 +15,57 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import Box from '@mui/material/Box'; import React, { useEffect } from 'react';
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 NodeProperty from '../../../../classes/model/node-property'; 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';
/** type IconPickerProp = {
* emoji picker for editor toolbar closeModal: () => void;
*/ iconModel: NodeProperty;
const IconPicker = (props: { closeModal: () => void; iconModel: NodeProperty }) => { };
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => { const IconPicker = ({ closeModal, iconModel }: IconPickerProp) => {
setValue(newValue); const [checked, setChecked] = React.useState(true);
const handleCheck = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(!checked);
};
// Review ...
useEffect(() => {
DesignerKeyboard.pause();
return () => { DesignerKeyboard.resume(); }
}, []);
const handleEmojiSelect = (emoji: EmojiClickData) => {
const emojiChar = emoji.emoji;
iconModel.setValue(`emoji:${emojiChar}`);
closeModal();
}; };
return ( return (
<Box sx={{ width: '250px' }}> <div style={{ padding: '5px' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}> <FormGroup>
<Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label="Icons tabs"> <FormControlLabel label="Show Images" control={<Switch onChange={handleCheck} />} />
{iconGroups.map((family, i) => ( </FormGroup>
<Tab
key={family.id}
icon={<img className="panelIcon" src={ImageIcon.getImageUrl(family.icons[0])} />}
{...a11yProps(i)}
/>
))}
</Tabs>
</Box>
{iconGroups.map((family, i) => (
<TabPanel key={family.id} value={value} index={i}>
{family.icons.map((icon) => (
<img
className="panelIcon"
key={icon}
src={ImageIcon.getImageUrl(icon)}
onClick={() => {
props.iconModel.setValue(icon);
props.closeModal();
}}
></img>
))}
</TabPanel>
))}
</Box>
);
};
/** {checked && (
* tab panel used for display icon families in tabs <EmojiPicker
*/ onEmojiClick={handleEmojiSelect}
const TabPanel = (props: { children?: React.ReactNode; index: number; value: number }) => { lazyLoadEmojis={true}
const { children, value, index } = props; autoFocusSearch={true}
previewConfig={{ showPreview: false }}
/>
)}
return ( {!checked && <IconImageTab iconModel={iconModel} />}
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
>
{value === index && <Box>{children}</Box>}
</div> </div>
); );
}; };
const a11yProps = (index: number) => {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
};
export default IconPicker; export default IconPicker;

View File

@ -15,7 +15,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { $msg } from '@wisemapping/mindplot';
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';

View File

@ -116,7 +116,7 @@ const AppBar = ({ model, mapInfo, capability, onAction, accountConfig }: AppBarP
intl.formatMessage({ id: 'appbar.tooltip-undo', defaultMessage: 'Undo' }), intl.formatMessage({ id: 'appbar.tooltip-undo', defaultMessage: 'Undo' }),
'Z', 'Z',
), ),
onClick: () => designer.undo(), onClick: () => model.getDesigner().undo(),
}} }}
disabledCondition={(event) => event.undoSteps > 0} disabledCondition={(event) => event.undoSteps > 0}
model={model} model={model}
@ -134,7 +134,7 @@ const AppBar = ({ model, mapInfo, capability, onAction, accountConfig }: AppBarP
intl.formatMessage({ id: 'appbar.tooltip-redo', defaultMessage: 'Redo' }), intl.formatMessage({ id: 'appbar.tooltip-redo', defaultMessage: 'Redo' }),
'Shift + Z', 'Shift + Z',
), ),
onClick: () => designer.redo(), onClick: () => model.getDesigner().redo(),
}} }}
disabledCondition={(event) => event.redoSteps > 0} disabledCondition={(event) => event.redoSteps > 0}
model={model} model={model}

View File

@ -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', defaultMessage: 'Add Relationship',
}), }),
onClick: (e) => { 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', id: 'editor-panel.tooltip-add-icon',
defaultMessage: 'Add Icon', defaultMessage: 'Add Icon',
}), }),
useClickToClose: true,
options: [ options: [
{ {
tooltip: 'Node icon', 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 = { const addNodeToolbarConfiguration = {
@ -323,9 +324,10 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] {
tooltip: tooltip:
intl.formatMessage({ id: 'editor-panel.tooltip-add-topic', defaultMessage: 'Add Topic' }) + intl.formatMessage({ id: 'editor-panel.tooltip-add-topic', defaultMessage: 'Add Topic' }) +
' (Enter)', ' (Enter)',
onClick: () => designer.createSiblingForSelectedNode(), onClick: () => model.getDesigner().createSiblingForSelectedNode(),
disabled: () => designer.getModel().filterSelectedTopics().length === 0, disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0,
}; };
const deleteNodeToolbarConfiguration = { const deleteNodeToolbarConfiguration = {
icon: <RemoveCircleOutlineIcon />, icon: <RemoveCircleOutlineIcon />,
tooltip: tooltip:
@ -333,9 +335,10 @@ export function buildEditorPanelConfig(model: Editor): ActionConfig[] {
id: 'editor-panel.tooltip-delete-topic', id: 'editor-panel.tooltip-delete-topic',
defaultMessage: 'Delete Topic', defaultMessage: 'Delete Topic',
}) + ' (Delete)', }) + ' (Delete)',
onClick: () => designer.deleteSelectedEntities(), onClick: () => model.getDesigner().deleteSelectedEntities(),
disabled: () => designer.getModel().filterSelectedTopics().length === 0, disabled: () => model.getDesigner().getModel().filterSelectedTopics().length === 0,
}; };
return [ return [
addNodeToolbarConfiguration, addNodeToolbarConfiguration,
deleteNodeToolbarConfiguration, deleteNodeToolbarConfiguration,

View File

@ -93,7 +93,6 @@ const Editor = ({
// Initialize locate ... // Initialize locate ...
const locale = options.locale; const locale = options.locale;
const msg = I18nMsg.loadLocaleData(locale); const msg = I18nMsg.loadLocaleData(locale);
return ( return (
<ThemeProvider theme={editorTheme}> <ThemeProvider theme={editorTheme}>
<IntlProvider locale={locale} messages={msg}> <IntlProvider locale={locale} messages={msg}>

View File

@ -53,7 +53,7 @@ export function buildZoomToolbarConfig(model: Editor, capability: Capability): A
% %
{!model?.isMapLoadded() {!model?.isMapLoadded()
? 100 ? 100
: Math.floor((1 / designer.getWorkSpace()?.getZoom()) * 100)} : Math.floor((1 / model.getDesigner().getWorkSpace()?.getZoom()) * 100)}
</Typography> </Typography>
</Box> </Box>
), ),

View File

@ -19,8 +19,8 @@ body {
height: 100%; height: 100%;
} }
.panelIcon { .panelIcon {
width: 20px; width: 25px;
height: 20px; height: 25px;
margin-left: 4px; margin-left: 4px;
margin-top: 3px; margin-top: 3px;
cursor: pointer; cursor: pointer;

View File

@ -33,6 +33,7 @@ import {
Exporter, Exporter,
Importer, Importer,
TextImporterFactory, TextImporterFactory,
XMLSerializerFactory,
} from '@wisemapping/mindplot'; } from '@wisemapping/mindplot';
import Editor from './components'; import Editor from './components';
@ -41,7 +42,6 @@ import MapInfo from './classes/model/map-info';
declare global { declare global {
// used in mindplot // used in mindplot
var designer: Designer;
var accountEmail: string; var accountEmail: string;
} }
@ -70,6 +70,7 @@ export {
TextImporterFactory, TextImporterFactory,
EditorOptions, EditorOptions,
MapInfo, MapInfo,
XMLSerializerFactory,
}; };
export default Editor; export default Editor;

View File

@ -16,7 +16,7 @@
* limitations under the License. * limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import Editor, { EditorOptions } from '../../../../src/index'; import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
import MapInfoImpl from './MapInfoImpl'; import MapInfoImpl from './MapInfoImpl';
@ -37,13 +37,14 @@ const options: EditorOptions = {
enableKeyboardEvents: true, enableKeyboardEvents: true,
}; };
ReactDOM.render( const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<Editor <Editor
mapInfo={new MapInfoImpl('welcome', 'Develop Map Title', false)} mapInfo={new MapInfoImpl('welcome', 'Develop Map Title', false)}
options={options} options={options}
persistenceManager={persistence} persistenceManager={persistence}
onAction={(action) => console.log('action called:', action)} onAction={(action) => console.log('action called:', action)}
onLoad={initialization} onLoad={initialization}
/>, />
document.getElementById('root'),
); );

View File

@ -16,7 +16,7 @@
* limitations under the License. * limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import Editor, { EditorOptions } from '../../../../src/index'; import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
import MapInfoImpl from './MapInfoImpl'; import MapInfoImpl from './MapInfoImpl';
@ -38,13 +38,14 @@ const options: EditorOptions = {
}; };
const mapInfo = new MapInfoImpl('welcome', 'Develop WiseMapping', true); const mapInfo = new MapInfoImpl('welcome', 'Develop WiseMapping', true);
ReactDOM.render(
<Editor const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<Editor
mapInfo={mapInfo} mapInfo={mapInfo}
options={options} options={options}
persistenceManager={persistence} persistenceManager={persistence}
onAction={(action) => console.log('action called:', action)} onAction={(action) => console.log('action called:', action)}
onLoad={initialization} onLoad={initialization}
/>, />
document.getElementById('root'),
); );

View File

@ -16,8 +16,8 @@
* limitations under the License. * limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { EditorOptions } from '../../../../src/index'; import Editor, { EditorOptions } from '../../../../src/index';
import { createRoot } from 'react-dom/client';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
import MapInfoImpl from './MapInfoImpl'; import MapInfoImpl from './MapInfoImpl';
@ -38,7 +38,9 @@ const options: EditorOptions = {
enableKeyboardEvents: true, enableKeyboardEvents: true,
}; };
ReactDOM.render( const container = document.getElementById('app');
const root = createRoot(container!);
root.render(
<Editor <Editor
mapInfo={new MapInfoImpl('welcome', 'Develop Map Title', false)} mapInfo={new MapInfoImpl('welcome', 'Develop Map Title', false)}
options={options} options={options}

View File

@ -1,9 +1,9 @@
import '../css/viewmode.css'; import '../css/viewmode.css';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import Editor, { EditorOptions } from '../../../../src/index'; import Editor, { EditorOptions } from '../../../../src/index';
import { LocalStorageManager, Designer } from '@wisemapping/mindplot'; import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
import MapInfoImpl from './MapInfoImpl'; import MapInfoImpl from './MapInfoImpl';
import { createRoot } from 'react-dom/client';
const initialization = (designer: Designer) => { const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => { designer.addEvent('loadSuccess', () => {
@ -38,13 +38,13 @@ const options: EditorOptions = {
enableKeyboardEvents: true, enableKeyboardEvents: true,
}; };
ReactDOM.render( const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<Editor <Editor
mapInfo={new MapInfoImpl('welcome', 'Develop Map Title', false)} mapInfo={new MapInfoImpl('welcome', 'Develop Map Title', false)}
options={options} options={options}
persistenceManager={persistence} persistenceManager={persistence}
onAction={(action) => console.log('action called:', action)} onAction={(action) => console.log('action called:', action)}
onLoad={initialization} onLoad={initialization}
/>, />);
document.getElementById('root'),
);

View File

@ -9,7 +9,6 @@ const prodConfig = {
}, },
externals: { externals: {
react: 'react', react: 'react',
'react-dom': 'react-dom',
'react-intl': 'react-intl', 'react-intl': 'react-intl',
}, },
plugins: [new CleanWebpackPlugin()], plugins: [new CleanWebpackPlugin()],

View File

@ -35,6 +35,7 @@
"@types/jquery": "^3.5.11", "@types/jquery": "^3.5.11",
"@wisemapping/core-js": "^0.4.0", "@wisemapping/core-js": "^0.4.0",
"@wisemapping/web2d": "^0.4.0", "@wisemapping/web2d": "^0.4.0",
"emoji-picker-react": "^4.4.3",
"jest": "^27.4.5", "jest": "^27.4.5",
"jquery": "3.6.0", "jquery": "3.6.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@ -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 topicsIds = this.getModel().filterTopicsIds();
const featureType: FeatureType = (
type === 'emoji' ? TopicFeatureFactory.EmojiIcon.id : TopicFeatureFactory.SvgIcon.id
) as FeatureType;
if (topicsIds.length > 0) { if (topicsIds.length > 0) {
this._actionDispatcher.addFeatureToTopic( this._actionDispatcher.addFeatureToTopic(topicsIds[0], featureType, {
topicsIds[0], id: iconType,
TopicFeatureFactory.Icon.id as FeatureType, });
{
id: iconType,
},
);
} }
} }

View File

@ -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;

View File

@ -1,70 +1,23 @@
/* import { Point, ElementClass } from '@wisemapping/web2d';
* 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 IconGroup from './IconGroup'; import IconGroup from './IconGroup';
import SizeType from './SizeType'; import SizeType from './SizeType';
import FeatureModel from './model/FeatureModel';
abstract class Icon { interface Icon {
protected _image: Image; getElement(): ElementClass;
protected _group: IconGroup; setGroup(group: IconGroup);
constructor(url: string) { getGroup(): IconGroup;
$assert(url, 'image url can not be null');
this._image = new Image();
this._image.setHref(url);
this._image.setSize(Icon.SIZE, Icon.SIZE);
}
getImage(): Image { getSize(): SizeType;
return this._image;
}
setGroup(group: IconGroup) { getPosition(): Point;
this._group = group;
}
getGroup(): IconGroup { addEvent(type: string, fnc): void;
return this._group;
}
getSize(): SizeType { remove(): void;
return this._image.getSize();
}
getPosition(): Point { getModel();
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 Icon; export default Icon;

View File

@ -19,7 +19,7 @@
import { $assert, $defined } from '@wisemapping/core-js'; import { $assert, $defined } from '@wisemapping/core-js';
import { Group, ElementClass, Point } from '@wisemapping/web2d'; import { Group, ElementClass, Point } from '@wisemapping/web2d';
import IconGroupRemoveTip from './IconGroupRemoveTip'; import IconGroupRemoveTip from './IconGroupRemoveTip';
import Icon from './Icon'; import ImageIcon from './ImageIcon';
import SizeType from './SizeType'; import SizeType from './SizeType';
import FeatureModel from './model/FeatureModel'; import FeatureModel from './model/FeatureModel';
@ -29,7 +29,7 @@ ORDER_BY_TYPE.set('note', 1);
ORDER_BY_TYPE.set('link', 2); ORDER_BY_TYPE.set('link', 2);
class IconGroup { class IconGroup {
private _icons: Icon[]; private _icons: ImageIcon[];
private _group: any; private _group: any;
@ -84,7 +84,7 @@ class IconGroup {
this._resize(this._icons.length); this._resize(this._icons.length);
} }
addIcon(icon: Icon, remove: boolean) { addIcon(icon: ImageIcon, remove: boolean) {
$defined(icon, 'icon is not defined'); $defined(icon, 'icon is not defined');
// Order could have change, need to re-add all. // Order could have change, need to re-add all.
@ -104,7 +104,7 @@ class IconGroup {
this._resize(this._icons.length); this._resize(this._icons.length);
this._icons.forEach((i, index) => { this._icons.forEach((i, index) => {
this._positionIcon(i, index); this._positionIcon(i, index);
const imageShape = i.getImage(); const imageShape = i.getElement();
this._group.append(imageShape); this._group.append(imageShape);
}); });
@ -140,11 +140,11 @@ class IconGroup {
this._removeIcon(icon); this._removeIcon(icon);
} }
private _removeIcon(icon: Icon) { private _removeIcon(icon: ImageIcon) {
$assert(icon, 'icon can not be null'); $assert(icon, 'icon can not be null');
this._removeTip.close(0); this._removeTip.close(0);
this._group.removeChild(icon.getImage()); this._group.removeChild(icon.getElement());
this._icons = this._icons.filter((i) => i !== icon); this._icons = this._icons.filter((i) => i !== icon);
this._resize(this._icons.length); this._resize(this._icons.length);
@ -175,13 +175,15 @@ class IconGroup {
private _resize(iconsLength: number) { private _resize(iconsLength: number) {
this._group.setSize(iconsLength * this._iconSize.width, this._iconSize.height); this._group.setSize(iconsLength * this._iconSize.width, this._iconSize.height);
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2; const iconSize = ImageIcon.SIZE + IconGroup.ICON_PADDING * 2;
this._group.setCoordSize(iconsLength * iconSize, iconSize); this._group.setCoordSize(iconsLength * iconSize, iconSize);
} }
private _positionIcon(icon: Icon, order: number) { private _positionIcon(icon: ImageIcon, order: number) {
const iconSize = Icon.SIZE + IconGroup.ICON_PADDING * 2; const iconSize = ImageIcon.SIZE + IconGroup.ICON_PADDING * 2;
icon.getImage().setPosition(iconSize * order + IconGroup.ICON_PADDING, IconGroup.ICON_PADDING); icon
.getElement()
.setPosition(iconSize * order + IconGroup.ICON_PADDING, IconGroup.ICON_PADDING);
} }
static ICON_PADDING = 5; static ICON_PADDING = 5;

View File

@ -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;

View File

@ -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;

View File

@ -16,14 +16,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import Icon from './Icon'; import ImageIcon from './ImageIcon';
import LinksImage from '../../assets/icons/links.svg'; import LinksImage from '../../assets/icons/links.svg';
import LinkModel from './model/LinkModel'; import LinkModel from './model/LinkModel';
import Topic from './Topic'; import Topic from './Topic';
import FeatureModel from './model/FeatureModel'; import FeatureModel from './model/FeatureModel';
import WidgetManager from './WidgetManager'; import WidgetManager from './WidgetManager';
class LinkIcon extends Icon { class LinkIcon extends ImageIcon {
private _linksModel: FeatureModel; private _linksModel: FeatureModel;
private _topic: Topic; private _topic: Topic;

View File

@ -16,14 +16,14 @@
* limitations under the License. * limitations under the License.
*/ */
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import Icon from './Icon';
import NotesImage from '../../assets/icons/notes.svg'; import NotesImage from '../../assets/icons/notes.svg';
import Topic from './Topic'; import Topic from './Topic';
import NoteModel from './model/NoteModel'; import NoteModel from './model/NoteModel';
import FeatureModel from './model/FeatureModel'; import FeatureModel from './model/FeatureModel';
import WidgetManager from './WidgetManager'; import WidgetManager from './WidgetManager';
import ImageIcon from './ImageIcon';
class NoteIcon extends Icon { class NoteIcon extends ImageIcon {
private _linksModel: NoteModel; private _linksModel: NoteModel;
private _topic: Topic; private _topic: Topic;

View File

@ -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;

View File

@ -40,7 +40,7 @@ import NoteModel from './model/NoteModel';
import LinkModel from './model/LinkModel'; import LinkModel from './model/LinkModel';
import SizeType from './SizeType'; import SizeType from './SizeType';
import FeatureModel from './model/FeatureModel'; import FeatureModel from './model/FeatureModel';
import Icon from './Icon'; import ImageIcon from './ImageIcon';
const ICON_SCALING_FACTOR = 1.3; const ICON_SCALING_FACTOR = 1.3;
@ -322,13 +322,18 @@ abstract class Topic extends NodeGraph {
const featuresModel = model.getFeatures(); const featuresModel = model.getFeatures();
featuresModel.forEach((f) => { featuresModel.forEach((f) => {
const icon = TopicFeatureFactory.createIcon(this, f, this.isReadOnly()); 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; return result;
} }
addFeature(featureModel: FeatureModel): Icon { addFeature(featureModel: FeatureModel): ImageIcon {
const iconGroup = this.getOrBuildIconGroup(); const iconGroup = this.getOrBuildIconGroup();
this.closeEditors(); this.closeEditors();
@ -336,11 +341,11 @@ abstract class Topic extends NodeGraph {
const model = this.getModel(); const model = this.getModel();
model.addFeature(featureModel); model.addFeature(featureModel);
const result: Icon = TopicFeatureFactory.createIcon(this, featureModel, this.isReadOnly()); const result: ImageIcon = TopicFeatureFactory.createIcon(this, featureModel, this.isReadOnly());
iconGroup.addIcon( const isIcon =
result, featureModel.getType() === TopicFeatureFactory.SvgIcon.id ||
featureModel.getType() === TopicFeatureFactory.Icon.id && !this.isReadOnly(), featureModel.getType() === TopicFeatureFactory.EmojiIcon.id;
); iconGroup.addIcon(result, isIcon && !this.isReadOnly());
this.adjustShapes(); this.adjustShapes();
return result; return result;
@ -533,7 +538,7 @@ abstract class Topic extends NodeGraph {
return result; return result;
} }
setBackgroundColor(color: string) { setBackgroundColor(color: string): void {
this._setBackgroundColor(color, true); this._setBackgroundColor(color, true);
} }
@ -552,7 +557,6 @@ abstract class Topic extends NodeGraph {
} }
} }
/** */
getBackgroundColor(): string { getBackgroundColor(): string {
const model = this.getModel(); const model = this.getModel();
let result = model.getBackgroundColor(); let result = model.getBackgroundColor();

View File

@ -17,15 +17,21 @@
*/ */
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import ImageIcon from './ImageIcon'; import EmojiCharIcon from './EmojiCharIcon';
import SvgImageIcon from './SvgImageIcon';
import LinkIcon from './LinkIcon'; import LinkIcon from './LinkIcon';
import NoteIcon from './NoteIcon'; import NoteIcon from './NoteIcon';
const TopicFeatureFactory = { const TopicFeatureFactory = {
/** the icon object */ /** the icon object */
Icon: { SvgIcon: {
id: 'icon', id: 'icon',
icon: ImageIcon, icon: SvgImageIcon,
},
EmojiIcon: {
id: 'eicon',
icon: EmojiCharIcon,
}, },
/** the link object */ /** the link object */
@ -52,7 +58,8 @@ const TopicFeatureFactory = {
}; };
TopicFeatureFactory._featuresMetadataById = [ TopicFeatureFactory._featuresMetadataById = [
TopicFeatureFactory.Icon, TopicFeatureFactory.SvgIcon,
TopicFeatureFactory.EmojiIcon,
TopicFeatureFactory.Link, TopicFeatureFactory.Link,
TopicFeatureFactory.Note, TopicFeatureFactory.Note,
]; ];

View File

@ -72,15 +72,15 @@ class WidgetManager {
} }
createTooltipForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) { 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) { 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) { configureEditorForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
const htmlImage = linkIcon.getImage().peer; const htmlImage = linkIcon.getElement().peer;
htmlImage.addEvent('click', (evt) => { htmlImage.addEvent('click', (evt) => {
this.showEditorForLink(topic, linkModel, linkIcon); this.showEditorForLink(topic, linkModel, linkIcon);
evt.stopPropagation(); evt.stopPropagation();
@ -88,7 +88,7 @@ class WidgetManager {
} }
configureEditorForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) { configureEditorForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
const htmlImage = noteIcon.getImage().peer; const htmlImage = noteIcon.getElement().peer;
htmlImage.addEvent('click', (evt) => { htmlImage.addEvent('click', (evt) => {
this.showEditorForNote(topic, noteModel, noteIcon); this.showEditorForNote(topic, noteModel, noteIcon);
evt.stopPropagation(); evt.stopPropagation();

View File

@ -2,7 +2,7 @@ import xmlFormatter from 'xml-formatter';
import { Mindmap } from '../..'; import { Mindmap } from '../..';
import INodeModel, { TopicShape } from '../model/INodeModel'; import INodeModel, { TopicShape } from '../model/INodeModel';
import RelationshipModel from '../model/RelationshipModel'; import RelationshipModel from '../model/RelationshipModel';
import IconModel from '../model/IconModel'; import SvgIconModel from '../model/SvgIconModel';
import FeatureModel from '../model/FeatureModel'; import FeatureModel from '../model/FeatureModel';
import LinkModel from '../model/LinkModel'; import LinkModel from '../model/LinkModel';
import NoteModel from '../model/NoteModel'; import NoteModel from '../model/NoteModel';
@ -228,7 +228,7 @@ class FreemindExporter extends Exporter {
} }
if (type === 'icon') { if (type === 'icon') {
const icon = feature as IconModel; const icon = feature as SvgIconModel;
const freemindIcon: Icon = new Icon(); const freemindIcon: Icon = new Icon();
freemindIcon.setBuiltin(icon.getIconType()); freemindIcon.setBuiltin(icon.getIconType());
freemindNode.setArrowlinkOrCloudOrEdge(freemindIcon); freemindNode.setArrowlinkOrCloudOrEdge(freemindIcon);

View File

@ -1,10 +1,10 @@
import IconModel from '../model/IconModel'; import SvgIconModel from '../model/SvgIconModel';
export default class FreemindIconConverter { export default class FreemindIconConverter {
private static freeIdToIcon: Map<string, IconModel> = new Map<string, IconModel>(); private static freeIdToIcon: Map<string, SvgIconModel> = new Map<string, SvgIconModel>();
public static toWiseId(iconId: string): number | null { 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; return result ? result.getId() : null;
} }
} }

View File

@ -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;

View File

@ -1,9 +1,10 @@
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import IconModel from './IconModel'; import SvgIconModel from './SvgIconModel';
import LinkModel from './LinkModel'; import LinkModel from './LinkModel';
import NoteModel from './NoteModel'; import NoteModel from './NoteModel';
import FeatureModel from './FeatureModel'; import FeatureModel from './FeatureModel';
import FeatureType from './FeatureType'; import FeatureType from './FeatureType';
import EmojiIconModel from './EmojiIconModel';
interface NodeById { interface NodeById {
id: FeatureType; id: FeatureType;
@ -14,7 +15,11 @@ class FeatureModelFactory {
static modelById: Array<NodeById> = [ static modelById: Array<NodeById> = [
{ {
id: 'icon', id: 'icon',
model: IconModel, model: SvgIconModel,
},
{
id: 'eicon',
model: EmojiIconModel,
}, },
{ {
id: 'link', id: 'link',

View File

@ -15,6 +15,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
type FeatureType = 'note' | 'link' | 'icon'; type FeatureType = 'note' | 'link' | 'icon' | 'eicon';
export default FeatureType; export default FeatureType;

View File

@ -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"
]
}
]

View File

@ -18,7 +18,7 @@
import { $assert } from '@wisemapping/core-js'; import { $assert } from '@wisemapping/core-js';
import FeatureModel from './FeatureModel'; import FeatureModel from './FeatureModel';
class IconModel extends FeatureModel { class SvgIconModel extends FeatureModel {
constructor(attributes) { constructor(attributes) {
super('icon'); super('icon');
this.setIconType(attributes.id); this.setIconType(attributes.id);
@ -33,4 +33,5 @@ class IconModel extends FeatureModel {
this.setAttribute('id', iconType); this.setAttribute('id', iconType);
} }
} }
export default IconModel;
export default SvgIconModel;

View File

@ -172,7 +172,7 @@ class XMLSerializerTango implements XMLMindmapSerializer {
const cdata = document.createCDATASection(this._rmXmlInv(value)); const cdata = document.createCDATASection(this._rmXmlInv(value));
featureDom.appendChild(cdata); featureDom.appendChild(cdata);
} else { } else {
featureDom.setAttribute(key, this._rmXmlInv(value)); featureDom.setAttribute(key, value);
} }
} }
parentTopic.appendChild(featureDom); parentTopic.appendChild(featureDom);

View File

@ -31,8 +31,10 @@ import Exporter from './components/export/Exporter';
import Importer from './components/import/Importer'; import Importer from './components/import/Importer';
import DesignerKeyboard from './components/DesignerKeyboard'; import DesignerKeyboard from './components/DesignerKeyboard';
import EditorRenderMode from './components/EditorRenderMode'; import EditorRenderMode from './components/EditorRenderMode';
import DesignerModel from './components/DesignerModel';
import SvgImageIcon from './components/SvgImageIcon';
import ImageIcon from './components/ImageIcon';
import MindplotWebComponent, { import MindplotWebComponent, {
MindplotWebComponentInterface, MindplotWebComponentInterface,
} from './components/MindplotWebComponent'; } from './components/MindplotWebComponent';
@ -48,8 +50,13 @@ import WidgetManager from './components/WidgetManager';
import { buildDesigner } from './components/DesignerBuilder'; import { buildDesigner } from './components/DesignerBuilder';
import { $notify } from './components/widget/ToolbarNotifier'; 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; const globalAny: any = global;
globalAny.jQuery = jquery; globalAny.jQuery = jquery;
@ -62,6 +69,7 @@ if (!customElements.get('mindplot-component')) {
export { export {
Mindmap, Mindmap,
Designer, Designer,
DesignerModel,
DesignerBuilder, DesignerBuilder,
PersistenceManager, PersistenceManager,
RESTPersistenceManager, RESTPersistenceManager,
@ -74,10 +82,9 @@ export {
ImageExporterFactory, ImageExporterFactory,
TextImporterFactory, TextImporterFactory,
Exporter, Exporter,
SvgImageIcon,
Importer, Importer,
ImageIcon,
$notify, $notify,
$msg,
DesignerKeyboard, DesignerKeyboard,
MindplotWebComponent, MindplotWebComponent,
MindplotWebComponentInterface, MindplotWebComponentInterface,
@ -87,4 +94,5 @@ export {
NoteModel, NoteModel,
WidgetManager, WidgetManager,
Topic, Topic,
XMLSerializerFactory,
}; };

View File

@ -29,7 +29,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.13", "@babel/core": "^7.18.13",
"@babel/plugin-transform-modules-commonjs": "^7.14.5", "@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", "babel-loader": "^8.2.2",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"core-js": "^3.15.2", "core-js": "^3.15.2",

View File

@ -1,6 +1,6 @@
/* eslint-disable no-alert */ /* eslint-disable no-alert */
import $ from 'jquery'; import $ from 'jquery';
import { Toolkit, Workspace, Line, Group, Elipse } from '../../src'; import { Workspace, Line, Group, Elipse } from '../../src';
global.$ = $; global.$ = $;

View File

@ -23,11 +23,14 @@
], ],
"plugins": [ "plugins": [
"react", "react",
"@typescript-eslint" "@typescript-eslint",
"react-hooks"
], ],
"rules": { "rules": {
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-module-boundary-types": "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
} }
} }

View File

@ -11,6 +11,9 @@
"accountinfo.firstname": { "accountinfo.firstname": {
"defaultMessage": "Primer nombre" "defaultMessage": "Primer nombre"
}, },
"registration.page-title": {
"defaultMessage": "Registrarse | WiseMapping"
},
"accountinfo.lastname": { "accountinfo.lastname": {
"defaultMessage": "Apellido" "defaultMessage": "Apellido"
}, },

View File

@ -14,6 +14,9 @@
"accountinfo.lastname": { "accountinfo.lastname": {
"defaultMessage": "Nom de famille" "defaultMessage": "Nom de famille"
}, },
"registration.page-title": {
"defaultMessage": "Inscription | WiseMapping"
},
"accountinfo.title": { "accountinfo.title": {
"defaultMessage": "Informations de compte" "defaultMessage": "Informations de compte"
}, },

View File

@ -25,57 +25,40 @@
"@formatjs/cli": "^2.13.15", "@formatjs/cli": "^2.13.15",
"@testing-library/cypress": "^7.0.3", "@testing-library/cypress": "^7.0.3",
"@types/testing-library__cypress": "^5.0.8", "@types/testing-library__cypress": "^5.0.8",
"@typescript-eslint/eslint-plugin": "^4.8.1", "@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^4.8.1", "@typescript-eslint/parser": "^5.41.0",
"brotli-webpack-plugin": "^1.1.0", "clean-webpack-plugin": "^3.0.05.10.11",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^7.1.2",
"copy-webpack-plugin": "^7.0.0", "copy-webpack-plugin": "^7.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.0.1",
"cypress": "^8.4.1", "cypress": "^8.4.1",
"cypress-image-snapshot": "^4.0.1", "cypress-image-snapshot": "^4.0.1",
"eslint": "^7.14.0", "eslint": "^7.14.0",
"eslint-config-prettier": "^8.0.0", "eslint-config-prettier": "^8.0.0",
"eslint-plugin-react": "^7.21.5", "eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-hooks": "^4.6.0",
"file-loader": "^6.2.0",
"html-webpack-dynamic-env-plugin": "^0.0.2",
"html-webpack-plugin": "^5.1.0", "html-webpack-plugin": "^5.1.0",
"prettier": "^2.2.1", "prettier": "^2.2.1",
"sass-loader": "^10.1.0",
"start-server-and-test": "^1.12.0", "start-server-and-test": "^1.12.0",
"style-loader": "^2.0.0", "typescript": "^4.8.4",
"ts-loader": "^8.0.11",
"ts-node": "^9.0.0",
"typescript": "^4.1.2",
"url-loader": "^4.1.1",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-bundle-analyzer": "^4.4.0", "webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.7.3" "webpack-merge": "^5.7.3"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.4", "@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@mui/icons-material": "^5.9.3", "@mui/icons-material": "^5.9.3",
"@mui/lab": "^5.0.0-alpha.98", "@mui/lab": "^5.0.0-alpha.98",
"@mui/material": "^5.9.3", "@mui/material": "^5.10.11",
"@mui/styles": "^5.9.3", "@mui/styles": "^5.9.3",
"@reduxjs/toolkit": "^1.5.0", "@reduxjs/toolkit": "^1.5.0",
"@wisemapping/editor": "^0.4.0", "@wisemapping/editor": "^0.4.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"react": "^17.0.2", "react": "^18.2.0",
"react-dom": "^17.0.2",
"react-ga4": "^1.4.1", "react-ga4": "^1.4.1",
"react-google-recaptcha": "^2.1.0", "react-google-recaptcha": "^2.1.0",
"react-intl": "^4.7.6", "react-intl": "^5.25.1",
"react-query": "^3.39.1", "react-query": "^3.39.1",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-router": "^5.1.8", "react-router-dom": "^5.2.0"
"react-router-dom": "^5.2.0",
"styled-components": "^5.3.5"
} }
} }

View File

@ -1,4 +1,4 @@
import { fetchAccount } from './../../redux/clientSlice'; import { useFetchAccount } from './../../redux/clientSlice';
import 'dayjs/locale/fr'; import 'dayjs/locale/fr';
import 'dayjs/locale/en'; import 'dayjs/locale/en';
import 'dayjs/locale/es'; import 'dayjs/locale/es';
@ -26,7 +26,7 @@ export default abstract class AppI18n {
const isTryPage = window.location.pathname.endsWith('/try'); const isTryPage = window.location.pathname.endsWith('/try');
let result: Locale; let result: Locale;
if (!isTryPage) { if (!isTryPage) {
const account = fetchAccount(); const account = useFetchAccount();
result = account?.locale ? account.locale : this.getDefaultLocale(); result = account?.locale ? account.locale : this.getDefaultLocale();
// If the local storage value is different, update ... // If the local storage value is different, update ...

View File

@ -873,6 +873,12 @@
"value": "Apellido" "value": "Apellido"
} }
], ],
"registration.page-title": [
{
"type": 0,
"value": "Registrarse | WiseMapping"
}
],
"registration.password": [ "registration.password": [
{ {
"type": 0, "type": 0,

View File

@ -919,6 +919,12 @@
"value": "Nom de famille" "value": "Nom de famille"
} }
], ],
"registration.page-title": [
{
"type": 0,
"value": "Inscription | WiseMapping"
}
],
"registration.password": [ "registration.password": [
{ {
"type": 0, "type": 0,

View File

@ -6,7 +6,7 @@ import {
LocalStorageManager, LocalStorageManager,
Mindmap, Mindmap,
MockPersistenceManager, MockPersistenceManager,
XMLSerializerTango, XMLSerializerFactory,
} from '@wisemapping/editor'; } from '@wisemapping/editor';
export const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => { export const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => {
@ -54,7 +54,7 @@ export const getMindmapFromPersistence = (mapId: string): Mindmap => {
'text/xml', 'text/xml',
); );
const serializer = new XMLSerializerTango(); const serializer = XMLSerializerFactory.getSerializer('tango');
mindmap = serializer.loadFromDom(xmlDoc, String(mapId)); mindmap = serializer.loadFromDom(xmlDoc, String(mapId));
} }
return mindmap; return mindmap;

View File

@ -25,7 +25,7 @@ import AppI18n, { Locales } from '../../classes/app-i18n';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { hotkeysEnabled } from '../../redux/editorSlice'; import { hotkeysEnabled } from '../../redux/editorSlice';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import { fetchAccount, fetchMapById } from '../../redux/clientSlice'; import { useFetchAccount, useFetchMapById } from '../../redux/clientSlice';
import EditorOptionsBuilder from './EditorOptionsBuilder'; import EditorOptionsBuilder from './EditorOptionsBuilder';
import { buildPersistenceManagerForEditor } from './PersistenceManagerUtils'; import { buildPersistenceManagerForEditor } from './PersistenceManagerUtils';
import { useTheme } from '@mui/material/styles'; 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` }); 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; let result: EditorRenderMode = null;
if (isTryMode) { if (isTryMode) {
result = 'showcase'; result = 'showcase';
} else if (global.mindmapLocked) { } else if (global.mindmapLocked) {
result = 'viewonly'; result = 'viewonly';
} else { } else {
const fetchResult = fetchMapById(mapId); const fetchResult = useFetchMapById(mapId);
if (!fetchResult.isLoading) { if (!fetchResult.isLoading) {
if (fetchResult.error) { if (fetchResult.error) {
throw new Error(`Map info could not be loaded: ${JSON.stringify(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 ? // What is the role ?
const mapId = EditorOptionsBuilder.loadMapId(); 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 // Account settings can be null and editor cannot be initilized multiple times. This creates problems
// at the i18n resource loading. // at the i18n resource loading.
const isAccountLoaded = mode === 'showcase' || fetchAccount; const isAccountLoaded = mode === 'showcase' || useFetchAccount;
const loadCompleted = mode && isAccountLoaded; const loadCompleted = mode && isAccountLoaded;
let options, persistence: PersistenceManager; let options, persistence: PersistenceManager;
@ -99,10 +99,10 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
} }
useEffect(() => { useEffect(() => {
if (options?.mapTitle) { if (mapInfo) {
document.title = `${options.mapTitle} | WiseMapping `; document.title = `${mapInfo.getTitle()} | WiseMapping `;
} }
}, [loadCompleted]); }, [mapInfo]);
return loadCompleted ? ( return loadCompleted ? (
<IntlProvider <IntlProvider

View File

@ -13,32 +13,6 @@ import Link from '@mui/material/Link';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import { getCsrfToken, getCsrfTokenParameter } from '../../utils'; import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
type ConfigStatusProps = {
enabled?: boolean;
};
const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.ReactElement => {
let result;
if (enabled === true) {
result = (
<div className="db-warn-msg">
<p>
<FormattedMessage
id="login.hsqldbcofig"
defaultMessage="Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL"
description="Missing production database configured"
/>
<a href="https://wisemapping.atlassian.net/wiki/display/WS/Database+Configuration">
{' '}
here
</a>
</p>
</div>
);
}
return result || null;
};
const LoginError = () => { const LoginError = () => {
// @Todo: This must be reviewed to be based on navigation state. // @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2 // Login error example: http://localhost:8080/c/login?login.error=2
@ -132,7 +106,6 @@ const LoginPage = (): React.ReactElement => {
<Link component={RouterLink} to="/c/forgot-password"> <Link component={RouterLink} to="/c/forgot-password">
<FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" /> <FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" />
</Link> </Link>
<ConfigStatusMessage />
</FormContainer> </FormContainer>
<Footer /> <Footer />

View File

@ -5,7 +5,7 @@ import Client, { ErrorInfo } from '../../../../classes/client';
import Input from '../../../form/input'; import Input from '../../../form/input';
import BaseDialog from '../../action-dispatcher/base-dialog'; import BaseDialog from '../../action-dispatcher/base-dialog';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { activeInstance, fetchAccount } from '../../../../redux/clientSlice'; import { activeInstance, useFetchAccount } from '../../../../redux/clientSlice';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
@ -63,7 +63,7 @@ const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElem
}, },
); );
const account = fetchAccount(); const account = useFetchAccount();
useEffect(() => { useEffect(() => {
if (account) { if (account) {
setModel({ setModel({

View File

@ -7,7 +7,7 @@ import SettingsApplicationsOutlined from '@mui/icons-material/SettingsApplicatio
import AccountCircle from '@mui/icons-material/AccountCircle'; import AccountCircle from '@mui/icons-material/AccountCircle';
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { fetchAccount } from '../../../redux/clientSlice'; import { useFetchAccount } from '../../../redux/clientSlice';
import AccountInfoDialog from './account-info-dialog'; import AccountInfoDialog from './account-info-dialog';
import ChangePasswordDialog from './change-password-dialog'; import ChangePasswordDialog from './change-password-dialog';
import LockOpenOutlined from '@mui/icons-material/LockOpenOutlined'; import LockOpenOutlined from '@mui/icons-material/LockOpenOutlined';
@ -34,7 +34,7 @@ const AccountMenu = (): React.ReactElement => {
elem.submit(); elem.submit();
}; };
const account = fetchAccount(); const account = useFetchAccount();
return ( return (
<span> <span>
<Tooltip <Tooltip

View File

@ -12,7 +12,7 @@ import LabelOutlined from '@mui/icons-material/LabelOutlined';
import HistoryOutlined from '@mui/icons-material/HistoryOutlined'; import HistoryOutlined from '@mui/icons-material/HistoryOutlined';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { fetchMapById } from '../../../redux/clientSlice'; import { useFetchMapById } from '../../../redux/clientSlice';
import Menu from '@mui/material/Menu'; import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemIcon from '@mui/material/ListItemIcon';
@ -52,7 +52,7 @@ const ActionChooser = (props: ActionProps): React.ReactElement => {
}; };
}; };
const role = mapId ? fetchMapById(mapId)?.map?.role : undefined; const role = mapId ? useFetchMapById(mapId)?.map?.role : undefined;
return ( return (
<Menu <Menu
anchorEl={anchor} anchorEl={anchor}

View File

@ -3,7 +3,7 @@ import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query'; import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../../../classes/client'; import Client, { ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'; import { activeInstance, useFetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from '..'; import { SimpleDialogProps, handleOnMutationSuccess } from '..';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
@ -30,7 +30,7 @@ const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
mutation.mutate(mapId); mutation.mutate(mapId);
}; };
const { map } = fetchMapById(mapId); const { map } = useFetchMapById(mapId);
const alertTitle = `${intl.formatMessage({ const alertTitle = `${intl.formatMessage({
id: 'action.delete-title', id: 'action.delete-title',
defaultMessage: 'Delete', defaultMessage: 'Delete',

View File

@ -5,7 +5,7 @@ import FormControl from '@mui/material/FormControl';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client'; import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'; import { activeInstance, useFetchMapById } from '../../../../redux/clientSlice';
import Input from '../../../form/input'; import Input from '../../../form/input';
import { SimpleDialogProps } from '..'; import { SimpleDialogProps } from '..';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
@ -56,7 +56,7 @@ const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElem
setModel({ ...model, [name as keyof BasicMapInfo]: value }); setModel({ ...model, [name as keyof BasicMapInfo]: value });
}; };
const { map } = fetchMapById(mapId); const { map } = useFetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
setModel(map); setModel(map);

View File

@ -3,7 +3,7 @@ import { FormattedMessage, useIntl } from 'react-intl';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
import { useStyles } from './style'; import { useStyles } from './style';
import Alert from '@mui/material/Alert'; import Alert from '@mui/material/Alert';
import { fetchMapById } from '../../../../redux/clientSlice'; import { useFetchMapById } from '../../../../redux/clientSlice';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
import RadioGroup from '@mui/material/RadioGroup'; import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
@ -39,7 +39,7 @@ const ExportDialog = ({
}: ExportDialogProps): React.ReactElement => { }: ExportDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const [submit, setSubmit] = React.useState<boolean>(false); const [submit, setSubmit] = React.useState<boolean>(false);
const { map } = fetchMapById(mapId); const { map } = useFetchMapById(mapId);
const [exportGroup, setExportGroup] = React.useState<ExportGroup>( const [exportGroup, setExportGroup] = React.useState<ExportGroup>(
enableImgExport ? 'image' : 'document', enableImgExport ? 'image' : 'document',

View File

@ -34,11 +34,13 @@ const ActionDispatcher = ({
fromEditor, fromEditor,
}: ActionDialogProps): React.ReactElement => { }: ActionDialogProps): React.ReactElement => {
useEffect(() => { useEffect(() => {
ReactGA.event({ if (action) {
category: 'map metadata', ReactGA.event({
action: action, category: 'map metadata',
nonInteraction: true, action: action,
}); nonInteraction: true,
});
}
}, [action]); }, [action]);
const handleOnClose = (success?: boolean): void => { const handleOnClose = (success?: boolean): void => {

View File

@ -6,7 +6,7 @@ import BaseDialog from '../base-dialog';
import { SimpleDialogProps } from '..'; import { SimpleDialogProps } from '..';
import { useStyles } from './style'; import { useStyles } from './style';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { fetchMapById } from '../../../../redux/clientSlice'; import { useFetchMapById } from '../../../../redux/clientSlice';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import Card from '@mui/material/Card'; import Card from '@mui/material/Card';
import ListItem from '@mui/material/ListItem'; import ListItem from '@mui/material/ListItem';
@ -18,7 +18,7 @@ import LocalizedFormat from 'dayjs/plugin/localizedFormat';
dayjs.extend(LocalizedFormat); dayjs.extend(LocalizedFormat);
const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId); const { map } = useFetchMapById(mapId);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();

View File

@ -3,7 +3,7 @@ import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query'; import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../../../classes/client'; import Client, { ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'; import { activeInstance, useFetchMapById } from '../../../../redux/clientSlice';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
import { handleOnMutationSuccess, SimpleDialogProps } from '..'; import { handleOnMutationSuccess, SimpleDialogProps } from '..';
import { useStyles } from './style'; import { useStyles } from './style';
@ -17,11 +17,11 @@ import Tab from '@mui/material/Tab';
import TabPanel from '@mui/lab/TabPanel'; import TabPanel from '@mui/lab/TabPanel';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import TextareaAutosize from '@mui/material/TextareaAutosize'; import TextareaAutosize from '@mui/material/TextareaAutosize';
import Box from '@mui/system/Box';
import AppConfig from '../../../../classes/app-config'; import AppConfig from '../../../../classes/app-config';
import Box from '@mui/material/Box';
const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId); const { map } = useFetchMapById(mapId);
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false); const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false);
@ -38,6 +38,7 @@ const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElemen
onSuccess: () => { onSuccess: () => {
setModel(model); setModel(model);
handleOnMutationSuccess(onClose, queryClient); handleOnMutationSuccess(onClose, queryClient);
queryClient.invalidateQueries(`maps-${mapId}`);
}, },
onError: (error) => { onError: (error) => {
setError(error); setError(error);

View File

@ -3,7 +3,7 @@ import { useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query'; import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client'; import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'; import { activeInstance, useFetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from '..'; import { SimpleDialogProps, handleOnMutationSuccess } from '..';
import Input from '../../../form/input'; import Input from '../../../form/input';
import BaseDialog from '../base-dialog'; import BaseDialog from '../base-dialog';
@ -58,7 +58,7 @@ const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
setModel({ ...model, [name as keyof BasicMapInfo]: value }); setModel({ ...model, [name as keyof BasicMapInfo]: value });
}; };
const { map } = fetchMapById(mapId); const { map } = useFetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
setModel(map); setModel(map);

View File

@ -1,16 +1,16 @@
import { alpha, Theme } from '@mui/material/styles'; import { alpha, useTheme } from '@mui/material/styles';
import createStyles from '@mui/styles/createStyles'; import createStyles from '@mui/styles/createStyles';
import makeStyles from '@mui/styles/makeStyles'; import makeStyles from '@mui/styles/makeStyles';
export const useStyles = makeStyles((theme: Theme) => export const useStyles = makeStyles(() =>
createStyles({ createStyles({
root: { root: {
width: '100%', width: '100%',
}, },
paper: { paper: {
width: '100%', width: '100%',
marginBottom: theme.spacing(2), marginBottom: useTheme().spacing(2),
}, },
table: { table: {
minWidth: 750, minWidth: 750,
@ -66,14 +66,14 @@ export const useStyles = makeStyles((theme: Theme) =>
}, },
search: { search: {
borderRadius: 9, borderRadius: 9,
backgroundColor: alpha(theme.palette.common.white, 0.15), backgroundColor: alpha(useTheme().palette.common.white, 0.15),
'&:hover': { '&:hover': {
backgroundColor: alpha(theme.palette.common.white, 0.25), backgroundColor: alpha(useTheme().palette.common.white, 0.25),
}, },
margin: '10px 0px', margin: '10px 0px',
width: '100%', width: '100%',
[theme.breakpoints.up('sm')]: { [useTheme().breakpoints.up('sm')]: {
marginLeft: theme.spacing(1), marginLeft: useTheme().spacing(1),
width: 'auto', width: 'auto',
}, },
float: 'right', float: 'right',
@ -97,10 +97,10 @@ export const useStyles = makeStyles((theme: Theme) =>
// vertical padding + font size from searchIcon // vertical padding + font size from searchIcon
border: '1px solid #ffa800', border: '1px solid #ffa800',
borderRadius: 4, borderRadius: 4,
paddingLeft: `calc(1em + ${theme.spacing(4)})`, paddingLeft: `calc(1em + ${useTheme().spacing(4)})`,
transition: theme.transitions.create('width'), transition: useTheme().transitions.create('width'),
width: '100%', width: '100%',
[theme.breakpoints.up('sm')]: { [useTheme().breakpoints.up('sm')]: {
width: '12ch', width: '12ch',
'&:focus': { '&:focus': {
width: '20ch', width: '20ch',

View File

@ -1,30 +1,19 @@
import { Theme } from '@mui/material/styles';
import createStyles from '@mui/styles/createStyles'; import createStyles from '@mui/styles/createStyles';
import makeStyles from '@mui/styles/makeStyles'; import makeStyles from '@mui/styles/makeStyles';
const drawerWidth = 300; const drawerWidth = 300;
export const useStyles = makeStyles((theme: Theme) => export const useStyles = makeStyles(() =>
createStyles({ createStyles({
root: { root: {
display: 'flex', display: 'flex',
}, },
appBar: { appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
background: '#ffffff', background: '#ffffff',
}, },
appBarShift: { appBarShift: {
marginLeft: drawerWidth, marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`, width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}, },
newMapButton: { newMapButton: {
marginRight: 10, marginRight: 10,
@ -48,10 +37,6 @@ export const useStyles = makeStyles((theme: Theme) =>
drawerOpen: { drawerOpen: {
background: '#ffa800', background: '#ffa800',
width: drawerWidth, width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
}, },
toolbar: { toolbar: {
display: 'flex', display: 'flex',

View File

@ -1,9 +1,11 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import App from './app'; import App from './app';
import { createRoot } from 'react-dom/client';
async function bootstrapApplication() { async function bootstrapApplication() {
ReactDOM.render(<App />, document.getElementById('root') as HTMLElement); const container = document.getElementById('root') as HTMLElement
const root = createRoot(container!);
root.render(<App />);
} }
bootstrapApplication(); bootstrapApplication();

View File

@ -58,7 +58,7 @@ type MapLoadResult = {
map: MapInfo | null; map: MapInfo | null;
}; };
export const fetchMapById = (id: number): MapLoadResult => { export const useFetchMapById = (id: number): MapLoadResult => {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const { isLoading, error, data } = useQuery<unknown, ErrorInfo, MapInfo[]>(`maps-${id}`, () => { const { isLoading, error, data } = useQuery<unknown, ErrorInfo, MapInfo[]>(`maps-${id}`, () => {
return client.fetchAllMaps(); return client.fetchAllMaps();
@ -85,7 +85,7 @@ export const fetchMapById = (id: number): MapLoadResult => {
return { isLoading: isLoading, error: errorMsg, map: map }; return { isLoading: isLoading, error: errorMsg, map: map };
}; };
export const fetchAccount = (): AccountInfo | undefined => { export const useFetchAccount = (): AccountInfo | undefined => {
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const { data } = useQuery<unknown, ErrorInfo, AccountInfo>('account', () => { const { data } = useQuery<unknown, ErrorInfo, AccountInfo>('account', () => {
return client.fetchAccountInfo(); return client.fetchAccountInfo();

View File

@ -1,9 +1,14 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import rootReducer from './rootReducer'; import rootReducer from './rootReducer';
// Create Service object... // Create Service object...
const store = configureStore({ const store = configureStore({
reducer: rootReducer, reducer: rootReducer,
middleware: [
...getDefaultMiddleware({
serializableCheck: false,
}),
],
}); });
export default store; export default store;

View File

@ -1,15 +1,15 @@
export const getCsrfToken = (): string | null => { export const getCsrfToken = (): string | null => {
const meta = document.head.querySelector('meta[name="_csrf"]'); const meta = document.head.querySelector('meta[name="_csrf"]');
if (!meta) { if (!meta) {
return null; return '';
} }
return meta.getAttribute('content'); return meta.getAttribute('content');
}; };
export const getCsrfTokenParameter = (): string | null => { export const getCsrfTokenParameter = (): string => {
const meta = document.head.querySelector('meta[name="_csrf_parameter"]'); const meta = document.head.querySelector('meta[name="_csrf_parameter"]');
if (!meta) { if (!meta) {
return null; return '';
} }
return meta.getAttribute('content'); return meta.getAttribute('content');
}; };

1463
yarn.lock

File diff suppressed because it is too large Load Diff