diff --git a/frontend/src/views/edit-project/EditProjectView.tsx b/frontend/src/views/edit-project/EditProjectView.tsx index 97ed5fd057..edff7a16af 100644 --- a/frontend/src/views/edit-project/EditProjectView.tsx +++ b/frontend/src/views/edit-project/EditProjectView.tsx @@ -107,19 +107,20 @@ export const EditProjectView = () => { if (representation && representation.id !== representationId) { const pathname = generatePath(routeMatch.path, { projectId, representationId: representation.id }); history.push({ pathname }); + } else if (editProjectView === 'loaded' && representation === null && representationId) { + const pathname = generatePath(routeMatch.path, { projectId, representationId: null }); + history.push({ pathname }); } - }, [projectId, routeMatch, history, representation, representationId]); + }, [editProjectView, projectId, routeMatch, history, representation, representationId]); let main = null; if (editProjectView === 'loaded') { const onRepresentationSelected = (representationSelected: Representation) => { - if (representationSelected.id !== representationId) { - const selectRepresentationEvent: SelectRepresentationEvent = { - type: 'SELECT_REPRESENTATION', - representation: representationSelected, - }; - dispatch(selectRepresentationEvent); - } + const selectRepresentationEvent: SelectRepresentationEvent = { + type: 'SELECT_REPRESENTATION', + representation: representationSelected, + }; + dispatch(selectRepresentationEvent); }; main = ( diff --git a/frontend/src/workbench/RepresentationArea.tsx b/frontend/src/workbench/RepresentationArea.tsx index 2126b22a0a..edb5b014cf 100644 --- a/frontend/src/workbench/RepresentationArea.tsx +++ b/frontend/src/workbench/RepresentationArea.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 @@ -24,6 +24,7 @@ const propTypes = { representations: PropTypes.array.isRequired, selection: PropTypes.object, displayedRepresentation: PropTypes.object, + onRepresentationClick: PropTypes.func.isRequired, setSelection: PropTypes.func.isRequired, }; export const RepresentationArea = ({ @@ -33,6 +34,8 @@ export const RepresentationArea = ({ selection, displayedRepresentation, setSelection, + onRepresentationClick, + onClose, }) => { let content; if (displayedRepresentation.kind === 'Diagram') { @@ -56,7 +59,8 @@ export const RepresentationArea = ({ {content} diff --git a/frontend/src/workbench/RepresentationNavigation.tsx b/frontend/src/workbench/RepresentationNavigation.tsx index 11e7a7301c..14165f6a6c 100644 --- a/frontend/src/workbench/RepresentationNavigation.tsx +++ b/frontend/src/workbench/RepresentationNavigation.tsx @@ -10,39 +10,90 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import Typography from '@material-ui/core/Typography'; -import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/core/styles'; +import Tab from '@material-ui/core/Tab'; +import Tabs from '@material-ui/core/Tabs'; +import CloseIcon from '@material-ui/icons/Close'; import React from 'react'; -import styles from './RepresentationNavigation.module.css'; +import { RepresentationNavigationProps } from 'workbench/RepresentationNavigation.types'; +import { Representation, Selection } from 'workbench/Workbench.types'; -const propTypes = { - representations: PropTypes.array.isRequired, - displayedRepresentation: PropTypes.object, - setSelection: PropTypes.func.isRequired, +const useRepresentationNavigationStyles = makeStyles((theme) => ({ + tabsRoot: { + minHeight: theme.spacing(4), + borderBottomColor: theme.palette.divider, + borderBottomWidth: '1px', + borderBottomStyle: 'solid', + }, + tabRoot: { + minHeight: theme.spacing(4), + textTransform: 'none', + }, + tabLabel: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + '& > *:nth-child(2)': { + marginLeft: theme.spacing(1), + }, + }, +})); + +const a11yProps = (id: string) => { + return { + id: `simple-tab-${id}`, + 'aria-controls': `simple-tabpanel-${id}`, + }; }; -export const RepresentationNavigation = ({ representations, displayedRepresentation, setSelection }) => { +export const RepresentationNavigation = ({ + representations, + displayedRepresentation, + onRepresentationClick, + onClose, +}: RepresentationNavigationProps) => { + const classes = useRepresentationNavigationStyles(); + + const onChange = (_, value) => { + const representationSelected = representations.find((representation) => representation.id === value); + const selection: Selection = { + id: representationSelected.id, + label: representationSelected.label, + kind: representationSelected.kind, + }; + onRepresentationClick(selection); + }; + const onRepresentationClose = (event, representation: Representation) => { + event.stopPropagation(); + onClose(representation); + }; return ( - + ); }; -RepresentationNavigation.propTypes = propTypes; diff --git a/frontend/src/workbench/RepresentationNavigation.module.css b/frontend/src/workbench/RepresentationNavigation.types.ts similarity index 51% rename from frontend/src/workbench/RepresentationNavigation.module.css rename to frontend/src/workbench/RepresentationNavigation.types.ts index 27ebbe798a..a7302198b9 100644 --- a/frontend/src/workbench/RepresentationNavigation.module.css +++ b/frontend/src/workbench/RepresentationNavigation.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Obeo. + * 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 @@ -10,25 +10,11 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -.representationNavigation { - display: flex; - flex-direction: row; - border-bottom: 1px solid var(--blue-lagoon); -} +import { Representation } from 'workbench/Workbench.types'; -.item { - padding: 8px; - border-right: 1px solid var(--blue-lagoon); -} - -.label { - font-size: var(--font-size-5); - font-weight: var(--font-weight-bold); - color: var(--blue-lagoon-lighten-60); -} -.label:hover { - color: var(--blue-lagoon-lighten-10); -} -.selected { - color: var(--blue-lagoon-lighten-10); -} +export type RepresentationNavigationProps = { + representations: Representation[]; + displayedRepresentation: Representation; + onRepresentationClick: (representation: Representation) => void; + onClose: (representation: Representation) => void; +}; diff --git a/frontend/src/workbench/Workbench.tsx b/frontend/src/workbench/Workbench.tsx index 01a4d819ac..e5209479a6 100644 --- a/frontend/src/workbench/Workbench.tsx +++ b/frontend/src/workbench/Workbench.tsx @@ -18,8 +18,15 @@ import { PropertiesWebSocketContainer } from 'properties/PropertiesWebSocketCont import React, { useEffect } from 'react'; import { OnboardArea } from 'workbench/OnboardArea'; import { RepresentationArea } from 'workbench/RepresentationArea'; -import { Selection, WorkbenchProps } from 'workbench/Workbench.types'; -import { UpdateSelectionEvent, WorkbenchContext, WorkbenchEvent, workbenchMachine } from 'workbench/WorkbenchMachine'; +import { Representation, Selection, WorkbenchProps } from 'workbench/Workbench.types'; +import { + HideRepresentationEvent, + ShowRepresentationEvent, + UpdateSelectionEvent, + WorkbenchContext, + WorkbenchEvent, + workbenchMachine, +} from 'workbench/WorkbenchMachine'; const useWorkbenchStyles = makeStyles((theme) => ({ main: { @@ -49,9 +56,21 @@ export const Workbench = ({ dispatch(updateSelectionEvent); }; + const onRepresentationClick = (representation: Representation) => { + const showRepresentationEvent: ShowRepresentationEvent = { type: 'SHOW_REPRESENTATION', representation }; + dispatch(showRepresentationEvent); + }; + + const onClose = (representation: Representation) => { + const hideRepresentationEvent: HideRepresentationEvent = { type: 'HIDE_REPRESENTATION', representation }; + dispatch(hideRepresentationEvent); + }; + useEffect(() => { - if (displayedRepresentation !== null && displayedRepresentation.id !== initialRepresentationSelected?.id) { + if (displayedRepresentation && displayedRepresentation.id !== initialRepresentationSelected?.id) { onRepresentationSelected(displayedRepresentation); + } else if (displayedRepresentation === null && initialRepresentationSelected) { + onRepresentationSelected(null); } }, [onRepresentationSelected, initialRepresentationSelected, displayedRepresentation]); @@ -69,6 +88,8 @@ export const Workbench = ({ displayedRepresentation={displayedRepresentation} selection={selection} setSelection={setSelection} + onRepresentationClick={onRepresentationClick} + onClose={onClose} readOnly={readOnly} /> ); diff --git a/frontend/src/workbench/WorkbenchMachine.ts b/frontend/src/workbench/WorkbenchMachine.ts index 7db5941a54..a56505629c 100644 --- a/frontend/src/workbench/WorkbenchMachine.ts +++ b/frontend/src/workbench/WorkbenchMachine.ts @@ -26,8 +26,9 @@ export interface WorkbenchContext { } export type ShowRepresentationEvent = { type: 'SHOW_REPRESENTATION'; representation: Representation }; +export type HideRepresentationEvent = { type: 'HIDE_REPRESENTATION'; representation: Representation }; export type UpdateSelectionEvent = { type: 'UPDATE_SELECTION'; selection: Selection }; -export type WorkbenchEvent = UpdateSelectionEvent | ShowRepresentationEvent; +export type WorkbenchEvent = UpdateSelectionEvent | ShowRepresentationEvent | HideRepresentationEvent; export const workbenchMachine = Machine( { @@ -48,6 +49,10 @@ export const workbenchMachine = Machine { + const { representation: representationToHide } = event as HideRepresentationEvent; + + const previousIndex = context.representations.findIndex( + (representation) => representation.id === context.displayedRepresentation.id + ); + const newRepresentations = context.representations.filter( + (representation) => representation.id !== representationToHide.id + ); + + if (newRepresentations.length === 0) { + // There are no representations anymore + return { displayedRepresentation: null, representations: [] }; + } else { + const newIndex = newRepresentations.findIndex( + (representation) => representation.id === context.displayedRepresentation.id + ); + + if (newIndex !== -1) { + // The previously displayed representation has not been closed + return { representations: newRepresentations }; + } else if (newRepresentations.length === previousIndex) { + // The previous representation has been closed and it was the last one + const displayedRepresentation = newRepresentations[previousIndex - 1]; + return { displayedRepresentation, representations: newRepresentations }; + } else { + const displayedRepresentation = newRepresentations[previousIndex]; + return { displayedRepresentation, representations: newRepresentations }; + } + } + }), }, } );