Add status change support in mindmap editor

This commit is contained in:
Paulo Gustavo Veiga 2022-10-21 17:23:32 -07:00
parent 7a9c89b8ae
commit 9b10a987f3
22 changed files with 492 additions and 197 deletions

View File

@ -181,6 +181,10 @@ const ActionConfigByRenderMode: Record<ActionType, CapabilitySupport> = {
hidden: ['viewonly', 'edition-viewer', 'edition-editor', 'edition-owner'], hidden: ['viewonly', 'edition-viewer', 'edition-editor', 'edition-owner'],
}, },
}, },
starred: undefined, starred: {
desktop: {
hidden: ['showcase', 'viewonly'],
},
},
}; };
export default Capability; export default Capability;

View File

@ -48,16 +48,16 @@ class Editor {
this.component.loadMap(mapId); this.component.loadMap(mapId);
} }
registerEvents(setToolbarsRerenderSwitch: (timestamp: number) => void, capability: Capability) { registerEvents(canvasUpdate: (timestamp: number) => void, capability: Capability) {
const desiger = this.component.getDesigner(); const desiger = this.component.getDesigner();
const onNodeBlurHandler = () => { const onNodeBlurHandler = () => {
if (!desiger.getModel().selectedTopic()) { if (!desiger.getModel().selectedTopic()) {
setToolbarsRerenderSwitch(Date.now()); canvasUpdate(Date.now());
} }
}; };
const onNodeFocusHandler = () => { const onNodeFocusHandler = () => {
setToolbarsRerenderSwitch(Date.now()); canvasUpdate(Date.now());
}; };
// Register events ... // Register events ...

View File

@ -0,0 +1,32 @@
/*
* 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.
*/
interface MapInfo {
isStarred(): Promise<boolean>;
updateStarred(value: boolean): Promise<void>;
getTitle(): string;
setTitle(value: string): void;
isLocked(): boolean;
getLockedMessage(): string;
getZoom(): number;
getId(): string;
}
export default MapInfo;

View File

@ -15,7 +15,7 @@
* 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 React from 'react'; import React, { useEffect, useState } from 'react';
import MaterialToolbar from '@mui/material/Toolbar'; import MaterialToolbar from '@mui/material/Toolbar';
import MaterialAppBar from '@mui/material/AppBar'; import MaterialAppBar from '@mui/material/AppBar';
import { ToolbarMenuItem } from '../toolbar'; import { ToolbarMenuItem } from '../toolbar';
@ -40,159 +40,168 @@ import Button from '@mui/material/Button';
import LogoTextBlackSvg from '../../../images/logo-text-black.svg'; import LogoTextBlackSvg from '../../../images/logo-text-black.svg';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import { ToolbarActionType } from '../toolbar/ToolbarActionType'; import { ToolbarActionType } from '../toolbar/ToolbarActionType';
import MapInfo from '../../classes/model/map-info';
interface AppBarProps { interface AppBarProps {
model: Editor; model: Editor;
mapTitle: string; mapInfo: MapInfo;
capability: Capability; capability: Capability;
onAction?: (type: ToolbarActionType) => void; onAction?: (type: ToolbarActionType) => void;
accountConfig?; accountConfig?;
} }
const appBarDivisor = {
render: () => <Typography component="div" sx={{ flexGrow: 1 }} />,
};
const AppBar = ({ model, mapTitle, capability, onAction, accountConfig }: AppBarProps) => { const AppBar = ({ model, mapInfo, capability, onAction, accountConfig }: AppBarProps) => {
const appBarDivisor = { const [isStarred, setStarred] = useState<undefined | boolean>(undefined);
render: () => <Typography component="div" sx={{ flexGrow: 1 }} />,
const handleStarredOnClick = () => {
const newStatus = !isStarred;
mapInfo.updateStarred(newStatus).then(() => setStarred(newStatus));
}; };
const buildConfig = ( useEffect(() => {
model: Editor, mapInfo
mapTitle: string, .isStarred()
capability: Capability, .then((value) => setStarred(value))
onAction: (type: ToolbarActionType) => void, .catch((e) => {
accountConfig, console.error(`Unexpected error loading starred status-> ${e}`);
): ActionConfig[] => { });
return [ }, []);
{
icon: <ArrowBackIosNewOutlinedIcon />, console.log(``);
tooltip: $msg('BACK_TO_MAP_LIST'), const config: ActionConfig[] = [
onClick: () => history.back(), {
}, icon: <ArrowBackIosNewOutlinedIcon />,
{ tooltip: $msg('BACK_TO_MAP_LIST'),
render: () => <img src={LogoTextBlackSvg} />, onClick: () => history.back(),
}, },
{ {
render: () => ( render: () => <img src={LogoTextBlackSvg} />,
<Tooltip title={mapTitle}> },
<Typography {
className="truncated" render: () => (
variant="body1" <Tooltip title={mapInfo.getTitle()}>
component="div" <Typography
sx={{ marginX: '1.5rem' }} className="truncated"
> variant="body1"
{mapTitle} component="div"
</Typography> sx={{ marginX: '1.5rem' }}
</Tooltip> >
), {mapInfo.getTitle()}
}, </Typography>
null, </Tooltip>
{ ),
render: () => ( },
<UndoAndRedo null,
configuration={{ {
icon: <UndoOutlinedIcon />, render: () => (
tooltip: $msg('UNDO') + ' (' + $msg('CTRL') + ' + Z)', <UndoAndRedo
onClick: () => designer.undo(), configuration={{
}} icon: <UndoOutlinedIcon />,
disabledCondition={(event) => event.undoSteps > 0} tooltip: $msg('UNDO') + ' (' + $msg('CTRL') + ' + Z)',
></UndoAndRedo> onClick: () => designer.undo(),
), }}
visible: !capability.isHidden('undo-changes'), disabledCondition={(event) => event.undoSteps > 0}
disabled: () => !model?.isMapLoadded(), />
}, ),
{ visible: !capability.isHidden('undo-changes'),
render: () => ( disabled: () => !model?.isMapLoadded(),
<UndoAndRedo },
configuration={{ {
icon: <RedoOutlinedIcon />, render: () => (
tooltip: $msg('REDO') + ' (' + $msg('CTRL') + ' + Shift + Z)', <UndoAndRedo
onClick: () => designer.redo(), configuration={{
}} icon: <RedoOutlinedIcon />,
disabledCondition={(event) => event.redoSteps > 0} tooltip: $msg('REDO') + ' (' + $msg('CTRL') + ' + Shift + Z)',
></UndoAndRedo> onClick: () => designer.redo(),
), }}
visible: !capability.isHidden('redo-changes'), disabledCondition={(event) => event.redoSteps > 0}
disabled: () => !model?.isMapLoadded(), />
}, ),
null, visible: !capability.isHidden('redo-changes'),
{ disabled: () => !model?.isMapLoadded(),
icon: <SaveOutlinedIcon />, },
tooltip: $msg('SAVE') + ' (' + $msg('CTRL') + ' + S)', null,
onClick: () => { {
model.save(true); icon: <SaveOutlinedIcon />,
}, tooltip: $msg('SAVE') + ' (' + $msg('CTRL') + ' + S)',
visible: !capability.isHidden('save'), onClick: () => {
disabled: () => !model?.isMapLoadded(), model.save(true);
}, },
{ visible: !capability.isHidden('save'),
icon: <RestoreOutlinedIcon />, disabled: () => !model?.isMapLoadded(),
tooltip: $msg('HISTORY'), },
onClick: () => onAction('history'), {
visible: !capability.isHidden('history'), icon: <RestoreOutlinedIcon />,
}, tooltip: $msg('HISTORY'),
appBarDivisor, onClick: () => onAction('history'),
{ visible: !capability.isHidden('history'),
tooltip: $msg('SAVE') + ' (' + $msg('CTRL') + ' + S)', },
render: () => ( appBarDivisor,
<IconButton size="small" onClick={() => {}}> {
<StarRateRoundedIcon tooltip: $msg('STARRED'),
color="action" render: () => (
style={{ <IconButton size="small" onClick={handleStarredOnClick}>
color: 'yellow', <StarRateRoundedIcon
}} color="action"
/> style={{
</IconButton> color: isStarred ? 'yellow' : 'gray',
), }}
visible: !capability.isHidden('starred'), />
disabled: () => !model?.isMapLoadded(), </IconButton>
}, ),
{
icon: <FileDownloadOutlinedIcon />, visible: !capability.isHidden('starred'),
tooltip: $msg('EXPORT'), disabled: () => isStarred !== undefined,
onClick: () => onAction('export'), },
visible: !capability.isHidden('export'), {
}, icon: <FileDownloadOutlinedIcon />,
{ tooltip: $msg('EXPORT'),
icon: <PrintOutlinedIcon />, onClick: () => onAction('export'),
tooltip: $msg('PRINT'), visible: !capability.isHidden('export'),
onClick: () => onAction('print'), },
visible: !capability.isHidden('print'), {
}, icon: <PrintOutlinedIcon />,
{ tooltip: $msg('PRINT'),
icon: <HelpOutlineOutlinedIcon />, onClick: () => onAction('print'),
onClick: () => onAction('info'), visible: !capability.isHidden('print'),
tooltip: $msg('MAP_INFO'), },
visible: !capability.isHidden('info'), {
}, icon: <HelpOutlineOutlinedIcon />,
{ onClick: () => onAction('info'),
icon: <CloudUploadOutlinedIcon />, tooltip: $msg('MAP_INFO'),
onClick: () => onAction('publish'), visible: !capability.isHidden('info'),
tooltip: $msg('PUBLISH'), },
visible: !capability.isHidden('publish'), {
}, icon: <CloudUploadOutlinedIcon />,
{ onClick: () => onAction('publish'),
render: () => ( tooltip: $msg('PUBLISH'),
<Button variant="contained" onClick={() => onAction('share')}> visible: !capability.isHidden('publish'),
{$msg('COLLABORATE')} },
</Button> {
), render: () => (
visible: !capability.isHidden('share'), <Button variant="contained" onClick={() => onAction('share')}>
}, {$msg('COLLABORATE')}
{ </Button>
render: () => accountConfig, ),
visible: !capability.isHidden('account'), visible: !capability.isHidden('share'),
}, },
{ {
render: () => ( render: () => accountConfig,
<Button variant="contained" onClick={() => (window.location.href = '/c/registration')}> visible: !capability.isHidden('account'),
{$msg('SIGN_UP')} },
</Button> {
), render: () => (
visible: !capability.isHidden('sign-up'), <Button variant="contained" onClick={() => (window.location.href = '/c/registration')}>
}, {$msg('SIGN_UP')}
]; </Button>
}; ),
visible: !capability.isHidden('sign-up'),
},
];
const config = buildConfig(model, mapTitle, capability, onAction, accountConfig);
return ( return (
<MaterialAppBar <MaterialAppBar
role="menubar" role="menubar"

View File

@ -15,7 +15,7 @@
* 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 React, { useCallback, useEffect, useState } from 'react'; import React, { ErrorInfo, useCallback, useEffect, useState } from 'react';
import Popover from '@mui/material/Popover'; import Popover from '@mui/material/Popover';
import Model from '../classes/model/editor'; import Model from '../classes/model/editor';
import { buildEditorPanelConfig, buildZoomToolbarConfig } from './toolbar/toolbarConfigBuilder'; import { buildEditorPanelConfig, buildZoomToolbarConfig } from './toolbar/toolbarConfigBuilder';
@ -40,19 +40,16 @@ import DefaultWidgetManager from '../classes/default-widget-manager';
import AppBar from './app-bar'; import AppBar from './app-bar';
import Capability from '../classes/action/capability'; import Capability from '../classes/action/capability';
import { ToolbarActionType } from './toolbar/ToolbarActionType'; import { ToolbarActionType } from './toolbar/ToolbarActionType';
import MapInfo from '../classes/model/map-info';
type EditorOptions = { export type EditorOptions = {
mode: EditorRenderMode; mode: EditorRenderMode;
locale: string; locale: string;
zoom?: number;
locked?: boolean;
lockedMsg?: string;
mapTitle: string;
enableKeyboardEvents: boolean; enableKeyboardEvents: boolean;
}; };
type EditorProps = { type EditorProps = {
mapId: string; mapInfo: MapInfo;
options: EditorOptions; options: EditorOptions;
persistenceManager: PersistenceManager; persistenceManager: PersistenceManager;
onAction: (action: ToolbarActionType) => void; onAction: (action: ToolbarActionType) => void;
@ -62,25 +59,24 @@ type EditorProps = {
}; };
const Editor = ({ const Editor = ({
mapId, mapInfo,
options, options,
persistenceManager, persistenceManager,
onAction, onAction,
theme, theme,
accountConfiguration, accountConfiguration,
}: EditorProps) => { }: EditorProps) => {
const [model, setModel]: [Model | undefined, Function] = useState(); const [model, setModel] = useState<Model | undefined>();
const [canvasUpdate, setCanvasUpdate] = useState<number>();
const editorTheme: Theme = theme ? theme : defaultEditorTheme; const editorTheme: Theme = theme ? theme : defaultEditorTheme;
const [toolbarsRerenderSwitch, setToolbarsRerenderSwitch] = useState(0);
const [popoverOpen, popoverTarget, widgetManager] = DefaultWidgetManager.create(); const [popoverOpen, popoverTarget, widgetManager] = DefaultWidgetManager.create();
const capability = new Capability(options.mode, options.locked); const capability = new Capability(options.mode, mapInfo.isLocked());
const mindplotRef = useCallback((component: MindplotWebComponent) => { const mindplotRef = useCallback((component: MindplotWebComponent) => {
// Initialized model ... // Initialized model ...
const model = new Model(component); const model = new Model(component);
model.loadMindmap(mapId, persistenceManager, widgetManager); model.loadMindmap(mapInfo.getId(), persistenceManager, widgetManager);
model.registerEvents(setToolbarsRerenderSwitch, capability); model.registerEvents(setCanvasUpdate, capability);
setModel(model); setModel(model);
}, []); }, []);
@ -101,7 +97,7 @@ const Editor = ({
<IntlProvider locale={locale} messages={msg}> <IntlProvider locale={locale} messages={msg}>
<AppBar <AppBar
model={model} model={model}
mapTitle={options.mapTitle} mapInfo={mapInfo}
capability={capability} capability={capability}
onAction={onAction} onAction={onAction}
accountConfig={accountConfiguration} accountConfig={accountConfiguration}
@ -120,11 +116,9 @@ const Editor = ({
{widgetManager.getEditorContent()} {widgetManager.getEditorContent()}
</Popover> </Popover>
{!capability.isHidden('edition-toolbar') && model?.isMapLoadded() && ( {!capability.isHidden('edition-toolbar') && model?.isMapLoadded() && (
<Toolbar <Toolbar configurations={buildEditorPanelConfig(model)} />
configurations={buildEditorPanelConfig(model)}
rerender={toolbarsRerenderSwitch}
></Toolbar>
)} )}
<Toolbar <Toolbar
configurations={buildZoomToolbarConfig(model, capability)} configurations={buildZoomToolbarConfig(model, capability)}
position={{ position={{
@ -134,22 +128,21 @@ const Editor = ({
}, },
vertical: false, vertical: false,
}} }}
rerender={toolbarsRerenderSwitch} />
></Toolbar>
<mindplot-component <mindplot-component
ref={mindplotRef} ref={mindplotRef}
id="mindmap-comp" id="mindmap-comp"
mode={options.mode} mode={options.mode}
locale={options.locale} locale={options.locale}
></mindplot-component> />
<Notifier id="headerNotifier"></Notifier> <Notifier id="headerNotifier" />
<WarningDialog <WarningDialog
capability={capability} capability={capability}
message={options.locked ? options.lockedMsg : ''} message={mapInfo.isLocked() ? mapInfo.getLockedMessage() : ''}
></WarningDialog> />
</IntlProvider> </IntlProvider>
</ThemeProvider> </ThemeProvider>
); );

View File

@ -34,7 +34,10 @@ import {
Importer, Importer,
TextImporterFactory, TextImporterFactory,
} from '@wisemapping/mindplot'; } from '@wisemapping/mindplot';
import Editor from './components'; import Editor from './components';
import { EditorOptions } from './components';
import MapInfo from './classes/model/map-info';
declare global { declare global {
// used in mindplot // used in mindplot
@ -65,6 +68,8 @@ export {
Exporter, Exporter,
Importer, Importer,
TextImporterFactory, TextImporterFactory,
EditorOptions,
MapInfo,
}; };
export default Editor; export default Editor;

View File

@ -0,0 +1,66 @@
/*
* 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 { MapInfo } from '../../../../src';
class MapInfoImpl implements MapInfo {
private id: string;
private title: string;
private locked: boolean;
private starred: boolean;
constructor(id: string, title: string, locked: boolean) {
this.id = id;
this.title = title;
this.locked = locked;
this.starred = true;
}
isStarred(): Promise<boolean> {
return Promise.resolve(this.starred);
}
updateStarred(value: boolean): Promise<void> {
this.starred = value;
return Promise.resolve();
}
getTitle(): string {
return this.title;
}
setTitle(value: string): void {
throw this.title;
}
isLocked(): boolean {
return this.locked;
}
getLockedMessage(): string {
return 'Map Is Locked !';
}
getZoom(): number {
return 0.8;
}
getId(): string {
return this.id;
}
}
export default MapInfoImpl;

View File

@ -19,6 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; 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';
const initialization = (designer: Designer) => { const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => { designer.addEvent('loadSuccess', () => {
@ -32,9 +33,6 @@ const initialization = (designer: Designer) => {
const persistence = new LocalStorageManager('samples/{id}.wxml', false, false); const persistence = new LocalStorageManager('samples/{id}.wxml', false, false);
const mapId = 'welcome'; const mapId = 'welcome';
const options: EditorOptions = { const options: EditorOptions = {
zoom: 0.8,
locked: false,
mapTitle: 'Develop WiseMapping',
mode: 'edition-owner', mode: 'edition-owner',
locale: 'en', locale: 'en',
enableKeyboardEvents: true, enableKeyboardEvents: true,
@ -42,7 +40,7 @@ const options: EditorOptions = {
ReactDOM.render( ReactDOM.render(
<Editor <Editor
mapId={mapId} 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)}

View File

@ -19,6 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; 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';
const initialization = (designer: Designer) => { const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => { designer.addEvent('loadSuccess', () => {
@ -30,20 +31,16 @@ const initialization = (designer: Designer) => {
}; };
const persistence = new LocalStorageManager('samples/{id}.wxml', false, false); const persistence = new LocalStorageManager('samples/{id}.wxml', false, false);
const mapId = 'welcome';
const options: EditorOptions = { const options: EditorOptions = {
zoom: 0.8,
locked: true,
lockedMsg: 'Blockeado',
mapTitle: 'Develop WiseMapping',
mode: 'edition-editor', mode: 'edition-editor',
locale: 'en', locale: 'en',
enableKeyboardEvents: true, enableKeyboardEvents: true,
}; };
const mapInfo = new MapInfoImpl('welcome', 'Develop WiseMapping', true);
ReactDOM.render( ReactDOM.render(
<Editor <Editor
mapId={mapId} mapInfo={mapInfo}
options={options} options={options}
persistenceManager={persistence} persistenceManager={persistence}
onAction={(action) => console.log('action called:', action)} onAction={(action) => console.log('action called:', action)}

View File

@ -19,6 +19,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; 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';
const initialization = (designer: Designer) => { const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => { designer.addEvent('loadSuccess', () => {
@ -32,9 +33,6 @@ const initialization = (designer: Designer) => {
const persistence = new LocalStorageManager('samples/{id}.wxml', false, false); const persistence = new LocalStorageManager('samples/{id}.wxml', false, false);
const mapId = 'welcome'; const mapId = 'welcome';
const options: EditorOptions = { const options: EditorOptions = {
zoom: 0.8,
locked: false,
mapTitle: 'Develop WiseMapping',
mode: 'showcase', mode: 'showcase',
locale: 'en', locale: 'en',
enableKeyboardEvents: true, enableKeyboardEvents: true,
@ -42,7 +40,7 @@ const options: EditorOptions = {
ReactDOM.render( ReactDOM.render(
<Editor <Editor
mapId={mapId} 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)}

View File

@ -3,6 +3,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; 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';
const initialization = (designer: Designer) => { const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => { designer.addEvent('loadSuccess', () => {
@ -32,9 +33,6 @@ const params = new URLSearchParams(window.location.search.substring(1));
const mapId = params.get('id') || 'welcome'; const mapId = params.get('id') || 'welcome';
const persistence = new LocalStorageManager('samples/{id}.wxml', false); const persistence = new LocalStorageManager('samples/{id}.wxml', false);
const options: EditorOptions = { const options: EditorOptions = {
zoom: 0.8,
locked: false,
mapTitle: 'Develop WiseMapping',
mode: 'viewonly', mode: 'viewonly',
locale: 'en', locale: 'en',
enableKeyboardEvents: true, enableKeyboardEvents: true,
@ -42,7 +40,7 @@ const options: EditorOptions = {
ReactDOM.render( ReactDOM.render(
<Editor <Editor
mapId={mapId} 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)}

View File

@ -13,6 +13,7 @@ import AppBar from '../../../src/components/app-bar';
import ActionConfig from '../../../src/classes/action/action-config'; import ActionConfig from '../../../src/classes/action/action-config';
import Capability from '../../../src/classes/action/capability'; import Capability from '../../../src/classes/action/capability';
import Editor from '../../../src/classes/model/editor'; import Editor from '../../../src/classes/model/editor';
import MapInfoImpl from '../../playground/map-render/js/MapInfoImpl';
require('babel-polyfill'); require('babel-polyfill');
jest.mock('../../../src/components/app-bar/styles.css', () => ''); jest.mock('../../../src/components/app-bar/styles.css', () => '');
@ -275,7 +276,14 @@ describe('AppBar', () => {
it('When render it displays a menu', () => { it('When render it displays a menu', () => {
const capacity = new Capability('edition-owner', false); const capacity = new Capability('edition-owner', false);
const model = new Editor(null); const model = new Editor(null);
render(<AppBar mapTitle="Some title" capability={capacity} model={model} />);
render(
<AppBar
mapInfo={new MapInfoImpl('welcome', 'Develop Map Title', false)}
capability={capacity}
model={model}
/>,
);
screen.getByRole('menubar'); screen.getByRole('menubar');
}); });
}); });

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<svg viewBox="9.449 8.932 132.742 34.405" width="133px" height="34.5px" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="9.449 8.932 132.742 34.405" width="100px" height="34.5px" xmlns="http://www.w3.org/2000/svg">
<mask id="i08j30ef6a" fill="#fff"> <mask id="i08j30ef6a" fill="#fff">
<path d="M 32.284 17.414 C 32.284 20.379 31.176 23.221 29.199 25.317 C 27.224 27.413 24.546 28.59 21.753 28.59 C 18.96 28.59 16.282 27.413 14.307 25.317 C 12.331 23.221 11.222 20.379 11.222 17.414 L 32.284 17.414 Z"/> <path d="M 32.284 17.414 C 32.284 20.379 31.176 23.221 29.199 25.317 C 27.224 27.413 24.546 28.59 21.753 28.59 C 18.96 28.59 16.282 27.413 14.307 25.317 C 12.331 23.221 11.222 20.379 11.222 17.414 L 32.284 17.414 Z"/>
</mask> </mask>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,3 +1,21 @@
/*
* 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 Client from '../client'; import Client from '../client';
import CacheDecoratorClient from '../client/cache-decorator-client'; import CacheDecoratorClient from '../client/cache-decorator-client';
import MockClient from '../client/mock-client'; import MockClient from '../client/mock-client';

View File

@ -17,6 +17,10 @@ class CacheDecoratorClient implements Client {
this.client = client; this.client = client;
} }
fetchStarred(id: number): Promise<boolean> {
return this.client.fetchStarred(id);
}
onSessionExpired(callback?: () => void): () => void { onSessionExpired(callback?: () => void): () => void {
return this.client.onSessionExpired(callback); return this.client.onSessionExpired(callback);
} }

View File

@ -80,6 +80,8 @@ interface Client {
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>; renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>;
fetchAllMaps(): Promise<MapInfo[]>; fetchAllMaps(): Promise<MapInfo[]>;
fetchStarred(id: number): Promise<boolean>;
fetchMapPermissions(id: number): Promise<Permission[]>; fetchMapPermissions(id: number): Promise<Permission[]>;
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void>; addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void>;
deleteMapPermission(id: number, email: string): Promise<void>; deleteMapPermission(id: number, email: string): Promise<void>;

View File

@ -1,3 +1,20 @@
/*
* 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 Client, { import Client, {
AccountInfo, AccountInfo,
BasicMapInfo, BasicMapInfo,
@ -108,6 +125,10 @@ class MockClient implements Client {
this.labels = [label1, label2, label3]; this.labels = [label1, label2, label3];
} }
fetchStarred(id: number): Promise<boolean> {
return Promise.resolve(this.maps.find((m) => m.id == id).starred);
}
onSessionExpired(callback?: () => void): () => void { onSessionExpired(callback?: () => void): () => void {
return callback; return callback;
} }
@ -123,8 +144,6 @@ class MockClient implements Client {
let perm = this.permissionsByMap.get(id) || []; let perm = this.permissionsByMap.get(id) || [];
perm = perm.concat(permissions); perm = perm.concat(permissions);
this.permissionsByMap.set(id, perm); this.permissionsByMap.set(id, perm);
console.debug(`Message ${message}`);
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -72,6 +72,25 @@ export default class RestClient implements Client {
}; };
return new Promise(handler); return new Promise(handler);
} }
fetchStarred(id: number): Promise<boolean> {
const handler = (success: (starred: boolean) => void, reject: (error: ErrorInfo) => void) => {
this.axios
.get(`${this.baseUrl}/c/restful/maps/${id}/starred`, {
headers: { 'Content-Type': 'text/plain' },
})
.then((response) => {
const data = response.data;
success(data);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> { addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {

View File

@ -0,0 +1,77 @@
/*
* 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 { MapInfo } from '@wisemapping/editor';
import Client from '../client';
class MapInfoImpl implements MapInfo {
private client: Client;
private id: number;
private title: string;
private zoom: number;
private locked: boolean;
private lockedMsg: string;
constructor(
id: number,
client: Client,
title: string,
locked: boolean,
lockedMsg: string,
zoom: number,
) {
this.id = id;
this.client = client;
this.title = title;
this.zoom = zoom;
this.locked = locked;
this.lockedMsg = lockedMsg;
}
isStarred(): Promise<boolean> {
return this.client.fetchStarred(this.id);
}
updateStarred(value: boolean): Promise<void> {
return this.client.updateStarred(this.id, value);
}
getTitle(): string {
return this.title;
}
setTitle(value: string): void {
this.client.renameMap(Number.parseInt(this.getId(), 10), { title: value });
}
isLocked(): boolean {
return this.locked;
}
getLockedMessage(): string {
return this.lockedMsg;
}
getZoom(): number {
return this.zoom;
}
getId(): string {
return String(this.id);
}
}
export default MapInfoImpl;

View File

@ -1,3 +1,20 @@
/*
* Copyright [2021] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import ActionDispatcher from '../maps-page/action-dispatcher'; import ActionDispatcher from '../maps-page/action-dispatcher';
import { ActionType } from '../maps-page/action-chooser'; import { ActionType } from '../maps-page/action-chooser';
@ -13,6 +30,10 @@ import EditorOptionsBuilder from './EditorOptionsBuilder';
import { buildPersistenceManagerForEditor } from './PersistenceManagerUtils'; import { buildPersistenceManagerForEditor } from './PersistenceManagerUtils';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import AccountMenu from '../maps-page/account-menu'; import AccountMenu from '../maps-page/account-menu';
import MapInfoImpl from '../../classes/editor-map-info';
import { MapInfo } from '@wisemapping/editor';
import { activeInstance } from '../../redux/clientSlice';
import Client from '../../classes/client';
export type EditorPropsType = { export type EditorPropsType = {
isTryMode: boolean; isTryMode: boolean;
@ -23,6 +44,7 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
const hotkey = useSelector(hotkeysEnabled); const hotkey = useSelector(hotkeysEnabled);
const userLocale = AppI18n.getUserLocale(); const userLocale = AppI18n.getUserLocale();
const theme = useTheme(); const theme = useTheme();
const client: Client = useSelector(activeInstance);
useEffect(() => { useEffect(() => {
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: `Map Editor` }); ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: `Map Editor` });
@ -62,16 +84,25 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
const loadCompleted = mode && isAccountLoaded; const loadCompleted = mode && isAccountLoaded;
let options, persistence: PersistenceManager; let options, persistence: PersistenceManager;
let mapInfo: MapInfo;
if (loadCompleted) { if (loadCompleted) {
options = EditorOptionsBuilder.build(userLocale.code, mode, hotkey); options = EditorOptionsBuilder.build(userLocale.code, mode, hotkey);
persistence = buildPersistenceManagerForEditor(mode); persistence = buildPersistenceManagerForEditor(mode);
mapInfo = new MapInfoImpl(
mapId,
client,
options.title,
options.isLocked,
options.lockedMsg,
options.zoom,
);
} }
useEffect(() => { useEffect(() => {
if (options?.mapTitle) { if (options?.mapTitle) {
document.title = `${options.mapTitle} | WiseMapping `; document.title = `${options.mapTitle} | WiseMapping `;
} }
}, options?.mapTitle); }, [loadCompleted]);
return loadCompleted ? ( return loadCompleted ? (
<IntlProvider <IntlProvider
@ -83,7 +114,7 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
onAction={setActiveDialog} onAction={setActiveDialog}
options={options} options={options}
persistenceManager={persistence} persistenceManager={persistence}
mapId={mapId} mapInfo={mapInfo}
theme={theme} theme={theme}
accountConfiguration={ accountConfiguration={
// Prevent load on non-authenticated. // Prevent load on non-authenticated.

View File

@ -1,3 +1,21 @@
/*
* 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.
*/
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';

View File

@ -7,7 +7,6 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, { module.exports = merge(common, {
mode: 'development', mode: 'development',
devtool: 'source-map', devtool: 'source-map',
watch: true,
devServer: { devServer: {
contentBase: path.join(__dirname, 'dist'), contentBase: path.join(__dirname, 'dist'),
port: 3000, port: 3000,