Skip to content

Commit

Permalink
[4376] Add current selection as a URL search parameter (WIP)
Browse files Browse the repository at this point in the history
* When the workbench selection changes, the URL search parameters are
now automatically updated to encode the contents of the selection.
* Reversely, when resolving a URL with such search parameters, the
workbench selection is set to those specified elements.

Bug: eclipse-sirius#4376
Signed-off-by: Florent Latombe <[email protected]>
  • Loading branch information
flatombe committed Jan 23, 2025
1 parent e11bd1b commit b4507e3
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021, 2024 Obeo.
* Copyright (c) 2021, 2025 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand Down Expand Up @@ -79,6 +79,7 @@ export const Workbench = ({
editingContextId,
initialRepresentationSelected,
onRepresentationSelected,
onSelectionChanged,
readOnly,
}: WorkbenchProps) => {
const { classes } = useWorkbenchStyles();
Expand Down Expand Up @@ -156,6 +157,10 @@ export const Workbench = ({
}
}, [onRepresentationSelected, initialRepresentationSelected, displayedRepresentation]);

useEffect(() => {
onSelectionChanged(selection);
}, [selection]);

const workbenchViewLeftSideContributions: WorkbenchViewContribution[] = [];
const workbenchViewRightSideContributions: WorkbenchViewContribution[] = [];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021, 2024 Obeo and others.
* Copyright (c) 2021, 2025 Obeo and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand All @@ -11,6 +11,7 @@
* Obeo - initial API and implementation
*******************************************************************************/
import React from 'react';
import { Selection } from '../selection/SelectionContext.types';

