From abdf2bf5ac1fed9fdd6e95b79b37a22432908fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20B=C3=A9gaudeau?= Date: Fri, 19 Feb 2021 12:11:25 +0100 Subject: [PATCH] =?UTF-8?q?[cleanup]=C2=A0Make=20the=20navbar=20used=20exp?= =?UTF-8?q?licit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Views are not handling the lifecycle of their navbar and it creates numerous issues since it complexify the lifecycle of the navbars. On top of that, it degrades the user experience because everyone starts the creation of a new view by using the View component without taking into account whether it's relevant or not. Signed-off-by: Stéphane Bégaudeau --- frontend/src/navbar/ProjectNavbar.tsx | 32 ++++++++ frontend/src/stories/view/ViewStory.tsx | 13 +--- frontend/src/views/modelers/ModelersView.tsx | 69 +++++++++++------ .../src/views/new-modeler/NewModelerView.tsx | 77 ++++++++++++------- .../src/views/new-project/NewProjectView.tsx | 77 ++++++++++++------- .../upload-project/UploadProjectView.tsx | 54 ++++++++----- 6 files changed, 211 insertions(+), 111 deletions(-) create mode 100644 frontend/src/navbar/ProjectNavbar.tsx diff --git a/frontend/src/navbar/ProjectNavbar.tsx b/frontend/src/navbar/ProjectNavbar.tsx new file mode 100644 index 0000000000..005e5a2622 --- /dev/null +++ b/frontend/src/navbar/ProjectNavbar.tsx @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2021 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { useBranding } from 'common/BrandingContext'; +import { Logo } from 'navbar/Logo'; +import { Title } from 'navbar/Title'; +import React from 'react'; +import styles from './LoggedInNavbar.module.css'; + +export const ProjectNavbar = () => { + const { productName, userStatus } = useBranding(); + return ( +
+
+
+ + + </div> + <div className={styles.rightArea}>{userStatus}</div> + </div> + </div> + ); +}; diff --git a/frontend/src/stories/view/ViewStory.tsx b/frontend/src/stories/view/ViewStory.tsx index 1b5810fa57..c531ba5576 100644 --- a/frontend/src/stories/view/ViewStory.tsx +++ b/frontend/src/stories/view/ViewStory.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Obeo. + * Copyright (c) 2019, 2021 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -14,7 +14,6 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Border } from 'stories/common/Border'; import { View } from 'views/View'; - import styles from './ViewStory.module.css'; export const ViewStory = () => { @@ -30,16 +29,6 @@ export const ViewStory = () => { </MemoryRouter> </Border> </div> - <div className={styles.condensed}> - Condensed view - <Border> - <MemoryRouter> - <View condensed> - <div className={styles.placeholder}></div> - </View> - </MemoryRouter> - </Border> - </div> </div> ); }; diff --git a/frontend/src/views/modelers/ModelersView.tsx b/frontend/src/views/modelers/ModelersView.tsx index 3e17357f32..6ea939a17a 100644 --- a/frontend/src/views/modelers/ModelersView.tsx +++ b/frontend/src/views/modelers/ModelersView.tsx @@ -12,6 +12,7 @@ *******************************************************************************/ import { useQuery } from '@apollo/client'; import Button from '@material-ui/core/Button'; +import Container from '@material-ui/core/Container'; import Grid from '@material-ui/core/Grid'; import IconButton from '@material-ui/core/IconButton'; import Link from '@material-ui/core/Link'; @@ -35,13 +36,14 @@ import EditIcon from '@material-ui/icons/Edit'; import MoreHorizIcon from '@material-ui/icons/MoreHoriz'; import PublishIcon from '@material-ui/icons/Publish'; import { useMachine } from '@xstate/react'; +import { useBranding } from 'common/BrandingContext'; import gql from 'graphql-tag'; import { PublishModelerModal } from 'modals/publish-modeler/PublishModelerModal'; import { RenameModelerModal } from 'modals/rename-modeler/RenameModelerModal'; +import { ProjectNavbar } from 'navbar/ProjectNavbar'; import React, { useEffect } from 'react'; import { Link as RouterLink, useParams } from 'react-router-dom'; import { Modeler } from 'views/modelers/ModelersView.types'; -import { View } from 'views/View'; import { CloseMenuEvent, CloseModalEvent, @@ -71,6 +73,16 @@ const getModelersQuery = gql` `; const useModelersViewStyles = makeStyles((theme) => ({ + modelersView: { + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: 'min-content 1fr min-content', + minHeight: '100vh', + }, + main: { + paddingTop: theme.spacing(3), + paddingBottom: theme.spacing(3), + }, modelersViewContainer: { display: 'flex', flexDirection: 'column', @@ -91,6 +103,7 @@ const useModelersViewStyles = makeStyles((theme) => ({ export const ModelersView = () => { const classes = useModelersViewStyles(); const { projectId } = useParams(); + const { footer } = useBranding(); const [{ value, context }, dispatch] = useMachine<ModelersViewContext, ModelersViewEvent>(modelersViewMachine); const { toast, modelersView } = value as SchemaValue; const { modelers, selectedModeler, menuAnchor, modalToDisplay, message } = context; @@ -172,28 +185,36 @@ export const ModelersView = () => { } return ( - <View> - <Grid container justify="center"> - <Grid item xs={8}> - <div className={classes.modelersViewContainer}> - <div className={classes.header}> - <Typography variant="h3">Modelers</Typography> - <div className={classes.actions}> - <Button - to={`/projects/${projectId}/new/modeler`} - component={RouterLink} - data-testid="create" - color="primary" - variant="contained" - disabled={modelersView === 'missing'}> - New - </Button> - </div> - </div> - {main} - </div> - </Grid> - </Grid> + <> + <div className={classes.modelersView}> + <ProjectNavbar /> + <main className={classes.main}> + <Container maxWidth="xl"> + <Grid container justify="center"> + <Grid item xs={8}> + <div className={classes.modelersViewContainer}> + <div className={classes.header}> + <Typography variant="h3">Modelers</Typography> + <div className={classes.actions}> + <Button + to={`/projects/${projectId}/new/modeler`} + component={RouterLink} + data-testid="create" + color="primary" + variant="contained" + disabled={modelersView === 'missing'}> + New + </Button> + </div> + </div> + {main} + </div> + </Grid> + </Grid> + </Container> + </main> + {footer} + </div> <Snackbar anchorOrigin={{ vertical: 'bottom', @@ -214,7 +235,7 @@ export const ModelersView = () => { } data-testid="error" /> - </View> + </> ); }; diff --git a/frontend/src/views/new-modeler/NewModelerView.tsx b/frontend/src/views/new-modeler/NewModelerView.tsx index 2d599c578e..e8804f201f 100644 --- a/frontend/src/views/new-modeler/NewModelerView.tsx +++ b/frontend/src/views/new-modeler/NewModelerView.tsx @@ -12,18 +12,20 @@ *******************************************************************************/ import { useMutation } from '@apollo/client'; import Button from '@material-ui/core/Button'; +import Container from '@material-ui/core/Container'; import IconButton from '@material-ui/core/IconButton'; import Snackbar from '@material-ui/core/Snackbar'; import { makeStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; import CloseIcon from '@material-ui/icons/Close'; import { useMachine } from '@xstate/react'; +import { useBranding } from 'common/BrandingContext'; import { Form } from 'core/form/Form'; import gql from 'graphql-tag'; +import { ProjectNavbar } from 'navbar/ProjectNavbar'; import React, { useEffect } from 'react'; import { Redirect, useParams } from 'react-router-dom'; import { FormContainer } from 'views/FormContainer'; -import { View } from 'views/View'; import { HandleChangedNameEvent, HandleCreateModelerEvent, @@ -55,6 +57,16 @@ const createModelerMutation = gql` `; const useNewModelerViewStyles = makeStyles((theme) => ({ + newModelerView: { + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: 'min-content 1fr min-content', + minHeight: '100vh', + }, + main: { + paddingTop: theme.spacing(3), + paddingBottom: theme.spacing(3), + }, buttons: { display: 'flex', flexDirection: 'row', @@ -64,6 +76,7 @@ const useNewModelerViewStyles = makeStyles((theme) => ({ export const NewModelerView = () => { const { projectId } = useParams(); const classes = useNewModelerViewStyles(); + const { footer } = useBranding(); const [{ value, context }, dispatch] = useMachine<NewModelerViewContext, NewModelerEvent>(newModelerViewMachine); const { newModelerView, toast } = value as SchemaValue; const { name, nameMessage, nameIsInvalid, message } = context; @@ -116,32 +129,40 @@ export const NewModelerView = () => { } return ( - <View condensed> - <FormContainer title="Create a new modeler" subtitle="Get started by creating a new modeler"> - <Form onSubmit={onCreateNewModeler}> - <TextField - error={nameIsInvalid} - helperText={nameMessage} - label="Name" - name="name" - value={name} - placeholder="Enter the modeler name" - data-testid="name" - autoFocus={true} - onChange={onNameChange} - /> - <div className={classes.buttons}> - <Button - variant="contained" - type="submit" - disabled={newModelerView !== 'valid'} - data-testid="create-modeler" - color="primary"> - Create - </Button> - </div> - </Form> - </FormContainer> + <> + <div className={classes.newModelerView}> + <ProjectNavbar /> + <main className={classes.main}> + <Container maxWidth="sm"> + <FormContainer title="Create a new modeler" subtitle="Get started by creating a new modeler"> + <Form onSubmit={onCreateNewModeler}> + <TextField + error={nameIsInvalid} + helperText={nameMessage} + label="Name" + name="name" + value={name} + placeholder="Enter the modeler name" + data-testid="name" + autoFocus={true} + onChange={onNameChange} + /> + <div className={classes.buttons}> + <Button + variant="contained" + type="submit" + disabled={newModelerView !== 'valid'} + data-testid="create-modeler" + color="primary"> + Create + </Button> + </div> + </Form> + </FormContainer> + </Container> + </main> + {footer} + </div> <Snackbar anchorOrigin={{ vertical: 'bottom', @@ -162,6 +183,6 @@ export const NewModelerView = () => { } data-testid="error" /> - </View> + </> ); }; diff --git a/frontend/src/views/new-project/NewProjectView.tsx b/frontend/src/views/new-project/NewProjectView.tsx index bdc5dcf6ed..7d3cf5190e 100644 --- a/frontend/src/views/new-project/NewProjectView.tsx +++ b/frontend/src/views/new-project/NewProjectView.tsx @@ -12,14 +12,17 @@ *******************************************************************************/ import { useMutation } from '@apollo/client'; import Button from '@material-ui/core/Button'; +import Container from '@material-ui/core/Container'; import IconButton from '@material-ui/core/IconButton'; import Snackbar from '@material-ui/core/Snackbar'; import { makeStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; import CloseIcon from '@material-ui/icons/Close'; import { useMachine } from '@xstate/react'; +import { useBranding } from 'common/BrandingContext'; import { Form } from 'core/form/Form'; import gql from 'graphql-tag'; +import { LoggedInNavbar } from 'navbar/LoggedInNavbar'; import React, { useEffect } from 'react'; import { Redirect } from 'react-router-dom'; import { FormContainer } from 'views/FormContainer'; @@ -39,7 +42,6 @@ import { SchemaValue, ShowToastEvent, } from 'views/new-project/NewProjectViewMachine'; -import { View } from 'views/View'; const createProjectMutation = gql` mutation createProject($input: CreateProjectInput!) { @@ -58,6 +60,16 @@ const createProjectMutation = gql` `; const useNewProjectViewStyles = makeStyles((theme) => ({ + newProjectView: { + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: 'min-content 1fr min-content', + minHeight: '100vh', + }, + main: { + paddingTop: theme.spacing(3), + paddingBottom: theme.spacing(3), + }, buttons: { display: 'flex', flexDirection: 'row', @@ -70,6 +82,7 @@ const isErrorPayload = (payload: GQLCreateProjectPayload): payload is GQLErrorPa export const NewProjectView = () => { const classes = useNewProjectViewStyles(); + const { footer } = useBranding(); const [{ value, context }, dispatch] = useMachine<NewProjectViewContext, NewProjectEvent>(newProjectViewMachine); const { newProjectView, toast } = value as SchemaValue; const { name, nameMessage, nameIsInvalid, message, newProjectId } = context; @@ -122,32 +135,40 @@ export const NewProjectView = () => { } return ( - <View condensed> - <FormContainer title="Create a new project" subtitle="Get started by creating a new project"> - <Form onSubmit={onCreateNewProject}> - <TextField - error={nameIsInvalid} - helperText={nameMessage} - label="Name" - name="name" - value={name} - placeholder="Enter the project name" - data-testid="name" - autoFocus={true} - onChange={onNameChange} - /> - <div className={classes.buttons}> - <Button - variant="contained" - type="submit" - disabled={newProjectView !== 'valid'} - data-testid="create-project" - color="primary"> - Create - </Button> - </div> - </Form> - </FormContainer> + <> + <div className={classes.newProjectView}> + <LoggedInNavbar /> + <main className={classes.main}> + <Container maxWidth="sm"> + <FormContainer title="Create a new project" subtitle="Get started by creating a new project"> + <Form onSubmit={onCreateNewProject}> + <TextField + error={nameIsInvalid} + helperText={nameMessage} + label="Name" + name="name" + value={name} + placeholder="Enter the project name" + data-testid="name" + autoFocus={true} + onChange={onNameChange} + /> + <div className={classes.buttons}> + <Button + variant="contained" + type="submit" + disabled={newProjectView !== 'valid'} + data-testid="create-project" + color="primary"> + Create + </Button> + </div> + </Form> + </FormContainer> + </Container> + </main> + {footer} + </div> <Snackbar anchorOrigin={{ vertical: 'bottom', @@ -168,6 +189,6 @@ export const NewProjectView = () => { } data-testid="error" /> - </View> + </> ); }; diff --git a/frontend/src/views/upload-project/UploadProjectView.tsx b/frontend/src/views/upload-project/UploadProjectView.tsx index 741768ce1e..4f6ebffd6c 100644 --- a/frontend/src/views/upload-project/UploadProjectView.tsx +++ b/frontend/src/views/upload-project/UploadProjectView.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Obeo. + * Copyright (c) 2019, 2021 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -11,6 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ import Button from '@material-ui/core/Button'; +import Container from '@material-ui/core/Container'; import IconButton from '@material-ui/core/IconButton'; import Snackbar from '@material-ui/core/Snackbar'; import { makeStyles } from '@material-ui/core/styles'; @@ -20,10 +21,10 @@ import { sendFile } from 'common/sendFile'; import { FileUpload } from 'core/file-upload/FileUpload'; import { Form } from 'core/form/Form'; import gql from 'graphql-tag'; +import { LoggedInNavbar } from 'navbar/LoggedInNavbar'; import React from 'react'; import { Redirect } from 'react-router-dom'; import { FormContainer } from 'views/FormContainer'; -import { View } from 'views/View'; import { SchemaValue, UploadProjectEvent, @@ -48,6 +49,16 @@ const uploadProjectMutation = gql` `.loc.source.body; const useUploadProjectViewStyles = makeStyles((theme) => ({ + uploadProjectView: { + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: 'min-content 1fr min-content', + minHeight: '100vh', + }, + main: { + paddingTop: theme.spacing(3), + paddingBottom: theme.spacing(3), + }, buttons: { display: 'flex', flexDirection: 'row', @@ -97,22 +108,27 @@ export const UploadProjectView = () => { return <Redirect to={`/projects/${newProjectId}/edit`} />; } return ( - <View condensed> - <FormContainer title="Upload a project" subtitle="Start with an existing project"> - <Form onSubmit={onUploadProject} encType="multipart/form-data"> - <FileUpload onFileSelected={onFileSelected} data-testid="file" /> - <div className={classes.buttons}> - <Button - variant="contained" - type="submit" - color="primary" - disabled={uploadProjectView !== 'fileSelected'} - data-testid="upload-project"> - Upload - </Button> - </div> - </Form> - </FormContainer> + <div className={classes.uploadProjectView}> + <LoggedInNavbar /> + <main className={classes.main}> + <Container maxWidth="sm"> + <FormContainer title="Upload a project" subtitle="Start with an existing project"> + <Form onSubmit={onUploadProject} encType="multipart/form-data"> + <FileUpload onFileSelected={onFileSelected} data-testid="file" /> + <div className={classes.buttons}> + <Button + variant="contained" + type="submit" + color="primary" + disabled={uploadProjectView !== 'fileSelected'} + data-testid="upload-project"> + Upload + </Button> + </div> + </Form> + </FormContainer> + </Container> + </main> <Snackbar anchorOrigin={{ vertical: 'bottom', @@ -129,6 +145,6 @@ export const UploadProjectView = () => { </IconButton> } /> - </View> + </div> ); };