Merged in feat/prettier (pull request #2)

Adding prettier and pre-commit + pre-push hooks

Approved-by: Paulo Veiga
This commit is contained in:
Juan Allo 2021-02-23 06:57:15 +00:00 committed by Paulo Veiga
commit b63d7ee665
77 changed files with 4683 additions and 3415 deletions

View File

@ -5,6 +5,7 @@
},
"extends": [
"eslint:recommended",
"prettier",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
node_modules
public
dist
lang
coverage
*.json

7
.prettierrc.json Normal file
View File

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

View File

@ -4,12 +4,15 @@
"bootstrap": "lerna bootstrap",
"build": "lerna run build",
"clean": "lerna clean && rm -rf node_modules",
"lint": "lerna run lint",
"lint": "lerna run lint --stream",
"test": "lerna run test --stream"
},
"private": true,
"devDependencies": {
"lerna": "^3.16.4"
"husky": "4",
"lerna": "^3.16.4",
"lint-staged": "^10.5.4",
"prettier": "^2.2.1"
},
"workspaces": [
"packages/*"
@ -37,5 +40,16 @@
]
},
"homepage": "http://localhost:8080/react",
"license": "https://wisemapping.atlassian.net/wiki/spaces/WS/pages/524357/WiseMapping+Public+License+Version+1.0+WPL"
"license": "https://wisemapping.atlassian.net/wiki/spaces/WS/pages/524357/WiseMapping+Public+License+Version+1.0+WPL",
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "yarn lint && yarn test"
}
},
"lint-staged": {
"**/*.{ts,tsx}": [
"prettier --write"
]
}
}

View File

@ -15,8 +15,10 @@
"@typescript-eslint/eslint-plugin": "^4.8.1",
"@typescript-eslint/parser": "^4.8.1",
"eslint": "^7.14.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"prettier": "^2.2.1",
"ts-loader": "^8.0.11",
"ts-node": "^9.0.0",
"typescript": "^4.1.2"

View File

@ -1,8 +1,6 @@
import React from 'react';
import { StyledCanvas } from './styled';
import React from 'react'
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`
height: 100%
width: 100%;
flex: 1;
`;
`

View File

@ -1,8 +1,6 @@
import React from 'react';
import { StyledFooter } from './styled';
import React from 'react'
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 { times } from '../../size';
import styled from 'styled-components'
import { times } from '../../size'
export const StyledFooter = styled.div`
height: ${times(10)};
width: 100%;
border: 1px solid black;
`;
height: ${times(10)};
width: 100%;
border: 1px solid black;
`

View File

