Skip to content

Commit

Permalink
[261] Ensure that the workspace view is located in the project router
Browse files Browse the repository at this point in the history
Bug: #261
Signed-off-by: Stéphane Bégaudeau <stephane.begaudeau@gmail.com>
  • Loading branch information
sbegaudeau committed Oct 10, 2023
1 parent 8493060 commit 43a0d3b
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 137 deletions.
2 changes: 0 additions & 2 deletions frontend/svalyn-studio-app/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { ProfilesRouter } from '../profiles/ProfilesRouter';
import { ProjectRouter } from '../projects/ProjectRouter';
import { SearchRouter } from '../search/SearchRouter';
import { SettingsRouter } from '../settings/SettingsRouter';
import { WorkspaceView } from '../workspace/WorkspaceView';

export const App = () => {
return (
Expand All @@ -45,7 +44,6 @@ export const App = () => {
<Route path="/new/*" element={<NewRouter />} />
<Route path="/orgs/:organizationIdentifier/*" element={<OrganizationRouter />} />
<Route path="/projects/:projectIdentifier/*" element={<ProjectRouter />} />
<Route path="/projects/:projectIdentifier/changes/:changeId" element={<WorkspaceView />} />
<Route path="/domains/*" element={<DomainsRouter />} />
<Route path="/search/*" element={<SearchRouter />} />
<Route path="/profiles/*" element={<ProfilesRouter />} />
Expand Down
87 changes: 87 additions & 0 deletions frontend/svalyn-studio-app/src/projects/ProjectProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2023 Stéphane Bégaudeau.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { gql, useQuery } from '@apollo/client';
import CorporateFareIcon from '@mui/icons-material/CorporateFare';
import { useSnackbar } from 'notistack';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { NotFoundView } from '../notfound/NotFoundView';
import { goToDomains, goToHelp, goToHome, goToNotifications, goToSettings } from '../palette/DefaultPaletteActions';
import { PaletteNavigationAction } from '../palette/Palette.types';
import { usePalette } from '../palette/usePalette';
import { GetProjectData, GetProjectVariables, ProjectProviderProps } from './ProjectProvider.types';
import { ProjectContext } from './useProject';

const getProjectQuery = gql`
query getProject($identifier: ID!) {
viewer {
project(identifier: $identifier) {
identifier
name
description
organization {
identifier
name
role
}
}
}
}
`;

export const ProjectProvider = ({ children }: ProjectProviderProps) => {
const { projectIdentifier } = useParams();

const { enqueueSnackbar } = useSnackbar();
const { setActions } = usePalette();

const variables: GetProjectVariables = { identifier: projectIdentifier ?? '' };
const { data, error } = useQuery<GetProjectData, GetProjectVariables>(getProjectQuery, { variables });

useEffect(() => {
if (data) {
const {
viewer: { project },
} = data;
if (project) {
const backToOrganization: PaletteNavigationAction = {
type: 'navigation-action',
id: 'go-to-organization',
icon: <CorporateFareIcon fontSize="small" />,
label: project.organization.name,
to: `/orgs/${project.organization.identifier}`,
};
setActions([goToHome, backToOrganization, goToDomains, goToNotifications, goToSettings, goToHelp]);
}
}
if (error) {
enqueueSnackbar(error.message, { variant: 'error' });
}
}, [data, error]);

if (!data) {
return null;
}
if (!data.viewer.project) {
return <NotFoundView />;
}

return <ProjectContext.Provider value={{ project: data.viewer.project }}>{children}</ProjectContext.Provider>;
};
49 changes: 49 additions & 0 deletions frontend/svalyn-studio-app/src/projects/ProjectProvider.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2023 Stéphane Bégaudeau.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

export interface ProjectProviderProps {
children?: React.ReactNode;
}

export interface GetProjectData {
viewer: Viewer;
}

export interface Viewer {
project: Project | null;
}

export interface Project {
identifier: string;
name: string;
description: string;
organization: Organization;
}

export interface Organization {
identifier: string;
name: string;
role: MembershipRole;
}

export type MembershipRole = 'ADMIN' | 'MEMBER' | 'NONE';

export interface GetProjectVariables {
identifier: string;
}
31 changes: 21 additions & 10 deletions frontend/svalyn-studio-app/src/projects/ProjectRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import { Route, Routes } from 'react-router-dom';
import { ProjectProvider } from './ProjectProvider';
import { ProjectShell } from './ProjectShell';
import { ProjectActivityView } from './activity/ProjectActivityView';
import { ChangeProposalRouter } from './changeproposal/ChangeProposalRouter';
Expand All @@ -27,21 +28,31 @@ import { NewChangeProposalView } from './new-changeproposal/NewChangeProposalVie
import { ResourceView } from './resource/ResourceView';
import { ProjectSettingsView } from './settings/ProjectSettingsView';
import { ProjectTagsView } from './tags/ProjectTagsView';
import { WorkspaceView } from './workspace/WorkspaceView';

export const ProjectRouter = () => {
return (
<ProjectShell>
<ProjectProvider>
<Routes>
<Route index element={<ProjectHomeView />} />
<Route path="branches/:branchName" element={<ProjectHomeView />} />
<Route path="activity" element={<ProjectActivityView />} />
<Route path="changeproposals" element={<ProjectChangeProposalsView />} />
<Route path="changeproposals/:changeProposalIdentifier/*" element={<ChangeProposalRouter />} />
<Route path="tags" element={<ProjectTagsView />} />
<Route path="settings" element={<ProjectSettingsView />} />
<Route path="new/changeproposal" element={<NewChangeProposalView />} />
<Route path="changes/:changeId/resources/*" element={<ResourceView />} />
<Route index element={withProjectShell(ProjectHomeView)} />
<Route path="branches/:branchName" element={withProjectShell(ProjectHomeView)} />
<Route path="activity" element={withProjectShell(ProjectActivityView)} />
<Route path="changeproposals" element={withProjectShell(ProjectChangeProposalsView)} />
<Route path="changeproposals/:changeProposalIdentifier/*" element={withProjectShell(ChangeProposalRouter)} />
<Route path="tags" element={withProjectShell(ProjectTagsView)} />
<Route path="settings" element={withProjectShell(ProjectSettingsView)} />
<Route path="new/changeproposal" element={withProjectShell(NewChangeProposalView)} />
<Route path="changes/:changeId" element={<WorkspaceView />} />
<Route path="changes/:changeId/resources/*" element={withProjectShell(ResourceView)} />
</Routes>
</ProjectProvider>
);
};

const withProjectShell = (Component: () => JSX.Element | null) => {
return (
<ProjectShell>
<Component />
</ProjectShell>
);
};
98 changes: 16 additions & 82 deletions frontend/svalyn-studio-app/src/projects/ProjectShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,95 +17,29 @@
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import { gql, useQuery } from '@apollo/client';
import CorporateFareIcon from '@mui/icons-material/CorporateFare';
import Box from '@mui/material/Box';
import { useSnackbar } from 'notistack';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Navbar } from '../navbars/Navbar';
import { NotFoundView } from '../notfound/NotFoundView';
import { goToDomains, goToHelp, goToHome, goToNotifications, goToSettings } from '../palette/DefaultPaletteActions';
import { PaletteNavigationAction } from '../palette/Palette.types';
import { usePalette } from '../palette/usePalette';
import { ProjectBreadcrumbs } from './ProjectBreadcrumbs';
import { ProjectDrawer } from './ProjectDrawer';
import { GetProjectData, GetProjectVariables, ProjectShellProps } from './ProjectShell.types';
import { ProjectContext } from './useProject';

const getProjectQuery = gql`
query getProject($identifier: ID!) {
viewer {
project(identifier: $identifier) {
identifier
name
description
organization {
identifier
name
role
}
}
}
}
`;
import { ProjectShellProps } from './ProjectShell.types';

export const ProjectShell = ({ children }: ProjectShellProps) => {
const { enqueueSnackbar } = useSnackbar();

const { projectIdentifier } = useParams();
const variables: GetProjectVariables = { identifier: projectIdentifier ?? '' };
const { data, error } = useQuery<GetProjectData, GetProjectVariables>(getProjectQuery, { variables });

const { setActions } = usePalette();

useEffect(() => {
if (data) {
const {
viewer: { project },
} = data;
if (project) {
const backToOrganization: PaletteNavigationAction = {
type: 'navigation-action',
id: 'go-to-organization',
icon: <CorporateFareIcon fontSize="small" />,
label: project.organization.name,
to: `/orgs/${project.organization.identifier}`,
};
setActions([goToHome, backToOrganization, goToDomains, goToNotifications, goToSettings, goToHelp]);
}
}
if (error) {
enqueueSnackbar(error.message, { variant: 'error' });
}
}, [data, error]);

if (!data) {
return null;
}

if (!data.viewer.project) {
return <NotFoundView />;
}

return (
<ProjectContext.Provider value={{ project: data.viewer.project }}>
<Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<Navbar>
<ProjectBreadcrumbs />
</Navbar>
<Box
sx={{
display: 'grid',
gridTemplateRows: '1fr',
gridTemplateColumns: '64px calc(100vw - 64px)',
flexGrow: '1',
}}
>
<ProjectDrawer />
{children}
</Box>
<Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<Navbar>
<ProjectBreadcrumbs />
</Navbar>
<Box
sx={{
display: 'grid',
gridTemplateRows: '1fr',
gridTemplateColumns: '64px calc(100vw - 64px)',
flexGrow: '1',
}}
>
<ProjectDrawer />
{children}
</Box>
</ProjectContext.Provider>
</Box>
);
};
27 changes: 0 additions & 27 deletions frontend/svalyn-studio-app/src/projects/ProjectShell.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,3 @@
export interface ProjectShellProps {
children?: React.ReactNode;
}

export interface GetProjectData {
viewer: Viewer;
}

export interface Viewer {
project: Project | null;
}

export interface Project {
identifier: string;
name: string;
description: string;
organization: Organization;
}

export interface Organization {
identifier: string;
name: string;
role: MembershipRole;
}

export type MembershipRole = 'ADMIN' | 'MEMBER' | 'NONE';

export interface GetProjectVariables {
identifier: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import Box from '@mui/material/Box';
import { useSnackbar } from 'notistack';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Workbench } from '../workbench/Workbench';
import { IEditingContext } from '../workbench/api/editingcontext/EditingContext.types';
import { EditingContextProvider } from '../workbench/api/editingcontext/EditingContextProvider';
import { IAdaptable, IAdapterFactory } from '../workbench/api/providers/AdapterFactory.types';
import { AdapterFactoryProvider } from '../workbench/api/providers/AdapterFactoryProvider';
import { Workbench } from '../../workbench/Workbench';
import { IEditingContext } from '../../workbench/api/editingcontext/EditingContext.types';
import { EditingContextProvider } from '../../workbench/api/editingcontext/EditingContextProvider';
import { IAdaptable, IAdapterFactory } from '../../workbench/api/providers/AdapterFactory.types';
import { AdapterFactoryProvider } from '../../workbench/api/providers/AdapterFactoryProvider';
import { Change, GetChangeResourcesData, GetChangeResourcesVariables } from './WorkspaceView.types';
import { ChangeData, ChangeDataItemProvider } from './data/ChangeData';
import { FileData, FileDataItemProvider } from './data/FileData';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
*/

import { ReactNode } from 'react';
import { IAdaptable } from '../../workbench/api/providers/AdapterFactory.types';
import { IAdaptable } from '../../../workbench/api/providers/AdapterFactory.types';
import {
IItemIdentityProvider,
IItemLabelProvider,
IItemViewProvider,
ITreeItemContentProvider,
ViewContent,
} from '../../workbench/api/providers/ItemProviders.types';
} from '../../../workbench/api/providers/ItemProviders.types';
import { IContainerData, IResourceData } from './ResourceData';

export class ChangeData implements IContainerData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@

import { ApolloClient, gql } from '@apollo/client';
import { ReactNode } from 'react';
import { IAdaptable } from '../../workbench/api/providers/AdapterFactory.types';
import { IAdaptable } from '../../../workbench/api/providers/AdapterFactory.types';
import {
IItemIdentityProvider,
IItemLabelProvider,
IItemViewProvider,
ITreeItemContentProvider,
ViewContent,
} from '../../workbench/api/providers/ItemProviders.types';
} from '../../../workbench/api/providers/ItemProviders.types';
import { IContainerData, IResourceData } from './ResourceData';

export class FileData implements IResourceData {
Expand Down

0 comments on commit 43a0d3b

Please sign in to comment.