Merged in feat/prettier (pull request #3)

adding back semicolons

* adding back semicolons
This commit is contained in:
Juan Allo 2021-02-23 07:02:15 +00:00
parent b63d7ee665
commit 3d538923a9
72 changed files with 1792 additions and 1792 deletions

View File

@ -1,7 +1,7 @@
{ {
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": false, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 100 "printWidth": 100
} }

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react';
import { StyledCanvas } from './styled' import { StyledCanvas } from './styled';
const Canvas = (): React.ReactElement => <StyledCanvas>canvas</StyledCanvas> const Canvas = (): React.ReactElement => <StyledCanvas>canvas</StyledCanvas>;
export default Canvas export default Canvas;

View File

@ -1,8 +1,8 @@
import styled from 'styled-components' import styled from 'styled-components';
export const StyledCanvas = styled.div` export const StyledCanvas = styled.div`
height: 100% height: 100%
width: 100%; width: 100%;
flex: 1; flex: 1;
` `;

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react';
import { StyledFooter } from './styled' import { StyledFooter } from './styled';
const Footer = (): React.ReactElement => <StyledFooter>footer</StyledFooter> const Footer = (): React.ReactElement => <StyledFooter>footer</StyledFooter>;
export default Footer export default Footer;

View File

@ -1,8 +1,8 @@
import styled from 'styled-components' import styled from 'styled-components';
import { times } from '../../size' import { times } from '../../size';
export const StyledFooter = styled.div` export const StyledFooter = styled.div`
height: ${times(10)}; height: ${times(10)};
width: 100%; width: 100%;
border: 1px solid black; border: 1px solid black;
` `;

View File

@ -1,8 +1,8 @@
import React from 'react' import React from 'react';
import Footer from '../footer' import Footer from '../footer';
import TopBar from '../top-bar' import TopBar from '../top-bar';
import Canvas from '../canvas' import Canvas from '../canvas';
import { StyledFrame } from './styled' import { StyledFrame } from './styled';
const Frame = (): React.ReactElement => ( const Frame = (): React.ReactElement => (
<StyledFrame> <StyledFrame>
@ -10,6 +10,6 @@ const Frame = (): React.ReactElement => (
<Canvas /> <Canvas />
<Footer /> <Footer />
</StyledFrame> </StyledFrame>
) );
export default Frame export default Frame;

View File

@ -1,8 +1,8 @@
import styled from 'styled-components' import styled from 'styled-components';
export const StyledFrame = styled.div` export const StyledFrame = styled.div`
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
` `;

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react';
import { StyledTopBar } from './styled' import { StyledTopBar } from './styled';
const TopBar = (): React.ReactElement => <StyledTopBar>top bar</StyledTopBar> const TopBar = (): React.ReactElement => <StyledTopBar>top bar</StyledTopBar>;
export default TopBar export default TopBar;

View File

@ -1,8 +1,8 @@
import styled from 'styled-components' import styled from 'styled-components';
import { times } from '../../size' import { times } from '../../size';
export const StyledTopBar = styled.div` export const StyledTopBar = styled.div`
height: ${times(10)}; height: ${times(10)};
width: 100%; width: 100%;
border: 1px solid black; border: 1px solid black;
` `;

View File

@ -1,3 +1,3 @@
import Editor from './components/frame' import Editor from './components/frame';
export default Editor export default Editor;

View File

@ -1,9 +1,9 @@
const unit = 4 // pixels const unit = 4; // pixels
export const XS = '4px' export const XS = '4px';
export const S = '8px' export const S = '8px';
export const M = '16px' export const M = '16px';
export const L = '24px' export const L = '24px';
export const XL = '24px' export const XL = '24px';
export const times = (n: number): string => `${unit * n}px` export const times = (n: number): string => `${unit * n}px`;

View File

@ -1,16 +1,16 @@
import MapsPage from '../pageObject/MapsPage' import MapsPage from '../pageObject/MapsPage';
context('Maps Page', () => { context('Maps Page', () => {
beforeEach(() => { beforeEach(() => {
cy.visit('http://localhost:3000/c/maps') cy.visit('http://localhost:3000/c/maps');
}) });
it('should load the maps page', () => { it('should load the maps page', () => {
MapsPage.isLoaded() MapsPage.isLoaded();
}) });
it('should open the create dialog', () => { it('should open the create dialog', () => {
MapsPage.create() MapsPage.create();
MapsPage.isCreateDialogVisible() MapsPage.isCreateDialogVisible();
}) });
}) });

View File

@ -1,14 +1,14 @@
export default class MapsPage { export default class MapsPage {
static isLoaded() { static isLoaded() {
return cy.findByTestId('create') return cy.findByTestId('create');
} }
static create() { static create() {
return cy.findByTestId('create').click() return cy.findByTestId('create').click();
} }
static isCreateDialogVisible() { static isCreateDialogVisible() {
//TODO move to findByText when the double create dialog issue is solved //TODO move to findByText when the double create dialog issue is solved
return cy.findAllByText('Create a new mindmap') return cy.findAllByText('Create a new mindmap');
} }
} }

View File

@ -4,4 +4,4 @@
module.exports = (on, config) => { module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits // `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config // `config` is the resolved Cypress config
} };

View File

@ -1 +1 @@
import '@testing-library/cypress/add-commands' import '@testing-library/cypress/add-commands';

View File

@ -1 +1 @@
import './commands' import './commands';

View File

@ -1,2 +1,2 @@
declare module '*.png' declare module '*.png';
declare module '*.svg' declare module '*.svg';

View File

@ -1,19 +1,19 @@
declare module '*.jpeg' declare module '*.jpeg';
declare module '*.jpg' declare module '*.jpg';
declare module '*.jpeg' declare module '*.jpeg';
declare module '*.png' declare module '*.png';
declare module '*.svg' declare module '*.svg';
declare module '*.json' declare module '*.json';
import { Dayjs } from 'dayjs' import { Dayjs } from 'dayjs';
type DateType = string | number | Date | Dayjs type DateType = string | number | Date | Dayjs;
// @Todo: review if there is a better support for this. // @Todo: review if there is a better support for this.
declare module 'dayjs' { declare module 'dayjs' {
interface Dayjs { interface Dayjs {
fromNow(withoutSuffix?: boolean): string fromNow(withoutSuffix?: boolean): string;
from(compared: DateType, withoutSuffix?: boolean): string from(compared: DateType, withoutSuffix?: boolean): string;
toNow(withoutSuffix?: boolean): string toNow(withoutSuffix?: boolean): string;
to(compared: DateType, withoutSuffix?: boolean): string to(compared: DateType, withoutSuffix?: boolean): string;
} }
} }

View File

@ -1,20 +1,20 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react';
import { IntlProvider } from 'react-intl' import { IntlProvider } from 'react-intl';
import { Route, Switch, Redirect, BrowserRouter as Router } from 'react-router-dom' import { Route, Switch, Redirect, BrowserRouter as Router } from 'react-router-dom';
import RegistrationSuccessPage from './components/registration-success-page' import RegistrationSuccessPage from './components/registration-success-page';
import ForgotPasswordSuccessPage from './components/forgot-password-success-page' import ForgotPasswordSuccessPage from './components/forgot-password-success-page';
import RegistationPage from './components/registration-page' import RegistationPage from './components/registration-page';
import LoginPage from './components/login-page' import LoginPage from './components/login-page';
import store from './redux/store' import store from './redux/store';
import { ForgotPasswordPage } from './components/forgot-password-page' import { ForgotPasswordPage } from './components/forgot-password-page';
import { Provider } from 'react-redux' import { Provider } from 'react-redux';
import { QueryClient, QueryClientProvider } from 'react-query' import { QueryClient, QueryClientProvider } from 'react-query';
import { theme } from './theme' import { theme } from './theme';
import AppI18n, { Locales } from './classes/app-i18n' import AppI18n, { Locales } from './classes/app-i18n';
import MapsPage from './components/maps-page' import MapsPage from './components/maps-page';
import CssBaseline from '@material-ui/core/CssBaseline' import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/core/styles' import { ThemeProvider } from '@material-ui/core/styles';
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@ -23,11 +23,11 @@ const queryClient = new QueryClient({
staleTime: 5 * 1000 * 60, // 10 minutes staleTime: 5 * 1000 * 60, // 10 minutes
}, },
}, },
}) });
const App = (): ReactElement => { const App = (): ReactElement => {
const appi18n = new AppI18n() const appi18n = new AppI18n();
const locale = appi18n.getBrowserLocale() const locale = appi18n.getBrowserLocale();
return locale.message ? ( return locale.message ? (
<Provider store={store}> <Provider store={store}>
@ -70,7 +70,7 @@ const App = (): ReactElement => {
</Provider> </Provider>
) : ( ) : (
<div>Loading ... </div> <div>Loading ... </div>
) );
} };
export default App export default App;

View File

@ -1,60 +1,60 @@
import { fetchAccount } from './../../redux/clientSlice' import { fetchAccount } from './../../redux/clientSlice';
import 'dayjs/locale/fr' import 'dayjs/locale/fr';
import 'dayjs/locale/en' import 'dayjs/locale/en';
import 'dayjs/locale/es' import 'dayjs/locale/es';
export class Locale { export class Locale {
code: LocaleCode code: LocaleCode;
label: string label: string;
message: Record<string, string> message: Record<string, string>;
constructor(code: LocaleCode, label: string, message: unknown) { constructor(code: LocaleCode, label: string, message: unknown) {
this.code = code this.code = code;
this.label = label this.label = label;
this.message = message as Record<string, string> this.message = message as Record<string, string>;
} }
} }
export default class AppI18n { export default class AppI18n {
public getUserLocale(): Locale { public getUserLocale(): Locale {
const account = fetchAccount() const account = fetchAccount();
return account ? account.locale : this.getBrowserLocale() return account ? account.locale : this.getBrowserLocale();
} }
public getBrowserLocale(): Locale { public getBrowserLocale(): Locale {
let localeCode = (navigator.languages && navigator.languages[0]) || navigator.language let localeCode = (navigator.languages && navigator.languages[0]) || navigator.language;
// Just remove the variant ... // Just remove the variant ...
localeCode = localeCode.split('-')[0] localeCode = localeCode.split('-')[0];
let result = Locales.EN let result = Locales.EN;
try { try {
result = localeFromStr(localeCode) result = localeFromStr(localeCode);
} catch { } catch {
console.warn(`Unsupported languange code ${localeCode}`) console.warn(`Unsupported languange code ${localeCode}`);
} }
return result return result;
} }
} }
export type LocaleCode = 'en' | 'es' | 'fr' | 'de' export type LocaleCode = 'en' | 'es' | 'fr' | 'de';
export const Locales = { export const Locales = {
EN: new Locale('en', 'English', require('./../../compiled-lang/en.json')), EN: new Locale('en', 'English', require('./../../compiled-lang/en.json')),
ES: new Locale('es', 'Español', require('./../../compiled-lang/es.json')), ES: new Locale('es', 'Español', require('./../../compiled-lang/es.json')),
DE: new Locale('fr', 'Français', require('./../../compiled-lang/fr.json')), DE: new Locale('fr', 'Français', require('./../../compiled-lang/fr.json')),
FR: new Locale('de', 'Deutsch', require('./../../compiled-lang/de.json')), FR: new Locale('de', 'Deutsch', require('./../../compiled-lang/de.json')),
} };
export const localeFromStr = (code: string): Locale => { export const localeFromStr = (code: string): Locale => {
const locales: Locale[] = Object.values(Locales) const locales: Locale[] = Object.values(Locales);
const result = locales.find((l) => l.code == code) const result = locales.find((l) => l.code == code);
if (!result) { if (!result) {
throw `Language code could not be found in list of default supported: + ${code}` throw `Language code could not be found in list of default supported: + ${code}`;
} }
return result return result;
} };

View File

@ -1,21 +1,21 @@
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import React from 'react' import React from 'react';
import { activeInstanceStatus, ClientStatus } from '../../../redux/clientSlice' import { activeInstanceStatus, ClientStatus } from '../../../redux/clientSlice';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog' import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle' import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent' import DialogContent from '@material-ui/core/DialogContent';
import Alert from '@material-ui/lab/Alert' import Alert from '@material-ui/lab/Alert';
import DialogActions from '@material-ui/core/DialogActions' import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import AlertTitle from '@material-ui/lab/AlertTitle' import AlertTitle from '@material-ui/lab/AlertTitle';
const ClientHealthSentinel = (): React.ReactElement => { const ClientHealthSentinel = (): React.ReactElement => {
const status: ClientStatus = useSelector(activeInstanceStatus) const status: ClientStatus = useSelector(activeInstanceStatus);
const handleOnClose = () => { const handleOnClose = () => {
window.location.href = '/c/login' window.location.href = '/c/login';
} };
return ( return (
<div> <div>
@ -50,6 +50,6 @@ const ClientHealthSentinel = (): React.ReactElement => {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</div> </div>
) );
} };
export default ClientHealthSentinel export default ClientHealthSentinel;

View File

@ -1,108 +1,108 @@
import { Locale, LocaleCode } from '../app-i18n' import { Locale, LocaleCode } from '../app-i18n';
export type NewUser = { export type NewUser = {
email: string email: string;
firstname: string firstname: string;
lastname: string lastname: string;
password: string password: string;
recaptcha: string | null recaptcha: string | null;
} };
export type ImportMapInfo = { export type ImportMapInfo = {
title: string title: string;
description?: string description?: string;
contentType?: string contentType?: string;
content?: ArrayBuffer | null | string content?: ArrayBuffer | null | string;
} };
export type Label = { export type Label = {
id: number id: number;
title: string title: string;
color: string color: string;
iconName: string iconName: string;
} };
export type Role = 'owner' | 'editor' | 'viewer' export type Role = 'owner' | 'editor' | 'viewer';
export type MapInfo = { export type MapInfo = {
id: number id: number;
starred: boolean starred: boolean;
title: string title: string;
labels: number[] labels: number[];
createdBy: string createdBy: string;
creationTime: string creationTime: string;
lastModificationBy: string lastModificationBy: string;
lastModificationTime: string lastModificationTime: string;
description: string description: string;
isPublic: boolean isPublic: boolean;
role: Role role: Role;
} };
export type ChangeHistory = { export type ChangeHistory = {
id: number id: number;
lastModificationBy: string lastModificationBy: string;
lastModificationTime: string lastModificationTime: string;
} };
export type BasicMapInfo = { export type BasicMapInfo = {
title: string title: string;
description?: string description?: string;
} };
export type FieldError = { export type FieldError = {
id: string id: string;
msg: string msg: string;
} };
export type ErrorInfo = { export type ErrorInfo = {
msg?: string msg?: string;
fields?: Map<string, string> fields?: Map<string, string>;
} };
export type AccountInfo = { export type AccountInfo = {
firstname: string firstname: string;
lastname: string lastname: string;
email: string email: string;
locale: Locale locale: Locale;
} };
export type Permission = { export type Permission = {
name?: string name?: string;
email: string email: string;
role: Role role: Role;
} };
interface Client { interface Client {
deleteAccount(): Promise<void> deleteAccount(): Promise<void>;
importMap(model: ImportMapInfo): Promise<number> importMap(model: ImportMapInfo): Promise<number>;
createMap(map: BasicMapInfo): Promise<number> createMap(map: BasicMapInfo): Promise<number>;
deleteMaps(ids: number[]): Promise<void> deleteMaps(ids: number[]): Promise<void>;
deleteMap(id: number): Promise<void> deleteMap(id: number): Promise<void>;
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> renameMap(id: number, basicInfo: BasicMapInfo): Promise<void>;
fetchAllMaps(): Promise<MapInfo[]> fetchAllMaps(): Promise<MapInfo[]>;
fetchMapPermissions(id: number): Promise<Permission[]> fetchMapPermissions(id: number): Promise<Permission[]>;
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void>;
deleteMapPermission(id: number, email: string): Promise<void> deleteMapPermission(id: number, email: string): Promise<void>;
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number>;
updateAccountLanguage(locale: LocaleCode): Promise<void> updateAccountLanguage(locale: LocaleCode): Promise<void>;
updateAccountPassword(pasword: string): Promise<void> updateAccountPassword(pasword: string): Promise<void>;
updateAccountInfo(firstname: string, lastname: string): Promise<void> updateAccountInfo(firstname: string, lastname: string): Promise<void>;
updateStarred(id: number, starred: boolean): Promise<void> updateStarred(id: number, starred: boolean): Promise<void>;
updateMapToPublic(id: number, starred: boolean): Promise<void> updateMapToPublic(id: number, starred: boolean): Promise<void>;
fetchLabels(): Promise<Label[]> fetchLabels(): Promise<Label[]>;
deleteLabel(id: number): Promise<void> deleteLabel(id: number): Promise<void>;
fetchAccountInfo(): Promise<AccountInfo> fetchAccountInfo(): Promise<AccountInfo>;
registerNewUser(user: NewUser): Promise<void> registerNewUser(user: NewUser): Promise<void>;
resetPassword(email: string): Promise<void> resetPassword(email: string): Promise<void>;
fetchHistory(id: number): Promise<ChangeHistory[]> fetchHistory(id: number): Promise<ChangeHistory[]>;
revertHistory(id: number, cid: number): Promise<void> revertHistory(id: number, cid: number): Promise<void>;
} }
export default Client export default Client;

View File

@ -7,13 +7,13 @@ import Client, {
MapInfo, MapInfo,
NewUser, NewUser,
Permission, Permission,
} from '..' } from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n' import { LocaleCode, localeFromStr } from '../../app-i18n';
class MockClient implements Client { 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();
constructor() { constructor() {
// Remove, just for develop .... // Remove, just for develop ....
@ -42,7 +42,7 @@ class MockClient implements Client {
description, description,
isPublic, isPublic,
role, role,
} };
} }
this.maps = [ this.maps = [
@ -85,31 +85,31 @@ class MockClient implements Client {
false, false,
'editor' 'editor'
), ),
] ];
this.labels = [ this.labels = [
{ id: 1, title: 'Red Label', iconName: '', color: 'red' }, { id: 1, title: 'Red Label', iconName: '', color: 'red' },
{ id: 2, title: 'Blue Label', iconName: '', color: 'blue' }, { id: 2, title: 'Blue Label', iconName: '', color: 'blue' },
] ];
} }
deleteMapPermission(id: number, email: string): Promise<void> { deleteMapPermission(id: number, email: string): Promise<void> {
let perm = this.permissionsByMap.get(id) || [] let perm = this.permissionsByMap.get(id) || [];
perm = perm.filter((p) => p.email != email) perm = perm.filter((p) => p.email != email);
this.permissionsByMap.set(id, perm) this.permissionsByMap.set(id, perm);
return Promise.resolve() return Promise.resolve();
} }
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> { addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
let perm = this.permissionsByMap.get(id) || [] let perm = this.permissionsByMap.get(id) || [];
perm = perm.concat(permissions) perm = perm.concat(permissions);
this.permissionsByMap.set(id, perm) this.permissionsByMap.set(id, perm);
console.log(`Message ${message}`) console.log(`Message ${message}`);
return Promise.resolve() return Promise.resolve();
} }
fetchMapPermissions(id: number): Promise<Permission[]> { fetchMapPermissions(id: number): Promise<Permission[]> {
let perm = this.permissionsByMap.get(id) let perm = this.permissionsByMap.get(id);
if (!perm) { if (!perm) {
perm = [ perm = [
{ {
@ -127,107 +127,107 @@ class MockClient implements Client {
email: 'pepe3@example.com', email: 'pepe3@example.com',
role: 'viewer', role: 'viewer',
}, },
] ];
this.permissionsByMap.set(id, perm) this.permissionsByMap.set(id, perm);
} }
return Promise.resolve(perm) return Promise.resolve(perm);
} }
deleteAccount(): Promise<void> { deleteAccount(): Promise<void> {
return Promise.resolve() return Promise.resolve();
} }
updateAccountInfo(firstname: string, lastname: string): Promise<void> { updateAccountInfo(firstname: string, lastname: string): Promise<void> {
console.log('firstname:' + firstname, +lastname) console.log('firstname:' + firstname, +lastname);
return Promise.resolve() return Promise.resolve();
} }
updateAccountPassword(pasword: string): Promise<void> { updateAccountPassword(pasword: string): Promise<void> {
console.log('password:' + pasword) console.log('password:' + pasword);
return Promise.resolve() return Promise.resolve();
} }
updateAccountLanguage(locale: LocaleCode): Promise<void> { updateAccountLanguage(locale: LocaleCode): Promise<void> {
localStorage.setItem('locale', locale) localStorage.setItem('locale', locale);
return Promise.resolve() return Promise.resolve();
} }
importMap(model: ImportMapInfo): Promise<number> { importMap(model: ImportMapInfo): Promise<number> {
console.log('model:' + model) console.log('model:' + model);
return Promise.resolve(10) return Promise.resolve(10);
} }
fetchAccountInfo(): Promise<AccountInfo> { fetchAccountInfo(): Promise<AccountInfo> {
console.log('Fetch account info ...') console.log('Fetch account info ...');
const locale: LocaleCode | null = localStorage.getItem('locale') as LocaleCode const locale: LocaleCode | null = localStorage.getItem('locale') as LocaleCode;
return Promise.resolve({ return Promise.resolve({
firstname: 'Costme', firstname: 'Costme',
lastname: 'Fulanito', lastname: 'Fulanito',
email: 'test@example.com', email: 'test@example.com',
locale: localeFromStr(locale), locale: localeFromStr(locale),
}) });
} }
deleteMaps(ids: number[]): Promise<void> { deleteMaps(ids: number[]): Promise<void> {
ids.forEach((id) => this.deleteMap(id)) ids.forEach((id) => this.deleteMap(id));
return Promise.resolve() return Promise.resolve();
} }
revertHistory(id: number, cid: number): Promise<void> { revertHistory(id: number, cid: number): Promise<void> {
console.log('model:' + id + cid) console.log('model:' + id + cid);
return Promise.resolve() return Promise.resolve();
} }
createMap(map: BasicMapInfo): Promise<number> { createMap(map: BasicMapInfo): Promise<number> {
throw new Error('Method not implemented.' + map) throw new Error('Method not implemented.' + map);
} }
fetchLabels(): Promise<Label[]> { fetchLabels(): Promise<Label[]> {
console.log('Fetching labels from server') console.log('Fetching labels from server');
return Promise.resolve(this.labels) return Promise.resolve(this.labels);
} }
updateMapToPublic(id: number, isPublic: boolean): Promise<void> { updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
const mapInfo = this.maps.find((m) => m.id == id) const mapInfo = this.maps.find((m) => m.id == id);
if (mapInfo) { if (mapInfo) {
mapInfo.isPublic = isPublic mapInfo.isPublic = isPublic;
} }
return Promise.resolve() return Promise.resolve();
} }
updateStarred(id: number, starred: boolean): Promise<void> { updateStarred(id: number, starred: boolean): Promise<void> {
const mapInfo = this.maps.find((m) => m.id == id) const mapInfo = this.maps.find((m) => m.id == id);
if (!mapInfo) { if (!mapInfo) {
console.log(`Could not find the map iwth id ${id}`) console.log(`Could not find the map iwth id ${id}`);
return Promise.reject() return Promise.reject();
} }
mapInfo.starred = starred mapInfo.starred = starred;
return Promise.resolve() return Promise.resolve();
} }
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> { renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> {
const exists = this.maps.find((m) => m.title == basicInfo.title) != undefined const exists = this.maps.find((m) => m.title == basicInfo.title) != undefined;
if (!exists) { if (!exists) {
this.maps = this.maps.map((m) => { this.maps = this.maps.map((m) => {
const result = m const result = m;
if (m.id == id) { if (m.id == id) {
result.description = basicInfo.description ? basicInfo.description : '' result.description = basicInfo.description ? basicInfo.description : '';
result.title = basicInfo.title result.title = basicInfo.title;
} }
return result return result;
}) });
return Promise.resolve() return Promise.resolve();
} else { } else {
const fieldErrors: Map<string, string> = new Map<string, string>() const fieldErrors: Map<string, string> = new Map<string, string>();
fieldErrors.set('name', 'name already exists ') fieldErrors.set('name', 'name already exists ');
return Promise.reject({ return Promise.reject({
msg: 'Map already exists ...' + basicInfo.title, msg: 'Map already exists ...' + basicInfo.title,
fields: fieldErrors, fields: fieldErrors,
}) });
} }
} }
fetchHistory(id: number): Promise<ChangeHistory[]> { fetchHistory(id: number): Promise<ChangeHistory[]> {
console.log(`Fetching history for ${id}`) console.log(`Fetching history for ${id}`);
const result = [ const result = [
{ {
id: 1, id: 1,
@ -264,12 +264,12 @@ class MockClient implements Client {
lastModificationBy: 'Paulo', lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z', lastModificationTime: '2008-06-02T00:00:00Z',
}, },
] ];
return Promise.resolve(result) return Promise.resolve(result);
} }
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> { duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
const exists = this.maps.find((m) => m.title == basicInfo.title) != undefined const exists = this.maps.find((m) => m.title == basicInfo.title) != undefined;
if (!exists) { if (!exists) {
const newMap: MapInfo = { const newMap: MapInfo = {
id: Math.random() * 1000, id: Math.random() * 1000,
@ -283,45 +283,45 @@ class MockClient implements Client {
creationTime: '2008-06-02T00:00:00Z', creationTime: '2008-06-02T00:00:00Z',
isPublic: false, isPublic: false,
role: 'owner', role: 'owner',
} };
this.maps.push(newMap) this.maps.push(newMap);
return Promise.resolve(newMap.id) return Promise.resolve(newMap.id);
} else { } else {
const fieldErrors: Map<string, string> = new Map<string, string>() const fieldErrors: Map<string, string> = new Map<string, string>();
fieldErrors.set('name', 'name already exists ') fieldErrors.set('name', 'name already exists ');
return Promise.reject({ return Promise.reject({
msg: 'Maps name must be unique:' + basicInfo.title, msg: 'Maps name must be unique:' + basicInfo.title,
fields: fieldErrors, fields: fieldErrors,
}) });
} }
} }
deleteLabel(id: number): Promise<void> { deleteLabel(id: number): Promise<void> {
this.labels = this.labels.filter((l) => l.id != id) this.labels = this.labels.filter((l) => l.id != id);
console.log('Label delete:' + this.labels) console.log('Label delete:' + this.labels);
return Promise.resolve() return Promise.resolve();
} }
deleteMap(id: number): Promise<void> { deleteMap(id: number): Promise<void> {
this.maps = this.maps.filter((m) => m.id != id) this.maps = this.maps.filter((m) => m.id != id);
return Promise.resolve() return Promise.resolve();
} }
registerNewUser(user: NewUser): Promise<void> { registerNewUser(user: NewUser): Promise<void> {
console.log('user:' + user) console.log('user:' + user);
return Promise.resolve() return Promise.resolve();
} }
fetchAllMaps(): Promise<MapInfo[]> { fetchAllMaps(): Promise<MapInfo[]> {
console.log('Fetching maps from server') console.log('Fetching maps from server');
return Promise.resolve(this.maps) return Promise.resolve(this.maps);
} }
resetPassword(email: string): Promise<void> { resetPassword(email: string): Promise<void> {
console.log('email:' + email) console.log('email:' + email);
return Promise.resolve() return Promise.resolve();
} }
} }
export default MockClient export default MockClient;