@ -1,15 +1,15 @@
import React from 'react';
import Footer from '../footer';
import TopBar from '../top-bar';
import Canvas from '../canvas';
import { StyledFrame } from './styled';
import React from 'react'
import Footer from '../footer'
import TopBar from '../top-bar'
import Canvas from '../canvas'
import { StyledFrame } from './styled'
const Frame = (): React.ReactElement => (
<StyledFrame>
<TopBar />
<Canvas />
<Footer />
</StyledFrame>
);
<StyledFrame>
<TopBar />
<Canvas />
<Footer />
</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`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
`;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
`

View File

@ -1,8 +1,6 @@
import React from 'react';
import React from 'react'
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 { times } from '../../size';
import styled from 'styled-components'
import { times } from '../../size'
export const StyledTopBar = styled.div`
height: ${times(10)};
width: 100%;
border: 1px solid black;
`;
height: ${times(10)};
width: 100%;
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,10 +1,9 @@
const unit = 4 // pixels
const unit = 4; // pixels
export const XS = '4px'
export const S = '8px'
export const M = '16px'
export const L = '24px'
export const XL = '24px'
export const XS = '4px';
export const S = '8px';
export const M = '16px';
export const L = '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", () => {
beforeEach(() => {
cy.visit("http://localhost:3000/c/maps");
});
context('Maps Page', () => {
beforeEach(() => {
cy.visit('http://localhost:3000/c/maps')
})
it("should load the maps page", () => {
MapsPage.isLoaded();
});
it('should load the maps page', () => {
MapsPage.isLoaded()
})
it("should open the create dialog", () => {
MapsPage.create();
MapsPage.isCreateDialogVisible();
});
});
it('should open the create dialog', () => {
MapsPage.create()
MapsPage.isCreateDialogVisible()
})
})

View File

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

View File

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

View File

@ -29,11 +29,13 @@
"css-loader": "^5.0.1",
"cypress": "^6.5.0",
"eslint": "^7.14.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"file-loader": "^6.2.0",
"html-webpack-dynamic-env-plugin": "^0.0.2",
"html-webpack-plugin": "^5.1.0",
"prettier": "^2.2.1",
"sass-loader": "^10.1.0",
"style-loader": "^2.0.0",
"ts-loader": "^8.0.11",

View File

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

View File

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

View File

@ -1,65 +1,76 @@
import React, { ReactElement } from 'react';
import { IntlProvider } from 'react-intl';
import { Route, Switch, Redirect, BrowserRouter as Router } from 'react-router-dom';
import React, { ReactElement } from 'react'
import { IntlProvider } from 'react-intl'
import { Route, Switch, Redirect, BrowserRouter as Router } from 'react-router-dom'
import RegistrationSuccessPage from './components/registration-success-page';
import ForgotPasswordSuccessPage from './components/forgot-password-success-page';
import RegistationPage from './components/registration-page';
import LoginPage from './components/login-page';
import store from "./redux/store";
import { ForgotPasswordPage } from './components/forgot-password-page';
import { Provider } from 'react-redux';
import { QueryClient, QueryClientProvider } from 'react-query';
import RegistrationSuccessPage from './components/registration-success-page'
import ForgotPasswordSuccessPage from './components/forgot-password-success-page'
import RegistationPage from './components/registration-page'
import LoginPage from './components/login-page'
import store from './redux/store'
import { ForgotPasswordPage } from './components/forgot-password-page'
import { Provider } from 'react-redux'
import { QueryClient, QueryClientProvider } from 'react-query'
import { theme } from './theme'
import AppI18n, { Locales } from './classes/app-i18n';
import MapsPage from './components/maps-page';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from '@material-ui/core/styles';
import AppI18n, { Locales } from './classes/app-i18n'
import MapsPage from './components/maps-page'
import CssBaseline from '@material-ui/core/CssBaseline'
import { ThemeProvider } from '@material-ui/core/styles'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchIntervalInBackground: false,
staleTime: 5 * 1000 * 60 // 10 minutes
}
}
});
defaultOptions: {
queries: {
refetchIntervalInBackground: false,
staleTime: 5 * 1000 * 60, // 10 minutes
},
},
})
const App = ():ReactElement => {
const appi18n = new AppI18n();
const locale = appi18n.getBrowserLocale();
const App = (): ReactElement => {
const appi18n = new AppI18n()
const locale = appi18n.getBrowserLocale()
return locale.message ? (
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<IntlProvider locale={locale.code} defaultLocale={Locales.EN.code} messages={locale.message as Record<string, string> }>
<CssBaseline />
<ThemeProvider theme={theme}>
<Router>
<Switch>
<Route exact path="/">
<Redirect to="/c/login" />
</Route>
<Route path="/c/login" component={LoginPage} />
<Route path="/c/registration">
<RegistationPage />
</Route>
<Route path="/c/registration-success" component={RegistrationSuccessPage} />
<Route path="/c/forgot-password">
<ForgotPasswordPage />
</Route>
<Route path="/c/forgot-password-success" component={ForgotPasswordSuccessPage} />
<Route path="/c/maps/">
<MapsPage />
</Route>
</Switch>
</Router>
</ThemeProvider>
</IntlProvider>
</QueryClientProvider>
</Provider>
) : (<div>Loading ... </div>)
return locale.message ? (
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<IntlProvider
locale={locale.code}
defaultLocale={Locales.EN.code}
messages={locale.message as Record<string, string>}
>
<CssBaseline />
<ThemeProvider theme={theme}>
<Router>
<Switch>
<Route exact path="/">
<Redirect to="/c/login" />
</Route>
<Route path="/c/login" component={LoginPage} />
<Route path="/c/registration">
<RegistationPage />
</Route>
<Route
path="/c/registration-success"
component={RegistrationSuccessPage}
/>
<Route path="/c/forgot-password">
<ForgotPasswordPage />
</Route>
<Route
path="/c/forgot-password-success"
component={ForgotPasswordSuccessPage}
/>
<Route path="/c/maps/">
<MapsPage />
</Route>
</Switch>
</Router>
</ThemeProvider>
</IntlProvider>
</QueryClientProvider>
</Provider>
) : (
<div>Loading ... </div>
)
}
export default App;
export default App

View File

@ -1,48 +1,46 @@
import { fetchAccount } from './../../redux/clientSlice';
import 'dayjs/locale/fr';
import 'dayjs/locale/en';
import 'dayjs/locale/es';
import { fetchAccount } from './../../redux/clientSlice'
import 'dayjs/locale/fr'
import 'dayjs/locale/en'
import 'dayjs/locale/es'
export class Locale {
code: LocaleCode;
label: string;
message: Record<string, string> ;
code: LocaleCode
label: string
message: Record<string, string>
constructor(code: LocaleCode, label: string, message: unknown) {
this.code = code;
this.label = label;
this.message = message as Record<string, string>;
this.code = code
this.label = label
this.message = message as Record<string, string>
}
}
export default class AppI18n {
public getUserLocale(): Locale {
const account = fetchAccount();
return account ? account.locale : this.getBrowserLocale();
const account = fetchAccount()
return account ? account.locale : this.getBrowserLocale()
}
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 ...
localeCode = localeCode.split('-')[0];
localeCode = localeCode.split('-')[0]
let result = Locales.EN;
let result = Locales.EN
try {
result = localeFromStr(localeCode)
} 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')),
ES: new Locale('es', 'Español', require('./../../compiled-lang/es.json')),
DE: new Locale('fr', 'Français', require('./../../compiled-lang/fr.json')),
@ -50,15 +48,13 @@ export const Locales =
}
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) {
throw `Language code could not be found in list of default supported: + ${code}`
}
return result;
return result
}

View File

@ -1,20 +1,20 @@
import { useSelector } from 'react-redux';
import React from "react";
import { activeInstanceStatus, ClientStatus } from '../../../redux/clientSlice';
import { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import Alert from '@material-ui/lab/Alert';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import AlertTitle from '@material-ui/lab/AlertTitle';
import { useSelector } from 'react-redux'
import React from 'react'
import { activeInstanceStatus, ClientStatus } from '../../../redux/clientSlice'
import { FormattedMessage } from 'react-intl'
import Dialog from '@material-ui/core/Dialog'
import DialogTitle from '@material-ui/core/DialogTitle'
import DialogContent from '@material-ui/core/DialogContent'
import Alert from '@material-ui/lab/Alert'
import DialogActions from '@material-ui/core/DialogActions'
import Button from '@material-ui/core/Button'
import AlertTitle from '@material-ui/lab/AlertTitle'
const ClientHealthSentinel = (): React.ReactElement => {
const status: ClientStatus = useSelector(activeInstanceStatus);
const status: ClientStatus = useSelector(activeInstanceStatus)
const handleOnClose = () => {
window.location.href = '/c/login';
window.location.href = '/c/login'
}
return (
@ -23,30 +23,33 @@ const ClientHealthSentinel = (): React.ReactElement => {
open={status.state != 'healthy'}
onClose={handleOnClose}
maxWidth="sm"
fullWidth={true}>
fullWidth={true}
>
<DialogTitle>
<FormattedMessage id="expired.title" defaultMessage="Your session has expired" />
<FormattedMessage
id="expired.title"
defaultMessage="Your session has expired"
/>
</DialogTitle>
<DialogContent>
<Alert severity="error">
<AlertTitle><FormattedMessage id="expired.description" defaultMessage="Your current session has expired. Please, sign in and try again." /></AlertTitle>
<AlertTitle>
<FormattedMessage
id="expired.description"
defaultMessage="Your current session has expired. Please, sign in and try again."
/>
</AlertTitle>
</Alert>
</DialogContent>
<DialogActions>
<Button
type="button"
color="primary"
size="medium"
onClick={handleOnClose} >
<Button type="button" color="primary" size="medium" onClick={handleOnClose}>
<FormattedMessage id="action.close-button" defaultMessage="Close" />
</Button>
</DialogActions>
</Dialog>
</div>
)
};
export default ClientHealthSentinel;
}
export default ClientHealthSentinel

View File

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

View File

@ -1,13 +1,21 @@
import Client, { AccountInfo, BasicMapInfo, ChangeHistory, ImportMapInfo, Label, MapInfo, NewUser, Permission } from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n';
import Client, {
AccountInfo,
BasicMapInfo,
ChangeHistory,
ImportMapInfo,
Label,
MapInfo,
NewUser,
Permission,
} from '..'
import { LocaleCode, localeFromStr } from '../../app-i18n'
class MockClient implements Client {
private maps: MapInfo[] = [];
private labels: Label[] = [];
private permissionsByMap: Map<number, Permission[]> = new Map();
private maps: MapInfo[] = []
private labels: Label[] = []
private permissionsByMap: Map<number, Permission[]> = new Map()
constructor() {
// Remove, just for develop ....
function createMapInfo(
id: number,
@ -22,251 +30,298 @@ class MockClient implements Client {
isPublic: boolean,
role: 'owner' | 'viewer' | 'editor'
): MapInfo {
return { id, title, labels, createdBy: creator, creationTime, lastModificationBy: modifiedByUser, lastModificationTime: modifiedTime, starred, description, isPublic, role };
return {
id,
title,
labels,
createdBy: creator,
creationTime,
lastModificationBy: modifiedByUser,
lastModificationTime: modifiedTime,
starred,
description,
isPublic,
role,
}
}
this.maps = [
createMapInfo(1, true, "El Mapa", [], "Paulo", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", true, 'owner'),
createMapInfo(11, false, "El Mapa3", [1, 2, 3], "Paulo3", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", false, 'editor'),
createMapInfo(12, false, "El Mapa3", [1, 2, 3], "Paulo3", "2008-06-02T00:00:00Z", "Berna", "2008-06-02T00:00:00Z", "", false, 'editor')
];
createMapInfo(
1,
true,
'El Mapa',
[],
'Paulo',
'2008-06-02T00:00:00Z',
'Berna',
'2008-06-02T00:00:00Z',
'',
true,
'owner'
),
createMapInfo(
11,
false,
'El Mapa3',
[1, 2, 3],
'Paulo3',
'2008-06-02T00:00:00Z',
'Berna',
'2008-06-02T00:00:00Z',
'',
false,
'editor'
),
createMapInfo(
12,
false,
'El Mapa3',
[1, 2, 3],
'Paulo3',
'2008-06-02T00:00:00Z',
'Berna',
'2008-06-02T00:00:00Z',
'',
false,
'editor'
),
]
this.labels = [
{ id: 1, title: "Red Label", iconName: "", color: 'red' },
{ id: 2, title: "Blue Label", iconName: "", color: 'blue' }
];
{ id: 1, title: 'Red Label', iconName: '', color: 'red' },
{ id: 2, title: 'Blue Label', iconName: '', color: 'blue' },
]
}
deleteMapPermission(id: number, email: string): Promise<void> {
let perm = this.permissionsByMap.get(id) || [];
perm = perm.filter(p=>p.email!=email)
this.permissionsByMap.set(id, perm);
return Promise.resolve();
let perm = this.permissionsByMap.get(id) || []
perm = perm.filter((p) => p.email != email)
this.permissionsByMap.set(id, perm)
return Promise.resolve()
}
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
let perm = this.permissionsByMap.get(id) || [];
perm = perm.concat(permissions);
this.permissionsByMap.set(id, perm);
let perm = this.permissionsByMap.get(id) || []
perm = perm.concat(permissions)
this.permissionsByMap.set(id, perm)
console.log(`Message ${message}`)
return Promise.resolve();
return Promise.resolve()
}
fetchMapPermissions(id: number): Promise<Permission[]> {
let perm = this.permissionsByMap.get(id);
let perm = this.permissionsByMap.get(id)
if (!perm) {
perm = [{
name: 'Cosme Editor',
email: 'pepe@example.com',
role: 'editor'
}, {
name: 'Cosme Owner',
email: 'pepe2@example.com',
role: 'owner'
}, {
name: 'Cosme Viewer',
email: 'pepe3@example.com',
role: 'viewer'
}];
this.permissionsByMap.set(id, perm);
perm = [
{
name: 'Cosme Editor',
email: 'pepe@example.com',
role: 'editor',
},
{
name: 'Cosme Owner',
email: 'pepe2@example.com',
role: 'owner',
},
{
name: 'Cosme Viewer',
email: 'pepe3@example.com',
role: 'viewer',
},
]
this.permissionsByMap.set(id, perm)
}
return Promise.resolve(perm);
return Promise.resolve(perm)
}
deleteAccount(): Promise<void> {
return Promise.resolve();
return Promise.resolve()
}
updateAccountInfo(firstname: string, lastname: string): Promise<void> {
console.log("firstname:" + firstname, +lastname)
return Promise.resolve();
console.log('firstname:' + firstname, +lastname)
return Promise.resolve()
}
updateAccountPassword(pasword: string): Promise<void> {
console.log("password:" + pasword)
return Promise.resolve();
console.log('password:' + pasword)
return Promise.resolve()
}
updateAccountLanguage(locale: LocaleCode): Promise<void> {
localStorage.setItem('locale', locale);
return Promise.resolve();
localStorage.setItem('locale', locale)
return Promise.resolve()
}
importMap(model: ImportMapInfo): Promise<number> {
console.log("model:" + model);
return Promise.resolve(10);
console.log('model:' + model)
return Promise.resolve(10)
}
fetchAccountInfo(): Promise<AccountInfo> {
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({
firstname: 'Costme',
lastname: 'Fulanito',
email: 'test@example.com',
locale: localeFromStr(locale)
});
locale: localeFromStr(locale),
})
}
deleteMaps(ids: number[]): Promise<void> {
ids.forEach(id => this.deleteMap(id));
return Promise.resolve();
ids.forEach((id) => this.deleteMap(id))
return Promise.resolve()
}
revertHistory(id: number, cid: number): Promise<void> {
console.log("model:" + id + cid);
return Promise.resolve();
console.log('model:' + id + cid)
return Promise.resolve()
}
createMap(map: BasicMapInfo): Promise<number> {
throw new Error("Method not implemented." + map);
throw new Error('Method not implemented.' + map)
}
fetchLabels(): Promise<Label[]> {
console.log("Fetching labels from server")
return Promise.resolve(this.labels);
console.log('Fetching labels from server')
return Promise.resolve(this.labels)
}
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) {
mapInfo.isPublic = isPublic;
mapInfo.isPublic = isPublic
}
return Promise.resolve();
return Promise.resolve()
}
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) {
console.log(`Could not find the map iwth id ${id}`);
return Promise.reject();
console.log(`Could not find the map iwth id ${id}`)
return Promise.reject()
}
mapInfo.starred = starred;
return Promise.resolve();
mapInfo.starred = starred
return Promise.resolve()
}
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) {
this.maps = this.maps.map(m => {
const result = m;
this.maps = this.maps.map((m) => {
const result = m
if (m.id == id) {
result.description = basicInfo.description ? basicInfo.description : '';
result.title = basicInfo.title;
result.description = basicInfo.description ? basicInfo.description : ''
result.title = basicInfo.title
}
return result;
return result
})
return Promise.resolve();
return Promise.resolve()
} 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 ')
return Promise.reject({
msg: 'Map already exists ...' + basicInfo.title,
fields: fieldErrors
fields: fieldErrors,
})
}
}
fetchHistory(id: number): Promise<ChangeHistory[]> {
console.log(`Fetching history for ${id}`)
const result = [{
id: 1,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z'
},
{
id: 2,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z'
}
,
{
id: 3,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z'
},
{
id: 4,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z'
},
{
id: 5,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z'
},
{
id: 6,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z'
},
{
id: 7,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z'
}
const result = [
{
id: 1,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z',
},
{
id: 2,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z',
},
{
id: 3,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z',
},
{
id: 4,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z',
},
{
id: 5,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z',
},
{
id: 6,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z',
},
{
id: 7,
lastModificationBy: 'Paulo',
lastModificationTime: '2008-06-02T00:00:00Z',
},
]
return Promise.resolve(result);
return Promise.resolve(result)
}
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) {
const newMap: MapInfo = {
id: Math.random() * 1000,
description: String(basicInfo.description),
title: basicInfo.title,
starred: false,
createdBy: "current user",
createdBy: 'current user',
labels: [],
lastModificationTime: "2008-06-02T00:00:00Z",
lastModificationBy: "Berna",
creationTime: "2008-06-02T00:00:00Z",
lastModificationTime: '2008-06-02T00:00:00Z',
lastModificationBy: 'Berna',
creationTime: '2008-06-02T00:00:00Z',
isPublic: false,
role: 'owner'
};
this.maps.push(newMap);
return Promise.resolve(newMap.id);
role: 'owner',
}
this.maps.push(newMap)
return Promise.resolve(newMap.id)
} 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 ')
return Promise.reject({
msg: 'Maps name must be unique:' + basicInfo.title,
fields: fieldErrors
fields: fieldErrors,
})
}
}
deleteLabel(id: number): Promise<void> {
this.labels = this.labels.filter(l => l.id != id);
console.log("Label delete:" + this.labels);
return Promise.resolve();
this.labels = this.labels.filter((l) => l.id != id)
console.log('Label delete:' + this.labels)
return Promise.resolve()
}
deleteMap(id: number): Promise<void> {
this.maps = this.maps.filter(m => m.id != id);
return Promise.resolve();
this.maps = this.maps.filter((m) => m.id != id)
return Promise.resolve()
}
registerNewUser(user: NewUser): Promise<void> {
console.log("user:" + user)
return Promise.resolve();
console.log('user:' + user)
return Promise.resolve()
}
fetchAllMaps(): Promise<MapInfo[]> {
console.log("Fetching maps from server")
return Promise.resolve(this.maps);
console.log('Fetching maps from server')
return Promise.resolve(this.maps)
}
resetPassword(email: string): Promise<void> {
console.log("email:" + email)
return Promise.resolve();
console.log('email:' + email)
return Promise.resolve()
}
}
export default MockClient;
export default MockClient

View File

@ -1,475 +1,548 @@
import axios from 'axios';
import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label, ChangeHistory, AccountInfo, ImportMapInfo, Permission } from '..';
import { LocaleCode, localeFromStr, Locales } from '../../app-i18n';
import axios from 'axios'
import Client, {
ErrorInfo,
MapInfo,
BasicMapInfo,
NewUser,
Label,
ChangeHistory,
AccountInfo,
ImportMapInfo,
Permission,
} from '..'
import { LocaleCode, localeFromStr, Locales } from '../../app-i18n'
export default class RestClient implements Client {
private baseUrl: string;
private baseUrl: string
private sessionExpired: () => void
constructor(baseUrl: string, sessionExpired: () => void) {
this.baseUrl = baseUrl;
this.sessionExpired = sessionExpired;
this.baseUrl = baseUrl
this.sessionExpired = sessionExpired
}
deleteMapPermission(id: number, email: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.delete(`${this.baseUrl}/c/restful/maps/${id}/collabs?email=${email}`,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.delete(`${this.baseUrl}/c/restful/maps/${id}/collabs?email=${email}`, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
addMapPermissions(id: number, message: string, permissions: Permission[]): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/c/restful/maps/${id}/collabs/`,
{
messasge: message,
collaborations: permissions
},
{ headers: { 'Content-Type': 'application/json' } }
).then(() => {
// All was ok, let's sent to success page ...;
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.put(
`${this.baseUrl}/c/restful/maps/${id}/collabs/`,
{
messasge: message,
collaborations: permissions,
},
{ headers: { 'Content-Type': 'application/json' } }
)
.then(() => {
// All was ok, let's sent to success page ...;
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
fetchMapPermissions(id: number): Promise<Permission[]> {
const handler = (success: (labels: Permission[]) => void, reject: (error: ErrorInfo) => void) => {
axios.get(`${this.baseUrl}/c/restful/maps/${id}/collabs`,
{
headers: { 'Content-Type': 'text/plain' }
}
).then(response => {
const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const perms: Permission[] = (data.collaborations as any[]).map(p => {
return {
id: p.id,
email: p.email,
name: p.name,
role: p.role
}
const handler = (
success: (labels: Permission[]) => void,
reject: (error: ErrorInfo) => void
) => {
axios
.get(`${this.baseUrl}/c/restful/maps/${id}/collabs`, {
headers: { 'Content-Type': 'text/plain' },
})
.then((response) => {
const data = response.data
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const perms: Permission[] = (data.collaborations as any[]).map((p) => {
return {
id: p.id,
email: p.email,
name: p.name,
role: p.role,
}
})
success(perms)
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
success(perms);
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
}
return new Promise(handler);
return new Promise(handler)
}
deleteAccount(): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.delete(`${this.baseUrl}/c/restful/account`,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.delete(`${this.baseUrl}/c/restful/account`, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
updateAccountInfo(firstname: string, lastname: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/c/restful/account/firstname`,
firstname,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
return axios.put(`${this.baseUrl}/c/restful/account/lastname`,
lastname,
{ headers: { 'Content-Type': 'text/plain' } }
)
}).then(() => {
// All was ok, let's sent to success page ...;
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.put(`${this.baseUrl}/c/restful/account/firstname`, firstname, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
return axios.put(`${this.baseUrl}/c/restful/account/lastname`, lastname, {
headers: { 'Content-Type': 'text/plain' },
})
})
.then(() => {
// All was ok, let's sent to success page ...;
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
updateAccountPassword(pasword: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/c/restful/account/password`,
pasword,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.put(`${this.baseUrl}/c/restful/account/password`, pasword, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
updateAccountLanguage(locale: LocaleCode): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/c/restful/account/locale`,
locale,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
// All was ok, let's sent to success page ...;
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.put(`${this.baseUrl}/c/restful/account/locale`, locale, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
// All was ok, let's sent to success page ...;
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
importMap(model: ImportMapInfo): Promise<number> {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
axios.post(`${this.baseUrl}/c/restful/maps?title=${model.title}&description=${model.description ? model.description : ''}`,
model.content,
{ headers: { 'Content-Type': model.contentType } }
).then(response => {
const mapId = response.headers.resourceid;
success(mapId);
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.post(
`${this.baseUrl}/c/restful/maps?title=${model.title}&description=${
model.description ? model.description : ''
}`,
model.content,
{ headers: { 'Content-Type': model.contentType } }
)
.then((response) => {
const mapId = response.headers.resourceid
success(mapId)
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
fetchAccountInfo(): Promise<AccountInfo> {
const handler = (success: (account: AccountInfo) => void, reject: (error: ErrorInfo) => void) => {
axios.get(`${this.baseUrl}/c/restful/account`,
{
headers: { 'Content-Type': 'application/json' }
}
).then(response => {
const account = response.data;
const locale: LocaleCode | null = account.locale;
success({
lastname: account.lastname ? account.lastname : '',
firstname: account.firstname ? account.firstname : '',
email: account.email,
locale: locale ? localeFromStr(locale) : Locales.EN
});
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
const handler = (
success: (account: AccountInfo) => void,
reject: (error: ErrorInfo) => void
) => {
axios
.get(`${this.baseUrl}/c/restful/account`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const account = response.data
const locale: LocaleCode | null = account.locale
success({
lastname: account.lastname ? account.lastname : '',
firstname: account.firstname ? account.firstname : '',
email: account.email,
locale: locale ? localeFromStr(locale) : Locales.EN,
})
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
deleteMaps(ids: number[]): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.delete(`${this.baseUrl}/c/restful/maps/batch?ids=${ids.join()}`,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
success();
}).catch(error => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
axios
.delete(`${this.baseUrl}/c/restful/maps/batch?ids=${ids.join()}`, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success()
})
.catch((error) => {
const response = error.response
const errorInfo = this.parseResponseOnError(response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
updateMapToPublic(id: number, isPublic: boolean): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/c/restful/maps/${id}/publish`,
isPublic,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
// All was ok, let's sent to success page ...;
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.put(`${this.baseUrl}/c/restful/maps/${id}/publish`, isPublic, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
// All was ok, let's sent to success page ...;
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
revertHistory(id: number, hid: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.post(`${this.baseUrl}/maps/${id}/history/${hid}`,
null,
{ headers: { 'Content-Type': 'text/pain' } }
).then(() => {
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.post(`${this.baseUrl}/maps/${id}/history/${hid}`, null, {
headers: { 'Content-Type': 'text/pain' },
})
.then(() => {
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
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> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/c/restful/maps/${id}/title`,
basicInfo.title,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
return axios.put(`${this.baseUrl}/c/restful/maps/${id}/description`,
basicInfo.description,
{ headers: { 'Content-Type': 'text/plain' } }
)
}).then(() => {
// All was ok, let's sent to success page ...;
success();
}).catch(error => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
axios
.put(`${this.baseUrl}/c/restful/maps/${id}/title`, basicInfo.title, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
return axios.put(
`${this.baseUrl}/c/restful/maps/${id}/description`,
basicInfo.description,
{ headers: { 'Content-Type': 'text/plain' } }
)
})
.then(() => {
// All was ok, let's sent to success page ...;
success()
})
.catch((error) => {
const response = error.response
const errorInfo = this.parseResponseOnError(response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
createMap(model: BasicMapInfo): Promise<number> {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
axios.post(`${this.baseUrl}/c/restful/maps?title=${model.title}&description=${model.description ? model.description : ''}`,
null,
{ headers: { 'Content-Type': 'application/json' } }
).then(response => {
const mapId = response.headers.resourceid;
success(mapId);
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.post(
`${this.baseUrl}/c/restful/maps?title=${model.title}&description=${
model.description ? model.description : ''
}`,
null,
{ headers: { 'Content-Type': 'application/json' } }
)
.then((response) => {
const mapId = response.headers.resourceid
success(mapId)
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
fetchAllMaps(): Promise<MapInfo[]> {
const handler = (success: (mapsInfo: MapInfo[]) => void, reject: (error: ErrorInfo) => void) => {
axios.get(`${this.baseUrl}/c/restful/maps/`,
{
headers: { 'Content-Type': 'application/json' }
}
).then(response => {
const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: MapInfo[] = (data.mindmapsInfo as any[]).map(m => {
return {
id: m.id,
starred: Boolean(m.starred),
title: m.title,
labels: m.labels,
createdBy: m.creator,
creationTime: m.creationTime,
lastModificationBy: m.lastModifierUser,
lastModificationTime: m.lastModificationTime,
description: m.description,
isPublic: m['public'],
role: m.role
}
const handler = (
success: (mapsInfo: MapInfo[]) => void,
reject: (error: ErrorInfo) => void
) => {
axios
.get(`${this.baseUrl}/c/restful/maps/`, {
headers: { 'Content-Type': 'application/json' },
})
success(maps);
}).catch(error => {
.then((response) => {
const data = response.data
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: MapInfo[] = (data.mindmapsInfo as any[]).map((m) => {
return {
id: m.id,
starred: Boolean(m.starred),
title: m.title,
labels: m.labels,
createdBy: m.creator,
creationTime: m.creationTime,
lastModificationBy: m.lastModifierUser,
lastModificationTime: m.lastModificationTime,
description: m.description,
isPublic: m['public'],
role: m.role,
}
})
success(maps)
})
.catch((error) => {
console.log('Maps List Error=>')
console.log(error)
console.log("Maps List Error=>")
console.log(error)
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
const response = error.response
const errorInfo = this.parseResponseOnError(response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
registerNewUser(user: NewUser): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.post(`${this.baseUrl}/service/users/`,
JSON.stringify(user),
{ headers: { 'Content-Type': 'application/json' } }
).then(() => {
// All was ok, let's sent to success page ...;
success();
}).catch(error => {
console.log(error);
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
axios
.post(`${this.baseUrl}/service/users/`, JSON.stringify(user), {
headers: { 'Content-Type': 'application/json' },
})
.then(() => {
// All was ok, let's sent to success page ...;
success()
})
.catch((error) => {
console.log(error)
const response = error.response
const errorInfo = this.parseResponseOnError(response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
deleteMap(id: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.delete( `${this.baseUrl}/c/restful/maps/${id}`,
{ headers: { 'Content-Type': 'application/json' } }
).then(() => {
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.delete(`${this.baseUrl}/c/restful/maps/${id}`, {
headers: { 'Content-Type': 'application/json' },
})
.then(() => {
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
resetPassword(email: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/service/users/resetPassword?email=${email}`,
null,
{ headers: { 'Content-Type': 'application/json' } }
).then(() => {
// All was ok, let's sent to success page ...;
success();
}).catch(error => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
axios
.put(`${this.baseUrl}/service/users/resetPassword?email=${email}`, null, {
headers: { 'Content-Type': 'application/json' },
})
.then(() => {
// All was ok, let's sent to success page ...;
success()
})
.catch((error) => {
const response = error.response
const errorInfo = this.parseResponseOnError(response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
duplicateMap(id: number, basicInfo: BasicMapInfo): Promise<number> {
const handler = (success: (mapId: number) => void, reject: (error: ErrorInfo) => void) => {
axios.post(`${this.baseUrl}/c/restful/maps/${id}`,
JSON.stringify(basicInfo),
{ headers: { 'Content-Type': 'application/json' } }
).then(response => {
const mapId = response.headers.resourceid;
success(mapId);
}).catch(error => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
axios
.post(`${this.baseUrl}/c/restful/maps/${id}`, JSON.stringify(basicInfo), {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const mapId = response.headers.resourceid
success(mapId)
})
.catch((error) => {
const response = error.response
const errorInfo = this.parseResponseOnError(response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
updateStarred(id: number, starred: boolean): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.put(`${this.baseUrl}/c/restful/maps/${id}/starred`,
starred,
{ headers: { 'Content-Type': 'text/plain' } }
).then(() => {
success();
}).catch(error => {
const response = error.response;
const errorInfo = this.parseResponseOnError(response);
reject(errorInfo);
});
axios
.put(`${this.baseUrl}/c/restful/maps/${id}/starred`, starred, {
headers: { 'Content-Type': 'text/plain' },
})
.then(() => {
success()
})
.catch((error) => {
const response = error.response
const errorInfo = this.parseResponseOnError(response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
fetchLabels(): Promise<Label[]> {
const handler = (success: (labels: Label[]) => void, reject: (error: ErrorInfo) => void) => {
axios.get(`${this.baseUrl}/c/restful/labels/`,
{
headers: { 'Content-Type': 'application/json' }
}
).then(response => {
const data = response.data;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: Label[] = (data.labels as any[]).map(l => {
return {
id: l.id,
color: l.color,
title: l.title,
iconName: l.iconName
}
const handler = (
success: (labels: Label[]) => void,
reject: (error: ErrorInfo) => void
) => {
axios
.get(`${this.baseUrl}/c/restful/labels/`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
const data = response.data
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const maps: Label[] = (data.labels as any[]).map((l) => {
return {
id: l.id,
color: l.color,
title: l.title,
iconName: l.iconName,
}
})
success(maps)
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
success(maps);
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
}
return new Promise(handler);
return new Promise(handler)
}
deleteLabel(id: number): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
axios.delete(`${this.baseUrl}/c/restful/label/${id}`).then(() => {
success();
}).catch(error => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
axios
.delete(`${this.baseUrl}/c/restful/label/${id}`)
.then(() => {
success()
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response)
reject(errorInfo)
})
}
return new Promise(handler);
return new Promise(handler)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private parseResponseOnError = (response: any): ErrorInfo => {
let result: ErrorInfo | undefined;
let result: ErrorInfo | undefined
if (response) {
const status: number = response.status;
const data = response.data;
console.log(data);
const status: number = response.status
const data = response.data
console.log(data)
switch (status) {
case 401:
case 302:
this.sessionExpired();
result = { msg: "Your current session has expired. Please, sign in and try again." };
break;
this.sessionExpired()
result = {
msg: 'Your current session has expired. Please, sign in and try again.',
}
break
default:
if (data) {
// Set global errors ...
result = {};
const globalErrors = data.globalErrors;
result = {}
const globalErrors = data.globalErrors
if (globalErrors && globalErrors.length > 0) {
result.msg = globalErrors[0];
result.msg = globalErrors[0]
}
// Set field errors ...
if (data.fieldErrors && Object.keys(data.fieldErrors).length > 0) {
result.fields = data.fieldErrors;
result.fields = data.fieldErrors
if (!result.msg) {
const key = Object.keys(data.fieldErrors)[0];
result.msg = data.fieldErrors[key];
const key = Object.keys(data.fieldErrors)[0]
result.msg = data.fieldErrors[key]
}
}
} else {
result = { msg: response.statusText };
result = { msg: response.statusText }
}
}
}
// Network related problem ...
if (!result) {
result = { msg: 'Unexpected error. Please, try latter' };
result = { msg: 'Unexpected error. Please, try latter' }
}
return result;
return result
}
}

View File

@ -5,7 +5,7 @@ import Client, { ErrorInfo } from '../../classes/client'
import Header from '../layout/header'
import Footer from '../layout/footer'
import FormContainer from '../layout/form-container';
import FormContainer from '../layout/form-container'
import { useSelector } from 'react-redux'
import { useMutation } from 'react-query'
import { activeInstance } from '../../redux/clientSlice'
@ -16,64 +16,75 @@ import SubmitButton from '../form/submit-button'
import Typography from '@material-ui/core/Typography'
const ForgotPassword = () => {
const [email, setEmail] = useState<string>('');
const [error, setError] = useState<ErrorInfo>();
const history = useHistory();
const intl = useIntl();
const [email, setEmail] = useState<string>('')
const [error, setError] = useState<ErrorInfo>()
const history = useHistory()
const intl = useIntl()
const service: Client = useSelector(activeInstance);
const mutation = useMutation<void, ErrorInfo, string>(
(email: string) => service.resetPassword(email),
{
onSuccess: () => history.push("/c/forgot-password-success"),
onError: (error) => {
setError(error);
}
const service: Client = useSelector(activeInstance)
const mutation = useMutation<void, ErrorInfo, string>(
(email: string) => service.resetPassword(email),
{
onSuccess: () => history.push('/c/forgot-password-success'),
onError: (error) => {
setError(error)
},
}
)
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
mutation.mutate(email)
}
);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
mutation.mutate(email);
}
return (
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="forgot.title" defaultMessage="Reset your password" />
</Typography>
return (
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="forgot.title" defaultMessage="Reset your password" />
</Typography>
<Typography>
<FormattedMessage
id="forgot.desc"
defaultMessage="We will send you an email to reset your password"
/>
</Typography>
<Typography>
<FormattedMessage id="forgot.desc" defaultMessage="We will send you an email to reset your password" />
</Typography>
<GlobalError error={error} />
<GlobalError error={error} />
<form onSubmit={handleOnSubmit}>
<Input
type="email"
name="email"
label={intl.formatMessage({ id: 'forgot.email', defaultMessage: 'Email' })}
autoComplete="email"
onChange={(e) => setEmail(e.target.value)}
error={error}
/>
<form onSubmit={handleOnSubmit}>
<Input type="email" name="email" label={intl.formatMessage({ id: "forgot.email", defaultMessage: "Email" })}
autoComplete="email" onChange={e => setEmail(e.target.value)} error={error} />
<SubmitButton value={intl.formatMessage({ id: "forgot.register", defaultMessage: "Send recovery link" })} />
</form>
</FormContainer>
);
<SubmitButton
value={intl.formatMessage({
id: 'forgot.register',
defaultMessage: 'Send recovery link',
})}
/>
</form>
</FormContainer>
)
}
const ForgotPasswordPage = ():React.ReactElement => {
const ForgotPasswordPage = (): React.ReactElement => {
useEffect(() => {
document.title = 'Reset Password | WiseMapping'
})
useEffect(() => {
document.title = 'Reset Password | WiseMapping';
});
return (
<div>
<Header type='only-signin' />
<ForgotPassword />
<Footer />
</div>
);
return (
<div>
<Header type="only-signin" />
<ForgotPassword />
<Footer />
</div>
)
}
export { ForgotPasswordPage }

View File

@ -1,40 +1,49 @@
import React, { useEffect } from 'react'
import { FormattedMessage } from 'react-intl'
import FormContainer from '../layout/form-container';
import FormContainer from '../layout/form-container'
import Header from '../layout/header'
import Footer from '../layout/footer'
import { Link as RouterLink } from 'react-router-dom'
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography'
import Button from '@material-ui/core/Button'
const ForgotPasswordSuccessPage = (): React.ReactElement => {
useEffect(() => {
document.title = 'Reset Password | WiseMapping';
});
useEffect(() => {
document.title = 'Reset Password | WiseMapping'
})
return (
<div>
<Header type='none' />
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="forgot.success.title" defaultMessage="Your temporal password has been sent" />
</Typography>
return (
<div>
<Header type="none" />
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage
id="forgot.success.title"
defaultMessage="Your temporal password has been sent"
/>
</Typography>
<Typography paragraph>
<FormattedMessage id="forgot.success.desc" defaultMessage="We've sent you an email that will allow you to reset your password. Please check your email now." />
</Typography>
<Typography paragraph>
<FormattedMessage
id="forgot.success.desc"
defaultMessage="We've sent you an email that will allow you to reset your password. Please check your email now."
/>
</Typography>
<Button color="primary" size="medium" variant="contained" component={RouterLink} to="/c/login" disableElevation={true}>
<FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button>
</FormContainer>
<Footer />
</div>
);
<Button
color="primary"
size="medium"
variant="contained"
component={RouterLink}
to="/c/login"
disableElevation={true}
>
<FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button>
</FormContainer>
<Footer />
</div>
)
}
export default ForgotPasswordSuccessPage

View File

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

View File

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

View File

@ -1,16 +1,16 @@
import TextField from "@material-ui/core/TextField";
import React, { ChangeEvent } from "react";
import { ErrorInfo } from "../../../classes/client";
import TextField from '@material-ui/core/TextField'
import React, { ChangeEvent } from 'react'
import { ErrorInfo } from '../../../classes/client'
type InputProps = {
name: string;
error?: ErrorInfo;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
label: string;
required?: boolean;
type: string;
name: string
error?: ErrorInfo
onChange?: (event: ChangeEvent<HTMLInputElement>) => void
label: string
required?: boolean
type: string
value?: string
autoComplete?: string;
autoComplete?: string
fullWidth?: boolean
disabled?: boolean
}
@ -25,17 +25,25 @@ const Input = ({
label,
autoComplete,
fullWidth = true,
disabled = false
disabled = false,
}: InputProps): React.ReactElement => {
const fieldError = error?.fields?.[name];
const fieldError = error?.fields?.[name]
return (
<TextField name={name} type={type} label={label}
value={value} onChange={onChange}
error={Boolean(fieldError)} helperText={fieldError}
variant="outlined" required={required} fullWidth={fullWidth} margin="dense" disabled={disabled} autoComplete={autoComplete} />
);
<TextField
name={name}
type={type}
label={label}
value={value}
onChange={onChange}
error={Boolean(fieldError)}
helperText={fieldError}
variant="outlined"
required={required}
fullWidth={fullWidth}
margin="dense"
disabled={disabled}
autoComplete={autoComplete}
/>
)
}
export default Input;
export default Input

View File

@ -1,27 +1,39 @@
import Button from '@material-ui/core/Button';
import Button from '@material-ui/core/Button'
import React, { useState } from 'react'
import { useIntl } from 'react-intl'
type SubmitButton = {
value: string;
disabled?: boolean;
value: string
disabled?: boolean
}
const SubmitButton = (props: SubmitButton): React.ReactElement => {
const [disabled] = useState(props.disabled ? true : false);
const intl = useIntl();
const [disabled] = useState(props.disabled ? true : false)
const intl = useIntl()
let valueTxt = props.value;
let valueTxt = props.value
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 (
<Button color="primary" size="medium" variant="contained" type="submit"
disableElevation={true} disabled={disabled}
style={{ width: '350px', height: '53px', padding: '0px 20px', margin: '7px 0px', fontSize: '18px' }} >
<Button
color="primary"
size="medium"
variant="contained"
type="submit"
disableElevation={true}
disabled={disabled}
style={{
width: '350px',
height: '53px',
padding: '0px 20px',
margin: '7px 0px',
fontSize: '18px',
}}
>
{value}
</Button>
);
)
}
export default SubmitButton;
export default SubmitButton

View File

@ -7,27 +7,67 @@ import { StyledFooter } from './styled'
const poweredByIcon = require('../../../images/pwrdby-white.svg')
const Footer = (): React.ReactElement => {
return (
<StyledFooter>
<div style={{ padding: 0, margin: 0 }}>
<a href="http://www.wisemapping.org/">
<img src={poweredByIcon} alt="Powered By WiseMapping" />
</a>
</div>
<div >
<h4><FormattedMessage id="footer.faqandhelp" defaultMessage="Help & FAQ" /></h4>
<div><a href="https://www.wisemapping.com/faq.html"> <FormattedMessage id="footer.faq" defaultMessage="F.A.Q." /> </a></div >
<div><a href="https://www.wisemapping.com/termsofuse.html"> <FormattedMessage id="footer.termsandconditions" defaultMessage="Term And Conditions" /> </a></div>
<div><a href="mailto:team@wisemapping.com"> <FormattedMessage id="footer.contactus" defaultMessage="Contact Us" /> </a></div>
</div>
<div >
<h4><FormattedMessage id="footer.others" defaultMessage="Others" /></h4>
<div><a href="https://www.wisemapping.com/aboutus.html"> <FormattedMessage id="footer.aboutus" defaultMessage="About Us" /></a></div >
<div><a href="mailto:feedback@wisemapping.com" > <FormattedMessage id="footer.feedback" defaultMessage="Feedback" /> </a></div>
<div><a href="http://www.wisemapping.org/"> <FormattedMessage id="footer.opensource" defaultMessage="Open Source" /> </a></div>
</div>
</StyledFooter>
)
return (
<StyledFooter>
<div style={{ padding: 0, margin: 0 }}>
<a href="http://www.wisemapping.org/">
<img src={poweredByIcon} alt="Powered By WiseMapping" />
</a>
</div>
<div>
<h4>
<FormattedMessage id="footer.faqandhelp" defaultMessage="Help & FAQ" />
</h4>
<div>
<a href="https://www.wisemapping.com/faq.html">
{' '}
<FormattedMessage id="footer.faq" defaultMessage="F.A.Q." />{' '}
</a>
</div>
<div>
<a href="https://www.wisemapping.com/termsofuse.html">
{' '}
<FormattedMessage
id="footer.termsandconditions"
defaultMessage="Term And Conditions"
/>{' '}
</a>
</div>
<div>
<a href="mailto:team@wisemapping.com">
{' '}
<FormattedMessage id="footer.contactus" defaultMessage="Contact Us" />{' '}
</a>
</div>
</div>
<div>
<h4>
<FormattedMessage id="footer.others" defaultMessage="Others" />
</h4>
<div>
<a href="https://www.wisemapping.com/aboutus.html">
{' '}
<FormattedMessage id="footer.aboutus" defaultMessage="About Us" />
</a>
</div>
<div>
<a href="mailto:feedback@wisemapping.com">
{' '}
<FormattedMessage id="footer.feedback" defaultMessage="Feedback" />{' '}
</a>
</div>
<div>
<a href="http://www.wisemapping.org/">
{' '}
<FormattedMessage
id="footer.opensource"
defaultMessage="Open Source"
/>{' '}
</a>
</div>
</div>
</StyledFooter>
)
}
export default Footer;
export default Footer

View File

@ -1,44 +1,44 @@
import styled from 'styled-components';
import styled from 'styled-components'
/* Footer */
export const StyledFooter = styled.footer`
height: 250px;
margin-top: 80px;
padding: 60px 40px 10px 50px;
background-color: #f9a826;
display: grid;
grid-template-columns: 200px 1fr 1fr 3fr;
height: 250px;
margin-top: 80px;
padding: 60px 40px 10px 50px;
background-color: #f9a826;
display: grid;
grid-template-columns: 200px 1fr 1fr 3fr;
& a {
font-size: 14px;
color: white;
word-wrap: nowrap;
}
& a {
font-size: 14px;
color: white;
word-wrap: nowrap;
}
& h4 {
font-size: 14px;
color: white;
word-wrap: nowrap;
font-weight: 500px;
margin: 0px;
}
& h4 {
font-size: 14px;
color: white;
word-wrap: nowrap;
font-weight: 500px;
margin: 0px;
}
&>svg {
grid-column: 1;
}
& > svg {
grid-column: 1;
}
& div:nth-child(2) {
grid-column: 2;
}
& div:nth-child(2) {
grid-column: 2;
}
& div:nth-child(3) {
grid-column: 3;
}
& div:nth-child(4) {
grid-column: 4;
text-align: right;
display: inline-block;
visibility: visible;
}`
& div:nth-child(3) {
grid-column: 3;
}
& div:nth-child(4) {
grid-column: 4;
text-align: right;
display: inline-block;
visibility: visible;
}
`

View File

@ -1,12 +1,12 @@
import Container from "@material-ui/core/Container";
import withStyles from "@material-ui/core/styles/withStyles";
import Container from '@material-ui/core/Container'
import withStyles from '@material-ui/core/styles/withStyles'
const FormContainer = withStyles({
root: {
padding: '20px 10px 0px 20px',
maxWidth: '380px',
textAlign: 'center'
}
textAlign: 'center',
},
})(Container)
export default FormContainer;
export default FormContainer

View File

@ -1,64 +1,96 @@
import { StyledNav, StyledDiv, Logo } from './styled';
import { StyledNav, StyledDiv, Logo } from './styled'
import React from 'react'
import { FormattedMessage } from 'react-intl'
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 {
type: 'only-signup' | 'only-signin' | 'none';
type: 'only-signup' | 'only-signin' | 'none'
}
export const Header = ({ type }: HeaderProps): React.ReactElement => {
let signUpButton
let text
let signInButton
if (type === 'only-signup') {
text = (
<span className="header-area-content-span">
<span>
<FormattedMessage
id="header.donthaveaccount"
defaultMessage="Don't have an account ?"
/>
</span>
</span>
)
signUpButton = <SignUpButton className="header-area-right2" />
} else if (type === 'only-signin') {
text = (
<span className="header-area-content-span">
<span>
<FormattedMessage
id="header.haveaccount"
defaultMessage="Already have an account?"
/>
</span>
</span>
)
signUpButton = <SignInButton className="header-area-right2" />
} else if (type === 'none') {
text = ''
signUpButton = ''
} else {
signUpButton = <SignUpButton className="header-area-right2" />
signInButton = <SignInButton className="header-area-right2" />
}
let signUpButton;
let text;
let signInButton;
if (type === 'only-signup') {
text = <span className="header-area-content-span"><span><FormattedMessage id="header.donthaveaccount" defaultMessage="Don't have an account ?" /></span></span>;
signUpButton = <SignUpButton className="header-area-right2" />;
} else if (type === 'only-signin') {
text = <span className="header-area-content-span"><span><FormattedMessage id="header.haveaccount" defaultMessage="Already have an account?" /></span></span>;
signUpButton = <SignInButton className="header-area-right2" />;
} else if (type === 'none') {
text = '';
signUpButton = '';
} else {
signUpButton = <SignUpButton className="header-area-right2" />
signInButton = <SignInButton className="header-area-right2" />;
}
return (
<StyledNav>
<StyledDiv>
<Logo><Link to="/c/login" className="header-logo"><img src={String(logo)} alt="logo" /></Link></Logo>
{text}
{signUpButton}
{signInButton}
</StyledDiv>
</StyledNav>
)
return (
<StyledNav>
<StyledDiv>
<Logo>
<Link to="/c/login" className="header-logo">
<img src={String(logo)} alt="logo" />
</Link>
</Logo>
{text}
{signUpButton}
{signInButton}
</StyledDiv>
</StyledNav>
)
}
interface ButtonProps {
className?: string;
className?: string
}
export const SignInButton = (props: ButtonProps): React.ReactElement => {
return (
<span className={`${props.className}`}>
<Button color="primary" size="medium" variant="outlined" component={Link} to="/c/login"><FormattedMessage id="login.signin" defaultMessage="Sign In" /></Button>
</span>);
return (
<span className={`${props.className}`}>
<Button color="primary" size="medium" variant="outlined" component={Link} to="/c/login">
<FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button>
</span>
)
}
const SignUpButton = (props: ButtonProps): React.ReactElement => {
return (
<span className={`${props.className}`}>
<Button color="primary" size="medium" variant="outlined" component={Link} to="/c/registration"><FormattedMessage id="login.signup" defaultMessage="Sign Up" /></Button>
</span>);
return (
<span className={`${props.className}`}>
<Button
color="primary"
size="medium"
variant="outlined"
component={Link}
to="/c/registration"
>
<FormattedMessage id="login.signup" defaultMessage="Sign Up" />
</Button>
</span>
)
}
export default Header;
export default Header

View File

@ -1,77 +1,77 @@
import styled from 'styled-components';
import styled from 'styled-components'
export const StyledNav = styled.nav`
height: 90px;
position: sticky;
top: -16px;
z-index: 1;
-webkit-backface-visibility: hidden;
&::before,
&::after {
content: '';
display: block;
height: 16px;
height: 90px;
position: sticky;
}
top: -16px;
z-index: 1;
-webkit-backface-visibility: hidden;
&::before {
top: 58px;
box-shadow: 0 4px 10px 0 rgba(202, 34, 34, 0.05), 0 5px 30px 0 rgba(0, 0, 0, 0.05);
}
&::before,
&::after {
content: '';
display: block;
height: 16px;
position: sticky;
}
&::after {
background: linear-gradient(white, rgba(255, 255, 255, 0.3));
top: 0;
z-index: 2;
}
&::before {
top: 58px;
box-shadow: 0 4px 10px 0 rgba(202, 34, 34, 0.05), 0 5px 30px 0 rgba(0, 0, 0, 0.05);
}
/* Review ....*/
.header-middle {
grid-column-start: 2;
}
&::after {
background: linear-gradient(white, rgba(255, 255, 255, 0.3));
top: 0;
z-index: 2;
}
.header-area-right1 {
grid-column-start: 3;
}
/* Review ....*/
.header-middle {
grid-column-start: 2;
}
.header-area-right2 {
grid-column-start: 4;
}
.header-area-right1 {
grid-column-start: 3;
}
.header-area-right1 span,
.header-area-right2 span {
font-size: 15px;
}
.header-area-content-span {
grid-column-start: 2;
grid-column-end: 4;
text-align: right;
font-size: 14px;
.header-area-right2 {
grid-column-start: 4;
}
.header-area-right1 span,
.header-area-right2 span {
font-size: 15px;
}
.header-area-content-span {
grid-column-start: 2;
grid-column-end: 4;
text-align: right;
font-size: 14px;
padding: 10px;
}
`
export const StyledDiv = styled.nav`
background: white;
height: 74px;
padding: 10px;
}
`;
position: sticky;
top: 0px;
margin-top: -16px;
z-index: 3;
export const StyledDiv = styled.nav`
background: white;
height: 74px;
padding: 10px;
position: sticky;
top: 0px;
margin-top: -16px;
z-index: 3;
display: grid;
white-space: nowrap;
grid-template-columns: 150px 1fr 130px 160px 50px;
display: grid;
white-space: nowrap;
grid-template-columns: 150px 1fr 130px 160px 50px;
`
export const Logo = styled.span`
grid-column-start: 1;
margin-left: 50px;
margin-top: 0px;
export const Logo = styled.span`
grid-column-start: 1;
margin-left: 50px;
margin-top: 0px;
.header-logo a {
padding: 0px;
}
.header-logo a {
padding: 0px;
}
`

View File

@ -1,95 +1,139 @@
import React, { useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { Link as RouterLink } from 'react-router-dom';
import Header from '../layout/header';
import Footer from '../layout/footer';
import SubmitButton from '../form/submit-button';
import Input from '../form/input';
import GlobalError from '../form/global-error';
import FormContainer from '../layout/form-container';
import Typography from '@material-ui/core/Typography';
import FormControl from '@material-ui/core/FormControl';
import Link from '@material-ui/core/Link';
import React, { useEffect } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { Link as RouterLink } from 'react-router-dom'
import Header from '../layout/header'
import Footer from '../layout/footer'
import SubmitButton from '../form/submit-button'
import Input from '../form/input'
import GlobalError from '../form/global-error'
import FormContainer from '../layout/form-container'
import Typography from '@material-ui/core/Typography'
import FormControl from '@material-ui/core/FormControl'
import Link from '@material-ui/core/Link'
type ConfigStatusProps = {
enabled?: boolean
enabled?: boolean
}
const ConfigStatusMessage = ({ enabled = false }: ConfigStatusProps): React.ReactElement => {
let result;
if (enabled === true) {
result = (<div className="db-warn-msg">
<p>
<FormattedMessage id="login.hsqldbcofig" defaultMessage="Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL" description="Missing production database configured" /><a href="https://wisemapping.atlassian.net/wiki/display/WS/Database+Configuration"> here</a>
</p>
</div>);
}
return result || null;
let result
if (enabled === true) {
result = (
<div className="db-warn-msg">
<p>
<FormattedMessage
id="login.hsqldbcofig"
defaultMessage="Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL"
description="Missing production database configured"
/>
<a href="https://wisemapping.atlassian.net/wiki/display/WS/Database+Configuration">
{' '}
here
</a>
</p>
</div>
)
}
return result || null
}
const LoginError = () => {
// @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2
const errorCode = new URLSearchParams(window.location.search).get('login_error');
const intl = useIntl();
// @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2
const errorCode = new URLSearchParams(window.location.search).get('login_error')
const intl = useIntl()
let msg: null | string = null;
if (errorCode) {
switch (errorCode) {
case "3":
msg = intl.formatMessage({ id: "login.userinactive", defaultMessage: "Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!." });
break;
default:
msg = intl.formatMessage({ id: "login.error", defaultMessage: "The email address or password you entered is not valid." });
let msg: null | string = null
if (errorCode) {
switch (errorCode) {
case '3':
msg = intl.formatMessage({
id: 'login.userinactive',
defaultMessage:
"Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!.",
})
break
default:
msg = intl.formatMessage({
id: 'login.error',
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 intl = useIntl();
const intl = useIntl()
useEffect(() => {
document.title = 'Login | WiseMapping';
});
useEffect(() => {
document.title = 'Login | WiseMapping'
})
return (
<div>
<Header type='only-signup' />
return (
<div>
<Header type="only-signup" />
<FormContainer maxWidth="xs">
<Typography variant="h4" component="h1">
<FormattedMessage id="login.title" defaultMessage="Welcome" />
</Typography>
<FormContainer maxWidth="xs">
<Typography variant="h4" component="h1">
<FormattedMessage id="login.title" defaultMessage="Welcome" />
</Typography>
<Typography paragraph>
<FormattedMessage id="login.desc" defaultMessage="Log into your account" />
</Typography>
<Typography paragraph>
<FormattedMessage id="login.desc" defaultMessage="Log into your account" />
</Typography>
<LoginError />
<LoginError />
<FormControl>
<form action="/c/perform-login" method="POST" >
<Input name="username" type="email" label={intl.formatMessage({ id: "login.email", defaultMessage: "Email" })} required autoComplete="email" />
<Input name="password" type="password" label={intl.formatMessage({ id: "login.password", defaultMessage: "Password" })} required autoComplete="current-password" />
<div>
<input name="remember-me" id="remember-me" type="checkbox" />
<label htmlFor="remember-me"><FormattedMessage id="login.remberme" defaultMessage="Remember me" /></label>
</div>
<SubmitButton value={intl.formatMessage({ id: "login.signin", defaultMessage: "Sign In" })} />
</form>
</FormControl>
<FormControl>
<form action="/c/perform-login" method="POST">
<Input
name="username"
type="email"
label={intl.formatMessage({
id: 'login.email',
defaultMessage: 'Email',
})}
required
autoComplete="email"
/>
<Input
name="password"
type="password"
label={intl.formatMessage({
id: 'login.password',
defaultMessage: 'Password',
})}
required
autoComplete="current-password"
/>
<div>
<input name="remember-me" id="remember-me" type="checkbox" />
<label htmlFor="remember-me">
<FormattedMessage
id="login.remberme"
defaultMessage="Remember me"
/>
</label>
</div>
<SubmitButton
value={intl.formatMessage({
id: 'login.signin',
defaultMessage: 'Sign In',
})}
/>
</form>
</FormControl>
<Link component={RouterLink} to="/c/forgot-password"><FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" /></Link>
<ConfigStatusMessage />
<Link component={RouterLink} to="/c/forgot-password">
<FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" />
</Link>
<ConfigStatusMessage />
</FormContainer>
</FormContainer>
<Footer />
</div>
);
<Footer />
</div>
)
}
export default LoginPage;
export default LoginPage

View File

@ -1,136 +1,178 @@
import React, { useEffect } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import Client, { ErrorInfo } from "../../../../classes/client";
import Input from "../../../form/input";
import BaseDialog from "../../action-dispatcher/base-dialog";
import { useSelector } from 'react-redux';
import { activeInstance, fetchAccount } from "../../../../redux/clientSlice";
import Alert from "@material-ui/lab/Alert";
import FormControl from "@material-ui/core/FormControl";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormGroup from "@material-ui/core/FormGroup";
import Switch from "@material-ui/core/Switch";
import React, { useEffect } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { useMutation, useQueryClient } from 'react-query'
import Client, { ErrorInfo } from '../../../../classes/client'
import Input from '../../../form/input'
import BaseDialog from '../../action-dispatcher/base-dialog'
import { useSelector } from 'react-redux'
import { activeInstance, fetchAccount } from '../../../../redux/clientSlice'
import Alert from '@material-ui/lab/Alert'
import FormControl from '@material-ui/core/FormControl'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import FormGroup from '@material-ui/core/FormGroup'
import Switch from '@material-ui/core/Switch'
type AccountInfoDialogProps = {
onClose: () => void
}
type AccountInfoModel = {
email: string,
firstname: string,
email: string
firstname: string
lastname: string
}
const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' };
const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps):React.ReactElement => {
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const [remove, setRemove] = React.useState<boolean>(false);
const defaultModel: AccountInfoModel = { firstname: '', lastname: '', email: '' }
const AccountInfoDialog = ({ onClose }: AccountInfoDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance)
const queryClient = useQueryClient()
const [remove, setRemove] = React.useState<boolean>(false)
const [model, setModel] = React.useState<AccountInfoModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const [model, setModel] = React.useState<AccountInfoModel>(defaultModel)
const [error, setError] = React.useState<ErrorInfo>()
const intl = useIntl()
const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>((model: AccountInfoModel) => {
return client.updateAccountInfo(model.firstname, model.lastname);
},
const mutationChangeName = useMutation<void, ErrorInfo, AccountInfoModel>(
(model: AccountInfoModel) => {
return client.updateAccountInfo(model.firstname, model.lastname)
},
{
onSuccess: () => {
queryClient.invalidateQueries('account')
onClose()
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const mutationRemove = useMutation<void, ErrorInfo, void>(() => {
return client.deleteAccount();
},
const mutationRemove = useMutation<void, ErrorInfo, void>(
() => {
return client.deleteAccount()
},
{
onSuccess: () => {
window.location.href = '/c/logout'
onClose()
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const account = fetchAccount();
const account = fetchAccount()
useEffect(() => {
if (account) {
setModel({
email: account?.email,
lastname: account?.lastname,
firstname: account?.firstname
});
firstname: account?.firstname,
})
}
}, [account?.email])
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
onClose()
setModel(defaultModel)
setError(undefined)
}
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
event.preventDefault()
if (remove) {
mutationRemove.mutate();
mutationRemove.mutate()
} else {
mutationChangeName.mutate(model);
mutationChangeName.mutate(model)
}
};
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
event.preventDefault()
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof AccountInfoModel]: value });
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof AccountInfoModel]: value })
}
const handleOnRemoveChange = (event) => {
setRemove(event.target.checked);
};
setRemove(event.target.checked)
}
return (
<BaseDialog onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({ id: 'accountinfo.title', defaultMessage: 'Account info' })}
submitButton={intl.formatMessage({ id: 'accountinfo.button', defaultMessage: 'Accept' })}>
submitButton={intl.formatMessage({
id: 'accountinfo.button',
defaultMessage: 'Accept',
})}
>
<FormControl fullWidth={true}>
<Input name="email" type="text" disabled={true} label={intl.formatMessage({ id: "accountinfo.email", defaultMessage: "Email" })}
value={model.email} onChange={handleOnChange} error={error} fullWidth={true} />
<Input
name="email"
type="text"
disabled={true}
label={intl.formatMessage({ id: 'accountinfo.email', defaultMessage: 'Email' })}
value={model.email}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input name="firstname" type="text" label={intl.formatMessage({ id: "accountinfo.firstname", defaultMessage: "First Name" })}
value={model.firstname} onChange={handleOnChange} required={true} fullWidth={true} />
<Input
name="firstname"
type="text"
label={intl.formatMessage({
id: 'accountinfo.firstname',
defaultMessage: 'First Name',
})}
value={model.firstname}
onChange={handleOnChange}
required={true}
fullWidth={true}
/>
<Input name="lastname" type="text" label={intl.formatMessage({ id: "accountinfo.lastname", defaultMessage: "Last Name" })}
value={model.lastname} onChange={handleOnChange} required={true} fullWidth={true} />
<Input
name="lastname"
type="text"
label={intl.formatMessage({
id: 'accountinfo.lastname',
defaultMessage: 'Last Name',
})}
value={model.lastname}
onChange={handleOnChange}
required={true}
fullWidth={true}
/>
<FormGroup>
{remove &&
{remove && (
<Alert severity="error">
<FormattedMessage id="account.delete-warning" defaultMessage="Keep in mind that you will not be able retrieve any mindmap you have added. All your information will be deleted and it can not be restored." />
<FormattedMessage
id="account.delete-warning"
defaultMessage="Keep in mind that you will not be able retrieve any mindmap you have added. All your information will be deleted and it can not be restored."
/>
</Alert>
}
)}
<FormControlLabel
control={<Switch checked={remove} onChange={(handleOnRemoveChange)} name="remove" color="primary" />}
control={
<Switch
checked={remove}
onChange={handleOnRemoveChange}
name="remove"
color="primary"
/>
}
label="Delete Account"
/>
</FormGroup>
</FormControl>
</BaseDialog>
);
)
}
export default AccountInfoDialog;
export default AccountInfoDialog

View File

@ -1,84 +1,116 @@
import FormControl from "@material-ui/core/FormControl";
import React from "react";
import { useIntl } from "react-intl";
import { useMutation } from "react-query";
import Client, { ErrorInfo } from "../../../../classes/client";
import Input from "../../../form/input";
import BaseDialog from "../../action-dispatcher/base-dialog";
import { useSelector } from 'react-redux';
import { activeInstance } from "../../../../redux/clientSlice";
import FormControl from '@material-ui/core/FormControl'
import React from 'react'
import { useIntl } from 'react-intl'
import { useMutation } from 'react-query'
import Client, { ErrorInfo } from '../../../../classes/client'
import Input from '../../../form/input'
import BaseDialog from '../../action-dispatcher/base-dialog'
import { useSelector } from 'react-redux'
import { activeInstance } from '../../../../redux/clientSlice'
type ChangePasswordDialogProps = {
onClose: () => void
}
type ChangePasswordModel = {
password: string,
password: string
retryPassword: string
}
const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' };
const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps):React.ReactElement => {
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const defaultModel: ChangePasswordModel = { password: '', retryPassword: '' }
const ChangePasswordDialog = ({ onClose }: ChangePasswordDialogProps): React.ReactElement => {
const client: Client = useSelector(activeInstance)
const [model, setModel] = React.useState<ChangePasswordModel>(defaultModel)
const [error, setError] = React.useState<ErrorInfo>()
const intl = useIntl()
const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>((model: ChangePasswordModel) => {
return client.updateAccountPassword(model.password);
},
const mutation = useMutation<void, ErrorInfo, ChangePasswordModel>(
(model: ChangePasswordModel) => {
return client.updateAccountPassword(model.password)
},
{
onSuccess: () => {
onClose()
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
onClose()
setModel(defaultModel)
setError(undefined)
}
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
event.preventDefault()
// Check password are equal ...
if (model.password != model.retryPassword) {
setError({ msg: intl.formatMessage({ id: 'changepwd.password-match', defaultMessage: 'Password do not match. Please, try again.' }) });
return;
setError({
msg: intl.formatMessage({
id: 'changepwd.password-match',
defaultMessage: 'Password do not match. Please, try again.',
}),
})
return
}
mutation.mutate(model);
};
mutation.mutate(model)
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
event.preventDefault()
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ChangePasswordModel]: value });
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof ChangePasswordModel]: value })
}
return (
<BaseDialog onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({ id: 'changepwd.title', defaultMessage: 'Change Password' })}
description={intl.formatMessage({ id: 'changepwd.description', defaultMessage: 'Please, provide the new password for your account.' })}
submitButton={intl.formatMessage({ id: 'changepwd.button', defaultMessage: 'Change' })}>
description={intl.formatMessage({
id: 'changepwd.description',
defaultMessage: 'Please, provide the new password for your account.',
})}
submitButton={intl.formatMessage({ id: 'changepwd.button', defaultMessage: 'Change' })}
>
<FormControl fullWidth={true}>
<Input name="password" type="password" label={intl.formatMessage({ id: "changepwd.password", defaultMessage: "Password" })}
value={model.password} onChange={handleOnChange} error={error} fullWidth={true} autoComplete="new-password" />
<Input
name="password"
type="password"
label={intl.formatMessage({
id: 'changepwd.password',
defaultMessage: 'Password',
})}
value={model.password}
onChange={handleOnChange}
error={error}
fullWidth={true}
autoComplete="new-password"
/>
<Input name="retryPassword" type="password" label={intl.formatMessage({ id: "changepwd.confirm-password", defaultMessage: "Confirm Password" })}
value={model.retryPassword} onChange={handleOnChange} required={true} fullWidth={true} autoComplete="new-password" />
<Input
name="retryPassword"
type="password"
label={intl.formatMessage({
id: 'changepwd.confirm-password',
defaultMessage: 'Confirm Password',
})}
value={model.retryPassword}
onChange={handleOnChange}
required={true}
fullWidth={true}
autoComplete="new-password"
/>
</FormControl>
</BaseDialog>
);
)
}
export default ChangePasswordDialog;
export default ChangePasswordDialog

View File

@ -1,44 +1,46 @@
import IconButton from "@material-ui/core/IconButton";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import Tooltip from "@material-ui/core/Tooltip";
import SettingsApplicationsOutlined from "@material-ui/icons/SettingsApplicationsOutlined";
import AccountCircle from "@material-ui/icons/AccountCircle";
import React from "react";
import { FormattedMessage } from "react-intl";
import { fetchAccount } from '../../../redux/clientSlice';
import AccountInfoDialog from './account-info-dialog';
import ChangePasswordDialog from './change-password-dialog';
import LockOpenOutlined from "@material-ui/icons/LockOpenOutlined";
import Link from "@material-ui/core/Link";
import ExitToAppOutlined from "@material-ui/icons/ExitToAppOutlined";
import IconButton from '@material-ui/core/IconButton'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import Tooltip from '@material-ui/core/Tooltip'
import SettingsApplicationsOutlined from '@material-ui/icons/SettingsApplicationsOutlined'
import AccountCircle from '@material-ui/icons/AccountCircle'
import React from 'react'
import { FormattedMessage } from 'react-intl'
import { fetchAccount } from '../../../redux/clientSlice'
import AccountInfoDialog from './account-info-dialog'
import ChangePasswordDialog from './change-password-dialog'
import LockOpenOutlined from '@material-ui/icons/LockOpenOutlined'
import Link from '@material-ui/core/Link'
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 [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const [action, setAction] = React.useState<ActionType>(undefined);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const open = Boolean(anchorEl)
const [action, setAction] = React.useState<ActionType>(undefined)
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null);
};
setAnchorEl(null)
}
const account = fetchAccount();
const account = fetchAccount()
return (
<span>
<Tooltip arrow={true} title={`${account?.firstname} ${account?.lastname} <${account?.email}>`}>
<IconButton
onClick={handleMenu}>
<Tooltip
arrow={true}
title={`${account?.firstname} ${account?.lastname} <${account?.email}>`}
>
<IconButton onClick={handleMenu}>
<AccountCircle fontSize="large" style={{ color: 'black' }} />
</IconButton >
</IconButton>
</Tooltip>
<Menu id="appbar-profile"
<Menu
id="appbar-profile"
anchorEl={anchorEl}
keepMounted
open={open}
@ -53,14 +55,22 @@ const AccountMenu = (): React.ReactElement => {
horizontal: 'right',
}}
>
<MenuItem onClick={() => { handleClose(), setAction('account-info') }}>
<MenuItem
onClick={() => {
handleClose(), setAction('account-info')
}}
>
<ListItemIcon>
<SettingsApplicationsOutlined fontSize="small" />
</ListItemIcon>
<FormattedMessage id="menu.account" defaultMessage="Account" />
</MenuItem>
<MenuItem onClick={() => { handleClose(), setAction('change-password') }}>
<MenuItem
onClick={() => {
handleClose(), setAction('change-password')
}}
>
<ListItemIcon>
<LockOpenOutlined fontSize="small" />
</ListItemIcon>
@ -76,15 +86,12 @@ const AccountMenu = (): React.ReactElement => {
</Link>
</MenuItem>
</Menu>
{action == 'change-password' &&
{action == 'change-password' && (
<ChangePasswordDialog onClose={() => setAction(undefined)} />
}
{action == 'account-info' &&
<AccountInfoDialog onClose={() => setAction(undefined)} />
}
</span>);
)}
{action == 'account-info' && <AccountInfoDialog onClose={() => setAction(undefined)} />}
</span>
)
}
export default AccountMenu;
export default AccountMenu

View File

@ -1,137 +1,155 @@
import React from 'react';
import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined';
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined';
import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined';
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
import EditOutlinedIcon from '@material-ui/icons/EditOutlined';
import PublicOutlinedIcon from '@material-ui/icons/PublicOutlined';
import PrintOutlinedIcon from '@material-ui/icons/PrintOutlined';
import ShareOutlinedIcon from '@material-ui/icons/ShareOutlined';
import LabelOutlined from '@material-ui/icons/LabelOutlined';
import React from 'react'
import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined'
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined'
import DeleteOutlinedIcon from '@material-ui/icons/DeleteOutlined'
import CloudDownloadOutlinedIcon from '@material-ui/icons/CloudDownloadOutlined'
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'
import EditOutlinedIcon from '@material-ui/icons/EditOutlined'
import PublicOutlinedIcon from '@material-ui/icons/PublicOutlined'
import PrintOutlinedIcon from '@material-ui/icons/PrintOutlined'
import ShareOutlinedIcon from '@material-ui/icons/ShareOutlined'
import LabelOutlined from '@material-ui/icons/LabelOutlined'
import { FormattedMessage } from 'react-intl';
import { fetchMapById } from '../../../redux/clientSlice';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import Divider from '@material-ui/core/Divider';
export type ActionType = 'open' | 'share' | 'import' | 'delete' | 'info' | 'create' | 'duplicate' | 'export' | 'label' | 'rename' | 'print' | 'info' | 'publish' | 'history' | undefined;
import { FormattedMessage } from 'react-intl'
import { fetchMapById } from '../../../redux/clientSlice'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import Divider from '@material-ui/core/Divider'
export type ActionType =
| 'open'
| 'share'
| 'import'
| 'delete'
| 'info'
| 'create'
| 'duplicate'
| 'export'
| 'label'
| 'rename'
| 'print'
| 'info'
| 'publish'
| 'history'
| undefined
interface ActionProps {
onClose: (action: ActionType) => void;
anchor?: HTMLElement;
mapId?: number
onClose: (action: ActionType) => void
anchor?: HTMLElement
mapId?: number
}
const ActionChooser = (props: ActionProps): React.ReactElement => {
const { anchor, onClose, mapId } = props;
const { anchor, onClose, mapId } = props
const handleOnClose = (action: ActionType): ((event: React.MouseEvent<HTMLLIElement>) => void) => {
return (event): void => {
event.stopPropagation();
onClose(action);
};
}
const handleOnClose = (
action: ActionType
): ((event: React.MouseEvent<HTMLLIElement>) => void) => {
return (event): void => {
event.stopPropagation()
onClose(action)
}
}
const role = mapId ? fetchMapById(mapId)?.map?.role : undefined;
return (
<Menu
anchorEl={anchor}
keepMounted
open={Boolean(anchor)}
onClose={handleOnClose(undefined)}
elevation={1}
>
<MenuItem onClick={handleOnClose('open')} style={{ width: "220px" }}>
<ListItemIcon>
<DescriptionOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.open" defaultMessage="Open" />
</MenuItem>
const role = mapId ? fetchMapById(mapId)?.map?.role : undefined
return (
<Menu
anchorEl={anchor}
keepMounted
open={Boolean(anchor)}
onClose={handleOnClose(undefined)}
elevation={1}
>
<MenuItem onClick={handleOnClose('open')} style={{ width: '220px' }}>
<ListItemIcon>
<DescriptionOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.open" defaultMessage="Open" />
</MenuItem>
<Divider />
<Divider />
<MenuItem onClick={handleOnClose('duplicate')}>
<ListItemIcon>
<FileCopyOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.duplicate" defaultMessage="Duplicate" />
</MenuItem>
<MenuItem onClick={handleOnClose('duplicate')}>
<ListItemIcon>
<FileCopyOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.duplicate" defaultMessage="Duplicate" />
</MenuItem>
{role == 'owner' &&
<MenuItem onClick={handleOnClose('rename')}>
<ListItemIcon>
<EditOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.rename" defaultMessage="Rename" />
</MenuItem>
}
{role == 'owner' && (
<MenuItem onClick={handleOnClose('rename')}>
<ListItemIcon>
<EditOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.rename" defaultMessage="Rename" />
</MenuItem>
)}
<MenuItem onClick={handleOnClose('label')}>
<ListItemIcon>
<LabelOutlined />
</ListItemIcon>
<FormattedMessage id="action.label" defaultMessage="Add Label" />
</MenuItem>
<MenuItem onClick={handleOnClose('label')}>
<ListItemIcon>
<LabelOutlined />
</ListItemIcon>
<FormattedMessage id="action.label" defaultMessage="Add Label" />
</MenuItem>
<MenuItem onClick={handleOnClose('delete')}>
<ListItemIcon>
<DeleteOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.delete" defaultMessage="Delete" />
</MenuItem>
<Divider />
<MenuItem onClick={handleOnClose('delete')}>
<ListItemIcon>
<DeleteOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.delete" defaultMessage="Delete" />
</MenuItem>
<Divider />
<MenuItem onClick={handleOnClose('export')}>
<ListItemIcon>
<CloudDownloadOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.export" defaultMessage="Export" />
</MenuItem>
<MenuItem onClick={handleOnClose('export')}>
<ListItemIcon>
<CloudDownloadOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.export" defaultMessage="Export" />
</MenuItem>
<MenuItem onClick={handleOnClose('print')}>
<ListItemIcon>
<PrintOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.print" defaultMessage="Print" />
</MenuItem>
<MenuItem onClick={handleOnClose('print')}>
<ListItemIcon>
<PrintOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.print" defaultMessage="Print" />
</MenuItem>
{role == 'owner' &&
<MenuItem onClick={handleOnClose('publish')}>
<ListItemIcon>
<PublicOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.publish" defaultMessage="Publish" />
</MenuItem>
}
{role == 'owner' && (
<MenuItem onClick={handleOnClose('publish')}>
<ListItemIcon>
<PublicOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.publish" defaultMessage="Publish" />
</MenuItem>
)}
{role == 'owner' &&
<MenuItem onClick={handleOnClose('share')}>
<ListItemIcon>
<ShareOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.share" defaultMessage="Share" />
</MenuItem>
}
<Divider />
{role == 'owner' && (
<MenuItem onClick={handleOnClose('share')}>
<ListItemIcon>
<ShareOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.share" defaultMessage="Share" />
</MenuItem>
)}
<Divider />
<MenuItem onClick={handleOnClose('info')}>
<ListItemIcon>
<InfoOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.info" defaultMessage="Info" />
</MenuItem>
<MenuItem onClick={handleOnClose('info')}>
<ListItemIcon>
<InfoOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.info" defaultMessage="Info" />
</MenuItem>
{role != 'viewer' &&
<MenuItem onClick={handleOnClose('history')}>
<ListItemIcon>
<DeleteOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.history" defaultMessage="History" />
</MenuItem>
}
</Menu>);
{role != 'viewer' && (
<MenuItem onClick={handleOnClose('history')}>
<ListItemIcon>
<DeleteOutlinedIcon />
</ListItemIcon>
<FormattedMessage id="action.history" defaultMessage="History" />
</MenuItem>
)}
</Menu>
)
}
export default ActionChooser;
export default ActionChooser

View File

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

View File

@ -1,38 +1,40 @@
import React from "react";
import { FormattedMessage } from "react-intl";
import { ErrorInfo } from "../../../../classes/client";
import { StyledDialog, StyledDialogActions, StyledDialogContent, StyledDialogTitle } from "./style";
import GlobalError from "../../../form/global-error";
import DialogContentText from "@material-ui/core/DialogContentText";
import Button from "@material-ui/core/Button";
import { PaperProps } from "@material-ui/core/Paper";
import React from 'react'
import { FormattedMessage } from 'react-intl'
import { ErrorInfo } from '../../../../classes/client'
import { StyledDialog, StyledDialogActions, StyledDialogContent, StyledDialogTitle } from './style'
import GlobalError from '../../../form/global-error'
import DialogContentText from '@material-ui/core/DialogContentText'
import Button from '@material-ui/core/Button'
import { PaperProps } from '@material-ui/core/Paper'
export type DialogProps = {
onClose: () => void;
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void;
children: unknown;
error?: ErrorInfo;
onClose: () => void
onSubmit?: (event: React.FormEvent<HTMLFormElement>) => void
children: unknown
error?: ErrorInfo
title: string;
description?: string;
title: string
description?: string
submitButton?: string;
actionUrl?: string;
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
PaperProps?: Partial<PaperProps>;
submitButton?: string
actionUrl?: string
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false
PaperProps?: Partial<PaperProps>
}
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>) => {
e.preventDefault();
e.preventDefault()
if (onSubmit) {
onSubmit(e);
onSubmit(e)
}
}
const description = props.description ? (<DialogContentText>{props.description}</DialogContentText>) : null;
const description = props.description ? (
<DialogContentText>{props.description}</DialogContentText>
) : null
return (
<div>
<StyledDialog
@ -40,11 +42,10 @@ const BaseDialog = (props: DialogProps): React.ReactElement => {
onClose={onClose}
maxWidth={maxWidth}
PaperProps={PaperProps}
fullWidth={true}>
fullWidth={true}
>
<form autoComplete="off" onSubmit={handleOnSubmit}>
<StyledDialogTitle>
{props.title}
</StyledDialogTitle>
<StyledDialogTitle>{props.title}</StyledDialogTitle>
<StyledDialogContent>
{description}
@ -53,30 +54,32 @@ const BaseDialog = (props: DialogProps): React.ReactElement => {
</StyledDialogContent>
<StyledDialogActions>
<Button
type="button"
color="primary"
size="medium"
onClick={onClose} >
{onSubmit ? (<FormattedMessage id="action.cancel-button" defaultMessage="Cancel" />) :
(<FormattedMessage id="action.close-button" defaultMessage="Close" />)
}
<Button type="button" color="primary" size="medium" onClick={onClose}>
{onSubmit ? (
<FormattedMessage
id="action.cancel-button"
defaultMessage="Cancel"
/>
) : (
<FormattedMessage id="action.close-button" defaultMessage="Close" />
)}
</Button>
{onSubmit &&
{onSubmit && (
<Button
color="primary"
size="medium"
variant="contained"
type="submit"
disableElevation={true}>
disableElevation={true}
>
{props.submitButton}
</Button>
}
)}
</StyledDialogActions>
</form>
</StyledDialog>
</div>
);
)
}
export default BaseDialog;
export default BaseDialog

View File

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

View File

@ -1,79 +1,109 @@
import React from 'react';
import { useIntl } from 'react-intl';
import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import FormControl from '@material-ui/core/FormControl';
import React from 'react'
import { useIntl } from 'react-intl'
import { useMutation } from 'react-query'
import { useSelector } from 'react-redux'
import FormControl from '@material-ui/core/FormControl'
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice';
import Input from '../../../form/input';
import BaseDialog from '../base-dialog';
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client'
import { activeInstance } from '../../../../redux/clientSlice'
import Input from '../../../form/input'
import BaseDialog from '../base-dialog'
export type CreateModel = {
title: string;
description?: string;
title: string
description?: string
}
export type CreateProps = {
onClose: () => void
}
const defaultModel: CreateModel = { title: '', description: '' };
const defaultModel: CreateModel = { title: '', description: '' }
const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => {
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<CreateModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const client: Client = useSelector(activeInstance)
const [model, setModel] = React.useState<CreateModel>(defaultModel)
const [error, setError] = React.useState<ErrorInfo>()
const intl = useIntl()
const mutation = useMutation<number, ErrorInfo, CreateModel>((model: CreateModel) => {
return client.createMap(model);
},
const mutation = useMutation<number, ErrorInfo, CreateModel>(
(model: CreateModel) => {
return client.createMap(model)
},
{
onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit`;
window.location.href = `/c/maps/${mapId}/edit`
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
onClose()
setModel(defaultModel)
setError(undefined)
}
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
event.preventDefault()
mutation.mutate(model)
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
event.preventDefault()
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value });
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof BasicMapInfo]: value })
}
return (
<div>
<BaseDialog onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
title={intl.formatMessage({ id: 'create.title', defaultMessage: 'Create a new mindmap' })}
description={intl.formatMessage({ id: 'create.description', defaultMessage: 'Please, fill the new map name and description.' })}
submitButton={intl.formatMessage({ id: 'create.button', defaultMessage: 'Create' })}>
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({
id: 'create.title',
defaultMessage: 'Create a new mindmap',
})}
description={intl.formatMessage({
id: 'create.description',
defaultMessage: 'Please, fill the new map name and description.',
})}
submitButton={intl.formatMessage({ id: 'create.button', defaultMessage: 'Create' })}
>
<FormControl fullWidth={true}>
<Input name="title" type="text" label={intl.formatMessage({ id: "action.rename-name-placeholder", defaultMessage: "Name" })}
value={model.title} onChange={handleOnChange} error={error} fullWidth={true} />
<Input
name="title"
type="text"
label={intl.formatMessage({
id: 'action.rename-name-placeholder',
defaultMessage: 'Name',
})}
value={model.title}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input name="description" type="text" label={intl.formatMessage({ id: "action.rename-description-placeholder", defaultMessage: "Description" })}
value={model.description} onChange={handleOnChange} required={false} fullWidth={true} />
<Input
name="description"
type="text"
label={intl.formatMessage({
id: 'action.rename-description-placeholder',
defaultMessage: 'Description',
})}
value={model.description}
onChange={handleOnChange}
required={false}
fullWidth={true}
/>
</FormControl>
</BaseDialog>
</div>
);
)
}
export default CreateDialog;
export default CreateDialog

View File

@ -1,59 +1,60 @@
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux";
import Client, { ErrorInfo } from "../../../../classes/client";
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from "..";
import BaseDialog from "../base-dialog";
import Alert from "@material-ui/lab/Alert";
import AlertTitle from "@material-ui/lab/AlertTitle";
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { useMutation, useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
import Client, { ErrorInfo } from '../../../../classes/client'
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'
import { SimpleDialogProps, handleOnMutationSuccess } from '..'
import BaseDialog from '../base-dialog'
import Alert from '@material-ui/lab/Alert'
import AlertTitle from '@material-ui/lab/AlertTitle'
const DeleteDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl()
const client: Client = useSelector(activeInstance)
const queryClient = useQueryClient()
const [error, setError] = React.useState<ErrorInfo>()
const mutation = useMutation((id: number) => client.deleteMap(id),
{
onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error: ErrorInfo) => {
setError(error);
}
const mutation = useMutation((id: number) => client.deleteMap(id), {
onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error: ErrorInfo) => {
setError(error)
},
})
const handleOnClose = (): void => {
onClose()
}
);
const handleOnClose = (): void => {
onClose();
};
const handleOnSubmit = (): void => {
mutation.mutate(mapId)
}
const handleOnSubmit = (): void => {
mutation.mutate(mapId);
}
// Fetch map model to be rendered ...
const { map } = fetchMapById(mapId);
const alertTitle = `Delete ${map?.title}`;
return (
<div>
<BaseDialog
error={error}
onClose={handleOnClose} onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}
submitButton={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}>
<Alert severity="warning">
<AlertTitle>{alertTitle}</AlertTitle>
<FormattedMessage id="action.delete-description"
defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?." />
</Alert>
</BaseDialog>
</div>
);
// Fetch map model to be rendered ...
const { map } = fetchMapById(mapId)
const alertTitle = `Delete ${map?.title}`
return (
<div>
<BaseDialog
error={error}
onClose={handleOnClose}
onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: 'action.delete-title', defaultMessage: 'Delete' })}
submitButton={intl.formatMessage({
id: 'action.delete-title',
defaultMessage: 'Delete',
})}
>
<Alert severity="warning">
<AlertTitle>{alertTitle}</AlertTitle>
<FormattedMessage
id="action.delete-description"
defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?."
/>
</Alert>
</BaseDialog>
</div>
)
}
export default DeleteDialog;
export default DeleteDialog

View File

@ -1,55 +1,68 @@
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux";
import Client from "../../../../classes/client";
import { activeInstance } from '../../../../redux/clientSlice';
import { handleOnMutationSuccess } from "..";
import BaseDialog from "../base-dialog";
import Alert from "@material-ui/lab/Alert";
import AlertTitle from "@material-ui/lab/AlertTitle";
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { useMutation, useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
import Client from '../../../../classes/client'
import { activeInstance } from '../../../../redux/clientSlice'
import { handleOnMutationSuccess } from '..'
import BaseDialog from '../base-dialog'
import Alert from '@material-ui/lab/Alert'
import AlertTitle from '@material-ui/lab/AlertTitle'
export type DeleteMultiselectDialogProps = {
mapsId: number[],
onClose: () => void
mapsId: number[]
onClose: () => void
}
const DeleteMultiselectDialog = ({ onClose, mapsId }: DeleteMultiselectDialogProps): React.ReactElement => {
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const DeleteMultiselectDialog = ({
onClose,
mapsId,
}: DeleteMultiselectDialogProps): React.ReactElement => {
const intl = useIntl()
const client: Client = useSelector(activeInstance)
const queryClient = useQueryClient()
const mutation = useMutation((ids: number[]) => client.deleteMaps(ids),
{
onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error) => {
console.error(`Unexpected error ${error}`);
}
const mutation = useMutation((ids: number[]) => client.deleteMaps(ids), {
onSuccess: () => handleOnMutationSuccess(onClose, queryClient),
onError: (error) => {
console.error(`Unexpected error ${error}`)
},
})
const handleOnClose = (): void => {
onClose()
}
);
const handleOnClose = (): void => {
onClose();
};
const handleOnSubmit = (): void => {
mutation.mutate(mapsId)
}
const handleOnSubmit = (): void => {
mutation.mutate(mapsId);
}
return (
<div>
<BaseDialog
onClose={handleOnClose} onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })}
submitButton={intl.formatMessage({ id: "action.delete-title", defaultMessage: "Delete" })} >
<Alert severity="warning">
<AlertTitle><FormattedMessage id="deletem.title" defaultMessage="All selected maps will be deleted" /></AlertTitle>
<FormattedMessage id="action.delete-description" defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?." />
</Alert>
</BaseDialog>
</div>
);
return (
<div>
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: 'action.delete-title', defaultMessage: 'Delete' })}
submitButton={intl.formatMessage({
id: 'action.delete-title',
defaultMessage: 'Delete',
})}
>
<Alert severity="warning">
<AlertTitle>
<FormattedMessage
id="deletem.title"
defaultMessage="All selected maps will be deleted"
/>
</AlertTitle>
<FormattedMessage
id="action.delete-description"
defaultMessage="Deleted mindmap can not be recovered. Do you want to continue ?."
/>
</Alert>
</BaseDialog>
</div>
)
}
export default DeleteMultiselectDialog;
export default DeleteMultiselectDialog

View File

@ -1,87 +1,117 @@
import React, { useEffect } from "react";
import { useIntl } from "react-intl";
import { useMutation } from "react-query";
import FormControl from "@material-ui/core/FormControl";
import { useSelector } from "react-redux";
import React, { useEffect } from 'react'
import { useIntl } from 'react-intl'
import { useMutation } from 'react-query'
import FormControl from '@material-ui/core/FormControl'
import { useSelector } from 'react-redux'
import Client, { BasicMapInfo, ErrorInfo } from "../../../../classes/client";
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import Input from "../../../form/input";
import { SimpleDialogProps } from "..";
import BaseDialog from "../base-dialog";
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client'
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'
import Input from '../../../form/input'
import { SimpleDialogProps } from '..'
import BaseDialog from '../base-dialog'
export type DuplicateModel = {
id: number;
title: string;
description?: string;
id: number
title: 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 service: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<DuplicateModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const service: Client = useSelector(activeInstance)
const [model, setModel] = React.useState<DuplicateModel>(defaultModel)
const [error, setError] = React.useState<ErrorInfo>()
const intl = useIntl();
const intl = useIntl()
const mutation = useMutation<number, ErrorInfo, DuplicateModel>((model: DuplicateModel) => {
const { id, ...rest } = model;
return service.duplicateMap(id, rest);
},
const mutation = useMutation<number, ErrorInfo, DuplicateModel>(
(model: DuplicateModel) => {
const { id, ...rest } = model
return service.duplicateMap(id, rest)
},
{
onSuccess: (mapId) => {
window.location.href = `/c/maps/${mapId}/edit`;
window.location.href = `/c/maps/${mapId}/edit`
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const handleOnClose = (): void => {
onClose();
};
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value });
onClose()
}
const { map } = fetchMapById(mapId);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault()
mutation.mutate(model)
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault()
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof BasicMapInfo]: value })
}
const { map } = fetchMapById(mapId)
useEffect(() => {
if (open && map) {
setModel(map);
setModel(map)
} else {
setModel(defaultModel);
setError(undefined);
setModel(defaultModel)
setError(undefined)
}
}, [mapId])
return (
<div>
<BaseDialog onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({ id: 'duplicate.title', defaultMessage: 'Duplicate' })}
description={intl.formatMessage({ id: 'rename.description', defaultMessage: 'Please, fill the new map name and description.' })}
submitButton={intl.formatMessage({ id: 'duplicate.title', defaultMessage: 'Duplicate' })}>
description={intl.formatMessage({
id: 'rename.description',
defaultMessage: 'Please, fill the new map name and description.',
})}
submitButton={intl.formatMessage({
id: 'duplicate.title',
defaultMessage: 'Duplicate',
})}
>
<FormControl fullWidth={true}>
<Input name="title" type="text" label={intl.formatMessage({ id: "action.rename-name-placeholder", defaultMessage: "Name" })}
value={model.title} onChange={handleOnChange} error={error} fullWidth={true} />
<Input
name="title"
type="text"
label={intl.formatMessage({
id: 'action.rename-name-placeholder',
defaultMessage: 'Name',
})}
value={model.title}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input name="description" type="text" label={intl.formatMessage({ id: "action.rename-description-placeholder", defaultMessage: "Description" })}
value={model.description} onChange={handleOnChange} required={false} fullWidth={true} />
<Input
name="description"
type="text"
label={intl.formatMessage({
id: 'action.rename-description-placeholder',
defaultMessage: 'Description',
})}
value={model.description}
onChange={handleOnChange}
required={false}
fullWidth={true}
/>
</FormControl>
</BaseDialog>
</div>
);
)
}
export default DuplicateDialog;
export default DuplicateDialog

View File

@ -1,97 +1,114 @@
import React, { useEffect } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import BaseDialog from "../base-dialog";
import { useStyles } from './style';
import Alert from "@material-ui/lab/Alert";
import { fetchMapById } from "../../../../redux/clientSlice";
import FormControl from "@material-ui/core/FormControl";
import RadioGroup from "@material-ui/core/RadioGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Radio from "@material-ui/core/Radio";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import React, { useEffect } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import BaseDialog from '../base-dialog'
import { useStyles } from './style'
import Alert from '@material-ui/lab/Alert'
import { fetchMapById } from '../../../../redux/clientSlice'
import FormControl from '@material-ui/core/FormControl'
import RadioGroup from '@material-ui/core/RadioGroup'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import Radio from '@material-ui/core/Radio'
import Select from '@material-ui/core/Select'
import MenuItem from '@material-ui/core/MenuItem'
type ExportFormat = 'pdf' | 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'txt';
type ExportGroup = 'image' | 'document' | 'mindmap-tool';
type ExportFormat = 'pdf' | 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'txt'
type ExportGroup = 'image' | 'document' | 'mindmap-tool'
type ExportDialogProps = {
mapId: number,
enableImgExport: boolean,
svgXml?: string,
onClose: () => void,
mapId: number
enableImgExport: boolean
svgXml?: string
onClose: () => void
}
const ExportDialog = ({ mapId, onClose, enableImgExport, svgXml }: ExportDialogProps): React.ReactElement => {
const intl = useIntl();
const [submit, setSubmit] = React.useState<boolean>(false);
const ExportDialog = ({
mapId,
onClose,
enableImgExport,
svgXml,
}: ExportDialogProps): React.ReactElement => {
const intl = useIntl()
const [submit, setSubmit] = React.useState<boolean>(false)
// 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
const [formTransformtRef, setTransformFormRef] = React.useState<any>();
const [exportGroup, setExportGroup] = React.useState<ExportGroup>(enableImgExport ? 'image' : 'document');
const [exportFormat, setExportFormat] = React.useState<ExportFormat>(enableImgExport ? 'svg' : 'xls');
const classes = useStyles();
const [formTransformtRef, setTransformFormRef] = React.useState<any>()
const [exportGroup, setExportGroup] = React.useState<ExportGroup>(
enableImgExport ? 'image' : 'document'
)
const [exportFormat, setExportFormat] = React.useState<ExportFormat>(
enableImgExport ? 'svg' : 'xls'
)
const classes = useStyles()
const handleOnExportFormatChange = (event) => {
setExportFormat(event.target.value);
};
setExportFormat(event.target.value)
}
const handleOnGroupChange = (event) => {
const value: ExportGroup = event.target.value;
setExportGroup(value);
const value: ExportGroup = event.target.value
setExportGroup(value)
let defaultFormat: ExportFormat;
let defaultFormat: ExportFormat
switch (value) {
case 'document':
defaultFormat = 'pdf';
break;
defaultFormat = 'pdf'
break
case 'image':
defaultFormat = 'svg';
break;
defaultFormat = 'svg'
break
case 'mindmap-tool':
defaultFormat = 'wxml';
break;
defaultFormat = 'wxml'
break
}
setExportFormat(defaultFormat);
setExportFormat(defaultFormat)
}
const handleOnClose = (): void => {
onClose();
};
onClose()
}
const handleOnSubmit = (): void => {
setSubmit(true);
setSubmit(true)
}
useEffect(() => {
if (submit) {
// Depending on the type of export. It will require differt POST.
if (exportFormat == 'pdf' || exportFormat == "svg" || exportFormat == "jpg" || exportFormat == "png") {
formTransformtRef?.submit();
if (
exportFormat == 'pdf' ||
exportFormat == 'svg' ||
exportFormat == 'jpg' ||
exportFormat == 'png'
) {
formTransformtRef?.submit()
} else {
formExportRef?.submit();
formExportRef?.submit()
}
onClose();
onClose()
}
}, [submit]);
}, [submit])
const { map } = fetchMapById(mapId);
const { map } = fetchMapById(mapId)
return (
<div>
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
title={intl.formatMessage({ id: "export.title", defaultMessage: "Export" })}
description={"Export this map in the format that you want and start using it in your presentations or sharing by email"}
submitButton={intl.formatMessage({ id: "export.title", defaultMessage: "Export" })} >
title={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })}
description={
'Export this map in the format that you want and start using it in your presentations or sharing by email'
}
submitButton={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })}
>
<Alert severity="info">
<FormattedMessage id="export.warning" defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar." />
<FormattedMessage
id="export.warning"
defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar."
/>
</Alert>
<FormControl component="fieldset" >
<FormControl component="fieldset">
<RadioGroup name="export" value={exportGroup} onChange={handleOnGroupChange}>
<FormControl>
<FormControlLabel
@ -99,21 +116,35 @@ const ExportDialog = ({ mapId, onClose, enableImgExport, svgXml }: ExportDialogP
value="image"
disabled={!enableImgExport}
control={<Radio color="primary" />}
label={intl.formatMessage({ id: "export.image", defaultMessage: "Image: Get a graphic representation of your map including all colors and shapes." })}
label={intl.formatMessage({
id: 'export.image',
defaultMessage:
'Image: Get a graphic representation of your map including all colors and shapes.',
})}
color="secondary"
style={{ fontSize: '9px' }} />
{(exportGroup == 'image') &&
(<Select
style={{ fontSize: '9px' }}
/>
{exportGroup == 'image' && (
<Select
onSelect={handleOnExportFormatChange}
variant="outlined"
value={exportFormat}
className={classes.label}>
<MenuItem value="svg" className={classes.menu}>Scalable Vector Graphics (SVG)</MenuItem>
<MenuItem value="pdf" className={classes.select} >Portable Document Format (PDF)</MenuItem>
<MenuItem value="png" className={classes.menu}>Portable Network Graphics (PNG)</MenuItem>
<MenuItem value="jpg" className={classes.menu}>JPEG Image (JPEG)</MenuItem>
</Select>)
}
className={classes.label}
>
<MenuItem value="svg" className={classes.menu}>
Scalable Vector Graphics (SVG)
</MenuItem>
<MenuItem value="pdf" className={classes.select}>
Portable Document Format (PDF)
</MenuItem>
<MenuItem value="png" className={classes.menu}>
Portable Network Graphics (PNG)
</MenuItem>
<MenuItem value="jpg" className={classes.menu}>
JPEG Image (JPEG)
</MenuItem>
</Select>
)}
</FormControl>
<FormControl>
@ -121,18 +152,28 @@ const ExportDialog = ({ mapId, onClose, enableImgExport, svgXml }: ExportDialogP
className={classes.label}
value="document"
control={<Radio color="primary" />}
label={intl.formatMessage({ id: "export.document-label", defaultMessage: "Document: Export your mindmap in a self-contained document ready to share" })}
color="secondary" />
{exportGroup == 'document' &&
(<Select onChange={handleOnExportFormatChange}
label={intl.formatMessage({
id: 'export.document-label',
defaultMessage:
'Document: Export your mindmap in a self-contained document ready to share',
})}
color="secondary"
/>
{exportGroup == 'document' && (
<Select
onChange={handleOnExportFormatChange}
variant="outlined"
value={exportFormat}
className={classes.select}
>
<MenuItem className={classes.select} value="xls">Microsoft Excel (XLS)</MenuItem>
<MenuItem className={classes.select} value="txt">Plain Text File (TXT)</MenuItem>
</Select>)
}
<MenuItem className={classes.select} value="xls">
Microsoft Excel (XLS)
</MenuItem>
<MenuItem className={classes.select} value="txt">
Plain Text File (TXT)
</MenuItem>
</Select>
)}
</FormControl>
<FormControl>
@ -140,39 +181,57 @@ const ExportDialog = ({ mapId, onClose, enableImgExport, svgXml }: ExportDialogP
className={classes.label}
value="mindmap-tool"
control={<Radio color="primary" />}
label={intl.formatMessage({ id: "export.document", defaultMessage: "Mindmap Tools: Export your mindmap in thirdparty mindmap tool formats" })}
color="secondary" />
{exportGroup == 'mindmap-tool' &&
(<Select
label={intl.formatMessage({
id: 'export.document',
defaultMessage:
'Mindmap Tools: Export your mindmap in thirdparty mindmap tool formats',
})}
color="secondary"
/>
{exportGroup == 'mindmap-tool' && (
<Select
onChange={handleOnExportFormatChange}
variant="outlined"
className={classes.select}
value={exportFormat}
>
<MenuItem className={classes.select} value="wxml">WiseMapping (WXML)</MenuItem>
<MenuItem className={classes.select} value="mm">Freemind 1.0.1 (MM)</MenuItem>
<MenuItem className={classes.select} value="mmap">MindManager (MMAP)</MenuItem>
</Select>)
}
<MenuItem className={classes.select} value="wxml">
WiseMapping (WXML)
</MenuItem>
<MenuItem className={classes.select} value="mm">
Freemind 1.0.1 (MM)
</MenuItem>
<MenuItem className={classes.select} value="mmap">
MindManager (MMAP)
</MenuItem>
</Select>
)}
</FormControl>
</RadioGroup>
</FormControl>
</BaseDialog>
{/* Hidden form for the purpose of summit */}
<form action={`/c/restful/maps/${mapId}.${exportFormat}`} ref={setExportFormRef} method="GET">
<form
action={`/c/restful/maps/${mapId}.${exportFormat}`}
ref={setExportFormRef}
method="GET"
>
<input name="download" type="hidden" value={exportFormat} />
<input name="filename" type="hidden" value={map?.title} />
</form>
<form action={`/c/restful/transform.${exportFormat}`} ref={setTransformFormRef} method="POST">
<form
action={`/c/restful/transform.${exportFormat}`}
ref={setTransformFormRef}
method="POST"
>
<input name="download" type="hidden" value={exportFormat} />
<input name="filename" type="hidden" value={map?.title} />
<input name="svgXml" id="svgXml" value={svgXml} type="hidden" />
</form>
</div>
);
)
}
export default ExportDialog;
export default ExportDialog

View File

@ -1,5 +1,5 @@
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import createStyles from '@material-ui/core/styles/createStyles'
import makeStyles from '@material-ui/core/styles/makeStyles'
export const useStyles = makeStyles(() =>
createStyles({
@ -8,13 +8,13 @@ export const useStyles = makeStyles(() =>
borderRadius: '9px',
width: '300px',
fontSize: '14px',
margin: '0px 40px'
margin: '0px 40px',
},
menu: {
fontSize: '14px'
fontSize: '14px',
},
label: {
margin: '5px 0px' }
margin: '5px 0px',
},
})
);
)

View File

@ -1,90 +1,133 @@
import React, { ErrorInfo } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useQuery } from "react-query";
import { useSelector } from "react-redux";
import Client, { ChangeHistory } from "../../../../classes/client";
import { activeInstance } from '../../../../redux/clientSlice';
import { SimpleDialogProps } from "..";
import BaseDialog from "../base-dialog";
import dayjs from "dayjs";
import TableContainer from "@material-ui/core/TableContainer";
import Table from "@material-ui/core/Table";
import TableRow from "@material-ui/core/TableRow";
import TableCell from "@material-ui/core/TableCell";
import TableHead from "@material-ui/core/TableHead";
import TableBody from "@material-ui/core/TableBody";
import Tooltip from "@material-ui/core/Tooltip";
import Link from "@material-ui/core/Link";
import Paper from "@material-ui/core/Paper";
import React, { ErrorInfo } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { useQuery } from 'react-query'
import { useSelector } from 'react-redux'
import Client, { ChangeHistory } from '../../../../classes/client'
import { activeInstance } from '../../../../redux/clientSlice'
import { SimpleDialogProps } from '..'
import BaseDialog from '../base-dialog'
import dayjs from 'dayjs'
import TableContainer from '@material-ui/core/TableContainer'
import Table from '@material-ui/core/Table'
import TableRow from '@material-ui/core/TableRow'
import TableCell from '@material-ui/core/TableCell'
import TableHead from '@material-ui/core/TableHead'
import TableBody from '@material-ui/core/TableBody'
import Tooltip from '@material-ui/core/Tooltip'
import Link from '@material-ui/core/Link'
import Paper from '@material-ui/core/Paper'
const HistoryDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl();
const intl = useIntl()
const client: Client = useSelector(activeInstance);
const { data } = useQuery<unknown, ErrorInfo, ChangeHistory[]>('history', () => {
return client.fetchHistory(mapId);
});
const changeHistory: ChangeHistory[] = data ? data : [];
const client: Client = useSelector(activeInstance)
const { data } = useQuery<unknown, ErrorInfo, ChangeHistory[]>('history', () => {
return client.fetchHistory(mapId)
})
const changeHistory: ChangeHistory[] = data ? data : []
const handleOnClose = (): void => {
onClose();
};
const handleOnClose = (): void => {
onClose()
}
const handleOnClick = (event, vid): void => {
event.preventDefault();
client.revertHistory(mapId, vid)
.then(() => {
handleOnClose();
})
};
const handleOnClick = (event, vid): void => {
event.preventDefault()
client.revertHistory(mapId, vid).then(() => {
handleOnClose()
})
}
return (
<div>
<BaseDialog
onClose={handleOnClose}
title={intl.formatMessage({ id: "action.history-title", defaultMessage: "Version history" })}
description={intl.formatMessage({ id: "action.history-description", defaultMessage: "List of changes introduced in the last 90 days." })} >
<TableContainer component={Paper} style={{ maxHeight: '200px' }} variant="outlined">
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell align="left"><FormattedMessage id="maps.modified-by" defaultMessage="Modified By" /></TableCell>
<TableCell align="left"><FormattedMessage id="maps.modified" defaultMessage="Modified" /></TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{changeHistory.length == 0 ? (
<TableRow>
<TableCell colSpan={4}><FormattedMessage id='history.no-changes' defaultMessage='There is no changes available' />
</TableCell>
</TableRow>
) :
changeHistory.map((row) => (
<TableRow key={row.id}>
<TableCell align="left">{row.lastModificationBy}</TableCell>
<TableCell align="left">
<Tooltip title={dayjs(row.lastModificationTime).format("lll")} placement="bottom-start">
<span>{dayjs(row.lastModificationTime).fromNow()}</span>
</Tooltip>
</TableCell>
<TableCell align="left"><Link href={`c/maps/${mapId}/${row.id}/view`} target="history"><FormattedMessage id="maps.view" defaultMessage="View" /></Link></TableCell>
<TableCell align="left"><Link href="#" onClick={(e) => handleOnClick(e, row.id)}><FormattedMessage id="maps.revert" defaultMessage="Revert" /></Link></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</BaseDialog>
</div>
);
return (
<div>
<BaseDialog
onClose={handleOnClose}
title={intl.formatMessage({
id: 'action.history-title',
defaultMessage: 'Version history',
})}
description={intl.formatMessage({
id: 'action.history-description',
defaultMessage: 'List of changes introduced in the last 90 days.',
})}
>
<TableContainer component={Paper} style={{ maxHeight: '200px' }} variant="outlined">
<Table size="small" stickyHeader>
<TableHead>
<TableRow>
<TableCell align="left">
<FormattedMessage
id="maps.modified-by"
defaultMessage="Modified By"
/>
</TableCell>
<TableCell align="left">
<FormattedMessage
id="maps.modified"
defaultMessage="Modified"
/>
</TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{changeHistory.length == 0 ? (
<TableRow>
<TableCell colSpan={4}>
<FormattedMessage
id="history.no-changes"
defaultMessage="There is no changes available"
/>
</TableCell>
</TableRow>
) : (
changeHistory.map((row) => (
<TableRow key={row.id}>
<TableCell align="left">{row.lastModificationBy}</TableCell>
<TableCell align="left">
<Tooltip
title={dayjs(row.lastModificationTime).format(
'lll'
)}
placement="bottom-start"
>
<span>
{dayjs(row.lastModificationTime).fromNow()}
</span>
</Tooltip>
</TableCell>
<TableCell align="left">
<Link
href={`c/maps/${mapId}/${row.id}/view`}
target="history"
>
<FormattedMessage
id="maps.view"
defaultMessage="View"
/>
</Link>
</TableCell>
<TableCell align="left">
<Link
href="#"
onClick={(e) => handleOnClick(e, row.id)}
>
<FormattedMessage
id="maps.revert"
defaultMessage="Revert"
/>
</Link>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>
</BaseDialog>
</div>
)
}
export default HistoryDialog;
export default HistoryDialog

View File

@ -1,101 +1,114 @@
import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl';
import React from 'react';
import Button from '@material-ui/core/Button'
import FormControl from '@material-ui/core/FormControl'
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice';
import Input from '../../../form/input';
import BaseDialog from '../base-dialog';
import { FormattedMessage, useIntl } from 'react-intl'
import { useMutation } from 'react-query'
import { useSelector } from 'react-redux'
import Client, { ErrorInfo } from '../../../../classes/client'
import { activeInstance } from '../../../../redux/clientSlice'
import Input from '../../../form/input'
import BaseDialog from '../base-dialog'
export type ImportModel = {
title: string;
description?: string;
contentType?: string;
content?: ArrayBuffer | null | string;
title: string
description?: string
contentType?: string
content?: ArrayBuffer | null | string
}
export type CreateProps = {
onClose: () => void
}
const defaultModel: ImportModel = { title: '' };
const defaultModel: ImportModel = { title: '' }
const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => {
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<ImportModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl();
const client: Client = useSelector(activeInstance)
const [model, setModel] = React.useState<ImportModel>(defaultModel)
const [error, setError] = React.useState<ErrorInfo>()
const intl = useIntl()
const mutation = useMutation<number, ErrorInfo, ImportModel>((model: ImportModel) => {
return client.importMap(model);
},
const mutation = useMutation<number, ErrorInfo, ImportModel>(
(model: ImportModel) => {
return client.importMap(model)
},
{
onSuccess: (mapId: number) => {
window.location.href = `/c/maps/${mapId}/edit`;
window.location.href = `/c/maps/${mapId}/edit`
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
onClose()
setModel(defaultModel)
setError(undefined)
}
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
event.preventDefault()
mutation.mutate(model)
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
event.preventDefault()
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ImportModel]: value });
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof ImportModel]: value })
}
const handleOnFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event?.target?.files;
const reader = new FileReader();
const files = event?.target?.files
const reader = new FileReader()
if (files) {
const file = files[0];
const file = files[0]
// Closure to capture the file information.
reader.onload = (event) => {
const fileContent = event?.target?.result;
model.content = fileContent;
const fileContent = event?.target?.result
model.content = fileContent
// Suggest file name ...
const fileName = file.name;
const fileName = file.name
if (fileName) {
const title = fileName.split('.')[0]
if (!model.title || 0 === model.title.length) {
model.title = title;
model.title = title
}
}
model.contentType = file.name.lastIndexOf(".wxml") != -1 ? "application/xml" : "application/freemind";
setModel({ ...model });
};
model.contentType =
file.name.lastIndexOf('.wxml') != -1
? 'application/xml'
: 'application/freemind'
setModel({ ...model })
}
// Read in the image file as a data URL.
reader.readAsText(file);
reader.readAsText(file)
}
};
}
return (
<div>
<BaseDialog onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
title={intl.formatMessage({ id: 'import.title', defaultMessage: 'Import existing mindmap' })}
description={intl.formatMessage({ id: 'import.description', defaultMessage: 'You can import FreeMind 1.0.1 and WiseMapping maps to your list of maps. Select the file you want to import.' })}
submitButton={intl.formatMessage({ id: 'import.button', defaultMessage: 'Create' })}>
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({
id: 'import.title',
defaultMessage: 'Import existing mindmap',
})}
description={intl.formatMessage({
id: 'import.description',
defaultMessage:
'You can import FreeMind 1.0.1 and WiseMapping maps to your list of maps. Select the file you want to import.',
})}
submitButton={intl.formatMessage({ id: 'import.button', defaultMessage: 'Create' })}
>
<FormControl fullWidth={true}>
<input
accept=".wxml,.mm"
@ -106,21 +119,49 @@ const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => {
onChange={handleOnFileChange}
/>
<Input name="title" type="text" label={intl.formatMessage({ id: "action.rename-name-placeholder", defaultMessage: "Name" })}
value={model.title} onChange={handleOnChange} error={error} fullWidth={true} />
<Input
name="title"
type="text"
label={intl.formatMessage({
id: 'action.rename-name-placeholder',
defaultMessage: 'Name',
})}
value={model.title}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input name="description" type="text" label={intl.formatMessage({ id: "action.rename-description-placeholder", defaultMessage: "Description" })}
value={model.description} onChange={handleOnChange} required={false} fullWidth={true} />
<Input
name="description"
type="text"
label={intl.formatMessage({
id: 'action.rename-description-placeholder',
defaultMessage: 'Description',
})}
value={model.description}
onChange={handleOnChange}
required={false}
fullWidth={true}
/>
<label htmlFor="contained-button-file">
<Button variant="outlined" color="primary" component="span" style={{ margin: '10px 5px', width: '100%' }}>
<FormattedMessage id="maps.choose-file" defaultMessage="Choose a file" />
<Button
variant="outlined"
color="primary"
component="span"
style={{ margin: '10px 5px', width: '100%' }}
>
<FormattedMessage
id="maps.choose-file"
defaultMessage="Choose a file"
/>
</Button>
</label>
</FormControl>
</BaseDialog>
</div>
);
)
}
export default ImportDialog;
export default ImportDialog

View File

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

View File

@ -1,104 +1,148 @@
import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { ErrorInfo } from '../../../../classes/client';
import BaseDialog from '../base-dialog';
import { SimpleDialogProps } from '..';
import { useStyles } from './style';
import dayjs from 'dayjs';
import { fetchMapById } from '../../../../redux/clientSlice';
import Paper from '@material-ui/core/Paper';
import Card from '@material-ui/core/Card';
import ListItem from '@material-ui/core/ListItem';
import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List';
import { ErrorInfo } from '../../../../classes/client'
import BaseDialog from '../base-dialog'
import { SimpleDialogProps } from '..'
import { useStyles } from './style'
import dayjs from 'dayjs'
import { fetchMapById } from '../../../../redux/clientSlice'
import Paper from '@material-ui/core/Paper'
import Card from '@material-ui/core/Card'
import ListItem from '@material-ui/core/ListItem'
import Typography from '@material-ui/core/Typography'
import List from '@material-ui/core/List'
const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId);
const [error, setError] = React.useState<ErrorInfo>();
const { map } = fetchMapById(mapId)
const [error, setError] = React.useState<ErrorInfo>()
const intl = useIntl();
const classes = useStyles();
const intl = useIntl()
const classes = useStyles()
const handleOnClose = (): void => {
onClose();
setError(undefined);
};
onClose()
setError(undefined)
}
return (
<BaseDialog onClose={handleOnClose} error={error}
<BaseDialog
onClose={handleOnClose}
error={error}
title={intl.formatMessage({ id: 'info.title', defaultMessage: 'Info' })}
description={intl.formatMessage({ id: 'info.description-msg', defaultMessage: 'By publishing the map you make it visible to everyone on the Internet.' })}
submitButton={intl.formatMessage({ id: 'info.button', defaultMessage: 'Accept' })}>
description={intl.formatMessage({
id: 'info.description-msg',
defaultMessage:
'By publishing the map you make it visible to everyone on the Internet.',
})}
submitButton={intl.formatMessage({ id: 'info.button', defaultMessage: 'Accept' })}
>
<Paper style={{ maxHeight: 200, overflowY: 'scroll' }} variant="outlined" elevation={0}>
<Card variant="outlined">
<List dense={true}>
<ListItem>
<Typography variant="body1" style={{ fontWeight: 'bold' }}>
<FormattedMessage id="info.basic-info" defaultMessage="Basic Info" />
<FormattedMessage
id="info.basic-info"
defaultMessage="Basic Info"
/>
</Typography>
</ListItem>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage id="info.name" defaultMessage="Name" />:
</Typography>
<Typography variant="body2">
{map?.title}
</Typography>
<Typography variant="body2">{map?.title}</Typography>
</ListItem>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<FormattedMessage id="info.description" defaultMessage="Description" />:
</Typography>
<Typography variant="body2">
{map?.description}
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage
id="info.description"
defaultMessage="Description"
/>
:
</Typography>
<Typography variant="body2">{map?.description}</Typography>
</ListItem>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage id="info.creator" defaultMessage="Creator" />:
</Typography>
</Typography>
<Typography variant="body2">{map?.createdBy}</Typography>
</ListItem>
<ListItem>
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage
id="info.creation-time"
defaultMessage="Creation Date"
/>
:
</Typography>
<Typography variant="body2">
{map?.createdBy}
{dayjs(map?.creationTime).format('lll')}
</Typography>
</ListItem>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<FormattedMessage id="info.creation-time" defaultMessage="Creation Date" />:
</Typography>
<Typography variant="body2">
{dayjs(map?.creationTime).format("lll")}
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage
id="info.modified-tny"
defaultMessage="Last Modified By"
/>
:
</Typography>
<Typography variant="body2">{map?.lastModificationBy}</Typography>
</ListItem>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<FormattedMessage id="info.modified-tny" defaultMessage="Last Modified By" />:
</Typography>
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage
id="info.modified-time"
defaultMessage="Last Modified Date"
/>
:
</Typography>
<Typography variant="body2">
{map?.lastModificationBy}
{dayjs(map?.lastModificationTime).format('lll')}
</Typography>
</ListItem>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<FormattedMessage id="info.modified-time" defaultMessage="Last Modified Date" />:
</Typography>
<Typography variant="body2">
{dayjs(map?.lastModificationTime).format("lll")}
</Typography>
</ListItem>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage id="info.starred" defaultMessage="Starred" />:
</Typography>
</Typography>
<Typography variant="body2">
{Boolean(map?.starred).toString()}
</Typography>
@ -106,7 +150,7 @@ const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement =
</List>
</Card>
<Card variant="outlined" style={{ marginTop: "10px" }}>
<Card variant="outlined" style={{ marginTop: '10px' }}>
<List dense={true}>
<ListItem>
<Typography variant="body1" style={{ fontWeight: 'bold' }}>
@ -115,18 +159,23 @@ const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement =
</ListItem>
</List>
<ListItem>
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
<FormattedMessage id="info.public-visibility" defaultMessage="Publicly Visible" />:
</Typography>
<Typography variant="body2">
{Boolean(map?.isPublic).toString()}
<Typography
variant="caption"
color="textPrimary"
className={classes.textDesc}
>
<FormattedMessage
id="info.public-visibility"
defaultMessage="Publicly Visible"
/>
:
</Typography>
<Typography variant="body2">{Boolean(map?.isPublic).toString()}</Typography>
</ListItem>
</Card>
</Paper>
</BaseDialog>
)
}
export default InfoDialog;
export default InfoDialog

View File

@ -1,15 +1,15 @@
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import createStyles from '@material-ui/core/styles/createStyles'
import makeStyles from '@material-ui/core/styles/makeStyles'
export const useStyles = makeStyles(() =>
createStyles({
textarea: {
width: '100%',
padding: '15px 15px',
marging: '0px 10px'
marging: '0px 10px',
},
textDesc: {
width: '150px'
}
}),
);
width: '150px',
},
})
)

View File

@ -1,76 +1,86 @@
import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { useMutation, useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import Client, { ErrorInfo } from '../../../../classes/client';
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import BaseDialog from '../base-dialog';
import { handleOnMutationSuccess, SimpleDialogProps } from '..';
import { useStyles } from './style';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Checkbox from '@material-ui/core/Checkbox';
import TabContext from '@material-ui/lab/TabContext';
import AppBar from '@material-ui/core/AppBar';
import TabList from '@material-ui/lab/TabList';
import Tab from '@material-ui/core/Tab';
import TabPanel from '@material-ui/lab/TabPanel';
import Typography from '@material-ui/core/Typography';
import TextareaAutosize from '@material-ui/core/TextareaAutosize';
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { useMutation, useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
import Client, { ErrorInfo } from '../../../../classes/client'
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'
import BaseDialog from '../base-dialog'
import { handleOnMutationSuccess, SimpleDialogProps } from '..'
import { useStyles } from './style'
import FormControl from '@material-ui/core/FormControl'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import Checkbox from '@material-ui/core/Checkbox'
import TabContext from '@material-ui/lab/TabContext'
import AppBar from '@material-ui/core/AppBar'
import TabList from '@material-ui/lab/TabList'
import Tab from '@material-ui/core/Tab'
import TabPanel from '@material-ui/lab/TabPanel'
import Typography from '@material-ui/core/Typography'
import TextareaAutosize from '@material-ui/core/TextareaAutosize'
const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const { map } = fetchMapById(mapId);
const { map } = fetchMapById(mapId)
const client: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false);
const [error, setError] = React.useState<ErrorInfo>();
const [activeTab, setActiveTab] = React.useState('1');
const queryClient = useQueryClient();
const intl = useIntl();
const client: Client = useSelector(activeInstance)
const [model, setModel] = React.useState<boolean>(map ? map.isPublic : false)
const [error, setError] = React.useState<ErrorInfo>()
const [activeTab, setActiveTab] = React.useState('1')
const queryClient = useQueryClient()
const intl = useIntl()
const classes = useStyles();
const mutation = useMutation<void, ErrorInfo, boolean>((model: boolean) => {
return client.updateMapToPublic(mapId, model);
},
const classes = useStyles()
const mutation = useMutation<void, ErrorInfo, boolean>(
(model: boolean) => {
return client.updateMapToPublic(mapId, model)
},
{
onSuccess: () => {
setModel(model);
handleOnMutationSuccess(onClose, queryClient);
setModel(model)
handleOnMutationSuccess(onClose, queryClient)
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const handleOnClose = (): void => {
onClose();
setError(undefined);
};
onClose()
setError(undefined)
}
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
event.preventDefault()
mutation.mutate(model)
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>, checked: boolean): void => {
event.preventDefault();
setModel(checked);
event.preventDefault()
setModel(checked)
}
const handleTabChange = (event, newValue) => {
setActiveTab(newValue);
};
setActiveTab(newValue)
}
return (
<div>
<BaseDialog onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({ id: 'publish.title', defaultMessage: 'Publish' })}
description={intl.formatMessage({ id: 'publish.description', defaultMessage: 'By publishing the map you make it visible to everyone on the Internet.' })}
submitButton={intl.formatMessage({ id: 'publish.button', defaultMessage: 'Accept' })}>
description={intl.formatMessage({
id: 'publish.description',
defaultMessage:
'By publishing the map you make it visible to everyone on the Internet.',
})}
submitButton={intl.formatMessage({
id: 'publish.button',
defaultMessage: 'Accept',
})}
>
<FormControl fullWidth={true}>
<FormControlLabel
control={
@ -81,7 +91,10 @@ const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElemen
color="primary"
/>
}
label={intl.formatMessage({ id: 'publish.checkbox', defaultMessage: 'Enable public sharing' })}
label={intl.formatMessage({
id: 'publish.checkbox',
defaultMessage: 'Enable public sharing',
})}
/>
</FormControl>
@ -89,29 +102,57 @@ const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElemen
<TabContext value={activeTab}>
<AppBar position="static">
<TabList onChange={handleTabChange}>
<Tab label={intl.formatMessage({ id: 'publish.embedded', defaultMessage: 'Embedded' })} value="1" />
<Tab label={intl.formatMessage({ id: 'publish.public-url', defaultMessage: 'Public URL' })} value="2" />
<Tab
label={intl.formatMessage({
id: 'publish.embedded',
defaultMessage: 'Embedded',
})}
value="1"
/>
<Tab
label={intl.formatMessage({
id: 'publish.public-url',
defaultMessage: 'Public URL',
})}
value="2"
/>
</TabList>
</AppBar>
<TabPanel value="1">
<Typography variant="subtitle2">
<FormattedMessage id="publish.embedded-msg" defaultMessage="Copy this snippet of code to embed in your blog or page:" />
<FormattedMessage
id="publish.embedded-msg"
defaultMessage="Copy this snippet of code to embed in your blog or page:"
/>
</Typography>
<TextareaAutosize className={classes.textarea} readOnly={true} spellCheck={false} rowsMax={6} defaultValue={`<iframe style="width:600px;height:400px;border:1px solid black" src="https://app.wisemapping.com/c/maps/${mapId}/embed?zoom=1.0"></iframe>`} />
<TextareaAutosize
className={classes.textarea}
readOnly={true}
spellCheck={false}
rowsMax={6}
defaultValue={`<iframe style="width:600px;height:400px;border:1px solid black" src="https://app.wisemapping.com/c/maps/${mapId}/embed?zoom=1.0"></iframe>`}
/>
</TabPanel>
<TabPanel value="2">
<Typography variant="subtitle2">
<FormattedMessage id="publish.public-url-msg" defaultMessage="Copy and paste the link below to share your map with colleagues:" />
<FormattedMessage
id="publish.public-url-msg"
defaultMessage="Copy and paste the link below to share your map with colleagues:"
/>
</Typography>
<TextareaAutosize className={classes.textarea} readOnly={true} spellCheck={false} rowsMax={1} defaultValue={`https://app.wisemapping.com/c/maps/${mapId}/public`} />
<TextareaAutosize
className={classes.textarea}
readOnly={true}
spellCheck={false}
rowsMax={1}
defaultValue={`https://app.wisemapping.com/c/maps/${mapId}/public`}
/>
</TabPanel>
</TabContext>
</div>
</BaseDialog>
</div>
);
)
}
export default PublishDialog;
export default PublishDialog

View File

@ -1,12 +1,12 @@
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import createStyles from '@material-ui/core/styles/createStyles'
import makeStyles from '@material-ui/core/styles/makeStyles'
export const useStyles = makeStyles(() =>
createStyles({
textarea: {
width: '100%',
padding: '15px 15px',
marging: '0px 10px'
}
}),
);
marging: '0px 10px',
},
})
)

View File

@ -1,89 +1,116 @@
import React, { useEffect } from "react";
import { useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import { useSelector } from "react-redux";
import Client, { BasicMapInfo, ErrorInfo } from "../../../../classes/client";
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice';
import { SimpleDialogProps, handleOnMutationSuccess } from "..";
import Input from "../../../form/input";
import BaseDialog from "../base-dialog";
import FormControl from "@material-ui/core/FormControl";
import React, { useEffect } from 'react'
import { useIntl } from 'react-intl'
import { useMutation, useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
import Client, { BasicMapInfo, ErrorInfo } from '../../../../classes/client'
import { activeInstance, fetchMapById } from '../../../../redux/clientSlice'
import { SimpleDialogProps, handleOnMutationSuccess } from '..'
import Input from '../../../form/input'
import BaseDialog from '../base-dialog'
import FormControl from '@material-ui/core/FormControl'
export type RenameModel = {
id: number;
title: string;
description?: string;
id: number
title: 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 service: Client = useSelector(activeInstance);
const [model, setModel] = React.useState<RenameModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const service: Client = useSelector(activeInstance)
const [model, setModel] = React.useState<RenameModel>(defaultModel)
const [error, setError] = React.useState<ErrorInfo>()
const intl = useIntl();
const queryClient = useQueryClient();
const intl = useIntl()
const queryClient = useQueryClient()
const mutation = useMutation<RenameModel, ErrorInfo, RenameModel>((model: RenameModel) => {
const { id, ...rest } = model;
return service.renameMap(id, rest).then(() => model);
},
const mutation = useMutation<RenameModel, ErrorInfo, RenameModel>(
(model: RenameModel) => {
const { id, ...rest } = model
return service.renameMap(id, rest).then(() => model)
},
{
onSuccess: () => {
handleOnMutationSuccess(onClose, queryClient);
handleOnMutationSuccess(onClose, queryClient)
},
onError: (error) => {
setError(error);
}
setError(error)
},
}
);
)
const handleOnClose = (): void => {
onClose();
setModel(defaultModel);
setError(undefined);
};
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof BasicMapInfo]: value });
onClose()
setModel(defaultModel)
setError(undefined)
}
const { map } = fetchMapById(mapId);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault()
mutation.mutate(model)
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault()
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof BasicMapInfo]: value })
}
const { map } = fetchMapById(mapId)
useEffect(() => {
if (open && map) {
setModel(map);
setModel(map)
} else {
setModel(defaultModel);
setError(undefined);
setModel(defaultModel)
setError(undefined)
}
}, [mapId])
return (
<div>
<BaseDialog onClose={handleOnClose} onSubmit={handleOnSubmit} error={error}
<BaseDialog
onClose={handleOnClose}
onSubmit={handleOnSubmit}
error={error}
title={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })}
description={intl.formatMessage({ id: 'rename.description', defaultMessage: 'Please, fill the new map name and description.' })}
submitButton={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })}>
description={intl.formatMessage({
id: 'rename.description',
defaultMessage: 'Please, fill the new map name and description.',
})}
submitButton={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })}
>
<FormControl fullWidth={true}>
<Input name="title" type="text" label={intl.formatMessage({ id: "action.rename-name-placeholder", defaultMessage: "Name" })}
value={model.title} onChange={handleOnChange} error={error} fullWidth={true} />
<Input
name="title"
type="text"
label={intl.formatMessage({
id: 'action.rename-name-placeholder',
defaultMessage: 'Name',
})}
value={model.title}
onChange={handleOnChange}
error={error}
fullWidth={true}
/>
<Input name="description" type="text" label={intl.formatMessage({ id: "action.rename-description-placeholder", defaultMessage: "Description" })}
value={model.description} onChange={handleOnChange} required={false} fullWidth={true} />
<Input
name="description"
type="text"
label={intl.formatMessage({
id: 'action.rename-description-placeholder',
defaultMessage: 'Description',
})}
value={model.description}
onChange={handleOnChange}
required={false}
fullWidth={true}
/>
</FormControl>
</BaseDialog>
</div>
);
)
}
export default RenameDialog;
export default RenameDialog

View File

@ -1,204 +1,255 @@
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { useSelector } from "react-redux";
import Client, { ErrorInfo, Permission } from "../../../../classes/client";
import { activeInstance } from '../../../../redux/clientSlice';
import { SimpleDialogProps } from "..";
import BaseDialog from "../base-dialog";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import IconButton from "@material-ui/core/IconButton";
import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
import DeleteIcon from '@material-ui/icons/Delete';
import Paper from "@material-ui/core/Paper";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Button from "@material-ui/core/Button";
import TextField from "@material-ui/core/TextField";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
import Typography from "@material-ui/core/Typography";
import { useStyles } from "./style";
import RoleIcon from "../../role-icon";
import Tooltip from "@material-ui/core/Tooltip";
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { useMutation, useQuery, useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
import Client, { ErrorInfo, Permission } from '../../../../classes/client'
import { activeInstance } from '../../../../redux/clientSlice'
import { SimpleDialogProps } from '..'
import BaseDialog from '../base-dialog'
import List from '@material-ui/core/List'
import ListItem from '@material-ui/core/ListItem'
import ListItemText from '@material-ui/core/ListItemText'
import IconButton from '@material-ui/core/IconButton'
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import DeleteIcon from '@material-ui/icons/Delete'
import Paper from '@material-ui/core/Paper'
import Select from '@material-ui/core/Select'
import MenuItem from '@material-ui/core/MenuItem'
import Button from '@material-ui/core/Button'
import TextField from '@material-ui/core/TextField'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import Checkbox from '@material-ui/core/Checkbox'
import Typography from '@material-ui/core/Typography'
import { useStyles } from './style'
import RoleIcon from '../../role-icon'
import Tooltip from '@material-ui/core/Tooltip'
type ShareModel = {
emails: string,
role: 'editor' | 'viewer',
message: string
emails: string
role: 'editor' | 'viewer'
message: string
}
const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' };
const defaultModel: ShareModel = { emails: '', role: 'editor', message: '' }
const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement => {
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const classes = useStyles();
const [showMessage, setShowMessage] = React.useState<boolean>(false);
const [model, setModel] = React.useState<ShareModel>(defaultModel);
const [error, setError] = React.useState<ErrorInfo>();
const intl = useIntl()
const client: Client = useSelector(activeInstance)
const queryClient = useQueryClient()
const classes = useStyles()
const [showMessage, setShowMessage] = React.useState<boolean>(false)
const [model, setModel] = React.useState<ShareModel>(defaultModel)
const [error, setError] = React.useState<ErrorInfo>()
const deleteMutation = useMutation(
(email: string) => {
return client.deleteMapPermission(mapId, email);
},
{
onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
}
}
);
const addMutation = useMutation(
(model: ShareModel) => {
const emails = model.emails.split("'");
const permissions = emails.map((email) => { return { email: email, role: model.role } });
return client.addMapPermissions(mapId, model.message, permissions);
},
{
onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`);
setModel(defaultModel);
},
onError: (error: ErrorInfo) => {
setError(error);
}
}
);
const handleOnClose = (): void => {
onClose();
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof ShareModel]: value });
}
const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.stopPropagation();
addMutation.mutate(model);
};
const handleOnDeleteClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, email: string): void => {
event.stopPropagation();
deleteMutation.mutate(email);
};
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(`perm-${mapId}`, () => {
return client.fetchMapPermissions(mapId);
});
const formatName = (perm: Permission): string => {
return perm.name ? `${perm.name}<${perm.email}>` : perm.email;
}
return (
<div>
<BaseDialog
onClose={handleOnClose}
title={intl.formatMessage({ id: "share.delete-title", defaultMessage: "Share with people" })}
description={intl.formatMessage({ id: "share.delete-description", defaultMessage: "Invite people to collaborate with you in the creation of your midnmap. They will be notified by email. " })}
PaperProps={{ classes: { root: classes.paper } }}
error={error}
>
<div className={classes.actionContainer}>
<TextField
id="emails"
name="emails"
required={true}
style={{ width: '300px' }}
size="small"
type="email"
variant="outlined"
placeholder="Add collaborator email"
label="Emails"
onChange={handleOnChange}
value={model.emails}
/>
<Select
variant="outlined"
onChange={handleOnChange}
value={model.role}
name="role"
style={{ margin: '0px 10px' }}
>
<MenuItem value='editor'><FormattedMessage id="share.can-edit" defaultMessage="Can edit" /></MenuItem>
<MenuItem value='viewer'><FormattedMessage id="share.can-view" defaultMessage="Can view" /></MenuItem>
</Select>
<FormControlLabel
value="start"
onChange={(event, value) => { setShowMessage(value) }}
style={{ fontSize: "5px" }}
control={<Checkbox color="primary" />}
label={<Typography variant="subtitle2"><FormattedMessage id="share.add-message" defaultMessage="Add message" /></Typography>}
labelPlacement="end"
/>
<Button
color="primary"
type="button"
variant="contained"
disableElevation={true}
onClick={handleOnAddClick}>
<FormattedMessage id="share.add-button" defaultMessage="Add" />
</Button>
{showMessage &&
<TextField
multiline
rows={3}
rowsMax={3}
className={classes.textArea}
variant="filled"
name="message"
onChange={handleOnChange}
value={model.message}
label={intl.formatMessage({ id: 'share.message', defaultMessage: 'Message' })}
/>
}
</div>
{!isLoading &&
<Paper elevation={1} className={classes.listPaper} variant="outlined">
<List>
{permissions && permissions.map((permission) => {
return (
<ListItem key={permission.email} role={undefined} dense button>
<ListItemText id={permission.email} primary={formatName(permission)} />
<RoleIcon role={permission.role} />
< ListItemSecondaryAction >
<Tooltip title={<FormattedMessage id="share.delete" defaultMessage="Delete collaborator" />}>
<IconButton edge="end" disabled={permission.role == 'owner'} onClick={e => handleOnDeleteClick(e, permission.email)}>
<DeleteIcon />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
);
})}
</List>
</Paper>
const deleteMutation = useMutation(
(email: string) => {
return client.deleteMapPermission(mapId, email)
},
{
onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`)
setModel(defaultModel)
},
onError: (error: ErrorInfo) => {
setError(error)
},
}
)
</BaseDialog>
</div >
);
const addMutation = useMutation(
(model: ShareModel) => {
const emails = model.emails.split("'")
const permissions = emails.map((email) => {
return { email: email, role: model.role }
})
return client.addMapPermissions(mapId, model.message, permissions)
},
{
onSuccess: () => {
queryClient.invalidateQueries(`perm-${mapId}`)
setModel(defaultModel)
},
onError: (error: ErrorInfo) => {
setError(error)
},
}
)
const handleOnClose = (): void => {
onClose()
}
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault()
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof ShareModel]: value })
}
const handleOnAddClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
event.stopPropagation()
addMutation.mutate(model)
}
const handleOnDeleteClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
email: string
): void => {
event.stopPropagation()
deleteMutation.mutate(email)
}
const { isLoading, data: permissions = [] } = useQuery<unknown, ErrorInfo, Permission[]>(
`perm-${mapId}`,
() => {
return client.fetchMapPermissions(mapId)
}
)
const formatName = (perm: Permission): string => {
return perm.name ? `${perm.name}<${perm.email}>` : perm.email
}
return (
<div>
<BaseDialog
onClose={handleOnClose}
title={intl.formatMessage({
id: 'share.delete-title',
defaultMessage: 'Share with people',
})}
description={intl.formatMessage({
id: 'share.delete-description',
defaultMessage:
'Invite people to collaborate with you in the creation of your midnmap. They will be notified by email. ',
})}
PaperProps={{ classes: { root: classes.paper } }}
error={error}
>
<div className={classes.actionContainer}>
<TextField
id="emails"
name="emails"
required={true}
style={{ width: '300px' }}
size="small"
type="email"
variant="outlined"
placeholder="Add collaborator email"
label="Emails"
onChange={handleOnChange}
value={model.emails}
/>
<Select
variant="outlined"
onChange={handleOnChange}
value={model.role}
name="role"
style={{ margin: '0px 10px' }}
>
<MenuItem value="editor">
<FormattedMessage id="share.can-edit" defaultMessage="Can edit" />
</MenuItem>
<MenuItem value="viewer">
<FormattedMessage id="share.can-view" defaultMessage="Can view" />
</MenuItem>
</Select>
<FormControlLabel
value="start"
onChange={(event, value) => {
setShowMessage(value)
}}
style={{ fontSize: '5px' }}
control={<Checkbox color="primary" />}
label={
<Typography variant="subtitle2">
<FormattedMessage
id="share.add-message"
defaultMessage="Add message"
/>
</Typography>
}
labelPlacement="end"
/>
<Button
color="primary"
type="button"
variant="contained"
disableElevation={true}
onClick={handleOnAddClick}
>
<FormattedMessage id="share.add-button" defaultMessage="Add" />
</Button>
{showMessage && (
<TextField
multiline
rows={3}
rowsMax={3}
className={classes.textArea}
variant="filled"
name="message"
onChange={handleOnChange}
value={model.message}
label={intl.formatMessage({
id: 'share.message',
defaultMessage: 'Message',
})}
/>
)}
</div>
{!isLoading && (
<Paper elevation={1} className={classes.listPaper} variant="outlined">
<List>
{permissions &&
permissions.map((permission) => {
return (
<ListItem
key={permission.email}
role={undefined}
dense
button
>
<ListItemText
id={permission.email}
primary={formatName(permission)}
/>
<RoleIcon role={permission.role} />
<ListItemSecondaryAction>
<Tooltip
title={
<FormattedMessage
id="share.delete"
defaultMessage="Delete collaborator"
/>
}
>
<IconButton
edge="end"
disabled={permission.role == 'owner'}
onClick={(e) =>
handleOnDeleteClick(e, permission.email)
}
>
<DeleteIcon />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
)
})}
</List>
</Paper>
)}
</BaseDialog>
</div>
)
}
export default ShareDialog;
export default ShareDialog

