Fix map loading ..

This commit is contained in:
Paulo Gustavo Veiga 2024-02-08 22:54:10 -08:00
parent fc0c03b2bc
commit e3d6f5dad5
20 changed files with 248 additions and 138 deletions

View File

@ -37,15 +37,18 @@ export const useEditor = ({
const [model, setModel] = useState<Model | undefined>(); const [model, setModel] = useState<Model | undefined>();
// useEditor hook creates mindplotRef // useEditor hook creates mindplotRef
const mindplotRef = useRef(null); const mindplotRef = useRef(null);
// This is required to redraw in case of chansges in the canvas... // This is required to redraw in case of changes in the canvas...
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [, setCanvasUpdate] = useState<number>(); const [, setCanvasUpdate] = useState<number>();
const { widgetManager } = useWidgetManager(); const { widgetManager } = useWidgetManager();
const capability = new Capability(options.mode, mapInfo.isLocked()); let capability;
if (options && mapInfo) {
capability = new Capability(options.mode, mapInfo.isLocked());
}
useEffect(() => { useEffect(() => {
if (!model) { if (!model && options) {
const model = new Model(mindplotRef.current); const model = new Model(mindplotRef.current);
model model
.loadMindmap(mapInfo.getId(), persistenceManager, widgetManager) .loadMindmap(mapInfo.getId(), persistenceManager, widgetManager)
@ -59,15 +62,17 @@ export const useEditor = ({
}); });
setModel(model); setModel(model);
} }
}, [mindplotRef]); }, [mindplotRef, options]);
useEffect(() => { useEffect(() => {
if (options.enableKeyboardEvents) { if (options) {
DesignerKeyboard.resume(); if (options.enableKeyboardEvents) {
} else { DesignerKeyboard.resume();
DesignerKeyboard.pause(); } else {
DesignerKeyboard.pause();
}
} }
}, [options.enableKeyboardEvents]); }, [options, options?.enableKeyboardEvents]);
return { model, mindplotRef, mapInfo, capability, options }; return { model, mindplotRef, mapInfo, capability, options };
}; };

View File

