diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 9cb34a5b75..d85214804a 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -31,6 +31,8 @@ - https://github.com/eclipse-sirius/sirius-web/issues/4110[#4110] [form] Add layout capabilities for form widgets - https://github.com/eclipse-sirius/sirius-web/issues/4177[#4177] [table] Add table representation support +- https://github.com/eclipse-sirius/sirius-web/issues/3990[#3990] [diagram] The palette can now be dragged +- https://github.com/eclipse-sirius/sirius-web/issues/3981[#3981] [diagram] The palette palette sections are showed as list === Improvements @@ -363,7 +365,6 @@ A migration participant has been added to automatically keep compatible all diag - https://github.com/eclipse-sirius/sirius-web/issues/3932[#3932] [forms] Do not display the 'Delete' button (even disabled) for non-deletable lists - https://github.com/eclipse-sirius/sirius-web/issues/3951[#3951] [sirius-web] Provide an error page to redirect users in case of error - https://github.com/eclipse-sirius/sirius-web/issues/3974[#3974] [diagram] Add support for `<` and `>` to trigger direct edit -- https://github.com/eclipse-sirius/sirius-web/issues/3990[#3990] [diagram] Make the diagram contextual palette draggable == v2024.7.0 diff --git a/packages/compatibility/backend/sirius-components-compatibility/src/main/java/org/eclipse/sirius/components/compatibility/services/diagrams/CompatibilityPaletteProvider.java b/packages/compatibility/backend/sirius-components-compatibility/src/main/java/org/eclipse/sirius/components/compatibility/services/diagrams/CompatibilityPaletteProvider.java index 6641939176..f01eb7abba 100644 --- a/packages/compatibility/backend/sirius-components-compatibility/src/main/java/org/eclipse/sirius/components/compatibility/services/diagrams/CompatibilityPaletteProvider.java +++ b/packages/compatibility/backend/sirius-components-compatibility/src/main/java/org/eclipse/sirius/components/compatibility/services/diagrams/CompatibilityPaletteProvider.java @@ -23,6 +23,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.sirius.components.collaborative.diagrams.api.DiagramImageConstants; import org.eclipse.sirius.components.collaborative.diagrams.api.IPaletteProvider; +import org.eclipse.sirius.components.collaborative.diagrams.dto.IPaletteEntry; import org.eclipse.sirius.components.collaborative.diagrams.dto.ITool; import org.eclipse.sirius.components.collaborative.diagrams.dto.Palette; import org.eclipse.sirius.components.collaborative.diagrams.dto.SingleClickOnDiagramElementTool; @@ -95,7 +96,7 @@ public Palette handle(Object targetElement, Object diagramElement, Object diagra .map(org.eclipse.sirius.diagram.description.DiagramDescription.class::cast) .findFirst(); - List toolSections = new ArrayList<>(); + List toolSections = new ArrayList<>(); if (optionalSiriusDiagramDescription.isPresent()) { org.eclipse.sirius.diagram.description.DiagramDescription siriusDiagramDescription = optionalSiriusDiagramDescription.get(); List filteredToolSections = this.getToolSectionFromDiagramDescriptionToolSection(diagramDescription).stream() @@ -107,7 +108,10 @@ public Palette handle(Object targetElement, Object diagramElement, Object diagra toolSections.addAll(this.createExtraToolSections(diagramElementDescription)); } String paletteId = "siriusComponents://palette?diagramId=" + optionalVsmElementId.get(); - return Palette.newPalette(paletteId).tools(List.of()).toolSections(toolSections).build(); + return Palette.newPalette(paletteId) + .quickAccessTools(List.of()) + .paletteEntries(toolSections) + .build(); } private List getToolSectionFromDiagramDescriptionToolSection(DiagramDescription diagramDescription) { diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/IPaletteEntry.java b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/IPaletteEntry.java new file mode 100644 index 0000000000..af350e71e5 --- /dev/null +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/IPaletteEntry.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.diagrams.dto; + +/** + * The common interface for elements displayed in the palette. + * + * @author fbarbin + */ +public interface IPaletteEntry { + + String id(); +} diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ITool.java b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ITool.java index 2b559bc19d..812761a861 100644 --- a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ITool.java +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ITool.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Obeo. + * Copyright (c) 2023, 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 @@ -19,9 +19,7 @@ * * @author mcharfadi */ -public interface ITool { - - String id(); +public interface ITool extends IPaletteEntry { String label(); diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/Palette.java b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/Palette.java index fbde2fb1fe..ae01f8b884 100644 --- a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/Palette.java +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/Palette.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Obeo. + * Copyright (c) 2023, 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 @@ -20,12 +20,12 @@ * * @author frouene */ -public record Palette(String id, List tools, List toolSections) { +public record Palette(String id, List quickAccessTools, List paletteEntries) { public Palette { Objects.requireNonNull(id); - Objects.requireNonNull(tools); - Objects.requireNonNull(toolSections); + Objects.requireNonNull(quickAccessTools); + Objects.requireNonNull(paletteEntries); } public static Builder newPalette(String id) { @@ -43,26 +43,26 @@ public static final class Builder { private final String id; - private List tools; + private List quickAccessTools; - private List toolSections; + private List paletteEntries; private Builder(String id) { this.id = Objects.requireNonNull(id); } - public Builder tools(List tools) { - this.tools = Objects.requireNonNull(tools); + public Builder quickAccessTools(List quickAccessTools) { + this.quickAccessTools = Objects.requireNonNull(quickAccessTools); return this; } - public Builder toolSections(List toolSections) { - this.toolSections = Objects.requireNonNull(toolSections); + public Builder paletteEntries(List paletteEntries) { + this.paletteEntries = Objects.requireNonNull(paletteEntries); return this; } public Palette build() { - return new Palette(this.id, this.tools, this.toolSections); + return new Palette(this.id, this.quickAccessTools, this.paletteEntries); } } diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/PaletteDivider.java b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/PaletteDivider.java new file mode 100644 index 0000000000..8e4f37ec6c --- /dev/null +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/PaletteDivider.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.diagrams.dto; + +import java.util.Objects; + +/** + * Represents a divider between tools or tool sections in the palette. + * + * @author fbarbin + */ +public record PaletteDivider(String id) implements IPaletteEntry { + + public PaletteDivider { + Objects.requireNonNull(id); + } +} diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnDiagramElementTool.java b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnDiagramElementTool.java index 790ebcb032..ba5bbdf035 100644 --- a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnDiagramElementTool.java +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnDiagramElementTool.java @@ -22,8 +22,13 @@ * * @author mcharfadi */ -public record SingleClickOnDiagramElementTool(String id, String label, List iconURL, List targetDescriptions, String dialogDescriptionId, - boolean appliesToDiagramRoot) implements ITool { +public record SingleClickOnDiagramElementTool( + String id, + String label, + List iconURL, + List targetDescriptions, + String dialogDescriptionId, + boolean appliesToDiagramRoot) implements ITool { public SingleClickOnDiagramElementTool { Objects.requireNonNull(id); diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnTwoDiagramElementsTool.java b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnTwoDiagramElementsTool.java index cdb8352342..ca84256bda 100644 --- a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnTwoDiagramElementsTool.java +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/SingleClickOnTwoDiagramElementsTool.java @@ -20,7 +20,12 @@ * * @author mcharfadi */ -public record SingleClickOnTwoDiagramElementsTool(String id, String label, List iconURL, List candidates, String dialogDescriptionId) implements ITool { +public record SingleClickOnTwoDiagramElementsTool( + String id, + String label, + List iconURL, + List candidates, + String dialogDescriptionId) implements ITool { public SingleClickOnTwoDiagramElementsTool { Objects.requireNonNull(id); diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ToolSection.java b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ToolSection.java index 3460f01694..91a91a91d9 100644 --- a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ToolSection.java +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/dto/ToolSection.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Obeo. + * Copyright (c) 2023, 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 @@ -20,7 +20,7 @@ * * @author mcharfadi */ -public record ToolSection(String id, String label, List iconURL, List tools) { +public record ToolSection(String id, String label, List iconURL, List tools) implements IPaletteEntry { public ToolSection { Objects.requireNonNull(id); diff --git a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/resources/schema/diagram.graphqls b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/resources/schema/diagram.graphqls index eda8b3ff3e..b51f74714e 100644 --- a/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/resources/schema/diagram.graphqls +++ b/packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/resources/schema/diagram.graphqls @@ -266,8 +266,18 @@ type Size { type Palette { id: ID! - tools: [Tool]! - toolSections: [ToolSection]! + quickAccessTools: [Tool]! + paletteEntries: [PaletteEntry]! +} + +union PaletteEntry = + ToolSection + | SingleClickOnDiagramElementTool + | SingleClickOnTwoDiagramElementsTool + | PaletteDivider + +type PaletteDivider { + id: ID! } type ToolSection { diff --git a/packages/diagrams/backend/sirius-components-diagrams-tests/src/main/java/org/eclipse/sirius/components/diagrams/tests/graphql/PaletteQueryRunner.java b/packages/diagrams/backend/sirius-components-diagrams-tests/src/main/java/org/eclipse/sirius/components/diagrams/tests/graphql/PaletteQueryRunner.java index 798a8b5869..1eec72eaf5 100644 --- a/packages/diagrams/backend/sirius-components-diagrams-tests/src/main/java/org/eclipse/sirius/components/diagrams/tests/graphql/PaletteQueryRunner.java +++ b/packages/diagrams/backend/sirius-components-diagrams-tests/src/main/java/org/eclipse/sirius/components/diagrams/tests/graphql/PaletteQueryRunner.java @@ -36,15 +36,18 @@ query getPalette($editingContextId: ID!, $representationId: ID!, $diagramElement ... on DiagramDescription { palette(diagramElementId: $diagramElementId) { id - tools { + quickAccessTools { ...ToolFields } - toolSections { - id - label - iconURL - tools { - ...ToolFields + paletteEntries { + ...ToolFields + ... on ToolSection { + id + label + iconURL + tools { + ...ToolFields + } } } } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts index e6cc73cce0..42f6233dce 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/index.ts @@ -58,10 +58,13 @@ export type { NodeContextValue } from './renderer/node/NodeContext.types'; export { NodeTypeContribution } from './renderer/node/NodeTypeContribution'; export type { DiagramNodeType } from './renderer/node/NodeTypes.types'; export { DiagramElementPalette } from './renderer/palette/DiagramElementPalette'; -export type { DiagramPaletteToolContributionComponentProps } from './renderer/palette/DiagramPaletteToolContribution.types'; -export type { GQLToolVariable, GQLToolVariableType } from './renderer/palette/Palette.types'; -export type { DiagramPaletteToolComponentProps } from './renderer/palette/tool/DiagramPaletteTool.types'; -export { diagramPaletteToolExtensionPoint } from './renderer/palette/tool/DiagramPaletteToolExtensionPoints'; +export type { DiagramPaletteToolComponentProps } from './renderer/palette/extensions/DiagramPaletteTool.types'; +export type { + DiagramPaletteToolContributionComponentProps, + DiagramPaletteToolContributionProps, +} from './renderer/palette/extensions/DiagramPaletteToolContribution.types'; +export { diagramPaletteToolExtensionPoint } from './renderer/palette/extensions/DiagramPaletteToolExtensionPoints'; +export type { GQLToolVariable, GQLToolVariableType } from './renderer/palette/usePalette.types'; export type { DiagramPanelActionProps } from './renderer/panel/DiagramPanel.types'; export { diagramPanelActionExtensionPoint } from './renderer/panel/DiagramPanelExtensionPoints'; export { DiagramRepresentation } from './representation/DiagramRepresentation'; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.types.ts index 6ef329f0cd..cf5386761f 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/connector/useConnector.types.ts @@ -60,14 +60,24 @@ export interface GQLDiagramDescription extends GQLRepresentationDescription { export interface GQLPalette { id: string; + paletteEntries: GQLPaletteEntry[]; +} + +export interface GQLPaletteEntry { + id: string; + __typename: string; +} +export interface GQLPaletteDivider extends GQLPaletteEntry {} + +export interface GQLToolSection extends GQLPaletteEntry { + label: string; + iconURL: string[]; tools: GQLTool[]; - toolSections: GQLToolSection[]; } export interface GQLToolSection { id: string; label: string; - imageURL: string; tools: GQLTool[]; __typename: string; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/delete/useDiagramDelete.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/delete/useDiagramDelete.tsx index bd9bb809f2..8c4b0e57d9 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/delete/useDiagramDelete.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/delete/useDiagramDelete.tsx @@ -25,7 +25,7 @@ import { GQLDeleteFromDiagramVariables, GQLDeletionPolicy, GQLErrorPayload, -} from '../palette/Palette.types'; +} from '../palette/usePalette.types'; import { UseDiagramDeleteValue } from './useDiagramDelete.types'; export const deleteFromDiagramMutation = gql` diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useConnectionCandidatesQuery.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useConnectionCandidatesQuery.ts index 8221e39aab..7cbc4baa6e 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useConnectionCandidatesQuery.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/handles/useConnectionCandidatesQuery.ts @@ -19,11 +19,12 @@ import { GQLGetToolSectionsData, GQLGetToolSectionsVariables, GQLNodeDescription, + GQLPaletteEntry, GQLRepresentationDescription, GQLSingleClickOnTwoDiagramElementsTool, + GQLToolSection, } from '../connector/useConnector.types'; import { NodeData } from '../DiagramRenderer.types'; -import { GQLTool } from '../palette/Palette.types'; const getToolSectionsQuery = gql` query getToolSections($editingContextId: ID!, $diagramId: ID!, $diagramElementId: ID!) { @@ -33,12 +34,12 @@ const getToolSectionsQuery = gql` description { ... on DiagramDescription { palette(diagramElementId: $diagramElementId) { - tools { + paletteEntries { ...ToolFields - } - toolSections { - tools { - ...ToolFields + ... on ToolSection { + tools { + ...ToolFields + } } } } @@ -68,9 +69,10 @@ const getToolSectionsQuery = gql` const isDiagramDescription = ( representationDescription: GQLRepresentationDescription ): representationDescription is GQLDiagramDescription => representationDescription.__typename === 'DiagramDescription'; -const isSingleClickOnTwoDiagramElementsTool = (tool: GQLTool): tool is GQLSingleClickOnTwoDiagramElementsTool => - tool.__typename === 'SingleClickOnTwoDiagramElementsTool'; - +const isSingleClickOnTwoDiagramElementsTool = ( + entry: GQLPaletteEntry +): entry is GQLSingleClickOnTwoDiagramElementsTool => entry.__typename === 'SingleClickOnTwoDiagramElementsTool'; +const isToolSection = (entry: GQLPaletteEntry): entry is GQLToolSection => entry.__typename === 'ToolSection'; export const useConnectionCandidatesQuery = ( editingContextId: string, diagramId: string, @@ -90,17 +92,24 @@ export const useConnectionCandidatesQuery = ( const diagramDescription: GQLRepresentationDescription | null = data?.viewer.editingContext.representation.description ?? null; - const nodeCandidates: GQLNodeDescription[] = useMemo(() => { + const collectConnectionToolsFromPaletteEntry = (entry: GQLPaletteEntry): GQLNodeDescription[] => { const candidates: GQLNodeDescription[] = []; - if (diagramDescription && isDiagramDescription(diagramDescription)) { - diagramDescription.palette.tools.filter(isSingleClickOnTwoDiagramElementsTool).forEach((tool) => { + if (isSingleClickOnTwoDiagramElementsTool(entry)) { + entry.candidates.forEach((candidate) => candidates.push(...candidate.targets)); + } else if (isToolSection(entry)) { + entry.tools.filter(isSingleClickOnTwoDiagramElementsTool).forEach((tool) => { tool.candidates.forEach((candidate) => candidates.push(...candidate.targets)); }); - diagramDescription.palette.toolSections.forEach((toolSection) => { - toolSection.tools.filter(isSingleClickOnTwoDiagramElementsTool).forEach((tool) => { - tool.candidates.forEach((candidate) => candidates.push(...candidate.targets)); - }); - }); + } + return candidates; + }; + + const nodeCandidates: GQLNodeDescription[] = useMemo(() => { + const candidates: GQLNodeDescription[] = []; + if (diagramDescription && isDiagramDescription(diagramDescription)) { + diagramDescription.palette.paletteEntries.forEach((entry) => + candidates.push(...collectConnectionToolsFromPaletteEntry(entry)) + ); } return candidates; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPalette.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPalette.tsx index 4643b04905..0dc91cb9fd 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPalette.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPalette.tsx @@ -52,7 +52,6 @@ export const DiagramElementPalette = memo( diagramElementId={diagramElementId} targetObjectId={targetObjectId} onDirectEditClick={handleDirectEditClick} - hideableDiagramElement /> ) : null; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.tsx index 61605f270d..be89cd7b2c 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.tsx @@ -11,216 +11,89 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { gql, useMutation, useQuery } from '@apollo/client'; -import { - ComponentExtension, - useComponents, - useDeletionConfirmationDialog, - useMultiToast, -} from '@eclipse-sirius/sirius-components-core'; -import AdjustIcon from '@mui/icons-material/Adjust'; -import DirectionsOffIcon from '@mui/icons-material/DirectionsOff'; -import TonalityIcon from '@mui/icons-material/Tonality'; +import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; import Box from '@mui/material/Box'; import Divider from '@mui/material/Divider'; -import IconButton from '@mui/material/IconButton'; import Paper from '@mui/material/Paper'; -import Tooltip from '@mui/material/Tooltip'; -import { Edge, Node, useReactFlow, useViewport } from '@xyflow/react'; -import { useCallback, useContext, useEffect, useState } from 'react'; -import Draggable from 'react-draggable'; +import { Edge, Node, useStoreApi, useViewport, XYPosition } from '@xyflow/react'; +import React, { useEffect, useState } from 'react'; +import Draggable, { DraggableData } from 'react-draggable'; import { makeStyles } from 'tss-react/mui'; -import { DiagramContext } from '../../contexts/DiagramContext'; -import { DiagramContextValue } from '../../contexts/DiagramContext.types'; -import { useDialog } from '../../dialog/useDialog'; -import { PinIcon } from '../../icons/PinIcon'; -import { UnpinIcon } from '../../icons/UnpinIcon'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; -import { Tool } from '../Tool'; -import { useAdjustSize } from '../adjust-size/useAdjustSize'; -import { useEditableEdgePath } from '../edge/useEditableEdgePath'; -import { useFadeDiagramElements } from '../fade/useFadeDiagramElements'; -import { usePinDiagramElements } from '../pin/usePinDiagramElements'; import { - GQLCollapsingState, - GQLDeleteFromDiagramData, - GQLDeleteFromDiagramInput, - GQLDeleteFromDiagramPayload, - GQLDeleteFromDiagramSuccessPayload, - GQLDeleteFromDiagramVariables, - GQLDeletionPolicy, - GQLDiagramDescription, - GQLErrorPayload, - GQLGetToolSectionsData, - GQLGetToolSectionsVariables, - GQLInvokeSingleClickOnDiagramElementToolData, - GQLInvokeSingleClickOnDiagramElementToolInput, - GQLInvokeSingleClickOnDiagramElementToolPayload, - GQLInvokeSingleClickOnDiagramElementToolSuccessPayload, - GQLInvokeSingleClickOnDiagramElementToolVariables, GQLPalette, - GQLRepresentationDescription, + GQLPaletteDivider, + GQLPaletteEntry, GQLSingleClickOnDiagramElementTool, - GQLTool, - GQLToolVariable, - GQLUpdateCollapsingStateData, - GQLUpdateCollapsingStateInput, - GQLUpdateCollapsingStateVariables, + GQLToolSection, PaletteProps, - PaletteState, + PaletteStyleProps, } from './Palette.types'; -import { ToolSection } from './tool-section/ToolSection'; -import { DiagramPaletteToolComponentProps } from './tool/DiagramPaletteTool.types'; -import { diagramPaletteToolExtensionPoint } from './tool/DiagramPaletteToolExtensionPoints'; +import { PaletteQuickAccessToolBar } from './quick-access-tool/PaletteQuickAccessToolBar'; +import { PaletteToolList } from './tool-list/PaletteToolList'; +import { usePalette } from './usePalette'; -const usePaletteStyle = makeStyles()((theme) => ({ +const usePaletteStyle = makeStyles()((theme, props) => ({ palette: { + display: 'grid', + gridAutoRows: `fit-content(100%)`, border: `1px solid ${theme.palette.divider}`, - borderRadius: '2px', + borderRadius: '10px', zIndex: 5, position: 'fixed', - maxWidth: theme.spacing(45.25), + width: props.paletteWidth, + height: props.paletteHeight, }, paletteHeader: { cursor: 'move', width: '100%', - height: '24px', - }, - quickAccessTools: { display: 'flex', - flexWrap: 'wrap', flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', - }, - toolIcon: { - width: theme.spacing(4.5), - color: theme.palette.text.primary, + color: theme.palette.grey[400], }, })); -const ToolFields = gql` - fragment ToolFields on Tool { - __typename - id - label - iconURL - ... on SingleClickOnDiagramElementTool { - targetDescriptions { - id - } - appliesToDiagramRoot - dialogDescriptionId - } - } -`; - -export const getPaletteQuery = gql` - ${ToolFields} - query getPalette($editingContextId: ID!, $diagramId: ID!, $diagramElementId: ID!) { - viewer { - editingContext(editingContextId: $editingContextId) { - representation(representationId: $diagramId) { - description { - ... on DiagramDescription { - palette(diagramElementId: $diagramElementId) { - id - tools { - ...ToolFields - } - toolSections { - id - label - iconURL - tools { - ...ToolFields - } - } - } - } - } - } - } - } - } -`; +export const isSingleClickOnDiagramElementTool = (tool: GQLPaletteEntry): tool is GQLSingleClickOnDiagramElementTool => + tool.__typename === 'SingleClickOnDiagramElementTool'; -const invokeSingleClickOnDiagramElementToolMutation = gql` - mutation invokeSingleClickOnDiagramElementTool($input: InvokeSingleClickOnDiagramElementToolInput!) { - invokeSingleClickOnDiagramElementTool(input: $input) { - __typename - ... on InvokeSingleClickOnDiagramElementToolSuccessPayload { - newSelection { - entries { - id - kind - } - } - messages { - body - level - } - } - ... on ErrorPayload { - messages { - body - level - } - } - } - } -`; +export const isToolSection = (entry: GQLPaletteEntry): entry is GQLToolSection => entry.__typename === 'ToolSection'; -export const deleteFromDiagramMutation = gql` - mutation deleteFromDiagram($input: DeleteFromDiagramInput!) { - deleteFromDiagram(input: $input) { - __typename - ... on ErrorPayload { - messages { - body - level - } - } - ... on DeleteFromDiagramSuccessPayload { - messages { - body - level - } - } - } - } -`; +export const isPaletteDivider = (entry: GQLPaletteDivider): entry is GQLToolSection => + entry.__typename === 'PaletteDivider'; -const updateCollapsingStateMutation = gql` - mutation updateCollapsingState($input: UpdateCollapsingStateInput!) { - updateCollapsingState(input: $input) { - __typename - ... on SuccessPayload { - id - } - ... on ErrorPayload { - message - } - } - } -`; +const computeDraggableBounds = (bounds?: DOMRect): XYPosition => { + return { + x: bounds?.width ?? 0, + y: bounds?.height ?? 0, + }; +}; -const isErrorPayload = ( - payload: GQLDeleteFromDiagramPayload | GQLInvokeSingleClickOnDiagramElementToolPayload -): payload is GQLErrorPayload => payload.__typename === 'ErrorPayload'; -const isDeleteSuccessPayload = (payload: GQLDeleteFromDiagramPayload): payload is GQLDeleteFromDiagramSuccessPayload => - payload.__typename === 'DeleteFromDiagramSuccessPayload'; -const isInvokeSingleClickSuccessPayload = ( - payload: GQLInvokeSingleClickOnDiagramElementToolPayload -): payload is GQLInvokeSingleClickOnDiagramElementToolSuccessPayload => - payload.__typename === 'InvokeSingleClickOnDiagramElementToolSuccessPayload'; +const paletteWidth = 200; +const paletteHeight = 275; -const isSingleClickOnDiagramElementTool = (tool: GQLTool): tool is GQLSingleClickOnDiagramElementTool => - tool.__typename === 'SingleClickOnDiagramElementTool'; +const getPaletteToolCount = (palette: GQLPalette): number => { + return ( + palette.paletteEntries.filter(isSingleClickOnDiagramElementTool).length + + palette.quickAccessTools.filter(isSingleClickOnDiagramElementTool).length + + palette.paletteEntries + .filter(isToolSection) + .filter((toolSection) => toolSection.tools.filter(isSingleClickOnDiagramElementTool).length > 0).length + ); +}; -const isDiagramDescription = ( - representationDescription: GQLRepresentationDescription -): representationDescription is GQLDiagramDescription => representationDescription.__typename === 'DiagramDescription'; +const computePaletteLocation = ( + paletteX: number, + paletteY: number, + viewportWidth: number, + viewportHeight: number +): XYPosition => { + return { + x: paletteX + paletteWidth < viewportWidth ? paletteX : viewportWidth - paletteWidth, + y: paletteY + paletteHeight < viewportHeight ? paletteY : viewportHeight - paletteHeight, + }; +}; export const Palette = ({ x: paletteX, @@ -228,111 +101,13 @@ export const Palette = ({ diagramElementId, targetObjectId, onDirectEditClick, - hideableDiagramElement, }: PaletteProps) => { - const [state, setState] = useState({ expandedToolSectionId: null }); + const { domNode, nodeLookup, edgeLookup } = useStoreApi, Edge>().getState(); + const { x: viewportWidth, y: viewportHeight } = computeDraggableBounds(domNode?.getBoundingClientRect()); - const { fadeDiagramElements } = useFadeDiagramElements(); - const { pinDiagramElements } = usePinDiagramElements(); - const { adjustSize } = useAdjustSize(); - const { removeEdgeLayoutData } = useEditableEdgePath(); - const { getNodes, getEdges } = useReactFlow, Edge>(); - const { diagramId, editingContextId } = useContext(DiagramContext); - - const { addErrorMessage, addMessages } = useMultiToast(); - const { showDeletionConfirmation } = useDeletionConfirmationDialog(); - const { showDialog } = useDialog(); - - const paletteToolComponents: ComponentExtension[] = useComponents( - diagramPaletteToolExtensionPoint - ); - - const { data: paletteData, error: paletteError } = useQuery( - getPaletteQuery, - { - variables: { - editingContextId, - diagramId, - diagramElementId, - }, - } - ); - - const description: GQLRepresentationDescription | undefined = - paletteData?.viewer.editingContext.representation.description; - const palette: GQLPalette | null = description && isDiagramDescription(description) ? description.palette : null; - const node = getNodes().find((node) => node.id === diagramElementId); - - const toolCount = - (palette - ? palette.tools.filter(isSingleClickOnDiagramElementTool).length + - palette.toolSections.filter( - (toolSection) => toolSection.tools.filter(isSingleClickOnDiagramElementTool).length > 0 - ).length - : 0) + - (hideableDiagramElement ? (node ? 3 : 1) : 0) + - paletteToolComponents.length; - const { classes } = usePaletteStyle(); - - let pinUnpinTool: JSX.Element | undefined; - let adjustSizeTool: JSX.Element | undefined; - if (node) { - pinUnpinTool = node.data.pinned ? ( - - pinDiagramElements([diagramElementId], !node.data.pinned)} - data-testid="Unpin-element"> - - - - ) : ( - - pinDiagramElements([diagramElementId], true)} - data-testid="Pin-element"> - - - - ); - adjustSizeTool = ( - - adjustSize(diagramElementId)} - data-testid="adjust-element"> - - - - ); - } - - let resetEditedEdgePath: JSX.Element | undefined; - const edge = getEdges().find((edge) => edge.id === diagramElementId); - if (edge) { - if (edge.data?.bendingPoints) { - resetEditedEdgePath = ( - - removeEdgeLayoutData(diagramElementId)} - data-testid="Reset-path"> - - - - ); - } - } + const diagramElement = nodeLookup.get(diagramElementId) || edgeLookup.get(diagramElementId); + const [controlledPosition, setControlledPosition] = useState({ x: 0, y: 0 }); let x: number = 0; let y: number = 0; const { x: viewportX, y: viewportY, zoom: viewportZoom } = useViewport(); @@ -340,199 +115,57 @@ export const Palette = ({ x = (paletteX - viewportX) / viewportZoom; y = (paletteY - viewportY) / viewportZoom; } + const { handleToolClick, palette } = usePalette({ x, y, diagramElementId, onDirectEditClick, targetObjectId }); useEffect(() => { - if (paletteError) { - addErrorMessage('An unexpected error has occurred, please refresh the page'); - } - }, [paletteError]); - - const handleToolSectionExpand = (expandedToolSectionId: string | null) => - setState((prevState) => ({ ...prevState, expandedToolSectionId })); - - const [deleteElementsMutation] = useMutation( - deleteFromDiagramMutation - ); - - const [invokeSingleClickOnDiagramElementTool] = useMutation< - GQLInvokeSingleClickOnDiagramElementToolData, - GQLInvokeSingleClickOnDiagramElementToolVariables - >(invokeSingleClickOnDiagramElementToolMutation); - - const invokeSingleClickTool = useCallback( - async (tool: GQLTool, variables: GQLToolVariable[]) => { - if (isSingleClickOnDiagramElementTool(tool)) { - const { id: toolId } = tool; - const input: GQLInvokeSingleClickOnDiagramElementToolInput = { - id: crypto.randomUUID(), - editingContextId, - representationId: diagramId, - diagramElementId, - toolId, - startingPositionX: x, - startingPositionY: y, - variables, - }; - - const { data } = await invokeSingleClickOnDiagramElementTool({ - variables: { input }, - }); - if (data) { - const { invokeSingleClickOnDiagramElementTool } = data; - if (isInvokeSingleClickSuccessPayload(invokeSingleClickOnDiagramElementTool)) { - addMessages(invokeSingleClickOnDiagramElementTool.messages); - } - if (isErrorPayload(invokeSingleClickOnDiagramElementTool)) { - addMessages(invokeSingleClickOnDiagramElementTool.messages); - } - } - } - }, - [ - editingContextId, - diagramId, - diagramElementId, - invokeSingleClickOnDiagramElementToolMutation, - isSingleClickOnDiagramElementTool, - ] - ); - - const invokeDelete = (diagramElementId: string, deletionPolicy: GQLDeletionPolicy) => { - const nodeId = getNodes().find((node) => node.id === diagramElementId); - if (nodeId) { - invokeDeleteMutation([diagramElementId], [], deletionPolicy); - } else { - const edgeId = getEdges().find((edge) => edge.id === diagramElementId); - if (edgeId) { - invokeDeleteMutation([], [diagramElementId], deletionPolicy); - } - } - }; - - const invokeDeleteMutation = useCallback( - async (nodeIds: string[], edgeIds: string[], deletionPolicy: GQLDeletionPolicy) => { - const input: GQLDeleteFromDiagramInput = { - id: crypto.randomUUID(), - editingContextId, - representationId: diagramId, - nodeIds, - edgeIds, - deletionPolicy, - }; - const { data } = await deleteElementsMutation({ variables: { input } }); - if (data) { - const { deleteFromDiagram } = data; - if (isErrorPayload(deleteFromDiagram) || isDeleteSuccessPayload(deleteFromDiagram)) { - addMessages(deleteFromDiagram.messages); - } - } - }, - [editingContextId, diagramId, deleteElementsMutation] - ); + const paletteLocation: XYPosition = computePaletteLocation(paletteX, paletteY, viewportWidth, viewportHeight); + setControlledPosition(paletteLocation); + }, [paletteX, paletteY, viewportWidth, viewportHeight]); - const [collapseExpandMutation] = useMutation( - updateCollapsingStateMutation - ); - - const collapseExpandElement = useCallback( - (nodeId: string, collapsingState: GQLCollapsingState) => { - const input: GQLUpdateCollapsingStateInput = { - id: crypto.randomUUID(), - editingContextId, - representationId: diagramId, - diagramElementId: nodeId, - collapsingState, - }; - collapseExpandMutation({ variables: { input } }); - }, - [editingContextId, diagramId, collapseExpandMutation] - ); + const { classes } = usePaletteStyle({ paletteWidth: `${paletteWidth}px`, paletteHeight: `${paletteHeight}px` }); - const handleDialogDescription = (tool: GQLSingleClickOnDiagramElementTool) => { - const onConfirm = (variables: GQLToolVariable[]) => { - invokeSingleClickTool(tool, variables); - }; - showDialog(tool.dialogDescriptionId, [{ name: 'targetObjectId', value: targetObjectId }], onConfirm, () => {}); - }; + const shouldRender = palette && (getPaletteToolCount(palette) > 0 || !!diagramElement); - const handleToolClick = (tool: GQLTool) => { - switch (tool.id) { - case 'edit': - onDirectEditClick(); - break; - case 'semantic-delete': - showDeletionConfirmation(() => { - invokeDelete(diagramElementId, GQLDeletionPolicy.SEMANTIC); - }); - break; - case 'graphical-delete': - invokeDelete(diagramElementId, GQLDeletionPolicy.GRAPHICAL); - break; - case 'expand': - collapseExpandElement(diagramElementId, GQLCollapsingState.EXPANDED); - break; - case 'collapse': - collapseExpandElement(diagramElementId, GQLCollapsingState.COLLAPSED); - break; - default: - if (isSingleClickOnDiagramElementTool(tool)) { - if (tool.dialogDescriptionId) { - handleDialogDescription(tool); - } else { - invokeSingleClickTool(tool, []); - } - } - break; - } - }; - - const invokeFadeDiagramElementTool = () => { - fadeDiagramElements([diagramElementId], true); - }; - - const shouldRender = palette && (node || (!node && toolCount > 0)); if (!shouldRender) { return null; } + const onPaletteDragStop = (_event, data: DraggableData) => { + setControlledPosition(data); + }; + + const nodeRef = React.createRef(); + const draggableBounds = { + left: 0, + top: 0, + bottom: viewportHeight - paletteHeight, + right: viewportWidth - paletteWidth, + }; return ( - - - - - - {palette?.tools.filter(isSingleClickOnDiagramElementTool).map((tool) => ( - - ))} - {palette?.toolSections.map((toolSection) => ( - - ))} - {paletteToolComponents.map(({ Component: PaletteToolComponent }, index) => ( - - ))} - {hideableDiagramElement ? ( - <> - - - - - - {pinUnpinTool} - {adjustSizeTool} - - ) : null} + + event.stopPropagation()}> + + + + + ); diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.types.ts index b8f21ceadc..0be3141858 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/Palette.types.ts @@ -10,7 +10,6 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { GQLMessage } from '../Tool.types'; export interface ContextualPaletteStyleProps { toolCount: number; @@ -37,163 +36,38 @@ export interface PaletteProps { diagramElementId: string; targetObjectId: string; onDirectEditClick: () => void; - hideableDiagramElement?: boolean; } -export interface PaletteState { - expandedToolSectionId: string | null; +export interface PaletteStyleProps { + paletteWidth: string; + paletteHeight: string; } -export interface GQLErrorPayload - extends GQLInvokeSingleClickOnDiagramElementToolPayload, - GQLDeleteFromDiagramPayload, - GQLUpdateCollapsingStatePayload { - message: string; - messages: GQLMessage[]; -} - -export interface GQLInvokeSingleClickOnDiagramElementToolData { - invokeSingleClickOnDiagramElementTool: GQLInvokeSingleClickOnDiagramElementToolPayload; -} - -export interface GQLInvokeSingleClickOnDiagramElementToolPayload { +export interface GQLTool extends GQLPaletteEntry { + label: string; + iconURL: string[]; __typename: string; } -export interface GQLInvokeSingleClickOnDiagramElementToolSuccessPayload - extends GQLInvokeSingleClickOnDiagramElementToolPayload { - id: string; - newSelection: GQLWorkbenchSelection; - messages: GQLMessage[]; -} - -export interface GQLWorkbenchSelection { - entries: GQLWorkbenchSelectionEntry[]; -} - -export interface GQLWorkbenchSelectionEntry { - id: string; - kind: string; -} - -export interface GQLInvokeSingleClickOnDiagramElementToolVariables { - input: GQLInvokeSingleClickOnDiagramElementToolInput; -} - -export interface GQLInvokeSingleClickOnDiagramElementToolInput { - id: string; - editingContextId: string; - representationId: string; - diagramElementId: string; - toolId: string; - startingPositionX: number; - startingPositionY: number; - variables: GQLToolVariable[]; -} - export interface GQLSingleClickOnDiagramElementTool extends GQLTool { appliesToDiagramRoot: boolean; dialogDescriptionId: string; } -export interface GQLGetToolSectionsVariables { - editingContextId: string; - diagramId: string; - diagramElementId: string; -} - -export interface GQLGetToolSectionsData { - viewer: GQLViewer; -} - -export interface GQLViewer { - editingContext: GQLEditingContext; -} - -export interface GQLEditingContext { - representation: GQLRepresentationMetadata; -} - -export interface GQLRepresentationMetadata { +export interface GQLPalette { id: string; - label: string; - kind: string; - description: GQLRepresentationDescription; + quickAccessTools: GQLTool[]; + paletteEntries: GQLPaletteEntry[]; } -export interface GQLRepresentationDescription { +export interface GQLPaletteEntry { id: string; __typename: string; } +export interface GQLPaletteDivider extends GQLPaletteEntry {} -export interface GQLDiagramDescription extends GQLRepresentationDescription { - palette: GQLPalette; -} - -export interface GQLPalette { - id: string; - tools: GQLTool[]; - toolSections: GQLToolSection[]; -} - -export interface GQLToolSection { - id: string; +export interface GQLToolSection extends GQLPaletteEntry { label: string; iconURL: string[]; tools: GQLTool[]; } - -export interface GQLDeleteFromDiagramVariables { - input: GQLDeleteFromDiagramInput; -} - -export interface GQLDeleteFromDiagramInput { - id: string; - editingContextId: string; - representationId: string; - nodeIds: string[]; - edgeIds: string[]; - deletionPolicy: GQLDeletionPolicy; -} - -export interface GQLDeleteFromDiagramData { - deleteFromDiagram: GQLDeleteFromDiagramPayload; -} - -export interface GQLDeleteFromDiagramPayload { - __typename: string; -} - -export interface GQLDeleteFromDiagramSuccessPayload extends GQLDeleteFromDiagramPayload { - messages: GQLMessage[]; -} - -export enum GQLDeletionPolicy { - SEMANTIC = 'SEMANTIC', - GRAPHICAL = 'GRAPHICAL', -} - -export enum GQLCollapsingState { - EXPANDED = 'EXPANDED', - COLLAPSED = 'COLLAPSED', -} - -export interface GQLUpdateCollapsingStateVariables { - input: GQLUpdateCollapsingStateInput; -} - -export interface GQLUpdateCollapsingStateInput { - id: string; - editingContextId: string; - representationId: string; - diagramElementId: string; - collapsingState: GQLCollapsingState; -} - -export interface GQLUpdateCollapsingStateData { - collapseExpandDiagramElement: GQLUpdateCollapsingStatePayload; -} - -export interface GQLUpdateCollapsingStatePayload { - __typename: string; -} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPaletteContext.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramElementPaletteContext.tsx similarity index 100% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPaletteContext.tsx rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramElementPaletteContext.tsx diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPaletteContext.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramElementPaletteContext.types.ts similarity index 100% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramElementPaletteContext.types.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramElementPaletteContext.types.ts diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteContext.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramPaletteContext.tsx similarity index 98% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteContext.tsx rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramPaletteContext.tsx index 55e15f9a03..1efcc5f301 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteContext.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramPaletteContext.tsx @@ -12,13 +12,13 @@ *******************************************************************************/ import React, { useCallback, useState } from 'react'; +import { GQLTool } from '../Palette.types'; import { DiagramPaletteContextProviderProps, DiagramPaletteContextProviderState, DiagramPaletteContextValue, ToolSectionWithLastTool, } from './DiagramPaletteContext.types'; -import { GQLTool } from './Palette.types'; const defaultValue: DiagramPaletteContextValue = { x: null, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteContext.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramPaletteContext.types.ts similarity index 96% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteContext.types.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramPaletteContext.types.ts index 3682be927d..8eec2f7772 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteContext.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/contexts/DiagramPaletteContext.types.ts @@ -11,7 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { GQLTool } from './Palette.types'; +import { GQLTool } from '../Palette.types'; export interface DiagramPaletteContextValue { x: number | null; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool/DiagramPaletteTool.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteTool.types.ts similarity index 100% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool/DiagramPaletteTool.types.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteTool.types.ts diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteToolContribution.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteToolContribution.types.ts similarity index 73% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteToolContribution.types.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteToolContribution.types.ts index 4b936658a3..2a8a42f7c2 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/DiagramPaletteToolContribution.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteToolContribution.types.ts @@ -11,9 +11,12 @@ * Obeo - initial API and implementation *******************************************************************************/ +import { Edge, Node } from '@xyflow/react'; +import { EdgeData, NodeData } from '../../DiagramRenderer.types'; + export interface DiagramPaletteToolContributionProps { - canHandle: (diagramId: string, diagramElementId: string) => boolean; - component: (props: DiagramPaletteToolContributionComponentProps) => JSX.Element | null; + canHandle: (element: Node | Edge) => boolean; + component: React.ComponentType; } export interface DiagramPaletteToolContributionComponentProps { diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool/DiagramPaletteToolExtensionPoints.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteToolExtensionPoints.ts similarity index 64% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool/DiagramPaletteToolExtensionPoints.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteToolExtensionPoints.ts index 57d42f4a01..d27f7e47ba 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool/DiagramPaletteToolExtensionPoints.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/extensions/DiagramPaletteToolExtensionPoints.ts @@ -10,10 +10,11 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { ComponentExtensionPoint } from '@eclipse-sirius/sirius-components-core'; -import { DiagramPaletteToolComponentProps } from './DiagramPaletteTool.types'; -export const diagramPaletteToolExtensionPoint: ComponentExtensionPoint = { +import { DataExtensionPoint } from '@eclipse-sirius/sirius-components-core'; +import { DiagramPaletteToolContributionProps } from './DiagramPaletteToolContribution.types'; + +export const diagramPaletteToolExtensionPoint: DataExtensionPoint> = { identifier: 'diagramPalette#tool', - FallbackComponent: () => null, + fallback: [], }; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/GroupPalette.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/GroupPalette.tsx index 915a63e8b6..5fd737c0ad 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/GroupPalette.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/GroupPalette.tsx @@ -41,8 +41,8 @@ import { useDistributeElements } from '../../layout/useDistributeElements'; import { ListNodeData } from '../../node/ListNode.types'; import { usePinDiagramElements } from '../../pin/usePinDiagramElements'; import { PalettePortal } from '../PalettePortal'; -import { PaletteTool } from '../PaletteTool'; import { GroupPaletteProps, GroupPaletteSectionTool, GroupPaletteState } from './GroupPalette.types'; +import { PaletteTool } from './PaletteTool'; const usePaletteStyle = makeStyles()((theme) => ({ palette: { diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/PaletteTool.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/PaletteTool.tsx similarity index 100% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/PaletteTool.tsx rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/PaletteTool.tsx diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/PaletteTool.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/PaletteTool.types.ts similarity index 100% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/PaletteTool.types.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/group-tool/PaletteTool.types.ts diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/AdjustSizeTool.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/AdjustSizeTool.tsx new file mode 100644 index 0000000000..9c6c463c77 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/AdjustSizeTool.tsx @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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 AdjustIcon from '@mui/icons-material/Adjust'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import { makeStyles } from 'tss-react/mui'; +import { useAdjustSize } from '../../adjust-size/useAdjustSize'; +import { AdjustSizeToolProps } from './AdjustSizeTool.types'; + +const useStyle = makeStyles()((theme) => ({ + toolIcon: { + width: theme.spacing(4.5), + color: theme.palette.text.primary, + }, +})); + +export const AdjustSizeTool = ({ diagramElementId }: AdjustSizeToolProps) => { + const { classes } = useStyle(); + const { adjustSize } = useAdjustSize(); + return ( + + adjustSize(diagramElementId)} + data-testid="adjust-element"> + + + + ); +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/AdjustSizeTool.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/AdjustSizeTool.types.ts new file mode 100644 index 0000000000..5f6057e1f3 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/AdjustSizeTool.types.ts @@ -0,0 +1,15 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +export interface AdjustSizeToolProps { + diagramElementId: string; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/FadeElementTool.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/FadeElementTool.tsx new file mode 100644 index 0000000000..471961c383 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/FadeElementTool.tsx @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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 TonalityIcon from '@mui/icons-material/Tonality'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import { makeStyles } from 'tss-react/mui'; +import { useFadeDiagramElements } from '../../fade/useFadeDiagramElements'; +import { FadeElementToolProps } from './FadeElementTool.types'; + +const useStyle = makeStyles()((theme) => ({ + toolIcon: { + width: theme.spacing(4.5), + color: theme.palette.text.primary, + }, +})); + +export const FadeElementTool = ({ diagramElementId, isFaded }: FadeElementToolProps) => { + const { classes } = useStyle(); + const { fadeDiagramElements } = useFadeDiagramElements(); + if (isFaded) { + return ( + + fadeDiagramElements([diagramElementId], false)} + data-testid="Fade-element"> + + + + ); + } else { + return ( + + fadeDiagramElements([diagramElementId], true)} + data-testid="Fade-element"> + + + + ); + } +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/FadeElementTool.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/FadeElementTool.types.ts new file mode 100644 index 0000000000..acf11ca803 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/FadeElementTool.types.ts @@ -0,0 +1,16 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +export interface FadeElementToolProps { + diagramElementId: string; + isFaded: boolean; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PaletteQuickAccessToolBar.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PaletteQuickAccessToolBar.tsx new file mode 100644 index 0000000000..ced9c2bc1e --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PaletteQuickAccessToolBar.tsx @@ -0,0 +1,114 @@ +/******************************************************************************* + * 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 { DataExtension, useData } from '@eclipse-sirius/sirius-components-core'; +import Box from '@mui/material/Box'; +import Divider from '@mui/material/Divider'; +import { Edge, Node, useStoreApi } from '@xyflow/react'; +import { makeStyles } from 'tss-react/mui'; +import { EdgeData, NodeData } from '../../DiagramRenderer.types'; +import { diagramPaletteToolExtensionPoint } from '../extensions/DiagramPaletteToolExtensionPoints'; +import { Tool } from './../../Tool'; +import { DiagramPaletteToolContributionProps } from './../extensions/DiagramPaletteToolContribution.types'; +import { AdjustSizeTool } from './AdjustSizeTool'; +import { FadeElementTool } from './FadeElementTool'; +import { PaletteQuickAccessToolBarProps } from './PaletteQuickAccessToolBar.types'; +import { PinUnPinTool } from './PinUnPinTool'; +import { ResetEditedEdgePathTool } from './ResetEditedEdgePathTool'; +const isPinnable = (diagramElement: Node | Edge): diagramElement is Node => { + return !!diagramElement.data && 'pinned' in diagramElement.data; +}; +const isFadable = (diagramElement: Node | Edge): diagramElement is Node => { + return !!diagramElement.data && 'faded' in diagramElement.data; +}; +const isBendable = (diagramElement: Node | Edge): diagramElement is Edge => { + return !!diagramElement.data && 'bendingPoints' in diagramElement.data && !!diagramElement.data.bendingPoints; +}; + +const useStyle = makeStyles()(() => ({ + quickAccessTools: { + display: 'flex', + flexWrap: 'nowrap', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + overflowX: 'auto', + }, +})); + +export const PaletteQuickAccessToolBar = ({ + diagramElementId, + quickAccessTools, + onToolClick, + x, + y, +}: PaletteQuickAccessToolBarProps) => { + const { classes } = useStyle(); + + const { nodeLookup, edgeLookup } = useStoreApi, Edge>().getState(); + const diagramElement = nodeLookup.get(diagramElementId) || edgeLookup.get(diagramElementId); + + const quickAccessToolComponents: JSX.Element[] = []; + quickAccessTools.forEach((tool) => + quickAccessToolComponents.push() + ); + + if (diagramElement) { + if (isPinnable(diagramElement)) { + quickAccessToolComponents.push( + + ); + } + if (isFadable(diagramElement)) { + quickAccessToolComponents.push( + + ); + } + if (isBendable(diagramElement)) { + quickAccessToolComponents.push( + + ); + } + + quickAccessToolComponents.push(); + + const paletteToolData: DataExtension = useData( + diagramPaletteToolExtensionPoint + ); + + paletteToolData.data + .filter((data) => data.canHandle(diagramElement)) + .map((data) => data.component) + .forEach((PaletteToolComponent, index) => + quickAccessToolComponents.push( + + ) + ); + } + + if (quickAccessToolComponents.length > 0) { + return ( + <> + {quickAccessToolComponents} + + + ); + } else { + return null; + } +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PaletteQuickAccessToolBar.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PaletteQuickAccessToolBar.types.ts new file mode 100644 index 0000000000..5b75a18206 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PaletteQuickAccessToolBar.types.ts @@ -0,0 +1,22 @@ +/******************************************************************************* + * 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 { GQLTool } from '../Palette.types'; + +export interface PaletteQuickAccessToolBarProps { + x: number; + y: number; + diagramElementId: string; + onToolClick: (tool: GQLTool) => void; + quickAccessTools: GQLTool[]; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PinUnPinTool.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PinUnPinTool.tsx new file mode 100644 index 0000000000..aaf4cc799f --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PinUnPinTool.tsx @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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 '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import { makeStyles } from 'tss-react/mui'; +import { PinIcon } from '../../../icons/PinIcon'; +import { UnpinIcon } from '../../../icons/UnpinIcon'; +import { usePinDiagramElements } from '../../pin/usePinDiagramElements'; +import { PinUnPinToolProps } from './PinUnPinTool.types'; + +const useStyle = makeStyles()((theme) => ({ + toolIcon: { + width: theme.spacing(4.5), + color: theme.palette.text.primary, + }, +})); + +export const PinUnPinTool = ({ diagramElementId, isPined }: PinUnPinToolProps) => { + const { classes } = useStyle(); + const { pinDiagramElements } = usePinDiagramElements(); + if (isPined) { + return ( + + pinDiagramElements([diagramElementId], false)} + data-testid="Unpin-element"> + + + + ); + } else { + return ( + + pinDiagramElements([diagramElementId], true)} + data-testid="Pin-element"> + + + + ); + } +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PinUnPinTool.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PinUnPinTool.types.ts new file mode 100644 index 0000000000..c960c1c9b4 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/PinUnPinTool.types.ts @@ -0,0 +1,16 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +export interface PinUnPinToolProps { + diagramElementId: string; + isPined: boolean; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/ResetEditedEdgePathTool.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/ResetEditedEdgePathTool.tsx new file mode 100644 index 0000000000..31a4b862c6 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/ResetEditedEdgePathTool.tsx @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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 DirectionsOffIcon from '@mui/icons-material/DirectionsOff'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import { makeStyles } from 'tss-react/mui'; +import { useEditableEdgePath } from '../../edge/useEditableEdgePath'; +import { ResetEditedEdgePathToolProps } from './ResetEditedEdgePathTool.types'; + +const useStyle = makeStyles()((theme) => ({ + toolIcon: { + width: theme.spacing(4.5), + color: theme.palette.text.primary, + }, +})); + +export const ResetEditedEdgePathTool = ({ diagramElementId }: ResetEditedEdgePathToolProps) => { + const { classes } = useStyle(); + const { removeEdgeLayoutData } = useEditableEdgePath(); + return ( + + removeEdgeLayoutData(diagramElementId)} + data-testid="Reset-path"> + + + + ); +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/ResetEditedEdgePathTool.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/ResetEditedEdgePathTool.types.ts new file mode 100644 index 0000000000..464209744e --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/quick-access-tool/ResetEditedEdgePathTool.types.ts @@ -0,0 +1,15 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +export interface ResetEditedEdgePathToolProps { + diagramElementId: string; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolList.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolList.tsx new file mode 100644 index 0000000000..ffc5b087ca --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolList.tsx @@ -0,0 +1,167 @@ +/******************************************************************************* + * 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 { IconOverlay } from '@eclipse-sirius/sirius-components-core'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import Box from '@mui/material/Box'; +import Divider from '@mui/material/Divider'; +import List from '@mui/material/List'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import Slide from '@mui/material/Slide'; +import Tooltip from '@mui/material/Tooltip'; +import React, { useState } from 'react'; +import { makeStyles } from 'tss-react/mui'; +import { isPaletteDivider, isSingleClickOnDiagramElementTool, isToolSection } from '../Palette'; +import { GQLPaletteEntry, GQLTool, GQLToolSection } from '../Palette.types'; +import { PaletteToolListProps, PaletteToolListStateValue } from './PaletteToolList.types'; +import { PaletteToolSectionList } from './PaletteToolSectionList'; + +const useStyle = makeStyles()((theme) => ({ + toolListContainer: { + display: 'grid', + overflowY: 'auto', + overflowX: 'hidden', + gridTemplateColumns: '100%', + }, + toolList: { + gridRowStart: 1, + gridColumnStart: 1, + width: '100%', + padding: 0, + }, + listItemText: { + '& .MuiListItemText-primary': { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + }, + listItemButton: { + paddingTop: 0, + paddingBottom: 0, + }, + listItemIcon: { + minWidth: 0, + marginRight: theme.spacing(2), + }, +})); + +export const PaletteToolList = ({ palette, onToolClick }: PaletteToolListProps) => { + const defaultValue: PaletteToolListStateValue = { + toolSection: null, + }; + + const [state, setState] = useState(defaultValue); + + const tooltipDelay = 750; + const tooltipPlacement = 'left'; + + const handleToolSectionClick = (event: React.MouseEvent, toolSection: GQLToolSection) => { + event.stopPropagation(); + setState((prevState) => ({ ...prevState, toolSection })); + }; + + const handleToolClick = (event: React.MouseEvent, tool: GQLTool) => { + event.stopPropagation(); + onToolClick(tool); + }; + + const onBackToMainList = () => { + setState((prevState) => ({ ...prevState, toolSection: null })); + }; + + const { classes } = useStyle(); + const convertPaletteEntry = (paletteEntry: GQLPaletteEntry): JSX.Element | null => { + let jsxElement: JSX.Element | null = null; + if (isSingleClickOnDiagramElementTool(paletteEntry)) { + jsxElement = ( + + handleToolClick(event, paletteEntry)} + data-testid={`tool-${paletteEntry.label}`}> + + + + + + + ); + } else if (isToolSection(paletteEntry)) { + jsxElement = ( + + handleToolSectionClick(event, paletteEntry)} + data-testid={`toolSection-${paletteEntry.label}`}> + + + + + ); + } else if (isPaletteDivider(paletteEntry)) { + jsxElement = ; + } + return jsxElement; + }; + + const containerRef = React.useRef(null); + return ( + + {palette.paletteEntries.filter(isToolSection).map((entry) => ( + +
+ +
+
+ ))} + + + {palette?.paletteEntries.map(convertPaletteEntry)} + + +
+ ); +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolList.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolList.types.ts new file mode 100644 index 0000000000..8ec69a6098 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolList.types.ts @@ -0,0 +1,23 @@ +/******************************************************************************* + * 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 { GQLPalette, GQLTool, GQLToolSection } from '../Palette.types'; + +export interface PaletteToolListProps { + onToolClick: (tool: GQLTool) => void; + palette: GQLPalette; +} + +export interface PaletteToolListStateValue { + toolSection: GQLToolSection | null; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolSectionList.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolSectionList.tsx new file mode 100644 index 0000000000..c3b63079e8 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolSectionList.tsx @@ -0,0 +1,103 @@ +/******************************************************************************* + * 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 { IconOverlay } from '@eclipse-sirius/sirius-components-core'; +import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore'; +import List from '@mui/material/List'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import Tooltip from '@mui/material/Tooltip'; +import { makeStyles } from 'tss-react/mui'; +import { GQLTool } from '../Palette.types'; +import { PaletteToolSectionListProps } from './PaletteToolSectionList.types'; + +const useStyle = makeStyles()((theme) => ({ + toolListItemIcon: { + minWidth: 0, + marginRight: 16, + }, + toolListItemButton: { + paddingTop: 0, + paddingBottom: 0, + }, + toolList: { + width: '100%', + padding: 0, + }, + listItemText: { + '& .MuiListItemText-primary': { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + }, + sectionTitleListItemText: { + '& .MuiListItemText-primary': { + fontWeight: theme.typography.fontWeightBold, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + }, +})); + +export const PaletteToolSectionList = ({ + toolSection, + onToolClick, + onBackToMainList, + tooltipDelay, + tooltipPlacement, +}: PaletteToolSectionListProps) => { + const { classes } = useStyle(); + + const handleBackToMainListClick = (event: React.MouseEvent): void => { + event.stopPropagation(); + onBackToMainList(); + }; + + const handleOnToolClick = (event: React.MouseEvent, tool: GQLTool): void => { + event.stopPropagation(); + onToolClick(tool); + }; + + return ( + + + + + + + + {toolSection?.tools.map((tool) => ( + + handleOnToolClick(event, tool)} + data-testid={`tool-${tool.label}`}> + + + + + + + ))} + + ); +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-section/ToolSection.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolSectionList.types.ts similarity index 71% rename from packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-section/ToolSection.types.ts rename to packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolSectionList.types.ts index 763a2a9c16..7eb40e1f7b 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-section/ToolSection.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-list/PaletteToolSectionList.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022, 2024 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,15 +10,13 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ +import { TooltipProps } from '@mui/material/Tooltip'; import { GQLTool, GQLToolSection } from '../Palette.types'; -export interface ToolSectionProps { - toolSection: GQLToolSection; +export interface PaletteToolSectionListProps { onToolClick: (tool: GQLTool) => void; - toolSectionExpandId: string | null; - onExpand: (toolSectionId: string | null) => void; -} - -export interface ToolSectionState { - expanded: boolean; + onBackToMainList: () => void; + toolSection: GQLToolSection; + tooltipDelay?: TooltipProps['enterDelay']; + tooltipPlacement?: TooltipProps['placement']; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-section/ToolSection.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-section/ToolSection.tsx deleted file mode 100644 index f0b23f5d97..0000000000 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/tool-section/ToolSection.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2023, 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 ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import ClickAwayListener from '@mui/material/ClickAwayListener'; -import Paper from '@mui/material/Paper'; -import Popper from '@mui/material/Popper'; -import { useCallback, useRef } from 'react'; -import { makeStyles } from 'tss-react/mui'; -import { Tool } from '../../Tool'; -import { GQLSingleClickOnDiagramElementTool, GQLTool } from '../Palette.types'; -import { useDiagramPalette } from '../useDiagramPalette'; -import { ToolSectionProps } from './ToolSection.types'; - -const useToolSectionStyles = makeStyles()((theme) => ({ - toolSection: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - width: theme.spacing(4.5), - }, - toolList: { - padding: '4px', - border: `1px solid ${theme.palette.divider}`, - borderRadius: '2px', - width: 'max-content', - }, - arrow: { - cursor: 'pointer', - height: '14px', - width: '14px', - marginLeft: '-4px', - marginTop: '12px', - }, -})); - -const isSingleClickOnDiagramElementTool = (tool: GQLTool): tool is GQLSingleClickOnDiagramElementTool => - tool.__typename === 'SingleClickOnDiagramElementTool'; - -export const ToolSection = ({ toolSection, onToolClick, toolSectionExpandId, onExpand }: ToolSectionProps) => { - const tools = toolSection.tools.filter(isSingleClickOnDiagramElementTool); - const { getLastToolInvoked, setLastToolInvoked } = useDiagramPalette(); - - const { classes } = useToolSectionStyles(); - - const onActiveTool = useCallback( - (tool) => { - onToolClick(tool); - setLastToolInvoked(toolSection.id, tool); - }, - [onToolClick, setLastToolInvoked, toolSection.id] - ); - - const anchorRef = useRef(null); - let caretContent: JSX.Element | undefined; - if (tools.length > 1) { - caretContent = ( - { - event.stopPropagation(); - onExpand(toolSectionExpandId === toolSection.id ? null : toolSection.id); - }} - data-testid="expand" - ref={anchorRef} - /> - ); - } - - const onMouseEnter = () => { - if (tools.length > 1) { - onExpand(toolSection.id); - } - }; - - const checkLastToolInvoked = (): GQLTool | undefined => { - const lastToolInvoked = getLastToolInvoked(toolSection.id); - if (lastToolInvoked && tools.some((tool) => tool.id === lastToolInvoked.id)) { - return lastToolInvoked; - } - return undefined; - }; - - const defaultTool: GQLTool | undefined = checkLastToolInvoked() || tools[0]; - - return ( - <> - {defaultTool && ( -
- onToolClick(defaultTool)} thumbnail /> - {caretContent} -
- )} - - - onExpand(null)}> -
- {tools.map((tool) => ( - onActiveTool(tool)} key={tool.id} /> - ))} -
-
-
-
- - ); -}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramElementPalette.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramElementPalette.tsx index 649c072acc..d9eef5464b 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramElementPalette.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramElementPalette.tsx @@ -15,8 +15,8 @@ import { useSelection } from '@eclipse-sirius/sirius-components-core'; import { Edge, Node, XYPosition, useStoreApi } from '@xyflow/react'; import { useCallback, useContext } from 'react'; import { EdgeData, NodeData } from '../DiagramRenderer.types'; -import { DiagramElementPaletteContext } from './DiagramElementPaletteContext'; -import { DiagramElementPaletteContextValue } from './DiagramElementPaletteContext.types'; +import { DiagramElementPaletteContext } from './contexts/DiagramElementPaletteContext'; +import { DiagramElementPaletteContextValue } from './contexts/DiagramElementPaletteContext.types'; import { UseDiagramElementPaletteValue } from './useDiagramElementPalette.types'; const computePalettePosition = (event: MouseEvent | React.MouseEvent, bounds: DOMRect | undefined): XYPosition => { diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramPalette.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramPalette.tsx index 2e17cdae07..04d5d3b6fc 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramPalette.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/useDiagramPalette.tsx @@ -13,8 +13,8 @@ import { XYPosition, useStoreApi } from '@xyflow/react'; import { useCallback, useContext } from 'react'; -import { DiagramPaletteContext } from './DiagramPaletteContext'; -import { DiagramPaletteContextValue } from './DiagramPaletteContext.types'; +import { DiagramPaletteContext } from './contexts/DiagramPaletteContext'; +import { DiagramPaletteContextValue } from './contexts/DiagramPaletteContext.types'; import { UseDiagramPaletteValue } from './useDiagramPalette.types'; const computePalettePosition = (event: MouseEvent | React.MouseEvent, bounds?: DOMRect): XYPosition => { diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/usePalette.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/usePalette.tsx new file mode 100644 index 0000000000..4ef0caf29d --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/usePalette.tsx @@ -0,0 +1,334 @@ +/******************************************************************************* + * 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 { gql, useMutation, useQuery } from '@apollo/client'; +import { useDeletionConfirmationDialog, useMultiToast } from '@eclipse-sirius/sirius-components-core'; +import { Edge, Node, useStoreApi } from '@xyflow/react'; +import { useCallback, useContext, useEffect } from 'react'; +import { DiagramContext } from '../../contexts/DiagramContext'; +import { DiagramContextValue } from '../../contexts/DiagramContext.types'; +import { useDialog } from '../../dialog/useDialog'; +import { EdgeData, NodeData } from '../DiagramRenderer.types'; +import { GQLPalette, GQLSingleClickOnDiagramElementTool, GQLTool } from './Palette.types'; + +import { + GQLCollapsingState, + GQLDeleteFromDiagramData, + GQLDeleteFromDiagramInput, + GQLDeleteFromDiagramPayload, + GQLDeleteFromDiagramSuccessPayload, + GQLDeleteFromDiagramVariables, + GQLDeletionPolicy, + GQLDiagramDescription, + GQLErrorPayload, + GQLGetToolSectionsData, + GQLGetToolSectionsVariables, + GQLInvokeSingleClickOnDiagramElementToolData, + GQLInvokeSingleClickOnDiagramElementToolInput, + GQLInvokeSingleClickOnDiagramElementToolPayload, + GQLInvokeSingleClickOnDiagramElementToolSuccessPayload, + GQLInvokeSingleClickOnDiagramElementToolVariables, + GQLRepresentationDescription, + GQLToolVariable, + GQLUpdateCollapsingStateData, + GQLUpdateCollapsingStateInput, + GQLUpdateCollapsingStateVariables, + UsePaletteProps, + UsePaletteValue, +} from './usePalette.types'; + +export const getPaletteQuery = gql` + fragment ToolFields on Tool { + __typename + id + label + iconURL + ... on SingleClickOnDiagramElementTool { + targetDescriptions { + id + } + appliesToDiagramRoot + dialogDescriptionId + } + } + query getPalette($editingContextId: ID!, $diagramId: ID!, $diagramElementId: ID!) { + viewer { + editingContext(editingContextId: $editingContextId) { + representation(representationId: $diagramId) { + description { + ... on DiagramDescription { + palette(diagramElementId: $diagramElementId) { + id + quickAccessTools { + ...ToolFields + } + paletteEntries { + ...ToolFields + ... on ToolSection { + id + label + iconURL + tools { + ...ToolFields + } + } + } + } + } + } + } + } + } + } +`; + +const invokeSingleClickOnDiagramElementToolMutation = gql` + mutation invokeSingleClickOnDiagramElementTool($input: InvokeSingleClickOnDiagramElementToolInput!) { + invokeSingleClickOnDiagramElementTool(input: $input) { + __typename + ... on InvokeSingleClickOnDiagramElementToolSuccessPayload { + messages { + body + level + } + } + ... on ErrorPayload { + messages { + body + level + } + } + } + } +`; + +export const deleteFromDiagramMutation = gql` + mutation deleteFromDiagram($input: DeleteFromDiagramInput!) { + deleteFromDiagram(input: $input) { + __typename + ... on ErrorPayload { + messages { + body + level + } + } + ... on DeleteFromDiagramSuccessPayload { + messages { + body + level + } + } + } + } +`; + +const updateCollapsingStateMutation = gql` + mutation updateCollapsingState($input: UpdateCollapsingStateInput!) { + updateCollapsingState(input: $input) { + __typename + ... on SuccessPayload { + id + } + ... on ErrorPayload { + message + } + } + } +`; + +const isErrorPayload = ( + payload: GQLDeleteFromDiagramPayload | GQLInvokeSingleClickOnDiagramElementToolPayload +): payload is GQLErrorPayload => payload.__typename === 'ErrorPayload'; +const isDeleteSuccessPayload = (payload: GQLDeleteFromDiagramPayload): payload is GQLDeleteFromDiagramSuccessPayload => + payload.__typename === 'DeleteFromDiagramSuccessPayload'; +const isInvokeSingleClickSuccessPayload = ( + payload: GQLInvokeSingleClickOnDiagramElementToolPayload +): payload is GQLInvokeSingleClickOnDiagramElementToolSuccessPayload => + payload.__typename === 'InvokeSingleClickOnDiagramElementToolSuccessPayload'; + +const isSingleClickOnDiagramElementTool = (tool: GQLTool): tool is GQLSingleClickOnDiagramElementTool => + tool.__typename === 'SingleClickOnDiagramElementTool'; + +const isDiagramDescription = ( + representationDescription: GQLRepresentationDescription +): representationDescription is GQLDiagramDescription => representationDescription.__typename === 'DiagramDescription'; + +export const usePalette = ({ + x, + y, + diagramElementId, + targetObjectId, + onDirectEditClick, +}: UsePaletteProps): UsePaletteValue => { + const { nodeLookup, edgeLookup } = useStoreApi, Edge>().getState(); + const { diagramId, editingContextId } = useContext(DiagramContext); + + const { addErrorMessage, addMessages } = useMultiToast(); + const { showDeletionConfirmation } = useDeletionConfirmationDialog(); + const { showDialog } = useDialog(); + + const { data: paletteData, error: paletteError } = useQuery( + getPaletteQuery, + { + variables: { + editingContextId, + diagramId, + diagramElementId, + }, + } + ); + + const description: GQLRepresentationDescription | undefined = + paletteData?.viewer.editingContext.representation.description; + const palette: GQLPalette | null = description && isDiagramDescription(description) ? description.palette : null; + useEffect(() => { + if (paletteError) { + addErrorMessage('An unexpected error has occurred, please refresh the page'); + } + }, [paletteError]); + + const [deleteElementsMutation] = useMutation( + deleteFromDiagramMutation + ); + + const [invokeSingleClickOnDiagramElementTool] = useMutation< + GQLInvokeSingleClickOnDiagramElementToolData, + GQLInvokeSingleClickOnDiagramElementToolVariables + >(invokeSingleClickOnDiagramElementToolMutation); + + const invokeSingleClickTool = useCallback( + async (tool: GQLTool, variables: GQLToolVariable[]) => { + if (isSingleClickOnDiagramElementTool(tool)) { + const { id: toolId } = tool; + const input: GQLInvokeSingleClickOnDiagramElementToolInput = { + id: crypto.randomUUID(), + editingContextId, + representationId: diagramId, + diagramElementId, + toolId, + startingPositionX: x, + startingPositionY: y, + variables, + }; + + const { data } = await invokeSingleClickOnDiagramElementTool({ + variables: { input }, + }); + if (data) { + const { invokeSingleClickOnDiagramElementTool } = data; + if (isInvokeSingleClickSuccessPayload(invokeSingleClickOnDiagramElementTool)) { + addMessages(invokeSingleClickOnDiagramElementTool.messages); + } + if (isErrorPayload(invokeSingleClickOnDiagramElementTool)) { + addMessages(invokeSingleClickOnDiagramElementTool.messages); + } + } + } + }, + [ + x, + y, + editingContextId, + diagramId, + diagramElementId, + invokeSingleClickOnDiagramElementToolMutation, + isSingleClickOnDiagramElementTool, + ] + ); + + const invokeDelete = (diagramElementId: string, deletionPolicy: GQLDeletionPolicy) => { + if (!!nodeLookup.get(diagramElementId)) { + invokeDeleteMutation([diagramElementId], [], deletionPolicy); + } else if (!!edgeLookup.get(diagramElementId)) { + invokeDeleteMutation([], [diagramElementId], deletionPolicy); + } + }; + + const invokeDeleteMutation = useCallback( + async (nodeIds: string[], edgeIds: string[], deletionPolicy: GQLDeletionPolicy) => { + const input: GQLDeleteFromDiagramInput = { + id: crypto.randomUUID(), + editingContextId, + representationId: diagramId, + nodeIds, + edgeIds, + deletionPolicy, + }; + const { data } = await deleteElementsMutation({ variables: { input } }); + if (data) { + const { deleteFromDiagram } = data; + if (isErrorPayload(deleteFromDiagram) || isDeleteSuccessPayload(deleteFromDiagram)) { + addMessages(deleteFromDiagram.messages); + } + } + }, + [editingContextId, diagramId, deleteElementsMutation] + ); + + const [collapseExpandMutation] = useMutation( + updateCollapsingStateMutation + ); + + const collapseExpandElement = useCallback( + (nodeId: string, collapsingState: GQLCollapsingState) => { + const input: GQLUpdateCollapsingStateInput = { + id: crypto.randomUUID(), + editingContextId, + representationId: diagramId, + diagramElementId: nodeId, + collapsingState, + }; + collapseExpandMutation({ variables: { input } }); + }, + [editingContextId, diagramId, collapseExpandMutation] + ); + + const handleDialogDescription = (tool: GQLSingleClickOnDiagramElementTool) => { + const onConfirm = (variables: GQLToolVariable[]) => { + invokeSingleClickTool(tool, variables); + }; + showDialog(tool.dialogDescriptionId, [{ name: 'targetObjectId', value: targetObjectId }], onConfirm, () => {}); + }; + + const handleToolClick = (tool: GQLTool) => { + switch (tool.id) { + case 'edit': + onDirectEditClick(); + break; + case 'semantic-delete': + showDeletionConfirmation(() => { + invokeDelete(diagramElementId, GQLDeletionPolicy.SEMANTIC); + }); + break; + case 'graphical-delete': + invokeDelete(diagramElementId, GQLDeletionPolicy.GRAPHICAL); + break; + case 'expand': + collapseExpandElement(diagramElementId, GQLCollapsingState.EXPANDED); + break; + case 'collapse': + collapseExpandElement(diagramElementId, GQLCollapsingState.COLLAPSED); + break; + default: + if (isSingleClickOnDiagramElementTool(tool)) { + if (tool.dialogDescriptionId) { + handleDialogDescription(tool); + } else { + invokeSingleClickTool(tool, []); + } + } + break; + } + }; + return { handleToolClick, palette }; +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/usePalette.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/usePalette.types.ts new file mode 100644 index 0000000000..9e12fe503a --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/palette/usePalette.types.ts @@ -0,0 +1,171 @@ +/******************************************************************************* + * 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 { GQLMessage } from './../Tool.types'; +import { GQLPalette, GQLTool } from './Palette.types'; +export interface UsePaletteProps { + x: number; + y: number; + diagramElementId: string; + targetObjectId: string; + onDirectEditClick: () => void; +} + +export interface UsePaletteValue { + handleToolClick: (tool: GQLTool) => void; + palette: GQLPalette | null; +} +export interface GQLGetToolSectionsVariables { + editingContextId: string; + diagramId: string; + diagramElementId: string; +} + +export interface GQLUpdateCollapsingStateData { + collapseExpandDiagramElement: GQLUpdateCollapsingStatePayload; +} + +export interface GQLUpdateCollapsingStatePayload { + __typename: string; +} + +export interface GQLUpdateCollapsingStateVariables { + input: GQLUpdateCollapsingStateInput; +} + +export interface GQLUpdateCollapsingStateInput { + id: string; + editingContextId: string; + representationId: string; + diagramElementId: string; + collapsingState: GQLCollapsingState; +} + +export enum GQLCollapsingState { + EXPANDED = 'EXPANDED', + COLLAPSED = 'COLLAPSED', +} + +export interface GQLDeleteFromDiagramSuccessPayload extends GQLDeleteFromDiagramPayload { + messages: GQLMessage[]; +} + +export interface GQLDeleteFromDiagramVariables { + input: GQLDeleteFromDiagramInput; +} + +export interface GQLDeleteFromDiagramInput { + id: string; + editingContextId: string; + representationId: string; + nodeIds: string[]; + edgeIds: string[]; + deletionPolicy: GQLDeletionPolicy; +} + +export interface GQLDeleteFromDiagramData { + deleteFromDiagram: GQLDeleteFromDiagramPayload; +} + +export interface GQLDeleteFromDiagramPayload { + __typename: string; +} + +export enum GQLDeletionPolicy { + SEMANTIC = 'SEMANTIC', + GRAPHICAL = 'GRAPHICAL', +} + +export interface GQLErrorPayload + extends GQLInvokeSingleClickOnDiagramElementToolPayload, + GQLDeleteFromDiagramPayload, + GQLUpdateCollapsingStatePayload { + message: string; + messages: GQLMessage[]; +} + +export interface GQLInvokeSingleClickOnDiagramElementToolData { + invokeSingleClickOnDiagramElementTool: GQLInvokeSingleClickOnDiagramElementToolPayload; +} + +export interface GQLInvokeSingleClickOnDiagramElementToolPayload { + __typename: string; +} + +export interface GQLInvokeSingleClickOnDiagramElementToolSuccessPayload + extends GQLInvokeSingleClickOnDiagramElementToolPayload { + id: string; + newSelection: GQLWorkbenchSelection; + messages: GQLMessage[]; +} + +export interface GQLWorkbenchSelection { + entries: GQLWorkbenchSelectionEntry[]; +} + +export interface GQLWorkbenchSelectionEntry { + id: string; + label: string; + kind: string; +} + +export interface GQLInvokeSingleClickOnDiagramElementToolVariables { + input: GQLInvokeSingleClickOnDiagramElementToolInput; +} + +export interface GQLInvokeSingleClickOnDiagramElementToolInput { + id: string; + editingContextId: string; + representationId: string; + diagramElementId: string; + toolId: string; + startingPositionX: number; + startingPositionY: number; + variables: GQLToolVariable[]; +} + +export interface GQLToolVariable { + name: string; + value: string; + type: GQLToolVariableType; +} + +export type GQLToolVariableType = 'STRING' | 'OBJECT_ID' | 'OBJECT_ID_ARRAY'; + +export interface GQLGetToolSectionsData { + viewer: GQLViewer; +} + +export interface GQLViewer { + editingContext: GQLEditingContext; +} + +export interface GQLEditingContext { + representation: GQLRepresentationMetadata; +} + +export interface GQLRepresentationMetadata { + id: string; + label: string; + kind: string; + description: GQLRepresentationDescription; +} + +export interface GQLRepresentationDescription { + id: string; + __typename: string; +} + +export interface GQLDiagramDescription extends GQLRepresentationDescription { + palette: GQLPalette; +} diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx index 07a0da1bdb..f0811de6cb 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx @@ -30,8 +30,8 @@ import { DropNodeContextProvider } from '../renderer/dropNode/DropNodeContext'; import { MarkerDefinitions } from '../renderer/edge/MarkerDefinitions'; import { FullscreenContextProvider } from '../renderer/fullscreen/FullscreenContext'; import { NodeContextProvider } from '../renderer/node/NodeContext'; -import { DiagramElementPaletteContextProvider } from '../renderer/palette/DiagramElementPaletteContext'; -import { DiagramPaletteContextProvider } from '../renderer/palette/DiagramPaletteContext'; +import { DiagramElementPaletteContextProvider } from '../renderer/palette/contexts/DiagramElementPaletteContext'; +import { DiagramPaletteContextProvider } from '../renderer/palette/contexts/DiagramPaletteContext'; import { DiagramRepresentationState, GQLDiagramDescription, diff --git a/packages/selection/frontend/sirius-components-selection/package.json b/packages/selection/frontend/sirius-components-selection/package.json index 62ce239eb9..f98154a2fa 100644 --- a/packages/selection/frontend/sirius-components-selection/package.json +++ b/packages/selection/frontend/sirius-components-selection/package.json @@ -42,7 +42,6 @@ "html-to-image": "1.11.11", "pathfinding": "0.4.18", "svg-path-parser": "1.1.0", - "react-draggable": "4.4.6", "@xyflow/react": "12.2.1", "graphql": "16.8.1", "react": "18.3.1", diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/diagrams/PaletteControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/diagrams/PaletteControllerTests.java index 7216778ecc..860e175f13 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/diagrams/PaletteControllerTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/diagrams/PaletteControllerTests.java @@ -97,7 +97,7 @@ public void givenDomainDiagramOnStudioWhenItIsOpenedThenEntitiesAreVisible() { ); var result = this.paletteQueryRunner.run(variables); - List topLevelToolsLabel = JsonPath.read(result, "$.data.viewer.editingContext.representation.description.palette.tools[*].label"); + List topLevelToolsLabel = JsonPath.read(result, "$.data.viewer.editingContext.representation.description.palette.paletteEntries[*].label"); assertThat(topLevelToolsLabel) .isNotEmpty() .anySatisfy(toolLabel -> assertThat(toolLabel).isEqualTo("New entity")); diff --git a/packages/sirius-web/frontend/sirius-web-papaya/src/PapayaExtensionRegistry.tsx b/packages/sirius-web/frontend/sirius-web-papaya/src/PapayaExtensionRegistry.tsx index 45767c1ad5..0fdf8c03e5 100644 --- a/packages/sirius-web/frontend/sirius-web-papaya/src/PapayaExtensionRegistry.tsx +++ b/packages/sirius-web/frontend/sirius-web-papaya/src/PapayaExtensionRegistry.tsx @@ -13,6 +13,7 @@ import { ComponentExtension, DataExtension, ExtensionRegistry } from '@eclipse-sirius/sirius-components-core'; import { + DiagramPaletteToolContributionProps, EdgeData, NodeData, ReactFlowPropsCustomizer, @@ -70,10 +71,19 @@ const papayaDiagramPanelExtension: DataExtension data: [reactFlowPropsCustomizer], }; papayaExtensionRegistry.putData(diagramRendererReactFlowPropsCustomizerExtensionPoint, papayaDiagramPanelExtension); - -papayaExtensionRegistry.addComponent(diagramPaletteToolExtensionPoint, { +const diagramPaletteToolContributions: DiagramPaletteToolContributionProps[] = [ + { + canHandle: (diagamElement: Node | Edge) => { + return diagamElement.data + ? diagamElement.data.targetObjectKind.startsWith('siriusComponents://semantic?domain=papaya&entity=Component') + : false; + }, + component: PapayaComponentLabelDetailToolContribution, + }, +]; +papayaExtensionRegistry.putData(diagramPaletteToolExtensionPoint, { identifier: `papaya_${diagramPaletteToolExtensionPoint.identifier}`, - Component: PapayaComponentLabelDetailToolContribution, + data: diagramPaletteToolContributions, }); export { papayaExtensionRegistry }; diff --git a/packages/sirius-web/frontend/sirius-web/package.json b/packages/sirius-web/frontend/sirius-web/package.json index 538719f676..3927d9deb4 100644 --- a/packages/sirius-web/frontend/sirius-web/package.json +++ b/packages/sirius-web/frontend/sirius-web/package.json @@ -52,8 +52,7 @@ "@xyflow/react": "12.2.1", "subscriptions-transport-ws": "0.11.0", "tss-react": "4.9.7", - "xstate": "4.32.1", - "react-draggable": "4.4.6" + "xstate": "4.32.1" }, "devDependencies": { "@testing-library/jest-dom": "5.14.1", diff --git a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/PaletteDefaultToolsProvider.java b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/PaletteDefaultToolsProvider.java new file mode 100644 index 0000000000..d203da808f --- /dev/null +++ b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/PaletteDefaultToolsProvider.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +package org.eclipse.sirius.components.view.emf.diagram; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.eclipse.sirius.components.collaborative.diagrams.api.DiagramImageConstants; +import org.eclipse.sirius.components.collaborative.diagrams.dto.ITool; +import org.eclipse.sirius.components.collaborative.diagrams.dto.SingleClickOnDiagramElementTool; +import org.eclipse.sirius.components.collaborative.diagrams.dto.ToolSection; +import org.eclipse.sirius.components.diagrams.Node; +import org.eclipse.sirius.components.diagrams.description.EdgeDescription; +import org.eclipse.sirius.components.diagrams.description.EdgeLabelKind; +import org.eclipse.sirius.components.diagrams.description.IDiagramElementDescription; +import org.eclipse.sirius.components.diagrams.description.NodeDescription; +import org.eclipse.sirius.components.diagrams.description.SynchronizationPolicy; +import org.eclipse.sirius.components.view.emf.diagram.api.IPaletteToolsProvider; +import org.springframework.stereotype.Service; + +/** + * An helper to build default tools in the palette. + * @author fbarbin + */ +@Service +public class PaletteDefaultToolsProvider implements IPaletteToolsProvider { + + @Override + public List createExtraToolSections(Object diagramElementDescription, Object diagramElement) { + List extraToolSections = new ArrayList<>(); + + if (diagramElementDescription instanceof NodeDescription || diagramElementDescription instanceof EdgeDescription) { + + List extraTools = this.createExtraTools(diagramElementDescription, diagramElement); + var editToolSection = ToolSection.newToolSection("edit-section") + .label("Edit") + .iconURL(List.of()) + .tools(extraTools) + .build(); + extraToolSections.add(editToolSection); + + } + return extraToolSections; + } + + @Override + public List createQuickAccessTools(Object diagramElementDescription, Object diagramElement) { + return this.createExtraTools(diagramElementDescription, diagramElement); + } + + private List createExtraTools(Object diagramElementDescription, Object diagramElement) { + List targetDescriptions = new ArrayList<>(); + boolean unsynchronizedMapping = false; + if (diagramElementDescription instanceof NodeDescription nodeDescription) { + targetDescriptions.add(nodeDescription); + unsynchronizedMapping = SynchronizationPolicy.UNSYNCHRONIZED.equals(nodeDescription.getSynchronizationPolicy()); + } else if (diagramElementDescription instanceof EdgeDescription edgeDescription) { + targetDescriptions.addAll(edgeDescription.getSourceNodeDescriptions()); + unsynchronizedMapping = SynchronizationPolicy.UNSYNCHRONIZED.equals(((EdgeDescription) diagramElementDescription).getSynchronizationPolicy()); + } + + List extraTools = new ArrayList<>(); + if (this.hasLabelEditTool(diagramElementDescription)) { + // Edit Tool (the handler is never called) + var editTool = this.createExtraEditLabelEditTool(targetDescriptions); + extraTools.add(editTool); + } + if (unsynchronizedMapping) { + // Graphical Delete Tool (the handler is never called) + var graphicalDeleteTool = this.createExtraGraphicalDeleteTool(targetDescriptions); + extraTools.add(graphicalDeleteTool); + } + if (this.hasDeleteTool(diagramElementDescription)) { + // Semantic Delete Tool (the handler is never called) + var semanticDeleteTool = this.createExtraSemanticDeleteTool(targetDescriptions); + extraTools.add(semanticDeleteTool); + } + if (this.isCollapsible(diagramElementDescription, diagramElement)) { + // Collapse or expand Tool (the handler is never called) + var expandCollapseTool = this.createExtraExpandCollapseTool(targetDescriptions, diagramElement); + expandCollapseTool.ifPresent(extraTools::add); + } + return extraTools; + } + + private Optional createExtraExpandCollapseTool(List targetDescriptions, Object diagramElement) { + + if (diagramElement instanceof Node node) { + ITool collapsingTool = null; + SingleClickOnDiagramElementTool collapseTool = SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("collapse") + .label("Collapse") + .iconURL(List.of(DiagramImageConstants.COLLAPSE_SVG)) + .targetDescriptions(targetDescriptions) + .appliesToDiagramRoot(false) + .build(); + + SingleClickOnDiagramElementTool expandTool = SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("expand") + .label("Expand") + .iconURL(List.of(DiagramImageConstants.EXPAND_SVG)) + .targetDescriptions(targetDescriptions) + .appliesToDiagramRoot(false) + .build(); + + collapsingTool = switch (node.getCollapsingState()) { + case EXPANDED -> collapseTool; + case COLLAPSED -> expandTool; + default -> null; + }; + return Optional.ofNullable(collapsingTool); + } + return Optional.empty(); + } + + private ITool createExtraSemanticDeleteTool(List targetDescriptions) { + return SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("semantic-delete") + .label("Delete from model") + .iconURL(List.of(DiagramImageConstants.SEMANTIC_DELETE_SVG)) + .targetDescriptions(targetDescriptions) + .appliesToDiagramRoot(false) + .build(); + } + + private ITool createExtraGraphicalDeleteTool(List targetDescriptions) { + return SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("graphical-delete") + .label("Delete from diagram") + .iconURL(List.of(DiagramImageConstants.GRAPHICAL_DELETE_SVG)) + .targetDescriptions(targetDescriptions) + .appliesToDiagramRoot(false) + .build(); + } + + private boolean isCollapsible(Object diagramElementDescription, Object diagramElement) { + if (diagramElementDescription instanceof NodeDescription nodeDescription && diagramElement instanceof Node) { + return nodeDescription.isCollapsible(); + } + return false; + } + + private boolean hasLabelEditTool(Object diagramElementDescription) { + boolean result = true; + if (diagramElementDescription instanceof NodeDescription nodeDescription) { + result = nodeDescription.getLabelEditHandler() != null; + } else if (diagramElementDescription instanceof EdgeDescription edgeDescription) { + if (edgeDescription.getLabelEditHandler() instanceof IViewEdgeLabelEditHandler viewEdgeLabelEditHandler) { + result = viewEdgeLabelEditHandler.hasLabelEditTool(EdgeLabelKind.CENTER_LABEL); + } else { + result = false; + } + } + return result; + } + + + private ITool createExtraEditLabelEditTool(List targetDescriptions) { + return SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("edit") + .label("Edit") + .iconURL(List.of(DiagramImageConstants.EDIT_SVG)) + .targetDescriptions(targetDescriptions) + .appliesToDiagramRoot(false) + .build(); + + } + + private boolean hasDeleteTool(Object diagramElementDescription) { + boolean result = true; + if (diagramElementDescription instanceof NodeDescription nodeDescription) { + if (nodeDescription.getDeleteHandler() instanceof IViewNodeDeleteHandler viewNodeDeleteHandler) { + result = viewNodeDeleteHandler.hasSemanticDeleteTool(); + } + } else if (diagramElementDescription instanceof EdgeDescription edgeDescription) { + if (edgeDescription.getDeleteHandler() instanceof IViewNodeDeleteHandler viewElementDeleteHandler) { + result = viewElementDeleteHandler.hasSemanticDeleteTool(); + } + } + return result; + } +} diff --git a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProvider.java b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProvider.java index 386286b44e..cc18f1ba80 100644 --- a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProvider.java +++ b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProvider.java @@ -21,11 +21,12 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; -import org.eclipse.sirius.components.collaborative.diagrams.api.DiagramImageConstants; import org.eclipse.sirius.components.collaborative.diagrams.api.IDiagramDescriptionService; import org.eclipse.sirius.components.collaborative.diagrams.api.IPaletteProvider; +import org.eclipse.sirius.components.collaborative.diagrams.dto.IPaletteEntry; import org.eclipse.sirius.components.collaborative.diagrams.dto.ITool; import org.eclipse.sirius.components.collaborative.diagrams.dto.Palette; +import org.eclipse.sirius.components.collaborative.diagrams.dto.PaletteDivider; import org.eclipse.sirius.components.collaborative.diagrams.dto.SingleClickOnDiagramElementTool; import org.eclipse.sirius.components.collaborative.diagrams.dto.SingleClickOnTwoDiagramElementsCandidate; import org.eclipse.sirius.components.collaborative.diagrams.dto.SingleClickOnTwoDiagramElementsTool; @@ -37,10 +38,7 @@ import org.eclipse.sirius.components.diagrams.Node; import org.eclipse.sirius.components.diagrams.description.DiagramDescription; import org.eclipse.sirius.components.diagrams.description.EdgeDescription; -import org.eclipse.sirius.components.diagrams.description.EdgeLabelKind; -import org.eclipse.sirius.components.diagrams.description.IDiagramElementDescription; import org.eclipse.sirius.components.diagrams.description.NodeDescription; -import org.eclipse.sirius.components.diagrams.description.SynchronizationPolicy; import org.eclipse.sirius.components.interpreter.AQLInterpreter; import org.eclipse.sirius.components.interpreter.Result; import org.eclipse.sirius.components.interpreter.Status; @@ -55,6 +53,7 @@ import org.eclipse.sirius.components.view.emf.IRepresentationDescriptionIdProvider; import org.eclipse.sirius.components.view.emf.IViewRepresentationDescriptionPredicate; import org.eclipse.sirius.components.view.emf.api.IViewAQLInterpreterFactory; +import org.eclipse.sirius.components.view.emf.diagram.api.IPaletteToolsProvider; import org.eclipse.sirius.components.view.emf.diagram.api.IViewDiagramDescriptionSearchService; import org.springframework.stereotype.Service; @@ -91,13 +90,18 @@ public class ViewPaletteProvider implements IPaletteProvider { private final Function idProvider = (eObject) -> UUID.nameUUIDFromBytes(EcoreUtil.getURI(eObject).toString().getBytes()); - public ViewPaletteProvider(IURLParser urlParser, IViewRepresentationDescriptionPredicate viewRepresentationDescriptionPredicate, IViewDiagramDescriptionSearchService viewDiagramDescriptionSearchService, IDiagramDescriptionService diagramDescriptionService, IDiagramIdProvider diagramIdProvider, IViewAQLInterpreterFactory aqlInterpreterFactory) { + private final List paletteToolsProviders; + + public ViewPaletteProvider(IURLParser urlParser, IViewRepresentationDescriptionPredicate viewRepresentationDescriptionPredicate, + IViewDiagramDescriptionSearchService viewDiagramDescriptionSearchService, IDiagramDescriptionService diagramDescriptionService, IDiagramIdProvider diagramIdProvider, + IViewAQLInterpreterFactory aqlInterpreterFactory, List paletteToolsProviders) { this.urlParser = Objects.requireNonNull(urlParser); this.viewRepresentationDescriptionPredicate = Objects.requireNonNull(viewRepresentationDescriptionPredicate); this.viewDiagramDescriptionSearchService = Objects.requireNonNull(viewDiagramDescriptionSearchService); this.diagramDescriptionService = Objects.requireNonNull(diagramDescriptionService); this.diagramIdProvider = Objects.requireNonNull(diagramIdProvider); this.aqlInterpreterFactory = Objects.requireNonNull(aqlInterpreterFactory); + this.paletteToolsProviders = Objects.requireNonNull(paletteToolsProviders); } @Override @@ -118,10 +122,10 @@ public Palette handle(Object targetElement, Object diagramElement, Object diagra palette = this.getDiagramPalette(diagramDescription, viewDiagramDescription, variableManager, interpreter); } else if (diagramElement instanceof Node && diagramElementDescription instanceof NodeDescription nodeDescription) { variableManager.put(Node.SELECTED_NODE, diagramElement); - palette = this.getNodePalette(editingContext, diagramDescription, nodeDescription, this.createExtraToolSections(diagramElementDescription, diagramElement), variableManager, interpreter); + palette = this.getNodePalette(editingContext, diagramDescription, diagramElement, nodeDescription, variableManager, interpreter); } else if (diagramElement instanceof Edge && diagramElementDescription instanceof EdgeDescription edgeDescription) { variableManager.put(Edge.SELECTED_EDGE, diagramElement); - palette = this.getEdgePalette(editingContext, edgeDescription, this.createExtraToolSections(diagramElementDescription, diagramElement), variableManager, interpreter); + palette = this.getEdgePalette(editingContext, edgeDescription, diagramElement, variableManager, interpreter); } } return palette; @@ -134,14 +138,19 @@ private Palette getDiagramPalette(DiagramDescription diagramDescription, org.ecl if (sourceElementId.isPresent()) { String diagramPaletteId = "siriusComponents://diagramPalette?diagramId=" + sourceElementId.get(); + List paletteEntries = new ArrayList<>(); + toolFinder.findNodeTools(viewDiagramDescription).stream() + .filter(tool -> this.checkPrecondition(tool, variableManager, interpreter)) + .map(tool -> this.createDiagramRootNodeTool(tool, variableManager, interpreter)) + .forEach(paletteEntries::add); + + toolFinder.findToolSections(viewDiagramDescription).stream() + .map(toolSection -> this.createToolSection(toolSection, variableManager, interpreter)) + .forEach(paletteEntries::add); + diagramPalette = Palette.newPalette(diagramPaletteId) - .tools(toolFinder.findNodeTools(viewDiagramDescription).stream() - .filter(tool -> this.checkPrecondition(tool, variableManager, interpreter)) - .map(tool -> this.createDiagramRootNodeTool(tool, variableManager, interpreter)) - .toList()) - .toolSections(toolFinder.findToolSections(viewDiagramDescription).stream() - .map(toolSection -> this.createToolSection(toolSection, variableManager, interpreter)) - .toList()) + .quickAccessTools(List.of()) + .paletteEntries(paletteEntries) .build(); } return diagramPalette; @@ -185,37 +194,43 @@ private ITool createNodeTool(NodeTool viewNodeTool, boolean appliesToDiagramRoot .build(); } - private Palette getNodePalette(IEditingContext editingContext, DiagramDescription diagramDescription, NodeDescription nodeDescription, List extraToolSections, VariableManager variableManager, AQLInterpreter interpreter) { - Optional sourceElementId = this.getSourceElementId(nodeDescription.getId()); + private Palette getNodePalette(IEditingContext editingContext, DiagramDescription diagramDescription, Object diagramElement, NodeDescription nodeDescription, VariableManager variableManager, AQLInterpreter interpreter) { + Optional sourceElementId = this.getSourceElementId(nodeDescription.getId()); Palette nodePalette = null; var toolFinder = new ToolFinder(); if (sourceElementId.isPresent()) { var optionalNodeDescription = this.viewDiagramDescriptionSearchService.findViewNodeDescriptionById(editingContext, nodeDescription.getId()); if (optionalNodeDescription.isPresent()) { + List extraToolSections = new ArrayList<>(); + paletteToolsProviders.stream().map(paletteToolsProvider -> paletteToolsProvider.createExtraToolSections(nodeDescription, diagramElement)).flatMap(List::stream) + .forEach(extraToolSections::add); + List quickAccessTools = new ArrayList<>(); + paletteToolsProviders.stream().map(paletteToolsProvider -> paletteToolsProvider.createQuickAccessTools(nodeDescription, diagramElement)).flatMap(List::stream) + .forEach(quickAccessTools::add); org.eclipse.sirius.components.view.diagram.NodeDescription viewNodeDescription = optionalNodeDescription.get(); - var tools = new ArrayList(); - tools.addAll(toolFinder.findNodeTools(viewNodeDescription).stream() + var paletteEntries = new ArrayList(); + toolFinder.findNodeTools(viewNodeDescription).stream() .filter(tool -> this.checkPrecondition(tool, variableManager, interpreter)) .map(tool -> this.createNodeTool(tool, variableManager, interpreter)) - .toList()); - tools.addAll(toolFinder.findEdgeTools(viewNodeDescription).stream() + .forEach(paletteEntries::add); + toolFinder.findEdgeTools(viewNodeDescription).stream() .filter(tool -> this.checkPrecondition(tool, variableManager, interpreter)) .map(viewEdgeTools -> this.createEdgeTool(viewEdgeTools, diagramDescription, nodeDescription, variableManager, interpreter)) - .toList()); + .forEach(paletteEntries::add); - var toolSections = new ArrayList(); - toolSections.addAll(toolFinder.findToolSections(viewNodeDescription).stream() + toolFinder.findToolSections(viewNodeDescription).stream() .map(nodeToolSection -> this.createToolSection(nodeToolSection, diagramDescription, nodeDescription, variableManager, interpreter)) - .toList()); - toolSections.addAll(extraToolSections); + .forEach(paletteEntries::add); + + paletteEntries.add(new PaletteDivider(UUID.randomUUID().toString())); + paletteEntries.addAll(extraToolSections); String nodePaletteId = "siriusComponents://nodePalette?nodeId=" + sourceElementId.get(); - nodePalette = Palette.newPalette(nodePaletteId) - .tools(tools) - .toolSections(toolSections) + nodePalette = Palette.newPalette(nodePaletteId).quickAccessTools(quickAccessTools) + .paletteEntries(paletteEntries) .build(); } } @@ -268,7 +283,8 @@ private ITool createEdgeTool(EdgeTool viewEdgeTool, DiagramDescription diagramDe .build(); } - private Palette getEdgePalette(IEditingContext editingContext, EdgeDescription edgeDescription, List extraToolSections, VariableManager variableManager, AQLInterpreter interpreter) { + private Palette getEdgePalette(IEditingContext editingContext, EdgeDescription edgeDescription, Object diagramElement, VariableManager variableManager, AQLInterpreter interpreter) { + List extraToolSections = new PaletteDefaultToolsProvider().createExtraToolSections(edgeDescription, diagramElement); Palette edgePalette = null; var toolFinder = new ToolFinder(); Optional optionalSourceElementId = this.getSourceElementId(edgeDescription.getId()); @@ -279,19 +295,22 @@ private Palette getEdgePalette(IEditingContext editingContext, EdgeDescription e if (optionalEdgeDescription.isPresent()) { org.eclipse.sirius.components.view.diagram.EdgeDescription viewEdgeDescription = optionalEdgeDescription.get(); - var toolSections = new ArrayList(); - toolSections.addAll(toolFinder.findToolSections(viewEdgeDescription).stream() + List paletteEntries = new ArrayList<>(); + toolFinder.findToolSections(viewEdgeDescription).stream() .map(edgeToolSection -> this.createToolSection(edgeToolSection, variableManager, interpreter)) - .toList()); - toolSections.addAll(extraToolSections); + .forEach(paletteEntries::add); + + toolFinder.findNodeTools(viewEdgeDescription).stream() + .filter(tool -> this.checkPrecondition(tool, variableManager, interpreter)) + .map(tool -> this.createNodeTool(tool, variableManager, interpreter)) + .forEach(paletteEntries::add); + + paletteEntries.addAll(extraToolSections); String edgePaletteId = "siriusComponents://edgePalette?edgeId=" + sourceElementId; edgePalette = Palette.newPalette(edgePaletteId) - .tools(toolFinder.findNodeTools(viewEdgeDescription).stream() - .filter(tool -> this.checkPrecondition(tool, variableManager, interpreter)) - .map(tool -> this.createNodeTool(tool, variableManager, interpreter)) - .toList()) - .toolSections(toolSections) + .quickAccessTools(List.of()) + .paletteEntries(paletteEntries) .build(); } @@ -317,159 +336,6 @@ private Optional getSourceElementId(String descriptionId) { return Optional.ofNullable(parameters.get(IRepresentationDescriptionIdProvider.SOURCE_ELEMENT_ID)).orElse(List.of()).stream().findFirst(); } - private List createExtraToolSections(Object diagramElementDescription, Object diagramElement) { - List extraToolSections = new ArrayList<>(); - - List targetDescriptions = new ArrayList<>(); - boolean unsynchronizedMapping = false; - if (diagramElementDescription instanceof NodeDescription nodeDescription) { - targetDescriptions.add(nodeDescription); - unsynchronizedMapping = SynchronizationPolicy.UNSYNCHRONIZED.equals(nodeDescription.getSynchronizationPolicy()); - } else if (diagramElementDescription instanceof EdgeDescription edgeDescription) { - targetDescriptions.addAll(edgeDescription.getSourceNodeDescriptions()); - unsynchronizedMapping = SynchronizationPolicy.UNSYNCHRONIZED.equals(((EdgeDescription) diagramElementDescription).getSynchronizationPolicy()); - } - - // Graphical Delete Tool for unsynchronized mapping only (the handler is never called) - if (diagramElementDescription instanceof NodeDescription || diagramElementDescription instanceof EdgeDescription) { - if (this.hasLabelEditTool(diagramElementDescription)) { - // Edit Tool (the handler is never called) - var editToolSection = this.createExtraEditLabelEditTool(targetDescriptions); - extraToolSections.add(editToolSection); - } - if (unsynchronizedMapping) { - // Graphical Delete Tool (the handler is never called) - var graphicalDeleteToolSection = this.createExtraGraphicalDeleteTool(targetDescriptions); - extraToolSections.add(graphicalDeleteToolSection); - } - if (this.hasDeleteTool(diagramElementDescription)) { - // Semantic Delete Tool (the handler is never called) - var semanticDeleteToolSection = this.createExtraSemanticDeleteTool(targetDescriptions); - extraToolSections.add(semanticDeleteToolSection); - } - if (this.isCollapsible(diagramElementDescription, diagramElement)) { - // Collapse or expand Tool (the handler is never called) - var expandCollapseToolSection = this.createExtraExpandCollapseTool(targetDescriptions, diagramElement); - extraToolSections.add(expandCollapseToolSection); - } - } - return extraToolSections; - } - - private ToolSection createExtraExpandCollapseTool(List targetDescriptions, Object diagramElement) { - var expandCollapseToolSectionBuilder = ToolSection.newToolSection("expand-collapse-section") - .label("") - .iconURL(List.of()) - .tools(List.of()); - - if (diagramElement instanceof Node node) { - List collapsingTools = new ArrayList<>(); - SingleClickOnDiagramElementTool collapseTool = SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("collapse") - .label("Collapse") - .iconURL(List.of(DiagramImageConstants.COLLAPSE_SVG)) - .targetDescriptions(targetDescriptions) - .appliesToDiagramRoot(false) - .build(); - - SingleClickOnDiagramElementTool expandTool = SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("expand") - .label("Expand") - .iconURL(List.of(DiagramImageConstants.EXPAND_SVG)) - .targetDescriptions(targetDescriptions) - .appliesToDiagramRoot(false) - .build(); - - switch (node.getCollapsingState()) { - case EXPANDED -> collapsingTools.add(collapseTool); - case COLLAPSED -> collapsingTools.add(expandTool); - default -> { - // Nothing on purpose - } - } - expandCollapseToolSectionBuilder.tools(collapsingTools); - } - return expandCollapseToolSectionBuilder.build(); - } - - private ToolSection createExtraSemanticDeleteTool(List targetDescriptions) { - SingleClickOnDiagramElementTool semanticDeleteTool = SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("semantic-delete") - .label("Delete from model") - .iconURL(List.of(DiagramImageConstants.SEMANTIC_DELETE_SVG)) - .targetDescriptions(targetDescriptions) - .appliesToDiagramRoot(false) - .build(); - - return ToolSection.newToolSection("semantic-delete-section") - .label("") - .iconURL(List.of()) - .tools(List.of(semanticDeleteTool)) - .build(); - } - - private ToolSection createExtraGraphicalDeleteTool(List targetDescriptions) { - SingleClickOnDiagramElementTool graphicalDeleteTool = SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("graphical-delete") - .label("Delete from diagram") - .iconURL(List.of(DiagramImageConstants.GRAPHICAL_DELETE_SVG)) - .targetDescriptions(targetDescriptions) - .appliesToDiagramRoot(false) - .build(); - - return ToolSection.newToolSection("graphical-delete-section") - .label("") - .iconURL(List.of()) - .tools(List.of(graphicalDeleteTool)) - .build(); - } - - private boolean isCollapsible(Object diagramElementDescription, Object diagramElement) { - if (diagramElementDescription instanceof NodeDescription nodeDescription && diagramElement instanceof Node) { - return nodeDescription.isCollapsible(); - } - return false; - } - - private boolean hasLabelEditTool(Object diagramElementDescription) { - boolean result = true; - if (diagramElementDescription instanceof NodeDescription nodeDescription) { - result = nodeDescription.getLabelEditHandler() != null; - } else if (diagramElementDescription instanceof EdgeDescription edgeDescription) { - if (edgeDescription.getLabelEditHandler() instanceof IViewEdgeLabelEditHandler viewEdgeLabelEditHandler) { - result = viewEdgeLabelEditHandler.hasLabelEditTool(EdgeLabelKind.CENTER_LABEL); - } else { - result = false; - } - } - return result; - } - - private ToolSection createExtraEditLabelEditTool(List targetDescriptions) { - SingleClickOnDiagramElementTool editTool = SingleClickOnDiagramElementTool.newSingleClickOnDiagramElementTool("edit") - .label("Edit") - .iconURL(List.of(DiagramImageConstants.EDIT_SVG)) - .targetDescriptions(targetDescriptions) - .appliesToDiagramRoot(false) - .build(); - - return ToolSection.newToolSection("edit-section") - .label("") - .iconURL(List.of()) - .tools(List.of(editTool)) - .build(); - } - - private boolean hasDeleteTool(Object diagramElementDescription) { - boolean result = true; - if (diagramElementDescription instanceof NodeDescription nodeDescription) { - if (nodeDescription.getDeleteHandler() instanceof IViewNodeDeleteHandler viewNodeDeleteHandler) { - result = viewNodeDeleteHandler.hasSemanticDeleteTool(); - } - } else if (diagramElementDescription instanceof EdgeDescription edgeDescription) { - if (edgeDescription.getDeleteHandler() instanceof IViewNodeDeleteHandler viewElementDeleteHandler) { - result = viewElementDeleteHandler.hasSemanticDeleteTool(); - } - } - return result; - } - private boolean checkPrecondition(Tool tool, VariableManager variableManager, AQLInterpreter interpreter) { String precondition = tool.getPreconditionExpression(); if (precondition != null && !precondition.isBlank()) { diff --git a/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/api/IPaletteToolsProvider.java b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/api/IPaletteToolsProvider.java new file mode 100644 index 0000000000..f204838063 --- /dev/null +++ b/packages/view/backend/sirius-components-view-emf/src/main/java/org/eclipse/sirius/components/view/emf/diagram/api/IPaletteToolsProvider.java @@ -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 + *******************************************************************************/ +package org.eclipse.sirius.components.view.emf.diagram.api; + +import org.eclipse.sirius.components.collaborative.diagrams.dto.ITool; +import org.eclipse.sirius.components.collaborative.diagrams.dto.ToolSection; + +import java.util.List; + +/** + * Common interface for services providing extra tools to the palette. + * + * @author fbarbin + */ +public interface IPaletteToolsProvider { + + List createExtraToolSections(Object diagramElementDescription, Object diagramElement); + + List createQuickAccessTools(Object diagramElementDescription, Object diagramElement); +} diff --git a/packages/view/backend/sirius-components-view-emf/src/test/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProviderTests.java b/packages/view/backend/sirius-components-view-emf/src/test/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProviderTests.java index 991f9ad6ec..dba02bfdc0 100644 --- a/packages/view/backend/sirius-components-view-emf/src/test/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProviderTests.java +++ b/packages/view/backend/sirius-components-view-emf/src/test/java/org/eclipse/sirius/components/view/emf/diagram/ViewPaletteProviderTests.java @@ -19,10 +19,11 @@ import java.util.Optional; import java.util.UUID; -import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.sirius.components.collaborative.diagrams.api.IDiagramDescriptionService; +import org.eclipse.sirius.components.collaborative.diagrams.dto.ITool; import org.eclipse.sirius.components.collaborative.diagrams.dto.SingleClickOnDiagramElementTool; import org.eclipse.sirius.components.collaborative.diagrams.dto.SingleClickOnTwoDiagramElementsTool; +import org.eclipse.sirius.components.collaborative.diagrams.dto.ToolSection; import org.eclipse.sirius.components.core.URLParser; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IURLParser; @@ -36,10 +37,8 @@ import org.eclipse.sirius.components.diagrams.description.InsideLabelDescription; import org.eclipse.sirius.components.diagrams.description.LabelStyleDescription; import org.eclipse.sirius.components.diagrams.description.NodeDescription; -import org.eclipse.sirius.components.interpreter.AQLInterpreter; import org.eclipse.sirius.components.representations.Failure; import org.eclipse.sirius.components.representations.Success; -import org.eclipse.sirius.components.representations.VariableManager; import org.eclipse.sirius.components.view.diagram.DiagramFactory; import org.eclipse.sirius.components.view.diagram.DiagramPalette; import org.eclipse.sirius.components.view.diagram.DiagramToolSection; @@ -125,40 +124,40 @@ public void getDiagramPaletteTest() { DiagramDescription diagramDescription = this.createDiagramDescription(); - VariableManager variableManager = new VariableManager(); - AQLInterpreter interpreter = new AQLInterpreter(List.of(), List.of(EcorePackage.eINSTANCE)); var diagram = new TestDiagramBuilder().getDiagram(UUID.randomUUID().toString()); var result = viewPaletteProvider.handle(null, diagram, diagramDescription, diagramDescription, new IEditingContext.NoOp()); assertThat(result).isNotNull(); assertThat(result.id()).isEqualTo("siriusComponents://diagramPalette?diagramId=sourceElementId"); - assertThat(result.tools()).hasSize(1); - assertThat(result.tools().get(0)).isInstanceOf(SingleClickOnDiagramElementTool.class); - assertThat(((SingleClickOnDiagramElementTool) result.tools().get(0)).appliesToDiagramRoot()).isTrue(); - assertThat(result.toolSections()).hasSize(1); - assertThat(result.toolSections().get(0).tools()).hasSize(1); - assertThat(result.toolSections().get(0).tools().get(0)).isInstanceOf(SingleClickOnDiagramElementTool.class); - assertThat(((SingleClickOnDiagramElementTool) result.toolSections().get(0).tools().get(0)).appliesToDiagramRoot()).isTrue(); + assertThat(result.paletteEntries()).filteredOn(ITool.class::isInstance).hasSize(1); + var tools = result.paletteEntries().stream().filter(ITool.class::isInstance).map(ITool.class::cast).toList(); + assertThat(tools.get(0)).isInstanceOf(SingleClickOnDiagramElementTool.class); + assertThat(((SingleClickOnDiagramElementTool) tools.get(0)).appliesToDiagramRoot()).isTrue(); + assertThat(result.paletteEntries()).filteredOn(ToolSection.class::isInstance).hasSize(1); + var toolSections = result.paletteEntries().stream().filter(ToolSection.class::isInstance).map(ToolSection.class::cast).toList(); + assertThat(toolSections.get(0).tools()).hasSize(1); + assertThat(toolSections.get(0).tools().get(0)).isInstanceOf(SingleClickOnDiagramElementTool.class); + assertThat(((SingleClickOnDiagramElementTool) toolSections.get(0).tools().get(0)).appliesToDiagramRoot()).isTrue(); } @Test public void getNodePaletteTest() { ViewPaletteProvider viewPaletteProvider = this.createViewPaletteProvider(); - VariableManager variableManager = new VariableManager(); - AQLInterpreter interpreter = new AQLInterpreter(List.of(), List.of(EcorePackage.eINSTANCE)); var node = new TestDiagramBuilder().getNode(UUID.randomUUID().toString(), true); var result = viewPaletteProvider.handle(null, node, this.createNodeDescription(), this.createDiagramDescription(), new IEditingContext.NoOp()); assertThat(result).isNotNull(); assertThat(result.id()).isEqualTo("siriusComponents://nodePalette?nodeId=sourceElementId"); - assertThat(result.tools()).hasSize(2); - assertThat(result.tools().get(0)).isInstanceOf(SingleClickOnDiagramElementTool.class); - assertThat(((SingleClickOnDiagramElementTool) result.tools().get(0)).appliesToDiagramRoot()).isFalse(); - assertThat(result.tools().get(1)).isInstanceOf(SingleClickOnTwoDiagramElementsTool.class); - assertThat(((SingleClickOnTwoDiagramElementsTool) result.tools().get(1)).candidates()).isNotEmpty(); - assertThat(result.toolSections()).hasSize(3); - assertThat(result.toolSections().get(0).tools()).hasSize(2); + assertThat(result.paletteEntries()).filteredOn(ITool.class::isInstance).hasSize(2); + var tools = result.paletteEntries().stream().filter(ITool.class::isInstance).map(ITool.class::cast).toList(); + assertThat(tools.get(0)).isInstanceOf(SingleClickOnDiagramElementTool.class); + assertThat(((SingleClickOnDiagramElementTool) tools.get(0)).appliesToDiagramRoot()).isFalse(); + assertThat(tools.get(1)).isInstanceOf(SingleClickOnTwoDiagramElementsTool.class); + assertThat(((SingleClickOnTwoDiagramElementsTool) tools.get(1)).candidates()).isNotEmpty(); + assertThat(result.paletteEntries()).filteredOn(ToolSection.class::isInstance).hasSize(2); + var toolSections = result.paletteEntries().stream().filter(ToolSection.class::isInstance).map(ToolSection.class::cast).toList(); + assertThat(toolSections.get(0).tools()).hasSize(2); } @Test @@ -179,18 +178,18 @@ public void getEdgePaletteTest() { .targetObjectLabelProvider(vm -> "") .build(); - VariableManager variableManager = new VariableManager(); - AQLInterpreter interpreter = new AQLInterpreter(List.of(), List.of(EcorePackage.eINSTANCE)); var edge = new TestDiagramBuilder().getEdge(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString()); var result = viewPaletteProvider.handle(null, edge, edgeDescription, this.createDiagramDescription(), new IEditingContext.NoOp()); assertThat(result).isNotNull(); assertThat(result.id()).isEqualTo("siriusComponents://edgePalette?edgeId=sourceElementId"); - assertThat(result.tools()).hasSize(1); - assertThat(result.tools().get(0)).isInstanceOf(SingleClickOnDiagramElementTool.class); - assertThat(((SingleClickOnDiagramElementTool) result.tools().get(0)).appliesToDiagramRoot()).isFalse(); - assertThat(result.toolSections()).hasSize(2); - assertThat(result.toolSections().get(0).tools()).hasSize(1); + assertThat(result.paletteEntries()).filteredOn(ITool.class::isInstance).hasSize(1); + var tool = result.paletteEntries().stream().filter(ITool.class::isInstance).map(ITool.class::cast).findFirst().orElse(null); + assertThat(tool).isInstanceOf(SingleClickOnDiagramElementTool.class); + assertThat(((SingleClickOnDiagramElementTool) tool).appliesToDiagramRoot()).isFalse(); + assertThat(result.paletteEntries()).filteredOn(ToolSection.class::isInstance).hasSize(2); + var toolSection = result.paletteEntries().stream().filter(ToolSection.class::isInstance).map(ToolSection.class::cast).findFirst().orElse(null); + assertThat(toolSection.tools()).hasSize(1); } private ViewPaletteProvider createViewPaletteProvider() { @@ -222,8 +221,8 @@ public Optional findViewEdgeDescriptionById(IEditingContext edi } }; - return new ViewPaletteProvider(urlParser, representationDescription -> true, viewDiagramDescriptionSearchService, new IDiagramDescriptionService.NoOp(), - new IDiagramIdProvider.NoOp(), new ViewAQLInterpreterFactory(List.of(), new StaticApplicationContext())); + return new ViewPaletteProvider(urlParser, representationDescription -> true, viewDiagramDescriptionSearchService, new IDiagramDescriptionService.NoOp(), new IDiagramIdProvider.NoOp(), + new ViewAQLInterpreterFactory(List.of(), new StaticApplicationContext()), List.of(new PaletteDefaultToolsProvider())); } private DiagramDescription createDiagramDescription() {