export interface GQLEditingContextEventPayload {
__typename: string;
Expand Down Expand Up @@ -56,6 +57,7 @@ export type WorkbenchProps = {
editingContextId: string;
initialRepresentationSelected: RepresentationMetadata | null;
onRepresentationSelected: (representation: RepresentationMetadata | null) => void;
onSelectionChanged: (selection: Selection | null) => void;
readOnly: boolean;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019, 2024 Obeo.
* Copyright (c) 2019, 2025 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand All @@ -14,7 +14,9 @@ import {
RepresentationMetadata,
Selection,
SelectionContextProvider,
SelectionEntry,
useData,
useSelection,
Workbench,
} from '@eclipse-sirius/sirius-components-core';
import { OmniboxContextEntry, OmniboxProvider } from '@eclipse-sirius/sirius-components-omnibox';
Expand All @@ -25,7 +27,15 @@ import {
} from '@eclipse-sirius/sirius-components-trees';
import { useMachine } from '@xstate/react';
import { useEffect } from 'react';
import { generatePath, Navigate, useNavigate, useParams, useResolvedPath } from 'react-router-dom';
import {
generatePath,
Navigate,
SetURLSearchParams,
useNavigate,
useParams,
useResolvedPath,
useSearchParams,
} from 'react-router-dom';
import { makeStyles } from 'tss-react/mui';
import { StateMachine } from 'xstate';
import { NavigationBar } from '../../navigationBar/NavigationBar';
Expand Down Expand Up @@ -56,11 +66,46 @@ const useEditProjectViewStyles = makeStyles()((_) => ({
},
}));

function createSelectionFromUrlSearchParams(urlSearchParams: URLSearchParams): Selection {
if (urlSearchParams !== null && urlSearchParams.has('selection')) {
const urlProjectElementIds: Array<string> = urlSearchParams.get('selection').split(',');
const selectionEntries: Array<SelectionEntry> = urlProjectElementIds.map((projectElementId) => {
// TODO: do we NEED kind? Can we find it? Otherwise does it get set automatically after our selection is set programmatically?
return { id: projectElementId, kind: null };
});
return { entries: selectionEntries };
} else {
return null;
}
}

function updateUrlSearchParamsWithSelection(setUrlSearchParams: SetURLSearchParams, selection: Selection) {
const selectedProjectElementIds: Array<string> = selection.entries
.map((selectionEntry) => {
return selectionEntry.id;
})
.filter((id) => id !== null);

// TODO: Do we need to encode the kind also? Not sure. For now we have some dataloss.
setUrlSearchParams((previousSearchParams: URLSearchParams) => {
if (selectedProjectElementIds.length > 0) {
previousSearchParams.set('selection', selectedProjectElementIds.join(','));
} else {
if (previousSearchParams.has('selection')) {
previousSearchParams.delete('selection');
}
}
return previousSearchParams;
});
}

export const EditProjectView = () => {
const navigate = useNavigate();
const routeMatch = useResolvedPath('.');
const { projectId, representationId } = useParams<EditProjectViewParams>();
const { classes } = useEditProjectViewStyles();
const [urlSearchParams, setUrlSearchParams]: [URLSearchParams, SetURLSearchParams] = useSearchParams();
const { selection, setSelection } = useSelection();

const [{ value, context }, dispatch] =
useMachine<StateMachine<EditProjectViewContext, EditProjectViewStateSchema, EditProjectViewEvent>>(
Expand All @@ -83,18 +128,52 @@ export const EditProjectView = () => {
dispatch(selectRepresentationEvent);
};

const workbenchOnSelectionChanged = (selection: Selection) => {
updateUrlSearchParamsWithSelection(setUrlSearchParams, selection);
};

useEffect(() => {
let pathname: string = null;
if (context.representation && context.representation.id !== representationId) {
const pathname = generatePath('/projects/:projectId/edit/:representationId', {
pathname = generatePath('/projects/:projectId/edit/:representationId', {
projectId,
representationId: context.representation.id,
});
navigate(pathname);
} else if (value === 'loaded' && context.representation === null && representationId) {
const pathname = generatePath('/projects/:projectId/edit/', { projectId });
navigate(pathname);
pathname = generatePath('/projects/:projectId/edit/', { projectId });
}

if (pathname !== null) {
if (urlSearchParams !== null && urlSearchParams.size > 0) {
// The navigation hook removes the search params, so we need to make sure we maintain them in the URL we navigate to.
navigate({
pathname: pathname,
search: `?${urlSearchParams}`,
});
} else {
navigate(pathname);
}
}
}, [value, projectId, routeMatch, history, context.representation, representationId]);
}, [value, projectId, routeMatch, history, context.representation, representationId, urlSearchParams]);

if (urlSearchParams !== null && urlSearchParams.has('selection')) {
const urlProjectElementIds: Array<string> = urlSearchParams.get('selection').split(',');

const currentlySelectedProjectElementIds: Array<string> = selection.entries.map(
(selectionEntry) => selectionEntry.id
);

const mustUpdateSelection: boolean = !(
urlProjectElementIds.length == currentlySelectedProjectElementIds.length &&
urlProjectElementIds.every(function (element, index) {
return element === currentlySelectedProjectElementIds[index];
})
);

if (mustUpdateSelection) {
setSelection(createSelectionFromUrlSearchParams(urlSearchParams));
}
}

let content: React.ReactNode = null;

Expand All @@ -109,16 +188,7 @@ export const EditProjectView = () => {
const { data: readOnlyPredicate } = useData(editProjectViewReadOnlyPredicateExtensionPoint);

if (value === 'loaded' && context.project) {
const initialSelection: Selection = {
entries: context.representation
? [
{
id: context.representation.id,
kind: context.representation.kind,
},
]
: [],
};
const initialSelection: Selection = createSelectionFromUrlSearchParams(urlSearchParams);

const readOnly = readOnlyPredicate(context.project);
const initialContextEntries: OmniboxContextEntry[] = [
Expand All @@ -135,6 +205,7 @@ export const EditProjectView = () => {
editingContextId={context.project.currentEditingContext.id}
initialRepresentationSelected={context.representation}
onRepresentationSelected={onRepresentationSelected}
onSelectionChanged={workbenchOnSelectionChanged}
readOnly={readOnly}
/>
</TreeToolBarProvider>
Expand Down

0 comments on commit b4507e3

Please sign in to comment.