From 923112a111e44de6fff6e6d47626b3deb149468e Mon Sep 17 00:00:00 2001 From: Gwendal Daniel Date: Thu, 12 Dec 2024 17:24:22 +0100 Subject: [PATCH] [889] Remove New Object/Representation menu entries for libraries Bug: https://github.com/eclipse-syson/syson/issues/889 Signed-off-by: Gwendal Daniel --- CHANGELOG.adoc | 1 + .../pages/release-notes/2025.1.0.adoc | 1 + ...ocumentTreeItemContextMenuContribution.tsx | 97 +++++++++++++++++ .../SysONExtensionRegistryMergeStrategy.ts | 42 ++++++++ ...NObjectTreeItemContextMenuContribution.tsx | 102 ++++++++++++++++++ frontend/syson/src/index.tsx | 14 +++ .../diagrams/diagramCreationTests.cy.ts | 51 +++++++++ .../semanticElementCreationTests.cy.ts | 51 +++++++++ integration-tests/cypress/usecases/SysMLv2.ts | 12 +++ .../cypress/workbench/Explorer.ts | 7 ++ 10 files changed, 378 insertions(+) create mode 100644 frontend/syson/src/extensions/SysONDocumentTreeItemContextMenuContribution.tsx create mode 100644 frontend/syson/src/extensions/SysONExtensionRegistryMergeStrategy.ts create mode 100644 frontend/syson/src/extensions/SysONObjectTreeItemContextMenuContribution.tsx diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 51025b31b..704a540e3 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -55,6 +55,7 @@ The explorer now contains the following directories for libraries: ** _User Libraries_: User-imported libraries - https://github.com/eclipse-syson/syson/issues/888[#888] [explorer] Remove 'New objects from text' contextual menu entry for libraries. - https://github.com/eclipse-syson/syson/issues/901[#901] [import] Allow to import `.kerml` textual files. +- https://github.com/eclipse-syson/syson/issues/889[#889] [explorer] Remove 'New object' and 'New representation' contextual menu entries for libraries. === New features diff --git a/doc/content/modules/user-manual/pages/release-notes/2025.1.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2025.1.0.adoc index f41932bdc..67301baa6 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2025.1.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2025.1.0.adoc @@ -57,6 +57,7 @@ image::release-notes-libraries-directories.png[Libraries Directories in the expl - Remove 'New objects from text' contextual menu entry for libraries. - Allow to import `.kerml` textual files. +- Remove 'New object' and 'New representation' contextual menu entries for libraries. == New features diff --git a/frontend/syson/src/extensions/SysONDocumentTreeItemContextMenuContribution.tsx b/frontend/syson/src/extensions/SysONDocumentTreeItemContextMenuContribution.tsx new file mode 100644 index 000000000..c80ea7963 --- /dev/null +++ b/frontend/syson/src/extensions/SysONDocumentTreeItemContextMenuContribution.tsx @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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 { Selection, ServerContext, ServerContextValue, useSelection } from '@eclipse-sirius/sirius-components-core'; +import { TreeItemContextMenuComponentProps } from '@eclipse-sirius/sirius-components-trees'; +import AddIcon from '@mui/icons-material/Add'; +import GetAppIcon from '@mui/icons-material/GetApp'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import MenuItem from '@mui/material/MenuItem'; +import { Fragment, forwardRef, useContext, useState } from 'react'; +import { NewRootObjectModal } from '@eclipse-sirius/sirius-web-application'; + +type Modal = 'CreateNewRootObject'; + +export const SysONDocumentTreeItemContextMenuContribution = forwardRef( + ( + { 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(); + onClose(); + }; + + let modalElement = null; + if (modal === 'CreateNewRootObject') { + modalElement = ( + + ); + } + + let menuItems = []; + if (item.editable) { + menuItems.push( + setModal('CreateNewRootObject')} + ref={ref} + disabled={readOnly} + aria-disabled> + + + + + + ); + } + menuItems.push( + + + + + + + ); + + return ( + + {menuItems} + {modalElement} + + ); + } +); diff --git a/frontend/syson/src/extensions/SysONExtensionRegistryMergeStrategy.ts b/frontend/syson/src/extensions/SysONExtensionRegistryMergeStrategy.ts new file mode 100644 index 000000000..a9bb7fa6d --- /dev/null +++ b/frontend/syson/src/extensions/SysONExtensionRegistryMergeStrategy.ts @@ -0,0 +1,42 @@ +/******************************************************************************* + * 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 { ComponentExtension, ExtensionRegistryMergeStrategy } from '@eclipse-sirius/sirius-components-core'; +import { DefaultExtensionRegistryMergeStrategy } from '@eclipse-sirius/sirius-web-application'; +import { treeItemContextMenuEntryExtensionPoint } from '@eclipse-sirius/sirius-components-trees'; + +export class SysONExtensionRegistryMergeStrategy + extends DefaultExtensionRegistryMergeStrategy + implements ExtensionRegistryMergeStrategy +{ + public override mergeComponentExtensions( + _identifier: string, + existingValues: ComponentExtension[], + newValues: ComponentExtension[] + ): ComponentExtension[] { + if (_identifier === treeItemContextMenuEntryExtensionPoint.identifier) { + return super.mergeComponentExtensions( + _identifier, + existingValues.filter((contribution) => { + // Discard default versions of these extensions, we want to replace them with our own. + return ( + contribution.identifier !== `siriusweb_${treeItemContextMenuEntryExtensionPoint.identifier}_object` && + contribution.identifier !== `siriusweb_${treeItemContextMenuEntryExtensionPoint.identifier}_document` + ); + }), + newValues + ); + } + return super.mergeComponentExtensions(_identifier, existingValues, newValues); + } +} diff --git a/frontend/syson/src/extensions/SysONObjectTreeItemContextMenuContribution.tsx b/frontend/syson/src/extensions/SysONObjectTreeItemContextMenuContribution.tsx new file mode 100644 index 000000000..264a98d28 --- /dev/null +++ b/frontend/syson/src/extensions/SysONObjectTreeItemContextMenuContribution.tsx @@ -0,0 +1,102 @@ +/******************************************************************************* + * 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 + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { Selection, useSelection } from '@eclipse-sirius/sirius-components-core'; +import { TreeItemContextMenuComponentProps } from '@eclipse-sirius/sirius-components-trees'; +import AddIcon from '@mui/icons-material/Add'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import MenuItem from '@mui/material/MenuItem'; +import { Fragment, forwardRef, useState } from 'react'; +import { NewObjectModal } from '@eclipse-sirius/sirius-web-application'; +import { NewRepresentationModal } from '@eclipse-sirius/sirius-web-application'; + +type Modal = 'CreateNewObject' | 'CreateNewRepresentation'; + +export const SysONObjectTreeItemContextMenuContribution = forwardRef( + ( + { 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(); + onClose(); + }; + + let modalElement = null; + if (modal === 'CreateNewObject') { + modalElement = ( + + ); + } else if (modal === 'CreateNewRepresentation') { + modalElement = ( + + ); + } + + let menuItems = []; + if (item.editable) { + menuItems.push( + setModal('CreateNewObject')} + data-testid="new-object" + disabled={readOnly} + ref={ref} + aria-disabled> + + + + + + ); + menuItems.push( + setModal('CreateNewRepresentation')} + data-testid="new-representation" + disabled={readOnly} + aria-disabled> + + + + + + ); + } + + return ( + + {menuItems} + {modalElement} + + ); + } +); diff --git a/frontend/syson/src/index.tsx b/frontend/syson/src/index.tsx index a4696e0e2..2f5fe9215 100644 --- a/frontend/syson/src/index.tsx +++ b/frontend/syson/src/index.tsx @@ -52,10 +52,13 @@ import { httpOrigin, wsOrigin } from './core/URL'; import { referenceWidgetDocumentTransform } from './extensions/ReferenceWidgetDocumentTransform'; import { SysONFooter } from './extensions/SysONFooter'; import { sysonTheme } from './theme/sysonTheme'; +import { SysONObjectTreeItemContextMenuContribution } from './extensions/SysONObjectTreeItemContextMenuContribution'; +import { SysONDocumentTreeItemContextMenuContribution } from './extensions/SysONDocumentTreeItemContextMenuContribution'; import './fonts.css'; import './reset.css'; import './variables.css'; +import { SysONExtensionRegistryMergeStrategy } from './extensions/SysONExtensionRegistryMergeStrategy'; if (process.env.NODE_ENV !== 'production') { loadDevMessages(); @@ -109,6 +112,16 @@ extensionRegistry.addComponent(diagramPanelActionExtensionPoint, { Component: SysONDiagramPanelMenu, }); +extensionRegistry.addComponent(treeItemContextMenuEntryExtensionPoint, { + identifier: `siriusweb_${treeItemContextMenuEntryExtensionPoint.identifier}_object`, + Component: SysONObjectTreeItemContextMenuContribution, +}); + +extensionRegistry.addComponent(treeItemContextMenuEntryExtensionPoint, { + identifier: `siriusweb_${treeItemContextMenuEntryExtensionPoint.identifier}_document`, + Component: SysONDocumentTreeItemContextMenuContribution, +}); + extensionRegistry.addComponent(treeItemContextMenuEntryExtensionPoint, { identifier: `syson${treeItemContextMenuEntryExtensionPoint.identifier}_insertTextualSysML`, Component: InsertTextualSysMLMenuContribution, @@ -147,6 +160,7 @@ root.render( httpOrigin={httpOrigin} wsOrigin={wsOrigin} theme={sysonTheme} + extensionRegistryMergeStrategy={new SysONExtensionRegistryMergeStrategy()} extensionRegistry={extensionRegistry}> diff --git a/integration-tests/cypress/e2e/project/diagrams/diagramCreationTests.cy.ts b/integration-tests/cypress/e2e/project/diagrams/diagramCreationTests.cy.ts index fb26f9dd4..45fde81fb 100644 --- a/integration-tests/cypress/e2e/project/diagrams/diagramCreationTests.cy.ts +++ b/integration-tests/cypress/e2e/project/diagrams/diagramCreationTests.cy.ts @@ -102,5 +102,56 @@ describe('Diagram Creation Tests', () => { diagram.getDiagram('actionFlowView').should('exist'); }); }); + + context('When we select the Libraries directory in the explorer', () => { + it('Then we cannot create a new representation in it', () => { + const explorer = new Explorer(); + explorer.getTreeItemByLabel(sysmlv2.getLibrariesLabel()).first().find('button').click(); + cy.getByTestId('new-representation').should('not.exist'); + }); + }); + + context('When we select the KerML directory in the explorer', () => { + it('Then we cannot create a new representation in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.getTreeItemByLabel(sysmlv2.getKerMLLabel()).first().find('button').click(); + cy.getByTestId('new-representation').should('not.exist'); + }); + }); + + context('When we select the SysML directory in the explorer', () => { + it('Then we cannot create a new representation in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.getTreeItemByLabel(sysmlv2.getSysMLLabel()).first().find('button').click(); + cy.getByTestId('new-representation').should('not.exist'); + }); + }); + + context('When we select the Base KerML model in the explorer', () => { + it('Then we cannot create a new representation in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.expand(sysmlv2.getKerMLLabel()); + explorer.getTreeItemByLabel('Base').first().find('button').click(); + cy.getByTestId('new-representation').should('not.exist'); + }); + }); + + context('When we select the Base KerML model top-level element in the explorer', () => { + it('Then we cannot create a new representation in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.expand(sysmlv2.getKerMLLabel()); + explorer.expand('Base'); + explorer + .getTreeItemByLabelAndKind('Base', 'siriusComponents://semantic?domain=sysml&entity=LibraryPackage') + .first() + .find('button') + .click(); + cy.getByTestId('new-representation').should('not.exist'); + }); + }); }); }); diff --git a/integration-tests/cypress/e2e/project/explorer/semanticElementCreationTests.cy.ts b/integration-tests/cypress/e2e/project/explorer/semanticElementCreationTests.cy.ts index 5b364131f..bf2e2026f 100644 --- a/integration-tests/cypress/e2e/project/explorer/semanticElementCreationTests.cy.ts +++ b/integration-tests/cypress/e2e/project/explorer/semanticElementCreationTests.cy.ts @@ -71,5 +71,56 @@ describe('Semantic Element Creation Tests', () => { explorer.getTreeItemByLabel('part').parents('li').first().siblings().contains('Package 1').should('exist'); }); }); + + context('When we select the Libraries directory in the explorer', () => { + it('Then we cannot create a new object in it', () => { + const explorer = new Explorer(); + explorer.getTreeItemByLabel(sysmlv2.getLibrariesLabel()).first().find('button').click(); + cy.getByTestId('new-object').should('not.exist'); + }); + }); + + context('When we select the KerML directory in the explorer', () => { + it('Then we cannot create a new object in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.getTreeItemByLabel(sysmlv2.getKerMLLabel()).first().find('button').click(); + cy.getByTestId('new-object').should('not.exist'); + }); + }); + + context('When we select the SysML directory in the explorer', () => { + it('Then we cannot create a new object in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.getTreeItemByLabel(sysmlv2.getSysMLLabel()).first().find('button').click(); + cy.getByTestId('new-object').should('not.exist'); + }); + }); + + context('When we select the Base KerML model in the explorer', () => { + it('Then we cannot create a new object in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.expand(sysmlv2.getKerMLLabel()); + explorer.getTreeItemByLabel('Base').first().find('button').click(); + cy.getByTestId('new-object').should('not.exist'); + }); + }); + + context('When we select the Base KerML model top-level element in the explorer', () => { + it('Then we cannot create a new object in it', () => { + const explorer = new Explorer(); + explorer.expand(sysmlv2.getLibrariesLabel()); + explorer.expand(sysmlv2.getKerMLLabel()); + explorer.expand('Base'); + explorer + .getTreeItemByLabelAndKind('Base', 'siriusComponents://semantic?domain=sysml&entity=LibraryPackage') + .first() + .find('button') + .click(); + cy.getByTestId('new-object').should('not.exist'); + }); + }); }); }); diff --git a/integration-tests/cypress/usecases/SysMLv2.ts b/integration-tests/cypress/usecases/SysMLv2.ts index 29bbdb306..24f949e2e 100644 --- a/integration-tests/cypress/usecases/SysMLv2.ts +++ b/integration-tests/cypress/usecases/SysMLv2.ts @@ -39,4 +39,16 @@ export class SysMLv2 { public getRootElementLabel(): string { return 'Package 1'; } + + public getLibrariesLabel(): string { + return 'Libraries'; + } + + public getKerMLLabel(): string { + return 'KerML'; + } + + public getSysMLLabel(): string { + return 'SysML'; + } } diff --git a/integration-tests/cypress/workbench/Explorer.ts b/integration-tests/cypress/workbench/Explorer.ts index 0f68dc5e0..b4730a464 100644 --- a/integration-tests/cypress/workbench/Explorer.ts +++ b/integration-tests/cypress/workbench/Explorer.ts @@ -20,6 +20,13 @@ export class Explorer { return this.getExplorerView().get(`[data-treeitemlabel="${treeItemLabel}"]`); } + public getTreeItemByLabelAndKind( + treeItemLabel: string, + treeItemKind: string + ): Cypress.Chainable> { + return this.getTreeItemByLabel(treeItemLabel).get(`[data-treeitemkind="${treeItemKind}"]`); + } + public getTreeItems(): Cypress.Chainable> { return this.getExplorerView().find('[data-treeitemid]'); }