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'
services:
e2e:
image: cypress/included:10.8.0
image: cypress/included:10.11.0
container_name: wisemapping-integration-tests
entrypoint: '/bin/sh -c "yarn install && yarn bootstrap && yarn build && yarn test:integration"'
working_dir: /e2e

View File

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

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

View File

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

View File

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

View File

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

View File

@ -36,12 +36,12 @@ const UndoAndRedo = ({ configuration, disabledCondition, model }: UndoAndRedo) =
setDisabled(!isDisabled);
return () => {
designer.removeEvent('modelUpdate', handleUpdate);
model.getDesigner().removeEvent('modelUpdate', handleUpdate);
};
};
if (model.getDesigner()) {
designer.addEvent('modelUpdate', handleUpdate);
model.getDesigner().addEvent('modelUpdate', handleUpdate);
}
}
}, [model?.isMapLoadded()]);
@ -52,7 +52,7 @@ const UndoAndRedo = ({ configuration, disabledCondition, model }: UndoAndRedo) =
...configuration,
disabled: () => disabled,
}}
></ToolbarMenuItem>
/>
);
};
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
* limitations under the License.
*/
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import React from 'react';
import iconGroups from './iconGroups.json';
import { ImageIcon } from '@wisemapping/mindplot';
import React, { useEffect } from 'react';
import NodeProperty from '../../../../classes/model/node-property';
import EmojiPicker, { EmojiClickData } from 'emoji-picker-react';
import DesignerKeyboard from '@wisemapping/mindplot/src/components/DesignerKeyboard';
import IconImageTab from './image-icon-tab';
import Switch from '@mui/material/Switch';
import FormGroup from '@mui/material/FormGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
/**
* emoji picker for editor toolbar
*/
const IconPicker = (props: { closeModal: () => void; iconModel: NodeProperty }) => {
const [value, setValue] = React.useState(0);
type IconPickerProp = {
closeModal: () => void;
iconModel: NodeProperty;
};
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
const IconPicker = ({ closeModal, iconModel }: IconPickerProp) => {
const [checked, setChecked] = React.useState(true);
const handleCheck = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(!checked);
};
// Review ...
useEffect(() => {
DesignerKeyboard.pause();
return () => { DesignerKeyboard.resume(); }
}, []);
const handleEmojiSelect = (emoji: EmojiClickData) => {
const emojiChar = emoji.emoji;
iconModel.setValue(`emoji:${emojiChar}`);
closeModal();
};
return (
<Box sx={{ width: '250px' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs variant="fullWidth" value={value} onChange={handleChange} aria-label="Icons tabs">
{iconGroups.map((family, i) => (
<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>
);
};
<div style={{ padding: '5px' }}>
<FormGroup>
<FormControlLabel label="Show Images" control={<Switch onChange={handleCheck} />} />
</FormGroup>
/**
* tab panel used for display icon families in tabs
*/
const TabPanel = (props: { children?: React.ReactNode; index: number; value: number }) => {
const { children, value, index } = props;
{checked && (
<EmojiPicker
onEmojiClick={handleEmojiSelect}
lazyLoadEmojis={true}
autoFocusSearch={true}
previewConfig={{ showPreview: false }}
/>
)}
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
>
{value === index && <Box>{children}</Box>}
{!checked && <IconImageTab iconModel={iconModel} />}
</div>
);
};
const a11yProps = (index: number) => {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
};
export default IconPicker;

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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 @@
/*
* Copyright [2021] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import { Image, Point } from '@wisemapping/web2d';
import { Point, ElementClass } from '@wisemapping/web2d';
import IconGroup from './IconGroup';
import SizeType from './SizeType';
import FeatureModel from './model/FeatureModel';
abstract class Icon {
protected _image: Image;
interface Icon {
getElement(): ElementClass;
protected _group: IconGroup;
setGroup(group: IconGroup);
constructor(url: string) {
$assert(url, 'image url can not be null');
this._image = new Image();
this._image.setHref(url);
this._image.setSize(Icon.SIZE, Icon.SIZE);
}
getGroup(): IconGroup;
getImage(): Image {
return this._image;
}
getSize(): SizeType;
setGroup(group: IconGroup) {
this._group = group;
}
getPosition(): Point;
getGroup(): IconGroup {
return this._group;
}
addEvent(type: string, fnc): void;
getSize(): SizeType {
return this._image.getSize();
}
remove(): void;
getPosition(): Point {
return this._image.getPosition();
}
addEvent(type: string, fnc): void {
this._image.addEvent(type, fnc);
}
// eslint-disable-next-line class-methods-use-this
remove() {
throw new Error('Unsupported operation');
}
abstract getModel(): FeatureModel;
static SIZE = 90;
getModel();
}
export default Icon;

View File

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

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.
*/
import { $assert } from '@wisemapping/core-js';
import Icon from './Icon';
import ImageIcon from './ImageIcon';
import LinksImage from '../../assets/icons/links.svg';
import LinkModel from './model/LinkModel';
import Topic from './Topic';
import FeatureModel from './model/FeatureModel';
import WidgetManager from './WidgetManager';
class LinkIcon extends Icon {
class LinkIcon extends ImageIcon {
private _linksModel: FeatureModel;
private _topic: Topic;

View File

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

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

View File

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

View File

@ -72,15 +72,15 @@ class WidgetManager {
}
createTooltipForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
this.createTooltip(linkIcon.getImage().peer, $msg('LINK'), linkModel, undefined);
this.createTooltip(linkIcon.getElement().peer, $msg('LINK'), linkModel, undefined);
}
createTooltipForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
this.createTooltip(noteIcon.getImage().peer, $msg('NOTE'), undefined, noteModel);
this.createTooltip(noteIcon.getElement().peer, $msg('NOTE'), undefined, noteModel);
}
configureEditorForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
const htmlImage = linkIcon.getImage().peer;
const htmlImage = linkIcon.getElement().peer;
htmlImage.addEvent('click', (evt) => {
this.showEditorForLink(topic, linkModel, linkIcon);
evt.stopPropagation();
@ -88,7 +88,7 @@ class WidgetManager {
}
configureEditorForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
const htmlImage = noteIcon.getImage().peer;
const htmlImage = noteIcon.getElement().peer;
htmlImage.addEvent('click', (evt) => {
this.showEditorForNote(topic, noteModel, noteIcon);
evt.stopPropagation();

View File

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

View File

@ -1,10 +1,10 @@
import IconModel from '../model/IconModel';
import SvgIconModel from '../model/SvgIconModel';
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 {
const result: IconModel = this.freeIdToIcon.get(iconId);
const result: SvgIconModel = this.freeIdToIcon.get(iconId);
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 IconModel from './IconModel';
import SvgIconModel from './SvgIconModel';
import LinkModel from './LinkModel';
import NoteModel from './NoteModel';
import FeatureModel from './FeatureModel';
import FeatureType from './FeatureType';
import EmojiIconModel from './EmojiIconModel';
interface NodeById {
id: FeatureType;
@ -14,7 +15,11 @@ class FeatureModelFactory {
static modelById: Array<NodeById> = [
{
id: 'icon',
model: IconModel,
model: SvgIconModel,
},
{
id: 'eicon',
model: EmojiIconModel,
},
{
id: 'link',

View File

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

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 FeatureModel from './FeatureModel';
class IconModel extends FeatureModel {
class SvgIconModel extends FeatureModel {
constructor(attributes) {
super('icon');
this.setIconType(attributes.id);
@ -33,4 +33,5 @@ class IconModel extends FeatureModel {
this.setAttribute('id', iconType);
}
}
export default IconModel;
export default SvgIconModel;

View File

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

View File

@ -31,8 +31,10 @@ import Exporter from './components/export/Exporter';
import Importer from './components/import/Importer';
import DesignerKeyboard from './components/DesignerKeyboard';
import EditorRenderMode from './components/EditorRenderMode';
import DesignerModel from './components/DesignerModel';
import SvgImageIcon from './components/SvgImageIcon';
import ImageIcon from './components/ImageIcon';
import MindplotWebComponent, {
MindplotWebComponentInterface,
} from './components/MindplotWebComponent';
@ -48,8 +50,13 @@ import WidgetManager from './components/WidgetManager';
import { buildDesigner } from './components/DesignerBuilder';
import { $notify } from './components/widget/ToolbarNotifier';
import XMLSerializerFactory from './components/persistence/XMLSerializerFactory';
import { $msg } from './components/Messages';
declare global {
// Todo: There are some global references that needs to be removed inside mindplot.
// eslint-disable-next-line vars-on-top, no-var
var designer: Designer;
}
const globalAny: any = global;
globalAny.jQuery = jquery;
@ -62,6 +69,7 @@ if (!customElements.get('mindplot-component')) {
export {
Mindmap,
Designer,
DesignerModel,
DesignerBuilder,
PersistenceManager,
RESTPersistenceManager,
@ -74,10 +82,9 @@ export {
ImageExporterFactory,
TextImporterFactory,
Exporter,
SvgImageIcon,
Importer,
ImageIcon,
$notify,
$msg,
DesignerKeyboard,
MindplotWebComponent,
MindplotWebComponentInterface,
@ -87,4 +94,5 @@ export {
NoteModel,
WidgetManager,
Topic,
XMLSerializerFactory,
};

View File

@ -29,7 +29,7 @@
"devDependencies": {
"@babel/core": "^7.18.13",
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
"@babel/preset-env": "^7.14.7",
"@babel/preset-env": "^7.19.4",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^4.0.0",
"core-js": "^3.15.2",

View File

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

View File

@ -23,11 +23,14 @@
],
"plugins": [
"react",
"@typescript-eslint"
"@typescript-eslint",
"react-hooks"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-module-boundary-types": "error",
"@typescript-eslint/no-unused-vars": "error"
"@typescript-eslint/no-unused-vars": "error",
"react-hooks/rules-of-hooks": "warn" // Checks rules of Hooks
// "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,32 +13,6 @@ import Link from '@mui/material/Link';
import ReactGA from 'react-ga4';
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 = () => {
// @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2
@ -132,7 +106,6 @@ const LoginPage = (): React.ReactElement => {
<Link component={RouterLink} to="/c/forgot-password">
<FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" />
</Link>
<ConfigStatusMessage />
</FormContainer>
<Footer />

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import LabelOutlined from '@mui/icons-material/LabelOutlined';
import HistoryOutlined from '@mui/icons-material/HistoryOutlined';
import { FormattedMessage } from 'react-intl';
import { fetchMapById } from '../../../redux/clientSlice';
import { useFetchMapById } from '../../../redux/clientSlice';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
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 (
<Menu
anchorEl={anchor}

View File

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

View File

@ -5,7 +5,7 @@ import FormControl from '@mui/material/FormControl';
import { useSelector } from 'react-redux';
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 { SimpleDialogProps } from '..';
import BaseDialog from '../base-dialog';
@ -56,7 +56,7 @@ const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElem
setModel({ ...model, [name as keyof BasicMapInfo]: value });
};
const { map } = fetchMapById(mapId);
const { map } = useFetchMapById(mapId);
useEffect(() => {
if (map) {
setModel(map);

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import { useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import { activeInstance, useFetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from '..';
import Input from '../../../form/input';
import BaseDialog from '../base-dialog';
@ -58,7 +58,7 @@ const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
setModel({ ...model, [name as keyof BasicMapInfo]: value });
};
const { map } = fetchMapById(mapId);
const { map } = useFetchMapById(mapId);
useEffect(() => {
if (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 makeStyles from '@mui/styles/makeStyles';
export const useStyles = makeStyles((theme: Theme) =>
export const useStyles = makeStyles(() =>
createStyles({
root: {
width: '100%',
},
paper: {
width: '100%',
marginBottom: theme.spacing(2),
marginBottom: useTheme().spacing(2),
},
table: {
minWidth: 750,
@ -66,14 +66,14 @@ export const useStyles = makeStyles((theme: Theme) =>
},
search: {
borderRadius: 9,
backgroundColor: alpha(theme.palette.common.white, 0.15),
backgroundColor: alpha(useTheme().palette.common.white, 0.15),
'&:hover': {
backgroundColor: alpha(theme.palette.common.white, 0.25),
backgroundColor: alpha(useTheme().palette.common.white, 0.25),
},
margin: '10px 0px',
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
[useTheme().breakpoints.up('sm')]: {
marginLeft: useTheme().spacing(1),
width: 'auto',
},
float: 'right',
@ -97,10 +97,10 @@ export const useStyles = makeStyles((theme: Theme) =>
// vertical padding + font size from searchIcon
border: '1px solid #ffa800',
borderRadius: 4,
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create('width'),
paddingLeft: `calc(1em + ${useTheme().spacing(4)})`,
transition: useTheme().transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
[useTheme().breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',

View File

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

View File

@ -1,9 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import { createRoot } from 'react-dom/client';
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();

View File

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

View File

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

View File

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

1463
yarn.lock

File diff suppressed because it is too large Load Diff