View File

@ -1,5 +1,5 @@
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import createStyles from '@material-ui/core/styles/createStyles'
import makeStyles from '@material-ui/core/styles/makeStyles'
export const useStyles = makeStyles(() =>
createStyles({
@ -7,21 +7,20 @@ export const useStyles = makeStyles(() =>
padding: '10px 0px',
border: '1px solid rgba(0, 0, 0, 0.12)',
borderRadius: '8px 8px 0px 0px',
textAlign: "center"
textAlign: 'center',
},
textArea:
{
textArea: {
width: '730px',
margin: '5px 0px',
padding: '10px'
padding: '10px',
},
listPaper: {
maxHeight: 200,
overflowY: 'scroll',
},
paper: {
width: "850px",
minWidth: "850px"
}
}),
);
width: '850px',
minWidth: '850px',
},
})
)

View File

@ -1,42 +1,43 @@
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import Help from "@material-ui/icons/Help";
import PolicyOutlined from "@material-ui/icons/PolicyOutlined";
import FeedbackOutlined from "@material-ui/icons/FeedbackOutlined";
import EmojiPeopleOutlined from "@material-ui/icons/EmailOutlined";
import EmailOutlined from "@material-ui/icons/EmailOutlined";
import IconButton from "@material-ui/core/IconButton";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import Link from "@material-ui/core/Link";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import Tooltip from "@material-ui/core/Tooltip";
import Help from '@material-ui/icons/Help'
import PolicyOutlined from '@material-ui/icons/PolicyOutlined'
import FeedbackOutlined from '@material-ui/icons/FeedbackOutlined'
import EmojiPeopleOutlined from '@material-ui/icons/EmailOutlined'
import EmailOutlined from '@material-ui/icons/EmailOutlined'
import IconButton from '@material-ui/core/IconButton'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import Link from '@material-ui/core/Link'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import Tooltip from '@material-ui/core/Tooltip'
const HelpMenu = (): React.ReactElement => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const intl = useIntl();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const open = Boolean(anchorEl)
const intl = useIntl()
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null);
};
setAnchorEl(null)
}
return (
<span>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'help.support', defaultMessage: 'Support' })}>
<IconButton
aria-haspopup="true"
onClick={handleMenu}>
<Tooltip
arrow={true}
title={intl.formatMessage({ id: 'help.support', defaultMessage: 'Support' })}
>
<IconButton aria-haspopup="true" onClick={handleMenu}>
<Help />
</IconButton>
</Tooltip>
<Menu id="appbar-profile"
<Menu
id="appbar-profile"
anchorEl={anchorEl}
keepMounted
open={open}
@ -49,14 +50,21 @@ const HelpMenu = (): React.ReactElement => {
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}>
}}
>
<MenuItem onClick={handleClose}>
<Link color="textSecondary" href="https://www.wisemapping.com/termsofuse.html" target="help">
<Link
color="textSecondary"
href="https://www.wisemapping.com/termsofuse.html"
target="help"
>
<ListItemIcon>
<PolicyOutlined fontSize="small" />
</ListItemIcon>
<FormattedMessage id="footer.termsandconditions" defaultMessage="Term And Conditions" />
<FormattedMessage
id="footer.termsandconditions"
defaultMessage="Term And Conditions"
/>
</Link>
</MenuItem>
@ -79,7 +87,11 @@ const HelpMenu = (): React.ReactElement => {
</MenuItem>
<MenuItem onClick={handleClose}>
<Link color="textSecondary" href="https://www.wisemapping.com/aboutus.html" target="help">
<Link
color="textSecondary"
href="https://www.wisemapping.com/aboutus.html"
target="help"
>
<ListItemIcon>
<EmojiPeopleOutlined fontSize="small" />
</ListItemIcon>
@ -87,7 +99,8 @@ const HelpMenu = (): React.ReactElement => {
</Link>
</MenuItem>
</Menu>
</span>);
</span>
)
}
export default HelpMenu;
export default HelpMenu