View File

@ -1,4 +1,4 @@
import axios from 'axios' import axios from 'axios';
import Client, { import Client, {
ErrorInfo, ErrorInfo,
MapInfo, MapInfo,
@ -9,16 +9,16 @@ import Client, {
AccountInfo, AccountInfo,
ImportMapInfo, ImportMapInfo,
Permission, Permission,
} from '..' } from '..';
import { LocaleCode, localeFromStr, Locales } from '../../app-i18n' import { LocaleCode, localeFromStr, Locales } 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 sessionExpired: () => void;
constructor(baseUrl: string, sessionExpired: () => void) { constructor(baseUrl: string, sessionExpired: () => void) {
this.baseUrl = baseUrl this.baseUrl = baseUrl;
this.sessionExpired = sessionExpired this.sessionExpired = sessionExpired;
} }
deleteMapPermission(id: number, email: string): Promise<void> { deleteMapPermission(id: number, email: string): Promise<void> {
@ -28,14 +28,14 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> { addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
@ -51,14 +51,14 @@ export default class RestClient implements Client {
) )
.then(() => { .then(() => {
// All was ok, let's sent to success page ...; // All was ok, let's sent to success page ...;
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
fetchMapPermissions(id: number): Promise<Permission[]> { fetchMapPermissions(id: number): Promise<Permission[]> {
@ -71,7 +71,7 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then((response) => { .then((response) => {
const data = response.data const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const perms: Permission[] = (data.collaborations as any[]).map((p) => { const perms: Permission[] = (data.collaborations as any[]).map((p) => {
return { return {
@ -79,16 +79,16 @@ export default class RestClient implements Client {
email: p.email, email: p.email,
name: p.name, name: p.name,
role: p.role, role: p.role,
} };
}) });
success(perms) success(perms);
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
deleteAccount(): Promise<void> { deleteAccount(): Promise<void> {
@ -98,14 +98,14 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
updateAccountInfo(firstname: string, lastname: string): Promise<void> { updateAccountInfo(firstname: string, lastname: string): Promise<void> {
@ -117,18 +117,18 @@ export default class RestClient implements Client {
.then(() => { .then(() => {
return axios.put(`${this.baseUrl}/c/restful/account/lastname`, lastname, { return axios.put(`${this.baseUrl}/c/restful/account/lastname`, lastname, {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) });
}) })
.then(() => { .then(() => {
// All was ok, let's sent to success page ...; // All was ok, let's sent to success page ...;
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
updateAccountPassword(pasword: string): Promise<void> { updateAccountPassword(pasword: string): Promise<void> {
@ -138,14 +138,14 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
updateAccountLanguage(locale: LocaleCode): Promise<void> { updateAccountLanguage(locale: LocaleCode): Promise<void> {
@ -156,14 +156,14 @@ export default class RestClient implements Client {
}) })
.then(() => { .then(() => {
// All was ok, let's sent to success page ...; // All was ok, let's sent to success page ...;
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
importMap(model: ImportMapInfo): Promise<number> { importMap(model: ImportMapInfo): Promise<number> {
@ -177,15 +177,15 @@ export default class RestClient implements Client {
{ headers: { 'Content-Type': model.contentType } } { headers: { 'Content-Type': model.contentType } }
) )
.then((response) => { .then((response) => {
const mapId = response.headers.resourceid const mapId = response.headers.resourceid;
success(mapId) success(mapId);
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
fetchAccountInfo(): Promise<AccountInfo> { fetchAccountInfo(): Promise<AccountInfo> {
@ -198,21 +198,21 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
const account = response.data const account = response.data;
const locale: LocaleCode | null = account.locale const locale: LocaleCode | null = account.locale;
success({ success({
lastname: account.lastname ? account.lastname : '', lastname: account.lastname ? account.lastname : '',
firstname: account.firstname ? account.firstname : '', firstname: account.firstname ? account.firstname : '',
email: account.email, email: account.email,
locale: locale ? localeFromStr(locale) : Locales.EN, locale: locale ? localeFromStr(locale) : Locales.EN,
}) });
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
deleteMaps(ids: number[]): Promise<void> { deleteMaps(ids: number[]): Promise<void> {
@ -222,15 +222,15 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const response = error.response const response = error.response;
const errorInfo = this.parseResponseOnError(response) const errorInfo = this.parseResponseOnError(response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
updateMapToPublic(id: number, isPublic: boolean): Promise<void> { updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
@ -241,14 +241,14 @@ export default class RestClient implements Client {
}) })
.then(() => { .then(() => {
// All was ok, let's sent to success page ...; // All was ok, let's sent to success page ...;
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
revertHistory(id: number, hid: number): Promise<void> { revertHistory(id: number, hid: number): Promise<void> {
@ -258,18 +258,18 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'text/pain' }, headers: { 'Content-Type': 'text/pain' },
}) })
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
fetchHistory(id: number): Promise<ChangeHistory[]> { fetchHistory(id: number): Promise<ChangeHistory[]> {
throw new Error(`Method not implemented. ${id}`) throw new Error(`Method not implemented. ${id}`);
} }
renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> { renameMap(id: number, basicInfo: BasicMapInfo): Promise<void> {
@ -283,19 +283,19 @@ export default class RestClient implements Client {
`${this.baseUrl}/c/restful/maps/${id}/description`, `${this.baseUrl}/c/restful/maps/${id}/description`,
basicInfo.description, basicInfo.description,
{ headers: { 'Content-Type': 'text/plain' } } { headers: { 'Content-Type': 'text/plain' } }
) );
}) })
.then(() => { .then(() => {
// All was ok, let's sent to success page ...; // All was ok, let's sent to success page ...;
success() success();
}) })
.catch((error) => { .catch((error) => {
const response = error.response const response = error.response;
const errorInfo = this.parseResponseOnError(response) const errorInfo = this.parseResponseOnError(response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
createMap(model: BasicMapInfo): Promise<number> { createMap(model: BasicMapInfo): Promise<number> {
@ -309,15 +309,15 @@ export default class RestClient implements Client {
{ headers: { 'Content-Type': 'application/json' } } { headers: { 'Content-Type': 'application/json' } }
) )
.then((response) => { .then((response) => {
const mapId = response.headers.resourceid const mapId = response.headers.resourceid;
success(mapId) success(mapId);
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
fetchAllMaps(): Promise<MapInfo[]> { fetchAllMaps(): Promise<MapInfo[]> {
@ -330,7 +330,7 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
const data = response.data const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: MapInfo[] = (data.mindmapsInfo as any[]).map((m) => { const maps: MapInfo[] = (data.mindmapsInfo as any[]).map((m) => {
return { return {
@ -345,20 +345,20 @@ export default class RestClient implements Client {
description: m.description, description: m.description,
isPublic: m['public'], isPublic: m['public'],
role: m.role, role: m.role,
} };
}) });
success(maps) success(maps);
}) })
.catch((error) => { .catch((error) => {
console.log('Maps List Error=>') console.log('Maps List Error=>');
console.log(error) console.log(error);
const response = error.response const response = error.response;
const errorInfo = this.parseResponseOnError(response) const errorInfo = this.parseResponseOnError(response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
registerNewUser(user: NewUser): Promise<void> { registerNewUser(user: NewUser): Promise<void> {
@ -369,16 +369,16 @@ export default class RestClient implements Client {
}) })
.then(() => { .then(() => {
// All was ok, let's sent to success page ...; // All was ok, let's sent to success page ...;
success() success();
}) })
.catch((error) => { .catch((error) => {
console.log(error) console.log(error);
const response = error.response const response = error.response;
const errorInfo = this.parseResponseOnError(response) const errorInfo = this.parseResponseOnError(response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
deleteMap(id: number): Promise<void> { deleteMap(id: number): Promise<void> {
@ -388,14 +388,14 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
resetPassword(email: string): Promise<void> { resetPassword(email: string): Promise<void> {
@ -406,15 +406,15 @@ export default class RestClient implements Client {
}) })
.then(() => { .then(() => {
// All was ok, let's sent to success page ...; // All was ok, let's sent to success page ...;
success() success();
}) })
.catch((error) => { .catch((error) => {
const response = error.response const response = error.response;
const errorInfo = this.parseResponseOnError(response) const errorInfo = this.parseResponseOnError(response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> { duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
@ -424,16 +424,16 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
const mapId = response.headers.resourceid const mapId = response.headers.resourceid;
success(mapId) success(mapId);
}) })
.catch((error) => { .catch((error) => {
const response = error.response const response = error.response;
const errorInfo = this.parseResponseOnError(response) const errorInfo = this.parseResponseOnError(response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
updateStarred(id: number, starred: boolean): Promise<void> { updateStarred(id: number, starred: boolean): Promise<void> {
@ -443,15 +443,15 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'text/plain' }, headers: { 'Content-Type': 'text/plain' },
}) })
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const response = error.response const response = error.response;
const errorInfo = this.parseResponseOnError(response) const errorInfo = this.parseResponseOnError(response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
fetchLabels(): Promise<Label[]> { fetchLabels(): Promise<Label[]> {
@ -464,7 +464,7 @@ export default class RestClient implements Client {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}) })
.then((response) => { .then((response) => {
const data = response.data const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: Label[] = (data.labels as any[]).map((l) => { const maps: Label[] = (data.labels as any[]).map((l) => {
return { return {
@ -472,16 +472,16 @@ export default class RestClient implements Client {
color: l.color, color: l.color,
title: l.title, title: l.title,
iconName: l.iconName, iconName: l.iconName,
} };
}) });
success(maps) success(maps);
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
deleteLabel(id: number): Promise<void> { deleteLabel(id: number): Promise<void> {
@ -489,60 +489,60 @@ export default class RestClient implements Client {
axios axios
.delete(`${this.baseUrl}/c/restful/label/${id}`) .delete(`${this.baseUrl}/c/restful/label/${id}`)
.then(() => { .then(() => {
success() success();
}) })
.catch((error) => { .catch((error) => {
const errorInfo = this.parseResponseOnError(error.response) const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo) reject(errorInfo);
}) });
} };
return new Promise(handler) return new Promise(handler);
} }
// 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 => {
let result: ErrorInfo | undefined let result: ErrorInfo | undefined;
if (response) { if (response) {
const status: number = response.status const status: number = response.status;
const data = response.data const data = response.data;
console.log(data) console.log(data);
switch (status) { switch (status) {
case 401: case 401:
case 302: case 302:
this.sessionExpired() this.sessionExpired();
result = { result = {
msg: 'Your current session has expired. Please, sign in and try again.', msg: 'Your current session has expired. Please, sign in and try again.',
} };
break break;
default: default:
if (data) { if (data) {
// Set global errors ... // Set global errors ...
result = {} result = {};
const globalErrors = data.globalErrors const globalErrors = data.globalErrors;
if (globalErrors && globalErrors.length > 0) { if (globalErrors && globalErrors.length > 0) {
result.msg = globalErrors[0] result.msg = globalErrors[0];
} }
// Set field errors ... // Set field errors ...
if (data.fieldErrors && Object.keys(data.fieldErrors).length > 0) { if (data.fieldErrors && Object.keys(data.fieldErrors).length > 0) {
result.fields = data.fieldErrors result.fields = data.fieldErrors;
if (!result.msg) { if (!result.msg) {
const key = Object.keys(data.fieldErrors)[0] const key = Object.keys(data.fieldErrors)[0];
result.msg = data.fieldErrors[key] result.msg = data.fieldErrors[key];
} }
} }
} else { } else {
result = { msg: response.statusText } result = { msg: response.statusText };
} }
} }
} }
// Network related problem ... // Network related problem ...
if (!result) { if (!result) {
result = { msg: 'Unexpected error. Please, try latter' } result = { msg: 'Unexpected error. Please, try latter' };
} }
return result return result;
} };
} }

View File

@ -1,41 +1,41 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom';
import Client, { ErrorInfo } from '../../classes/client' import Client, { ErrorInfo } from '../../classes/client';
import Header from '../layout/header' import Header from '../layout/header';
import Footer from '../layout/footer' import Footer from '../layout/footer';
import FormContainer from '../layout/form-container' import FormContainer from '../layout/form-container';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import { useMutation } from 'react-query' import { useMutation } from 'react-query';
import { activeInstance } from '../../redux/clientSlice' import { activeInstance } from '../../redux/clientSlice';
import Input from '../form/input' import Input from '../form/input';
import GlobalError from '../form/global-error' import GlobalError from '../form/global-error';
import SubmitButton from '../form/submit-button' import SubmitButton from '../form/submit-button';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
const ForgotPassword = () => { const ForgotPassword = () => {
const [email, setEmail] = useState<string>('') const [email, setEmail] = useState<string>('');
const [error, setError] = useState<ErrorInfo>() const [error, setError] = useState<ErrorInfo>();
const history = useHistory() const history = useHistory();
const intl = useIntl() const intl = useIntl();
const service: Client = useSelector(activeInstance) const service: Client = useSelector(activeInstance);
const mutation = useMutation<void, ErrorInfo, string>( const mutation = useMutation<void, ErrorInfo, string>(
(email: string) => service.resetPassword(email), (email: string) => service.resetPassword(email),
{ {
onSuccess: () => history.push('/c/forgot-password-success'), onSuccess: () => history.push('/c/forgot-password-success'),
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault() event.preventDefault();
mutation.mutate(email) mutation.mutate(email);
} };
return ( return (
<FormContainer> <FormContainer>
@ -70,13 +70,13 @@ const ForgotPassword = () => {
/> />
</form> </form>
</FormContainer> </FormContainer>
) );
} };
const ForgotPasswordPage = (): React.ReactElement => { const ForgotPasswordPage = (): React.ReactElement => {
useEffect(() => { useEffect(() => {
document.title = 'Reset Password | WiseMapping' document.title = 'Reset Password | WiseMapping';
}) });
return ( return (
<div> <div>
@ -84,7 +84,7 @@ const ForgotPasswordPage = (): React.ReactElement => {
<ForgotPassword /> <ForgotPassword />
<Footer /> <Footer />
</div> </div>
) );
} };
export { ForgotPasswordPage } export { ForgotPasswordPage };

View File

@ -1,16 +1,16 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import FormContainer from '../layout/form-container' import FormContainer from '../layout/form-container';
import Header from '../layout/header' import Header from '../layout/header';
import Footer from '../layout/footer' import Footer from '../layout/footer';
import { Link as RouterLink } from 'react-router-dom' import { Link as RouterLink } from 'react-router-dom';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
const ForgotPasswordSuccessPage = (): React.ReactElement => { const ForgotPasswordSuccessPage = (): React.ReactElement => {
useEffect(() => { useEffect(() => {
document.title = 'Reset Password | WiseMapping' document.title = 'Reset Password | WiseMapping';
}) });
return ( return (
<div> <div>
@ -43,7 +43,7 @@ const ForgotPasswordSuccessPage = (): React.ReactElement => {
</FormContainer> </FormContainer>
<Footer /> <Footer />
</div> </div>
) );
} };
export default ForgotPasswordSuccessPage export default ForgotPasswordSuccessPage;

View File

@ -1,22 +1,22 @@
import React from 'react' import React from 'react';
import { ErrorInfo } from '../../../classes/client' import { ErrorInfo } from '../../../classes/client';
import StyledAlert from './styled' import StyledAlert from './styled';
type GlobalErrorProps = { type GlobalErrorProps = {
error?: ErrorInfo error?: ErrorInfo;
} };
const GlobalError = (props: GlobalErrorProps): React.ReactElement | null => { const GlobalError = (props: GlobalErrorProps): React.ReactElement | null => {
const error = props.error const error = props.error;
const hasError = Boolean(error?.msg) const hasError = Boolean(error?.msg);
const errorMsg = error?.msg const errorMsg = error?.msg;
return hasError ? ( return hasError ? (
<StyledAlert severity="error" variant="filled" hidden={!hasError}> <StyledAlert severity="error" variant="filled" hidden={!hasError}>
{' '} {' '}
{errorMsg} {errorMsg}
</StyledAlert> </StyledAlert>
) : null ) : null;
} };
export default GlobalError export default GlobalError;

View File

@ -1,11 +1,11 @@
import withStyles from '@material-ui/core/styles/withStyles' import withStyles from '@material-ui/core/styles/withStyles';
import Alert from '@material-ui/lab/Alert' import Alert from '@material-ui/lab/Alert';
export const StyledAlert = withStyles({ export const StyledAlert = withStyles({
root: { root: {
padding: '10px 15px', padding: '10px 15px',
margin: '5px 0px ', margin: '5px 0px ',
}, },
})(Alert) })(Alert);
export default StyledAlert export default StyledAlert;

View File

@ -1,19 +1,19 @@
import TextField from '@material-ui/core/TextField' import TextField from '@material-ui/core/TextField';
import React, { ChangeEvent } from 'react' import React, { ChangeEvent } from 'react';
import { ErrorInfo } from '../../../classes/client' import { ErrorInfo } from '../../../classes/client';
type InputProps = { type InputProps = {
name: string name: string;
error?: ErrorInfo error?: ErrorInfo;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
label: string label: string;
required?: boolean required?: boolean;
type: string type: string;
value?: string value?: string;
autoComplete?: string autoComplete?: string;
fullWidth?: boolean fullWidth?: boolean;
disabled?: boolean disabled?: boolean;
} };
const Input = ({ const Input = ({
name, name,
@ -27,7 +27,7 @@ const Input = ({
fullWidth = true, fullWidth = true,
disabled = false, disabled = false,
}: InputProps): React.ReactElement => { }: InputProps): React.ReactElement => {
const fieldError = error?.fields?.[name] const fieldError = error?.fields?.[name];
return ( return (
<TextField <TextField
name={name} name={name}
@ -44,6 +44,6 @@ const Input = ({
disabled={disabled} disabled={disabled}
autoComplete={autoComplete} autoComplete={autoComplete}
/> />
) );
} };
export default Input export default Input;

View File

@ -1,20 +1,20 @@
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import React, { useState } from 'react' import React, { useState } from 'react';
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl';
type SubmitButton = { type SubmitButton = {
value: string value: string;
disabled?: boolean disabled?: boolean;
} };
const SubmitButton = (props: SubmitButton): React.ReactElement => { const SubmitButton = (props: SubmitButton): React.ReactElement => {
const [disabled] = useState(props.disabled ? true : false) const [disabled] = useState(props.disabled ? true : false);
const intl = useIntl() const intl = useIntl();
let valueTxt = props.value let valueTxt = props.value;
if (disabled) { if (disabled) {
valueTxt = intl.formatMessage({ id: 'common.wait', defaultMessage: 'Please wait ...' }) valueTxt = intl.formatMessage({ id: 'common.wait', defaultMessage: 'Please wait ...' });
} }
const [value] = useState(valueTxt) const [value] = useState(valueTxt);
return ( return (
<Button <Button
color="primary" color="primary"
@ -33,7 +33,7 @@ const SubmitButton = (props: SubmitButton): React.ReactElement => {
> >
{value} {value}
</Button> </Button>
) );
} };
export default SubmitButton export default SubmitButton;

View File

@ -1,10 +1,10 @@
import React from 'react' import React from 'react';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import { StyledFooter } from './styled' import { StyledFooter } from './styled';
// FIXME: use SVG loader // FIXME: use SVG loader
// eslint-disable-next-line // eslint-disable-next-line
const poweredByIcon = require('../../../images/pwrdby-white.svg') const poweredByIcon = require('../../../images/pwrdby-white.svg');
const Footer = (): React.ReactElement => { const Footer = (): React.ReactElement => {
return ( return (
@ -67,7 +67,7 @@ const Footer = (): React.ReactElement => {
</div> </div>
</div> </div>
</StyledFooter> </StyledFooter>
) );
} };
export default Footer export default Footer;

View File

@ -1,4 +1,4 @@
import styled from 'styled-components' import styled from 'styled-components';
/* Footer */ /* Footer */
export const StyledFooter = styled.footer` export const StyledFooter = styled.footer`
@ -41,4 +41,4 @@ export const StyledFooter = styled.footer`
display: inline-block; display: inline-block;
visibility: visible; visibility: visible;
} }
` `;

View File

@ -1,5 +1,5 @@
import Container from '@material-ui/core/Container' import Container from '@material-ui/core/Container';
import withStyles from '@material-ui/core/styles/withStyles' import withStyles from '@material-ui/core/styles/withStyles';
const FormContainer = withStyles({ const FormContainer = withStyles({
root: { root: {
@ -7,6 +7,6 @@ const FormContainer = withStyles({
maxWidth: '380px', maxWidth: '380px',
textAlign: 'center', textAlign: 'center',
}, },
})(Container) })(Container);
export default FormContainer export default FormContainer;

View File

@ -1,20 +1,20 @@
import { StyledNav, StyledDiv, Logo } from './styled' import { StyledNav, StyledDiv, Logo } from './styled';
import React from 'react' import React from 'react';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import logo from '../../../images/logo-small.svg' import logo from '../../../images/logo-small.svg';
interface HeaderProps { interface HeaderProps {
type: 'only-signup' | 'only-signin' | 'none' type: 'only-signup' | 'only-signin' | 'none';
} }
export const Header = ({ type }: HeaderProps): React.ReactElement => { export const Header = ({ type }: HeaderProps): React.ReactElement => {
let signUpButton let signUpButton;
let text let text;
let signInButton let signInButton;
if (type === 'only-signup') { if (type === 'only-signup') {
text = ( text = (
<span className="header-area-content-span"> <span className="header-area-content-span">
@ -25,8 +25,8 @@ export const Header = ({ type }: HeaderProps): React.ReactElement => {
/> />
</span> </span>
</span> </span>
) );
signUpButton = <SignUpButton className="header-area-right2" /> signUpButton = <SignUpButton className="header-area-right2" />;
} else if (type === 'only-signin') { } else if (type === 'only-signin') {
text = ( text = (
<span className="header-area-content-span"> <span className="header-area-content-span">
@ -37,14 +37,14 @@ export const Header = ({ type }: HeaderProps): React.ReactElement => {
/> />
</span> </span>
</span> </span>
) );
signUpButton = <SignInButton className="header-area-right2" /> signUpButton = <SignInButton className="header-area-right2" />;
} else if (type === 'none') { } else if (type === 'none') {
text = '' text = '';
signUpButton = '' signUpButton = '';
} else { } else {
signUpButton = <SignUpButton className="header-area-right2" /> signUpButton = <SignUpButton className="header-area-right2" />;
signInButton = <SignInButton className="header-area-right2" /> signInButton = <SignInButton className="header-area-right2" />;
} }
return ( return (
@ -60,11 +60,11 @@ export const Header = ({ type }: HeaderProps): React.ReactElement => {
{signInButton} {signInButton}
</StyledDiv> </StyledDiv>
</StyledNav> </StyledNav>
) );
} };
interface ButtonProps { interface ButtonProps {
className?: string className?: string;
} }
export const SignInButton = (props: ButtonProps): React.ReactElement => { export const SignInButton = (props: ButtonProps): React.ReactElement => {
@ -74,8 +74,8 @@ export const SignInButton = (props: ButtonProps): React.ReactElement => {
<FormattedMessage id="login.signin" defaultMessage="Sign In" /> <FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button> </Button>
</span> </span>
) );
} };
const SignUpButton = (props: ButtonProps): React.ReactElement => { const SignUpButton = (props: ButtonProps): React.ReactElement => {
return ( return (
@ -90,7 +90,7 @@ const SignUpButton = (props: ButtonProps): React.ReactElement => {
<FormattedMessage id="login.signup" defaultMessage="Sign Up" /> <FormattedMessage id="login.signup" defaultMessage="Sign Up" />
</Button> </Button>
</span> </span>
) );
} };
export default Header export default Header;

View File

@ -1,4 +1,4 @@
import styled from 'styled-components' import styled from 'styled-components';
export const StyledNav = styled.nav` export const StyledNav = styled.nav`
height: 90px; height: 90px;
@ -50,7 +50,7 @@ export const StyledNav = styled.nav`
font-size: 14px; font-size: 14px;
padding: 10px; padding: 10px;
} }
` `;
export const StyledDiv = styled.nav` export const StyledDiv = styled.nav`
background: white; background: white;
@ -64,7 +64,7 @@ export const StyledDiv = styled.nav`
display: grid; display: grid;
white-space: nowrap; white-space: nowrap;
grid-template-columns: 150px 1fr 130px 160px 50px; grid-template-columns: 150px 1fr 130px 160px 50px;
` `;
export const Logo = styled.span` export const Logo = styled.span`
grid-column-start: 1; grid-column-start: 1;
@ -74,4 +74,4 @@ export const Logo = styled.span`
.header-logo a { .header-logo a {
padding: 0px; padding: 0px;
} }
` `;

View File

@ -1,22 +1,22 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { Link as RouterLink } from 'react-router-dom' import { Link as RouterLink } from 'react-router-dom';
import Header from '../layout/header' import Header from '../layout/header';
import Footer from '../layout/footer' import Footer from '../layout/footer';
import SubmitButton from '../form/submit-button' import SubmitButton from '../form/submit-button';
import Input from '../form/input' import Input from '../form/input';
import GlobalError from '../form/global-error' import GlobalError from '../form/global-error';
import FormContainer from '../layout/form-container' import FormContainer from '../layout/form-container';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import Link from '@material-ui/core/Link' import Link from '@material-ui/core/Link';
type ConfigStatusProps = { type ConfigStatusProps = {
enabled?: boolean enabled?: boolean;
} };
const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.ReactElement => { const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.ReactElement => {
let result let result;
if (enabled === true) { if (enabled === true) {
result = ( result = (
<div className="db-warn-msg"> <div className="db-warn-msg">
@ -32,18 +32,18 @@ const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.Reac
</a> </a>
</p> </p>
</div> </div>
) );
} }
return result || null return result || null;
} };
const LoginError = () => { const LoginError = () => {
// @Todo: This must be reviewed to be based on navigation state. // @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2 // Login error example: http://localhost:8080/c/login?login.error=2
const errorCode = new URLSearchParams(window.location.search).get('login_error') const errorCode = new URLSearchParams(window.location.search).get('login_error');
const intl = useIntl() const intl = useIntl();
let msg: null | string = null let msg: null | string = null;
if (errorCode) { if (errorCode) {
switch (errorCode) { switch (errorCode) {
case '3': case '3':
@ -51,24 +51,24 @@ const LoginError = () => {
id: 'login.userinactive', id: 'login.userinactive',
defaultMessage: defaultMessage:
"Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!.", "Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!.",
}) });
break break;
default: default:
msg = intl.formatMessage({ msg = intl.formatMessage({
id: 'login.error', id: 'login.error',
defaultMessage: 'The email address or password you entered is not valid.', defaultMessage: 'The email address or password you entered is not valid.',
}) });
} }
} }
return msg ? <GlobalError error={{ msg: msg }} /> : null return msg ? <GlobalError error={{ msg: msg }} /> : null;
} };
const LoginPage = (): React.ReactElement => { const LoginPage = (): React.ReactElement => {
const intl = useIntl() const intl = useIntl();
useEffect(() => { useEffect(() => {
document.title = 'Login | WiseMapping' document.title = 'Login | WiseMapping';
}) });
return ( return (
<div> <div>
@ -133,7 +133,7 @@ const LoginPage = (): React.ReactElement => {
<Footer /> <Footer />
</div> </div>
) );
} };
export default LoginPage export default LoginPage;

View File

@ -1,105 +1,105 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query' import { useMutation, useQueryClient } from 'react-query';
import Client, { ErrorInfo } from '../../../../classes/client' import Client, { ErrorInfo } from '../../../../classes/client';
import Input from '../../../form/input' import Input from '../../../form/input';
import BaseDialog from '../../action-dispatcher/base-dialog' import BaseDialog from '../../action-dispatcher/base-dialog';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import { activeInstance, fetchAccount } from '../../../../redux/clientSlice' import { activeInstance, fetchAccount } from '../../../../redux/clientSlice';
import Alert from '@material-ui/lab/Alert' import Alert from '@material-ui/lab/Alert';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel' import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormGroup from '@material-ui/core/FormGroup' import FormGroup from '@material-ui/core/FormGroup';
import Switch from '@material-ui/core/Switch' import Switch from '@material-ui/core/Switch';
type AccountInfoDialogProps = { type AccountInfoDialogProps = {
onClose: () => void onClose: () => void;
} };
type AccountInfoModel = { type AccountInfoModel = {
email: string email: string;
firstname: string firstname: string;
lastname: string lastname: string;
} };
const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' } const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' };
const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElement => { const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient() const queryClient = useQueryClient();
const [remove, setRemove] = React.useState<boolean>(false) const [remove, setRemove] = React.useState<boolean>(false);
const [model, setModel] = React.useState<AccountInfoModel>(defaultModel) const [model, setModel] = React.useState<AccountInfoModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl() const intl = useIntl();
const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>( const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>(
(model: AccountInfoModel) => { (model: AccountInfoModel) => {
return client.updateAccountInfo(model.firstname, model.lastname) return client.updateAccountInfo(model.firstname, model.lastname);
}, },
{ {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries('account') queryClient.invalidateQueries('account');
onClose() onClose();
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const mutationRemove = useMutation<void, ErrorInfo, void>( const mutationRemove = useMutation<void, ErrorInfo, void>(
() => { () => {
return client.deleteAccount() return client.deleteAccount();
}, },
{ {
onSuccess: () => { onSuccess: () => {
window.location.href = '/c/logout' window.location.href = '/c/logout';
onClose() onClose();
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const account = fetchAccount() const account = fetchAccount();
useEffect(() => { useEffect(() => {
if (account) { if (account) {
setModel({ setModel({
email: account?.email, email: account?.email,
lastname: account?.lastname, lastname: account?.lastname,
firstname: account?.firstname, firstname: account?.firstname,
}) });
} }
}, [account?.email]) }, [account?.email]);
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
setModel(defaultModel) setModel(defaultModel);
setError(undefined) setError(undefined);
} };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
if (remove) { if (remove) {
mutationRemove.mutate() mutationRemove.mutate();
} else { } else {
mutationChangeName.mutate(model) mutationChangeName.mutate(model);
} }
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof AccountInfoModel]: value }) setModel({ ...model, [name as keyof AccountInfoModel]: value });
} };
const handleOnRemoveChange = (event) => { const handleOnRemoveChange = (event) => {
setRemove(event.target.checked) setRemove(event.target.checked);
} };
return ( return (
<BaseDialog <BaseDialog
@ -173,6 +173,6 @@ const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElem
</FormGroup> </FormGroup>
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
) );
} };
export default AccountInfoDialog export default AccountInfoDialog;

View File

@ -1,51 +1,51 @@
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import React from 'react' import React from 'react';
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl';
import { useMutation } from 'react-query' import { useMutation } from 'react-query';
import Client, { ErrorInfo } from '../../../../classes/client' import Client, { ErrorInfo } from '../../../../classes/client';
import Input from '../../../form/input' import Input from '../../../form/input';
import BaseDialog from '../../action-dispatcher/base-dialog' import BaseDialog from '../../action-dispatcher/base-dialog';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import { activeInstance } from '../../../../redux/clientSlice' import { activeInstance } from '../../../../redux/clientSlice';
type ChangePasswordDialogProps = { type ChangePasswordDialogProps = {
onClose: () => void onClose: () => void;
} };
type ChangePasswordModel = { type ChangePasswordModel = {
password: string password: string;
retryPassword: string retryPassword: string;
} };
const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' } const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' };
const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.ReactElement => { const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel) const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl() const intl = useIntl();
const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>( const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>(
(model: ChangePasswordModel) => { (model: ChangePasswordModel) => {
return client.updateAccountPassword(model.password) return client.updateAccountPassword(model.password);
}, },
{ {
onSuccess: () => { onSuccess: () => {
onClose() onClose();
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
setModel(defaultModel) setModel(defaultModel);
setError(undefined) setError(undefined);
} };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
// Check password are equal ... // Check password are equal ...
if (model.password != model.retryPassword) { if (model.password != model.retryPassword) {
@ -54,20 +54,20 @@ const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.Rea
id: 'changepwd.password-match', id: 'changepwd.password-match',
defaultMessage: 'Password do not match. Please, try again.', defaultMessage: 'Password do not match. Please, try again.',
}), }),
}) });
return return;
} }
mutation.mutate(model) mutation.mutate(model);
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof ChangePasswordModel]: value }) setModel({ ...model, [name as keyof ChangePasswordModel]: value });
} };
return ( return (
<BaseDialog <BaseDialog
@ -111,6 +111,6 @@ const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.Rea
/> />
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
) );
} };
export default ChangePasswordDialog export default ChangePasswordDialog;

View File

@ -1,34 +1,34 @@
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton';
import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemIcon from '@material-ui/core/ListItemIcon';
import Menu from '@material-ui/core/Menu' import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
import SettingsApplicationsOutlined from '@material-ui/icons/SettingsApplicationsOutlined' import SettingsApplicationsOutlined from '@material-ui/icons/SettingsApplicationsOutlined';
import AccountCircle from '@material-ui/icons/AccountCircle' import AccountCircle from '@material-ui/icons/AccountCircle';
import React from 'react' import React from 'react';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import { fetchAccount } from '../../../redux/clientSlice' import { fetchAccount } from '../../../redux/clientSlice';
import AccountInfoDialog from './account-info-dialog' import AccountInfoDialog from './account-info-dialog';
import ChangePasswordDialog from './change-password-dialog' import ChangePasswordDialog from './change-password-dialog';
import LockOpenOutlined from '@material-ui/icons/LockOpenOutlined' import LockOpenOutlined from '@material-ui/icons/LockOpenOutlined';
import Link from '@material-ui/core/Link' import Link from '@material-ui/core/Link';
import ExitToAppOutlined from '@material-ui/icons/ExitToAppOutlined' import ExitToAppOutlined from '@material-ui/icons/ExitToAppOutlined';
type ActionType = 'change-password' | 'account-info' | undefined type ActionType = 'change-password' | 'account-info' | undefined;
const AccountMenu = (): React.ReactElement => { const AccountMenu = (): React.ReactElement => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null) const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl) const open = Boolean(anchorEl);
const [action, setAction] = React.useState<ActionType>(undefined) const [action, setAction] = React.useState<ActionType>(undefined);
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget) setAnchorEl(event.currentTarget);
} };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null) setAnchorEl(null);
} };
const account = fetchAccount() const account = fetchAccount();
return ( return (
<span> <span>
<Tooltip <Tooltip
@ -57,7 +57,7 @@ const AccountMenu = (): React.ReactElement => {
> >
<MenuItem <MenuItem
onClick={() => { onClick={() => {
handleClose(), setAction('account-info') handleClose(), setAction('account-info');
}} }}
> >
<ListItemIcon> <ListItemIcon>
@ -68,7 +68,7 @@ const AccountMenu = (): React.ReactElement => {
<MenuItem <MenuItem
onClick={() => { onClick={() => {
handleClose(), setAction('change-password') handleClose(), setAction('change-password');
}} }}
> >
<ListItemIcon> <ListItemIcon>
@ -91,7 +91,7 @@ const AccountMenu = (): React.ReactElement => {
)} )}
{action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />} {action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />}
</span> </span>
) );
} };
export default AccountMenu export default AccountMenu;

View File

@ -1,21 +1,21 @@
import React from 'react' import React from 'react';
import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined' import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined';
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined' import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined' import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined';
import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined' import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined';
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined' import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
import EditOutlinedIcon from '@material-ui/icons/EditOutlined' import EditOutlinedIcon from '@material-ui/icons/EditOutlined';
import PublicOutlinedIcon from '@material-ui/icons/PublicOutlined' import PublicOutlinedIcon from '@material-ui/icons/PublicOutlined';
import PrintOutlinedIcon from '@material-ui/icons/PrintOutlined' import PrintOutlinedIcon from '@material-ui/icons/PrintOutlined';
import ShareOutlinedIcon from '@material-ui/icons/ShareOutlined' import ShareOutlinedIcon from '@material-ui/icons/ShareOutlined';
import LabelOutlined from '@material-ui/icons/LabelOutlined' import LabelOutlined from '@material-ui/icons/LabelOutlined';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import { fetchMapById } from '../../../redux/clientSlice' import { fetchMapById } from '../../../redux/clientSlice';
import Menu from '@material-ui/core/Menu' import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemIcon from '@material-ui/core/ListItemIcon';
import Divider from '@material-ui/core/Divider' import Divider from '@material-ui/core/Divider';
export type ActionType = export type ActionType =
| 'open' | 'open'
| 'share' | 'share'
@ -31,27 +31,27 @@ export type ActionType =
| 'info' | 'info'
| 'publish' | 'publish'
| 'history' | 'history'
| undefined | undefined;
interface ActionProps { interface ActionProps {
onClose: (action: ActionType) => void onClose: (action: ActionType) => void;
anchor?: HTMLElement anchor?: HTMLElement;
mapId?: number mapId?: number;
} }
const ActionChooser = (props: ActionProps): React.ReactElement => { const ActionChooser = (props: ActionProps): React.ReactElement => {
const { anchor, onClose, mapId } = props const { anchor, onClose, mapId } = props;
const handleOnClose = ( const handleOnClose = (
action: ActionType action: ActionType
): ((event: React.MouseEvent<HTMLLIElement>) => void) => { ): ((event: React.MouseEvent<HTMLLIElement>) => void) => {
return (event): void => { return (event): void => {
event.stopPropagation() event.stopPropagation();
onClose(action) onClose(action);
} };
} };
const role = mapId ? fetchMapById(mapId)?.map?.role : undefined const role = mapId ? fetchMapById(mapId)?.map?.role : undefined;
return ( return (
<Menu <Menu
anchorEl={anchor} anchorEl={anchor}
@ -149,7 +149,7 @@ const ActionChooser = (props: ActionProps): React.ReactElement => {
</MenuItem> </MenuItem>
)} )}
</Menu> </Menu>
) );
} };
export default ActionChooser export default ActionChooser;

View File

@ -1,8 +1,8 @@
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem';
import withStyles from '@material-ui/core/styles/withStyles' import withStyles from '@material-ui/core/styles/withStyles';
export const StyledMenuItem = withStyles({ export const StyledMenuItem = withStyles({
root: { root: {
width: '300px', width: '300px',
}, },
})(MenuItem) })(MenuItem);

View File

@ -1,40 +1,40 @@
import React from 'react' import React from 'react';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import { ErrorInfo } from '../../../../classes/client' import { ErrorInfo } from '../../../../classes/client';
import { StyledDialog, StyledDialogActions, StyledDialogContent, StyledDialogTitle } from './style' import { StyledDialog, StyledDialogActions, StyledDialogContent, StyledDialogTitle } from './style';
import GlobalError from '../../../form/global-error' import GlobalError from '../../../form/global-error';
import DialogContentText from '@material-ui/core/DialogContentText' import DialogContentText from '@material-ui/core/DialogContentText';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import { PaperProps } from '@material-ui/core/Paper' import { PaperProps } from '@material-ui/core/Paper';
export type DialogProps = { export type DialogProps = {
onClose: () => void onClose: () => void;
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
children: unknown children: unknown;
error?: ErrorInfo error?: ErrorInfo;
title: string title: string;
description?: string description?: string;
submitButton?: string submitButton?: string;
actionUrl?: string actionUrl?: string;
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
PaperProps?: Partial<PaperProps> PaperProps?: Partial<PaperProps>;
} };
const BaseDialog = (props: DialogProps): React.ReactElement => { const BaseDialog = (props: DialogProps): React.ReactElement => {
const { onClose, onSubmit, maxWidth = 'sm', PaperProps } = props const { onClose, onSubmit, maxWidth = 'sm', PaperProps } = props;
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault();
if (onSubmit) { if (onSubmit) {
onSubmit(e) onSubmit(e);
} }
} };
const description = props.description ? ( const description = props.description ? (
<DialogContentText>{props.description}</DialogContentText> <DialogContentText>{props.description}</DialogContentText>
) : null ) : null;
return ( return (
<div> <div>
<StyledDialog <StyledDialog
@ -79,7 +79,7 @@ const BaseDialog = (props: DialogProps): React.ReactElement => {
</form> </form>
</StyledDialog> </StyledDialog>
</div> </div>
) );
} };
export default BaseDialog export default BaseDialog;

View File

@ -1,29 +1,29 @@
import Dialog from '@material-ui/core/Dialog' import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions' import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent' import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle' import DialogTitle from '@material-ui/core/DialogTitle';
import withStyles from '@material-ui/core/styles/withStyles' import withStyles from '@material-ui/core/styles/withStyles';
export const StyledDialogContent = withStyles({ export const StyledDialogContent = withStyles({
root: { root: {
padding: '0px 39px', padding: '0px 39px',
}, },
})(DialogContent) })(DialogContent);
export const StyledDialogTitle = withStyles({ export const StyledDialogTitle = withStyles({
root: { root: {
padding: '39px 39px 10px 39px', padding: '39px 39px 10px 39px',
}, },
})(DialogTitle) })(DialogTitle);
export const StyledDialogActions = withStyles({ export const StyledDialogActions = withStyles({
root: { root: {
padding: '39px 39px 39px 39px', padding: '39px 39px 39px 39px',
}, },
})(DialogActions) })(DialogActions);
export const StyledDialog = withStyles({ export const StyledDialog = withStyles({
root: { root: {
borderRadius: '9px', borderRadius: '9px',
}, },
})(Dialog) })(Dialog);

View File

@ -1,62 +1,62 @@
import React from 'react' import React from 'react';
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl';
import { useMutation } from 'react-query' import { useMutation } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client' import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice' import { activeInstance } from '../../../../redux/clientSlice';
import Input from '../../../form/input' import Input from '../../../form/input';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
export type CreateModel = { export type CreateModel = {
title: string title: string;
description?: string description?: string;
} };
export type CreateProps = { export type CreateProps = {
onClose: () => void onClose: () => void;
} };
const defaultModel: CreateModel = { title: '', description: '' } const defaultModel: CreateModel = { title: '', description: '' };
const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => { const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => {
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<CreateModel>(defaultModel) const [model, setModel] = React.useState<CreateModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl() const intl = useIntl();
const mutation = useMutation<number, ErrorInfo, CreateModel>( const mutation = useMutation<number, ErrorInfo, CreateModel>(
(model: CreateModel) => { (model: CreateModel) => {
return client.createMap(model) return client.createMap(model);
}, },
{ {
onSuccess: (mapId: number) => { onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit` window.location.href = `/c/maps/${mapId}/edit`;
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
setModel(defaultModel) setModel(defaultModel);
setError(undefined) setError(undefined);
} };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
mutation.mutate(model) mutation.mutate(model);
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value }) setModel({ ...model, [name as keyof BasicMapInfo]: value });
} };
return ( return (
<div> <div>
@ -103,7 +103,7 @@ const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => {
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default CreateDialog export default CreateDialog;

View File

@ -1,38 +1,38 @@
import React from 'react' import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query' import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../../../classes/client' import Client, { ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice' import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from '..' import { SimpleDialogProps, handleOnMutationSuccess } from '..';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import Alert from '@material-ui/lab/Alert' import Alert from '@material-ui/lab/Alert';
import AlertTitle from '@material-ui/lab/AlertTitle' import AlertTitle from '@material-ui/lab/AlertTitle';
const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl() const intl = useIntl();
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient() const queryClient = useQueryClient();
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const mutation = useMutation((id: number) => client.deleteMap(id), { const mutation = useMutation((id: number) => client.deleteMap(id), {
onSuccess: () => handleOnMutationSuccess(onClose, queryClient), onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error: ErrorInfo) => { onError: (error: ErrorInfo) => {
setError(error) setError(error);
}, },
}) });
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
} };
const handleOnSubmit = (): void => { const handleOnSubmit = (): void => {
mutation.mutate(mapId) mutation.mutate(mapId);
} };
// Fetch map model to be rendered ... // Fetch map model to be rendered ...
const { map } = fetchMapById(mapId) const { map } = fetchMapById(mapId);
const alertTitle = `Delete ${map?.title}` const alertTitle = `Delete ${map?.title}`;
return ( return (
<div> <div>
<BaseDialog <BaseDialog
@ -54,7 +54,7 @@ const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
</Alert> </Alert>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default DeleteDialog export default DeleteDialog;

View File

@ -1,41 +1,41 @@
import React from 'react' import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query' import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client from '../../../../classes/client' import Client from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice' import { activeInstance } from '../../../../redux/clientSlice';
import { handleOnMutationSuccess } from '..' import { handleOnMutationSuccess } from '..';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import Alert from '@material-ui/lab/Alert' import Alert from '@material-ui/lab/Alert';
import AlertTitle from '@material-ui/lab/AlertTitle' import AlertTitle from '@material-ui/lab/AlertTitle';
export type DeleteMultiselectDialogProps = { export type DeleteMultiselectDialogProps = {
mapsId: number[] mapsId: number[];
onClose: () => void onClose: () => void;
} };
const DeleteMultiselectDialog = ({ const DeleteMultiselectDialog = ({
onClose, onClose,
mapsId, mapsId,
}: DeleteMultiselectDialogProps): React.ReactElement => { }: DeleteMultiselectDialogProps): React.ReactElement => {
const intl = useIntl() const intl = useIntl();
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient() const queryClient = useQueryClient();
const mutation = useMutation((ids: number[]) => client.deleteMaps(ids), { const mutation = useMutation((ids: number[]) => client.deleteMaps(ids), {
onSuccess: () => handleOnMutationSuccess(onClose, queryClient), onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error) => { onError: (error) => {
console.error(`Unexpected error ${error}`) console.error(`Unexpected error ${error}`);
}, },
}) });
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
} };
const handleOnSubmit = (): void => { const handleOnSubmit = (): void => {
mutation.mutate(mapsId) mutation.mutate(mapsId);
} };
return ( return (
<div> <div>
@ -62,7 +62,7 @@ const DeleteMultiselectDialog = ({
</Alert> </Alert>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default DeleteMultiselectDialog export default DeleteMultiselectDialog;

View File

@ -1,70 +1,70 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl';
import { useMutation } from 'react-query' import { useMutation } from 'react-query';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client' import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice' import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import Input from '../../../form/input' import Input from '../../../form/input';
import { SimpleDialogProps } from '..' import { SimpleDialogProps } from '..';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
export type DuplicateModel = { export type DuplicateModel = {
id: number id: number;
title: string title: string;
description?: string description?: string;
} };
const defaultModel: DuplicateModel = { title: '', description: '', id: -1 } const defaultModel: DuplicateModel = { title: '', description: '', id: -1 };
const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const service: Client = useSelector(activeInstance) const service: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<DuplicateModel>(defaultModel) const [model, setModel] = React.useState<DuplicateModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl() const intl = useIntl();
const mutation = useMutation<number, ErrorInfo, DuplicateModel>( const mutation = useMutation<number, ErrorInfo, DuplicateModel>(
(model: DuplicateModel) => { (model: DuplicateModel) => {
const { id, ...rest } = model const { id, ...rest } = model;
return service.duplicateMap(id, rest) return service.duplicateMap(id, rest);
}, },
{ {
onSuccess: (mapId) => { onSuccess: (mapId) => {
window.location.href = `/c/maps/${mapId}/edit` window.location.href = `/c/maps/${mapId}/edit`;
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
} };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
mutation.mutate(model) mutation.mutate(model);
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value }) setModel({ ...model, [name as keyof BasicMapInfo]: value });
} };
const { map } = fetchMapById(mapId) const { map } = fetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (open && map) { if (open && map) {
setModel(map) setModel(map);
} else { } else {
setModel(defaultModel) setModel(defaultModel);
setError(undefined) setError(undefined);
} }
}, [mapId]) }, [mapId]);
return ( return (
<div> <div>
@ -111,7 +111,7 @@ const DuplicateDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElem
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default DuplicateDialog export default DuplicateDialog;

View File

@ -1,25 +1,25 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import { useStyles } from './style' import { useStyles } from './style';
import Alert from '@material-ui/lab/Alert' import Alert from '@material-ui/lab/Alert';
import { fetchMapById } from '../../../../redux/clientSlice' import { fetchMapById } from '../../../../redux/clientSlice';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import RadioGroup from '@material-ui/core/RadioGroup' import RadioGroup from '@material-ui/core/RadioGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel' import FormControlLabel from '@material-ui/core/FormControlLabel';
import Radio from '@material-ui/core/Radio' import Radio from '@material-ui/core/Radio';
import Select from '@material-ui/core/Select' import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem';
type ExportFormat = 'pdf' | 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'txt' type ExportFormat = 'pdf' | 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'txt';
type ExportGroup = 'image' | 'document' | 'mindmap-tool' type ExportGroup = 'image' | 'document' | 'mindmap-tool';
type ExportDialogProps = { type ExportDialogProps = {
mapId: number mapId: number;
enableImgExport: boolean enableImgExport: boolean;
svgXml?: string svgXml?: string;
onClose: () => void onClose: () => void;
} };
const ExportDialog = ({ const ExportDialog = ({
mapId, mapId,
@ -27,50 +27,50 @@ const ExportDialog = ({
enableImgExport, enableImgExport,
svgXml, svgXml,
}: ExportDialogProps): React.ReactElement => { }: ExportDialogProps): React.ReactElement => {
const intl = useIntl() const intl = useIntl();
const [submit, setSubmit] = React.useState<boolean>(false) const [submit, setSubmit] = React.useState<boolean>(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const [formExportRef, setExportFormRef] = React.useState<any>() const [formExportRef, setExportFormRef] = React.useState<any>();
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const [formTransformtRef, setTransformFormRef] = React.useState<any>() const [formTransformtRef, setTransformFormRef] = React.useState<any>();
const [exportGroup, setExportGroup] = React.useState<ExportGroup>( const [exportGroup, setExportGroup] = React.useState<ExportGroup>(
enableImgExport ? 'image' : 'document' enableImgExport ? 'image' : 'document'
) );
const [exportFormat, setExportFormat] = React.useState<ExportFormat>( const [exportFormat, setExportFormat] = React.useState<ExportFormat>(
enableImgExport ? 'svg' : 'xls' enableImgExport ? 'svg' : 'xls'
) );
const classes = useStyles() const classes = useStyles();
const handleOnExportFormatChange = (event) => { const handleOnExportFormatChange = (event) => {
setExportFormat(event.target.value) setExportFormat(event.target.value);
} };
const handleOnGroupChange = (event) => { const handleOnGroupChange = (event) => {
const value: ExportGroup = event.target.value const value: ExportGroup = event.target.value;
setExportGroup(value) setExportGroup(value);
let defaultFormat: ExportFormat let defaultFormat: ExportFormat;
switch (value) { switch (value) {
case 'document': case 'document':
defaultFormat = 'pdf' defaultFormat = 'pdf';
break break;
case 'image': case 'image':
defaultFormat = 'svg' defaultFormat = 'svg';
break break;
case 'mindmap-tool': case 'mindmap-tool':
defaultFormat = 'wxml' defaultFormat = 'wxml';
break break;
} }
setExportFormat(defaultFormat) setExportFormat(defaultFormat);
} };
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
} };
const handleOnSubmit = (): void => { const handleOnSubmit = (): void => {
setSubmit(true) setSubmit(true);
} };
useEffect(() => { useEffect(() => {
if (submit) { if (submit) {
@ -81,15 +81,15 @@ const ExportDialog = ({
exportFormat == 'jpg' || exportFormat == 'jpg' ||
exportFormat == 'png' exportFormat == 'png'
) { ) {
formTransformtRef?.submit() formTransformtRef?.submit();
} else { } else {
formExportRef?.submit() formExportRef?.submit();
} }
onClose() onClose();
} }
}, [submit]) }, [submit]);
const { map } = fetchMapById(mapId) const { map } = fetchMapById(mapId);
return ( return (
<div> <div>
<BaseDialog <BaseDialog
@ -231,7 +231,7 @@ const ExportDialog = ({
<input name="svgXml" id="svgXml" value={svgXml} type="hidden" /> <input name="svgXml" id="svgXml" value={svgXml} type="hidden" />
</form> </form>
</div> </div>
) );
} };
export default ExportDialog export default ExportDialog;

View File

@ -1,5 +1,5 @@
import createStyles from '@material-ui/core/styles/createStyles' import createStyles from '@material-ui/core/styles/createStyles';
import makeStyles from '@material-ui/core/styles/makeStyles' import makeStyles from '@material-ui/core/styles/makeStyles';
export const useStyles = makeStyles(() => export const useStyles = makeStyles(() =>
createStyles({ createStyles({
@ -17,4 +17,4 @@ export const useStyles = makeStyles(() =>
margin: '5px 0px', margin: '5px 0px',
}, },
}) })
) );

View File

@ -1,42 +1,42 @@
import React, { ErrorInfo } from 'react' import React, { ErrorInfo } from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useQuery } from 'react-query' import { useQuery } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { ChangeHistory } from '../../../../classes/client' import Client, { ChangeHistory } from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice' import { activeInstance } from '../../../../redux/clientSlice';
import { SimpleDialogProps } from '..' import { SimpleDialogProps } from '..';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import dayjs from 'dayjs' import dayjs from 'dayjs';
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer';
import Table from '@material-ui/core/Table' import Table from '@material-ui/core/Table';
import TableRow from '@material-ui/core/TableRow' import TableRow from '@material-ui/core/TableRow';
import TableCell from '@material-ui/core/TableCell' import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead' import TableHead from '@material-ui/core/TableHead';
import TableBody from '@material-ui/core/TableBody' import TableBody from '@material-ui/core/TableBody';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
import Link from '@material-ui/core/Link' import Link from '@material-ui/core/Link';
import Paper from '@material-ui/core/Paper' import Paper from '@material-ui/core/Paper';
const HistoryDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const HistoryDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl() const intl = useIntl();
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const { data } = useQuery<unknown, ErrorInfo, ChangeHistory[]>('history', () => { const { data } = useQuery<unknown, ErrorInfo, ChangeHistory[]>('history', () => {
return client.fetchHistory(mapId) return client.fetchHistory(mapId);
}) });
const changeHistory: ChangeHistory[] = data ? data : [] const changeHistory: ChangeHistory[] = data ? data : [];
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
} };
const handleOnClick = (event, vid): void => { const handleOnClick = (event, vid): void => {
event.preventDefault() event.preventDefault();
client.revertHistory(mapId, vid).then(() => { client.revertHistory(mapId, vid).then(() => {
handleOnClose() handleOnClose();
}) });
} };
return ( return (
<div> <div>
@ -127,7 +127,7 @@ const HistoryDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElemen
</TableContainer> </TableContainer>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default HistoryDialog export default HistoryDialog;

View File

@ -1,96 +1,96 @@
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import React from 'react' import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation } from 'react-query' import { useMutation } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../../../classes/client' import Client, { ErrorInfo } from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice' import { activeInstance } from '../../../../redux/clientSlice';
import Input from '../../../form/input' import Input from '../../../form/input';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
export type ImportModel = { export type ImportModel = {
title: string title: string;
description?: string description?: string;
contentType?: string contentType?: string;
content?: ArrayBuffer | null | string content?: ArrayBuffer | null | string;
} };
export type CreateProps = { export type CreateProps = {
onClose: () => void onClose: () => void;
} };
const defaultModel: ImportModel = { title: '' } const defaultModel: ImportModel = { title: '' };
const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => { const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => {
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ImportModel>(defaultModel) const [model, setModel] = React.useState<ImportModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl() const intl = useIntl();
const mutation = useMutation<number, ErrorInfo, ImportModel>( const mutation = useMutation<number, ErrorInfo, ImportModel>(
(model: ImportModel) => { (model: ImportModel) => {
return client.importMap(model) return client.importMap(model);
}, },
{ {
onSuccess: (mapId: number) => { onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit` window.location.href = `/c/maps/${mapId}/edit`;
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
setModel(defaultModel) setModel(defaultModel);
setError(undefined) setError(undefined);
} };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
mutation.mutate(model) mutation.mutate(model);
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof ImportModel]: value }) setModel({ ...model, [name as keyof ImportModel]: value });
} };
const handleOnFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleOnFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event?.target?.files const files = event?.target?.files;
const reader = new FileReader() const reader = new FileReader();
if (files) { if (files) {
const file = files[0] const file = files[0];
// Closure to capture the file information. // Closure to capture the file information.
reader.onload = (event) => { reader.onload = (event) => {
const fileContent = event?.target?.result const fileContent = event?.target?.result;
model.content = fileContent model.content = fileContent;
// Suggest file name ... // Suggest file name ...
const fileName = file.name const fileName = file.name;
if (fileName) { if (fileName) {
const title = fileName.split('.')[0] const title = fileName.split('.')[0];
if (!model.title || 0 === model.title.length) { if (!model.title || 0 === model.title.length) {
model.title = title model.title = title;
} }
} }
model.contentType = model.contentType =
file.name.lastIndexOf('.wxml') != -1 file.name.lastIndexOf('.wxml') != -1
? 'application/xml' ? 'application/xml'
: 'application/freemind' : 'application/freemind';
setModel({ ...model }) setModel({ ...model });
} };
// Read in the image file as a data URL. // Read in the image file as a data URL.
reader.readAsText(file) reader.readAsText(file);
} }
} };
return ( return (
<div> <div>
@ -161,7 +161,7 @@ const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => {
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default ImportDialog export default ImportDialog;

View File

@ -1,41 +1,41 @@
import React from 'react' import React from 'react';
import RenameDialog from './rename-dialog' import RenameDialog from './rename-dialog';
import DeleteDialog from './delete-dialog' import DeleteDialog from './delete-dialog';
import { ActionType } from '../action-chooser' import { ActionType } from '../action-chooser';
import { QueryClient } from 'react-query' import { QueryClient } from 'react-query';
import DuplicateDialog from './duplicate-dialog' import DuplicateDialog from './duplicate-dialog';
import CreateDialog from './create-dialog' import CreateDialog from './create-dialog';
import HistoryDialog from './history-dialog' import HistoryDialog from './history-dialog';
import ImportDialog from './import-dialog' import ImportDialog from './import-dialog';
import PublishDialog from './publish-dialog' import PublishDialog from './publish-dialog';
import InfoDialog from './info-dialog' import InfoDialog from './info-dialog';
import DeleteMultiselectDialog from './delete-multiselect-dialog' import DeleteMultiselectDialog from './delete-multiselect-dialog';
import ExportDialog from './export-dialog' import ExportDialog from './export-dialog';
import ShareDialog from './share-dialog' import ShareDialog from './share-dialog';
export type BasicMapInfo = { export type BasicMapInfo = {
name: string name: string;
description: string | undefined description: string | undefined;
} };
type ActionDialogProps = { type ActionDialogProps = {
action?: ActionType action?: ActionType;
mapsId: number[] mapsId: number[];
onClose: () => void onClose: () => void;
} };
const ActionDispatcher = ({ mapsId, action, onClose }: ActionDialogProps): React.ReactElement => { const ActionDispatcher = ({ mapsId, action, onClose }: ActionDialogProps): React.ReactElement => {
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
} };
switch (action) { switch (action) {
case 'open': case 'open':
window.location.href = `/c/maps/${mapsId}/edit` window.location.href = `/c/maps/${mapsId}/edit`;
break break;
case 'print': case 'print':
window.open(`/c/maps/${mapsId}/print`, 'print') window.open(`/c/maps/${mapsId}/print`, 'print');
break break;
} }
return ( return (
@ -61,16 +61,16 @@ const ActionDispatcher = ({ mapsId, action, onClose }: ActionDialogProps): React
)} )}
{action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />} {action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />}
</span> </span>
) );
} };
export const handleOnMutationSuccess = (onClose: () => void, queryClient: QueryClient): void => { export const handleOnMutationSuccess = (onClose: () => void, queryClient: QueryClient): void => {
queryClient.invalidateQueries('maps') queryClient.invalidateQueries('maps');
onClose() onClose();
} };
export type SimpleDialogProps = { export type SimpleDialogProps = {
mapId: number mapId: number;
onClose: () => void onClose: () => void;
} };
export default ActionDispatcher export default ActionDispatcher;

View File

@ -1,29 +1,29 @@
import React from 'react' import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { ErrorInfo } from '../../../../classes/client' import { ErrorInfo } from '../../../../classes/client';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import { SimpleDialogProps } from '..' import { SimpleDialogProps } from '..';
import { useStyles } from './style' import { useStyles } from './style';
import dayjs from 'dayjs' import dayjs from 'dayjs';
import { fetchMapById } from '../../../../redux/clientSlice' import { fetchMapById } from '../../../../redux/clientSlice';
import Paper from '@material-ui/core/Paper' import Paper from '@material-ui/core/Paper';
import Card from '@material-ui/core/Card' import Card from '@material-ui/core/Card';
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List' import List from '@material-ui/core/List';
const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId) const { map } = fetchMapById(mapId);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl() const intl = useIntl();
const classes = useStyles() const classes = useStyles();
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
setError(undefined) setError(undefined);
} };
return ( return (
<BaseDialog <BaseDialog
@ -175,7 +175,7 @@ const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement =
</Card> </Card>
</Paper> </Paper>
</BaseDialog> </BaseDialog>
) );
} };
export default InfoDialog export default InfoDialog;

View File

@ -1,5 +1,5 @@
import createStyles from '@material-ui/core/styles/createStyles' import createStyles from '@material-ui/core/styles/createStyles';
import makeStyles from '@material-ui/core/styles/makeStyles' import makeStyles from '@material-ui/core/styles/makeStyles';
export const useStyles = makeStyles(() => export const useStyles = makeStyles(() =>
createStyles({ createStyles({
@ -12,4 +12,4 @@ export const useStyles = makeStyles(() =>
width: '150px', width: '150px',
}, },
}) })
) );

View File

@ -1,68 +1,68 @@
import React from 'react' import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query' import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../../../classes/client' import Client, { ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice' import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import { handleOnMutationSuccess, SimpleDialogProps } from '..' import { handleOnMutationSuccess, SimpleDialogProps } from '..';
import { useStyles } from './style' import { useStyles } from './style';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel' import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox' import Checkbox from '@material-ui/core/Checkbox';
import TabContext from '@material-ui/lab/TabContext' import TabContext from '@material-ui/lab/TabContext';
import AppBar from '@material-ui/core/AppBar' import AppBar from '@material-ui/core/AppBar';
import TabList from '@material-ui/lab/TabList' import TabList from '@material-ui/lab/TabList';
import Tab from '@material-ui/core/Tab' import Tab from '@material-ui/core/Tab';
import TabPanel from '@material-ui/lab/TabPanel' import TabPanel from '@material-ui/lab/TabPanel';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
import TextareaAutosize from '@material-ui/core/TextareaAutosize' import TextareaAutosize from '@material-ui/core/TextareaAutosize';
const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId) const { map } = fetchMapById(mapId);
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false) const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const [activeTab, setActiveTab] = React.useState('1') const [activeTab, setActiveTab] = React.useState('1');
const queryClient = useQueryClient() const queryClient = useQueryClient();
const intl = useIntl() const intl = useIntl();
const classes = useStyles() const classes = useStyles();
const mutation = useMutation<void, ErrorInfo, boolean>( const mutation = useMutation<void, ErrorInfo, boolean>(
(model: boolean) => { (model: boolean) => {
return client.updateMapToPublic(mapId, model) return client.updateMapToPublic(mapId, model);
}, },
{ {
onSuccess: () => { onSuccess: () => {
setModel(model) setModel(model);
handleOnMutationSuccess(onClose, queryClient) handleOnMutationSuccess(onClose, queryClient);
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
setError(undefined) setError(undefined);
} };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
mutation.mutate(model) mutation.mutate(model);
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean): void => {
event.preventDefault() event.preventDefault();
setModel(checked) setModel(checked);
} };
const handleTabChange = (event, newValue) => { const handleTabChange = (event, newValue) => {
setActiveTab(newValue) setActiveTab(newValue);
} };
return ( return (
<div> <div>
@ -152,7 +152,7 @@ const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElemen
</div> </div>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default PublishDialog export default PublishDialog;

View File

@ -1,5 +1,5 @@
import createStyles from '@material-ui/core/styles/createStyles' import createStyles from '@material-ui/core/styles/createStyles';
import makeStyles from '@material-ui/core/styles/makeStyles' import makeStyles from '@material-ui/core/styles/makeStyles';
export const useStyles = makeStyles(() => export const useStyles = makeStyles(() =>
createStyles({ createStyles({
@ -9,4 +9,4 @@ export const useStyles = makeStyles(() =>
marging: '0px 10px', marging: '0px 10px',
}, },
}) })
) );

View File

@ -1,72 +1,72 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { useIntl } from 'react-intl' import { useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query' import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client' import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice' import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from '..' import { SimpleDialogProps, handleOnMutationSuccess } from '..';
import Input from '../../../form/input' import Input from '../../../form/input';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
export type RenameModel = { export type RenameModel = {
id: number id: number;
title: string title: string;
description?: string description?: string;
} };
const defaultModel: RenameModel = { title: '', description: '', id: -1 } const defaultModel: RenameModel = { title: '', description: '', id: -1 };
const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const service: Client = useSelector(activeInstance) const service: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<RenameModel>(defaultModel) const [model, setModel] = React.useState<RenameModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl() const intl = useIntl();
const queryClient = useQueryClient() const queryClient = useQueryClient();
const mutation = useMutation<RenameModel, ErrorInfo, RenameModel>( const mutation = useMutation<RenameModel, ErrorInfo, RenameModel>(
(model: RenameModel) => { (model: RenameModel) => {
const { id, ...rest } = model const { id, ...rest } = model;
return service.renameMap(id, rest).then(() => model) return service.renameMap(id, rest).then(() => model);
}, },
{ {
onSuccess: () => { onSuccess: () => {
handleOnMutationSuccess(onClose, queryClient) handleOnMutationSuccess(onClose, queryClient);
}, },
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
setModel(defaultModel) setModel(defaultModel);
setError(undefined) setError(undefined);
} };
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
mutation.mutate(model) mutation.mutate(model);
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value }) setModel({ ...model, [name as keyof BasicMapInfo]: value });
} };
const { map } = fetchMapById(mapId) const { map } = fetchMapById(mapId);
useEffect(() => { useEffect(() => {
if (open && map) { if (open && map) {
setModel(map) setModel(map);
} else { } else {
setModel(defaultModel) setModel(defaultModel);
setError(undefined) setError(undefined);
} }
}, [mapId]) }, [mapId]);
return ( return (
<div> <div>
@ -110,7 +110,7 @@ const RenameDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
</FormControl> </FormControl>
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default RenameDialog export default RenameDialog;

View File

@ -1,115 +1,115 @@
import React from 'react' import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQuery, useQueryClient } from 'react-query' import { useMutation, useQuery, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { ErrorInfo, Permission } from '../../../../classes/client' import Client, { ErrorInfo, Permission } from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice' import { activeInstance } from '../../../../redux/clientSlice';
import { SimpleDialogProps } from '..' import { SimpleDialogProps } from '..';
import BaseDialog from '../base-dialog' import BaseDialog from '../base-dialog';
import List from '@material-ui/core/List' import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText';
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import DeleteIcon from '@material-ui/icons/Delete' import DeleteIcon from '@material-ui/icons/Delete';
import Paper from '@material-ui/core/Paper' import Paper from '@material-ui/core/Paper';
import Select from '@material-ui/core/Select' import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField' import TextField from '@material-ui/core/TextField';
import FormControlLabel from '@material-ui/core/FormControlLabel' import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox' import Checkbox from '@material-ui/core/Checkbox';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
import { useStyles } from './style' import { useStyles } from './style';
import RoleIcon from '../../role-icon' import RoleIcon from '../../role-icon';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
type ShareModel = { type ShareModel = {
emails: string emails: string;
role: 'editor' | 'viewer' role: 'editor' | 'viewer';
message: string message: string;
} };
const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' } const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' };
const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => { const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl() const intl = useIntl();
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient() const queryClient = useQueryClient();
const classes = useStyles() const classes = useStyles();
const [showMessage, setShowMessage] = React.useState<boolean>(false) const [showMessage, setShowMessage] = React.useState<boolean>(false);
const [model, setModel] = React.useState<ShareModel>(defaultModel) const [model, setModel] = React.useState<ShareModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>() const [error, setError] = React.useState<ErrorInfo>();
const deleteMutation = useMutation( const deleteMutation = useMutation(
(email: string) => { (email: string) => {
return client.deleteMapPermission(mapId, email) return client.deleteMapPermission(mapId, email);
}, },
{ {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`) queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel) setModel(defaultModel);
}, },
onError: (error: ErrorInfo) => { onError: (error: ErrorInfo) => {
setError(error) setError(error);
}, },
} }
) );
const addMutation = useMutation( const addMutation = useMutation(
(model: ShareModel) => { (model: ShareModel) => {
const emails = model.emails.split("'") const emails = model.emails.split("'");
const permissions = emails.map((email) => { const permissions = emails.map((email) => {
return { email: email, role: model.role } return { email: email, role: model.role };
}) });
return client.addMapPermissions(mapId, model.message, permissions) return client.addMapPermissions(mapId, model.message, permissions);
}, },
{ {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`) queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel) setModel(defaultModel);
}, },
onError: (error: ErrorInfo) => { onError: (error: ErrorInfo) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnClose = (): void => { const handleOnClose = (): void => {
onClose() onClose();
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof ShareModel]: value }) setModel({ ...model, [name as keyof ShareModel]: value });
} };
const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => { const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.stopPropagation() event.stopPropagation();
addMutation.mutate(model) addMutation.mutate(model);
} };
const handleOnDeleteClick = ( const handleOnDeleteClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>, event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
email: string email: string
): void => { ): void => {
event.stopPropagation() event.stopPropagation();
deleteMutation.mutate(email) deleteMutation.mutate(email);
} };
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>( const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(
`perm-${mapId}`, `perm-${mapId}`,
() => { () => {
return client.fetchMapPermissions(mapId) return client.fetchMapPermissions(mapId);
} }
) );
const formatName = (perm: Permission): string => { const formatName = (perm: Permission): string => {
return perm.name ? `${perm.name}<${perm.email}>` : perm.email return perm.name ? `${perm.name}<${perm.email}>` : perm.email;
} };
return ( return (
<div> <div>
@ -160,7 +160,7 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
<FormControlLabel <FormControlLabel
value="start" value="start"
onChange={(event, value) => { onChange={(event, value) => {
setShowMessage(value) setShowMessage(value);
}} }}
style={{ fontSize: '5px' }} style={{ fontSize: '5px' }}
control={<Checkbox color="primary" />} control={<Checkbox color="primary" />}
@ -242,14 +242,14 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
</Tooltip> </Tooltip>
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
) );
})} })}
</List> </List>
</Paper> </Paper>
)} )}
</BaseDialog> </BaseDialog>
</div> </div>
) );
} };
export default ShareDialog export default ShareDialog;

View File

@ -1,5 +1,5 @@
import createStyles from '@material-ui/core/styles/createStyles' import createStyles from '@material-ui/core/styles/createStyles';
import makeStyles from '@material-ui/core/styles/makeStyles' import makeStyles from '@material-ui/core/styles/makeStyles';
export const useStyles = makeStyles(() => export const useStyles = makeStyles(() =>
createStyles({ createStyles({
@ -23,4 +23,4 @@ export const useStyles = makeStyles(() =>
minWidth: '850px', minWidth: '850px',
}, },
}) })
) );

View File

@ -1,30 +1,30 @@
import React from 'react' import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import Help from '@material-ui/icons/Help' import Help from '@material-ui/icons/Help';
import PolicyOutlined from '@material-ui/icons/PolicyOutlined' import PolicyOutlined from '@material-ui/icons/PolicyOutlined';
import FeedbackOutlined from '@material-ui/icons/FeedbackOutlined' import FeedbackOutlined from '@material-ui/icons/FeedbackOutlined';
import EmojiPeopleOutlined from '@material-ui/icons/EmailOutlined' import EmojiPeopleOutlined from '@material-ui/icons/EmailOutlined';
import EmailOutlined from '@material-ui/icons/EmailOutlined' import EmailOutlined from '@material-ui/icons/EmailOutlined';
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton';
import Menu from '@material-ui/core/Menu' import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem';
import Link from '@material-ui/core/Link' import Link from '@material-ui/core/Link';
import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemIcon from '@material-ui/core/ListItemIcon';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
const HelpMenu = (): React.ReactElement => { const HelpMenu = (): React.ReactElement => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null) const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl) const open = Boolean(anchorEl);
const intl = useIntl() const intl = useIntl();
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget) setAnchorEl(event.currentTarget);
} };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null) setAnchorEl(null);
} };
return ( return (
<span> <span>
@ -100,7 +100,7 @@ const HelpMenu = (): React.ReactElement => {
</MenuItem> </MenuItem>
</Menu> </Menu>
</span> </span>
) );
} };
export default HelpMenu export default HelpMenu;

View File

@ -1,96 +1,96 @@
import React, { ErrorInfo, ReactElement, useEffect } from 'react' import React, { ErrorInfo, ReactElement, useEffect } from 'react';
import clsx from 'clsx' import clsx from 'clsx';
import Drawer from '@material-ui/core/Drawer' import Drawer from '@material-ui/core/Drawer';
import AppBar from '@material-ui/core/AppBar' import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar' import Toolbar from '@material-ui/core/Toolbar';
import List from '@material-ui/core/List' import List from '@material-ui/core/List';
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton';
import { useStyles } from './style' import { useStyles } from './style';
import { MapsList } from './maps-list' import { MapsList } from './maps-list';
import { FormattedMessage, IntlProvider, useIntl } from 'react-intl' import { FormattedMessage, IntlProvider, useIntl } from 'react-intl';
import { useQuery, useMutation, useQueryClient } from 'react-query' import { useQuery, useMutation, useQueryClient } from 'react-query';
import { activeInstance } from '../../redux/clientSlice' import { activeInstance } from '../../redux/clientSlice';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import Client, { Label } from '../../classes/client' 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 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';
import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItem from '@material-ui/core/ListItem' import ListItem from '@material-ui/core/ListItem';
import AddCircleTwoTone from '@material-ui/icons/AddCircleTwoTone' import AddCircleTwoTone from '@material-ui/icons/AddCircleTwoTone';
import CloudUploadTwoTone from '@material-ui/icons/CloudUploadTwoTone' import CloudUploadTwoTone from '@material-ui/icons/CloudUploadTwoTone';
import DeleteOutlineTwoTone from '@material-ui/icons/DeleteOutlineTwoTone' import DeleteOutlineTwoTone from '@material-ui/icons/DeleteOutlineTwoTone';
import LabelTwoTone from '@material-ui/icons/LabelTwoTone' import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
import PersonOutlineTwoTone from '@material-ui/icons/PersonOutlineTwoTone' import PersonOutlineTwoTone from '@material-ui/icons/PersonOutlineTwoTone';
import PublicTwoTone from '@material-ui/icons/PublicTwoTone' import PublicTwoTone from '@material-ui/icons/PublicTwoTone';
import ScatterPlotTwoTone from '@material-ui/icons/ScatterPlotTwoTone' import ScatterPlotTwoTone from '@material-ui/icons/ScatterPlotTwoTone';
import ShareTwoTone from '@material-ui/icons/ShareTwoTone' import ShareTwoTone from '@material-ui/icons/ShareTwoTone';
import StarTwoTone from '@material-ui/icons/StarTwoTone' import StarTwoTone from '@material-ui/icons/StarTwoTone';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import Link from '@material-ui/core/Link' import Link from '@material-ui/core/Link';
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import logoIcon from '../../images/logo-small.svg' import logoIcon from '../../images/logo-small.svg';
import poweredByIcon from '../../images/pwrdby-white.svg' import poweredByIcon from '../../images/pwrdby-white.svg';
export type Filter = GenericFilter | LabelFilter export type Filter = GenericFilter | LabelFilter;
export interface GenericFilter { export interface GenericFilter {
type: 'public' | 'all' | 'starred' | 'shared' | 'label' | 'owned' type: 'public' | 'all' | 'starred' | 'shared' | 'label' | 'owned';
} }
export interface LabelFilter { export interface LabelFilter {
type: 'label' type: 'label';
label: Label label: Label;
} }
interface ToolbarButtonInfo { interface ToolbarButtonInfo {
filter: GenericFilter | LabelFilter filter: GenericFilter | LabelFilter;
label: string label: string;
icon: React.ReactElement icon: React.ReactElement;
} }
const MapsPage = (): ReactElement => { const MapsPage = (): ReactElement => {
const classes = useStyles() const classes = useStyles();
const [filter, setFilter] = React.useState<Filter>({ type: 'all' }) const [filter, setFilter] = React.useState<Filter>({ type: 'all' });
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient() const queryClient = useQueryClient();
const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined) const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined);
const intl = useIntl() const intl = useIntl();
useEffect(() => { useEffect(() => {
document.title = 'Maps | WiseMapping' document.title = 'Maps | WiseMapping';
}, []) }, []);
const mutation = useMutation((id: number) => client.deleteLabel(id), { const mutation = useMutation((id: number) => client.deleteLabel(id), {
onSuccess: () => queryClient.invalidateQueries('labels'), onSuccess: () => queryClient.invalidateQueries('labels'),
onError: (error) => { onError: (error) => {
console.error(`Unexpected error ${error}`) console.error(`Unexpected error ${error}`);
}, },
}) });
const handleMenuClick = (filter: Filter) => { const handleMenuClick = (filter: Filter) => {
queryClient.invalidateQueries('maps') queryClient.invalidateQueries('maps');
setFilter(filter) setFilter(filter);
} };
const handleLabelDelete = (id: number) => { const handleLabelDelete = (id: number) => {
mutation.mutate(id) mutation.mutate(id);
} };
const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', () => { const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', () => {
return client.fetchLabels() return client.fetchLabels();
}) });
const labels: Label[] = data ? data : [] const labels: Label[] = data ? data : [];
const filterButtons: ToolbarButtonInfo[] = [ const filterButtons: ToolbarButtonInfo[] = [
{ {
filter: { type: 'all' }, filter: { type: 'all' },
@ -117,7 +117,7 @@ const MapsPage = (): ReactElement => {
label: 'Public', label: 'Public',
icon: <PublicTwoTone color="secondary" />, icon: <PublicTwoTone color="secondary" />,
}, },
] ];
labels.forEach((l) => labels.forEach((l) =>
filterButtons.push({ filterButtons.push({
@ -125,11 +125,11 @@ const MapsPage = (): ReactElement => {
label: l.title, label: l.title,
icon: <LabelTwoTone style={{ color: l.color ? l.color : 'inherit' }} />, icon: <LabelTwoTone style={{ color: l.color ? l.color : 'inherit' }} />,
}) })
) );
// Configure using user settings ... // Configure using user settings ...
const appi18n = new AppI18n() const appi18n = new AppI18n();
const userLocale = appi18n.getUserLocale() const userLocale = appi18n.getUserLocale();
return ( return (
<IntlProvider <IntlProvider
@ -232,7 +232,7 @@ const MapsPage = (): ReactElement => {
(buttonInfo.filter as LabelFilter).label (buttonInfo.filter as LabelFilter).label
}`} }`}
/> />
) );
})} })}
</List> </List>
@ -251,46 +251,46 @@ const MapsPage = (): ReactElement => {
</main> </main>
</div> </div>
</IntlProvider> </IntlProvider>
) );
} };
interface ListItemProps { interface ListItemProps {
icon: React.ReactElement icon: React.ReactElement;
label: string label: string;
filter: Filter filter: Filter;
active?: Filter active?: Filter;
onClick: (filter: Filter) => void onClick: (filter: Filter) => void;
onDelete?: (id: number) => void onDelete?: (id: number) => void;
} }
const StyleListItem = (props: ListItemProps) => { const StyleListItem = (props: ListItemProps) => {
const icon = props.icon const icon = props.icon;
const label = props.label const label = props.label;
const filter = props.filter const filter = props.filter;
const activeFilter = props.active const activeFilter = props.active;
const onClick = props.onClick const onClick = props.onClick;
const onDeleteLabel = props.onDelete const onDeleteLabel = props.onDelete;
const isSelected = const isSelected =
activeFilter && activeFilter &&
activeFilter.type == filter.type && activeFilter.type == filter.type &&
(activeFilter.type != 'label' || (activeFilter.type != 'label' ||
(activeFilter as LabelFilter).label == (filter as LabelFilter).label) (activeFilter as LabelFilter).label == (filter as LabelFilter).label);
const handleOnClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, filter: Filter) => { const handleOnClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, filter: Filter) => {
event.stopPropagation() event.stopPropagation();
onClick(filter) onClick(filter);
} };
const handleOnDelete = ( const handleOnDelete = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>, event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
filter: Filter filter: Filter
) => { ) => {
event.stopPropagation() event.stopPropagation();
if (!onDeleteLabel) { if (!onDeleteLabel) {
throw 'Illegal state exeption' throw 'Illegal state exeption';
} }
onDeleteLabel((filter as LabelFilter).label.id) onDeleteLabel((filter as LabelFilter).label.id);
} };
return ( return (
<ListItem button selected={isSelected} onClick={(e) => handleOnClick(e, filter)}> <ListItem button selected={isSelected} onClick={(e) => handleOnClick(e, filter)}>
@ -308,7 +308,7 @@ const StyleListItem = (props: ListItemProps) => {
</ListItemSecondaryAction> </ListItemSecondaryAction>
)} )}
</ListItem> </ListItem>
) );
} };
export default MapsPage export default MapsPage;

