From a495414d901060ae34134541d6f4abc1ffa8271e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20ROU=C3=8BN=C3=89?= Date: Wed, 17 Jul 2024 15:41:10 +0200 Subject: [PATCH] [3785] Add an extension point to contribute custom tree item menu entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-web/issues/3785 Signed-off-by: Florian ROUËNÉ --- CHANGELOG.adoc | 5 +- .../src/components/ModelBrowserTreeView.tsx | 23 ++- .../extension/DefaultExtensionRegistry.tsx | 25 ++- ...DiagramTreeItemContextMenuContribution.tsx | 7 +- ...ocumentTreeItemContextMenuContribution.tsx | 16 +- .../views/edit-project/EditProjectView.tsx | 60 +------ .../ObjectTreeItemContextMenuContribution.tsx | 8 +- .../sirius-components-trees/src/index.ts | 5 +- .../src/treeitems/TreeItem.tsx | 164 +++++------------- .../src/treeitems/TreeItem.types.ts | 6 +- .../src/treeitems/TreeItemAction.tsx | 100 +++++++++++ .../src/treeitems/TreeItemAction.types.ts | 30 ++++ .../src/treeitems/TreeItemContextMenu.tsx | 44 ++--- .../treeitems/TreeItemContextMenu.types.ts | 10 +- ...s.ts => TreeItemContextMenuEntry.types.ts} | 7 +- ...reeItemContextMenuEntryExtensionPoints.ts} | 11 +- .../src/trees/Tree.tsx | 4 +- .../src/trees/Tree.types.ts | 5 +- .../src/views/TreeView.tsx | 2 + .../src/views/TreeView.types.ts | 2 + 20 files changed, 301 insertions(+), 233 deletions(-) create mode 100644 packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.tsx create mode 100644 packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.types.ts rename packages/trees/frontend/sirius-components-trees/src/treeitems/{TreeItemContextMenuContribution.types.ts => TreeItemContextMenuEntry.types.ts} (76%) rename packages/trees/frontend/sirius-components-trees/src/treeitems/{TreeItemContextMenuContribution.tsx => TreeItemContextMenuEntryExtensionPoints.ts} (55%) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 9eb8b6cf63..5321924c8a 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -28,9 +28,7 @@ As a result, the following maven modules have been deleted: `sirius-web-sample-application`, `sirius-web-spring`, `sirius-web-graphql`, `sirius-web-services-api`, `sirius-web-services`, `sirius-web-persistence`. - https://github.com/eclipse-sirius/sirius-web/issues/3676[#3676] [representation] Remove the label from representation subscriptions and from graphql types. + Also remove the label from representation metadata (and sometimes the whole metadata) when the label or metadata were not needed. -- https://github.com/eclipse-sirius/sirius-web/issues/1470[#1470] [view] Since the computation of converted description ID has changed, the identifier of existing representations is not valid anymore, and these representations cannot be opened. -- https://github.com/eclipse-sirius/sirius-web/issues/2759[#2759] [diagram] Update some API relative to the diagram: -- https://github.com/eclipse-sirius/sirius-web/issues/1470[#1470] [view] Since the computation of converted description ID has changed, the identifier of existing representations is not valid anymore, and these representations cannot be opened. +- https://github.com/eclipse-sirius/sirius-web/issues/1470[#1470] [view] Since the computation of converted description ID has changed, the identifier of existing representations is not valid anymore, and these representations cannot be opened.- https://github.com/eclipse-sirius/sirius-web/issues/2759[#2759] [diagram] Update some API relative to the diagram:- https://github.com/eclipse-sirius/sirius-web/issues/1470[#1470] [view] Since the computation of converted description ID has changed, the identifier of existing representations is not valid anymore, and these representations cannot be opened. * The `NodeTool#selectionDescription` attribute of type `SelectionDescription` has been renamed into `dialogDescription` and is now of type `DialogDescription`. * The `SelectionDescription` type has been renamed into `SelectionDialogDescription` and inherit from `DialogDescription`. @@ -83,6 +81,7 @@ Thanks to this change, the various form based representations (details, related - https://github.com/eclipse-sirius/sirius-web/issues/3794[#3794] [view] In diagram view DSL, _NodeLabelStyle#showIcon_ has been change to _NodeLabelStyle#showIconExpression_, allowing to dynamically change this style option depending on a condition evaluated at runtime. A migration participant has been added to automatically keep compatible all diagram descriptions created before 2024.9.0. - https://github.com/eclipse-sirius/sirius-web/issues/3774[#3774] [sirius-web] Add an extension point to contribute custom tools to the diagram palette +- https://github.com/eclipse-sirius/sirius-web/issues/3785[#3785] [sirius-web] Add an extension point to contribute custom tree item menu entry == v2024.7.0 diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx index 04641fa367..d7e241f822 100644 --- a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx @@ -10,8 +10,11 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { TreeView } from '@eclipse-sirius/sirius-components-trees'; + +import { TreeView, TreeItemActionProps } from '@eclipse-sirius/sirius-components-trees'; +import IconButton from '@material-ui/core/IconButton'; import { makeStyles } from '@material-ui/core/styles'; +import UnfoldMoreIcon from '@material-ui/icons/UnfoldMore'; import { useState } from 'react'; import { ModelBrowserFilterBar } from './ModelBrowserFilterBar'; import { ModelBrowserTreeViewProps, ModelBrowserTreeViewState } from './ModelBrowserTreeView.types'; @@ -65,8 +68,26 @@ export const ModelBrowserTreeView = ({ textToFilter={state.filterBarText} textToHighlight={state.filterBarText} markedItemIds={markedItemIds} + treeItemActionRender={(props) => } /> ); }; + +const WidgetReferenceTreeItemAction = ({ onExpandAll, item, isHovered }: TreeItemActionProps) => { + if (!onExpandAll || !item || !item.hasChildren || !isHovered) { + return null; + } + return ( + { + onExpandAll(item); + }}> + + + ); +}; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx index 98d46f9ab7..442d5fcba3 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx @@ -30,7 +30,7 @@ import { FormDescriptionEditorRepresentation } from '@eclipse-sirius/sirius-comp import { FormRepresentation } from '@eclipse-sirius/sirius-components-forms'; import { GanttRepresentation } from '@eclipse-sirius/sirius-components-gantt'; import { PortalRepresentation } from '@eclipse-sirius/sirius-components-portals'; -import { ExplorerView } from '@eclipse-sirius/sirius-components-trees'; +import { ExplorerView, treeItemContextMenuEntryExtensionPoint } from '@eclipse-sirius/sirius-components-trees'; import { ValidationView } from '@eclipse-sirius/sirius-components-validation'; import AccountTreeIcon from '@material-ui/icons/AccountTree'; import Filter from '@material-ui/icons/Filter'; @@ -46,6 +46,9 @@ import { createProjectAreaCardExtensionPoint } from '../views/project-browser/cr import { NewProjectCard } from '../views/project-browser/create-projects-area/NewProjectCard'; import { ShowAllProjectTemplatesCard } from '../views/project-browser/create-projects-area/ShowAllProjectTemplatesCard'; import { UploadProjectCard } from '../views/project-browser/create-projects-area/UploadProjectCard'; +import { DocumentTreeItemContextMenuContribution } from '../views/edit-project/DocumentTreeItemContextMenuContribution'; +import { ObjectTreeItemContextMenuContribution } from '../views/edit-project/ObjectTreeItemContextMenuContribution'; +import { DiagramTreeItemContextMenuContribution } from '../views/edit-project/DiagramTreeItemContextMenuContribution'; import { SelectionDialog } from '@eclipse-sirius/sirius-components-selection'; const getType = (representation: RepresentationMetadata): string | null => { @@ -191,4 +194,24 @@ defaultExtensionRegistry.putData(diagramDialogContr data: diagramDialogContributions, }); +/******************************************************************************* + * + * Tree item context menu + * + * Used to register new components in the tree item context menu + * + *******************************************************************************/ +defaultExtensionRegistry.addComponent(treeItemContextMenuEntryExtensionPoint, { + identifier: `siriusweb_${treeItemContextMenuEntryExtensionPoint.identifier}_document`, + Component: DocumentTreeItemContextMenuContribution, +}); +defaultExtensionRegistry.addComponent(treeItemContextMenuEntryExtensionPoint, { + identifier: `siriusweb_${treeItemContextMenuEntryExtensionPoint.identifier}_object`, + Component: ObjectTreeItemContextMenuContribution, +}); +defaultExtensionRegistry.addComponent(treeItemContextMenuEntryExtensionPoint, { + identifier: `siriusweb_${treeItemContextMenuEntryExtensionPoint.identifier}_diagram`, + Component: DiagramTreeItemContextMenuContribution, +}); + export { defaultExtensionRegistry }; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DiagramTreeItemContextMenuContribution.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DiagramTreeItemContextMenuContribution.tsx index c389bea014..74a2577cb0 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DiagramTreeItemContextMenuContribution.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DiagramTreeItemContextMenuContribution.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2023 Obeo. + * Copyright (c) 2022, 2024 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,10 @@ import { TreeItemContextMenuComponentProps } from '@eclipse-sirius/sirius-compon import { Fragment, forwardRef } from 'react'; export const DiagramTreeItemContextMenuContribution = forwardRef( - ({}: TreeItemContextMenuComponentProps, _: React.ForwardedRef) => { + ({ treeId, item }: TreeItemContextMenuComponentProps, _: React.ForwardedRef) => { + if (!treeId.startsWith('explorer://') || item.kind !== 'siriusComponents://representation?type=Diagram') { + return null; + } return ; } ); diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DocumentTreeItemContextMenuContribution.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DocumentTreeItemContextMenuContribution.tsx index a130687cee..90b8767d4b 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DocumentTreeItemContextMenuContribution.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/DocumentTreeItemContextMenuContribution.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2023 Obeo. + * Copyright (c) 2021, 2024 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,13 +24,17 @@ type Modal = 'CreateNewRootObject'; export const DocumentTreeItemContextMenuContribution = forwardRef( ( - { editingContextId, item, readOnly, expandItem, onClose }: TreeItemContextMenuComponentProps, + { editingContextId, treeId, item, readOnly, expandItem, onClose }: TreeItemContextMenuComponentProps, ref: React.ForwardedRef ) => { const { httpOrigin } = useContext(ServerContext); const [modal, setModal] = useState(null); const { setSelection } = useSelection(); + if (!treeId.startsWith('explorer://') || !item.kind.startsWith('siriusWeb://document')) { + return null; + } + const onObjectCreated = (selection: Selection) => { setSelection(selection); expandItem(); @@ -82,11 +86,3 @@ export const DocumentTreeItemContextMenuContribution = forwardRef( ); } ); - -/* -if (!item.expanded && item.hasChildren) { - onExpand(item.id, depth); - } - const { id, label, kind } = object; - setSelection({ id, label, kind }); -*/ diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx index 20620ddb74..cb9724b523 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx @@ -18,10 +18,6 @@ import { } from '@eclipse-sirius/sirius-components-core'; import { useMachine } from '@xstate/react'; import { - GQLTreeItem, - TreeItemContextMenuContext, - TreeItemContextMenuContextValue, - TreeItemContextMenuContribution, TreeToolBarContext, TreeToolBarContextValue, TreeToolBarContribution, @@ -32,14 +28,8 @@ import { makeStyles } from '@material-ui/core/styles'; import { useEffect } from 'react'; import { generatePath, useHistory, useParams, useRouteMatch } from 'react-router-dom'; import { NavigationBar } from '../../navigationBar/NavigationBar'; -import { DiagramTreeItemContextMenuContribution } from './DiagramTreeItemContextMenuContribution'; -import { DocumentTreeItemContextMenuContribution } from './DocumentTreeItemContextMenuContribution'; import { EditProjectNavbar } from './EditProjectNavbar/EditProjectNavbar'; -import { - EditProjectViewParams, - TreeItemContextMenuProviderProps, - TreeToolBarProviderProps, -} from './EditProjectView.types'; +import { EditProjectViewParams, TreeToolBarProviderProps } from './EditProjectView.types'; import { EditProjectViewContext, EditProjectViewEvent, @@ -47,7 +37,6 @@ import { SelectRepresentationEvent, editProjectViewMachine, } from './EditProjectViewMachine'; -import { ObjectTreeItemContextMenuContribution } from './ObjectTreeItemContextMenuContribution'; import { ProjectContext } from './ProjectContext'; import { NewDocumentModalContribution } from './TreeToolBarContributions/NewDocumentModalContribution'; import { UploadDocumentModalContribution } from './TreeToolBarContributions/UploadDocumentModalContribution'; @@ -133,16 +122,14 @@ export const EditProjectView = () => { - - - - - + + + ); @@ -151,35 +138,6 @@ export const EditProjectView = () => { return
{content}
; }; -const TreeItemContextMenuProvider = ({ children }: TreeItemContextMenuProviderProps) => { - const treeItemContextMenuContributions: TreeItemContextMenuContextValue = [ - - treeId.startsWith('explorer://') && item.kind.startsWith('siriusWeb://document') - } - component={DocumentTreeItemContextMenuContribution} - />, - - treeId.startsWith('explorer://') && item.kind.startsWith('siriusComponents://semantic') - } - component={ObjectTreeItemContextMenuContribution} - />, - - treeId.startsWith('explorer://') && item.kind === 'siriusComponents://representation?type=Diagram' - } - component={DiagramTreeItemContextMenuContribution} - />, - ]; - - return ( - - {children} - - ); -}; - const TreeToolBarProvider = ({ children }: TreeToolBarProviderProps) => { const treeToolBarContributions: TreeToolBarContextValue = [ , diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/ObjectTreeItemContextMenuContribution.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/ObjectTreeItemContextMenuContribution.tsx index cf4c8fcadd..cd788a2a4d 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/ObjectTreeItemContextMenuContribution.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/ObjectTreeItemContextMenuContribution.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2023 Obeo. + * Copyright (c) 2021, 2024 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,12 +24,16 @@ type Modal = 'CreateNewObject' | 'CreateNewRepresentation'; export const ObjectTreeItemContextMenuContribution = forwardRef( ( - { editingContextId, item, readOnly, expandItem, onClose }: TreeItemContextMenuComponentProps, + { editingContextId, treeId, item, readOnly, expandItem, onClose }: TreeItemContextMenuComponentProps, ref: React.ForwardedRef ) => { const [modal, setModal] = useState(null); const { setSelection } = useSelection(); + if (!treeId.startsWith('explorer://') || !item.kind.startsWith('siriusComponents://semantic')) { + return null; + } + const onObjectCreated = (selection: Selection) => { setSelection(selection); expandItem(); diff --git a/packages/trees/frontend/sirius-components-trees/src/index.ts b/packages/trees/frontend/sirius-components-trees/src/index.ts index db25705807..89bc33b575 100644 --- a/packages/trees/frontend/sirius-components-trees/src/index.ts +++ b/packages/trees/frontend/sirius-components-trees/src/index.ts @@ -14,10 +14,11 @@ export * from './toolbar/TreeToolBarContext'; export * from './toolbar/TreeToolBarContext.types'; export * from './toolbar/TreeToolBarContribution'; export * from './toolbar/TreeToolBarContribution.types'; +export * from './treeitems/TreeItemAction.types'; export * from './treeitems/TreeItemContextMenu'; export * from './treeitems/TreeItemContextMenu.types'; -export * from './treeitems/TreeItemContextMenuContribution'; -export * from './treeitems/TreeItemContextMenuContribution.types'; +export * from './treeitems/TreeItemContextMenuEntry.types'; +export * from './treeitems/TreeItemContextMenuEntryExtensionPoints'; export * from './treeitems/filterTreeItem'; export * from './views/ExplorerView'; export * from './views/TreeView'; diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx index 57fdea8b67..d7a94bbf44 100644 --- a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx @@ -17,19 +17,15 @@ import { SelectionEntry, useSelection, } from '@eclipse-sirius/sirius-components-core'; -import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import CropDinIcon from '@material-ui/icons/CropDin'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; -import UnfoldMoreIcon from '@material-ui/icons/UnfoldMore'; -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { TreeItemProps, TreeItemState } from './TreeItem.types'; import { TreeItemArrow } from './TreeItemArrow'; -import { TreeItemContextMenu, TreeItemContextMenuContext } from './TreeItemContextMenu'; -import { TreeItemContextMenuContextValue } from './TreeItemContextMenu.types'; import { TreeItemDirectEditInput } from './TreeItemDirectEditInput'; import { isFilterCandidate, splitText } from './filterTreeItem'; +import { TreeItemAction } from './TreeItemAction'; const useTreeItemStyle = makeStyles((theme) => ({ treeItem: { @@ -60,18 +56,6 @@ const useTreeItemStyle = makeStyles((theme) => ({ arrow: { cursor: 'pointer', }, - more: { - hover: { - backgroundColor: theme.palette.action.hover, - }, - focus: { - backgroundColor: theme.palette.action.selected, - }, - }, - expandIcon: { - marginLeft: 'auto', - marginRight: theme.spacing(1), - }, content: { display: 'grid', gridTemplateRows: '1fr', @@ -125,25 +109,18 @@ export const TreeItem = ({ textToFilter, enableMultiSelection, markedItemIds, + treeItemActionRender, }: TreeItemProps) => { const classes = useTreeItemStyle(); - const treeItemMenuContributionComponents = useContext(TreeItemContextMenuContext) - .filter((contribution) => contribution.props.canHandle(treeId, item)) - .map((contribution) => contribution.props.component); - const initialState: TreeItemState = { - showContextMenu: false, - menuAnchor: null, editingMode: false, - label: item.label, - prevSelectionId: null, editingKey: null, isHovered: false, }; const [state, setState] = useState(initialState); - const { showContextMenu, menuAnchor, editingMode } = state; + const { editingMode } = state; const refDom = useRef() as any; @@ -160,75 +137,24 @@ export const TreeItem = ({ return { ...prevState, isHovered: false }; }); }; - // Context menu handling - const openContextMenu = (event) => { - if (!showContextMenu) { - const { currentTarget } = event; - setState((prevState) => { - return { - showContextMenu: true, - menuAnchor: currentTarget, - editingMode: false, - editingKey: prevState.editingKey, - label: item.label, - prevSelectionId: prevState.prevSelectionId, - isHovered: prevState.isHovered, - }; - }); - } - }; - let contextMenu = null; - if (showContextMenu) { - const closeContextMenu = () => { - setState((prevState) => { - return { - modalDisplayed: null, - showContextMenu: false, - menuAnchor: null, - editingMode: false, - editingKey: prevState.editingKey, - label: item.label, - prevSelectionId: prevState.prevSelectionId, - isHovered: prevState.isHovered, - }; - }); - }; - const enterEditingMode = () => { - setState((prevState) => { - return { - modalDisplayed: null, - showContextMenu: false, - menuAnchor: null, - editingMode: true, - editingKey: null, - label: item.label, - prevSelectionId: prevState.prevSelectionId, - isHovered: prevState.isHovered, - }; - }); - }; + const onTreeItemAction = () => { + setState((prevState) => { + return { ...prevState, isHovered: false }; + }); + }; - contextMenu = ( - - ); - } + const enterEditingMode = () => { + setState((prevState) => ({ + ...prevState, + editingMode: true, + editingKey: null, + })); + }; - let children = null; + let content = null; if (item.expanded && item.children) { - children = ( + content = (
    {item.children.map((childItem) => { return ( @@ -245,6 +171,7 @@ export const TreeItem = ({ textToHighlight={textToHighlight} textToFilter={textToFilter} markedItemIds={markedItemIds} + treeItemActionRender={treeItemActionRender} /> ); @@ -409,8 +336,6 @@ export const TreeItem = ({ } } - const shouldDisplayMoreButton = item.deletable || item.editable || treeItemMenuContributionComponents.length > 0; - let currentTreeItem: JSX.Element | null; if (textToFilter && isFilterCandidate(item, textToFilter)) { currentTreeItem = null; @@ -447,32 +372,37 @@ export const TreeItem = ({ {image} {text} - {shouldDisplayMoreButton ? ( - - - - ) : null} +
    + {treeItemActionRender ? ( + treeItemActionRender({ + editingContextId: editingContextId, + treeId: treeId, + item: item, + depth: depth, + onExpand: onExpand, + onExpandAll: onExpandAll, + readOnly: readOnly, + onEnterEditingMode: enterEditingMode, + isHovered: state.isHovered, + }) + ) : ( + + )} +
    - {!shouldDisplayMoreButton && state.isHovered && item.hasChildren && ( - { - onExpandAll(item); - }}> - - - )} - {children} - {contextMenu} + {content} ); } diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.types.ts b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.types.ts index 772f1f7a20..40346ec187 100644 --- a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.types.ts @@ -11,6 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ import { GQLTreeItem } from '../views/TreeView.types'; +import { TreeItemActionProps } from './TreeItemAction.types'; export interface TreeItemProps { editingContextId: string; @@ -24,14 +25,11 @@ export interface TreeItemProps { textToFilter: string | null; enableMultiSelection: boolean; markedItemIds: string[]; + treeItemActionRender?: (props: TreeItemActionProps) => React.ReactNode; } export interface TreeItemState { - showContextMenu: boolean; - menuAnchor: Element | null; editingMode: boolean; - label: string; - prevSelectionId: string | null; editingKey: string | null; isHovered: boolean; } diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.tsx b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.tsx new file mode 100644 index 0000000000..9775076ec5 --- /dev/null +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.tsx @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2024 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 IconButton from '@material-ui/core/IconButton'; +import { makeStyles } from '@material-ui/core/styles'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { useState } from 'react'; +import { TreeItemActionProps, TreeItemActionState } from './TreeItemAction.types'; +import { TreeItemContextMenu } from './TreeItemContextMenu'; + +const useTreeItemActionStyle = makeStyles((theme) => ({ + more: { + hover: { + backgroundColor: theme.palette.action.hover, + }, + focus: { + backgroundColor: theme.palette.action.selected, + }, + }, +})); + +export const TreeItemAction = ({ + editingContextId, + treeId, + item, + readOnly, + depth, + onExpand, + onExpandAll, + onEnterEditingMode, +}: TreeItemActionProps) => { + const classes = useTreeItemActionStyle(); + const [state, setState] = useState({ + showContextMenu: false, + menuAnchor: null, + }); + + const openContextMenu = (event) => { + if (!state.showContextMenu) { + const { currentTarget } = event; + setState((prevState) => ({ + ...prevState, + showContextMenu: true, + menuAnchor: currentTarget, + })); + } + }; + + let contextMenu = null; + if (state.showContextMenu) { + const closeContextMenu = () => { + setState((prevState) => ({ + ...prevState, + showContextMenu: false, + menuAnchor: null, + })); + }; + const enterEditingMode = () => { + setState((prevState) => ({ + ...prevState, + showContextMenu: false, + menuAnchor: null, + })); + onEnterEditingMode(); + }; + + contextMenu = ( + + ); + } + + return ( + <> + + + + {contextMenu} + + ); +}; diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.types.ts b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.types.ts new file mode 100644 index 0000000000..5d6f79a8a4 --- /dev/null +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemAction.types.ts @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2024 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 { GQLTreeItem } from '../views/TreeView.types'; + +export interface TreeItemActionProps { + editingContextId: string; + treeId: string; + item: GQLTreeItem; + depth: number; + onExpand: (id: string, depth: number) => void; + onExpandAll: (treeItem: GQLTreeItem) => void; + onEnterEditingMode: () => void; + readOnly: boolean; + isHovered: boolean; +} + +export interface TreeItemActionState { + showContextMenu: boolean; + menuAnchor: Element | null; +} diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenu.tsx b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenu.tsx index db68279330..f6e72b1239 100644 --- a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenu.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenu.tsx @@ -11,7 +11,12 @@ * Obeo - initial API and implementation *******************************************************************************/ import { gql, useMutation } from '@apollo/client'; -import { Toast, useDeletionConfirmationDialog } from '@eclipse-sirius/sirius-components-core'; +import { + Toast, + useDeletionConfirmationDialog, + useComponents, + ComponentExtension, +} from '@eclipse-sirius/sirius-components-core'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Menu from '@material-ui/core/Menu'; @@ -19,18 +24,18 @@ import MenuItem from '@material-ui/core/MenuItem'; import DeleteIcon from '@material-ui/icons/Delete'; import EditIcon from '@material-ui/icons/Edit'; import UnfoldMore from '@material-ui/icons/UnfoldMore'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { GQLDeleteTreeItemData, GQLDeleteTreeItemInput, GQLDeleteTreeItemPayload, GQLDeleteTreeItemVariables, GQLErrorPayload, - TreeItemContextMenuContextValue, TreeItemContextMenuProps, TreeItemContextMenuState, } from './TreeItemContextMenu.types'; -import { TreeItemContextMenuComponentProps } from './TreeItemContextMenuContribution.types'; +import { treeItemContextMenuEntryExtensionPoint } from './TreeItemContextMenuEntryExtensionPoints'; +import { TreeItemContextMenuComponentProps } from './TreeItemContextMenuEntry.types'; const deleteTreeItemMutation = gql` mutation deleteTreeItem($input: DeleteTreeItemInput!) { @@ -46,15 +51,12 @@ const deleteTreeItemMutation = gql` const isErrorPayload = (payload: GQLDeleteTreeItemPayload): payload is GQLErrorPayload => payload.__typename === 'ErrorPayload'; -export const TreeItemContextMenuContext = React.createContext([]); - export const TreeItemContextMenu = ({ menuAnchor, editingContextId, treeId, item, readOnly, - treeItemMenuContributionComponents, depth, onExpand, onExpandAll, @@ -65,6 +67,10 @@ export const TreeItemContextMenu = ({ const { showDeletionConfirmation } = useDeletionConfirmationDialog(); + const treeItemMenuContextComponents: ComponentExtension[] = useComponents( + treeItemContextMenuEntryExtensionPoint + ); + const expandItem = () => { if (!item.expanded && item.hasChildren) { onExpand(item.id, depth); @@ -119,19 +125,17 @@ export const TreeItemContextMenu = ({ vertical: 'bottom', horizontal: 'right', }}> - {treeItemMenuContributionComponents.map((component, index) => { - const props: TreeItemContextMenuComponentProps = { - editingContextId, - item, - readOnly, - onClose, - expandItem, - key: index.toString(), - treeId: treeId, - }; - const element = React.createElement(component, props); - return element; - })} + {treeItemMenuContextComponents.map(({ Component: TreeItemMenuContextComponent }, index) => ( + + ))} {item.hasChildren ? ( []; export interface TreeItemContextMenuProps { menuAnchor: Element; @@ -24,7 +18,6 @@ export interface TreeItemContextMenuProps { editingContextId: string; treeId: string; readOnly: boolean; - treeItemMenuContributionComponents: ((props: TreeItemContextMenuComponentProps) => JSX.Element)[]; depth: number; onExpand: (id: string, depth: number) => void; onExpandAll: (treeItem: GQLTreeItem) => void; @@ -35,6 +28,7 @@ export interface TreeItemContextMenuProps { export interface GQLDeleteTreeItemData { deleteTreeItem: GQLDeleteTreeItemPayload; } + export interface GQLDeleteTreeItemPayload { __typename: string; } diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuContribution.types.ts b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuEntry.types.ts similarity index 76% rename from packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuContribution.types.ts rename to packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuEntry.types.ts index 1f4fe94c5c..6057659a56 100644 --- a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuContribution.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuEntry.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2023 Obeo. + * Copyright (c) 2024 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 @@ -12,11 +12,6 @@ *******************************************************************************/ import { GQLTreeItem } from '../views/TreeView.types'; -export interface TreeItemContextMenuContributionProps { - canHandle: (treeId: string, item: GQLTreeItem) => boolean; - component: (props: TreeItemContextMenuComponentProps) => JSX.Element | null; -} - export interface TreeItemContextMenuComponentProps { editingContextId: string; treeId: string; diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuContribution.tsx b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuEntryExtensionPoints.ts similarity index 55% rename from packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuContribution.tsx rename to packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuEntryExtensionPoints.ts index f30028d3fb..ecf778fc91 100644 --- a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuContribution.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemContextMenuEntryExtensionPoints.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2022 Obeo. + * Copyright (c) 2024 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,8 +10,11 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { TreeItemContextMenuContributionProps } from './TreeItemContextMenuContribution.types'; -export const TreeItemContextMenuContribution = ({}: TreeItemContextMenuContributionProps) => { - return null; // Do nothing on purpose for now +import { ComponentExtensionPoint } from '@eclipse-sirius/sirius-components-core'; +import { TreeItemContextMenuComponentProps } from './TreeItemContextMenuEntry.types'; + +export const treeItemContextMenuEntryExtensionPoint: ComponentExtensionPoint = { + identifier: 'treeItem#contextMenuEntry', + FallbackComponent: () => null, }; diff --git a/packages/trees/frontend/sirius-components-trees/src/trees/Tree.tsx b/packages/trees/frontend/sirius-components-trees/src/trees/Tree.tsx index 6e80d0ddb0..19c2a776ca 100644 --- a/packages/trees/frontend/sirius-components-trees/src/trees/Tree.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/trees/Tree.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2023 Obeo. + * Copyright (c) 2019, 2024 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 @@ -32,6 +32,7 @@ export const Tree = ({ textToHighlight, textToFilter, markedItemIds, + treeItemActionRender, }: TreeProps) => { const classes = useTreeStyle(); const treeElement = useRef(null); @@ -121,6 +122,7 @@ export const Tree = ({ textToHighlight={textToHighlight} textToFilter={textToFilter} markedItemIds={markedItemIds} + treeItemActionRender={treeItemActionRender} /> ))} diff --git a/packages/trees/frontend/sirius-components-trees/src/trees/Tree.types.ts b/packages/trees/frontend/sirius-components-trees/src/trees/Tree.types.ts index 0055c7f1b4..fee5521ca0 100644 --- a/packages/trees/frontend/sirius-components-trees/src/trees/Tree.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/trees/Tree.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, 2023 Obeo. + * Copyright (c) 2021, 2024 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 { GQLTree, GQLTreeItem } from '../views/TreeView.types'; +import { TreeItemActionProps } from '../treeitems/TreeItemAction.types'; export interface TreeProps { editingContextId: string; @@ -22,4 +23,6 @@ export interface TreeProps { textToHighlight: string | null; textToFilter: string | null; markedItemIds: string[]; + children?: React.ReactElement; + treeItemActionRender?: (props: TreeItemActionProps) => React.ReactNode; } diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx index 859f6aacba..77f07a839d 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx @@ -82,6 +82,7 @@ export const TreeView = ({ textToHighlight, textToFilter, markedItemIds = [], + treeItemActionRender, }: TreeViewComponentProps) => { const [{ value, context }, dispatch] = useMachine(treeViewMachine, { context: { @@ -240,6 +241,7 @@ export const TreeView = ({ markedItemIds={markedItemIds} textToFilter={textToFilter} textToHighlight={textToHighlight} + treeItemActionRender={treeItemActionRender} /> ) : null} diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts index c4e74e1a04..d31ffbe451 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts @@ -12,6 +12,7 @@ *******************************************************************************/ import { WorkbenchViewComponentProps } from '@eclipse-sirius/sirius-components-core'; +import { TreeItemActionProps } from '../treeitems/TreeItemAction.types'; export interface TreeViewComponentProps extends WorkbenchViewComponentProps { treeId: string; @@ -21,6 +22,7 @@ export interface TreeViewComponentProps extends WorkbenchViewComponentProps { textToHighlight: string | null; textToFilter: string | null; markedItemIds?: string[]; + treeItemActionRender?: (props: TreeItemActionProps) => React.ReactNode; } export interface TreeConverter {