View File

@ -1,137 +1,142 @@
import React, { ErrorInfo, ReactElement, useEffect } from 'react';
import clsx from 'clsx';
import Drawer from '@material-ui/core/Drawer';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import List from '@material-ui/core/List';
import IconButton from '@material-ui/core/IconButton';
import { useStyles } from './style';
import { MapsList } from './maps-list';
import { FormattedMessage, IntlProvider, useIntl } from 'react-intl';
import { useQuery, useMutation, useQueryClient } from 'react-query';
import { activeInstance } from '../../redux/clientSlice';
import { useSelector } from 'react-redux';
import Client, { Label } from '../../classes/client';
import ActionDispatcher from './action-dispatcher';
import { ActionType } from './action-chooser';
import AccountMenu from './account-menu';
import ClientHealthSentinel from '../../classes/client/client-health-sentinel';
import HelpMenu from './help-menu';
import LanguageMenu from './language-menu';
import AppI18n, { Locales } from '../../classes/app-i18n';
import React, { ErrorInfo, ReactElement, useEffect } from 'react'
import clsx from 'clsx'
import Drawer from '@material-ui/core/Drawer'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import List from '@material-ui/core/List'
import IconButton from '@material-ui/core/IconButton'
import { useStyles } from './style'
import { MapsList } from './maps-list'
import { FormattedMessage, IntlProvider, useIntl } from 'react-intl'
import { useQuery, useMutation, useQueryClient } from 'react-query'
import { activeInstance } from '../../redux/clientSlice'
import { useSelector } from 'react-redux'
import Client, { Label } from '../../classes/client'
import ActionDispatcher from './action-dispatcher'
import { ActionType } from './action-chooser'
import AccountMenu from './account-menu'
import ClientHealthSentinel from '../../classes/client/client-health-sentinel'
import HelpMenu from './help-menu'
import LanguageMenu from './language-menu'
import AppI18n, { Locales } from '../../classes/app-i18n'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItem from '@material-ui/core/ListItem'
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItem from '@material-ui/core/ListItem';
import AddCircleTwoTone from '@material-ui/icons/AddCircleTwoTone'
import CloudUploadTwoTone from '@material-ui/icons/CloudUploadTwoTone'
import DeleteOutlineTwoTone from '@material-ui/icons/DeleteOutlineTwoTone'
import LabelTwoTone from '@material-ui/icons/LabelTwoTone'
import PersonOutlineTwoTone from '@material-ui/icons/PersonOutlineTwoTone'
import PublicTwoTone from '@material-ui/icons/PublicTwoTone'
import ScatterPlotTwoTone from '@material-ui/icons/ScatterPlotTwoTone'
import ShareTwoTone from '@material-ui/icons/ShareTwoTone'
import StarTwoTone from '@material-ui/icons/StarTwoTone'
import Tooltip from '@material-ui/core/Tooltip'
import Button from '@material-ui/core/Button'
import Link from '@material-ui/core/Link'
import ListItemText from '@material-ui/core/ListItemText'
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import AddCircleTwoTone from '@material-ui/icons/AddCircleTwoTone';
import CloudUploadTwoTone from '@material-ui/icons/CloudUploadTwoTone';
import DeleteOutlineTwoTone from '@material-ui/icons/DeleteOutlineTwoTone';
import LabelTwoTone from '@material-ui/icons/LabelTwoTone';
import PersonOutlineTwoTone from '@material-ui/icons/PersonOutlineTwoTone';
import PublicTwoTone from '@material-ui/icons/PublicTwoTone';
import ScatterPlotTwoTone from '@material-ui/icons/ScatterPlotTwoTone';
import ShareTwoTone from '@material-ui/icons/ShareTwoTone';
import StarTwoTone from '@material-ui/icons/StarTwoTone';
import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button';
import Link from '@material-ui/core/Link';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import logoIcon from '../../images/logo-small.svg'
import poweredByIcon from '../../images/pwrdby-white.svg'
import logoIcon from '../../images/logo-small.svg';
import poweredByIcon from '../../images/pwrdby-white.svg';
export type Filter = GenericFilter | LabelFilter;
export type Filter = GenericFilter | LabelFilter
export interface GenericFilter {
type: 'public' | 'all' | 'starred' | 'shared' | 'label' | 'owned';
type: 'public' | 'all' | 'starred' | 'shared' | 'label' | 'owned'
}
export interface LabelFilter {
type: 'label',
type: 'label'
label: Label
}
interface ToolbarButtonInfo {
filter: GenericFilter | LabelFilter,
filter: GenericFilter | LabelFilter
label: string
icon: React.ReactElement;
icon: React.ReactElement
}
const MapsPage = (): ReactElement => {
const classes = useStyles();
const [filter, setFilter] = React.useState<Filter>({ type: 'all' });
const client: Client = useSelector(activeInstance);
const queryClient = useQueryClient();
const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined);
const intl = useIntl();
const classes = useStyles()
const [filter, setFilter] = React.useState<Filter>({ type: 'all' })
const client: Client = useSelector(activeInstance)
const queryClient = useQueryClient()
const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined)
const intl = useIntl()
useEffect(() => {
document.title = 'Maps | WiseMapping';
}, []);
document.title = 'Maps | WiseMapping'
}, [])
const mutation = useMutation(
(id: number) => client.deleteLabel(id),
{
onSuccess: () => queryClient.invalidateQueries('labels'),
onError: (error) => {
console.error(`Unexpected error ${error}`);
}
}
);
const mutation = useMutation((id: number) => client.deleteLabel(id), {
onSuccess: () => queryClient.invalidateQueries('labels'),
onError: (error) => {
console.error(`Unexpected error ${error}`)
},
})
const handleMenuClick = (filter: Filter) => {
queryClient.invalidateQueries('maps');
setFilter(filter);
};
queryClient.invalidateQueries('maps')
setFilter(filter)
}
const handleLabelDelete = (id: number) => {
mutation.mutate(id);
};
mutation.mutate(id)
}
const { data } = useQuery<unknown, ErrorInfo, Label[]>('labels', () => {
return client.fetchLabels();
});
return client.fetchLabels()
})
const labels: Label[] = data ? data : [];
const filterButtons: ToolbarButtonInfo[] = [{
filter: { type: 'all' },
label: 'All',
icon: <ScatterPlotTwoTone color="secondary" />
}, {
filter: { type: 'owned' },
label: 'Owned',
icon: <PersonOutlineTwoTone color="secondary" />
}, {
filter: { type: 'starred' },
label: 'Starred',
icon: <StarTwoTone color="secondary" />
}, {
filter: { type: 'shared' },
label: 'Shared with me',
icon: <ShareTwoTone color="secondary" />
}, {
filter: { type: 'public' },
label: 'Public',
icon: <PublicTwoTone color="secondary" />
}];
labels.forEach(l => filterButtons.push({
filter: { type: 'label', label: l },
label: l.title,
icon: <LabelTwoTone style={{ color: l.color ? l.color : 'inherit' }} />
}))
const labels: Label[] = data ? data : []
const filterButtons: ToolbarButtonInfo[] = [
{
filter: { type: 'all' },
label: 'All',
icon: <ScatterPlotTwoTone color="secondary" />,
},
{
filter: { type: 'owned' },
label: 'Owned',
icon: <PersonOutlineTwoTone color="secondary" />,
},
{
filter: { type: 'starred' },
label: 'Starred',
icon: <StarTwoTone color="secondary" />,
},
{
filter: { type: 'shared' },
label: 'Shared with me',
icon: <ShareTwoTone color="secondary" />,
},
{
filter: { type: 'public' },
label: 'Public',
icon: <PublicTwoTone color="secondary" />,
},
]
labels.forEach((l) =>
filterButtons.push({
filter: { type: 'label', label: l },
label: l.title,
icon: <LabelTwoTone style={{ color: l.color ? l.color : 'inherit' }} />,
})
)
// Configure using user settings ...
const appi18n = new AppI18n();
const userLocale = appi18n.getUserLocale();
const appi18n = new AppI18n()
const userLocale = appi18n.getUserLocale()
return (
<IntlProvider locale={userLocale.code} defaultLocale={Locales.EN.code} messages={userLocale.message} >
<IntlProvider
locale={userLocale.code}
defaultLocale={Locales.EN.code}
messages={userLocale.message}
>
<div className={classes.root}>
<ClientHealthSentinel />
<AppBar
@ -139,12 +144,19 @@ const MapsPage = (): ReactElement => {
className={clsx(classes.appBar, {
[classes.appBarShift]: open,
})}
variant='outlined'
elevation={0}>
variant="outlined"
elevation={0}
>
<Toolbar>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'maps.create-tooltip', defaultMessage: 'Create a New Map' })}>
<Button color="primary"
<Tooltip
arrow={true}
title={intl.formatMessage({
id: 'maps.create-tooltip',
defaultMessage: 'Create a New Map',
})}
>
<Button
color="primary"
data-testid="create"
size="medium"
variant="contained"
@ -152,12 +164,19 @@ const MapsPage = (): ReactElement => {
disableElevation={true}
startIcon={<AddCircleTwoTone />}
className={classes.newMapButton}
onClick={() => setActiveDialog('create')}>
onClick={() => setActiveDialog('create')}
>
<FormattedMessage id="action.new" defaultMessage="New Map" />
</Button>
</Tooltip>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'maps.import-desc', defaultMessage: 'Import from other tools' })}>
<Tooltip
arrow={true}
title={intl.formatMessage({
id: 'maps.import-desc',
defaultMessage: 'Import from other tools',
})}
>
<Button
color="primary"
size="medium"
@ -166,11 +185,16 @@ const MapsPage = (): ReactElement => {
disableElevation={true}
startIcon={<CloudUploadTwoTone />}
className={classes.importButton}
onClick={() => setActiveDialog('import')}>
onClick={() => setActiveDialog('import')}
>
<FormattedMessage id="action.import" defaultMessage="Import" />
</Button>
</Tooltip>
<ActionDispatcher action={activeDialog} onClose={() => setActiveDialog(undefined)} mapsId={[]} />
<ActionDispatcher
action={activeDialog}
onClose={() => setActiveDialog(undefined)}
mapsId={[]}
/>
<div className={classes.rightButtonGroup}>
<LanguageMenu />
@ -182,34 +206,40 @@ const MapsPage = (): ReactElement => {
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open
[classes.drawerOpen]: open,
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open
[classes.drawerOpen]: open,
}),
}}>
<div style={{ padding: "20px 0 20px 15px" }} key="logo">
}}
>
<div style={{ padding: '20px 0 20px 15px' }} key="logo">
<img src={logoIcon} alt="logo" />
</div>
<List component="nav">
{filterButtons.map(buttonInfo => {
return (<StyleListItem
icon={buttonInfo.icon}
label={buttonInfo.label}
filter={buttonInfo.filter}
active={filter}
onClick={handleMenuClick}
onDelete={handleLabelDelete}
key={`${buttonInfo.filter.type}:${(buttonInfo.filter as LabelFilter).label}`}
/>)
}
)}
{filterButtons.map((buttonInfo) => {
return (
<StyleListItem
icon={buttonInfo.icon}
label={buttonInfo.label}
filter={buttonInfo.filter}
active={filter}
onClick={handleMenuClick}
onDelete={handleLabelDelete}
key={`${buttonInfo.filter.type}:${
(buttonInfo.filter as LabelFilter).label
}`}
/>
)
})}
</List>
<div style={{ position: 'absolute', bottom: '10px', left: '20px' }} key="power-by">
<div
style={{ position: 'absolute', bottom: '10px', left: '20px' }}
key="power-by"
>
<Link href="http://www.wisemapping.org/">
<img src={poweredByIcon} alt="Powered By WiseMapping" />
</Link>
@ -221,60 +251,64 @@ const MapsPage = (): ReactElement => {
</main>
</div>
</IntlProvider>
);
)
}
interface ListItemProps {
icon: React.ReactElement,
label: string,
filter: Filter,
icon: React.ReactElement
label: string
filter: Filter
active?: Filter
onClick: (filter: Filter) => void;
onDelete?: (id: number) => void;
onClick: (filter: Filter) => void
onDelete?: (id: number) => void
}
const StyleListItem = (props: ListItemProps) => {
const icon = props.icon;
const label = props.label;
const filter = props.filter;
const activeFilter = props.active;
const onClick = props.onClick;
const onDeleteLabel = props.onDelete;
const isSelected = activeFilter
&& (activeFilter.type == filter.type)
&& (activeFilter.type != 'label' || ((activeFilter as LabelFilter).label == (filter as LabelFilter).label));
const icon = props.icon
const label = props.label
const filter = props.filter
const activeFilter = props.active
const onClick = props.onClick
const onDeleteLabel = props.onDelete
const isSelected =
activeFilter &&
activeFilter.type == filter.type &&
(activeFilter.type != 'label' ||
(activeFilter as LabelFilter).label == (filter as LabelFilter).label)
const handleOnClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>, filter: Filter) => {
event.stopPropagation();
onClick(filter);
event.stopPropagation()
onClick(filter)
}
const handleOnDelete = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, filter: Filter) => {
event.stopPropagation();
const handleOnDelete = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
filter: Filter
) => {
event.stopPropagation()
if (!onDeleteLabel) {
throw "Illegal state exeption";
throw 'Illegal state exeption'
}
onDeleteLabel((filter as LabelFilter).label.id);
onDeleteLabel((filter as LabelFilter).label.id)
}
return (
<ListItem button
selected={isSelected}
onClick={e => handleOnClick(e, filter)}>
<ListItemIcon>
{icon}
</ListItemIcon>
<ListItem button selected={isSelected} onClick={(e) => handleOnClick(e, filter)}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText style={{ color: 'white' }} primary={label} />
{filter.type == 'label' &&
{filter.type == 'label' && (
<ListItemSecondaryAction>
<IconButton edge="end" aria-label="delete" onClick={e => handleOnDelete(e, filter)}>
<IconButton
edge="end"
aria-label="delete"
onClick={(e) => handleOnDelete(e, filter)}
>
<DeleteOutlineTwoTone color="secondary" />
</IconButton>
</ListItemSecondaryAction>
}
)}
</ListItem>
);
)
}
export default MapsPage;
export default MapsPage

