Skip to content

Commit

Permalink
[1545] Extract selection and setSelection in a hook
Browse files Browse the repository at this point in the history
Bug: #1545
Signed-off-by: Guillaume Coutable <guillaume.coutable@obeo.fr>
  • Loading branch information
gcoutable authored and sbegaudeau committed Dec 15, 2023
1 parent 01ac013 commit e262c60
Show file tree
Hide file tree
Showing 105 changed files with 881 additions and 936 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
=== Architectural decision records

- [ADR-122] Use the page object model in integration tests
- [ADR-123] Improve frontend selection support


=== Breaking changes
Expand Down Expand Up @@ -83,6 +84,7 @@ const nodeTypeRegistry: NodeTypeRegistry = {

- https://github.com/eclipse-sirius/sirius-web/issues/2735[#2735] [gantt] Contribute the first version of the Gantt representation.
- https://github.com/eclipse-sirius/sirius-web/issues/2403[2403] [view] Add support for If and Let constructs in the operations DSL.
- https://github.com/eclipse-sirius/sirius-web/issues/1545[#1545] [core] The current selection can now be read and set from anywhere using the new `useSelection()` hook.

=== Improvements

Expand Down Expand Up @@ -184,6 +186,7 @@ Use `useDropNodeStyle(node.id)` instead to get the feedback of the drop feedback
- https://github.com/eclipse-sirius/sirius-web/issues/2671[#2671] [diagram] Switch to Reactflow by default.
To open a diagram with Sprotty, suffixes the diagram name with `__SPROTTY`


=== Dependency update

- Remove the dependency to elk.js
Expand Down
79 changes: 79 additions & 0 deletions doc/adrs/123_improve_frontend_selection_support.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
= ADR-123 - Improve frontend selection support

== Context

Selection handling is currently managed by the `Workbench` frontend component, which is only part (although the main one) of the project edition view/page (EditProjectView).
Actions of the EditProjectView which are not part of the workbench (for example in the navbar and its menu) should also be able to read and set the selection.

== Decision

We will extract selection handling from `Workbench(Machine).tsx` into a custom hook.
This will simplify the Workbench state machine.

It will also isolate selection handling implementation and enable a lot of simplications where we used to drill down `selection` and/or `setSelection` using props.
Components which need to read/set the selection will be able to do so using:

[source,js]
----
const {selection, setSelection} = useSelection();
----

Some components (notably the workbench itself) need to be able to react to selection changes.
They can do this with a `useEffect()` which reacts to changes in the selection value:

[source,js]
----
const {selection} = useSelection();
useEffect(() => {
/* handle the new selection value */
}, [selection]);
----

Components which used to get the selection and/or setSelection callback as part of their props just to pass them to their children will be simplified.

The state needed for the new hook will be handled by the `SelectionContext` and it will be possible to initialize the selection.

[source,js]
----
export const SelectionContext = React.createContext(undefined);
export const SelectionContextProvider = ({ initialSelection, children }) => {
const [state, setState] = useState({
selection: initialSelection ?? { entries: [] },
});
const setSelection = useCallback((selection: Selection) => {
setState((prevState) => ({ ...prevState, selection }));
}, []);
return (
<SelectionContext.Provider
value={{ selection: state.selection, setSelection }}>
{children}
</SelectionContext.Provider>
);
}
----

The `SelectionContextProvider` will be used as the parent of the `Workbench` and the `Project Navbar` in `EditProjectView`:

[source,js]
----
return (
<SelectionContextProvider initialSelection={initialSelection}>
{navbar}
{main}
<Snackbar />
</SelectionContextProvider>
);
----

This makes sense because the selection is relative to a particular project/editing context.

== Status

Work in progress
4 changes: 4 additions & 0 deletions packages/core/frontend/sirius-components-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export * from './contexts/ToastContext.types';
export * from './dataTransferTypes';
export * from './icon/IconOverlay';
export * from './materialui';
export * from './selection/SelectionContext';
export * from './selection/SelectionContext.types';
export * from './selection/useSelection';
export * from './selection/useSelection.types';
export * from './theme';
export * from './toast/MultiToast';
export * from './toast/Toast';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2023 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 React, { useCallback, useState } from 'react';
import { Representation } from '../workbench/Workbench.types';
import {
Selection,
SelectionContextProviderProps,
SelectionContextProviderState,
SelectionContextValue,
SelectionEntry,
} from './SelectionContext.types';

const defaultValue: SelectionContextValue = {
selection: { entries: [] },
setSelection: () => {},
};

export const SelectionContext = React.createContext<SelectionContextValue>(defaultValue);

const isRepresentation = (selectionEntry: SelectionEntry): selectionEntry is Representation =>
selectionEntry.kind.startsWith('siriusComponents://representation');

export const SelectionContextProvider = ({ initialSelection, children }: SelectionContextProviderProps) => {
const [state, setState] = useState<SelectionContextProviderState>({
selection: initialSelection ?? { entries: [] },
});

const setSelection = useCallback((selection: Selection) => {
const selectedRepresentation = selection.entries.filter(isRepresentation);
setState((prevState) => ({ ...prevState, selection, selectedRepresentation }));
}, []);

return (
<SelectionContext.Provider value={{ selection: state.selection, setSelection }}>
{children}
</SelectionContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*******************************************************************************
* Copyright (c) 2023 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
*******************************************************************************/

export interface Selection {
entries: SelectionEntry[];
}

export interface SelectionEntry {
id: string;
label: string;
kind: string;
}

export interface SelectionContextValue {
selection: Selection;
setSelection: (selection: Selection) => void;
}

export interface SelectionContextProviderProps {
initialSelection: Selection | null;
children: React.ReactNode;
}

export interface SelectionContextProviderState {
selection: Selection;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*******************************************************************************
* Copyright (c) 2023 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 { useContext } from 'react';
import { SelectionContext } from './SelectionContext';
import { UseSelectionValue } from './useSelection.types';

export const useSelection = (): UseSelectionValue => {
const { selection, setSelection } = useContext<UseSelectionValue>(SelectionContext);

return { selection, setSelection };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*******************************************************************************
* Copyright (c) 2023 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 { Selection } from './SelectionContext.types';

export interface UseSelectionValue {
selection: Selection;

setSelection: (selection: Selection) => void;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019, 2022 Obeo.
* Copyright (c) 2019, 2023 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 Down Expand Up @@ -47,8 +47,6 @@ interface PanelState {

export const Panels = ({
editingContextId,
selection,
setSelection,
readOnly,
leftContributions,
rightContributions,
Expand Down Expand Up @@ -166,8 +164,6 @@ export const Panels = ({
<div className={styles.panel}>
<Site
editingContextId={editingContextId}
selection={selection}
setSelection={setSelection}
readOnly={readOnly}
side="left"
expanded={leftPanelState.expanded}
Expand Down Expand Up @@ -198,8 +194,6 @@ export const Panels = ({
<div className={styles.panel}>
<Site
editingContextId={editingContextId}
selection={selection}
setSelection={setSelection}
readOnly={readOnly}
side="right"
expanded={rightPanelState.expanded}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@
* Obeo - initial API and implementation
*******************************************************************************/

import { Selection } from './Workbench.types';

export interface PanelsProps {
editingContextId: string;
selection: Selection;
setSelection: (selection: Selection) => void;
readOnly: boolean;
leftContributions: React.ReactElement[];
rightContributions: React.ReactElement[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,7 @@ const useSiteStyles = makeStyles((theme) => ({
},
}));

export const Site = ({
editingContextId,
selection,
setSelection,
readOnly,
side,
expanded,
toggleExpansion,
contributions,
}: SiteProps) => {
export const Site = ({ editingContextId, readOnly, side, expanded, toggleExpansion, contributions }: SiteProps) => {
const classes = useSiteStyles();
const [isExpanded, setExpanded] = useState<boolean>(expanded);
const [selectedViewIndex, setSelectedViewIndex] = useState<number>(0);
Expand Down Expand Up @@ -160,12 +151,7 @@ export const Site = ({
<Typography className={classes.viewHeaderTitle}>{title}</Typography>
</div>
<div className={classes.viewContent} data-testid={`view-${title}`}>
<Component
editingContextId={editingContextId}
selection={selection}
setSelection={setSelection}
readOnly={readOnly}
/>
<Component editingContextId={editingContextId} readOnly={readOnly} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@
* Obeo - initial API and implementation
*******************************************************************************/
import React from 'react';
import { Selection, WorkbenchViewSide } from './Workbench.types';
import { WorkbenchViewSide } from './Workbench.types';

export interface SiteProps {
editingContextId: string;
selection: Selection;
setSelection: (selection: Selection) => void;
readOnly: boolean;
side: WorkbenchViewSide;
expanded: boolean;
Expand Down

0 comments on commit e262c60

Please sign in to comment.