View File

@ -1,55 +1,55 @@
import TranslateTwoTone from '@material-ui/icons/TranslateTwoTone' import TranslateTwoTone from '@material-ui/icons/TranslateTwoTone';
import React from 'react' import React from 'react';
import { useMutation, useQueryClient } from 'react-query' import { useMutation, useQueryClient } from 'react-query';
import Client from '../../../classes/client' import Client from '../../../classes/client';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import { activeInstance, fetchAccount } from '../../../redux/clientSlice' import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import { LocaleCode, Locales } from '../../../classes/app-i18n' import { LocaleCode, Locales } from '../../../classes/app-i18n';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import Menu from '@material-ui/core/Menu' import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem';
import Dialog from '@material-ui/core/Dialog' import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle' import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent' import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText' import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions' import DialogActions from '@material-ui/core/DialogActions';
import Divider from '@material-ui/core/Divider' import Divider from '@material-ui/core/Divider';
const LanguageMenu = (): React.ReactElement => { const LanguageMenu = (): React.ReactElement => {
const queryClient = useQueryClient() const queryClient = useQueryClient();
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null) const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false) const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false);
const open = Boolean(anchorEl) const open = Boolean(anchorEl);
const intl = useIntl() const intl = useIntl();
const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale), { const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale), {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries('account') queryClient.invalidateQueries('account');
handleClose() handleClose();
}, },
onError: (error) => { onError: (error) => {
console.error(`Unexpected error ${error}`) console.error(`Unexpected error ${error}`);
}, },
}) });
const handleMenu = (event: React.MouseEvent<HTMLElement>) => { const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget) setAnchorEl(event.currentTarget);
} };
const handleClose = () => { const handleClose = () => {
setAnchorEl(null) setAnchorEl(null);
} };
const handleOnClick = (event: React.MouseEvent<HTMLElement>) => { const handleOnClick = (event: React.MouseEvent<HTMLElement>) => {
const localeCode = event.target['id'] const localeCode = event.target['id'];
mutation.mutate(localeCode) mutation.mutate(localeCode);
} };
const accountInfo = fetchAccount() const accountInfo = fetchAccount();
return ( return (
<span> <span>
<Tooltip <Tooltip
@ -106,8 +106,8 @@ const LanguageMenu = (): React.ReactElement => {
<MenuItem <MenuItem
onClick={() => { onClick={() => {
handleClose() handleClose();
setHelpDialogOpen(true) setHelpDialogOpen(true);
}} }}
> >
<FormattedMessage id="language.help" defaultMessage="Help to Translate" /> <FormattedMessage id="language.help" defaultMessage="Help to Translate" />
@ -115,12 +115,12 @@ const LanguageMenu = (): React.ReactElement => {
</Menu> </Menu>
{openHelpDialog && <HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />} {openHelpDialog && <HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />}
</span> </span>
) );
} };
type HelpUsToTranslateDialogProp = { type HelpUsToTranslateDialogProp = {
onClose: () => void onClose: () => void;
} };
const HelpUsToTranslateDialog = ({ onClose }: HelpUsToTranslateDialogProp) => { const HelpUsToTranslateDialog = ({ onClose }: HelpUsToTranslateDialogProp) => {
return ( return (
<Dialog open={true} onClose={onClose}> <Dialog open={true} onClose={onClose}>
@ -137,7 +137,7 @@ const HelpUsToTranslateDialog = ({ onClose }: HelpUsToTranslateDialogProp) => {
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
) );
} };
export default LanguageMenu export default LanguageMenu;

View File

@ -1,54 +1,54 @@
import React, { useEffect, CSSProperties } from 'react' import React, { useEffect, CSSProperties } from 'react';
import { useStyles } from './styled' import { useStyles } from './styled';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import { activeInstance, fetchAccount } from '../../../redux/clientSlice' import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
import { useMutation, useQuery, useQueryClient } from 'react-query' import { useMutation, useQuery, useQueryClient } from 'react-query';
import Client, { ErrorInfo, MapInfo } from '../../../classes/client' import Client, { ErrorInfo, MapInfo } from '../../../classes/client';
import ActionChooser, { ActionType } from '../action-chooser' import ActionChooser, { ActionType } from '../action-chooser';
import ActionDispatcher from '../action-dispatcher' import ActionDispatcher from '../action-dispatcher';
import dayjs from 'dayjs' import dayjs from 'dayjs';
import { Filter, LabelFilter } from '..' import { Filter, LabelFilter } from '..';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import Table from '@material-ui/core/Table' import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody' import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell' import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead' import TableHead from '@material-ui/core/TableHead';
import TablePagination from '@material-ui/core/TablePagination' import TablePagination from '@material-ui/core/TablePagination';
import TableRow from '@material-ui/core/TableRow' import TableRow from '@material-ui/core/TableRow';
import TableSortLabel from '@material-ui/core/TableSortLabel' import TableSortLabel from '@material-ui/core/TableSortLabel';
import Toolbar from '@material-ui/core/Toolbar' import Toolbar from '@material-ui/core/Toolbar';
import Paper from '@material-ui/core/Paper' import Paper from '@material-ui/core/Paper';
import Checkbox from '@material-ui/core/Checkbox' import Checkbox from '@material-ui/core/Checkbox';
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
import InputBase from '@material-ui/core/InputBase' import InputBase from '@material-ui/core/InputBase';
import Link from '@material-ui/core/Link' import Link from '@material-ui/core/Link';
import LabelTwoTone from '@material-ui/icons/LabelTwoTone' import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
import DeleteOutlined from '@material-ui/icons/DeleteOutlined' import DeleteOutlined from '@material-ui/icons/DeleteOutlined';
import MoreHorizIcon from '@material-ui/icons/MoreHoriz' import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded' import StarRateRoundedIcon from '@material-ui/icons/StarRateRounded';
import SearchIcon from '@material-ui/icons/Search' import SearchIcon from '@material-ui/icons/Search';
// Load fromNow pluggin // Load fromNow pluggin
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime) dayjs.extend(relativeTime);
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) { function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (b[orderBy] < a[orderBy]) { if (b[orderBy] < a[orderBy]) {
return -1 return -1;
} }
if (b[orderBy] > a[orderBy]) { if (b[orderBy] > a[orderBy]) {
return 1 return 1;
} }
return 0 return 0;
} }
type Order = 'asc' | 'desc' type Order = 'asc' | 'desc';
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function getComparator<Key extends keyof any>( function getComparator<Key extends keyof any>(
@ -60,38 +60,38 @@ function getComparator<Key extends keyof any>(
) => number { ) => number {
return order === 'desc' return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy) ? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy);
} }
function stableSort<T>(array: T[], comparator: (a: T, b: T) => number) { function stableSort<T>(array: T[], comparator: (a: T, b: T) => number) {
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]) const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
stabilizedThis.sort((a, b) => { stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]) const order = comparator(a[0], b[0]);
if (order !== 0) return order if (order !== 0) return order;
return a[1] - b[1] return a[1] - b[1];
}) });
return stabilizedThis.map((el) => el[0]) return stabilizedThis.map((el) => el[0]);
} }
interface HeadCell { interface HeadCell {
id: keyof MapInfo id: keyof MapInfo;
label?: string label?: string;
numeric: boolean numeric: boolean;
style?: CSSProperties style?: CSSProperties;
} }
interface EnhancedTableProps { interface EnhancedTableProps {
classes: ReturnType<typeof useStyles> classes: ReturnType<typeof useStyles>;
numSelected: number numSelected: number;
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof MapInfo) => void onRequestSort: (event: React.MouseEvent<unknown>, property: keyof MapInfo) => void;
onSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void onSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
order: Order order: Order;
orderBy: string orderBy: string;
rowCount: number rowCount: number;
} }
function EnhancedTableHead(props: EnhancedTableProps) { function EnhancedTableHead(props: EnhancedTableProps) {
const intl = useIntl() const intl = useIntl();
const { const {
classes, classes,
@ -101,11 +101,11 @@ function EnhancedTableHead(props: EnhancedTableProps) {
numSelected, numSelected,
rowCount, rowCount,
onRequestSort, onRequestSort,
} = props } = props;
const createSortHandler = (property: keyof MapInfo) => (event: React.MouseEvent<unknown>) => { const createSortHandler = (property: keyof MapInfo) => (event: React.MouseEvent<unknown>) => {
onRequestSort(event, property) onRequestSort(event, property);
} };
const headCells: HeadCell[] = [ const headCells: HeadCell[] = [
{ {
@ -126,7 +126,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
label: intl.formatMessage({ id: 'map.last-update', defaultMessage: 'Last Update' }), label: intl.formatMessage({ id: 'map.last-update', defaultMessage: 'Last Update' }),
style: { width: '70px', whiteSpace: 'nowrap' }, style: { width: '70px', whiteSpace: 'nowrap' },
}, },
] ];
return ( return (
<TableHead> <TableHead>
@ -176,7 +176,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
)} )}
</TableSortLabel> </TableSortLabel>
</TableCell> </TableCell>
) );
})} })}
<TableCell <TableCell
@ -186,195 +186,195 @@ function EnhancedTableHead(props: EnhancedTableProps) {
></TableCell> ></TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
) );
} }
type ActionPanelState = { type ActionPanelState = {
el: HTMLElement | undefined el: HTMLElement | undefined;
mapId: number mapId: number;
} };
interface MapsListProps { interface MapsListProps {
filter: Filter filter: Filter;
} }
const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => boolean) => { const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => boolean) => {
return (mapInfo: MapInfo) => { return (mapInfo: MapInfo) => {
// Check for filter condition // Check for filter condition
let result = false let result = false;
switch (filter.type) { switch (filter.type) {
case 'all': case 'all':
result = true result = true;
break break;
case 'starred': case 'starred':
result = mapInfo.starred result = mapInfo.starred;
break break;
case 'owned': case 'owned':
result = mapInfo.role == 'owner' result = mapInfo.role == 'owner';
break break;
case 'shared': case 'shared':
result = mapInfo.role != 'owner' result = mapInfo.role != 'owner';
break break;
case 'label': case 'label':
result = result =
!mapInfo.labels || mapInfo.labels.includes((filter as LabelFilter).label.id) !mapInfo.labels || mapInfo.labels.includes((filter as LabelFilter).label.id);
break break;
case 'public': case 'public':
result = mapInfo.isPublic result = mapInfo.isPublic;
break break;
default: default:
result = false result = false;
} }
// Does it match search filter criteria... // Does it match search filter criteria...
if (search && result) { if (search && result) {
result = mapInfo.title.toLowerCase().indexOf(search.toLowerCase()) != -1 result = mapInfo.title.toLowerCase().indexOf(search.toLowerCase()) != -1;
} }
return result return result;
} };
} };
export const MapsList = (props: MapsListProps): React.ReactElement => { export const MapsList = (props: MapsListProps): React.ReactElement => {
const classes = useStyles() const classes = useStyles();
const [order, setOrder] = React.useState<Order>('asc') const [order, setOrder] = React.useState<Order>('asc');
const [filter, setFilter] = React.useState<Filter>({ type: 'all' }) const [filter, setFilter] = React.useState<Filter>({ type: 'all' });
const [orderBy, setOrderBy] = React.useState<keyof MapInfo>('lastModificationTime') const [orderBy, setOrderBy] = React.useState<keyof MapInfo>('lastModificationTime');
const [selected, setSelected] = React.useState<number[]>([]) const [selected, setSelected] = React.useState<number[]>([]);
const [searchCondition, setSearchCondition] = React.useState<string>('') const [searchCondition, setSearchCondition] = React.useState<string>('');
const [page, setPage] = React.useState(0) const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10) const [rowsPerPage, setRowsPerPage] = React.useState(10);
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const intl = useIntl() const intl = useIntl();
const queryClient = useQueryClient() const queryClient = useQueryClient();
// Configure locale ... // Configure locale ...
const account = fetchAccount() const account = fetchAccount();
if (account) { if (account) {
dayjs.locale(account.locale.code) dayjs.locale(account.locale.code);
} }
useEffect(() => { useEffect(() => {
setSelected([]) setSelected([]);
setPage(0) setPage(0);
setFilter(props.filter) setFilter(props.filter);
}, [props.filter.type, (props.filter as LabelFilter).label]) }, [props.filter.type, (props.filter as LabelFilter).label]);
const { isLoading, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => { const { isLoading, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
return client.fetchAllMaps() return client.fetchAllMaps();
}) });
const mapsInfo: MapInfo[] = data ? data.filter(mapsFilter(filter, searchCondition)) : [] const mapsInfo: MapInfo[] = data ? data.filter(mapsFilter(filter, searchCondition)) : [];
const [activeRowAction, setActiveRowAction] = React.useState<ActionPanelState | undefined>( const [activeRowAction, setActiveRowAction] = React.useState<ActionPanelState | undefined>(
undefined undefined
) );
type ActiveDialog = { type ActiveDialog = {
actionType: ActionType actionType: ActionType;
mapsId: number[] mapsId: number[];
} };
const [activeDialog, setActiveDialog] = React.useState<ActiveDialog | undefined>(undefined) const [activeDialog, setActiveDialog] = React.useState<ActiveDialog | undefined>(undefined);
const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof MapInfo) => { const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof MapInfo) => {
const isAsc = orderBy === property && order === 'asc' const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc') setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property) setOrderBy(property);
} };
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.target.checked) { if (event.target.checked) {
const newSelecteds = mapsInfo.map((n) => n.id) const newSelecteds = mapsInfo.map((n) => n.id);
setSelected(newSelecteds) setSelected(newSelecteds);
return return;
} }
setSelected([]) setSelected([]);
} };
const handleRowClick = (event: React.MouseEvent<unknown>, id: number): void => { const handleRowClick = (event: React.MouseEvent<unknown>, id: number): void => {
const selectedIndex = selected.indexOf(id) const selectedIndex = selected.indexOf(id);
let newSelected: number[] = [] let newSelected: number[] = [];
if (selectedIndex === -1) { if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id) newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) { } else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1)) newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) { } else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1)) newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) { } else if (selectedIndex > 0) {
newSelected = newSelected.concat( newSelected = newSelected.concat(
selected.slice(0, selectedIndex), selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1) selected.slice(selectedIndex + 1)
) );
} }
setSelected(newSelected) setSelected(newSelected);
} };
const handleChangePage = (event: unknown, newPage: number) => { const handleChangePage = (event: unknown, newPage: number) => {
setPage(newPage) setPage(newPage);
} };
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => { const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10)) setRowsPerPage(parseInt(event.target.value, 10));
setPage(0) setPage(0);
} };
const handleActionClick = (mapId: number): ((event) => void) => { const handleActionClick = (mapId: number): ((event) => void) => {
return (event): void => { return (event): void => {
setActiveRowAction({ setActiveRowAction({
mapId: mapId, mapId: mapId,
el: event.currentTarget, el: event.currentTarget,
}) });
event.stopPropagation() event.stopPropagation();
} };
} };
const starredMultation = useMutation<void, ErrorInfo, number>( const starredMultation = useMutation<void, ErrorInfo, number>(
(id: number) => { (id: number) => {
const map = mapsInfo.find((m) => m.id == id) const map = mapsInfo.find((m) => m.id == id);
return client.updateStarred(id, !map?.starred) return client.updateStarred(id, !map?.starred);
}, },
{ {
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries('maps') queryClient.invalidateQueries('maps');
}, },
onError: () => { onError: () => {
// setError(error); // setError(error);
}, },
} }
) );
const handleStarred = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, id: number) => { const handleStarred = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, id: number) => {
event.stopPropagation() event.stopPropagation();
starredMultation.mutate(id) starredMultation.mutate(id);
} };
const handleActionMenuClose = (action: ActionType): void => { const handleActionMenuClose = (action: ActionType): void => {
if (action) { if (action) {
const mapId = activeRowAction?.mapId const mapId = activeRowAction?.mapId;
setActiveDialog({ setActiveDialog({
actionType: action as ActionType, actionType: action as ActionType,
mapsId: [mapId] as number[], mapsId: [mapId] as number[],
}) });
} }
setActiveRowAction(undefined) setActiveRowAction(undefined);
} };
const handleOnSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleOnSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchCondition(e.target.value) setSearchCondition(e.target.value);
} };
const handleDeleteClick = () => { const handleDeleteClick = () => {
setActiveDialog({ setActiveDialog({
actionType: 'delete', actionType: 'delete',
mapsId: selected, mapsId: selected,
}) });
} };
const isSelected = (id: number) => selected.indexOf(id) !== -1 const isSelected = (id: number) => selected.indexOf(id) !== -1;
return ( return (
<div className={classes.root}> <div className={classes.root}>
<ActionChooser <ActionChooser
@ -480,8 +480,8 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
stableSort(mapsInfo, getComparator(order, orderBy)) stableSort(mapsInfo, getComparator(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row: MapInfo) => { .map((row: MapInfo) => {
const isItemSelected = isSelected(row.id) const isItemSelected = isSelected(row.id);
const labelId = row.id const labelId = row.id;
return ( return (
<TableRow <TableRow
@ -592,7 +592,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
</Tooltip> </Tooltip>
</TableCell> </TableCell>
</TableRow> </TableRow>
) );
}) })
)} )}
</TableBody> </TableBody>
@ -606,5 +606,5 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
mapsId={activeDialog ? activeDialog.mapsId : []} mapsId={activeDialog ? activeDialog.mapsId : []}
/> />
</div> </div>
) );
} };