View File

@ -1,61 +1,64 @@
import TranslateTwoTone from '@material-ui/icons/TranslateTwoTone';
import React from "react";
import { useMutation, useQueryClient } from "react-query";
import Client from "../../../classes/client";
import { useSelector } from 'react-redux';
import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
import { FormattedMessage, useIntl } from 'react-intl';
import { LocaleCode, Locales } from '../../../classes/app-i18n';
import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions';
import Divider from '@material-ui/core/Divider';
import TranslateTwoTone from '@material-ui/icons/TranslateTwoTone'
import React from 'react'
import { useMutation, useQueryClient } from 'react-query'
import Client from '../../../classes/client'
import { useSelector } from 'react-redux'
import { activeInstance, fetchAccount } from '../../../redux/clientSlice'
import { FormattedMessage, useIntl } from 'react-intl'
import { LocaleCode, Locales } from '../../../classes/app-i18n'
import Tooltip from '@material-ui/core/Tooltip'
import Button from '@material-ui/core/Button'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import Dialog from '@material-ui/core/Dialog'
import DialogTitle from '@material-ui/core/DialogTitle'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogActions from '@material-ui/core/DialogActions'
import Divider from '@material-ui/core/Divider'
const LanguageMenu = (): React.ReactElement => {
const queryClient = useQueryClient();
const client: Client = useSelector(activeInstance);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false);
const queryClient = useQueryClient()
const client: Client = useSelector(activeInstance)
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
const [openHelpDialog, setHelpDialogOpen] = React.useState<boolean>(false)
const open = Boolean(anchorEl);
const intl = useIntl();
const open = Boolean(anchorEl)
const intl = useIntl()
const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale),
{
onSuccess: () => {
queryClient.invalidateQueries('account')
handleClose();
},
onError: (error) => {
console.error(`Unexpected error ${error}`)
}
}
);
const mutation = useMutation((locale: LocaleCode) => client.updateAccountLanguage(locale), {
onSuccess: () => {
queryClient.invalidateQueries('account')
handleClose()
},
onError: (error) => {
console.error(`Unexpected error ${error}`)
},
})
const handleMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleOnClick = (event: React.MouseEvent<HTMLElement>) => {
const localeCode = event.target['id'];
mutation.mutate(localeCode);
setAnchorEl(event.currentTarget)
}
const accountInfo = fetchAccount();
const handleClose = () => {
setAnchorEl(null)
}
const handleOnClick = (event: React.MouseEvent<HTMLElement>) => {
const localeCode = event.target['id']
mutation.mutate(localeCode)
}
const accountInfo = fetchAccount()
return (
<span>
<Tooltip arrow={true} title={intl.formatMessage({ id: 'language.change', defaultMessage: 'Change Language' })}>
<Tooltip
arrow={true}
title={intl.formatMessage({
id: 'language.change',
defaultMessage: 'Change Language',
})}
>
<Button
size="small"
variant="outlined"
@ -68,7 +71,8 @@ const LanguageMenu = (): React.ReactElement => {
{accountInfo?.locale?.label}
</Button>
</Tooltip>
<Menu id="appbar-language"
<Menu
id="appbar-language"
anchorEl={anchorEl}
keepMounted
open={open}
@ -100,38 +104,40 @@ const LanguageMenu = (): React.ReactElement => {
</MenuItem>
<Divider />
<MenuItem onClick={() => { handleClose(); setHelpDialogOpen(true) }} >
<MenuItem
onClick={() => {
handleClose()
setHelpDialogOpen(true)
}}
>
<FormattedMessage id="language.help" defaultMessage="Help to Translate" />
</MenuItem>
</Menu>
{openHelpDialog &&
<HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />
}
</span>);
{openHelpDialog && <HelpUsToTranslateDialog onClose={() => setHelpDialogOpen(false)} />}
</span>
)
}
type HelpUsToTranslateDialogProp = {
onClose: () => void
}
const HelpUsToTranslateDialog = ({ onClose }: HelpUsToTranslateDialogProp) => {
return (
<Dialog
open={true}
onClose={onClose}
>
<Dialog open={true} onClose={onClose}>
<DialogTitle>Help us to support more languages !</DialogTitle>
<DialogContent>
<DialogContentText>
We need your help !. If you are interested, send us an email to team@wisemapping.com.
We need your help !. If you are interested, send us an email to
team@wisemapping.com.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={onClose}>Close</Button>
<Button autoFocus onClick={onClose}>
Close
</Button>
</DialogActions>
</Dialog>
);
)
}
export default LanguageMenu;
export default LanguageMenu

