Skip to content

Commit

Permalink
[3557] Add ProjectSettings tab extension point
Browse files Browse the repository at this point in the history
Bug: eclipse-sirius#3557
Signed-off-by: William Piers <william.piers@obeo.fr>
  • Loading branch information
wpiers authored and sbegaudeau committed Aug 7, 2024
1 parent baef1d6 commit b82afb2
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 13 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ A migration participant has been added to automatically keep compatible all diag
- https://github.com/eclipse-sirius/sirius-web/issues/3785[#3785] [sirius-web] Add an extension point to contribute custom tree item menu entry
- https://github.com/eclipse-sirius/sirius-web/issues/3782[#3782] [sirius-web] Add an extension point to contribute custom widgets
- https://github.com/eclipse-sirius/sirius-web/issues/3801[#3801] Add an equivalent of IViewDiagramDescriptionSearchService for Form descriptions
- https://github.com/eclipse-sirius/sirius-web/issues/3557[#3550] [sirius-web] Add an extension point to contribute setting pages


== v2024.7.0

Expand Down Expand Up @@ -201,6 +203,7 @@ image:doc/screenshots/diagramFilterView.png[Diagram Filter View, 70%]
- https://github.com/eclipse-sirius/sirius-web/issues/3662[#3662] [sirius-web] Add an extension point to contribute props to ReactFlow diagram renderer, contributed props must be memoized
- https://github.com/eclipse-sirius/sirius-web/issues/3695[#3695] [form] Add an extension point to contribute widget labels decorators


=== Improvements

- https://github.com/eclipse-sirius/sirius-web/issues/3511[#3511] [diagram] Improve edge handle position for border nodes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
} from '@eclipse-sirius/sirius-components-widget-reference';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import Filter from '@mui/icons-material/Filter';
import ImageIcon from '@mui/icons-material/Image';
import LinkIcon from '@mui/icons-material/Link';
import MenuIcon from '@mui/icons-material/Menu';
import WarningIcon from '@mui/icons-material/Warning';
Expand All @@ -62,10 +63,13 @@ import { createProjectAreaCardExtensionPoint } from '../views/project-browser/cr
import { NewProjectCard } from '../views/project-browser/create-projects-area/NewProjectCard';
import { ShowAllProjectTemplatesCard } from '../views/project-browser/create-projects-area/ShowAllProjectTemplatesCard';
import { UploadProjectCard } from '../views/project-browser/create-projects-area/UploadProjectCard';
import { ProjectImagesSettings } from '../views/project-settings/images/ProjectImagesSettings';
import { ellipseNodeStyleDocumentTransform } from './ElipseNodeDocumentTransform';
import { referenceWidgetDocumentTransform } from './ReferenceWidgetDocumentTransform';