@ -26,11 +26,13 @@ class RESTPersistenceManager extends PersistenceManager {
private lockUrl: string; private lockUrl: string;
private onSave: boolean; private jwt: string | undefined;
private clearTimeout; private clearTimeout;
constructor(options: { documentUrl: string; revertUrl: string; lockUrl: string }) { private onSave: boolean;
constructor(options: { documentUrl: string; revertUrl: string; lockUrl: string; jwt?: string }) {
$assert(options.documentUrl, 'documentUrl can not be null'); $assert(options.documentUrl, 'documentUrl can not be null');
$assert(options.revertUrl, 'revertUrl can not be null'); $assert(options.revertUrl, 'revertUrl can not be null');
$assert(options.lockUrl, 'lockUrl can not be null'); $assert(options.lockUrl, 'lockUrl can not be null');
@ -40,6 +42,7 @@ class RESTPersistenceManager extends PersistenceManager {
this.revertUrl = options.revertUrl; this.revertUrl = options.revertUrl;
this.lockUrl = options.lockUrl; this.lockUrl = options.lockUrl;
this.onSave = false; this.onSave = false;
this.jwt = options.jwt;
} }
saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events): void { saveMapXml(mapId: string, mapXml: Document, pref: string, saveHistory: boolean, events): void {
@ -61,14 +64,7 @@ class RESTPersistenceManager extends PersistenceManager {
const persistence = this; const persistence = this;
const crfs = this.getCSRFToken(); const headers = this._buildHttpHeader('application/json; charset=utf-8', 'application/json');
const headers = {
'Content-Type': 'application/json; charset=utf-8',
Accept: 'application/json',
};
if (crfs) {
headers['X-CSRF-Token'] = crfs;
}
fetch(`${this.documentUrl.replace('{id}', mapId)}?${query}`, { fetch(`${this.documentUrl.replace('{id}', mapId)}?${query}`, {
method: 'PUT', method: 'PUT',
@ -133,15 +129,7 @@ class RESTPersistenceManager extends PersistenceManager {
} }
discardChanges(mapId: string): void { discardChanges(mapId: string): void {
const crfs = this.getCSRFToken(); const headers = this._buildHttpHeader('application/json; charset=utf-8');
const headers = {
'Content-Type': 'application/json; charset=utf-8',
Accept: 'application/json',
};
if (crfs) {
headers['X-CSRF-Token'] = crfs;
}
fetch(this.revertUrl.replace('{id}', mapId), { fetch(this.revertUrl.replace('{id}', mapId), {
method: 'POST', method: 'POST',
headers, headers,
@ -149,14 +137,7 @@ class RESTPersistenceManager extends PersistenceManager {
} }
unlockMap(mapId: string): void { unlockMap(mapId: string): void {
const crfs = this.getCSRFToken(); const headers = this._buildHttpHeader('text/plain; charset=utf-8');
const headers = {
'Content-Type': 'text/plain; charset=utf-8',
};
if (crfs) {
headers['X-CSRF-Token'] = crfs;
}
fetch(this.lockUrl.replace('{id}', mapId), { fetch(this.lockUrl.replace('{id}', mapId), {
method: 'PUT', method: 'PUT',
headers, headers,
@ -180,14 +161,7 @@ class RESTPersistenceManager extends PersistenceManager {
loadMapDom(mapId: string): Promise<Document> { loadMapDom(mapId: string): Promise<Document> {
const url = `${this.documentUrl.replace('{id}', mapId)}/xml`; const url = `${this.documentUrl.replace('{id}', mapId)}/xml`;
const crfs = this.getCSRFToken(); const headers = this._buildHttpHeader('text/plain; charset=utf-8', 'application/xml');
const headers = {
'Content-Type': 'text/plain; charset=utf-8',
Accept: 'application/xml',
};
if (crfs) {
headers['X-CSRF-Token'] = crfs;
}
return fetch(url, { return fetch(url, {
method: 'get', method: 'get',
@ -202,6 +176,28 @@ class RESTPersistenceManager extends PersistenceManager {
}) })
.then((xmlStr) => new DOMParser().parseFromString(xmlStr, 'text/xml')); .then((xmlStr) => new DOMParser().parseFromString(xmlStr, 'text/xml'));
} }
private _buildHttpHeader(contentType: string, accept?: string) {
const headers = {
'Content-Type': contentType,
};
if (accept) {
// eslint-disable-next-line dot-notation
headers['Accept'] = accept;
}
if (this.jwt) {
// eslint-disable-next-line dot-notation
headers['Authorization'] = `Bearer ${this.jwt} `;
}
const crfs = this.getCSRFToken();
if (crfs) {
headers['X-CSRF-Token'] = crfs;
}
return headers;
}
} }
export default RESTPersistenceManager; export default RESTPersistenceManager;

View File

@ -17,7 +17,7 @@
*/ */
import React, { ReactElement, Suspense, useEffect } from 'react'; import React, { ReactElement, Suspense, useEffect } from 'react';
import { FormattedMessage, IntlProvider } from 'react-intl'; import { FormattedMessage, IntlProvider } from 'react-intl';
import { Route, Routes, BrowserRouter as Router, useNavigate } from 'react-router-dom'; import { Route, Routes, BrowserRouter as Router, useNavigate, useParams } from 'react-router-dom';
import ForgotPasswordSuccessPage from './components/forgot-password-success-page'; import ForgotPasswordSuccessPage from './components/forgot-password-success-page';
import RegistationPage from './components/registration-page'; import RegistationPage from './components/registration-page';
import LoginPage from './components/login-page'; import LoginPage from './components/login-page';
@ -66,6 +66,11 @@ function Redirect({ to }) {
return null; return null;
} }
const PageEditorWhapper = ({ isTryMode }: { isTryMode: boolean }) => {
const mapId: string = useParams().id!;
return <EditorPage isTryMode={isTryMode} mapId={Number.parseInt(mapId)} />;
};
const App = (): ReactElement => { const App = (): ReactElement => {
const locale = AppI18n.getDefaultLocale(); const locale = AppI18n.getDefaultLocale();
const overwriteView = window.errorMvcView; const overwriteView = window.errorMvcView;
@ -126,7 +131,7 @@ const App = (): ReactElement => {
</div> </div>
} }
> >
<EditorPage isTryMode={false} /> <PageEditorWhapper isTryMode={false} />
</Suspense> </Suspense>
} }
/> />
@ -143,7 +148,7 @@ const App = (): ReactElement => {
</div> </div>
} }
> >
<EditorPage isTryMode={true} /> <PageEditorWhapper isTryMode={true} />
</Suspense> </Suspense>
} }
/> />

View File

@ -38,7 +38,7 @@ class _AppConfig {
googleOauth2Url: '/c/registration-google?code=aFakeCode', googleOauth2Url: '/c/registration-google?code=aFakeCode',
}; };
isDevelopEnv(): boolean { isMockEnv(): boolean {
const config = this.getInstance(); const config = this.getInstance();
return config.clientType === 'mock'; return config.clientType === 'mock';
} }

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 { Locale, LocaleCode } from '../app-i18n'; import { Locale, LocaleCode } from '../app-i18n';
export type JwtAuth = { export type JwtAuth = {
@ -42,6 +59,14 @@ export type MapInfo = {
role: Role; role: Role;
}; };
export type MapMetadata = {
id: number;
title: string;
isLocked: boolean;
isLockedBy?: string;
zoom: number;
};
export type ChangeHistory = { export type ChangeHistory = {
id: number; id: number;
lastModificationBy: string; lastModificationBy: string;
@ -99,6 +124,7 @@ interface Client {
deleteMap(id: number): Promise<void>; deleteMap(id: number): Promise<void>;
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>; renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>;
fetchAllMaps(): Promise<MapInfo[]>; fetchAllMaps(): Promise<MapInfo[]>;
fetchMapMetadata(id: number): Promise<MapMetadata>;
fetchStarred(id: number): Promise<boolean>; fetchStarred(id: number): Promise<boolean>;

View File

@ -27,6 +27,7 @@ import Client, {
Oauth2CallbackResult, Oauth2CallbackResult,
ForgotPasswordResult, ForgotPasswordResult,
JwtAuth, JwtAuth,
MapMetadata,
} from '..'; } from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n'; import { LocaleCode, localeFromStr } from '../../app-i18n';
import Cookies from 'universal-cookie'; import Cookies from 'universal-cookie';
@ -128,6 +129,15 @@ class MockClient implements Client {
this.labels = [label1, label2, label3]; this.labels = [label1, label2, label3];
} }
fetchMapMetadata(id: number): Promise<MapMetadata> {
return Promise.resolve({
title: 'my map',
id: id,
isLocked: false,
zoom: 0.8,
});
}
logout(): Promise<void> { logout(): Promise<void> {
return Promise.resolve(); return Promise.resolve();
} }

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 axios, { AxiosInstance, AxiosResponse } from 'axios'; import axios, { AxiosInstance, AxiosResponse } from 'axios';
import Client, { import Client, {
ErrorInfo, ErrorInfo,
@ -12,6 +29,7 @@ import Client, {
Oauth2CallbackResult, Oauth2CallbackResult,
ForgotPasswordResult, ForgotPasswordResult,
JwtAuth, JwtAuth,
MapMetadata,
} from '..'; } from '..';
import { getCsrfToken } from '../../../utils'; import { getCsrfToken } from '../../../utils';
import { LocaleCode, localeFromStr } from '../../app-i18n'; import { LocaleCode, localeFromStr } from '../../app-i18n';
@ -20,11 +38,11 @@ import Cookies from 'universal-cookie';
export default class RestClient implements Client { export default class RestClient implements Client {
private baseUrl: string; private baseUrl: string;
private axios: AxiosInstance; private axios: AxiosInstance;
private _onSessionExpired: () => void;
private checkResponseForSessionExpired = <T>(error: { private checkResponseForSessionExpired = <T>(error: {
response?: AxiosResponse<T>; response?: AxiosResponse<T>;
}): Promise<{ response?: AxiosResponse<T> }> => { }): Promise<{ response?: AxiosResponse<T> }> => {
// TODO: Improve session timeout response and response handling
if (error.response && (error.response.status === 405 || error.response.status === 403)) { if (error.response && (error.response.status === 405 || error.response.status === 403)) {
this.sessionExpired(); this.sessionExpired();
} }
@ -63,6 +81,28 @@ export default class RestClient implements Client {
); );
} }
fetchMapMetadata(id: number): Promise<MapMetadata> {
const handler = (
success: (mapMetadata: MapMetadata) => void,
reject: (error: ErrorInfo) => void,
) => {
this.axios
.get(`${this.baseUrl}/api/restful/maps/${id}/metadata`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const data = response.data;
success(data);
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
logout(): Promise<void> { logout(): Promise<void> {
// Set jwt token on cookie ... // Set jwt token on cookie ...
const cookies = new Cookies(); const cookies = new Cookies();
@ -100,7 +140,6 @@ export default class RestClient implements Client {
return token ? `Bearer ${token}` : null; return token ? `Bearer ${token}` : null;
} }
private _onSessionExpired: () => void;
onSessionExpired(callback?: () => void): () => void { onSessionExpired(callback?: () => void): () => void {
if (callback) { if (callback) {
this._onSessionExpired = callback; this._onSessionExpired = callback;

View File

@ -1,44 +0,0 @@
import { EditorOptions } from '@wisemapping/editor';
import { EditorRenderMode } from '@wisemapping/editor';
import AppConfig from '../../classes/app-config';
class EditorOptionsBuilder {
static build(locale: string, mode: EditorRenderMode, hotkeys: boolean): EditorOptions {
let options: EditorOptions = {
enableKeyboardEvents: hotkeys,
locale: locale,
mode: mode,
};
if (!AppConfig.isDevelopEnv()) {
options = {
zoom: globalThis.userOptions?.zoom ? globalThis?.userOptions?.zoom : 0.8,
locked: globalThis.mindmapLocked,
lockedMsg: globalThis.mindmapLockedMsg,
mapTitle: globalThis.mapTitle,
...options,
};
} else {
// Running in a development mode.
console.log('Running editor in development mode');
options = {
zoom: 0.8,
locked: false,
mapTitle: 'Develop Mindnap',
...options,
};
}
return options;
}
static loadMapId(): number {
const result = !AppConfig.isDevelopEnv() ? globalThis.mapId : 11;
if (result === undefined) {
throw Error(
`Could not resolve mapId. Map Id: globalThis.mapId: ${result} , globalThis.mapTitle: ${globalThis.mapTitle}, globalThis.lockSession: ${globalThis.lockSession}`,
);
}
return result;
}
}
export default EditorOptionsBuilder;

View File

@ -4,7 +4,7 @@ import { LocalStorageManager, Mindmap, XMLSerializerFactory } from '@wisemapping
export const fetchMindmap = async (mapId: number): Promise<Mindmap> => { export const fetchMindmap = async (mapId: number): Promise<Mindmap> => {
let mindmap: Mindmap; let mindmap: Mindmap;
if (AppConfig.isRestClient()) { if (AppConfig.isRestClient()) {
const persistence = new LocalStorageManager(`/c/restful/maps/{id}/document/xml`, true); const persistence = new LocalStorageManager(`/api/restful/maps/{id}/document/xml`, true);
mindmap = await persistence.load(String(mapId)); mindmap = await persistence.load(String(mapId));
} else { } else {
const parser = new DOMParser(); const parser = new DOMParser();

View File

@ -34,8 +34,8 @@ import {
useFetchMapById, useFetchMapById,
activeInstance, activeInstance,
sessionExpired, sessionExpired,
useFetchMapMetadata,
} from '../../redux/clientSlice'; } from '../../redux/clientSlice';
import EditorOptionsBuilder from './EditorOptionsBuilder';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import MapInfoImpl from '../../classes/editor-map-info'; import MapInfoImpl from '../../classes/editor-map-info';
import { MapInfo } from '@wisemapping/editor'; import { MapInfo } from '@wisemapping/editor';
@ -43,24 +43,31 @@ import Client from '../../classes/client';
import AppConfig from '../../classes/app-config'; import AppConfig from '../../classes/app-config';
import exampleMap from '../../classes/client/mock-client/example-map.wxml'; import exampleMap from '../../classes/client/mock-client/example-map.wxml';
import ClientHealthSentinel from '../common/client-health-sentinel'; import ClientHealthSentinel from '../common/client-health-sentinel';
import Cookies from 'universal-cookie';
const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => { const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => {
let persistenceManager: PersistenceManager; let persistenceManager: PersistenceManager;
if (AppConfig.isRestClient()) { if (AppConfig.isRestClient()) {
if (mode === 'edition-owner' || mode === 'edition-editor') { if (mode === 'edition-owner' || mode === 'edition-editor') {
// Fetch JWT token ...
const cookies = new Cookies();
const token = cookies.get('jwt-auth-token');
persistenceManager = new RESTPersistenceManager({ persistenceManager = new RESTPersistenceManager({
documentUrl: '/c/restful/maps/{id}/document', documentUrl: '/api/restful/maps/{id}/document',
revertUrl: '/c/restful/maps/{id}/history/latest', revertUrl: '/api/restful/maps/{id}/history/latest',
lockUrl: '/c/restful/maps/{id}/lock', lockUrl: '/api/restful/maps/{id}/lock',
jwt: token,
}); });
} else { } else {
persistenceManager = new LocalStorageManager( persistenceManager = new LocalStorageManager(
`/c/restful/maps/{id}/${ `/api/restful/maps/{id}/${
globalThis.historyId ? `${globalThis.historyId}/` : '' globalThis.historyId ? `${globalThis.historyId}/` : ''
}document/xml${mode === 'showcase' ? '-pub' : ''}`, }document/xml${mode === 'showcase' ? '-pub' : ''}`,
true, true,
); );
} }
persistenceManager.addErrorHandler((error) => { persistenceManager.addErrorHandler((error) => {
if (error.errorType === 'session-expired') { if (error.errorType === 'session-expired') {
// TODO: this line was in RestPersistenceClient, do something similar here // TODO: this line was in RestPersistenceClient, do something similar here
@ -75,6 +82,7 @@ const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => {
export type EditorPropsType = { export type EditorPropsType = {
isTryMode: boolean; isTryMode: boolean;
mapId: number;
}; };
type ActionType = type ActionType =
@ -97,7 +105,14 @@ type ActionType =
const ActionDispatcher = React.lazy(() => import('../maps-page/action-dispatcher')); const ActionDispatcher = React.lazy(() => import('../maps-page/action-dispatcher'));
const AccountMenu = React.lazy(() => import('../maps-page/account-menu')); const AccountMenu = React.lazy(() => import('../maps-page/account-menu'));
const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => { type EditorMetadata = {
mode: EditorRenderMode;
title: string;
isLocked: boolean;
zoom: number;
};
const EditorPage = ({ mapId, isTryMode }: EditorPropsType): React.ReactElement => {
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null); const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
const hotkey = useSelector(hotkeysEnabled); const hotkey = useSelector(hotkeysEnabled);
const userLocale = AppI18n.getUserLocale(); const userLocale = AppI18n.getUserLocale();
@ -119,51 +134,79 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: `Map Editor` }); ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: `Map Editor` });
}, []); }, []);
const useFindEditorMode = (isTryMode: boolean, mapId: number): EditorRenderMode | null => { const useFindEditorMode = (isTryMode: boolean, mapId: number): EditorMetadata | undefined => {
let result: EditorRenderMode = null; let mode: EditorRenderMode = null;
if (isTryMode) { let title = '';
result = 'showcase'; let isLocked = false;
} else if (globalThis.mindmapLocked) {
result = 'viewonly';
} else {
const fetchResult = useFetchMapById(mapId);
if (!fetchResult.isLoading) {
if (fetchResult.error) {
throw new Error(`Map info could not be loaded: ${JSON.stringify(fetchResult.error)}`);
}
if (!fetchResult.map) { if (isTryMode) {
mode = 'showcase';
title = 'Try map';
isLocked = false;
} else {
const fetchMapInfoResult = useFetchMapById(mapId);
const fetchMetadataResult = useFetchMapMetadata(mapId);
if (!fetchMapInfoResult.isLoading && !fetchMetadataResult.isLoading) {
if (fetchMapInfoResult.error || fetchMetadataResult.error) {
throw new Error( throw new Error(
`Map info could not be loaded. Info not present: ${JSON.stringify(fetchResult)}`, `Map info could not be loaded: ${JSON.stringify(fetchMapInfoResult.error)}`,
); );
} }
result = `edition-${fetchResult.map.role}`;
if (!fetchMapInfoResult.data) {
throw new Error(
`Map info could not be loaded. Info not present: ${JSON.stringify(fetchMapInfoResult)}`,
);
}
if (!fetchMetadataResult.data) {
throw new Error(
`Map info could not be loaded. Info not present: ${JSON.stringify(
fetchMetadataResult,
)}`,
);
}
if (fetchMetadataResult.data?.isLocked) {
mode = 'viewonly';
} else {
mode = `edition-${fetchMapInfoResult.data.role}`;
}
isLocked = fetchMetadataResult.data.isLocked;
title = fetchMetadataResult.data.title;
} }
} }
return result; return mode ? { mode: mode, isLocked: isLocked, title: title, zoom: 0.8 } : undefined;
}; };
// What is the role ? // What is the role ?
const mapId = EditorOptionsBuilder.loadMapId(); const mapMetadata = useFindEditorMode(isTryMode, mapId);
const mode = useFindEditorMode(isTryMode, mapId);
// Account settings can be null and editor cannot be initilized multiple times. This creates problems // Account settings can be null and editor cannot be initilized multiple times. This creates problems
// at the i18n resource loading. // at the i18n resource loading.
const isAccountLoaded = mode === 'showcase' || useFetchAccount; const isAccountLoaded = mapMetadata?.mode === 'showcase' || useFetchAccount;
const loadCompleted = mode && isAccountLoaded; const loadCompleted = mapMetadata && isAccountLoaded;
let options: EditorOptions, persistence: PersistenceManager; let persistence: PersistenceManager;
let mapInfo: MapInfo; let mapInfo: MapInfo;
let options: EditorOptions;
if (loadCompleted) { if (loadCompleted) {
options = EditorOptionsBuilder.build(userLocale.code, mode, hotkey); // Configure de
persistence = buildPersistenceManagerForEditor(mode); options = {
enableKeyboardEvents: hotkey,
locale: userLocale.code,
mode: mapMetadata.mode,
};
persistence = buildPersistenceManagerForEditor(mapMetadata.mode);
mapInfo = new MapInfoImpl( mapInfo = new MapInfoImpl(
mapId, mapId,
client, client,
options.mapTitle, mapMetadata.title,
options.locked, mapMetadata.isLocked,
options.lockedMsg, '',
options.zoom, mapMetadata.zoom,
); );
} }

View File

@ -52,7 +52,7 @@ const ActionChooser = (props: ActionProps): React.ReactElement => {
}; };
}; };
const role = mapId !== undefined ? useFetchMapById(mapId)?.map?.role : undefined; const role = mapId !== undefined ? useFetchMapById(mapId)?.data?.role : undefined;
return ( return (
<Menu <Menu
anchorEl={anchor} anchorEl={anchor}

View File

@ -30,7 +30,7 @@ const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
mutation.mutate(mapId); mutation.mutate(mapId);
}; };
const { map } = useFetchMapById(mapId); const { data: map } = useFetchMapById(mapId);
const alertTitle = `${intl.formatMessage({ const alertTitle = `${intl.formatMessage({
id: 'action.delete-title', id: 'action.delete-title',
defaultMessage: 'Delete', defaultMessage: 'Delete',

View File

@ -56,7 +56,7 @@ const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElem
setModel({ ...model, [name as keyof BasicMapInfo]: value }); setModel({ ...model, [name as keyof BasicMapInfo]: value });
}; };
const { map } = useFetchMapById(mapId); const { data: map } = useFetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
setModel(map); setModel(map);

View File

@ -39,7 +39,7 @@ const ExportDialog = ({
}: ExportDialogProps): React.ReactElement => { }: ExportDialogProps): React.ReactElement => {
const intl = useIntl(); const intl = useIntl();
const [submit, setSubmit] = React.useState<boolean>(false); const [submit, setSubmit] = React.useState<boolean>(false);
const { map } = useFetchMapById(mapId); const { data: map } = useFetchMapById(mapId);
const [exportGroup, setExportGroup] = React.useState<ExportGroup>( const [exportGroup, setExportGroup] = React.useState<ExportGroup>(
enableImgExport ? 'image' : 'document', enableImgExport ? 'image' : 'document',

View File

@ -18,7 +18,7 @@ import LocalizedFormat from 'dayjs/plugin/localizedFormat';
dayjs.extend(LocalizedFormat); dayjs.extend(LocalizedFormat);
const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = useFetchMapById(mapId); const { data: map } = useFetchMapById(mapId);
const [error, setError] = React.useState<ErrorInfo>(); const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl(); const intl = useIntl();

View File

@ -21,7 +21,7 @@ import AppConfig from '../../../../classes/app-config';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = useFetchMapById(mapId); const { data: map } = useFetchMapById(mapId);
const client: Client = useSelector(activeInstance); const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false); const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false);

View File

@ -58,7 +58,7 @@ const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
setModel({ ...model, [name as keyof BasicMapInfo]: value }); setModel({ ...model, [name as keyof BasicMapInfo]: value });
}; };
const { map } = useFetchMapById(mapId); const { data: map } = useFetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
setModel(map); setModel(map);

View File

@ -62,6 +62,7 @@ import LabelDeleteConfirm from './maps-list/label-delete-confirm';
import ReactGA from 'react-ga4'; import ReactGA from 'react-ga4';
import { CSSObject, Interpolation, Theme } from '@emotion/react'; import { CSSObject, Interpolation, Theme } from '@emotion/react';
import withEmotionStyles from '../HOCs/withEmotionStyles'; import withEmotionStyles from '../HOCs/withEmotionStyles';
import { useNavigate } from 'react-router-dom';
export type Filter = GenericFilter | LabelFilter; export type Filter = GenericFilter | LabelFilter;
@ -91,6 +92,7 @@ const MapsPage = (): ReactElement => {
localStorage.getItem('desktopDrawerOpen') === 'true', localStorage.getItem('desktopDrawerOpen') === 'true',
); );
const classes = useStyles(desktopDrawerOpen); const classes = useStyles(desktopDrawerOpen);
const navigate = useNavigate();
const handleMobileDrawerToggle = () => { const handleMobileDrawerToggle = () => {
setMobileDrawerOpen(!mobileDrawerOpen); setMobileDrawerOpen(!mobileDrawerOpen);
@ -123,6 +125,16 @@ const MapsPage = (): ReactElement => {
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Maps List' }); ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Maps List' });
}, []); }, []);
useEffect(() => {
if (client) {
client.onSessionExpired(() => {
navigate('/c/login');
});
} else {
console.warn('Session expiration wont be handled because could not find client');
}
}, []);
const mutation = useMutation((id: number) => client.deleteLabel(id), { const mutation = useMutation((id: number) => client.deleteLabel(id), {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries('labels'); queryClient.invalidateQueries('labels');

View File

@ -19,7 +19,7 @@
/* 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';
import Client, { AccountInfo, ErrorInfo, MapInfo } from '../classes/client'; import Client, { AccountInfo, ErrorInfo, MapInfo, MapMetadata } from '../classes/client';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import AppConfig from '../classes/app-config'; import AppConfig from '../classes/app-config';
import { RootState } from './rootReducer'; import { RootState } from './rootReducer';
@ -55,7 +55,7 @@ export const clientSlice = createSlice({
type MapLoadResult = { type MapLoadResult = {
isLoading: boolean; isLoading: boolean;
error: ErrorInfo | null; error: ErrorInfo | null;
map: MapInfo | undefined; data: MapInfo | undefined;
}; };
export const useFetchMapById = (id: number): MapLoadResult => { export const useFetchMapById = (id: number): MapLoadResult => {
@ -82,7 +82,24 @@ export const useFetchMapById = (id: number): MapLoadResult => {
}; };
} }
} }
return { isLoading: isLoading, error: errorMsg, map: map }; return { isLoading: isLoading, error: errorMsg, data: map };
};
type MapMetadataLoadResult = {
isLoading: boolean;
error: ErrorInfo | null;
data: MapMetadata | undefined;
};
export const useFetchMapMetadata = (id: number): MapMetadataLoadResult => {
const client: Client = useSelector(activeInstance);
const { isLoading, error, data } = useQuery<unknown, ErrorInfo, MapMetadata>(
`maps-metadata-${id}`,
() => {
return client.fetchMapMetadata(id);
},
);
return { isLoading: isLoading, error: error, data: data };
}; };
export const useFetchAccount = (): AccountInfo | undefined => { export const useFetchAccount = (): AccountInfo | undefined => {

View File

@ -14,6 +14,7 @@ module.exports = merge(common, {
template: path.join(__dirname, 'public/index.html'), template: path.join(__dirname, 'public/index.html'),
templateParameters: { templateParameters: {
PUBLIC_URL: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'https://www.wisemapping.com', PUBLIC_URL: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'https://www.wisemapping.com',
CLIENT_TYPE: 'rest'
}, },
base: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'https://www.wisemapping.com', base: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : 'https://www.wisemapping.com',
}), }),