File diff suppressed because it is too large Load Diff

View File

@ -1,103 +1,103 @@
import { fade } from "@material-ui/core/styles";
import { Theme } from "@material-ui/core/styles/createMuiTheme";
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import { fade } from '@material-ui/core/styles'
import { Theme } from '@material-ui/core/styles/createMuiTheme'
import createStyles from '@material-ui/core/styles/createStyles'
import makeStyles from '@material-ui/core/styles/makeStyles'
export const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: '100%',
},
paper: {
width: '100%',
marginBottom: theme.spacing(2),
},
table: {
minWidth: 750,
'& tr:nth-child(even)': {
background: 'white'
},
'& tr:nth-child(odd)':
{
background: 'rgba(221, 221, 221, 0.35)'
},
// '&:hover tr': {
// backgroundColor: 'rgba(150, 150, 150, 0.7)',
// }
},
headerCell: {
background: 'white',
fontWeight: 'bold',
color: 'rgba(0, 0, 0, 0.44)',
border: 0
},
bodyCell: {
border: '0px'
},
visuallyHidden: {
border: 0,
clip: 'rect(0 0 0 0)',
height: 1,
margin: -1,
overflow: 'hidden',
padding: 0,
position: 'absolute',
top: 20,
width: 1,
},
toolbar: {
display: 'flex',
borderBottom: '1px solid #cccccc',
padding: '0',
marging: '0'
},
toolbarActions: {
flexGrow: 1,
},
toolbarListActions: {
flexGrow: 1
},
search: {
borderRadius: 9,
backgroundColor: fade(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
margin: '10px 0px',
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
float: 'right'
},
searchIcon: {
padding: '6px 0 0 5px',
height: '100%',
position: 'absolute',
pointerEvents: 'none',
alignItems: 'center',
justifyContent: 'center',
},
searchInputRoot: {
color: 'inherit',
}, toolbalLeft: {
float: 'right'
},
searchInputInput: {
// padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
border: '1px solid #ffa800',
borderRadius: 4,
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',
createStyles({
root: {
width: '100%',
},
},
}
})
);
paper: {
width: '100%',
marginBottom: theme.spacing(2),
},
table: {
minWidth: 750,
'& tr:nth-child(even)': {
background: 'white',
},
'& tr:nth-child(odd)': {
background: 'rgba(221, 221, 221, 0.35)',
},
// '&:hover tr': {
// backgroundColor: 'rgba(150, 150, 150, 0.7)',
// }
},
headerCell: {
background: 'white',
fontWeight: 'bold',
color: 'rgba(0, 0, 0, 0.44)',
border: 0,
},
bodyCell: {
border: '0px',
},
visuallyHidden: {
border: 0,
clip: 'rect(0 0 0 0)',
height: 1,
margin: -1,
overflow: 'hidden',
padding: 0,
position: 'absolute',
top: 20,
width: 1,
},
toolbar: {
display: 'flex',
borderBottom: '1px solid #cccccc',
padding: '0',
marging: '0',
},
toolbarActions: {
flexGrow: 1,
},
toolbarListActions: {
flexGrow: 1,
},
search: {
borderRadius: 9,
backgroundColor: fade(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
margin: '10px 0px',
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
float: 'right',
},
searchIcon: {
padding: '6px 0 0 5px',
height: '100%',
position: 'absolute',
pointerEvents: 'none',
alignItems: 'center',
justifyContent: 'center',
},
searchInputRoot: {
color: 'inherit',
},
toolbalLeft: {
float: 'right',
},
searchInputInput: {
// padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
border: '1px solid #ffa800',
borderRadius: 4,
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',
},
},
},
})
)