View File

@ -1,7 +1,7 @@
import { fade } from '@material-ui/core/styles' import { fade } from '@material-ui/core/styles';
import { Theme } from '@material-ui/core/styles/createMuiTheme' import { Theme } from '@material-ui/core/styles/createMuiTheme';
import createStyles from '@material-ui/core/styles/createStyles' import createStyles from '@material-ui/core/styles/createStyles';
import makeStyles from '@material-ui/core/styles/makeStyles' import makeStyles from '@material-ui/core/styles/makeStyles';
export const useStyles = makeStyles((theme: Theme) => export const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -100,4 +100,4 @@ export const useStyles = makeStyles((theme: Theme) =>
}, },
}, },
}) })
) );

View File

@ -1,16 +1,16 @@
import React from 'react' import React from 'react';
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip';
import PersonSharpIcon from '@material-ui/icons/PersonSharp' import PersonSharpIcon from '@material-ui/icons/PersonSharp';
import EditSharpIcon from '@material-ui/icons/EditSharp' import EditSharpIcon from '@material-ui/icons/EditSharp';
import VisibilitySharpIcon from '@material-ui/icons/VisibilitySharp' import VisibilitySharpIcon from '@material-ui/icons/VisibilitySharp';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import { Role } from '../../../classes/client' import { Role } from '../../../classes/client';
type RoleIconProps = { type RoleIconProps = {
role: Role role: Role;
} };
const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => { const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => {
return ( return (
@ -42,7 +42,7 @@ const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => {
</Tooltip> </Tooltip>
)} )}
</span> </span>
) );
} };
export default RoleIcon export default RoleIcon;

