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 = {
|
export type EditorPropsType = {
|
||||||
initCallback?: (locale: string) => void;
|
initCallback?: (locale: string, persistenceManager: PersistenceManager) => void;
|
||||||
mapId?: number;
|
mapId?: number;
|
||||||
isTryMode: boolean;
|
isTryMode: boolean;
|
||||||
readOnlyMode: boolean;
|
readOnlyMode: boolean;
|
||||||
locale?: string;
|
locale?: string;
|
||||||
onAction: (action: ToolbarActionType) => void;
|
onAction: (action: ToolbarActionType) => void;
|
||||||
hotkeys?: boolean;
|
hotkeys?: boolean;
|
||||||
|
persistenceManager: PersistenceManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadLocaleData = (locale: string) => {
|
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 ...
|
// Change page title ...
|
||||||
document.title = `${global.mapTitle} | WiseMapping `;
|
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 params = new URLSearchParams(window.location.search.substring(1));
|
||||||
|
|
||||||
const zoomParam = Number.parseFloat(params.get('zoom'));
|
const zoomParam = Number.parseFloat(params.get('zoom'));
|
||||||
const options = DesignerOptionsBuilder.buildOptions({
|
const options = DesignerOptionsBuilder.buildOptions({
|
||||||
persistenceManager: persistence,
|
persistenceManager,
|
||||||
readOnly: Boolean(global.readOnly || false),
|
readOnly: Boolean(global.readOnly || false),
|
||||||
mapId: String(global.mapId),
|
mapId: String(global.mapId),
|
||||||
container: 'mindplot',
|
container: 'mindplot',
|
||||||
@ -120,9 +103,10 @@ const Editor = ({
|
|||||||
locale = 'en',
|
locale = 'en',
|
||||||
onAction,
|
onAction,
|
||||||
hotkeys = true,
|
hotkeys = true,
|
||||||
|
persistenceManager,
|
||||||
}: EditorPropsType): React.ReactElement => {
|
}: EditorPropsType): React.ReactElement => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
initCallback(locale);
|
initCallback(locale, persistenceManager);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
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 { Mindmap } from '..';
|
||||||
import XMLSerializerFactory from './persistence/XMLSerializerFactory';
|
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 {
|
abstract class PersistenceManager {
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
static _instance: PersistenceManager;
|
static _instance: PersistenceManager;
|
||||||
|
|
||||||
|
private _errorHandlers: PersistenceErrorCallback[] = [];
|
||||||
|
|
||||||
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events?) {
|
save(mindmap: Mindmap, editorProperties, saveHistory: boolean, events?) {
|
||||||
$assert(mindmap, 'mindmap can not be null');
|
$assert(mindmap, 'mindmap can not be null');
|
||||||
$assert(editorProperties, 'editorProperties can not be null');
|
$assert(editorProperties, 'editorProperties can not be null');
|
||||||
@ -48,6 +58,24 @@ abstract class PersistenceManager {
|
|||||||
return PersistenceManager.loadFromDom(mapId, domDocument);
|
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 discardChanges(mapId: string): void;
|
||||||
|
|
||||||
abstract loadMapDom(mapId: string): Document;
|
abstract loadMapDom(mapId: string): Document;
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import { $assert } from '@wisemapping/core-js';
|
import { $assert } from '@wisemapping/core-js';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { $msg } from './Messages';
|
import { $msg } from './Messages';
|
||||||
import PersistenceManager from './PersistenceManager';
|
import PersistenceManager, { PersistenceError } from './PersistenceManager';
|
||||||
|
|
||||||
class RESTPersistenceManager extends PersistenceManager {
|
class RESTPersistenceManager extends PersistenceManager {
|
||||||
private documentUrl: string;
|
private documentUrl: string;
|
||||||
@ -76,7 +76,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
// Blob helps to resuce the memory on large payload.
|
// Blob helps to resuce the memory on large payload.
|
||||||
body: new Blob([JSON.stringify(data)], { type: 'text/plain' }),
|
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) => {
|
).then(async (response: Response) => {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@ -86,7 +86,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
console.log(`Saving error: ${response.status}`);
|
console.log(`Saving error: ${response.status}`);
|
||||||
let userMsg;
|
let userMsg;
|
||||||
if (response.status === 405) {
|
if (response.status === 405) {
|
||||||
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED') };
|
userMsg = { severity: 'SEVERE', message: $msg('SESSION_EXPIRED'), errorType: 'session-expired' };
|
||||||
} else {
|
} else {
|
||||||
const responseText = await response.text();
|
const responseText = await response.text();
|
||||||
const contentType = response.headers['Content-Type'];
|
const contentType = response.headers['Content-Type'];
|
||||||
@ -101,6 +101,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
userMsg = persistence._buildError(serverMsg);
|
userMsg = persistence._buildError(serverMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.triggerError(userMsg);
|
||||||
events.onError(userMsg);
|
events.onError(userMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,9 +110,11 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
clearTimeout(persistence.clearTimeout);
|
clearTimeout(persistence.clearTimeout);
|
||||||
}
|
}
|
||||||
persistence.onSave = false;
|
persistence.onSave = false;
|
||||||
}).catch((error) => {
|
}).catch(() => {
|
||||||
console.log(`Unexpected save error => ${error}`);
|
const userMsg: PersistenceError = {
|
||||||
const userMsg = { severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED') };
|
severity: 'SEVERE', message: $msg('SAVE_COULD_NOT_BE_COMPLETED'), errorType: 'generic',
|
||||||
|
};
|
||||||
|
this.triggerError(userMsg);
|
||||||
events.onError(userMsg);
|
events.onError(userMsg);
|
||||||
|
|
||||||
// Clear event timeout ...
|
// Clear event timeout ...
|
||||||
@ -127,7 +130,7 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
fetch(this.revertUrl.replace('{id}', mapId),
|
fetch(this.revertUrl.replace('{id}', mapId),
|
||||||
{
|
{
|
||||||
method: 'POST',
|
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),
|
this.lockUrl.replace('{id}', mapId),
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain', 'X-CSRF-Token': this.getCSRFToken() },
|
||||||
body: 'false',
|
body: 'false',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -156,14 +159,17 @@ class RESTPersistenceManager extends PersistenceManager {
|
|||||||
return { severity, message };
|
return { severity, message };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCSRFToken(): string {
|
||||||
|
return document.head.querySelector('meta[name="_csrf"]').getAttribute('content');
|
||||||
|
}
|
||||||
|
|
||||||
loadMapDom(mapId: string): Document {
|
loadMapDom(mapId: string): Document {
|
||||||
// Let's try to open one from the local directory ...
|
|
||||||
let xml: Document;
|
let xml: Document;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `${this.documentUrl.replace('{id}', mapId)}/xml`,
|
url: `${this.documentUrl.replace('{id}', mapId)}/xml`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
async: false,
|
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) {
|
success(responseText) {
|
||||||
xml = responseText;
|
xml = responseText;
|
||||||
},
|
},
|
||||||
|
@ -25,7 +25,8 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
|||||||
// Overwite default behaviour ...
|
// Overwite default behaviour ...
|
||||||
},
|
},
|
||||||
setValue() {
|
setValue() {
|
||||||
window.location = '/c/logout';
|
const elem = document.getElementById('logoutFrom');
|
||||||
|
elem.submit();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
super(elemId, model);
|
super(elemId, model);
|
||||||
@ -59,6 +60,7 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
|||||||
content[0].innerHTML = `
|
content[0].innerHTML = `
|
||||||
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
||||||
<p>${global.accountEmail}</p>
|
<p>${global.accountEmail}</p>
|
||||||
|
<form action="/c/logout" method='POST' id="logoutFrom"></form>
|
||||||
<div id="account-logout" model='logout' style='text-align:center'>
|
<div id="account-logout" model='logout' style='text-align:center'>
|
||||||
Logout
|
Logout
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,6 +22,7 @@ import PersistenceManager from './components/PersistenceManager';
|
|||||||
import Designer from './components/Designer';
|
import Designer from './components/Designer';
|
||||||
import LocalStorageManager from './components/LocalStorageManager';
|
import LocalStorageManager from './components/LocalStorageManager';
|
||||||
import RESTPersistenceManager from './components/RestPersistenceManager';
|
import RESTPersistenceManager from './components/RestPersistenceManager';
|
||||||
|
import MockPersistenceManager from './components/MockPersistenceManager';
|
||||||
import Menu from './components/widget/Menu';
|
import Menu from './components/widget/Menu';
|
||||||
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
|
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
|
||||||
import ImageExporterFactory from './components/export/ImageExporterFactory';
|
import ImageExporterFactory from './components/export/ImageExporterFactory';
|
||||||
@ -48,6 +49,7 @@ export {
|
|||||||
DesignerBuilder,
|
DesignerBuilder,
|
||||||
PersistenceManager,
|
PersistenceManager,
|
||||||
RESTPersistenceManager,
|
RESTPersistenceManager,
|
||||||
|
MockPersistenceManager,
|
||||||
LocalStorageManager,
|
LocalStorageManager,
|
||||||
DesignerOptionsBuilder,
|
DesignerOptionsBuilder,
|
||||||
buildDesigner,
|
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 '*.png';
|
||||||
declare module '*.svg';
|
declare module '*.svg';
|
||||||
|
declare module '*.wxml';
|
@ -17,6 +17,7 @@ import { ThemeProvider, Theme, StyledEngineProvider } from '@mui/material/styles
|
|||||||
import ReactGA from 'react-ga';
|
import ReactGA from 'react-ga';
|
||||||
import EditorPage from './components/editor-page';
|
import EditorPage from './components/editor-page';
|
||||||
import AppConfig from './classes/app-config';
|
import AppConfig from './classes/app-config';
|
||||||
|
import withSessionExpirationHandling from './components/HOCs/withSessionExpirationHandling';
|
||||||
|
|
||||||
|
|
||||||
declare module '@mui/styles/defaultTheme' {
|
declare module '@mui/styles/defaultTheme' {
|
||||||
@ -43,6 +44,8 @@ const App = (): ReactElement => {
|
|||||||
const istTryMode = global.memoryPersistence;
|
const istTryMode = global.memoryPersistence;
|
||||||
const mapId = parseInt(global.mapId, 10);
|
const mapId = parseInt(global.mapId, 10);
|
||||||
|
|
||||||
|
const EditorPageComponent = withSessionExpirationHandling(EditorPage);
|
||||||
|
|
||||||
return locale.message ? (
|
return locale.message ? (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
@ -80,13 +83,13 @@ const App = (): ReactElement => {
|
|||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact path="/c/maps/"
|
exact path="/c/maps/"
|
||||||
component={MapsPage}
|
component={withSessionExpirationHandling(MapsPage)}
|
||||||
/>
|
/>
|
||||||
<Route exact path="/c/maps/:id/edit">
|
<Route exact path="/c/maps/:id/edit">
|
||||||
<EditorPage isTryMode={istTryMode} mapId={mapId} />
|
<EditorPageComponent isTryMode={istTryMode} mapId={mapId} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route exact path="/c/maps/:id/try">
|
<Route exact path="/c/maps/:id/try">
|
||||||
<EditorPage isTryMode={istTryMode} mapId={mapId} />
|
<EditorPageComponent isTryMode={istTryMode} mapId={mapId} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { sessionExpired } from "../../redux/clientSlice";
|
|
||||||
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";
|
||||||
@ -53,9 +52,7 @@ class _AppConfig {
|
|||||||
const config = this.getInstance();
|
const config = this.getInstance();
|
||||||
let result: Client;
|
let result: Client;
|
||||||
if (config.clientType == 'rest') {
|
if (config.clientType == 'rest') {
|
||||||
result = new RestClient(config.apiBaseUrl, () => {
|
result = new RestClient(config.apiBaseUrl);
|
||||||
sessionExpired();
|
|
||||||
});
|
|
||||||
console.log('Service using rest client. ' + JSON.stringify(config));
|
console.log('Service using rest client. ' + JSON.stringify(config));
|
||||||
} else {
|
} else {
|
||||||
console.log('Warning:Service using mockservice client');
|
console.log('Warning:Service using mockservice client');
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Mindmap } from '@wisemapping/mindplot';
|
import { Mindmap, PersistenceManager } from '@wisemapping/mindplot';
|
||||||
import Client, {
|
import Client, {
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
BasicMapInfo,
|
BasicMapInfo,
|
||||||
@ -17,7 +17,11 @@ class CacheDecoratorClient implements Client {
|
|||||||
constructor(client: Client) {
|
constructor(client: Client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSessionExpired(callback?: () => void): () => void {
|
||||||
|
return this.client.onSessionExpired(callback);
|
||||||
|
}
|
||||||
|
|
||||||
fetchMindmap(id: number): Mindmap {
|
fetchMindmap(id: number): Mindmap {
|
||||||
return this.client.fetchMindmap(id);
|
return this.client.fetchMindmap(id);
|
||||||
}
|
}
|
||||||
@ -125,6 +129,15 @@ class CacheDecoratorClient implements Client {
|
|||||||
revertHistory(id: number, cid: number): Promise<void> {
|
revertHistory(id: number, cid: number): Promise<void> {
|
||||||
return this.client.revertHistory(id, cid);
|
return this.client.revertHistory(id, cid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildPersistenceManager(): PersistenceManager {
|
||||||
|
return this.client.buildPersistenceManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
removePersistenceManager(): void {
|
||||||
|
return this.client.removePersistenceManager();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CacheDecoratorClient;
|
export default CacheDecoratorClient;
|
@ -45,7 +45,7 @@ const ClientHealthSentinel = (): React.ReactElement => {
|
|||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button type="button" color="primary" size="medium" onClick={handleOnClose}>
|
<Button type="button" color="primary" size="medium" onClick={handleOnClose}>
|
||||||
<FormattedMessage id="action.close-button" defaultMessage="Close" />
|
<FormattedMessage id="login.signin" defaultMessage="Sign In" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Mindmap } from '@wisemapping/mindplot';
|
import { Mindmap, PersistenceManager } from '@wisemapping/mindplot';
|
||||||
import { Locale, LocaleCode } from '../app-i18n';
|
import { Locale, LocaleCode } from '../app-i18n';
|
||||||
|
|
||||||
export type NewUser = {
|
export type NewUser = {
|
||||||
@ -109,6 +109,10 @@ interface Client {
|
|||||||
|
|
||||||
fetchMindmap(id:number): Mindmap;
|
fetchMindmap(id:number): Mindmap;
|
||||||
|
|
||||||
|
buildPersistenceManager(): PersistenceManager;
|
||||||
|
removePersistenceManager(): void;
|
||||||
|
|
||||||
|
onSessionExpired(callback?: () => void): () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Client;
|
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 XMLSerializerTango from '@wisemapping/mindplot/src/components/persistence/XMLSerializerTango';
|
||||||
import Client, {
|
import Client, {
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
@ -11,6 +11,7 @@ import Client, {
|
|||||||
Permission,
|
Permission,
|
||||||
} from '..';
|
} from '..';
|
||||||
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
||||||
|
import exampleMap from './example-map.wxml';
|
||||||
|
|
||||||
const label1: Label = {
|
const label1: Label = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -34,7 +35,8 @@ class MockClient implements Client {
|
|||||||
private maps: MapInfo[] = [];
|
private maps: MapInfo[] = [];
|
||||||
private labels: Label[] = [];
|
private labels: Label[] = [];
|
||||||
private permissionsByMap: Map<number, Permission[]> = new Map();
|
private permissionsByMap: Map<number, Permission[]> = new Map();
|
||||||
|
private persistenceManager: PersistenceManager;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Remove, just for develop ....
|
// Remove, just for develop ....
|
||||||
function createMapInfo(
|
function createMapInfo(
|
||||||
@ -109,6 +111,10 @@ class MockClient implements Client {
|
|||||||
|
|
||||||
this.labels = [label1, label2, label3];
|
this.labels = [label1, label2, label3];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSessionExpired(callback?: () => void): () => void {
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
fetchMindmap(id: number): Mindmap {
|
fetchMindmap(id: number): Mindmap {
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
@ -392,6 +398,21 @@ class MockClient implements Client {
|
|||||||
console.log('email:' + email);
|
console.log('email:' + email);
|
||||||
return Promise.resolve();
|
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;
|
export default MockClient;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { LocalStorageManager, Mindmap } from '@wisemapping/mindplot';
|
import { LocalStorageManager, Mindmap, PersistenceManager, RESTPersistenceManager } from '@wisemapping/mindplot';
|
||||||
import axios from 'axios';
|
import { PersistenceError } from '@wisemapping/mindplot/src/components/PersistenceManager';
|
||||||
|
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import Client, {
|
import Client, {
|
||||||
ErrorInfo,
|
ErrorInfo,
|
||||||
MapInfo,
|
MapInfo,
|
||||||
@ -11,15 +12,46 @@ import Client, {
|
|||||||
ImportMapInfo,
|
ImportMapInfo,
|
||||||
Permission,
|
Permission,
|
||||||
} from '..';
|
} from '..';
|
||||||
|
import { getCsrfToken } from '../../../utils';
|
||||||
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
||||||
|
|
||||||
export default class RestClient implements Client {
|
export default class RestClient implements Client {
|
||||||
private baseUrl: string;
|
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.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 {
|
fetchMindmap(id: number): Mindmap {
|
||||||
@ -34,7 +66,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
deleteMapPermission(id: number, email: string): Promise<void> {
|
deleteMapPermission(id: number, email: string): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.delete(`${this.baseUrl}/c/restful/maps/${id}/collabs?email=${encodeURIComponent(email)}`, {
|
.delete(`${this.baseUrl}/c/restful/maps/${id}/collabs?email=${encodeURIComponent(email)}`, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -51,7 +83,7 @@ export default class RestClient implements Client {
|
|||||||
// 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) => {
|
||||||
axios
|
this.axios
|
||||||
.put(
|
.put(
|
||||||
`${this.baseUrl}/c/restful/maps/${id}/collabs/`,
|
`${this.baseUrl}/c/restful/maps/${id}/collabs/`,
|
||||||
{
|
{
|
||||||
@ -77,7 +109,7 @@ export default class RestClient implements Client {
|
|||||||
success: (labels: Permission[]) => void,
|
success: (labels: Permission[]) => void,
|
||||||
reject: (error: ErrorInfo) => void
|
reject: (error: ErrorInfo) => void
|
||||||
) => {
|
) => {
|
||||||
axios
|
this.axios
|
||||||
.get(`${this.baseUrl}/c/restful/maps/${id}/collabs`, {
|
.get(`${this.baseUrl}/c/restful/maps/${id}/collabs`, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -104,7 +136,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
deleteAccount(): Promise<void> {
|
deleteAccount(): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.delete(`${this.baseUrl}/c/restful/account`, {
|
.delete(`${this.baseUrl}/c/restful/account`, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -121,12 +153,12 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
updateAccountInfo(firstname: string, lastname: string): Promise<void> {
|
updateAccountInfo(firstname: string, lastname: string): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.put(`${this.baseUrl}/c/restful/account/firstname`, firstname, {
|
.put(`${this.baseUrl}/c/restful/account/firstname`, firstname, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
.then(() => {
|
.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' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -144,7 +176,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
updateAccountPassword(pasword: string): Promise<void> {
|
updateAccountPassword(pasword: string): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.put(`${this.baseUrl}/c/restful/account/password`, pasword, {
|
.put(`${this.baseUrl}/c/restful/account/password`, pasword, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -161,7 +193,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
updateAccountLanguage(locale: LocaleCode): Promise<void> {
|
updateAccountLanguage(locale: LocaleCode): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.put(`${this.baseUrl}/c/restful/account/locale`, locale, {
|
.put(`${this.baseUrl}/c/restful/account/locale`, locale, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -179,7 +211,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
importMap(model: ImportMapInfo): Promise<number> {
|
importMap(model: ImportMapInfo): Promise<number> {
|
||||||
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.post(
|
.post(
|
||||||
`${this.baseUrl}/c/restful/maps?title=${encodeURIComponent(model.title)}&description=${model.description ? model.description : ''
|
`${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,
|
success: (account: AccountInfo) => void,
|
||||||
reject: (error: ErrorInfo) => void
|
reject: (error: ErrorInfo) => void
|
||||||
) => {
|
) => {
|
||||||
axios
|
this.axios
|
||||||
.get(`${this.baseUrl}/c/restful/account`, {
|
.get(`${this.baseUrl}/c/restful/account`, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -227,7 +259,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
deleteMaps(ids: number[]): Promise<void> {
|
deleteMaps(ids: number[]): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.delete(`${this.baseUrl}/c/restful/maps/batch?ids=${ids.join()}`, {
|
.delete(`${this.baseUrl}/c/restful/maps/batch?ids=${ids.join()}`, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -245,7 +277,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
|
updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.put(`${this.baseUrl}/c/restful/maps/${id}/publish`, isPublic.toString(), {
|
.put(`${this.baseUrl}/c/restful/maps/${id}/publish`, isPublic.toString(), {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -262,7 +294,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
revertHistory(id: number, hid: number): Promise<void> {
|
revertHistory(id: number, hid: number): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.post(`${this.baseUrl}/c/restful/maps/${id}/history/${hid}`, null, {
|
.post(`${this.baseUrl}/c/restful/maps/${id}/history/${hid}`, null, {
|
||||||
headers: { 'Content-Type': 'text/pain' },
|
headers: { 'Content-Type': 'text/pain' },
|
||||||
})
|
})
|
||||||
@ -282,7 +314,7 @@ export default class RestClient implements Client {
|
|||||||
success: (historyList: ChangeHistory[]) => void,
|
success: (historyList: ChangeHistory[]) => void,
|
||||||
reject: (error: ErrorInfo) => void
|
reject: (error: ErrorInfo) => void
|
||||||
) => {
|
) => {
|
||||||
axios
|
this.axios
|
||||||
.get(`${this.baseUrl}/c/restful/maps/${id}/history/`, {
|
.get(`${this.baseUrl}/c/restful/maps/${id}/history/`, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -307,12 +339,12 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> {
|
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.put(`${this.baseUrl}/c/restful/maps/${id}/title`, basicInfo.title, {
|
.put(`${this.baseUrl}/c/restful/maps/${id}/title`, basicInfo.title, {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return axios.put(
|
return this.axios.put(
|
||||||
`${this.baseUrl}/c/restful/maps/${id}/description`, basicInfo.description || ' ',
|
`${this.baseUrl}/c/restful/maps/${id}/description`, basicInfo.description || ' ',
|
||||||
{ headers: { 'Content-Type': 'text/plain' } }
|
{ headers: { 'Content-Type': 'text/plain' } }
|
||||||
);
|
);
|
||||||
@ -332,7 +364,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
createMap(model: BasicMapInfo): Promise<number> {
|
createMap(model: BasicMapInfo): Promise<number> {
|
||||||
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.post(
|
.post(
|
||||||
`${this.baseUrl}/c/restful/maps?title=${model.title}&description=${model.description ? model.description : ''
|
`${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,
|
success: (mapsInfo: MapInfo[]) => void,
|
||||||
reject: (error: ErrorInfo) => void
|
reject: (error: ErrorInfo) => void
|
||||||
) => {
|
) => {
|
||||||
axios
|
this.axios
|
||||||
.get(`${this.baseUrl}/c/restful/maps/`, {
|
.get(`${this.baseUrl}/c/restful/maps/`, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -390,7 +422,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
registerNewUser(user: NewUser): Promise<void> {
|
registerNewUser(user: NewUser): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.post(`${this.baseUrl}/service/users/`, JSON.stringify(user), {
|
.post(`${this.baseUrl}/service/users/`, JSON.stringify(user), {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -408,7 +440,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
deleteMap(id: number): Promise<void> {
|
deleteMap(id: number): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.delete(`${this.baseUrl}/c/restful/maps/${id}`, {
|
.delete(`${this.baseUrl}/c/restful/maps/${id}`, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -425,7 +457,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
resetPassword(email: string): Promise<void> {
|
resetPassword(email: string): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.put(`${this.baseUrl}/service/users/resetPassword?email=${encodeURIComponent(email)}`, null, {
|
.put(`${this.baseUrl}/service/users/resetPassword?email=${encodeURIComponent(email)}`, null, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -444,7 +476,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
|
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
|
||||||
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.post(`${this.baseUrl}/c/restful/maps/${id}`, JSON.stringify(basicInfo), {
|
.post(`${this.baseUrl}/c/restful/maps/${id}`, JSON.stringify(basicInfo), {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -462,9 +494,8 @@ export default class RestClient implements Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateStarred(id: number, starred: boolean): Promise<void> {
|
updateStarred(id: number, starred: boolean): Promise<void> {
|
||||||
console.debug(`Starred => ${starred}`)
|
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.put(`${this.baseUrl}/c/restful/maps/${id}/starred`, starred.toString(), {
|
.put(`${this.baseUrl}/c/restful/maps/${id}/starred`, starred.toString(), {
|
||||||
headers: { 'Content-Type': 'text/plain' },
|
headers: { 'Content-Type': 'text/plain' },
|
||||||
})
|
})
|
||||||
@ -485,7 +516,7 @@ export default class RestClient implements Client {
|
|||||||
success: (labels: Label[]) => void,
|
success: (labels: Label[]) => void,
|
||||||
reject: (error: ErrorInfo) => void
|
reject: (error: ErrorInfo) => void
|
||||||
) => {
|
) => {
|
||||||
axios
|
this.axios
|
||||||
.get(`${this.baseUrl}/c/restful/labels/`, {
|
.get(`${this.baseUrl}/c/restful/labels/`, {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -512,7 +543,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
createLabel(title: string, color: string): Promise<number> {
|
createLabel(title: string, color: string): Promise<number> {
|
||||||
const handler = (success: (labelId: number) => void, reject: (error: ErrorInfo) => void) => {
|
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' }), {
|
.post(`${this.baseUrl}/c/restful/labels`, JSON.stringify({ title, color, iconName: 'smile' }), {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -529,7 +560,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
deleteLabel(id: number): Promise<void> {
|
deleteLabel(id: number): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.delete(`${this.baseUrl}/c/restful/labels/${id}`)
|
.delete(`${this.baseUrl}/c/restful/labels/${id}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
success();
|
success();
|
||||||
@ -544,7 +575,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.post(`${this.baseUrl}/c/restful/maps/${mapId}/labels`, JSON.stringify(labelId), {
|
.post(`${this.baseUrl}/c/restful/maps/${mapId}/labels`, JSON.stringify(labelId), {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
})
|
||||||
@ -561,7 +592,7 @@ export default class RestClient implements Client {
|
|||||||
|
|
||||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||||
axios
|
this.axios
|
||||||
.delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`)
|
.delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
success();
|
success();
|
||||||
@ -574,6 +605,45 @@ export default class RestClient implements Client {
|
|||||||
return new Promise(handler);
|
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
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private parseResponseOnError = (response: any): ErrorInfo => {
|
private parseResponseOnError = (response: any): ErrorInfo => {
|
||||||
console.error("Backend error=>");
|
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 { useSelector } from 'react-redux';
|
||||||
import { hotkeysEnabled } from '../../redux/editorSlice';
|
import { hotkeysEnabled } from '../../redux/editorSlice';
|
||||||
import ReactGA from 'react-ga';
|
import ReactGA from 'react-ga';
|
||||||
|
import Client from '../../classes/client';
|
||||||
|
import { activeInstance } from '../../redux/clientSlice';
|
||||||
|
import { PersistenceManager } from '@wisemapping/mindplot';
|
||||||
|
|
||||||
export type EditorPropsType = {
|
export type EditorPropsType = {
|
||||||
mapId: number;
|
mapId: number;
|
||||||
@ -16,13 +19,24 @@ const EditorPage = ({ mapId, ...props }: EditorPropsType): React.ReactElement =>
|
|||||||
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
|
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
|
||||||
const hotkeys = useSelector(hotkeysEnabled);
|
const hotkeys = useSelector(hotkeysEnabled);
|
||||||
const userLocale = AppI18n.getUserLocale();
|
const userLocale = AppI18n.getUserLocale();
|
||||||
|
const client: Client = useSelector(activeInstance);
|
||||||
|
const [persistenceManager, setPersistenceManager] = React.useState<PersistenceManager>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.pageview(window.location.pathname + window.location.search);
|
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 <>
|
return <>
|
||||||
<Editor {...props} onAction={setActiveDialog} locale={userLocale.code} hotkeys={hotkeys} />
|
<Editor {...props} onAction={setActiveDialog}
|
||||||
|
locale={userLocale.code} hotkeys={hotkeys}
|
||||||
|
persistenceManager={persistenceManager} />
|
||||||
{
|
{
|
||||||
activeDialog &&
|
activeDialog &&
|
||||||
<ActionDispatcher
|
<ActionDispatcher
|
||||||
|
@ -15,6 +15,7 @@ import SubmitButton from '../form/submit-button';
|
|||||||
import ReactGA from 'react-ga';
|
import ReactGA from 'react-ga';
|
||||||
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
|
||||||
|
|
||||||
const ForgotPassword = () => {
|
const ForgotPassword = () => {
|
||||||
const [email, setEmail] = useState<string>('');
|
const [email, setEmail] = useState<string>('');
|
||||||
@ -54,6 +55,7 @@ const ForgotPassword = () => {
|
|||||||
<GlobalError error={error} />
|
<GlobalError error={error} />
|
||||||
|
|
||||||
<form onSubmit={handleOnSubmit}>
|
<form onSubmit={handleOnSubmit}>
|
||||||
|
<input type='hidden' value={getCsrfToken()} name={getCsrfTokenParameter()} />
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
|
@ -11,6 +11,7 @@ import Typography from '@mui/material/Typography';
|
|||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import ReactGA from 'react-ga';
|
import ReactGA from 'react-ga';
|
||||||
|
import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
|
||||||
|
|
||||||
type ConfigStatusProps = {
|
type ConfigStatusProps = {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@ -89,6 +90,7 @@ const LoginPage = (): React.ReactElement => {
|
|||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<form action="/c/perform-login" method="POST">
|
<form action="/c/perform-login" method="POST">
|
||||||
|
<input type='hidden' value={getCsrfToken()} name={getCsrfTokenParameter()}/>
|
||||||
<Input
|
<Input
|
||||||
name="username"
|
name="username"
|
||||||
type="email"
|
type="email"
|
||||||
|
@ -54,7 +54,7 @@ const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElem
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
window.location.href = '/c/logout';
|
window.location.href = '/c/login';
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
|
@ -28,6 +28,12 @@ const AccountMenu = (): React.ReactElement => {
|
|||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogout = (event: MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const elem = document.getElementById('logoutFrom') as HTMLFormElement;
|
||||||
|
elem.submit();
|
||||||
|
};
|
||||||
|
|
||||||
const account = fetchAccount();
|
const account = fetchAccount();
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@ -77,7 +83,8 @@ const AccountMenu = (): React.ReactElement => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<MenuItem onClick={handleClose}>
|
<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>
|
<ListItemIcon>
|
||||||
<ExitToAppOutlined fontSize="small" />
|
<ExitToAppOutlined fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@ -85,11 +92,13 @@ const AccountMenu = (): React.ReactElement => {
|
|||||||
</Link>
|
</Link>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
{action == 'change-password' && (
|
{
|
||||||
<ChangePasswordDialog onClose={() => setAction(undefined)} />
|
action == 'change-password' && (
|
||||||
)}
|
<ChangePasswordDialog onClose={() => setAction(undefined)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
{action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />}
|
{action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />}
|
||||||
</span>
|
</span >
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ import Client, { Label } from '../../classes/client';
|
|||||||
import ActionDispatcher from './action-dispatcher';
|
import ActionDispatcher from './action-dispatcher';
|
||||||
import { ActionType } from './action-chooser';
|
import { ActionType } from './action-chooser';
|
||||||
import AccountMenu from './account-menu';
|
import AccountMenu from './account-menu';
|
||||||
import ClientHealthSentinel from '../../classes/client/client-health-sentinel';
|
|
||||||
import HelpMenu from './help-menu';
|
import HelpMenu from './help-menu';
|
||||||
import LanguageMenu from './language-menu';
|
import LanguageMenu from './language-menu';
|
||||||
import AppI18n, { Locales } from '../../classes/app-i18n';
|
import AppI18n, { Locales } from '../../classes/app-i18n';
|
||||||
@ -153,7 +152,6 @@ const MapsPage = (): ReactElement => {
|
|||||||
messages={userLocale.message}
|
messages={userLocale.message}
|
||||||
>
|
>
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<ClientHealthSentinel />
|
|
||||||
<AppBar
|
<AppBar
|
||||||
position="fixed"
|
position="fixed"
|
||||||
className={clsx(classes.appBar, {
|
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)$/,
|
test: /\.(png|jpe?g|gif|svg)$/,
|
||||||
type: 'asset/inline',
|
type: 'asset/inline',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.wxml$/i,
|
||||||
|
type: 'asset/source',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
|
Loading…
Reference in New Issue
Block a user