Skip to content

Commit

Permalink
[153] Add the ability to close representations
Browse files Browse the repository at this point in the history
Bug: #153
Signed-off-by: Stéphane Bégaudeau <stephane.begaudeau@obeo.fr>
Signed-off-by: Steve Monnier <steve.monnier@obeo.fr>
  • Loading branch information
sbegaudeau committed Feb 9, 2021
1 parent 3af7bba commit 163fa6a
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 60 deletions.
17 changes: 9 additions & 8 deletions frontend/src/views/edit-project/EditProjectView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/workbench/RepresentationArea.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 = ({
Expand All @@ -33,6 +34,8 @@ export const RepresentationArea = ({
selection,
displayedRepresentation,
setSelection,
onRepresentationClick,
onClose,
}) => {
let content;
if (displayedRepresentation.kind === 'Diagram') {
Expand All @@ -56,7 +59,8 @@ export const RepresentationArea = ({
<RepresentationNavigation
representations={representations}
displayedRepresentation={displayedRepresentation}
setSelection={setSelection}
onRepresentationClick={onRepresentationClick}
onClose={onClose}
/>
{content}
</div>
Expand Down
99 changes: 75 additions & 24 deletions frontend/src/workbench/RepresentationNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ul className={styles.representationNavigation}>
<Tabs
classes={{ root: classes.tabsRoot }}
value={displayedRepresentation.id}
onChange={onChange}
variant="scrollable"
scrollButtons="on"
textColor="primary"
indicatorColor="primary">
{representations.map((representation) => {
let labelClassName = styles.label;
const isSelected = representation.id === displayedRepresentation.id;
if (isSelected) {
labelClassName = `${labelClassName} ${styles.selected}`;
}
const { id, label, kind } = representation;
return (
<li
<Tab
{...a11yProps(representation.id)}
classes={{ root: classes.tabRoot }}
value={representation.id}
label={
<div className={classes.tabLabel}>
{representation.label}
<CloseIcon fontSize="small" onClick={(event) => onRepresentationClose(event, representation)} />
</div>
}
key={representation.id}
className={styles.item}
onClick={() => setSelection({ id, label, kind })}
data-testid={`representation-tab-${label}`}
data-testselected={isSelected}>
<Typography className={labelClassName}>{label}</Typography>
</li>
data-testid={`representation-tab-${representation.label}`}
data-testselected={representation.id === displayedRepresentation.id}
/>
);
})}
</ul>
</Tabs>
);
};
RepresentationNavigation.propTypes = propTypes;
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
};
27 changes: 24 additions & 3 deletions frontend/src/workbench/Workbench.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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]);

Expand All @@ -69,6 +88,8 @@ export const Workbench = ({
displayedRepresentation={displayedRepresentation}
selection={selection}
setSelection={setSelection}
onRepresentationClick={onRepresentationClick}
onClose={onClose}
readOnly={readOnly}
/>
);
Expand Down
38 changes: 37 additions & 1 deletion frontend/src/workbench/WorkbenchMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<WorkbenchContext, WorkbenchStateSchema, WorkbenchEvent>(
{
Expand All @@ -48,6 +49,10 @@ export const workbenchMachine = Machine<WorkbenchContext, WorkbenchStateSchema,
target: 'initial',
actions: 'showRepresentation',
},
HIDE_REPRESENTATION: {
target: 'initial',
actions: 'hideRepresentation',
},
},
},
},
Expand Down Expand Up @@ -78,6 +83,37 @@ export const workbenchMachine = Machine<WorkbenchContext, WorkbenchStateSchema,
const { representation } = event as ShowRepresentationEvent;
return { displayedRepresentation: representation };
}),
hideRepresentation: assign((context, event) => {
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 };
}
}
}),
},
}
);

0 comments on commit 163fa6a

Please sign in to comment.