mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-11-22 06:37:56 +01:00
Add expiration dialog.
This commit is contained in:
parent
93aae24988
commit
24b9cadf80
@ -38,13 +38,14 @@ declare global {
|
||||
}
|
||||
|
||||
export type EditorPropsType = {
|
||||
initCallback?: (locale: string) => void;
|
||||
initCallback?: (locale: string, persistenceManager: PersistenceManager) => void;
|
||||
mapId?: number;
|
||||
isTryMode: boolean;
|
||||
readOnlyMode: boolean;
|
||||
locale?: string;
|
||||
onAction: (action: ToolbarActionType) => void;
|
||||
hotkeys?: boolean;
|
||||
persistenceManager: PersistenceManager;
|
||||
};
|
||||
|
||||
const loadLocaleData = (locale: string) => {
|
||||
@ -62,33 +63,15 @@ const loadLocaleData = (locale: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const initMindplot = (locale: string) => {
|
||||
const initMindplot = (locale: string, persistenceManager: PersistenceManager) => {
|
||||
// Change page title ...
|
||||
document.title = `${global.mapTitle} | WiseMapping `;
|
||||
|
||||
// Configure persistence manager ...
|
||||
let persistence: PersistenceManager;
|
||||
if (!global.memoryPersistence && !global.readOnly) {
|
||||
persistence = new RESTPersistenceManager({
|
||||
documentUrl: '/c/restful/maps/{id}/document',
|
||||
revertUrl: '/c/restful/maps/{id}/history/latest',
|
||||
lockUrl: '/c/restful/maps/{id}/lock',
|
||||
timestamp: global.lockTimestamp,
|
||||
session: global.lockSession,
|
||||
});
|
||||
} else {
|
||||
persistence = new LocalStorageManager(
|
||||
`/c/restful/maps/{id}/${global.historyId ? `${global.historyId}/` : ''}document/xml${!global.isAuth ? '-pub' : ''
|
||||
}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(window.location.search.substring(1));
|
||||
|
||||
const zoomParam = Number.parseFloat(params.get('zoom'));
|
||||
const options = DesignerOptionsBuilder.buildOptions({
|
||||
persistenceManager: persistence,
|
||||
persistenceManager,
|
||||
readOnly: Boolean(global.readOnly || false),
|
||||
mapId: String(global.mapId),
|
||||
container: 'mindplot',
|
||||
@ -120,9 +103,10 @@ const Editor = ({
|
||||
locale = 'en',
|
||||
onAction,
|
||||
hotkeys = true,
|
||||
persistenceManager,
|
||||
}: EditorPropsType): React.ReactElement => {
|
||||
React.useEffect(() => {
|
||||
initCallback(locale);
|
||||
initCallback(locale, persistenceManager);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
48
packages/mindplot/src/components/MockPersistenceManager.ts
Normal file
48
packages/mindplot/src/components/MockPersistenceManager.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright [2022] [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 $ from 'jquery';
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import PersistenceManager from './PersistenceManager';
|
||||
|
||||
class MockPersistenceManager extends PersistenceManager {
|
||||
private exampleMap: string;
|
||||
|
||||
constructor(exampleMapAsXml: string) {
|
||||
super();
|
||||
$assert(exampleMapAsXml, 'The test map must be set');
|
||||
this.exampleMap = exampleMapAsXml;
|
||||
}
|
||||
|
||||
saveMapXml(): void {
|
||||
// Ignore, no implementation required ...
|
||||
}
|
||||
|
||||
discardChanges() {
|
||||
// Ignore, no implementation required ...
|
||||
}
|
||||
|
||||
loadMapDom() {
|
||||
return $.parseXML(this.exampleMap);
|
||||
}
|
||||
|
||||
unlockMap(): void {
|
||||
// Ignore, no implementation required ...
|
||||
}
|
||||
}
|
||||
|
||||
export default MockPersistenceManager;
|
@ -20,10 +20,20 @@ import { $assert } from '@wisemapping/core-js';
|
||||
import { Mindmap } from '..';
|
||||
import XMLSerializerFactory from './persistence/XMLSerializerFactory';
|
||||
|
||||
export type PersistenceError = {
|
||||
severity: string;
|
||||
message: string;
|
||||
errorType?: 'session-expired' | 'bad-request' | 'generic';
|
||||
};
|
||||
|
||||
export type PersistenceErrorCallback = (error: PersistenceError) => void;
|
||||
|
||||
abstract class PersistenceManager {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
static _instance: PersistenceManager;
|
||||
|
||||
private _errorHandlers: PersistenceErrorCallback[] = [];
|
||||
|
||||
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events?) {
|
||||
$assert(mindmap, 'mindmap can not be null');
|
||||
$assert(editorProperties, 'editorProperties can not be null');
|
||||
@ -48,6 +58,24 @@ abstract class PersistenceManager {
|
||||
return PersistenceManager.loadFromDom(mapId, domDocument);
|
||||
}
|
||||
|
||||
triggerError(error: PersistenceError) {
|
||||
this._errorHandlers.forEach((handler) => handler(error));
|
||||
}
|
||||
|
||||
addErrorHandler(callback: PersistenceErrorCallback) {
|
||||
this._errorHandlers.push(callback);
|
||||
}
|
||||
|
||||
removeErrorHandler(callback?: PersistenceErrorCallback) {
|
||||
if (!callback) {
|
||||
this._errorHandlers.length = 0;
|
||||
}
|
||||
const index = this._errorHandlers.findIndex((handler) => handler === callback);
|
||||
if (index !== -1) {
|
||||
this._errorHandlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
abstract discardChanges(mapId: string): void;
|
||||
|
||||
abstract loadMapDom(mapId: string): Document;
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import $ from 'jquery';
|
||||
import { $msg } from './Messages';
|
||||
import PersistenceManager from './PersistenceManager';
|
||||
import PersistenceManager, { PersistenceError } from './PersistenceManager';
|
||||
|
||||
class RESTPersistenceManager extends PersistenceManager {
|
||||
private documentUrl: string;
|
||||
@ -76,7 +76,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
method: 'PUT',
|
||||
// Blob helps to resuce the memory on large payload.
|
||||
body: new Blob([JSON.stringify(data)], { type: 'text/plain' }),
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
},
|
||||
).then(async (response: Response) => {
|
||||
if (response.ok) {
|
||||
@ -86,7 +86,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
console.log(`Saving error: ${response.status}`);
|
||||
let userMsg;
|
||||
if (response.status === 405) {
|
||||
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED') };
|
||||
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED'), errorType: 'session-expired' };
|
||||
} else {
|
||||
const responseText = await response.text();
|
||||
const contentType = response.headers['Content-Type'];
|
||||
@ -101,6 +101,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
userMsg = persistence._buildError(serverMsg);
|
||||
}
|
||||
}
|
||||
this.triggerError(userMsg);
|
||||
events.onError(userMsg);
|
||||
}
|
||||
|
||||
@ -109,9 +110,11 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
clearTimeout(persistence.clearTimeout);
|
||||
}
|
||||
persistence.onSave = false;
|
||||
}).catch((error) => {
|
||||
console.log(`Unexpected save error => ${error}`);
|
||||
const userMsg = { severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED') };
|
||||
}).catch(() => {
|
||||
const userMsg: PersistenceError = {
|
||||
severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED'), errorType: 'generic',
|
||||
};
|
||||
this.triggerError(userMsg);
|
||||
events.onError(userMsg);
|
||||
|
||||
// Clear event timeout ...
|
||||
@ -127,7 +130,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
fetch(this.revertUrl.replace('{id}', mapId),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8', Accept: 'application/json', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
});
|
||||
}
|
||||
|
||||
@ -136,7 +139,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
this.lockUrl.replace('{id}', mapId),
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
headers: { 'Content-Type': 'text/plain', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
body: 'false',
|
||||
},
|
||||
);
|
||||
@ -156,14 +159,17 @@ class RESTPersistenceManager extends PersistenceManager {
|
||||
return { severity, message };
|
||||
}
|
||||
|
||||
private getCSRFToken(): string {
|
||||
return document.head.querySelector('meta[name="_csrf"]').getAttribute('content');
|
||||
}
|
||||
|
||||
loadMapDom(mapId: string): Document {
|
||||
// Let's try to open one from the local directory ...
|
||||
let xml: Document;
|
||||
$.ajax({
|
||||
url: `${this.documentUrl.replace('{id}', mapId)}/xml`,
|
||||
method: 'get',
|
||||
async: false,
|
||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml' },
|
||||
headers: { 'Content-Type': 'text/plain', Accept: 'application/xml', 'X-CSRF-Token': this.getCSRFToken() },
|
||||
success(responseText) {
|
||||
xml = responseText;
|
||||
},
|
||||
|
@ -25,7 +25,8 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
||||
// Overwite default behaviour ...
|
||||
},
|
||||
setValue() {
|
||||
window.location = '/c/logout';
|
||||
const elem = document.getElementById('logoutFrom');
|
||||
elem.submit();
|
||||
},
|
||||
};
|
||||
super(elemId, model);
|
||||
@ -59,6 +60,7 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
||||
content[0].innerHTML = `
|
||||
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
||||
<p>${global.accountEmail}</p>
|
||||
<form action="/c/logout" method='POST' id="logoutFrom"></form>
|
||||
<div id="account-logout" model='logout' style='text-align:center'>
|
||||
Logout
|
||||
</div>
|
||||
|
@ -22,6 +22,7 @@ import PersistenceManager from './components/PersistenceManager';
|
||||
import Designer from './components/Designer';
|
||||
import LocalStorageManager from './components/LocalStorageManager';
|
||||
import RESTPersistenceManager from './components/RestPersistenceManager';
|
||||
import MockPersistenceManager from './components/MockPersistenceManager';
|
||||
import Menu from './components/widget/Menu';
|
||||
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
|
||||
import ImageExporterFactory from './components/export/ImageExporterFactory';
|
||||
@ -48,6 +49,7 @@ export {
|
||||
DesignerBuilder,
|
||||
PersistenceManager,
|
||||
RESTPersistenceManager,
|
||||
MockPersistenceManager,
|
||||
LocalStorageManager,
|
||||
DesignerOptionsBuilder,
|
||||
buildDesigner,
|
||||
|
1
packages/webapp/src/@types/index.d.ts
vendored
1
packages/webapp/src/@types/index.d.ts
vendored
@ -1,2 +1,3 @@
|
||||
declare module '*.png';
|
||||
declare module '*.svg';
|
||||
declare module '*.wxml';
|
@ -17,6 +17,7 @@ import { ThemeProvider, Theme, StyledEngineProvider } from '@mui/material/styles
|
||||
import ReactGA from 'react-ga';
|
||||
import EditorPage from './components/editor-page';
|
||||
import AppConfig from './classes/app-config';
|
||||
import withSessionExpirationHandling from './components/HOCs/withSessionExpirationHandling';
|
||||
|
||||
|
||||
declare module '@mui/styles/defaultTheme' {
|
||||
@ -43,6 +44,8 @@ const App = (): ReactElement => {
|
||||
const istTryMode = global.memoryPersistence;
|
||||
const mapId = parseInt(global.mapId, 10);
|
||||
|
||||
const EditorPageComponent = withSessionExpirationHandling(EditorPage);
|
||||
|
||||
return locale.message ? (
|
||||
<Provider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
@ -80,13 +83,13 @@ const App = (): ReactElement => {
|
||||
/>
|
||||
<Route
|
||||
exact path="/c/maps/"
|
||||
component={MapsPage}
|
||||
component={withSessionExpirationHandling(MapsPage)}
|
||||
/>
|
||||
<Route exact path="/c/maps/:id/edit">
|
||||
<EditorPage isTryMode={istTryMode} mapId={mapId} />
|
||||
<EditorPageComponent isTryMode={istTryMode} mapId={mapId} />
|
||||
</Route>
|
||||
<Route exact path="/c/maps/:id/try">
|
||||
<EditorPage isTryMode={istTryMode} mapId={mapId} />
|
||||
<EditorPageComponent isTryMode={istTryMode} mapId={mapId} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { sessionExpired } from "../../redux/clientSlice";
|
||||
import Client from "../client";
|
||||
import CacheDecoratorClient from "../client/cache-decorator-client";
|
||||
import MockClient from "../client/mock-client";
|
||||
@ -53,9 +52,7 @@ class _AppConfig {
|
||||
const config = this.getInstance();
|
||||
let result: Client;
|
||||
if (config.clientType == 'rest') {
|
||||
result = new RestClient(config.apiBaseUrl, () => {
|
||||
sessionExpired();
|
||||
});
|
||||
result = new RestClient(config.apiBaseUrl);
|
||||
console.log('Service using rest client. ' + JSON.stringify(config));
|
||||
} else {
|
||||
console.log('Warning:Service using mockservice client');
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Mindmap } from '@wisemapping/mindplot';
|
||||
import { Mindmap, PersistenceManager } from '@wisemapping/mindplot';
|
||||
import Client, {
|
||||
AccountInfo,
|
||||
BasicMapInfo,
|
||||
@ -18,6 +18,10 @@ class CacheDecoratorClient implements Client {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
onSessionExpired(callback?: () => void): () => void {
|
||||
return this.client.onSessionExpired(callback);
|
||||
}
|
||||
|
||||
fetchMindmap(id: number): Mindmap {
|
||||
return this.client.fetchMindmap(id);
|
||||
}
|
||||
@ -125,6 +129,15 @@ class CacheDecoratorClient implements Client {
|
||||
revertHistory(id: number, cid: number): Promise<void> {
|
||||
return this.client.revertHistory(id, cid);
|
||||
}
|
||||
|
||||
buildPersistenceManager(): PersistenceManager {
|
||||
return this.client.buildPersistenceManager();
|
||||
}
|
||||
|
||||
removePersistenceManager(): void {
|
||||
return this.client.removePersistenceManager();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default CacheDecoratorClient;
|
@ -45,7 +45,7 @@ const ClientHealthSentinel = (): React.ReactElement => {
|
||||
|
||||
<DialogActions>
|
||||
<Button type="button" color="primary" size="medium" onClick={handleOnClose}>
|
||||
<FormattedMessage id="action.close-button" defaultMessage="Close" />
|
||||
<FormattedMessage id="login.signin" defaultMessage="Sign In" />
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Mindmap } from '@wisemapping/mindplot';
|
||||
import { Mindmap, PersistenceManager } from '@wisemapping/mindplot';
|
||||
import { Locale, LocaleCode } from '../app-i18n';
|
||||
|
||||
export type NewUser = {
|
||||
@ -109,6 +109,10 @@ interface Client {
|
||||
|
||||
fetchMindmap(id:number): Mindmap;
|
||||
|
||||
buildPersistenceManager(): PersistenceManager;
|
||||
removePersistenceManager(): void;
|
||||
|
||||
onSessionExpired(callback?: () => void): () => void;
|
||||
}
|
||||
|
||||
export default Client;
|
||||
|
@ -0,0 +1,70 @@
|
||||
<map name="3" version="tango">
|
||||
<topic central="true" text="Welcome To WiseMapping" id="1" fontStyle=";;#ffffff;;;">
|
||||
<icon id="sign_info"/>
|
||||
<topic position="199,-112" order="0" id="30">
|
||||
<text><![CDATA[5 min tutorial video ?
|
||||
Follow the link !]]></text>
|
||||
<link url="https://www.youtube.com/tv?vq=medium#/watch?v=rKxZwNKs9cE" urlType="url"/>
|
||||
<icon id="hard_computer"/>
|
||||
</topic>
|
||||
<topic position="-167,-112" order="1" text="Try it Now!" id="11" fontStyle=";;#525c61;;;" bgColor="#250be3" brColor="#080559">
|
||||
<icon id="face_surprise"/>
|
||||
<topic position="-260,-141" order="0" text="Double Click" id="12" fontStyle=";;#525c61;;italic;"/>
|
||||
<topic position="-278,-112" order="1" id="13">
|
||||
<text><![CDATA[Press "enter" to add a
|
||||
Sibling]]></text>
|
||||
</topic>
|
||||
<topic position="-271,-83" order="2" text="Drag map to move" id="14" fontStyle=";;#525c61;;italic;"/>
|
||||
</topic>
|
||||
<topic position="155,-18" order="2" text="Features" id="15" fontStyle=";;#525c61;;;">
|
||||
<topic position="244,-80" order="0" text="Links to Sites" id="16" fontStyle=";6;#525c61;;;">
|
||||
<link url="http://www.digg.com" urlType="url"/>
|
||||
</topic>
|
||||
<topic position="224,-30" order="1" text="Styles" id="31">
|
||||
<topic position="285,-55" order="0" text="Fonts" id="17" fontStyle=";;#525c61;;;"/>
|
||||
<topic position="299,-30" order="1" text="Topic Shapes" shape="line" id="19" fontStyle=";;#525c61;;;"/>
|
||||
<topic position="295,-5" order="2" text="Topic Color" id="18" fontStyle=";;#525c61;;;"/>
|
||||
</topic>
|
||||
<topic position="229,20" order="2" text="Icons" id="20" fontStyle=";;#525c61;;;">
|
||||
<icon id="object_rainbow"/>
|
||||
</topic>
|
||||
<topic position="249,45" order="3" text="History Changes" id="21" fontStyle=";;#525c61;;;">
|
||||
<icon id="arrowc_turn_left"/>
|
||||
</topic>
|
||||
</topic>
|
||||
<topic position="-176,-21" order="3" text="Mind Mapping" id="6" fontStyle=";;#525c61;;;" bgColor="#edabff">
|
||||
<icon id="thumb_thumb_up"/>
|
||||
<topic position="-293,-58" order="0" text="Share with Collegues" id="7" fontStyle=";;#525c61;;;"/>
|
||||
<topic position="-266,-33" order="1" text="Online" id="8" fontStyle=";;#525c61;;;"/>
|
||||
<topic position="-288,-8" order="2" text="Anyplace, Anytime" id="9" fontStyle=";;#525c61;;;"/>
|
||||
<topic position="-266,17" order="3" text="Free!!!" id="10" fontStyle=";;#525c61;;;"/>
|
||||
</topic>
|
||||
<topic position="171,95" order="4" text="Productivity" id="2" fontStyle=";;#525c61;;;" bgColor="#d9b518">
|
||||
<icon id="chart_bar"/>
|
||||
<topic position="281,70" order="0" text="Share your ideas" id="3" fontStyle=";;#525c61;;;">
|
||||
<icon id="bulb_light_on"/>
|
||||
</topic>
|
||||
<topic position="270,95" order="1" text="Brainstorming" id="4" fontStyle=";;#525c61;;;"/>
|
||||
<topic position="256,120" order="2" text="Visual " id="5" fontStyle=";;#525c61;;;"/>
|
||||
</topic>
|
||||
<topic position="-191,54" order="5" text="Install In Your Server" id="27" fontStyle=";;#525c61;;;">
|
||||
<icon id="hard_computer"/>
|
||||
<topic position="-319,42" order="0" text="Open Source" id="29" fontStyle=";;#525c61;;;">
|
||||
<icon id="soft_penguin"/>
|
||||
<link url="http://www.wisemapping.org/" urlType="url"/>
|
||||
</topic>
|
||||
<topic position="-310,67" order="1" text="Download" id="28" fontStyle=";;#525c61;;;">
|
||||
<link url="http://www.wisemapping.com/inyourserver.html" urlType="url"/>
|
||||
</topic>
|
||||
</topic>
|
||||
<topic position="-169,117" order="7" text="Collaborate" id="32">
|
||||
<icon id="people_group"/>
|
||||
<topic position="-253,92" order="0" text="Embed" id="33"/>
|
||||
<topic position="-254,117" order="1" text="Publish" id="34"/>
|
||||
<topic position="-277,142" order="2" text="Share for Edition" id="35">
|
||||
<icon id="mail_envelop"/>
|
||||
</topic>
|
||||
</topic>
|
||||
</topic>
|
||||
<relationship srcTopicId="30" destTopicId="11" lineType="3" srcCtrlPoint="-80,-56" destCtrlPoint="110,-116" endArrow="false" startArrow="true"/>
|
||||
</map>
|
@ -1,4 +1,4 @@
|
||||
import { Mindmap } from '@wisemapping/mindplot';
|
||||
import { Mindmap, MockPersistenceManager, PersistenceManager } from '@wisemapping/mindplot';
|
||||
import XMLSerializerTango from '@wisemapping/mindplot/src/components/persistence/XMLSerializerTango';
|
||||
import Client, {
|
||||
AccountInfo,
|
||||
@ -11,6 +11,7 @@ import Client, {
|
||||
Permission,
|
||||
} from '..';
|
||||
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
||||
import exampleMap from './example-map.wxml';
|
||||
|
||||
const label1: Label = {
|
||||
id: 1,
|
||||
@ -34,6 +35,7 @@ class MockClient implements Client {
|
||||
private maps: MapInfo[] = [];
|
||||
private labels: Label[] = [];
|
||||
private permissionsByMap: Map<number, Permission[]> = new Map();
|
||||
private persistenceManager: PersistenceManager;
|
||||
|
||||
constructor() {
|
||||
// Remove, just for develop ....
|
||||
@ -110,6 +112,10 @@ class MockClient implements Client {
|
||||
this.labels = [label1, label2, label3];
|
||||
}
|
||||
|
||||
onSessionExpired(callback?: () => void): () => void {
|
||||
return callback;
|
||||
}
|
||||
|
||||
fetchMindmap(id: number): Mindmap {
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(`
|
||||
@ -392,6 +398,21 @@ class MockClient implements Client {
|
||||
console.log('email:' + email);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
buildPersistenceManager(): PersistenceManager {
|
||||
if (this.persistenceManager){
|
||||
return this.persistenceManager;
|
||||
}
|
||||
const persistence: PersistenceManager = new MockPersistenceManager(exampleMap);
|
||||
this.persistenceManager = persistence;
|
||||
return persistence;
|
||||
}
|
||||
|
||||
removePersistenceManager(): void {
|
||||
if (this.persistenceManager) {
|
||||
delete this.persistenceManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MockClient;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { LocalStorageManager, Mindmap } from '@wisemapping/mindplot';
|
||||
import axios from 'axios';
|
||||
import { LocalStorageManager, Mindmap, PersistenceManager, RESTPersistenceManager } from '@wisemapping/mindplot';
|
||||
import { PersistenceError } from '@wisemapping/mindplot/src/components/PersistenceManager';
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import Client, {
|
||||
ErrorInfo,
|
||||
MapInfo,
|
||||
@ -11,15 +12,46 @@ import Client, {
|
||||
ImportMapInfo,
|
||||
Permission,
|
||||
} from '..';
|
||||
import { getCsrfToken } from '../../../utils';
|
||||
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
||||
|
||||
export default class RestClient implements Client {
|
||||
private baseUrl: string;
|
||||
private sessionExpired: () => void;
|
||||
private persistenceManager: PersistenceManager;
|
||||
private axios: AxiosInstance;
|
||||
|
||||
constructor(baseUrl: string, sessionExpired: () => void) {
|
||||
private checkResponseForSessionExpired = <T>(error: { response?: AxiosResponse<T> }): Promise<{ response?: AxiosResponse<T> }> => {
|
||||
// TODO: Improve session timeout response and response handling
|
||||
if (error.response && error.response.status === 405) {
|
||||
this.sessionExpired();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
};
|
||||
|
||||
constructor(baseUrl: string) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.sessionExpired = sessionExpired;
|
||||
this.axios = axios.create({ maxRedirects: 0 });
|
||||
const csrfToken = getCsrfToken();
|
||||
if (csrfToken) {
|
||||
this.axios.defaults.headers['X-CSRF-TOKEN'] = csrfToken;
|
||||
} else {
|
||||
console.warn('csrf token not found in html head');
|
||||
}
|
||||
this.axios.interceptors.response.use((r) => r, (r) => this.checkResponseForSessionExpired(r));
|
||||
}
|
||||
|
||||
private _onSessionExpired : () => void;
|
||||
onSessionExpired(callback?: () => void): () => void {
|
||||
if (callback) {
|
||||
this._onSessionExpired = callback;
|
||||
}
|
||||
return this._onSessionExpired;
|
||||
}
|
||||
|
||||
private sessionExpired() {
|
||||
if (this._onSessionExpired) {
|
||||
this._onSessionExpired();
|
||||
}
|
||||
}
|
||||
|
||||
fetchMindmap(id: number): Mindmap {
|
||||
@ -34,7 +66,7 @@ export default class RestClient implements Client {
|
||||
|
||||
deleteMapPermission(id: number, email: string): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.delete(`${this.baseUrl}/c/restful/maps/${id}/collabs?email=${encodeURIComponent(email)}`, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -51,7 +83,7 @@ export default class RestClient implements Client {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(
|
||||
`${this.baseUrl}/c/restful/maps/${id}/collabs/`,
|
||||
{
|
||||
@ -77,7 +109,7 @@ export default class RestClient implements Client {
|
||||
success: (labels: Permission[]) => void,
|
||||
reject: (error: ErrorInfo) => void
|
||||
) => {
|
||||
axios
|
||||
this.axios
|
||||
.get(`${this.baseUrl}/c/restful/maps/${id}/collabs`, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -104,7 +136,7 @@ export default class RestClient implements Client {
|
||||
|
||||
deleteAccount(): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.delete(`${this.baseUrl}/c/restful/account`, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -121,12 +153,12 @@ export default class RestClient implements Client {
|
||||
|
||||
updateAccountInfo(firstname: string, lastname: string): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(`${this.baseUrl}/c/restful/account/firstname`, firstname, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
.then(() => {
|
||||
return axios.put(`${this.baseUrl}/c/restful/account/lastname`, lastname, {
|
||||
return this.axios.put(`${this.baseUrl}/c/restful/account/lastname`, lastname, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
});
|
||||
})
|
||||
@ -144,7 +176,7 @@ export default class RestClient implements Client {
|
||||
|
||||
updateAccountPassword(pasword: string): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(`${this.baseUrl}/c/restful/account/password`, pasword, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -161,7 +193,7 @@ export default class RestClient implements Client {
|
||||
|
||||
updateAccountLanguage(locale: LocaleCode): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(`${this.baseUrl}/c/restful/account/locale`, locale, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -179,7 +211,7 @@ export default class RestClient implements Client {
|
||||
|
||||
importMap(model: ImportMapInfo): Promise<number> {
|
||||
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.post(
|
||||
`${this.baseUrl}/c/restful/maps?title=${encodeURIComponent(model.title)}&description=${model.description ? model.description : ''
|
||||
}`,
|
||||
@ -203,7 +235,7 @@ export default class RestClient implements Client {
|
||||
success: (account: AccountInfo) => void,
|
||||
reject: (error: ErrorInfo) => void
|
||||
) => {
|
||||
axios
|
||||
this.axios
|
||||
.get(`${this.baseUrl}/c/restful/account`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -227,7 +259,7 @@ export default class RestClient implements Client {
|
||||
|
||||
deleteMaps(ids: number[]): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.delete(`${this.baseUrl}/c/restful/maps/batch?ids=${ids.join()}`, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -245,7 +277,7 @@ export default class RestClient implements Client {
|
||||
|
||||
updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(`${this.baseUrl}/c/restful/maps/${id}/publish`, isPublic.toString(), {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -262,7 +294,7 @@ export default class RestClient implements Client {
|
||||
|
||||
revertHistory(id: number, hid: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.post(`${this.baseUrl}/c/restful/maps/${id}/history/${hid}`, null, {
|
||||
headers: { 'Content-Type': 'text/pain' },
|
||||
})
|
||||
@ -282,7 +314,7 @@ export default class RestClient implements Client {
|
||||
success: (historyList: ChangeHistory[]) => void,
|
||||
reject: (error: ErrorInfo) => void
|
||||
) => {
|
||||
axios
|
||||
this.axios
|
||||
.get(`${this.baseUrl}/c/restful/maps/${id}/history/`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -307,12 +339,12 @@ export default class RestClient implements Client {
|
||||
|
||||
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(`${this.baseUrl}/c/restful/maps/${id}/title`, basicInfo.title, {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
.then(() => {
|
||||
return axios.put(
|
||||
return this.axios.put(
|
||||
`${this.baseUrl}/c/restful/maps/${id}/description`, basicInfo.description || ' ',
|
||||
{ headers: { 'Content-Type': 'text/plain' } }
|
||||
);
|
||||
@ -332,7 +364,7 @@ export default class RestClient implements Client {
|
||||
|
||||
createMap(model: BasicMapInfo): Promise<number> {
|
||||
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.post(
|
||||
`${this.baseUrl}/c/restful/maps?title=${model.title}&description=${model.description ? model.description : ''
|
||||
}`,
|
||||
@ -356,7 +388,7 @@ export default class RestClient implements Client {
|
||||
success: (mapsInfo: MapInfo[]) => void,
|
||||
reject: (error: ErrorInfo) => void
|
||||
) => {
|
||||
axios
|
||||
this.axios
|
||||
.get(`${this.baseUrl}/c/restful/maps/`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -390,7 +422,7 @@ export default class RestClient implements Client {
|
||||
|
||||
registerNewUser(user: NewUser): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.post(`${this.baseUrl}/service/users/`, JSON.stringify(user), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -408,7 +440,7 @@ export default class RestClient implements Client {
|
||||
|
||||
deleteMap(id: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.delete(`${this.baseUrl}/c/restful/maps/${id}`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -425,7 +457,7 @@ export default class RestClient implements Client {
|
||||
|
||||
resetPassword(email: string): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(`${this.baseUrl}/service/users/resetPassword?email=${encodeURIComponent(email)}`, null, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -444,7 +476,7 @@ export default class RestClient implements Client {
|
||||
|
||||
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
|
||||
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.post(`${this.baseUrl}/c/restful/maps/${id}`, JSON.stringify(basicInfo), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -462,9 +494,8 @@ export default class RestClient implements Client {
|
||||
}
|
||||
|
||||
updateStarred(id: number, starred: boolean): Promise<void> {
|
||||
console.debug(`Starred => ${starred}`)
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.put(`${this.baseUrl}/c/restful/maps/${id}/starred`, starred.toString(), {
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
})
|
||||
@ -485,7 +516,7 @@ export default class RestClient implements Client {
|
||||
success: (labels: Label[]) => void,
|
||||
reject: (error: ErrorInfo) => void
|
||||
) => {
|
||||
axios
|
||||
this.axios
|
||||
.get(`${this.baseUrl}/c/restful/labels/`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -512,7 +543,7 @@ export default class RestClient implements Client {
|
||||
|
||||
createLabel(title: string, color: string): Promise<number> {
|
||||
const handler = (success: (labelId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.post(`${this.baseUrl}/c/restful/labels`, JSON.stringify({ title, color, iconName: 'smile' }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -529,7 +560,7 @@ export default class RestClient implements Client {
|
||||
|
||||
deleteLabel(id: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.delete(`${this.baseUrl}/c/restful/labels/${id}`)
|
||||
.then(() => {
|
||||
success();
|
||||
@ -544,7 +575,7 @@ export default class RestClient implements Client {
|
||||
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.post(`${this.baseUrl}/c/restful/maps/${mapId}/labels`, JSON.stringify(labelId), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
@ -561,7 +592,7 @@ export default class RestClient implements Client {
|
||||
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
this.axios
|
||||
.delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`)
|
||||
.then(() => {
|
||||
success();
|
||||
@ -574,6 +605,45 @@ export default class RestClient implements Client {
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
private onPersistenceManagerError(error: PersistenceError) {
|
||||
if (error.errorType === 'session-expired') {
|
||||
this.sessionExpired();
|
||||
}
|
||||
}
|
||||
|
||||
buildPersistenceManager(): PersistenceManager {
|
||||
if (this.persistenceManager){
|
||||
return this.persistenceManager;
|
||||
}
|
||||
// TODO: Move globals out, make urls configurable
|
||||
let persistence: PersistenceManager;
|
||||
if (!global.memoryPersistence && !global.readOnly) {
|
||||
persistence = new RESTPersistenceManager({
|
||||
documentUrl: '/c/restful/maps/{id}/document',
|
||||
revertUrl: '/c/restful/maps/{id}/history/latest',
|
||||
lockUrl: '/c/restful/maps/{id}/lock',
|
||||
timestamp: global.lockTimestamp,
|
||||
session: global.lockSession,
|
||||
});
|
||||
} else {
|
||||
persistence = new LocalStorageManager(
|
||||
`/c/restful/maps/{id}/${global.historyId ? `${global.historyId}/` : ''}document/xml${!global.isAuth ? '-pub' : ''
|
||||
}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
persistence.addErrorHandler((err) => this.onPersistenceManagerError(err));
|
||||
this.persistenceManager = persistence;
|
||||
return persistence;
|
||||
}
|
||||
|
||||
removePersistenceManager(): void {
|
||||
if (this.persistenceManager) {
|
||||
this.persistenceManager.removeErrorHandler();
|
||||
delete this.persistenceManager;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private parseResponseOnError = (response: any): ErrorInfo => {
|
||||
console.error("Backend error=>");
|
||||
|
@ -0,0 +1,32 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import React, { ComponentType, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Client from '../../classes/client';
|
||||
import ClientHealthSentinel from '../../classes/client/client-health-sentinel';
|
||||
import { activeInstance, sessionExpired } from '../../redux/clientSlice';
|
||||
|
||||
function withSessionExpirationHandling<T>(Component: ComponentType<T>) {
|
||||
return (hocProps: T): React.ReactElement => {
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (client) {
|
||||
client.onSessionExpired(() => {
|
||||
dispatch(sessionExpired());
|
||||
});
|
||||
} else {
|
||||
console.warn('Session expiration wont be handled because could not find client');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ClientHealthSentinel />
|
||||
<Component {...hocProps} />;
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default withSessionExpirationHandling;
|
@ -6,6 +6,9 @@ import AppI18n from '../../classes/app-i18n';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { hotkeysEnabled } from '../../redux/editorSlice';
|
||||
import ReactGA from 'react-ga';
|
||||
import Client from '../../classes/client';
|
||||
import { activeInstance } from '../../redux/clientSlice';
|
||||
import { PersistenceManager } from '@wisemapping/mindplot';
|
||||
|
||||
export type EditorPropsType = {
|
||||
mapId: number;
|
||||
@ -16,13 +19,24 @@ const EditorPage = ({ mapId, ...props }: EditorPropsType): React.ReactElement =>
|
||||
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
|
||||
const hotkeys = useSelector(hotkeysEnabled);
|
||||
const userLocale = AppI18n.getUserLocale();
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const [persistenceManager, setPersistenceManager] = React.useState<PersistenceManager>();
|
||||
|
||||
useEffect(() => {
|
||||
ReactGA.pageview(window.location.pathname + window.location.search);
|
||||
const persistence = client.buildPersistenceManager();
|
||||
setPersistenceManager(persistence);
|
||||
return () => client.removePersistenceManager();
|
||||
}, []);
|
||||
|
||||
if (!persistenceManager) {
|
||||
// persistenceManager must be ready for the editor to work
|
||||
return null;
|
||||
}
|
||||
return <>
|
||||
<Editor {...props} onAction={setActiveDialog} locale={userLocale.code} hotkeys={hotkeys} />
|
||||
<Editor {...props} onAction={setActiveDialog}
|
||||
locale={userLocale.code} hotkeys={hotkeys}
|
||||
persistenceManager={persistenceManager} />
|
||||
{
|
||||
activeDialog &&
|
||||
<ActionDispatcher
|
||||
|
@ -15,6 +15,7 @@ import SubmitButton from '../form/submit-button';
|
||||
import ReactGA from 'react-ga';
|
||||
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
|
||||
|
||||
const ForgotPassword = () => {
|
||||
const [email, setEmail] = useState<string>('');
|
||||
@ -54,6 +55,7 @@ const ForgotPassword = () => {
|
||||
<GlobalError error={error} />
|
||||
|
||||
<form onSubmit={handleOnSubmit}>
|
||||
<input type='hidden' value={getCsrfToken()} name={getCsrfTokenParameter()} />
|
||||
<Input
|
||||
type="email"
|
||||
name="email"
|
||||
|
@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import Link from '@mui/material/Link';
|
||||
import ReactGA from 'react-ga';
|
||||
import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
|
||||
|
||||
type ConfigStatusProps = {
|
||||
enabled?: boolean;
|
||||
@ -89,6 +90,7 @@ const LoginPage = (): React.ReactElement => {
|
||||
|
||||
<FormControl>
|
||||
<form action="/c/perform-login" method="POST">
|
||||
<input type='hidden' value={getCsrfToken()} name={getCsrfTokenParameter()}/>
|
||||
<Input
|
||||
name="username"
|
||||
type="email"
|
||||
|
@ -54,7 +54,7 @@ const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElem
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
window.location.href = '/c/logout';
|
||||
window.location.href = '/c/login';
|
||||
onClose();
|
||||
},
|
||||
onError: (error) => {
|
||||
|
@ -28,6 +28,12 @@ const AccountMenu = (): React.ReactElement => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleLogout = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
const elem = document.getElementById('logoutFrom') as HTMLFormElement;
|
||||
elem.submit();
|
||||
};
|
||||
|
||||
const account = fetchAccount();
|
||||
return (
|
||||
<span>
|
||||
@ -77,7 +83,8 @@ const AccountMenu = (): React.ReactElement => {
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem onClick={handleClose}>
|
||||
<Link color="textSecondary" href="/c/logout">
|
||||
<form action="/c/logout" method='POST' id="logoutFrom"></form>
|
||||
<Link color="textSecondary" href="/c/logout" onClick={(e) => handleLogout(e)}>
|
||||
<ListItemIcon>
|
||||
<ExitToAppOutlined fontSize="small" />
|
||||
</ListItemIcon>
|
||||
@ -85,9 +92,11 @@ const AccountMenu = (): React.ReactElement => {
|
||||
</Link>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
{action == 'change-password' && (
|
||||
{
|
||||
action == 'change-password' && (
|
||||
<ChangePasswordDialog onClose={() => setAction(undefined)} />
|
||||
)}
|
||||
)
|
||||
}
|
||||
{action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />}
|
||||
</span >
|
||||
);
|
||||
|
@ -15,7 +15,6 @@ import Client, { Label } from '../../classes/client';
|
||||
import ActionDispatcher from './action-dispatcher';
|
||||
import { ActionType } from './action-chooser';
|
||||
import AccountMenu from './account-menu';
|
||||
import ClientHealthSentinel from '../../classes/client/client-health-sentinel';
|
||||
import HelpMenu from './help-menu';
|
||||
import LanguageMenu from './language-menu';
|
||||
import AppI18n, { Locales } from '../../classes/app-i18n';
|
||||
@ -153,7 +152,6 @@ const MapsPage = (): ReactElement => {
|
||||
messages={userLocale.message}
|
||||
>
|
||||
<div className={classes.root}>
|
||||
<ClientHealthSentinel />
|
||||
<AppBar
|
||||
position="fixed"
|
||||
className={clsx(classes.appBar, {
|
||||
|
15
packages/webapp/src/utils.ts
Normal file
15
packages/webapp/src/utils.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export const getCsrfToken = (): string | null => {
|
||||
const meta = document.head.querySelector('meta[name="_csrf"]');
|
||||
if (!meta) {
|
||||
return null;
|
||||
}
|
||||
return meta.getAttribute('content');
|
||||
};
|
||||
|
||||
export const getCsrfTokenParameter = (): string | null => {
|
||||
const meta = document.head.querySelector('meta[name="_csrf_parameter"]');
|
||||
if (!meta) {
|
||||
return null;
|
||||
}
|
||||
return meta.getAttribute('content');
|
||||
};
|
@ -30,6 +30,10 @@ module.exports = {
|
||||
test: /\.(png|jpe?g|gif|svg)$/,
|
||||
type: 'asset/inline',
|
||||
},
|
||||
{
|
||||
test: /\.wxml$/i,
|
||||
type: 'asset/source',
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
|
Loading…
Reference in New Issue
Block a user