import { SelectionDialog } from '@eclipse-sirius/sirius-components-selection';
import { ProjectSettingTabContribution } from '../views/project-settings/ProjectSettingsView.types';
import { projectSettingsTabExtensionPoint } from '../views/project-settings/ProjectSettingsViewExtensionPoints';
const getType = (representation: RepresentationMetadata): string | null => {
const query = representation.kind.substring(representation.kind.indexOf('?') + 1, representation.kind.length);
const params = new URLSearchParams(query);
Expand Down Expand Up @@ -276,7 +280,7 @@ defaultExtensionRegistry.putData(apolloClientOptionsConfigurersExtensionPoint, {
const isReferenceWidget = (widget: GQLWidget): widget is GQLReferenceWidget => widget.__typename === 'ReferenceWidget';

defaultExtensionRegistry.putData(widgetContributionExtensionPoint, {
identifier: 'siriusWeb_${widgetContributionExtensionPoint.identifier}_referenceWidget',
identifier: `siriusWeb_${widgetContributionExtensionPoint.identifier}_referenceWidget`,
data: [
{
name: 'ReferenceWidget',
Expand All @@ -294,4 +298,25 @@ defaultExtensionRegistry.putData(widgetContributionExtensionPoint, {
],
});

/*******************************************************************************
*
* Project settings
*
* Used to register the settings pages for the projects
*
*******************************************************************************/
const defaultSettingPages: ProjectSettingTabContribution[] = [
{
id: 'images',
title: 'Images',
icon: <ImageIcon />,
component: ProjectImagesSettings,
},
];

defaultExtensionRegistry.putData(projectSettingsTabExtensionPoint, {
identifier: `siriusWeb_${projectSettingsTabExtensionPoint.identifier}`,
data: defaultSettingPages,
});

export { defaultExtensionRegistry };
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ export { createProjectAreaCardExtensionPoint } from './views/project-browser/cre
export { projectActionButtonMenuItemExtensionPoint } from './views/project-browser/list-projects-area/ProjectActionButtonExtensionPoints';
export { type ProjectRowProps } from './views/project-browser/list-projects-area/ProjectRow.types';
export { projectsTableRowExtensionPoint } from './views/project-browser/list-projects-area/ProjectsTableExtensionPoints';
export {
type ProjectSettingTabContribution,
type ProjectSettingTabProps,
} from './views/project-settings/ProjectSettingsView.types';
export * from './views/project-settings/ProjectSettingsViewExtensionPoints';
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,40 @@
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
import { useComponent } from '@eclipse-sirius/sirius-components-core';
import { gql, useQuery } from '@apollo/client';
import { useComponent, useData, useMultiToast } from '@eclipse-sirius/sirius-components-core';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import Typography from '@mui/material/Typography';
import { useEffect, useState } from 'react';
import { Redirect, Link as RouterLink, useParams } from 'react-router-dom';
import { makeStyles } from 'tss-react/mui';
import { footerExtensionPoint } from '../../footer/FooterExtensionPoints';

import { NavigationBar } from '../../navigationBar/NavigationBar';
import { ProjectImagesSettings } from './images/ProjectImagesSettings';
import {
GQLGetProjectData,
GQLGetProjectVariables,
GQLProject,
ProjectSettingsParams,
ProjectSettingsViewState,
ProjectSettingTabContribution,
ProjectSettingTabProps,
} from './ProjectSettingsView.types';
import { projectSettingsTabExtensionPoint } from './ProjectSettingsViewExtensionPoints';

const getProjectQuery = gql`
query getProject($projectId: ID!) {
viewer {
project(projectId: $projectId) {
id
name
}
}
}
`;

const useProjectSettingsViewStyles = makeStyles()((theme) => ({
projectSettingsView: {
Expand All @@ -25,25 +52,140 @@ const useProjectSettingsViewStyles = makeStyles()((theme) => ({
gridTemplateRows: 'min-content 1fr min-content',
minHeight: '100vh',
},
center: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
title: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
titleLabel: {
marginRight: theme.spacing(2),
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '100ch',
color: 'inherit',
},
main: {
paddingTop: theme.spacing(3),
paddingBottom: theme.spacing(3),
},
header: {
display: 'grid',
gridTemplateColumns: '1fr max-content',
alignItems: 'center',
padding: theme.spacing(3),
},
tabs: {
display: 'grid',
gridTemplateColumns: '200px 1fr',
gridTemplateRows: '1fr',
gap: theme.spacing(4),
},
tab: {
justifyContent: 'start',
minHeight: 0,
},
noSettingsFound: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingTop: theme.spacing(20),
},
}));

export const ProjectSettingsView = () => {
const { classes } = useProjectSettingsViewStyles();
const { projectId } = useParams<ProjectSettingsParams>();

const { data: projectSettingsTabContributions } = useData(projectSettingsTabExtensionPoint);
const initialSelectTabId: string | null = projectSettingsTabContributions[0]?.id ?? null;

const [state, setState] = useState<ProjectSettingsViewState>({
selectedTabId: initialSelectTabId,
});

const { loading, data, error } = useQuery<GQLGetProjectData, GQLGetProjectVariables>(getProjectQuery, {
variables: {
projectId,
},
});
const { addErrorMessage } = useMultiToast();
useEffect(() => {
if (error) {
addErrorMessage(error.message);
}
}, [error]);
const project: GQLProject | null = data?.viewer.project;

const handleTabChange = (_event, newValue: string) =>
setState((prevState) => ({ ...prevState, selectedTabId: newValue }));

const settingContentContribution: ProjectSettingTabContribution | null = projectSettingsTabContributions.filter(
(contribution) => contribution.id === state.selectedTabId
)[0];
const SettingContent: () => JSX.Element = () => {
if (settingContentContribution) {
const { component: Component } = settingContentContribution;

const props: ProjectSettingTabProps = {};
return <Component {...props} />;
}
return null;
};

const { Component: Footer } = useComponent(footerExtensionPoint);

if (loading) {
return null;
}
if (!project) {
return <Redirect to="/" />;
}

const { id, name } = project;
return (
<div className={classes.projectSettingsView}>
<NavigationBar />
<NavigationBar>
<div className={classes.center}>
<div className={classes.title}>
<Link
variant="h6"
component={RouterLink}
to={`/projects/${id}/edit`}
noWrap
className={classes.titleLabel}
data-testid={`navbar-${name}`}>
{name}
</Link>
</div>
</div>
</NavigationBar>

<main className={classes.main}>
<Container maxWidth="xl">
<Grid container justifyContent="center">
<Grid item xs={8}>
<ProjectImagesSettings />
</Grid>
</Grid>
<div className={classes.header}>
<Typography variant="h4">Settings</Typography>
</div>
{projectSettingsTabContributions.length > 0 && settingContentContribution != null ? (
<div className={classes.tabs}>
<Tabs value={state.selectedTabId} onChange={handleTabChange} orientation="vertical">
{projectSettingsTabContributions.map(({ id, title, icon }) => (
<Tab className={classes.tab} label={title} icon={icon} iconPosition="start" key={id} value={id} />
))}
</Tabs>
<SettingContent />
</div>
) : (
<div className={classes.noSettingsFound}>
<Typography variant="h5">No setting pages found</Typography>
</div>
)}
</Container>
</main>
<Footer />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*******************************************************************************
* 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 ProjectSettingsParams {
projectId: string;
}

export interface ProjectSettingTabContribution {
id: string;
title: string;
icon: React.ReactElement;
component: (props: ProjectSettingTabProps) => JSX.Element | null;
}

export interface ProjectSettingTabProps {}

export interface ProjectSettingsViewState {
selectedTabId: string | null;
}

export interface GQLGetProjectVariables {
projectId: string;
}

export interface GQLGetProjectData {
viewer: GQLViewer;
}

export interface GQLViewer {
project: GQLProject | null;
}

export interface GQLProject {
id: string;
name: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*******************************************************************************
* Copyright (c) 2021, 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

import { DataExtensionPoint } from '@eclipse-sirius/sirius-components-core';
import { ProjectSettingTabContribution } from './ProjectSettingsView.types';

export const projectSettingsTabExtensionPoint: DataExtensionPoint<Array<ProjectSettingTabContribution>> = {
identifier: 'projectSettings#tabContribution',
fallback: [],
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { makeStyles } from 'tss-react/mui';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { makeStyles } from 'tss-react/mui';
import { ProjectSettingTabProps } from '../ProjectSettingsView.types';
import { ImageTable } from './ImageTable';
import { ProjectImagesSettingsParams, ProjectImagesSettingsState } from './ProjectImagesSettings.types';
import { UploadImageModal } from './upload-image/UploadImageModal';
Expand Down Expand Up @@ -43,7 +44,7 @@ const useProjectImagesSettingsStyles = makeStyles()((theme) => ({
},
}));

export const ProjectImagesSettings = () => {
export const ProjectImagesSettings = ({}: ProjectSettingTabProps) => {
const [state, setState] = useState<ProjectImagesSettingsState>({
modal: null,
});
Expand Down Expand Up @@ -83,7 +84,7 @@ export const ProjectImagesSettings = () => {
<>
<div className={classes.imageSettingsViewContainer}>
<div className={classes.header}>
<Typography variant="h4">Project Images</Typography>
<Typography variant="h5">Project Images</Typography>

<div className={classes.actions}>
<Button data-testid="upload-image" color="primary" variant="outlined" onClick={onTriggerUpload}>
Expand Down

0 comments on commit b82afb2

Please sign in to comment.