View File

@ -1,8 +1,8 @@
import { Theme } from '@material-ui/core/styles/createMuiTheme' import { Theme } from '@material-ui/core/styles/createMuiTheme';
import createStyles from '@material-ui/core/styles/createStyles' import createStyles from '@material-ui/core/styles/createStyles';
import makeStyles from '@material-ui/core/styles/makeStyles' import makeStyles from '@material-ui/core/styles/makeStyles';
const drawerWidth = 300 const drawerWidth = 300;
export const useStyles = makeStyles((theme: Theme) => export const useStyles = makeStyles((theme: Theme) =>
createStyles({ createStyles({
@ -60,4 +60,4 @@ export const useStyles = makeStyles((theme: Theme) =>
padding: theme.spacing(3), padding: theme.spacing(3),
}, },
}) })
) );

View File

@ -1,60 +1,60 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl' import { FormattedMessage, useIntl } from 'react-intl';
import ReCAPTCHA from 'react-google-recaptcha' import ReCAPTCHA from 'react-google-recaptcha';
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom';
import Client, { ErrorInfo } from '../../classes/client' import Client, { ErrorInfo } from '../../classes/client';
import FormContainer from '../layout/form-container' import FormContainer from '../layout/form-container';
import Header from '../layout/header' import Header from '../layout/header';
import Footer from '../layout/footer' import Footer from '../layout/footer';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
import { useMutation } from 'react-query' import { useMutation } from 'react-query';
import { activeInstance } from '../../redux/clientSlice' import { activeInstance } from '../../redux/clientSlice';
import Input from '../form/input' import Input from '../form/input';
import GlobalError from '../form/global-error' import GlobalError from '../form/global-error';
import SubmitButton from '../form/submit-button' import SubmitButton from '../form/submit-button';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
import FormControl from '@material-ui/core/FormControl' import FormControl from '@material-ui/core/FormControl';
export type Model = { export type Model = {
email: string email: string;
lastname: string lastname: string;
firstname: string firstname: string;
password: string password: string;
recaptcha: string recaptcha: string;
} };
const defaultModel: Model = { email: '', lastname: '', firstname: '', password: '', recaptcha: '' } const defaultModel: Model = { email: '', lastname: '', firstname: '', password: '', recaptcha: '' };
const RegistrationForm = () => { const RegistrationForm = () => {
const [model, setModel] = useState<Model>(defaultModel) const [model, setModel] = useState<Model>(defaultModel);
const [error, setError] = useState<ErrorInfo>() const [error, setError] = useState<ErrorInfo>();
const history = useHistory() const history = useHistory();
const intl = useIntl() const intl = useIntl();
const Client: Client = useSelector(activeInstance) const Client: Client = useSelector(activeInstance);
const mutation = useMutation<void, ErrorInfo, Model>( const mutation = useMutation<void, ErrorInfo, Model>(
(model: Model) => Client.registerNewUser({ ...model }), (model: Model) => Client.registerNewUser({ ...model }),
{ {
onSuccess: () => history.push('/c/registration-success'), onSuccess: () => history.push('/c/registration-success'),
onError: (error) => { onError: (error) => {
setError(error) setError(error);
}, },
} }
) );
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => { const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault() event.preventDefault();
mutation.mutate(model) mutation.mutate(model);
} };
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => { const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault() event.preventDefault();
const name = event.target.name const name = event.target.name;
const value = event.target.value const value = event.target.value;
setModel({ ...model, [name as keyof Model]: value }) setModel({ ...model, [name as keyof Model]: value });
} };
return ( return (
<FormContainer> <FormContainer>
@ -125,8 +125,8 @@ const RegistrationForm = () => {
<ReCAPTCHA <ReCAPTCHA
sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
onChange={(value: string) => { onChange={(value: string) => {
model.recaptcha = value model.recaptcha = value;
setModel(model) setModel(model);
}} }}
/> />
</div> </div>
@ -147,13 +147,13 @@ const RegistrationForm = () => {
</form> </form>
</FormControl> </FormControl>
</FormContainer> </FormContainer>
) );
} };
const RegistationPage = (): React.ReactElement => { const RegistationPage = (): React.ReactElement => {
useEffect(() => { useEffect(() => {
document.title = 'Registration | WiseMapping' document.title = 'Registration | WiseMapping';
}) });
return ( return (
<div> <div>
@ -161,7 +161,7 @@ const RegistationPage = (): React.ReactElement => {
<RegistrationForm /> <RegistrationForm />
<Footer /> <Footer />
</div> </div>
) );
} };
export default RegistationPage export default RegistationPage;

View File

@ -1,16 +1,16 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl';
import FormContainer from '../layout/form-container' import FormContainer from '../layout/form-container';
import Header from '../layout/header' import Header from '../layout/header';
import Footer from '../layout/footer' import Footer from '../layout/footer';
import { Link as RouterLink } from 'react-router-dom' import { Link as RouterLink } from 'react-router-dom';
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button';
const RegistrationSuccessPage = (): React.ReactElement => { const RegistrationSuccessPage = (): React.ReactElement => {
useEffect(() => { useEffect(() => {
document.title = 'Reset Password | WiseMapping' document.title = 'Reset Password | WiseMapping';
}) });
return ( return (
<div> <div>
@ -43,7 +43,7 @@ const RegistrationSuccessPage = (): React.ReactElement => {
</FormContainer> </FormContainer>
<Footer /> <Footer />
</div> </div>
) );
} };
export default RegistrationSuccessPage export default RegistrationSuccessPage;

View File

@ -1,9 +1,9 @@
import React from 'react' import React from 'react';
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom';
import App from './app' import App from './app';
async function bootstrapApplication() { async function bootstrapApplication() {
ReactDOM.render(<App />, document.getElementById('root') as HTMLElement) ReactDOM.render(<App />, document.getElementById('root') as HTMLElement);
} }
bootstrapApplication() bootstrapApplication();

View File

@ -1,53 +1,53 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { createSlice } from '@reduxjs/toolkit' import { createSlice } from '@reduxjs/toolkit';
import { useQuery } from 'react-query' import { useQuery } from 'react-query';
import Client, { AccountInfo, ErrorInfo, MapInfo } from '../classes/client' import Client, { AccountInfo, ErrorInfo, MapInfo } from '../classes/client';
import MockClient from '../classes/client/mock-client' import MockClient from '../classes/client/mock-client';
import RestClient from '../classes/client/rest-client' import RestClient from '../classes/client/rest-client';
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux';
interface ConfigInfo { interface ConfigInfo {
apiBaseUrl: string apiBaseUrl: string;
} }
class RutimeConfig { class RutimeConfig {
private config: ConfigInfo private config: ConfigInfo;
load() { load() {
// Config can be inserted in the html page to define the global properties ... // Config can be inserted in the html page to define the global properties ...
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
this.config = (window as any).serverconfig this.config = (window as any).serverconfig;
return this return this;
} }
buildClient(): Client { buildClient(): Client {
let result: Client let result: Client;
if (this.config) { if (this.config) {
result = new RestClient(this.config.apiBaseUrl, () => { result = new RestClient(this.config.apiBaseUrl, () => {
sessionExpired() sessionExpired();
}) });
console.log('Service using rest client. ' + JSON.stringify(this.config)) console.log('Service using rest client. ' + JSON.stringify(this.config));
} else { } else {
console.log('Warning:Service using mockservice client') console.log('Warning:Service using mockservice client');
result = new MockClient() result = new MockClient();
} }
return result return result;
} }
} }
export interface ClientStatus { export interface ClientStatus {
state: 'healthy' | 'session-expired' state: 'healthy' | 'session-expired';
msg?: string msg?: string;
} }
export interface ClientState { export interface ClientState {
instance: Client instance: Client;
status: ClientStatus status: ClientStatus;
} }
const initialState: ClientState = { const initialState: ClientState = {
instance: new RutimeConfig().load().buildClient(), instance: new RutimeConfig().load().buildClient(),
status: { state: 'healthy' }, status: { state: 'healthy' },
} };
export const clientSlice = createSlice({ export const clientSlice = createSlice({
name: 'client', name: 'client',
@ -57,45 +57,45 @@ export const clientSlice = createSlice({
state.status = { state.status = {
state: 'session-expired', state: 'session-expired',
msg: 'Sessions has expired. You need to login again', msg: 'Sessions has expired. You need to login again',
} };
}, },
}, },
}) });
type MapLoadResult = { type MapLoadResult = {
isLoading: boolean isLoading: boolean;
error: ErrorInfo | null error: ErrorInfo | null;
map: MapInfo | null map: MapInfo | null;
} };
export const fetchMapById = (id: number): MapLoadResult => { export const fetchMapById = (id: number): MapLoadResult => {
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const { isLoading, error, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => { const { isLoading, error, data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
return client.fetchAllMaps() return client.fetchAllMaps();
}) });
const result = data?.find((m) => m.id == id) const result = data?.find((m) => m.id == id);
const map = result || null const map = result || null;
return { isLoading: isLoading, error: error, map: map } return { isLoading: isLoading, error: error, map: map };
} };
export const fetchAccount = (): AccountInfo | undefined => { export const fetchAccount = (): AccountInfo | undefined => {
const client: Client = useSelector(activeInstance) const client: Client = useSelector(activeInstance);
const { data } = useQuery<unknown, ErrorInfo, AccountInfo>('account', () => { const { data } = useQuery<unknown, ErrorInfo, AccountInfo>('account', () => {
return client.fetchAccountInfo() return client.fetchAccountInfo();
}) });
return data return data;
} };
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const activeInstance = (state: any): Client => { export const activeInstance = (state: any): Client => {
return state.client.instance return state.client.instance;
} };
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const activeInstanceStatus = (state: any): ClientStatus => { export const activeInstanceStatus = (state: any): ClientStatus => {
return state.client.status return state.client.status;
} };
export const { sessionExpired } = clientSlice.actions export const { sessionExpired } = clientSlice.actions;
export default clientSlice.reducer export default clientSlice.reducer;

View File

@ -1,11 +1,11 @@
import { configureStore } from '@reduxjs/toolkit' import { configureStore } from '@reduxjs/toolkit';
import clientReducer from './clientSlice' import clientReducer from './clientSlice';
// Create Service object... // Create Service object...
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
client: clientReducer, client: clientReducer,
}, },
}) });
export default store export default store;

View File

@ -1,4 +1,4 @@
import createMuiTheme from '@material-ui/core/styles/createMuiTheme' import createMuiTheme from '@material-ui/core/styles/createMuiTheme';
const theme = createMuiTheme({ const theme = createMuiTheme({
overrides: { overrides: {
@ -71,6 +71,6 @@ const theme = createMuiTheme({
contrastText: '#FFFFFF', contrastText: '#FFFFFF',
}, },
}, },
}) });
export { theme } export { theme };