View File

@ -1,47 +1,48 @@
import React from "react";
import React from 'react'
import Tooltip from "@material-ui/core/Tooltip";
import PersonSharpIcon from '@material-ui/icons/PersonSharp';
import EditSharpIcon from '@material-ui/icons/EditSharp';
import VisibilitySharpIcon from '@material-ui/icons/VisibilitySharp';
import Tooltip from '@material-ui/core/Tooltip'
import PersonSharpIcon from '@material-ui/icons/PersonSharp'
import EditSharpIcon from '@material-ui/icons/EditSharp'
import VisibilitySharpIcon from '@material-ui/icons/VisibilitySharp'
import { FormattedMessage } from "react-intl";
import { Role } from "../../../classes/client";
import { FormattedMessage } from 'react-intl'
import { Role } from '../../../classes/client'
type RoleIconProps = {
role: Role;
}
const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => {
role: Role
}
const RoleIcon = ({ role }: RoleIconProps): React.ReactElement => {
return (
<span>
{role == 'owner' &&
<Tooltip
title={<FormattedMessage id="role.owner" defaultMessage="Onwer" />}
arrow={true}
>
<PersonSharpIcon />
</Tooltip>
}
<span>
{role == 'owner' && (
<Tooltip
title={<FormattedMessage id="role.owner" defaultMessage="Onwer" />}
arrow={true}
>
<PersonSharpIcon />
</Tooltip>
)}
{ role == 'editor' &&
<Tooltip
title={<FormattedMessage id="role.editor" defaultMessage="Editor" />}
arrow={true}>
<EditSharpIcon />
</Tooltip>
}
{role == 'editor' && (
<Tooltip
title={<FormattedMessage id="role.editor" defaultMessage="Editor" />}
arrow={true}
>
<EditSharpIcon />
</Tooltip>
)}
{role == 'viewer' &&
<Tooltip
title={<FormattedMessage id="role.viewer" defaultMessage="Viewer" />}
arrow={true}>
<VisibilitySharpIcon />
</Tooltip>
}
</span>)
};
export default RoleIcon;
{role == 'viewer' && (
<Tooltip
title={<FormattedMessage id="role.viewer" defaultMessage="Viewer" />}
arrow={true}
>
<VisibilitySharpIcon />
</Tooltip>
)}
</span>
)
}
export default RoleIcon

