diff --git a/packages/editor/src/classes/default-widget-manager/index.ts b/packages/editor/src/classes/default-widget-manager/index.ts index 5ae0159f..d8920721 100644 --- a/packages/editor/src/classes/default-widget-manager/index.ts +++ b/packages/editor/src/classes/default-widget-manager/index.ts @@ -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]; } } diff --git a/packages/editor/src/components/action-widget/pane/save-and-delete/index.tsx b/packages/editor/src/components/action-widget/pane/save-and-delete/index.tsx index 19bce58c..6371f701 100644 --- a/packages/editor/src/components/action-widget/pane/save-and-delete/index.tsx +++ b/packages/editor/src/components/action-widget/pane/save-and-delete/index.tsx @@ -34,10 +34,6 @@ const SaveAndDelete = (props: { - - {props.model.getValue() && props.model.getValue().trim() !== '' && ( { diff --git a/packages/editor/src/components/action-widget/pane/topic-link/index.tsx b/packages/editor/src/components/action-widget/pane/topic-link/index.tsx index 6d571d1b..d529523a 100644 --- a/packages/editor/src/components/action-widget/pane/topic-link/index.tsx +++ b/packages/editor/src/components/action-widget/pane/topic-link/index.tsx @@ -59,7 +59,7 @@ const TopicLink = (props: { const isValidUrl = !url || checkURL(url); return ( - + ), }} - sx={{ pr: 1 }} + margin="dense" > +
{ const [model, setModel] = useState(); @@ -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(); - 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 ( - - - + + - - {widgetManager.getEditorContent()} - + + + setPopoverOpen(false)} aria-label={'Close'}> + + + + {widgetManager.getEditorContent()} + - - + + - + - - + + - {!model?.isMapLoadded() && ( - - - - )} - - + {!model?.isMapLoadded() && ( + + + + )} + ); }; export default Editor; diff --git a/packages/editor/src/components/toolbar/index.tsx b/packages/editor/src/components/toolbar/index.tsx index c4741eca..a5c817b0 100644 --- a/packages/editor/src/components/toolbar/index.tsx +++ b/packages/editor/src/components/toolbar/index.tsx @@ -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: { }} > setOpen(true) }} + configuration={{ + ...props.configuration, + onClick: () => setOpen(true), + selected: () => open, + }} /> + {props.configuration.useClickToClose && ( + + setOpen(false)} aria-label={'Close'}> + + + + )}
e.stopPropagation()}> {props.configuration.options?.map((o, i) => { if (o?.visible === false) { diff --git a/packages/editor/test/unit/toolbar/toolbar.test.tsx b/packages/editor/test/unit/toolbar/toolbar.test.tsx index b106e0e6..ceb5c309 100644 --- a/packages/editor/test/unit/toolbar/toolbar.test.tsx +++ b/packages/editor/test/unit/toolbar/toolbar.test.tsx @@ -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: , + 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(); + 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(); + 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', () => { diff --git a/packages/webapp/.eslintrc.json b/packages/webapp/.eslintrc.json index 257c1652..3bdfb583 100644 --- a/packages/webapp/.eslintrc.json +++ b/packages/webapp/.eslintrc.json @@ -40,6 +40,7 @@ "!@mui/material/test-utils/*" ] } - ] + ], + "react/no-unknown-property": ["error", { "ignore": ["css"] }] } } \ No newline at end of file diff --git a/packages/webapp/cypress/e2e/editor.cy.ts b/packages/webapp/cypress/e2e/editor.cy.ts index d3281877..66ab0f1c 100644 --- a/packages/webapp/cypress/e2e/editor.cy.ts +++ b/packages/webapp/cypress/e2e/editor.cy.ts @@ -1,6 +1,6 @@ context('Editor Page', () => { beforeEach(() => { - cy.visit('c/maps/11/edit'); + cy.visit('/c/maps/11/edit'); }); it('page loaded', () => { diff --git a/packages/webapp/cypress/e2e/maps.cy.ts b/packages/webapp/cypress/e2e/maps.cy.ts index 6b61cf37..42aaa302 100644 --- a/packages/webapp/cypress/e2e/maps.cy.ts +++ b/packages/webapp/cypress/e2e/maps.cy.ts @@ -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'); + }); +}); diff --git a/packages/webapp/lang/es.json b/packages/webapp/lang/es.json index 1eed4da1..8fee199d 100644 --- a/packages/webapp/lang/es.json +++ b/packages/webapp/lang/es.json @@ -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" }, diff --git a/packages/webapp/package.json b/packages/webapp/package.json index d00859ac..923cf887 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -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", diff --git a/packages/webapp/src/app.tsx b/packages/webapp/src/app.tsx index d1b1f9ce..81071ce6 100644 --- a/packages/webapp/src/app.tsx +++ b/packages/webapp/src/app.tsx @@ -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 ? ( @@ -64,42 +58,67 @@ const App = (): ReactElement => { messages={locale.message as Record} > - - - - - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - - -
- } - > - - - } - /> - } - /> - } /> - - - + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } + /> + + + + } + > + + + } + /> + + + + } + > + + + } + /> + + + + } + > + + + } + /> + + + + diff --git a/packages/webapp/src/compiled-lang/en.json b/packages/webapp/src/compiled-lang/en.json index 1ec2fdce..0422bdb1 100644 --- a/packages/webapp/src/compiled-lang/en.json +++ b/packages/webapp/src/compiled-lang/en.json @@ -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": [ diff --git a/packages/webapp/src/compiled-lang/es.json b/packages/webapp/src/compiled-lang/es.json index d905e643..0f01bd73 100644 --- a/packages/webapp/src/compiled-lang/es.json +++ b/packages/webapp/src/compiled-lang/es.json @@ -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, diff --git a/packages/webapp/src/components/HOCs/withEmotionStyles.tsx b/packages/webapp/src/components/HOCs/withEmotionStyles.tsx new file mode 100644 index 00000000..61fcec04 --- /dev/null +++ b/packages/webapp/src/components/HOCs/withEmotionStyles.tsx @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import React, { ComponentType } from 'react'; + +function withEmotionStyles(css) { + return (Component: ComponentType) => { + const WithEmotionStyles = (hocProps): React.ReactElement => { + return ; + }; + WithEmotionStyles.displayName = `withEmotionStyles(${getDisplayName(Component)})`; + return WithEmotionStyles; + }; +} + +function getDisplayName(WrappedComponent) { + return WrappedComponent.displayName || WrappedComponent.name || 'Component'; +} + +export default withEmotionStyles; diff --git a/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts b/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts index ee2fc406..052dc802 100644 --- a/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts +++ b/packages/webapp/src/components/editor-page/PersistenceManagerUtils.ts @@ -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 => { let mindmap: Mindmap; diff --git a/packages/webapp/src/components/editor-page/index.tsx b/packages/webapp/src/components/editor-page/index.tsx index d2605a48..7f5be4c3 100644 --- a/packages/webapp/src/components/editor-page/index.tsx +++ b/packages/webapp/src/components/editor-page/index.tsx @@ -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(null); @@ -144,4 +197,4 @@ const EditorPage = ({ isTryMode }: EditorPropsType): React.ReactElement => { ); }; -export default EditorPage; +export default withSessionExpirationHandling(EditorPage); diff --git a/packages/webapp/src/components/forgot-password-page/index.tsx b/packages/webapp/src/components/forgot-password-page/index.tsx index 3840086c..0d194c8a 100644 --- a/packages/webapp/src/components/forgot-password-page/index.tsx +++ b/packages/webapp/src/components/forgot-password-page/index.tsx @@ -69,7 +69,7 @@ const ForgotPassword = () => { } return ( - + diff --git a/packages/webapp/src/components/form/global-error/styled.ts b/packages/webapp/src/components/form/global-error/styled.ts index 55e0d184..12812361 100644 --- a/packages/webapp/src/components/form/global-error/styled.ts +++ b/packages/webapp/src/components/form/global-error/styled.ts @@ -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; diff --git a/packages/webapp/src/components/layout/form-container/index.tsx b/packages/webapp/src/components/layout/form-container/index.tsx index 26cb83ff..239b64e0 100644 --- a/packages/webapp/src/components/layout/form-container/index.tsx +++ b/packages/webapp/src/components/layout/form-container/index.tsx @@ -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; diff --git a/packages/webapp/src/components/maps-page/action-chooser/index.tsx b/packages/webapp/src/components/maps-page/action-chooser/index.tsx index 833e2cb5..6f002a51 100644 --- a/packages/webapp/src/components/maps-page/action-chooser/index.tsx +++ b/packages/webapp/src/components/maps-page/action-chooser/index.tsx @@ -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 ( void; @@ -21,7 +21,7 @@ export type DialogProps = { submitButton?: string; actionUrl?: string; maxWidth?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | false; - PaperProps?: Partial; + 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) => { 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} >
diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/style.ts b/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/style.ts index 08bcbb7b..61872792 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/style.ts +++ b/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/style.ts @@ -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); diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx index 6f3e6f46..7bc2b14d 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/export-dialog/index.tsx @@ -185,7 +185,7 @@ const ExportDialog = ({ } @@ -203,20 +203,20 @@ const ExportDialog = ({ onChange={handleOnExportFormatChange} variant="outlined" value={exportFormat} - className={classes.select} + css={classes.select} > - + Scalable Vector Graphics (SVG) - + Portable Network Graphics (PNG) - + JPEG Image (JPEG) } label={intl.formatMessage({ id: 'export.img-center', @@ -229,7 +229,7 @@ const ExportDialog = ({ } label={intl.formatMessage({ @@ -244,12 +244,12 @@ const ExportDialog = ({ onChange={handleOnExportFormatChange} variant="outlined" value={exportFormat} - className={classes.select} + css={classes.select} > - + Plain Text File (TXT) - + Markdown (MD) {/* @@ -261,7 +261,7 @@ const ExportDialog = ({ } label={intl.formatMessage({ @@ -275,13 +275,13 @@ const ExportDialog = ({ @@ -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 {!isLoading && ( - + } variant="outlined"> {permissions && permissions.map((permission) => { return ( - + } + id={permission.email} + primary={formatName(permission)} + /> diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/share-dialog/style.ts b/packages/webapp/src/components/maps-page/action-dispatcher/share-dialog/style.ts index 1a9a591b..4364e237 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/share-dialog/style.ts +++ b/packages/webapp/src/components/maps-page/action-dispatcher/share-dialog/style.ts @@ -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', + }, + }, + }); diff --git a/packages/webapp/src/components/maps-page/index.tsx b/packages/webapp/src/components/maps-page/index.tsx index 683c432a..57ed4fe5 100644 --- a/packages/webapp/src/components/maps-page/index.tsx +++ b/packages/webapp/src/components/maps-page/index.tsx @@ -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({ type: 'all' }); const client: Client = useSelector(activeInstance); const queryClient = useQueryClient(); const [activeDialog, setActiveDialog] = React.useState(undefined); const [labelToDelete, setLabelToDelete] = React.useState(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 = ( + <> +
+ logo +
+ + {filterButtons.map((buttonInfo) => { + return ( + + ); + })} + +
+ + Powered By WiseMapping + +
+ + ); + + const container = document !== undefined ? () => document.body : undefined; + return ( -
+
+ + + + + {!desktopOpen && } + {desktopOpen && } + { type="button" disableElevation={true} startIcon={} - className={classes.newMapButton} + css={classes.newMapButton} onClick={() => setActiveDialog('create')} > - + + + @@ -200,10 +268,12 @@ const MapsPage = (): ReactElement => { type="button" disableElevation={true} startIcon={} - className={classes.importButton} + css={classes.importButton} onClick={() => setActiveDialog('import')} > - + + + { fromEditor /> -
+
}> @@ -221,44 +291,29 @@ const MapsPage = (): ReactElement => { -
- logo -
- - - {filterButtons.map((buttonInfo) => { - return ( - - ); - })} - - -
- - Powered By WiseMapping - -
+ {drawerItemsList}
-
-
+ + {drawerItemsList} + +
+
@@ -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 ( handleOnClick(e, filter)}> - {icon} + + {icon} + {filter.type == 'label' && ( diff --git a/packages/webapp/src/components/maps-page/language-menu/index.tsx b/packages/webapp/src/components/maps-page/language-menu/index.tsx index 517cb80c..e8701aef 100644 --- a/packages/webapp/src/components/maps-page/language-menu/index.tsx +++ b/packages/webapp/src/components/maps-page/language-menu/index.tsx @@ -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={} > - {userLocale.label} + {userLocale.label} - - - + + + + + + - - + + - - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/packages/webapp/src/components/maps-page/maps-list/index.tsx b/packages/webapp/src/components/maps-page/maps-list/index.tsx index 6b4e93ca..acf70f9d 100644 --- a/packages/webapp/src/components/maps-page/maps-list/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/index.tsx @@ -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} > 0 && numSelected < rowCount} @@ -145,7 +153,7 @@ function EnhancedTableHead(props: EnhancedTableProps) { /> - + {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} > + }> {order === 'desc' ? 'sorted descending' : 'sorted ascending'} )} @@ -172,7 +180,7 @@ function EnhancedTableHead(props: EnhancedTableProps) { ); })} - + ); @@ -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, 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 ( -
+
- - -
+ + +
{selected.length > 0 && ( { )}
-
+
+
}> +
}> + +
+ } + /> +
} count={mapsInfo.length} rowsPerPageOptions={[]} rowsPerPage={rowsPerPage} @@ -490,29 +513,108 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { onRowsPerPageChange={handleChangeRowsPerPage} component="div" /> - -
-
- -
- -
- + + {isLoading ? ( + + Loading ... + + ) : mapsInfo.length == 0 ? ( + + + + + + ) : ( + stableSort(mapsInfo, getComparator(order, orderBy)) + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row: MapInfo) => { + return ( + + e.stopPropagation()} + > + +
e.stopPropagation()}> + handleStarred(e, row.id)}> + + +
+ + } + action={ + + + + + + } + title={ + + {row.title} + + } + subheader={ + + {intl.formatMessage({ + id: 'map.last-update', + defaultMessage: 'Last Update', + })} + : + + {dayjs(row.lastModificationTime).fromNow()} + + + } + /> + +
+ ); + }) + )} +
+
{ selected={isItemSelected} style={{ border: '0' }} > - + { /> - + { - + { - + { @@ -613,9 +715,9 @@ export const MapsList = (props: MapsListProps): React.ReactElement => { /> - {row.createdBy} + {row.createdBy} - + { - + - 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', + }, + }); +}; diff --git a/packages/webapp/src/components/maps-page/style.ts b/packages/webapp/src/components/maps-page/style.ts index 86ef8598..7bef83bd 100644 --- a/packages/webapp/src/components/maps-page/style.ts +++ b/packages/webapp/src/components/maps-page/style.ts @@ -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', }, - }), -); + }); +} diff --git a/packages/webapp/src/theme/useStyles.ts b/packages/webapp/src/theme/useStyles.ts new file mode 100644 index 00000000..e702dbc8 --- /dev/null +++ b/packages/webapp/src/theme/useStyles.ts @@ -0,0 +1,20 @@ +import { css } from '@emotion/react'; +import { useTheme } from '@emotion/react'; + +const getStyle = (value) => { + return css(value); +}; + +function useClasses(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;