mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-12-24 12:23:49 +01:00
Add mobile support for mindmap list.
This commit is contained in:
parent
82dcd34dc5
commit
5f67aa8c5c
@ -76,12 +76,12 @@ export class DefaultWidgetManager extends WidgetManager {
|
||||
topic.closeEditors();
|
||||
}
|
||||
|
||||
static useCreate(): [boolean, Element | undefined, DefaultWidgetManager] {
|
||||
static useCreate(): [boolean, (boolean) => void, Element | undefined, DefaultWidgetManager] {
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const [popoverTarget, setPopoverTarget] = useState(undefined);
|
||||
const widgetManager = useRef(new DefaultWidgetManager(setPopoverOpen, setPopoverTarget));
|
||||
|
||||
return [popoverOpen, popoverTarget, widgetManager.current];
|
||||
return [popoverOpen, setPopoverOpen, popoverTarget, widgetManager.current];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,10 +34,6 @@ const SaveAndDelete = (props: {
|
||||
<FormattedMessage id="action.accept" defaultMessage="Accept" />
|
||||
</Button>
|
||||
|
||||
<Button color="primary" variant="outlined" onClick={props.closeModal}>
|
||||
<FormattedMessage id="action.cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
|
||||
{props.model.getValue() && props.model.getValue().trim() !== '' && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
|
@ -59,7 +59,7 @@ const TopicLink = (props: {
|
||||
const isValidUrl = !url || checkURL(url);
|
||||
|
||||
return (
|
||||
<Box display="flex" sx={{ p: 1 }}>
|
||||
<Box sx={{ p: 1 }}>
|
||||
<Input
|
||||
autoFocus
|
||||
error={!isValidUrl}
|
||||
@ -87,8 +87,9 @@ const TopicLink = (props: {
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
sx={{ pr: 1 }}
|
||||
margin="dense"
|
||||
></Input>
|
||||
<br />
|
||||
<SaveAndDelete
|
||||
model={props.urlModel}
|
||||
closeModal={props.closeModal}
|
||||
|
@ -29,9 +29,7 @@ import {
|
||||
} from '@wisemapping/mindplot';
|
||||
|
||||
import I18nMsg from '../classes/i18n-msg';
|
||||
import { theme as defaultEditorTheme } from '../theme';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import ThemeProvider from '@mui/material/styles/ThemeProvider';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { Notifier } from './warning-dialog/styled';
|
||||
import WarningDialog from './warning-dialog';
|
||||
@ -42,6 +40,9 @@ import { ToolbarActionType } from './toolbar/ToolbarActionType';
|
||||
import MapInfo from '../classes/model/map-info';
|
||||
import EditorToolbar from './editor-toolbar';
|
||||
import ZoomPanel from './zoom-panel';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Box from '@mui/material/Box';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { SpinnerCentered } from './style';
|
||||
|
||||
export type EditorOptions = {
|
||||
@ -66,7 +67,6 @@ const Editor = ({
|
||||
options,
|
||||
persistenceManager,
|
||||
onAction,
|
||||
theme,
|
||||
accountConfiguration,
|
||||
}: EditorProps): ReactElement => {
|
||||
const [model, setModel] = useState<Model | undefined>();
|
||||
@ -75,8 +75,8 @@ const Editor = ({
|
||||
// This is required to redraw in case of chansges in the canvas...
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [canvasUpdate, setCanvasUpdate] = useState<number>();
|
||||
const editorTheme: Theme = theme ? theme : defaultEditorTheme;
|
||||
const [popoverOpen, popoverTarget, widgetManager] = DefaultWidgetManager.useCreate();
|
||||
const [popoverOpen, setPopoverOpen, popoverTarget, widgetManager] =
|
||||
DefaultWidgetManager.useCreate();
|
||||
const capability = new Capability(options.mode, mapInfo.isLocked());
|
||||
|
||||
useEffect(() => {
|
||||
@ -108,59 +108,62 @@ const Editor = ({
|
||||
const locale = options.locale;
|
||||
const msg = I18nMsg.loadLocaleData(locale);
|
||||
return (
|
||||
<ThemeProvider theme={editorTheme}>
|
||||
<IntlProvider locale={locale} messages={msg}>
|
||||
<AppBar
|
||||
model={model}
|
||||
mapInfo={mapInfo}
|
||||
capability={capability}
|
||||
onAction={onAction}
|
||||
accountConfig={accountConfiguration}
|
||||
/>
|
||||
<IntlProvider locale={locale} messages={msg}>
|
||||
<AppBar
|
||||
model={model}
|
||||
mapInfo={mapInfo}
|
||||
capability={capability}
|
||||
onAction={onAction}
|
||||
accountConfig={accountConfiguration}
|
||||
/>
|
||||
|
||||
<Popover
|
||||
id="popover"
|
||||
open={popoverOpen}
|
||||
anchorEl={popoverTarget}
|
||||
onClose={widgetManager.handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
>
|
||||
{widgetManager.getEditorContent()}
|
||||
</Popover>
|
||||
<Popover
|
||||
id="popover"
|
||||
open={popoverOpen}
|
||||
anchorEl={popoverTarget}
|
||||
onClose={widgetManager.handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
>
|
||||
<Box alignItems={'end'}>
|
||||
<IconButton onClick={() => setPopoverOpen(false)} aria-label={'Close'}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
{widgetManager.getEditorContent()}
|
||||
</Popover>
|
||||
|
||||
<EditorToolbar model={model} capability={capability} />
|
||||
<ZoomPanel model={model} capability={capability} />
|
||||
<EditorToolbar model={model} capability={capability} />
|
||||
<ZoomPanel model={model} capability={capability} />
|
||||
|
||||
<mindplot-component
|
||||
ref={mindplotRef}
|
||||
id="mindmap-comp"
|
||||
mode={options.mode}
|
||||
locale={options.locale}
|
||||
zoom={options.zoom}
|
||||
/>
|
||||
<mindplot-component
|
||||
ref={mindplotRef}
|
||||
id="mindmap-comp"
|
||||
mode={options.mode}
|
||||
locale={options.locale}
|
||||
zoom={options.zoom}
|
||||
/>
|
||||
|
||||
<Notifier id="headerNotifier" />
|
||||
<WarningDialog
|
||||
capability={capability}
|
||||
message={mapInfo.isLocked() ? mapInfo.getLockedMessage() : ''}
|
||||
/>
|
||||
<Notifier id="headerNotifier" />
|
||||
<WarningDialog
|
||||
capability={capability}
|
||||
message={mapInfo.isLocked() ? mapInfo.getLockedMessage() : ''}
|
||||
/>
|
||||
|
||||
{!model?.isMapLoadded() && (
|
||||
<SpinnerCentered>
|
||||
<Vortex
|
||||
visible={true}
|
||||
height="160"
|
||||
width="160"
|
||||
ariaLabel="vortex-loading"
|
||||
colors={['#ffde1a', '#ffce00', '#ffa700', '#ff8d00', '#ff7400', '#ffde1a']}
|
||||
/>
|
||||
</SpinnerCentered>
|
||||
)}
|
||||
</IntlProvider>
|
||||
</ThemeProvider>
|
||||
{!model?.isMapLoadded() && (
|
||||
<SpinnerCentered>
|
||||
<Vortex
|
||||
visible={true}
|
||||
height="160"
|
||||
width="160"
|
||||
ariaLabel="vortex-loading"
|
||||
colors={['#ffde1a', '#ffce00', '#ffa700', '#ff8d00', '#ff7400', '#ffde1a']}
|
||||
/>
|
||||
</SpinnerCentered>
|
||||
)}
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
export default Editor;
|
||||
|
@ -25,6 +25,7 @@ import '../app-bar/styles.css';
|
||||
import Box from '@mui/material/Box';
|
||||
import ToolbarPosition from '../../classes/model/toolbar-position';
|
||||
import ActionConfig from '../../classes/action/action-config';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
|
||||
/**
|
||||
* Common button
|
||||
@ -106,7 +107,11 @@ export const ToolbarSubmenu = (props: {
|
||||
}}
|
||||
>
|
||||
<ToolbarButtonOption
|
||||
configuration={{ ...props.configuration, onClick: () => setOpen(true) }}
|
||||
configuration={{
|
||||
...props.configuration,
|
||||
onClick: () => setOpen(true),
|
||||
selected: () => open,
|
||||
}}
|
||||
/>
|
||||
<Popover
|
||||
role="submenu"
|
||||
@ -125,6 +130,13 @@ export const ToolbarSubmenu = (props: {
|
||||
}}
|
||||
elevation={props.elevation}
|
||||
>
|
||||
{props.configuration.useClickToClose && (
|
||||
<Box alignItems={'end'}>
|
||||
<IconButton onClick={() => setOpen(false)} aria-label={'Close'}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
<div style={{ display: 'flex' }} onScroll={(e) => e.stopPropagation()}>
|
||||
{props.configuration.options?.map((o, i) => {
|
||||
if (o?.visible === false) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import React from 'react';
|
||||
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
||||
import { render, fireEvent, waitFor, screen, findByLabelText } from '@testing-library/react';
|
||||
import ThreeDRotation from '@mui/icons-material/ThreeDRotation';
|
||||
import Toolbar, {
|
||||
ToolbarButtonOption,
|
||||
@ -80,6 +80,12 @@ const iconFunctionConfig: ActionConfig = {
|
||||
onClick: jest.fn(),
|
||||
};
|
||||
|
||||
const submenuOnClickConfig: ActionConfig = {
|
||||
icon: <ThreeDRotation></ThreeDRotation>,
|
||||
useClickToClose: true,
|
||||
options: [config, null, config, null],
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@ -192,6 +198,7 @@ describe('Editor Toolbar Submenu', () => {
|
||||
const item = screen.getByRole('menuitem');
|
||||
fireEvent.mouseOver(item);
|
||||
const clickeableDiv = await screen.findByTestId('custom-render-div');
|
||||
expect(screen.queryByRole('submenu')).toBeTruthy();
|
||||
|
||||
fireEvent.click(clickeableDiv);
|
||||
|
||||
@ -206,6 +213,28 @@ describe('Editor Toolbar Submenu', () => {
|
||||
|
||||
expect(screen.queryByRole('submenu')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('Given a useClickToOpen configuration when mouse is over, not shows a submenu ', async () => {
|
||||
render(<ToolbarSubmenu configuration={submenuOnClickConfig}></ToolbarSubmenu>);
|
||||
const item = screen.getByRole('menuitem');
|
||||
|
||||
fireEvent.mouseOver(item);
|
||||
|
||||
expect(screen.queryByRole('submenu')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('Given a useClickToOpen configuration when click, shows a submenu with close button', async () => {
|
||||
render(<ToolbarSubmenu configuration={submenuOnClickConfig}></ToolbarSubmenu>);
|
||||
const item = screen.getByRole('button');
|
||||
|
||||
fireEvent.click(item);
|
||||
|
||||
await screen.findByRole('submenu');
|
||||
const closeButton = await screen.findByLabelText('Close');
|
||||
|
||||
fireEvent.click(closeButton);
|
||||
expect(screen.queryByRole('submenu')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toolbar menu item', () => {
|
||||
|
@ -40,6 +40,7 @@
|
||||
"!@mui/material/test-utils/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"react/no-unknown-property": ["error", { "ignore": ["css"] }]
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
context('Editor Page', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('c/maps/11/edit');
|
||||
cy.visit('/c/maps/11/edit');
|
||||
});
|
||||
|
||||
it('page loaded', () => {
|
||||
|
@ -7,3 +7,43 @@ context('Maps Page', () => {
|
||||
// cy.matchImageSnapshot('maps');
|
||||
});
|
||||
});
|
||||
|
||||
context('iphone-5 resolution', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport('iphone-5');
|
||||
cy.visit('/c/maps', {
|
||||
onLoad: (win) => {
|
||||
win.onerror = null;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Displays mobile menu button', () => {
|
||||
cy.get('#open-main-drawer').should('be.visible');
|
||||
});
|
||||
|
||||
it('Displays mobile menu on click', () => {
|
||||
cy.get('.MuiDrawer-root').should('not.be.visible');
|
||||
cy.get('#open-main-drawer').should('be.visible').click();
|
||||
cy.get('.MuiDrawer-root').should('be.visible');
|
||||
});
|
||||
|
||||
it('Displays a card list', () => {
|
||||
cy.get('.MuiCard-root').should('have.length', 3);
|
||||
});
|
||||
});
|
||||
|
||||
context('720p resolution', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport(1280, 720);
|
||||
cy.visit('/c/maps');
|
||||
});
|
||||
|
||||
it('Displays mobile menu button', () => {
|
||||
cy.get('#open-desktop-drawer').should('be.visible');
|
||||
});
|
||||
|
||||
it('Displays a table with maps', () => {
|
||||
cy.get('.MuiTableBody-root').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
@ -263,6 +263,33 @@
|
||||
"info.title": {
|
||||
"defaultMessage": "Información"
|
||||
},
|
||||
"label.add-button": {
|
||||
"defaultMessage": "Agregar etiqueta"
|
||||
},
|
||||
"label.add-for": {
|
||||
"defaultMessage": "Editando etiquetas para el mapa:"
|
||||
},
|
||||
"label.add-placeholder": {
|
||||
"defaultMessage": "Título"
|
||||
},
|
||||
"label.change-color": {
|
||||
"defaultMessage": "Cambiar el color"
|
||||
},
|
||||
"label.delete-description": {
|
||||
"defaultMessage": "será borrado de todos los mapas a los que se encuentra asociado. Desea continuar?"
|
||||
},
|
||||
"label.delete-title": {
|
||||
"defaultMessage": "Confirmación"
|
||||
},
|
||||
"label.description": {
|
||||
"defaultMessage": "Usa las etiquetas para organizar tus mapas."
|
||||
},
|
||||
"label.maps-count": {
|
||||
"defaultMessage": "{count} mapas"
|
||||
},
|
||||
"label.title": {
|
||||
"defaultMessage": "Agregar etiqueta"
|
||||
},
|
||||
"language.change": {
|
||||
"defaultMessage": "Cambiar idioma"
|
||||
},
|
||||
|
@ -49,7 +49,6 @@
|
||||
"@mui/icons-material": "^5.9.3",
|
||||
"@mui/lab": "^5.0.0-alpha.98",
|
||||
"@mui/material": "^5.10.11",
|
||||
"@mui/styles": "^5.9.3",
|
||||
"@reduxjs/toolkit": "^1.5.0",
|
||||
"@wisemapping/editor": "^0.4.0",
|
||||
"axios": "^0.27.2",
|
||||
|
@ -11,21 +11,16 @@ import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { theme } from './theme';
|
||||
import AppI18n, { Locales } from './classes/app-i18n';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { ThemeProvider, Theme, StyledEngineProvider } from '@mui/material/styles';
|
||||
import { ThemeProvider as MuiThemeProvider, StyledEngineProvider } from '@mui/material/styles';
|
||||
import ReactGA from 'react-ga4';
|
||||
import AppConfig from './classes/app-config';
|
||||
import withSessionExpirationHandling from './components/HOCs/withSessionExpirationHandling';
|
||||
import RegistrationSuccessPage from './components/registration-success-page';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import RegistrationCallbackPage from './components/registration-callback';
|
||||
|
||||
const EditorPage = React.lazy(() => import('./components/editor-page'));
|
||||
const MapsPage = React.lazy(() => import('./components/maps-page'));
|
||||
|
||||
declare module '@mui/styles/defaultTheme' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface DefaultTheme extends Theme {}
|
||||
}
|
||||
|
||||
// Google Analytics Initialization.
|
||||
ReactGA.initialize([
|
||||
{
|
||||
@ -53,7 +48,6 @@ function Redirect({ to }) {
|
||||
|
||||
const App = (): ReactElement => {
|
||||
const locale = AppI18n.getDefaultLocale();
|
||||
const EnhacedEditorPage = withSessionExpirationHandling(EditorPage);
|
||||
|
||||
return locale.message ? (
|
||||
<Provider store={store}>
|
||||
@ -64,42 +58,67 @@ const App = (): ReactElement => {
|
||||
messages={locale.message as Record<string, string>}
|
||||
>
|
||||
<StyledEngineProvider injectFirst>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Redirect to="/c/login" />} />
|
||||
<Route path="/c/login" element={<LoginPage />} />
|
||||
<Route path="/c/registration" element={<RegistationPage />} />
|
||||
<Route path="/c/registration-google" element={<RegistrationCallbackPage />} />
|
||||
<Route path="/c/registration-success" element={<RegistrationSuccessPage />} />
|
||||
<Route path="/c/forgot-password" element={<ForgotPasswordPage />} />
|
||||
<Route
|
||||
path="/c/forgot-password-success"
|
||||
element={<ForgotPasswordSuccessPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/c/maps/"
|
||||
element={
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<FormattedMessage id="dialog.loading" defaultMessage="Loading ..." />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<MapsPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/c/maps/:id/edit"
|
||||
element={<EnhacedEditorPage isTryMode={false} />}
|
||||
/>
|
||||
<Route path="/c/maps/:id/try" element={<EnhacedEditorPage isTryMode={true} />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Redirect to="/c/login" />} />
|
||||
<Route path="/c/login" element={<LoginPage />} />
|
||||
<Route path="/c/registration" element={<RegistationPage />} />
|
||||
<Route path="/c/registration-google" element={<RegistrationCallbackPage />} />
|
||||
<Route path="/c/registration-success" element={<RegistrationSuccessPage />} />
|
||||
<Route path="/c/forgot-password" element={<ForgotPasswordPage />} />
|
||||
<Route
|
||||
path="/c/forgot-password-success"
|
||||
element={<ForgotPasswordSuccessPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/c/maps/"
|
||||
element={
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<FormattedMessage id="dialog.loading" defaultMessage="Loading ..." />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<MapsPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/c/maps/:id/edit"
|
||||
element={
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<FormattedMessage id="dialog.loading" defaultMessage="Loading ..." />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EditorPage isTryMode={false} />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/c/maps/:id/try"
|
||||
element={
|
||||
<Suspense
|
||||
fallback={
|
||||
<div>
|
||||
<FormattedMessage id="dialog.loading" defaultMessage="Loading ..." />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EditorPage isTryMode={true} />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</MuiThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
</IntlProvider>
|
||||
</QueryClientProvider>
|
||||
|
@ -386,7 +386,7 @@
|
||||
"forgot.oauth.message": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "You dont need password, please login using Google"
|
||||
"value": "You dont need password, please login using Google."
|
||||
}
|
||||
],
|
||||
"forgot.page-title": [
|
||||
@ -631,6 +631,12 @@
|
||||
"value": "Log into your account"
|
||||
}
|
||||
],
|
||||
"login.division": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "or"
|
||||
}
|
||||
],
|
||||
"login.email": [
|
||||
{
|
||||
"type": 0,
|
||||
@ -964,7 +970,7 @@
|
||||
"registration.callback.waiting.title": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Finishing ..."
|
||||
"value": "Finishing..."
|
||||
}
|
||||
],
|
||||
"registration.desc": [
|
||||
|
@ -555,6 +555,64 @@
|
||||
"value": "Información"
|
||||
}
|
||||
],
|
||||
"label.add-button": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Agregar etiqueta"
|
||||
}
|
||||
],
|
||||
"label.add-for": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Editando etiquetas para el mapa:"
|
||||
}
|
||||
],
|
||||
"label.add-placeholder": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Título"
|
||||
}
|
||||
],
|
||||
"label.change-color": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Cambiar el color"
|
||||
}
|
||||
],
|
||||
"label.delete-description": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "será borrado de todos los mapas a los que se encuentra asociado. Desea continuar?"
|
||||
}
|
||||
],
|
||||
"label.delete-title": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Confirmación"
|
||||
}
|
||||
],
|
||||
"label.description": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Usa las etiquetas para organizar tus mapas."
|
||||
}
|
||||
],
|
||||
"label.maps-count": [
|
||||
{
|
||||
"type": 1,
|
||||
"value": "count"
|
||||
},
|
||||
{
|
||||
"type": 0,
|
||||
"value": " mapas"
|
||||
}
|
||||
],
|
||||
"label.title": [
|
||||
{
|
||||
"type": 0,
|
||||
"value": "Agregar etiqueta"
|
||||
}
|
||||
],
|
||||
"language.change": [
|
||||
{
|
||||
"type": 0,
|
||||
|
18
packages/webapp/src/components/HOCs/withEmotionStyles.tsx
Normal file
18
packages/webapp/src/components/HOCs/withEmotionStyles.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import React, { ComponentType } from 'react';
|
||||
|
||||
function withEmotionStyles<T>(css) {
|
||||
return (Component: ComponentType<T>) => {
|
||||
const WithEmotionStyles = (hocProps): React.ReactElement => {
|
||||
return <Component {...hocProps} css={{ ...hocProps.paperCss, ...css }} />;
|
||||
};
|
||||
WithEmotionStyles.displayName = `withEmotionStyles(${getDisplayName(Component)})`;
|
||||
return WithEmotionStyles;
|
||||
};
|
||||
}
|
||||
|
||||
function getDisplayName(WrappedComponent) {
|
||||
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
||||
}
|
||||
|
||||
export default withEmotionStyles;
|
@ -1,42 +1,5 @@
|
||||
import AppConfig from '../../classes/app-config';
|
||||
import exampleMap from '../../classes/client/mock-client/example-map.wxml';
|
||||
import {
|
||||
PersistenceManager,
|
||||
RESTPersistenceManager,
|
||||
LocalStorageManager,
|
||||
Mindmap,
|
||||
MockPersistenceManager,
|
||||
XMLSerializerFactory,
|
||||
} from '@wisemapping/editor';
|
||||
|
||||
export const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => {
|
||||
let persistenceManager: PersistenceManager;
|
||||
if (AppConfig.isRestClient()) {
|
||||
if (mode === 'edition-owner' || mode === 'edition-editor') {
|
||||
persistenceManager = new RESTPersistenceManager({
|
||||
documentUrl: '/c/restful/maps/{id}/document',
|
||||
revertUrl: '/c/restful/maps/{id}/history/latest',
|
||||
lockUrl: '/c/restful/maps/{id}/lock',
|
||||
});
|
||||
} else {
|
||||
persistenceManager = new LocalStorageManager(
|
||||
`/c/restful/maps/{id}/${
|
||||
globalThis.historyId ? `${globalThis.historyId}/` : ''
|
||||
}document/xml${mode === 'showcase' ? '-pub' : ''}`,
|
||||
true,
|
||||
);
|
||||
}
|
||||
persistenceManager.addErrorHandler((error) => {
|
||||
if (error.errorType === 'session-expired') {
|
||||
// TODO: this line was in RestPersistenceClient, do something similar here
|
||||
//client.sessionExpired();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
persistenceManager = new MockPersistenceManager(exampleMap);
|
||||
}
|
||||
return persistenceManager;
|
||||
};
|
||||
import { LocalStorageManager, Mindmap, XMLSerializerFactory } from '@wisemapping/editor';
|
||||
|
||||
export const fetchMindmap = async (mapId: number): Promise<Mindmap> => {
|
||||
let mindmap: Mindmap;
|
||||
|
@ -16,10 +16,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useEffect } from 'react';
|
||||
import ActionDispatcher from '../maps-page/action-dispatcher';
|
||||
import { ActionType } from '../maps-page/action-chooser';
|
||||
import Editor from '@wisemapping/editor';
|
||||
import { EditorRenderMode, PersistenceManager } from '@wisemapping/editor';
|
||||
import {
|
||||
EditorRenderMode,
|
||||
PersistenceManager,
|
||||
RESTPersistenceManager,
|
||||
LocalStorageManager,
|
||||
MockPersistenceManager,
|
||||
} from '@wisemapping/editor';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import AppI18n, { Locales } from '../../classes/app-i18n';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -27,17 +31,66 @@ import { hotkeysEnabled } from '../../redux/editorSlice';
|
||||
import ReactGA from 'react-ga4';
|
||||
import { useFetchAccount, useFetchMapById } from '../../redux/clientSlice';
|
||||
import EditorOptionsBuilder from './EditorOptionsBuilder';
|
||||
import { buildPersistenceManagerForEditor } from './PersistenceManagerUtils';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import AccountMenu from '../maps-page/account-menu';
|
||||
import MapInfoImpl from '../../classes/editor-map-info';
|
||||
import { MapInfo } from '@wisemapping/editor';
|
||||
import { activeInstance } from '../../redux/clientSlice';
|
||||
import Client from '../../classes/client';
|
||||
import AppConfig from '../../classes/app-config';
|
||||
import exampleMap from '../../classes/client/mock-client/example-map.wxml';
|
||||
import withSessionExpirationHandling from '../HOCs/withSessionExpirationHandling';
|
||||
|
||||
const buildPersistenceManagerForEditor = (mode: string): PersistenceManager => {
|
||||
let persistenceManager: PersistenceManager;
|
||||
if (AppConfig.isRestClient()) {
|
||||
if (mode === 'edition-owner' || mode === 'edition-editor') {
|
||||
persistenceManager = new RESTPersistenceManager({
|
||||
documentUrl: '/c/restful/maps/{id}/document',
|
||||
revertUrl: '/c/restful/maps/{id}/history/latest',
|
||||
lockUrl: '/c/restful/maps/{id}/lock',
|
||||
});
|
||||
} else {
|
||||
persistenceManager = new LocalStorageManager(
|
||||
`/c/restful/maps/{id}/${
|
||||
globalThis.historyId ? `${globalThis.historyId}/` : ''
|
||||
}document/xml${mode === 'showcase' ? '-pub' : ''}`,
|
||||
true,
|
||||
);
|
||||
}
|
||||
persistenceManager.addErrorHandler((error) => {
|
||||
if (error.errorType === 'session-expired') {
|
||||
// TODO: this line was in RestPersistenceClient, do something similar here
|
||||
//client.sessionExpired();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
persistenceManager = new MockPersistenceManager(exampleMap);
|
||||
}
|
||||
return persistenceManager;
|
||||
};
|
||||
|
||||
export type EditorPropsType = {
|
||||
isTryMode: boolean;
|
||||
};
|
||||
type ActionType =
|
||||
| 'open'
|
||||
| 'share'
|
||||
| 'import'
|
||||
| 'delete'
|
||||
| 'info'
|
||||
| 'create'
|
||||
| 'duplicate'
|
||||
| 'export'
|
||||
| 'label'
|
||||
| 'rename'
|
||||
| 'print'
|
||||
| 'info'
|
||||
| 'publish'
|
||||
| 'history'
|
||||
| undefined;
|
||||
|
||||
const ActionDispatcher = React.lazy(() => import('../maps-page/action-dispatcher'));
|
||||
const AccountMenu = React.lazy(() => import('../maps-page/account-menu'));
|
||||
|
||||
const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
|
||||
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
|
||||
@ -144,4 +197,4 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => {
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorPage;
|
||||
export default withSessionExpirationHandling(EditorPage);
|
||||
|
@ -69,7 +69,7 @@ const ForgotPassword = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<FormContainer>
|
||||
<FormContainer maxWidth="xs">
|
||||
<Typography variant="h4" component="h1">
|
||||
<FormattedMessage id="forgot.title" defaultMessage="Reset your password" />
|
||||
</Typography>
|
||||
|
@ -1,11 +1,9 @@
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import withEmotionStyles from '../../HOCs/withEmotionStyles';
|
||||
import Alert from '@mui/material/Alert';
|
||||
|
||||
export const StyledAlert = withStyles({
|
||||
root: {
|
||||
padding: '10px 15px',
|
||||
margin: '5px 0px ',
|
||||
},
|
||||
export const StyledAlert = withEmotionStyles({
|
||||
padding: '10px 15px',
|
||||
margin: '5px 0px ',
|
||||
})(Alert);
|
||||
|
||||
export default StyledAlert;
|
||||
|
@ -1,12 +1,10 @@
|
||||
import withEmotionStyles from '../../HOCs/withEmotionStyles';
|
||||
import Container from '@mui/material/Container';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
|
||||
const FormContainer = withStyles({
|
||||
root: {
|
||||
padding: '20px 10px 0px 20px',
|
||||
maxWidth: '380px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
const FormContainer = withEmotionStyles({
|
||||
padding: '20px 10px 0px 20px',
|
||||
maxWidth: '380px !important',
|
||||
textAlign: 'center',
|
||||
})(Container);
|
||||
|
||||
export default FormContainer;
|
||||
|
@ -52,7 +52,7 @@ const ActionChooser = (props: ActionProps): React.ReactElement => {
|
||||
};
|
||||
};
|
||||
|
||||
const role = mapId ? useFetchMapById(mapId)?.map?.role : undefined;
|
||||
const role = mapId !== undefined ? useFetchMapById(mapId)?.map?.role : undefined;
|
||||
return (
|
||||
<Menu
|
||||
anchorEl={anchor}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import withEmotionStyles from '../../HOCs/withEmotionStyles';
|
||||
|
||||
export const StyledMenuItem = withStyles({
|
||||
export const StyledMenuItem = withEmotionStyles({
|
||||
root: {
|
||||
width: '300px',
|
||||
},
|
||||
|
@ -5,9 +5,9 @@ import { StyledDialog, StyledDialogActions, StyledDialogContent, StyledDialogTit
|
||||
import GlobalError from '../../../form/global-error';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import Button from '@mui/material/Button';
|
||||
import { PaperProps } from '@mui/material/Paper';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { disableHotkeys, enableHotkeys } from '../../../../redux/editorSlice';
|
||||
import { CSSObject } from '@emotion/react';
|
||||
|
||||
export type DialogProps = {
|
||||
onClose: () => void;
|
||||
@ -21,7 +21,7 @@ export type DialogProps = {
|
||||
submitButton?: string;
|
||||
actionUrl?: string;
|
||||
maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false;
|
||||
PaperProps?: Partial<PaperProps>;
|
||||
paperCss?: CSSObject;
|
||||
};
|
||||
|
||||
const BaseDialog = (props: DialogProps): React.ReactElement => {
|
||||
@ -32,7 +32,7 @@ const BaseDialog = (props: DialogProps): React.ReactElement => {
|
||||
dispatch(enableHotkeys());
|
||||
};
|
||||
}, []);
|
||||
const { onClose, onSubmit, maxWidth = 'sm', PaperProps } = props;
|
||||
const { onClose, onSubmit, maxWidth = 'sm', paperCss } = props;
|
||||
|
||||
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
@ -50,7 +50,7 @@ const BaseDialog = (props: DialogProps): React.ReactElement => {
|
||||
open={true}
|
||||
onClose={onClose}
|
||||
maxWidth={maxWidth}
|
||||
PaperProps={PaperProps}
|
||||
paperCss={{ '& .MuiPaper-root.MuiDialog-paper': paperCss }}
|
||||
fullWidth={true}
|
||||
>
|
||||
<form autoComplete="off" onSubmit={handleOnSubmit}>
|
||||
|
@ -2,28 +2,20 @@ import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import withStyles from '@mui/styles/withStyles';
|
||||
import withEmotionStyles from '../../../HOCs/withEmotionStyles';
|
||||
|
||||
export const StyledDialogContent = withStyles({
|
||||
root: {
|
||||
padding: '0px 39px',
|
||||
},
|
||||
export const StyledDialogContent = withEmotionStyles({
|
||||
padding: '0px 39px',
|
||||
})(DialogContent);
|
||||
|
||||
export const StyledDialogTitle = withStyles({
|
||||
root: {
|
||||
padding: '39px 39px 10px 39px',
|
||||
},
|
||||
export const StyledDialogTitle = withEmotionStyles({
|
||||
padding: '39px 39px 10px 39px',
|
||||
})(DialogTitle);
|
||||
|
||||
export const StyledDialogActions = withStyles({
|
||||
root: {
|
||||
padding: '39px 39px 39px 39px',
|
||||
},
|
||||
export const StyledDialogActions = withEmotionStyles({
|
||||
padding: '39px 39px 39px 39px',
|
||||
})(DialogActions);
|
||||
|
||||
export const StyledDialog = withStyles({
|
||||
root: {
|
||||
borderRadius: '9px',
|
||||
},
|
||||
export const StyledDialog = withEmotionStyles({
|
||||
borderRadius: '9px',
|
||||
})(Dialog);
|
||||
|
@ -185,7 +185,7 @@ const ExportDialog = ({
|
||||
<RadioGroup name="export" value={exportGroup} onChange={handleOnGroupChange}>
|
||||
<FormControl>
|
||||
<FormControlLabel
|
||||
className={classes.label}
|
||||
css={classes.label}
|
||||
value="image"
|
||||
disabled={!enableImgExport}
|
||||
control={<Radio color="primary" />}
|
||||
@ -203,20 +203,20 @@ const ExportDialog = ({
|
||||
onChange={handleOnExportFormatChange}
|
||||
variant="outlined"
|
||||
value={exportFormat}
|
||||
className={classes.select}
|
||||
css={classes.select}
|
||||
>
|
||||
<MenuItem value="svg" className={classes.menu}>
|
||||
<MenuItem value="svg" css={classes.menu}>
|
||||
Scalable Vector Graphics (SVG)
|
||||
</MenuItem>
|
||||
<MenuItem value="png" className={classes.menu}>
|
||||
<MenuItem value="png" css={classes.menu}>
|
||||
Portable Network Graphics (PNG)
|
||||
</MenuItem>
|
||||
<MenuItem value="jpg" className={classes.menu}>
|
||||
<MenuItem value="jpg" css={classes.menu}>
|
||||
JPEG Image (JPEG)
|
||||
</MenuItem>
|
||||
</Select>
|
||||
<FormControlLabel
|
||||
className={classes.select}
|
||||
css={classes.select}
|
||||
control={<Checkbox checked={zoomToFit} onChange={handleOnZoomToFit} />}
|
||||
label={intl.formatMessage({
|
||||
id: 'export.img-center',
|
||||
@ -229,7 +229,7 @@ const ExportDialog = ({
|
||||
|
||||
<FormControl>
|
||||
<FormControlLabel
|
||||
className={classes.label}
|
||||
css={classes.label}
|
||||
value="document"
|
||||
control={<Radio color="primary" />}
|
||||
label={intl.formatMessage({
|
||||
@ -244,12 +244,12 @@ const ExportDialog = ({
|
||||
onChange={handleOnExportFormatChange}
|
||||
variant="outlined"
|
||||
value={exportFormat}
|
||||
className={classes.select}
|
||||
css={classes.select}
|
||||
>
|
||||
<MenuItem className={classes.select} value="txt">
|
||||
<MenuItem css={classes.select} value="txt">
|
||||
Plain Text File (TXT)
|
||||
</MenuItem>
|
||||
<MenuItem className={classes.select} value="md">
|
||||
<MenuItem css={classes.select} value="md">
|
||||
Markdown (MD)
|
||||
</MenuItem>
|
||||
{/* <MenuItem className={classes.select} value="xls">
|
||||
@ -261,7 +261,7 @@ const ExportDialog = ({
|
||||
|
||||
<FormControl>
|
||||
<FormControlLabel
|
||||
className={classes.label}
|
||||
css={classes.label}
|
||||
value="mindmap-tool"
|
||||
control={<Radio color="primary" />}
|
||||
label={intl.formatMessage({
|
||||
@ -275,13 +275,13 @@ const ExportDialog = ({
|
||||
<Select
|
||||
onChange={handleOnExportFormatChange}
|
||||
variant="outlined"
|
||||
className={classes.select}
|
||||
css={classes.select}
|
||||
value={exportFormat}
|
||||
>
|
||||
<MenuItem className={classes.select} value="wxml">
|
||||
<MenuItem css={classes.select} value="wxml">
|
||||
WiseMapping (WXML)
|
||||
</MenuItem>
|
||||
<MenuItem className={classes.select} value="mm">
|
||||
<MenuItem css={classes.select} value="mm">
|
||||
Freemind 1.0.1 (MM)
|
||||
</MenuItem>
|
||||
{/* <MenuItem className={classes.select} value="mmap">
|
||||
|
@ -1,8 +1,8 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import useClasses from '../../../../theme/useStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
export const useStyles = () =>
|
||||
useClasses({
|
||||
select: {
|
||||
height: '40px',
|
||||
borderRadius: '9px',
|
||||
@ -16,5 +16,4 @@ export const useStyles = makeStyles(() =>
|
||||
label: {
|
||||
margin: '5px 0px',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -50,9 +50,11 @@ const ActionDispatcher = ({
|
||||
switch (action) {
|
||||
case 'open':
|
||||
window.location.href = `/c/maps/${mapsId}/edit`;
|
||||
handleOnClose(true);
|
||||
break;
|
||||
case 'print':
|
||||
window.open(`/c/maps/${mapsId}/print`, 'print');
|
||||
handleOnClose(true);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement =
|
||||
>
|
||||
<Paper style={{ maxHeight: 200, overflowY: 'scroll' }} variant="outlined" elevation={0}>
|
||||
<Card variant="outlined">
|
||||
<List dense={true}>
|
||||
<List dense={true} css={classes.list}>
|
||||
<ListItem>
|
||||
<Typography variant="body1" style={{ fontWeight: 'bold' }}>
|
||||
<FormattedMessage id="info.basic-info" defaultMessage="Basic Info" />
|
||||
@ -50,42 +50,42 @@ const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement =
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
|
||||
<Typography variant="caption" color="textPrimary" css={classes.textDesc}>
|
||||
<FormattedMessage id="info.name" defaultMessage="Name" />:
|
||||
</Typography>
|
||||
<Typography variant="body2">{map?.title}</Typography>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
|
||||
<Typography variant="caption" color="textPrimary" css={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" css={classes.textDesc}>
|
||||
<FormattedMessage id="info.creator" defaultMessage="Creator" />:
|
||||
</Typography>
|
||||
<Typography variant="body2">{map?.createdBy}</Typography>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
|
||||
<Typography variant="caption" color="textPrimary" css={classes.textDesc}>
|
||||
<FormattedMessage id="info.creation-time" defaultMessage="Creation Date" />:
|
||||
</Typography>
|
||||
<Typography variant="body2">{dayjs(map?.creationTime).format('LLL')}</Typography>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
|
||||
<Typography variant="caption" color="textPrimary" css={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}>
|
||||
<Typography variant="caption" color="textPrimary" css={classes.textDesc}>
|
||||
<FormattedMessage id="info.modified-time" defaultMessage="Last Modified Date" />:
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
@ -94,7 +94,7 @@ const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement =
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
|
||||
<Typography variant="caption" color="textPrimary" css={classes.textDesc}>
|
||||
<FormattedMessage id="info.starred" defaultMessage="Starred" />:
|
||||
</Typography>
|
||||
<Typography variant="body2">{Boolean(map?.starred).toString()}</Typography>
|
||||
@ -111,7 +111,7 @@ const InfoDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement =
|
||||
</ListItem>
|
||||
</List>
|
||||
<ListItem>
|
||||
<Typography variant="caption" color="textPrimary" className={classes.textDesc}>
|
||||
<Typography variant="caption" color="textPrimary" css={classes.textDesc}>
|
||||
<FormattedMessage id="info.public-visibility" defaultMessage="Publicly Visible" />:
|
||||
</Typography>
|
||||
<Typography variant="body2">{Boolean(map?.isPublic).toString()}</Typography>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import useClasses from '../../../../theme/useStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
export const useStyles = () =>
|
||||
useClasses({
|
||||
textarea: {
|
||||
width: '100%',
|
||||
padding: '15px 15px',
|
||||
@ -11,5 +12,14 @@ export const useStyles = makeStyles(() =>
|
||||
textDesc: {
|
||||
width: '150px',
|
||||
},
|
||||
}),
|
||||
);
|
||||
list: {
|
||||
'& li': {
|
||||
[useTheme().breakpoints.down('sm')]: {
|
||||
display: 'block',
|
||||
},
|
||||
'& p': {
|
||||
marginLeft: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ import Client, { ErrorInfo, Label, MapInfo } from '../../../../classes/client';
|
||||
import { LabelSelector } from '../../maps-list/label-selector';
|
||||
import { activeInstance } from '../../../../redux/clientSlice';
|
||||
import { ChangeLabelMutationFunctionParam, getChangeLabelMutationFunction } from '../../maps-list';
|
||||
import { Interpolation, Theme } from '@emotion/react';
|
||||
|
||||
const LabelDialog = ({ mapsId, onClose }: MultiDialogProps): React.ReactElement => {
|
||||
const intl = useIntl();
|
||||
@ -62,11 +63,11 @@ const LabelDialog = ({ mapsId, onClose }: MultiDialogProps): React.ReactElement
|
||||
id: 'label.description',
|
||||
defaultMessage: 'Use labels to organize your maps.',
|
||||
})}
|
||||
PaperProps={{ classes: { root: classes.paper } }}
|
||||
paperCss={classes.paper}
|
||||
error={error}
|
||||
>
|
||||
<>
|
||||
<Typography variant="body2" marginTop="10px">
|
||||
<Typography variant="body2" marginTop="10px" css={classes.title as Interpolation<Theme>}>
|
||||
<FormattedMessage id="label.add-for" defaultMessage="Editing labels for " />
|
||||
{maps.length > 1 ? (
|
||||
<FormattedMessage
|
||||
|
@ -1,10 +1,13 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import useClasses from '../../../../theme/useStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
export const useStyles = () =>
|
||||
useClasses({
|
||||
paper: {
|
||||
maxWidth: '420px',
|
||||
},
|
||||
}),
|
||||
);
|
||||
title: {
|
||||
maxWidth: '100%',
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
});
|
||||
|
@ -122,7 +122,7 @@ const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElemen
|
||||
/>
|
||||
</Typography>
|
||||
<TextareaAutosize
|
||||
className={classes.textarea}
|
||||
css={classes.textarea}
|
||||
readOnly={true}
|
||||
spellCheck={false}
|
||||
maxRows={6}
|
||||
@ -137,7 +137,7 @@ const PublishDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElemen
|
||||
/>
|
||||
</Typography>
|
||||
<TextareaAutosize
|
||||
className={classes.textarea}
|
||||
css={classes.textarea}
|
||||
readOnly={true}
|
||||
spellCheck={false}
|
||||
maxRows={1}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import useClasses from '../../../../theme/useStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
export const useStyles = () =>
|
||||
useClasses({
|
||||
textarea: {
|
||||
width: '100%',
|
||||
padding: '15px 15px',
|
||||
marging: '0px 10px',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@ -24,6 +24,7 @@ import Typography from '@mui/material/Typography';
|
||||
import { useStyles } from './style';
|
||||
import RoleIcon from '../../role-icon';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import { Interpolation, Theme } from '@emotion/react';
|
||||
|
||||
type ShareModel = {
|
||||
emails: string;
|
||||
@ -138,15 +139,14 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
|
||||
defaultMessage:
|
||||
'Invite people to collaborate with you in the creation of your mindmap. They will be notified by email. ',
|
||||
})}
|
||||
PaperProps={{ classes: { root: classes.paper } }}
|
||||
paperCss={classes.paper}
|
||||
error={error}
|
||||
>
|
||||
<div className={classes.actionContainer}>
|
||||
<div css={classes.actionContainer as Interpolation<Theme>}>
|
||||
<TextField
|
||||
id="emails"
|
||||
name="emails"
|
||||
required={true}
|
||||
style={{ width: '300px' }}
|
||||
size="small"
|
||||
type="email"
|
||||
variant="outlined"
|
||||
@ -154,6 +154,7 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
|
||||
label="Emails"
|
||||
onChange={handleOnChange}
|
||||
value={model.emails}
|
||||
css={[classes.fullWidthInMobile, classes.email]}
|
||||
/>
|
||||
|
||||
<Select
|
||||
@ -161,7 +162,7 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
|
||||
onChange={handleOnChange}
|
||||
value={model.role}
|
||||
name="role"
|
||||
style={{ margin: '0px 10px' }}
|
||||
css={[classes.fullWidthInMobile, classes.role]}
|
||||
>
|
||||
<MenuItem value="editor">
|
||||
<FormattedMessage id="share.can-edit" defaultMessage="Can edit" />
|
||||
@ -202,7 +203,7 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
|
||||
multiline
|
||||
rows={3}
|
||||
maxRows={3}
|
||||
className={classes.textArea}
|
||||
css={classes.textArea}
|
||||
variant="filled"
|
||||
name="message"
|
||||
onChange={handleOnChange}
|
||||
@ -216,13 +217,17 @@ const ShareDialog = ({ mapId, onClose }: SimpleDialogProps): React.ReactElement
|
||||
</div>
|
||||
|
||||
{!isLoading && (
|
||||
<Paper elevation={1} className={classes.listPaper} variant="outlined">
|
||||
<Paper elevation={1} css={classes.listPaper as Interpolation<Theme>} variant="outlined">
|
||||
<List>
|
||||
{permissions &&
|
||||
permissions.map((permission) => {
|
||||
return (
|
||||
<ListItem key={permission.email} role={undefined} dense button>
|
||||
<ListItemText id={permission.email} primary={formatName(permission)} />
|
||||
<ListItemText
|
||||
css={classes.listItemText as Interpolation<Theme>}
|
||||
id={permission.email}
|
||||
primary={formatName(permission)}
|
||||
/>
|
||||
|
||||
<RoleIcon role={permission.role} />
|
||||
<ListItemSecondaryAction>
|
||||
|
@ -1,18 +1,37 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import useClasses from '../../../../theme/useStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
export const useStyles = () =>
|
||||
useClasses({
|
||||
actionContainer: {
|
||||
padding: '10px 0px',
|
||||
border: '1px solid rgba(0, 0, 0, 0.12)',
|
||||
borderRadius: '8px 8px 0px 0px',
|
||||
textAlign: 'center',
|
||||
},
|
||||
fullWidthInMobile: {
|
||||
[useTheme().breakpoints.down('sm')]: {
|
||||
minWidth: '99%',
|
||||
marginBottom: 5,
|
||||
},
|
||||
},
|
||||
email: {
|
||||
[useTheme().breakpoints.up('sm')]: {
|
||||
width: '300px',
|
||||
},
|
||||
},
|
||||
role: {
|
||||
[useTheme().breakpoints.up('sm')]: {
|
||||
margin: '0px 10px',
|
||||
},
|
||||
},
|
||||
textArea: {
|
||||
width: '730px',
|
||||
margin: '5px 0px',
|
||||
padding: '10px',
|
||||
[useTheme().breakpoints.up('sm')]: {
|
||||
width: '730px',
|
||||
margin: '5px 0px',
|
||||
padding: '10px',
|
||||
},
|
||||
},
|
||||
listPaper: {
|
||||
maxHeight: 200,
|
||||
@ -21,6 +40,16 @@ export const useStyles = makeStyles(() =>
|
||||
paper: {
|
||||
width: '850px',
|
||||
minWidth: '850px',
|
||||
[useTheme().breakpoints.down('sm')]: {
|
||||
minWidth: '100%',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
listItemText: {
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
'& span': {
|
||||
display: 'inline',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { ErrorInfo, ReactElement, useEffect } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
@ -20,6 +19,9 @@ import LanguageMenu from './language-menu';
|
||||
import AppI18n, { Locales } from '../../classes/app-i18n';
|
||||
|
||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import ArrowRight from '@mui/icons-material/NavigateNext';
|
||||
import ArrowLeft from '@mui/icons-material/NavigateBefore';
|
||||
|
||||
import AddCircleTwoTone from '@mui/icons-material/AddCircleTwoTone';
|
||||
import CloudUploadTwoTone from '@mui/icons-material/CloudUploadTwoTone';
|
||||
@ -41,7 +43,8 @@ import logoIcon from './logo-small.svg';
|
||||
import poweredByIcon from './pwrdby-white.svg';
|
||||
import LabelDeleteConfirm from './maps-list/label-delete-confirm';
|
||||
import ReactGA from 'react-ga4';
|
||||
import { withStyles } from '@mui/styles';
|
||||
import { CSSObject, Interpolation, Theme } from '@emotion/react';
|
||||
import withEmotionStyles from '../HOCs/withEmotionStyles';
|
||||
|
||||
export type Filter = GenericFilter | LabelFilter;
|
||||
|
||||
@ -61,12 +64,22 @@ interface ToolbarButtonInfo {
|
||||
}
|
||||
|
||||
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 [labelToDelete, setLabelToDelete] = React.useState<number | null>(null);
|
||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
||||
const [desktopOpen, setDesktopOpen] = React.useState(true);
|
||||
const classes = useStyles(desktopOpen);
|
||||
|
||||
const handleMobileDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
|
||||
const handleDesktopDrawerToggle = () => {
|
||||
setDesktopOpen(!desktopOpen);
|
||||
};
|
||||
// Reload based on user preference ...
|
||||
const userLocale = AppI18n.getUserLocale();
|
||||
|
||||
@ -85,6 +98,7 @@ const MapsPage = (): ReactElement => {
|
||||
id: 'maps.page-title',
|
||||
defaultMessage: 'My Maps | WiseMapping',
|
||||
});
|
||||
window.scrollTo(0, 0);
|
||||
ReactGA.send({ hitType: 'pageview', page: window.location.pathname, title: 'Maps List' });
|
||||
}, []);
|
||||
|
||||
@ -101,6 +115,7 @@ const MapsPage = (): ReactElement => {
|
||||
const handleMenuClick = (filter: Filter) => {
|
||||
queryClient.invalidateQueries('maps');
|
||||
setFilter(filter);
|
||||
mobileOpen && setMobileOpen(false);
|
||||
};
|
||||
|
||||
const handleLabelDelete = (id: number) => {
|
||||
@ -148,22 +163,73 @@ const MapsPage = (): ReactElement => {
|
||||
}),
|
||||
);
|
||||
|
||||
const drawerItemsList = (
|
||||
<>
|
||||
<div style={{ padding: '20px 0 20px 16px' }} 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={setLabelToDelete}
|
||||
key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
<div
|
||||
className="poweredByIcon"
|
||||
style={{ position: 'absolute', bottom: '10px', left: '20px' }}
|
||||
key="power-by"
|
||||
>
|
||||
<Link href="http://www.wisemapping.org/">
|
||||
<img src={poweredByIcon} alt="Powered By WiseMapping" />
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const container = document !== undefined ? () => document.body : undefined;
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={userLocale.code}
|
||||
defaultLocale={Locales.EN.code}
|
||||
messages={userLocale.message}
|
||||
>
|
||||
<div className={classes.root}>
|
||||
<div css={classes.root}>
|
||||
<AppBar
|
||||
position="fixed"
|
||||
className={clsx(classes.appBar, {
|
||||
[classes.appBarShift]: open,
|
||||
})}
|
||||
css={[classes.appBar, open && classes.appBarShift]}
|
||||
variant="outlined"
|
||||
elevation={0}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={handleMobileDrawerToggle}
|
||||
sx={{ mr: 2, display: { sm: 'none' } }}
|
||||
id="open-main-drawer"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={handleDesktopDrawerToggle}
|
||||
sx={{ p: 0, mr: 2, display: { xs: 'none', sm: 'inherit' } }}
|
||||
id="open-desktop-drawer"
|
||||
>
|
||||
{!desktopOpen && <ArrowRight />}
|
||||
{desktopOpen && <ArrowLeft />}
|
||||
</IconButton>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
@ -179,10 +245,12 @@ const MapsPage = (): ReactElement => {
|
||||
type="button"
|
||||
disableElevation={true}
|
||||
startIcon={<AddCircleTwoTone />}
|
||||
className={classes.newMapButton}
|
||||
css={classes.newMapButton}
|
||||
onClick={() => setActiveDialog('create')}
|
||||
>
|
||||
<FormattedMessage id="action.new" defaultMessage="New map" />
|
||||
<span className="message">
|
||||
<FormattedMessage id="action.new" defaultMessage="New map" />
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@ -200,10 +268,12 @@ const MapsPage = (): ReactElement => {
|
||||
type="button"
|
||||
disableElevation={true}
|
||||
startIcon={<CloudUploadTwoTone />}
|
||||
className={classes.importButton}
|
||||
css={classes.importButton}
|
||||
onClick={() => setActiveDialog('import')}
|
||||
>
|
||||
<FormattedMessage id="action.import" defaultMessage="Import" />
|
||||
<span className="message">
|
||||
<FormattedMessage id="action.import" defaultMessage="Import" />
|
||||
</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<ActionDispatcher
|
||||
@ -213,7 +283,7 @@ const MapsPage = (): ReactElement => {
|
||||
fromEditor
|
||||
/>
|
||||
|
||||
<div className={classes.rightButtonGroup}>
|
||||
<div css={classes.rightButtonGroup as Interpolation<Theme>}>
|
||||
<LanguageMenu />
|
||||
<HelpMenu />
|
||||
<AccountMenu />
|
||||
@ -221,44 +291,29 @@ const MapsPage = (): ReactElement => {
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
className={clsx(classes.drawer, {
|
||||
[classes.drawerOpen]: open,
|
||||
})}
|
||||
classes={{
|
||||
paper: clsx({
|
||||
[classes.drawerOpen]: open,
|
||||
}),
|
||||
container={container}
|
||||
variant={'temporary'}
|
||||
open={mobileOpen}
|
||||
onClose={handleMobileDrawerToggle}
|
||||
ModalProps={{
|
||||
keepMounted: true,
|
||||
}}
|
||||
css={[classes.mobileDrawer, { '& .MuiPaper-root': classes.drawerOpen }]}
|
||||
>
|
||||
<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={setLabelToDelete}
|
||||
key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
{drawerItemsList}
|
||||
</Drawer>
|
||||
<main className={classes.content}>
|
||||
<div className={classes.toolbar} />
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
css={[
|
||||
classes.drawer as CSSObject,
|
||||
classes.drawerOpen,
|
||||
{ '& .MuiPaper-root': classes.drawerOpen },
|
||||
]}
|
||||
>
|
||||
{drawerItemsList}
|
||||
</Drawer>
|
||||
<main css={classes.content}>
|
||||
<div css={classes.toolbar} />
|
||||
<MapsList filter={filter} />
|
||||
</main>
|
||||
</div>
|
||||
@ -286,24 +341,21 @@ interface ListItemProps {
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/61486061/how-to-set-selected-and-hover-color-of-listitem-in-mui
|
||||
const CustomListItem = withStyles({
|
||||
root: {
|
||||
'&$selected': {
|
||||
backgroundColor: 'rgb(210, 140, 5)',
|
||||
const CustomListItem = withEmotionStyles({
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: 'rgb(210, 140, 5)',
|
||||
color: 'white',
|
||||
'& .MuiListItemIcon-root': {
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
'&.Mui-selected:hover': {
|
||||
backgroundColor: 'rgb(210, 140, 5)',
|
||||
color: 'white',
|
||||
'& .MuiListItemIcon-root': {
|
||||
color: 'white',
|
||||
'& .MuiListItemIcon-root': {
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
'&$selected:hover': {
|
||||
backgroundColor: 'rgb(210, 140, 5)',
|
||||
color: 'white',
|
||||
'& .MuiListItemIcon-root': {
|
||||
color: 'white',
|
||||
},
|
||||
},
|
||||
},
|
||||
selected: {},
|
||||
})(ListItemButton);
|
||||
|
||||
const StyleListItem = (props: ListItemProps) => {
|
||||
@ -336,7 +388,9 @@ const StyleListItem = (props: ListItemProps) => {
|
||||
|
||||
return (
|
||||
<CustomListItem selected={isSelected} onClick={(e) => handleOnClick(e, filter)}>
|
||||
<ListItemIcon>{icon}</ListItemIcon>
|
||||
<Tooltip title={label} disableInteractive>
|
||||
<ListItemIcon>{icon}</ListItemIcon>
|
||||
</Tooltip>
|
||||
<ListItemText style={{ color: 'white' }} primary={label} />
|
||||
{filter.type == 'label' && (
|
||||
<ListItemSecondaryAction>
|
||||
|
@ -16,6 +16,8 @@ import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { mobileAppbarButton } from '../style';
|
||||
|
||||
const LanguageMenu = (): React.ReactElement => {
|
||||
const queryClient = useQueryClient();
|
||||
@ -25,6 +27,8 @@ const LanguageMenu = (): React.ReactElement => {
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const smMediaQuery = theme.breakpoints.down('sm');
|
||||
|
||||
// Todo: For some reasons, in some situations locale is null. More research needed.
|
||||
const mutation = useMutation(
|
||||
@ -68,11 +72,14 @@ const LanguageMenu = (): React.ReactElement => {
|
||||
variant="outlined"
|
||||
disableElevation={true}
|
||||
color="primary"
|
||||
css={{
|
||||
[smMediaQuery]: mobileAppbarButton,
|
||||
}}
|
||||
style={{ borderColor: 'gray', color: 'gray' }}
|
||||
onClick={handleMenu}
|
||||
startIcon={<TranslateTwoTone style={{ color: 'inherit' }} />}
|
||||
>
|
||||
{userLocale.label}
|
||||
<span className="message">{userLocale.label}</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 3.4 KiB |
@ -29,6 +29,8 @@ import InputBase from '@mui/material/InputBase';
|
||||
import Link from '@mui/material/Link';
|
||||
|
||||
import DeleteOutlined from '@mui/icons-material/DeleteOutlined';
|
||||
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import StarRateRoundedIcon from '@mui/icons-material/StarRateRounded';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
@ -38,6 +40,12 @@ import { LabelsCell } from './labels-cell';
|
||||
import LocalizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import AppI18n from '../../../classes/app-i18n';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
import { CSSObject, Interpolation, Theme } from '@emotion/react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import CardHeader from '@mui/material/CardHeader';
|
||||
|
||||
dayjs.extend(LocalizedFormat);
|
||||
dayjs.extend(relativeTime);
|
||||
@ -134,7 +142,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
||||
padding="checkbox"
|
||||
key="select"
|
||||
style={{ width: '20px' }}
|
||||
className={classes.headerCell}
|
||||
css={classes.headerCell}
|
||||
>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
@ -145,7 +153,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell padding="checkbox" key="starred" className={classes.headerCell}></TableCell>
|
||||
<TableCell padding="checkbox" key="starred" css={classes.headerCell}></TableCell>
|
||||
|
||||
{headCells.map((headCell) => {
|
||||
return (
|
||||
@ -153,7 +161,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
||||
key={headCell.id}
|
||||
sortDirection={orderBy === headCell.id ? order : false}
|
||||
style={headCell.style}
|
||||
className={classes.headerCell}
|
||||
css={classes.headerCell}
|
||||
>
|
||||
<TableSortLabel
|
||||
active={orderBy === headCell.id}
|
||||
@ -163,7 +171,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
||||
{headCell.label}
|
||||
|
||||
{orderBy === headCell.id && (
|
||||
<span className={classes.visuallyHidden}>
|
||||
<span css={classes.visuallyHidden as Interpolation<Theme>}>
|
||||
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
||||
</span>
|
||||
)}
|
||||
@ -172,7 +180,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
|
||||
);
|
||||
})}
|
||||
|
||||
<TableCell padding="checkbox" key="action" className={classes.headerCell}></TableCell>
|
||||
<TableCell padding="checkbox" key="action" css={classes.headerCell}></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
@ -334,7 +342,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
mapId: mapId,
|
||||
el: event.currentTarget,
|
||||
});
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
};
|
||||
|
||||
@ -364,7 +372,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
);
|
||||
|
||||
const handleStarred = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>, id: number) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
starredMultation.mutate(id);
|
||||
};
|
||||
|
||||
@ -423,16 +431,16 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
|
||||
const isSelected = (id: number) => selected.indexOf(id) !== -1;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div css={classes.root}>
|
||||
<ActionChooser
|
||||
anchor={activeRowAction?.el}
|
||||
onClose={handleActionMenuClose}
|
||||
mapId={activeRowAction?.mapId}
|
||||
/>
|
||||
|
||||
<Paper className={classes.paper} elevation={0}>
|
||||
<Toolbar className={classes.toolbar} variant="dense">
|
||||
<div className={classes.toolbarActions}>
|
||||
<Paper css={classes.paper} elevation={0}>
|
||||
<Toolbar css={classes.toolbar} variant="dense">
|
||||
<div css={classes.toolbarActions}>
|
||||
{selected.length > 0 && (
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
@ -479,9 +487,24 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={classes.toolbarListActions}>
|
||||
<div>
|
||||
<div css={classes.search as Interpolation<Theme>}>
|
||||
<div css={classes.searchIcon as Interpolation<Theme>}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<InputBase
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'maps.search-action',
|
||||
defaultMessage: 'Search ...',
|
||||
})}
|
||||
css={[classes.searchInputRoot, classes.searchInputInput]}
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
onChange={handleOnSearchChange}
|
||||
// startAdornment={<SearchIcon />}
|
||||
/>
|
||||
</div>
|
||||
<TablePagination
|
||||
style={{ float: 'right', border: '0', paddingBottom: '5px' }}
|
||||
css={classes.tablePagination as Interpolation<Theme>}
|
||||
count={mapsInfo.length}
|
||||
rowsPerPageOptions={[]}
|
||||
rowsPerPage={rowsPerPage}
|
||||
@ -490,29 +513,108 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
component="div"
|
||||
/>
|
||||
|
||||
<div className={classes.search}>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<InputBase
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'maps.search-action',
|
||||
defaultMessage: 'Search ...',
|
||||
})}
|
||||
classes={{
|
||||
root: classes.searchInputRoot,
|
||||
input: classes.searchInputInput,
|
||||
}}
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
onChange={handleOnSearchChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Toolbar>
|
||||
|
||||
<TableContainer>
|
||||
<Table className={classes.table} size="small" stickyHeader>
|
||||
<Box css={classes.cards}>
|
||||
{isLoading ? (
|
||||
<Card>
|
||||
<CardContent>Loading ...</CardContent>
|
||||
</Card>
|
||||
) : mapsInfo.length == 0 ? (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<FormattedMessage
|
||||
id="maps.empty-result"
|
||||
defaultMessage="No matching mindmap found with the current filter criteria."
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
stableSort(mapsInfo, getComparator(order, orderBy))
|
||||
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
.map((row: MapInfo) => {
|
||||
return (
|
||||
<Card key={row.id} css={{ maxWidth: '94vw', margin: '3vw' }}>
|
||||
<Link
|
||||
href={`/c/maps/${row.id}/edit`}
|
||||
underline="none"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<CardHeader
|
||||
css={classes.cardHeader}
|
||||
avatar={
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
id: 'maps.tooltip-starred',
|
||||
defaultMessage: 'Starred',
|
||||
})}
|
||||
>
|
||||
<div className="hola" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton size="small" onClick={(e) => handleStarred(e, row.id)}>
|
||||
<StarRateRoundedIcon
|
||||
color="action"
|
||||
style={{
|
||||
color: row.starred ? 'yellow' : 'gray',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
action={
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
id: 'map.more-actions',
|
||||
defaultMessage: 'More Actions',
|
||||
})}
|
||||
>
|
||||
<IconButton aria-label="settings" onClick={handleActionClick(row.id)}>
|
||||
<MoreVertIcon color="action" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
title={
|
||||
<Typography sx={{ fontSize: 'large' }} noWrap color="text.secondary">
|
||||
{row.title}
|
||||
</Typography>
|
||||
}
|
||||
subheader={
|
||||
<Typography variant="subtitle2">
|
||||
{intl.formatMessage({
|
||||
id: 'map.last-update',
|
||||
defaultMessage: 'Last Update',
|
||||
})}
|
||||
<span>: </span>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
id: 'maps.modified-by-desc',
|
||||
defaultMessage: 'Modified by {by} on {on}',
|
||||
},
|
||||
{
|
||||
by: row.lastModificationBy,
|
||||
on: dayjs(row.lastModificationTime).format('lll'),
|
||||
},
|
||||
)}
|
||||
placement="bottom-start"
|
||||
>
|
||||
<span>{dayjs(row.lastModificationTime).fromNow()}</span>
|
||||
</Tooltip>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
</Card>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Box>
|
||||
<Table css={classes.table} size="small" stickyHeader>
|
||||
<EnhancedTableHead
|
||||
classes={classes}
|
||||
numSelected={selected.length}
|
||||
@ -555,7 +657,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
selected={isItemSelected}
|
||||
style={{ border: '0' }}
|
||||
>
|
||||
<TableCell padding="checkbox" className={classes.bodyCell}>
|
||||
<TableCell padding="checkbox" css={classes.bodyCell}>
|
||||
<Checkbox
|
||||
checked={isItemSelected}
|
||||
inputProps={{
|
||||
@ -565,7 +667,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell padding="checkbox" className={classes.bodyCell}>
|
||||
<TableCell padding="checkbox" css={classes.bodyCell}>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
@ -584,7 +686,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
<TableCell css={classes.bodyCell}>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
@ -604,7 +706,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={[classes.bodyCell, classes.labelsCell].join(' ')}>
|
||||
<TableCell css={[classes.bodyCell, classes.labelsCell as CSSObject]}>
|
||||
<LabelsCell
|
||||
labels={row.labels}
|
||||
onDelete={(lbl) => {
|
||||
@ -613,9 +715,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>{row.createdBy}</TableCell>
|
||||
<TableCell css={classes.bodyCell}>{row.createdBy}</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
<TableCell css={classes.bodyCell}>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage(
|
||||
@ -634,7 +736,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
<TableCell css={classes.bodyCell}>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
|
@ -1,28 +1,37 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { alpha, useTheme } from '@mui/material/styles';
|
||||
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import useClasses from '../../../theme/useStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
export const useStyles = () => {
|
||||
const theme = useTheme();
|
||||
const smMediaQuery = theme.breakpoints.down('sm');
|
||||
|
||||
return useClasses({
|
||||
root: {
|
||||
width: '100%',
|
||||
},
|
||||
paper: {
|
||||
width: '100%',
|
||||
marginBottom: useTheme().spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
cards: {
|
||||
display: 'none',
|
||||
[smMediaQuery]: {
|
||||
display: 'block',
|
||||
},
|
||||
},
|
||||
table: {
|
||||
[smMediaQuery]: {
|
||||
display: 'none',
|
||||
},
|
||||
minWidth: 750,
|
||||
'& tr:nth-child(even)': {
|
||||
'& tr:nth-of-type(2n)': {
|
||||
background: 'white',
|
||||
},
|
||||
'& tr:nth-child(odd)': {
|
||||
'& tr:nth-of-type(2n+1)': {
|
||||
background: 'rgba(221, 221, 221, 0.35)',
|
||||
},
|
||||
// '&:hover tr': {
|
||||
// backgroundColor: 'rgba(150, 150, 150, 0.7)',
|
||||
// }
|
||||
},
|
||||
headerCell: {
|
||||
background: 'white',
|
||||
@ -61,22 +70,31 @@ export const useStyles = makeStyles(() =>
|
||||
flexGrow: 1,
|
||||
paddingLeft: '23px;',
|
||||
},
|
||||
toolbarListActions: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
search: {
|
||||
borderRadius: 9,
|
||||
backgroundColor: alpha(useTheme().palette.common.white, 0.15),
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.15),
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(useTheme().palette.common.white, 0.25),
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.25),
|
||||
},
|
||||
margin: '10px 0px',
|
||||
width: '100%',
|
||||
[useTheme().breakpoints.up('sm')]: {
|
||||
marginLeft: useTheme().spacing(1),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
marginLeft: theme.spacing(1),
|
||||
width: 'auto',
|
||||
},
|
||||
float: 'left',
|
||||
[smMediaQuery]: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
tablePagination: {
|
||||
float: 'right',
|
||||
border: '0',
|
||||
paddingBottom: '5px',
|
||||
[smMediaQuery]: {
|
||||
width: '50%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
padding: '6px 0 0 5px',
|
||||
@ -93,19 +111,22 @@ export const useStyles = makeStyles(() =>
|
||||
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 + ${useTheme().spacing(4)})`,
|
||||
transition: useTheme().transitions.create('width'),
|
||||
width: '100%',
|
||||
[useTheme().breakpoints.up('sm')]: {
|
||||
width: '12ch',
|
||||
'&:focus': {
|
||||
width: '20ch',
|
||||
'& .MuiInputBase-input': {
|
||||
border: '1px solid #ffa800',
|
||||
borderRadius: 4,
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
||||
transition: theme.transitions.create('width'),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: '12ch',
|
||||
'&:focus': {
|
||||
width: '20ch',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
cardHeader: {
|
||||
padding: '4px',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -1,10 +1,45 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { CSSObject } from '@emotion/react';
|
||||
import { Theme, useTheme } from '@mui/material/styles';
|
||||
import useClasses from '../../theme/useStyles';
|
||||
|
||||
const drawerWidth = 300;
|
||||
const openedMixin = (theme: Theme, drawerWidth): CSSObject => ({
|
||||
width: drawerWidth,
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
});
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
const closedMixin = (theme: Theme): CSSObject => ({
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
overflowX: 'hidden',
|
||||
width: `calc(${theme.spacing(7)} + 1px)`,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: `calc(${theme.spacing(8)} + 1px)`,
|
||||
},
|
||||
});
|
||||
|
||||
export const mobileAppbarButton = {
|
||||
padding: 0,
|
||||
minWidth: 'unset',
|
||||
'& .message': {
|
||||
display: 'none',
|
||||
},
|
||||
'& .MuiButton-startIcon': {
|
||||
margin: 0,
|
||||
padding: 10,
|
||||
},
|
||||
};
|
||||
export function useStyles(drawerOpen) {
|
||||
const drawerWidth = drawerOpen ? 300 : 66;
|
||||
const theme = useTheme();
|
||||
const smMediaQuery = theme.breakpoints.down('sm');
|
||||
return useClasses({
|
||||
root: {
|
||||
display: 'flex',
|
||||
},
|
||||
@ -14,29 +49,70 @@ export const useStyles = makeStyles(() =>
|
||||
appBarShift: {
|
||||
marginLeft: drawerWidth,
|
||||
width: `calc(100% - ${drawerWidth}px)`,
|
||||
[smMediaQuery]: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
newMapButton: {
|
||||
marginRight: 10,
|
||||
minWidth: '130px',
|
||||
[smMediaQuery]: mobileAppbarButton,
|
||||
},
|
||||
importButton: {
|
||||
marginRight: 10,
|
||||
minWidth: '130px',
|
||||
[smMediaQuery]: mobileAppbarButton,
|
||||
},
|
||||
rightButtonGroup: {
|
||||
marginRight: 10,
|
||||
flexGrow: 10,
|
||||
textAlign: 'right',
|
||||
minWidth: '280px',
|
||||
[smMediaQuery]: {
|
||||
minWidth: 'unset',
|
||||
marginRight: 0,
|
||||
},
|
||||
},
|
||||
drawer: {
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[smMediaQuery]: {
|
||||
display: 'none',
|
||||
},
|
||||
whiteSpace: 'nowrap',
|
||||
boxSizing: 'border-box',
|
||||
...(drawerOpen && {
|
||||
...openedMixin(theme, drawerWidth),
|
||||
'& .MuiDrawer-paper': openedMixin(theme, drawerWidth),
|
||||
}),
|
||||
...(!drawerOpen && {
|
||||
...closedMixin(theme),
|
||||
'& .MuiDrawer-paper': closedMixin(theme),
|
||||
}),
|
||||
'& .MuiListItemText-root': {
|
||||
display: 'block',
|
||||
...(!drawerOpen && {
|
||||
color: 'transparent',
|
||||
'& span': { color: 'transparent' },
|
||||
}),
|
||||
},
|
||||
'& .MuiListItemSecondaryAction-root, & .poweredByIcon': {
|
||||
display: 'block',
|
||||
...(!drawerOpen && { display: 'none' }),
|
||||
},
|
||||
},
|
||||
mobileDrawer: {
|
||||
display: 'none',
|
||||
[smMediaQuery]: {
|
||||
display: 'block',
|
||||
},
|
||||
},
|
||||
drawerOpen: {
|
||||
background: '#ffa800',
|
||||
width: drawerWidth,
|
||||
[smMediaQuery]: {
|
||||
width: 300,
|
||||
},
|
||||
},
|
||||
toolbar: {
|
||||
display: 'flex',
|
||||
@ -47,5 +123,5 @@ export const useStyles = makeStyles(() =>
|
||||
flexGrow: 1,
|
||||
padding: '24px 0px',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
20
packages/webapp/src/theme/useStyles.ts
Normal file
20
packages/webapp/src/theme/useStyles.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { css } from '@emotion/react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
const getStyle = (value) => {
|
||||
return css(value);
|
||||
};
|
||||
|
||||
function useClasses<T>(stylesElement: T): T {
|
||||
const theme = useTheme();
|
||||
const rawClasses = typeof stylesElement === 'function' ? stylesElement(theme) : stylesElement;
|
||||
const prepared = {};
|
||||
|
||||
Object.entries(rawClasses).forEach(([key, value]) => {
|
||||
prepared[key] = getStyle(value);
|
||||
});
|
||||
|
||||
return prepared as T;
|
||||
}
|
||||
|
||||
export default useClasses;
|
Loading…
Reference in New Issue
Block a user