View File

@ -1,8 +1,8 @@
import { Theme } from "@material-ui/core/styles/createMuiTheme";
import createStyles from "@material-ui/core/styles/createStyles";
import makeStyles from "@material-ui/core/styles/makeStyles";
import { Theme } from '@material-ui/core/styles/createMuiTheme'
import createStyles from '@material-ui/core/styles/createStyles'
import makeStyles from '@material-ui/core/styles/makeStyles'
const drawerWidth = 300;
const drawerWidth = 300
export const useStyles = makeStyles((theme: Theme) =>
createStyles({
@ -16,7 +16,6 @@ export const useStyles = makeStyles((theme: Theme) =>
duration: theme.transitions.duration.leavingScreen,
}),
background: '#ffffff',
},
appBarShift: {
marginLeft: drawerWidth,
@ -36,7 +35,7 @@ export const useStyles = makeStyles((theme: Theme) =>
marginRight: 10,
flexGrow: 10,
textAlign: 'right',
minWidth: '200px'
minWidth: '200px',
},
drawer: {
width: drawerWidth,
@ -54,11 +53,11 @@ export const useStyles = makeStyles((theme: Theme) =>
toolbar: {
display: 'flex',
justifyContent: 'flex-end',
minHeight: '44px'
minHeight: '44px',
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
}
}),
);
},
})
)

View File

@ -1,122 +1,167 @@
import React, { useState, useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import ReCAPTCHA from 'react-google-recaptcha';
import { useHistory } from 'react-router-dom';
import Client, { ErrorInfo } from '../../classes/client';
import FormContainer from '../layout/form-container';
import React, { useState, useEffect } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import ReCAPTCHA from 'react-google-recaptcha'
import { useHistory } from 'react-router-dom'
import Client, { ErrorInfo } from '../../classes/client'
import FormContainer from '../layout/form-container'
import Header from '../layout/header';
import Footer from '../layout/footer';
import Header from '../layout/header'
import Footer from '../layout/footer'
import { useSelector } from 'react-redux';
import { useMutation } from 'react-query';
import { activeInstance } from '../../redux/clientSlice';
import Input from '../form/input';
import GlobalError from '../form/global-error';
import SubmitButton from '../form/submit-button';
import Typography from '@material-ui/core/Typography';
import FormControl from '@material-ui/core/FormControl';
import { useSelector } from 'react-redux'
import { useMutation } from 'react-query'
import { activeInstance } from '../../redux/clientSlice'
import Input from '../form/input'
import GlobalError from '../form/global-error'
import SubmitButton from '../form/submit-button'
import Typography from '@material-ui/core/Typography'
import FormControl from '@material-ui/core/FormControl'
export type Model = {
email: string;
lastname: string;
firstname: string;
password: string;
recaptcha: string;
email: string
lastname: string
firstname: string
password: string
recaptcha: string
}
const defaultModel: Model = { email: '', lastname: '', firstname: '', password: '', recaptcha: '' };
const defaultModel: Model = { email: '', lastname: '', firstname: '', password: '', recaptcha: '' }
const RegistrationForm = () => {
const [model, setModel] = useState<Model>(defaultModel);
const [error, setError] = useState<ErrorInfo>();
const history = useHistory();
const intl = useIntl();
const [model, setModel] = useState<Model>(defaultModel)
const [error, setError] = useState<ErrorInfo>()
const history = useHistory()
const intl = useIntl()
const Client: Client = useSelector(activeInstance);
const mutation = useMutation<void, ErrorInfo, Model>(
(model: Model) => Client.registerNewUser({ ...model }),
{
onSuccess: () => history.push("/c/registration-success"),
onError: (error) => {
setError(error);
}
const Client: Client = useSelector(activeInstance)
const mutation = useMutation<void, ErrorInfo, Model>(
(model: Model) => Client.registerNewUser({ ...model }),
{
onSuccess: () => history.push('/c/registration-success'),
onError: (error) => {
setError(error)
},
}
)
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault()
mutation.mutate(model)
}
);
const handleOnSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
event.preventDefault();
mutation.mutate(model);
};
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault()
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
event.preventDefault();
const name = event.target.name
const value = event.target.value
setModel({ ...model, [name as keyof Model]: value })
}
const name = event.target.name;
const value = event.target.value;
setModel({ ...model, [name as keyof Model]: value });
}
return (
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="registration.title" defaultMessage="Become a member" />
</Typography>
<Typography paragraph>
<FormattedMessage
id="registration.desc"
defaultMessage="Signing up is free and just take a moment "
/>
</Typography>
return (
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="registration.title" defaultMessage="Become a member" />
</Typography>
<FormControl>
<form onSubmit={handleOnSubmit}>
<GlobalError error={error} />
<Typography paragraph>
<FormattedMessage id="registration.desc" defaultMessage="Signing up is free and just take a moment " />
</Typography>
<Input
name="email"
type="email"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.email',
defaultMessage: 'Email',
})}
autoComplete="email"
error={error}
/>
<FormControl>
<Input
name="firstname"
type="text"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.firstname',
defaultMessage: 'First Name',
})}
autoComplete="given-name"
error={error}
/>
<form onSubmit={handleOnSubmit}>
<GlobalError error={error} />
<Input
name="lastname"
type="text"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.lastname',
defaultMessage: 'Last Name',
})}
autoComplete="family-name"
error={error}
/>
<Input name="email" type="email" onChange={handleOnChange} label={intl.formatMessage({ id: "registration.email", defaultMessage: "Email" })}
autoComplete="email" error={error} />
<Input
name="password"
type="password"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.password',
defaultMessage: 'Password',
})}
autoComplete="new-password"
error={error}
/>
<Input name="firstname" type="text" onChange={handleOnChange} label={intl.formatMessage({ id: "registration.firstname", defaultMessage: "First Name" })}
autoComplete="given-name" error={error} />
<div style={{ width: '330px', padding: '5px 0px 5px 20px' }}>
<ReCAPTCHA
sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
onChange={(value: string) => {
model.recaptcha = value
setModel(model)
}}
/>
</div>
<Input name="lastname" type="text" onChange={handleOnChange} label={intl.formatMessage({ id: "registration.lastname", defaultMessage: "Last Name" })}
autoComplete="family-name" error={error} />
<div style={{ fontSize: '12px', padding: '10px 0px' }}>
<FormattedMessage
id="registration.termandconditions"
defaultMessage="Terms of Client: Please check the WiseMapping Account information you've entered above, and review the Terms of Client here. By clicking on 'Register' below you are agreeing to the Terms of Client above and the Privacy Policy"
/>
</div>
<Input name="password" type="password" onChange={handleOnChange} label={intl.formatMessage({ id: "registration.password", defaultMessage: "Password" })}
autoComplete="new-password" error={error} />
<div style={{ width: '330px', padding: '5px 0px 5px 20px' }}>
<ReCAPTCHA
sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
onChange={(value: string) => { model.recaptcha = value; setModel(model) }} />
</div>
<div style={{ fontSize: "12px", padding: "10px 0px" }}>
<FormattedMessage id="registration.termandconditions" defaultMessage="Terms of Client: Please check the WiseMapping Account information you've entered above, and review the Terms of Client here. By clicking on 'Register' below you are agreeing to the Terms of Client above and the Privacy Policy" />
</div>
<SubmitButton value={intl.formatMessage({ id: "registration.register", defaultMessage: "Register" })} />
</form>
</FormControl>
</FormContainer>
);
<SubmitButton
value={intl.formatMessage({
id: 'registration.register',
defaultMessage: 'Register',
})}
/>
</form>
</FormControl>
</FormContainer>
)
}
const RegistationPage = (): React.ReactElement => {
useEffect(() => {
document.title = 'Registration | WiseMapping'
})
useEffect(() => {
document.title = 'Registration | WiseMapping';
});
return (
<div>
<Header type='only-signin' />
<RegistrationForm />
<Footer />
</div>
);
return (
<div>
<Header type="only-signin" />
<RegistrationForm />
<Footer />
</div>
)
}
export default RegistationPage;
export default RegistationPage

View File

@ -1,41 +1,49 @@
import React, { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import FormContainer from '../layout/form-container';
import Header from '../layout/header';
import Footer from '../layout/footer';
import { Link as RouterLink } from 'react-router-dom';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import React, { useEffect } from 'react'
import { FormattedMessage } from 'react-intl'
import FormContainer from '../layout/form-container'
import Header from '../layout/header'
import Footer from '../layout/footer'
import { Link as RouterLink } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import Button from '@material-ui/core/Button'
const RegistrationSuccessPage = (): React.ReactElement => {
useEffect(() => {
document.title = 'Reset Password | WiseMapping';
});
useEffect(() => {
document.title = 'Reset Password | WiseMapping'
})
return (
<div>
<Header type='none' />
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="resetpassword.success.title" defaultMessage="Your account has been created successfully" />
</Typography>
return (
<div>
<Header type="none" />
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage
id="resetpassword.success.title"
defaultMessage="Your account has been created successfully"
/>
</Typography>
<Typography paragraph>
<FormattedMessage id="registration.success.desc" defaultMessage="Click 'Sign In' button below and start creating mind maps." />
</Typography>
<Typography paragraph>
<FormattedMessage
id="registration.success.desc"
defaultMessage="Click 'Sign In' button below and start creating mind maps."
/>
</Typography>
<Button color="primary" size="medium" variant="contained" component={RouterLink} to="/c/login" disableElevation={true}>
<FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button>
</FormContainer>
<Footer />
</div>
);
<Button
color="primary"
size="medium"
variant="contained"
component={RouterLink}
to="/c/login"
disableElevation={true}
>
<FormattedMessage id="login.signin" defaultMessage="Sign In" />
</Button>
</FormContainer>
<Footer />
</div>
)
}
export default RegistrationSuccessPage;
export default RegistrationSuccessPage

View File

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

View File

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

View File

@ -1,12 +1,11 @@
import { configureStore } from '@reduxjs/toolkit';
import clientReducer from './clientSlice';
import { configureStore } from '@reduxjs/toolkit'
import clientReducer from './clientSlice'
// Create Service object...
const store = configureStore({
reducer: {
client: clientReducer
}
});
reducer: {
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({
overrides: {
@ -19,13 +19,13 @@ const theme = createMuiTheme({
},
'&:hover:not($disabled):not($focused):not($error) $notchedOutline': {
borderColor: '#f9a826',
}
},
},
},
MuiInputLabel: {
root: {
color: '#f9a826'
}
color: '#f9a826',
},
},
MuiButton: {
root: {
@ -35,29 +35,26 @@ const theme = createMuiTheme({
textTransform: 'none',
borderRadius: '9px',
padding: '6px 54px 6px 54px',
width: '136px'
width: '136px',
},
containedPrimary: {
color: 'white',
'&:hover': {
backgroundColor: 'rgba(249, 168, 38, 0.91)'
}
backgroundColor: 'rgba(249, 168, 38, 0.91)',
},
},
textPrimary: {
}
}
textPrimary: {},
},
},
typography: {
fontFamily: [
'Montserrat'
].join(','),
fontFamily: ['Montserrat'].join(','),
h4: {
color: '#ffa800',
fontWeight: 600
fontWeight: 600,
},
h6: {
fontSize: '25px',
fontWeight: 'bold'
fontWeight: 'bold',
},
},
palette: {
@ -73,9 +70,7 @@ const theme = createMuiTheme({
dark: '#FFFFFF',
contrastText: '#FFFFFF',
},
},
})
}
});
export {theme };
export { theme }

279
yarn.lock
View File

@ -1353,6 +1353,11 @@
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
@ -1747,6 +1752,14 @@ agentkeepalive@^3.4.1:
dependencies:
humanize-ms "^1.2.1"
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
dependencies:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv-errors@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
@ -1785,6 +1798,13 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
ansi-escapes@^4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
dependencies:
type-fest "^0.11.0"
ansi-html@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@ -2465,6 +2485,11 @@ clean-css@^4.2.3:
dependencies:
source-map "~0.6.0"
clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
clean-webpack-plugin@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz#a99d8ec34c1c628a4541567aa7b457446460c62b"
@ -2485,6 +2510,13 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
dependencies:
restore-cursor "^3.1.0"
cli-table3@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee"
@ -2503,6 +2535,14 @@ cli-truncate@^0.2.1:
slice-ansi "0.0.4"
string-width "^1.0.1"
cli-truncate@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7"
integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==
dependencies:
slice-ansi "^3.0.0"
string-width "^4.2.0"
cli-width@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
@ -2617,6 +2657,11 @@ compare-func@^2.0.0:
array-ify "^1.0.0"
dot-prop "^5.1.0"
compare-versions@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -2829,6 +2874,17 @@ cosmiconfig@^5.1.0:
js-yaml "^3.13.1"
parse-json "^4.0.0"
cosmiconfig@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3"
integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==
dependencies:
"@types/parse-json" "^4.0.0"
import-fresh "^3.2.1"
parse-json "^5.0.0"
path-type "^4.0.0"
yaml "^1.10.0"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@ -3019,7 +3075,7 @@ debug@3.1.0:
dependencies:
ms "2.0.0"
debug@4.3.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
debug@4.3.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
@ -3479,6 +3535,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
eslint-config-prettier@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.0.0.tgz#024d661444319686c588c8849c8da33815dbdb1c"
integrity sha512-5EaAVPsIHu+grmm5WKjxUia4yHgRrbkd8I0ffqUSwixCPMVBrbS97UnzlEY/Q7OWo584vgixefM0kJnUfo/VjA==
eslint-plugin-react-hooks@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
@ -3670,7 +3731,7 @@ execa@^1.0.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
execa@^4.0.2:
execa@^4.0.2, execa@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==
@ -3893,6 +3954,13 @@ figures@^2.0.0:
dependencies:
escape-string-regexp "^1.0.5"
figures@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
dependencies:
escape-string-regexp "^1.0.5"
file-entry-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a"
@ -3967,6 +4035,21 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
find-up@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
find-versions@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965"
integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==
dependencies:
semver-regex "^3.1.2"
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@ -4129,6 +4212,11 @@ get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0:
has "^1.0.3"
has-symbols "^1.0.1"
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
get-pkg-repo@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d"
@ -4600,6 +4688,22 @@ humanize-ms@^1.2.1:
dependencies:
ms "^2.0.0"
husky@4:
version "4.3.8"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d"
integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==
dependencies:
chalk "^4.0.0"
ci-info "^2.0.0"
compare-versions "^3.6.0"
cosmiconfig "^7.0.0"
find-versions "^4.0.0"
opencollective-postinstall "^2.0.2"
pkg-dir "^5.0.0"
please-upgrade-node "^3.2.0"
slash "^3.0.0"
which-pm-runs "^1.0.0"
hyphenate-style-name@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
@ -4994,7 +5098,7 @@ is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
is-obj@^1.0.0:
is-obj@^1.0.0, is-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@ -5056,6 +5160,11 @@ is-regex@^1.0.4, is-regex@^1.1.1:
call-bind "^1.0.2"
has-symbols "^1.0.1"
is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
is-ssh@^1.3.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.2.tgz#a4b82ab63d73976fd8263cceee27f99a88bdae2b"
@ -5384,6 +5493,27 @@ lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
lint-staged@^10.5.4:
version "10.5.4"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665"
integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==
dependencies:
chalk "^4.1.0"
cli-truncate "^2.1.0"
commander "^6.2.0"
cosmiconfig "^7.0.0"
debug "^4.2.0"
dedent "^0.7.0"
enquirer "^2.3.6"
execa "^4.1.0"
listr2 "^3.2.2"
log-symbols "^4.0.0"
micromatch "^4.0.2"
normalize-path "^3.0.0"
please-upgrade-node "^3.2.0"
string-argv "0.3.1"
stringify-object "^3.3.0"
listr-silent-renderer@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
@ -5413,6 +5543,21 @@ listr-verbose-renderer@^0.5.0:
date-fns "^1.27.2"
figures "^2.0.0"
listr2@^3.2.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.3.2.tgz#e9dc899ba5997f365d25349d62b586f01257be5b"
integrity sha512-mGwDWg5Zq2m96Ern+RFTgzh6otSfLtHqhWKhoNvCErr46komaWAs1G8K6Th4VENps3cKySKGJXL1yAiCjmt5IQ==
dependencies:
chalk "^4.1.0"
cli-truncate "^2.1.0"
figures "^3.2.0"
indent-string "^4.0.0"
log-update "^4.0.0"
p-map "^4.0.0"
rxjs "^6.6.3"
through "^2.3.8"
wrap-ansi "^7.0.0"
listr@^0.14.3:
version "0.14.3"
resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586"
@ -5497,6 +5642,13 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@ -5574,6 +5726,16 @@ log-update@^2.3.0:
cli-cursor "^2.0.0"
wrap-ansi "^3.0.1"
log-update@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1"
integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==
dependencies:
ansi-escapes "^4.3.0"
cli-cursor "^3.1.0"
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
loglevel@^1.6.8:
version "1.7.1"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
@ -6358,6 +6520,11 @@ onetime@^5.1.0, onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
opencollective-postinstall@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==
opener@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
@ -6457,6 +6624,13 @@ p-locate@^4.1.0:
dependencies:
p-limit "^2.2.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
p-map-series@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca"
@ -6467,6 +6641,13 @@ p-map@^2.0.0, p-map@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
p-map@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==
dependencies:
aggregate-error "^3.0.0"
p-pipe@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9"
@ -6711,6 +6892,20 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
pkg-dir@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760"
integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==
dependencies:
find-up "^5.0.0"
please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
dependencies:
semver-compare "^1.0.0"
popper.js@1.16.1-lts:
version "1.16.1-lts"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
@ -6847,6 +7042,11 @@ prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
prettier@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
pretty-bytes@^5.4.1:
version "5.5.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.5.0.tgz#0cecda50a74a941589498011cf23275aa82b339e"
@ -7472,6 +7672,14 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
dependencies:
onetime "^5.1.0"
signal-exit "^3.0.2"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
@ -7583,6 +7791,16 @@ selfsigned@^1.10.8:
dependencies:
node-forge "^0.10.0"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
semver-regex@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807"
integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@ -7742,6 +7960,15 @@ slice-ansi@0.0.4:
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
slice-ansi@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==
dependencies:
ansi-styles "^4.0.0"
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
slice-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
@ -8017,6 +8244,11 @@ strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
string-argv@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-hash@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
@ -8044,7 +8276,7 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^4.2.0:
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
dependencies:
@ -8090,6 +8322,15 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
stringify-object@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
dependencies:
get-own-enumerable-property-symbols "^3.0.0"
is-obj "^1.0.1"
is-regexp "^1.0.0"
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@ -8348,7 +8589,7 @@ through2@^4.0.0:
dependencies:
readable-stream "3"
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3, through@~2.3.1:
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@ -8497,6 +8738,11 @@ type-check@^0.4.0, type-check@~0.4.0:
dependencies:
prelude-ls "^1.2.1"
type-fest@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
type-fest@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
@ -8945,6 +9191,24 @@ wrap-ansi@^5.1.0:
string-width "^3.0.0"
strip-ansi "^5.0.0"
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@ -9012,6 +9276,11 @@ yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
yaml@^1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"