mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-22 14:54:22 +00:00
💥 Implement group creation, deletion and editing
This commit is contained in:
parent
6f3e2fc9e9
commit
1f1b0c6e41
11 changed files with 315 additions and 49 deletions
|
@ -9,9 +9,9 @@ import {SophonFooter} from "./components/elements/SophonFooter"
|
||||||
import {ErrorCatcherBox} from "./components/errors/ErrorCatcherBox"
|
import {ErrorCatcherBox} from "./components/errors/ErrorCatcherBox"
|
||||||
import {ResourceDescriptionBox} from "./components/generic/ResourceDescriptionBox"
|
import {ResourceDescriptionBox} from "./components/generic/ResourceDescriptionBox"
|
||||||
import {GroupCreateBox} from "./components/group/GroupCreateBox"
|
import {GroupCreateBox} from "./components/group/GroupCreateBox"
|
||||||
import {GroupListBox} from "./components/group/GroupListBox"
|
|
||||||
import {GroupMembersBox} from "./components/group/GroupMembersBox"
|
import {GroupMembersBox} from "./components/group/GroupMembersBox"
|
||||||
import {GroupRouter} from "./components/group/GroupRouter"
|
import {GroupRouter} from "./components/group/GroupRouter"
|
||||||
|
import {GroupStepPage} from "./components/group/GroupStepPage"
|
||||||
import {SophonDescriptionBox} from "./components/informative/SophonDescriptionBox"
|
import {SophonDescriptionBox} from "./components/informative/SophonDescriptionBox"
|
||||||
import {InstanceDescriptionBox} from "./components/instance/InstanceDescriptionBox"
|
import {InstanceDescriptionBox} from "./components/instance/InstanceDescriptionBox"
|
||||||
import {InstanceFormBox} from "./components/instance/InstanceFormBox"
|
import {InstanceFormBox} from "./components/instance/InstanceFormBox"
|
||||||
|
@ -19,12 +19,14 @@ import {InstanceRouter} from "./components/instance/InstanceRouter"
|
||||||
import {NotebookListBox} from "./components/notebook/NotebookListBox"
|
import {NotebookListBox} from "./components/notebook/NotebookListBox"
|
||||||
import {NotebookRouter} from "./components/notebook/NotebookRouter"
|
import {NotebookRouter} from "./components/notebook/NotebookRouter"
|
||||||
import {DebugBox} from "./components/placeholder/DebugBox"
|
import {DebugBox} from "./components/placeholder/DebugBox"
|
||||||
|
import {ProjectCreateBox} from "./components/project/ProjectCreateBox"
|
||||||
import {ProjectListBox} from "./components/project/ProjectListBox"
|
import {ProjectListBox} from "./components/project/ProjectListBox"
|
||||||
import {ProjectRouter} from "./components/project/ProjectRouter"
|
import {ProjectRouter} from "./components/project/ProjectRouter"
|
||||||
import {ThemedBluelib} from "./components/theme/ThemedBluelib"
|
import {ThemedBluelib} from "./components/theme/ThemedBluelib"
|
||||||
import {ThemedTitle} from "./components/theme/ThemedTitle"
|
import {ThemedTitle} from "./components/theme/ThemedTitle"
|
||||||
import {AuthorizationProvider} from "./contexts/authorization"
|
import {AuthorizationProvider} from "./contexts/authorization"
|
||||||
import {CacheProvider} from "./contexts/cache"
|
import {CacheProvider} from "./contexts/cache"
|
||||||
|
import {GroupProvider} from "./contexts/group"
|
||||||
import {InstanceProvider} from "./contexts/instance"
|
import {InstanceProvider} from "./contexts/instance"
|
||||||
import {ThemeProvider} from "./contexts/theme"
|
import {ThemeProvider} from "./contexts/theme"
|
||||||
|
|
||||||
|
@ -32,39 +34,41 @@ import {ThemeProvider} from "./contexts/theme"
|
||||||
function App({..._}: RouteComponentProps) {
|
function App({..._}: RouteComponentProps) {
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() => <>
|
() => <>
|
||||||
|
<Chapter>
|
||||||
<SophonDescriptionBox/>
|
<SophonDescriptionBox/>
|
||||||
|
</Chapter>
|
||||||
<InstanceProvider>
|
<InstanceProvider>
|
||||||
<InstanceRouter
|
<InstanceRouter
|
||||||
unselectedRoute={() => <>
|
unselectedRoute={() => <>
|
||||||
<InstanceFormBox/>
|
<InstanceFormBox/>
|
||||||
</>}
|
</>}
|
||||||
selectedRoute={() => <>
|
selectedRoute={() => <>
|
||||||
|
<Chapter>
|
||||||
<InstanceDescriptionBox/>
|
<InstanceDescriptionBox/>
|
||||||
|
</Chapter>
|
||||||
<AuthorizationProvider>
|
<AuthorizationProvider>
|
||||||
<CacheProvider>
|
<CacheProvider>
|
||||||
<AuthorizationRouter
|
<AuthorizationRouter
|
||||||
unselectedRoute={() => <>
|
unselectedRoute={AuthorizationStepPage}
|
||||||
<AuthorizationStepPage/>
|
|
||||||
</>}
|
|
||||||
selectedRoute={() => <>
|
selectedRoute={() => <>
|
||||||
<GroupRouter
|
<GroupRouter
|
||||||
unselectedRoute={({viewSet}) => <>
|
unselectedRoute={GroupStepPage}
|
||||||
<GroupListBox viewSet={viewSet}/>
|
|
||||||
<GroupCreateBox viewSet={viewSet}/>
|
|
||||||
</>}
|
|
||||||
selectedRoute={({selection}) => <>
|
selectedRoute={({selection}) => <>
|
||||||
|
<GroupProvider resource={selection}>
|
||||||
<Chapter>
|
<Chapter>
|
||||||
<ResourceDescriptionBox resource={selection} icon={faUsers}/>
|
<ResourceDescriptionBox resource={selection} icon={faUsers}/>
|
||||||
<GroupMembersBox resource={selection}/>
|
<GroupMembersBox/>
|
||||||
</Chapter>
|
</Chapter>
|
||||||
<ProjectRouter
|
<ProjectRouter
|
||||||
groupPk={selection.value.slug}
|
groupPk={selection.value.slug}
|
||||||
unselectedRoute={({viewSet}) => <>
|
unselectedRoute={({viewSet}) => <>
|
||||||
<GroupCreateBox resource={selection}/>
|
<GroupCreateBox resource={selection}/>
|
||||||
<ProjectListBox viewSet={viewSet}/>
|
<ProjectListBox viewSet={viewSet}/>
|
||||||
|
<ProjectCreateBox viewSet={viewSet}/>
|
||||||
</>}
|
</>}
|
||||||
selectedRoute={({selection}) => <>
|
selectedRoute={({selection}) => <>
|
||||||
<ResourceDescriptionBox resource={selection} icon={faProjectDiagram}/>
|
<ResourceDescriptionBox resource={selection} icon={faProjectDiagram}/>
|
||||||
|
<ProjectCreateBox resource={selection}/>
|
||||||
<NotebookRouter
|
<NotebookRouter
|
||||||
projectPk={selection.value.slug}
|
projectPk={selection.value.slug}
|
||||||
unselectedRoute={({viewSet}) => <>
|
unselectedRoute={({viewSet}) => <>
|
||||||
|
@ -76,6 +80,7 @@ function App({..._}: RouteComponentProps) {
|
||||||
/>
|
/>
|
||||||
</>}
|
</>}
|
||||||
/>
|
/>
|
||||||
|
</GroupProvider>
|
||||||
</>}
|
</>}
|
||||||
/>
|
/>
|
||||||
</>}
|
</>}
|
||||||
|
|
|
@ -43,7 +43,7 @@ export function GroupCreateBox({viewSet, resource}: GroupCreateBoxProps): JSX.El
|
||||||
const description =
|
const description =
|
||||||
useFormState<string>(
|
useFormState<string>(
|
||||||
resource?.value.description ?? "",
|
resource?.value.description ?? "",
|
||||||
Validators.notZeroLength,
|
Validators.alwaysValid,
|
||||||
)
|
)
|
||||||
|
|
||||||
const members =
|
const members =
|
||||||
|
|
|
@ -4,18 +4,13 @@ import {Box, Heading, Idiomatic, ListUnordered, UAnnotation} from "@steffo/bluel
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {useAuthorizationContext} from "../../contexts/authorization"
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
import {useCacheContext} from "../../contexts/cache"
|
import {useCacheContext} from "../../contexts/cache"
|
||||||
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
import {useGroupContext} from "../../contexts/group"
|
||||||
import {SophonResearchGroup} from "../../types/SophonTypes"
|
|
||||||
|
|
||||||
|
|
||||||
export interface GroupMembersBoxProps {
|
export function GroupMembersBox(): JSX.Element | null {
|
||||||
resource: ManagedResource<SophonResearchGroup>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function GroupMembersBox({resource}: GroupMembersBoxProps): JSX.Element | null {
|
|
||||||
const authorization = useAuthorizationContext()
|
const authorization = useAuthorizationContext()
|
||||||
const cache = useCacheContext()
|
const cache = useCacheContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
if(!cache) {
|
if(!cache) {
|
||||||
return null
|
return null
|
||||||
|
@ -23,8 +18,11 @@ export function GroupMembersBox({resource}: GroupMembersBoxProps): JSX.Element |
|
||||||
if(!cache.users) {
|
if(!cache.users) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
if(!group) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const trueMembers = [...new Set([resource.value.owner, ...resource.value.members])]
|
const trueMembers = [...new Set([group.value.owner, ...group.value.members])]
|
||||||
|
|
||||||
const users = trueMembers.map((id, index) => {
|
const users = trueMembers.map((id, index) => {
|
||||||
const user = cache.getUserById(id)
|
const user = cache.getUserById(id)
|
||||||
|
@ -45,7 +43,7 @@ export function GroupMembersBox({resource}: GroupMembersBoxProps): JSX.Element |
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Heading level={3}>
|
<Heading level={3}>
|
||||||
<FontAwesomeIcon icon={faUsersCog}/> Members of <Idiomatic>{resource.value.name}</Idiomatic>
|
<FontAwesomeIcon icon={faUsersCog}/> Members of <Idiomatic>{group.value.name}</Idiomatic>
|
||||||
</Heading>
|
</Heading>
|
||||||
<ListUnordered>
|
<ListUnordered>
|
||||||
{users}
|
{users}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import {useManagedViewSet} from "../../hooks/useManagedViewSet"
|
import {useManagedViewSet} from "../../hooks/useManagedViewSet"
|
||||||
import {Dict} from "../../types/ExtraTypes"
|
|
||||||
import {SophonResearchGroup} from "../../types/SophonTypes"
|
import {SophonResearchGroup} from "../../types/SophonTypes"
|
||||||
import {ViewSetRouter} from "../routing/ViewSetRouter"
|
import {ViewSetRouter} from "../routing/ViewSetRouter"
|
||||||
|
|
||||||
|
|
||||||
export interface GroupRouterProps {
|
export interface GroupRouterProps {
|
||||||
unselectedRoute: (props: Dict<any>) => JSX.Element | null,
|
unselectedRoute: (props: any) => JSX.Element | null,
|
||||||
selectedRoute: (props: Dict<any>) => JSX.Element | null,
|
selectedRoute: (props: any) => JSX.Element | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
20
frontend/src/components/group/GroupStepPage.tsx
Normal file
20
frontend/src/components/group/GroupStepPage.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import {ManagedViewSet} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonResearchGroup} from "../../types/SophonTypes"
|
||||||
|
import {GroupCreateBox} from "./GroupCreateBox"
|
||||||
|
import {GroupListBox} from "./GroupListBox"
|
||||||
|
|
||||||
|
|
||||||
|
export interface GroupStepPageProps {
|
||||||
|
viewSet: ManagedViewSet<SophonResearchGroup>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function GroupStepPage({viewSet}: GroupStepPageProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GroupListBox viewSet={viewSet}/>
|
||||||
|
<GroupCreateBox viewSet={viewSet}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
165
frontend/src/components/project/ProjectCreateBox.tsx
Normal file
165
frontend/src/components/project/ProjectCreateBox.tsx
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import {Box, Details, Form, Idiomatic as I, useFormState} from "@steffo/bluelib-react"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useGroupContext} from "../../contexts/group"
|
||||||
|
import {ManagedResource, ManagedViewSet} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonResearchProject} from "../../types/SophonTypes"
|
||||||
|
import {Validators} from "../../utils/Validators"
|
||||||
|
|
||||||
|
|
||||||
|
export interface ProjectCreateBoxProps {
|
||||||
|
viewSet?: ManagedViewSet<SophonResearchProject>,
|
||||||
|
resource?: ManagedResource<SophonResearchProject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function ProjectCreateBox({viewSet, resource}: ProjectCreateBoxProps): JSX.Element | null {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
|
const name =
|
||||||
|
useFormState<string>(
|
||||||
|
resource?.value.name ?? "",
|
||||||
|
Validators.notZeroLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
const description =
|
||||||
|
useFormState<string>(
|
||||||
|
resource?.value.description ?? "",
|
||||||
|
Validators.alwaysValid,
|
||||||
|
)
|
||||||
|
|
||||||
|
const visibility =
|
||||||
|
useFormState<"PUBLIC" | "INTERNAL" | "PRIVATE" | undefined>(
|
||||||
|
resource?.value.visibility ?? undefined,
|
||||||
|
Validators.notEmpty,
|
||||||
|
)
|
||||||
|
|
||||||
|
const slug =
|
||||||
|
React.useMemo(
|
||||||
|
() => resource ? resource.value.slug : name.value.replaceAll(/[^A-Za-z0-9-]/g, "-").toLowerCase(),
|
||||||
|
[resource, name],
|
||||||
|
)
|
||||||
|
|
||||||
|
const canAdministrate =
|
||||||
|
React.useMemo(
|
||||||
|
() => {
|
||||||
|
if(resource) {
|
||||||
|
if(!authorization) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!group) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!authorization.state.user) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if(!(
|
||||||
|
group.value.members.includes(authorization.state.user.id) || group.value.owner === authorization.state.user.id
|
||||||
|
)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[authorization, resource],
|
||||||
|
)
|
||||||
|
|
||||||
|
const applyChanges =
|
||||||
|
React.useCallback(
|
||||||
|
async () => {
|
||||||
|
if(resource) {
|
||||||
|
await resource.update({
|
||||||
|
name: name.value,
|
||||||
|
slug: slug,
|
||||||
|
description: description.value,
|
||||||
|
visibility: visibility.value,
|
||||||
|
group: group!.value.slug,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await viewSet!.create({
|
||||||
|
name: name.value,
|
||||||
|
slug: slug,
|
||||||
|
description: description.value,
|
||||||
|
visibility: visibility.value,
|
||||||
|
group: group!.value.slug,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[viewSet, resource, name, slug, description, visibility, group],
|
||||||
|
)
|
||||||
|
|
||||||
|
const canApply =
|
||||||
|
React.useMemo(
|
||||||
|
() => name.validity === true && visibility.validity === true && Boolean(authorization?.state.user?.username) && group,
|
||||||
|
[name, visibility, authorization],
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasError =
|
||||||
|
React.useMemo(
|
||||||
|
() => viewSet?.operationError || resource?.error,
|
||||||
|
[viewSet, resource],
|
||||||
|
)
|
||||||
|
|
||||||
|
if(!authorization?.state.token ||
|
||||||
|
!(
|
||||||
|
viewSet || resource
|
||||||
|
) ||
|
||||||
|
!canAdministrate) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Details>
|
||||||
|
<Details.Summary>
|
||||||
|
{resource ? <>Edit <I>{resource.value.name}</I></> : "Create a new research project"}
|
||||||
|
</Details.Summary>
|
||||||
|
<Details.Content>
|
||||||
|
<Form>
|
||||||
|
<Form.Field
|
||||||
|
label={"Name"}
|
||||||
|
required={true}
|
||||||
|
{...name}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
label={"Slug"}
|
||||||
|
required={true}
|
||||||
|
disabled={true}
|
||||||
|
value={slug}
|
||||||
|
validity={slug.length > 0 ? true : undefined}
|
||||||
|
/>
|
||||||
|
<Form.Area
|
||||||
|
label={"Description"}
|
||||||
|
{...description}
|
||||||
|
/>
|
||||||
|
<Form.Select
|
||||||
|
label={"Visibility"}
|
||||||
|
options={{
|
||||||
|
"": undefined,
|
||||||
|
"🔒 Private": "PRIVATE",
|
||||||
|
"🎓 Internal": "INTERNAL",
|
||||||
|
"🌍 Public": "PUBLIC",
|
||||||
|
}}
|
||||||
|
{...visibility}
|
||||||
|
/>
|
||||||
|
<Form.Row>
|
||||||
|
<Form.Button
|
||||||
|
type={"button"}
|
||||||
|
onClick={applyChanges}
|
||||||
|
disabled={!canApply}
|
||||||
|
builtinColor={hasError ? "red" : undefined}
|
||||||
|
>
|
||||||
|
{resource ? "Edit" : "Create"}
|
||||||
|
</Form.Button>
|
||||||
|
</Form.Row>
|
||||||
|
</Form>
|
||||||
|
</Details.Content>
|
||||||
|
</Details>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
40
frontend/src/components/project/ProjectDeleteButton.tsx
Normal file
40
frontend/src/components/project/ProjectDeleteButton.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {faTrash} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useGroupContext} from "../../contexts/group"
|
||||||
|
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonResearchProject} from "../../types/SophonTypes"
|
||||||
|
import {SafetyButton} from "../elements/SafetyButton"
|
||||||
|
|
||||||
|
|
||||||
|
export interface ProjectDeleteButtonProps {
|
||||||
|
resource: ManagedResource<SophonResearchProject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function ProjectDeleteButton({resource}: ProjectDeleteButtonProps): JSX.Element | null {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
|
if(!authorization) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!group) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!authorization.state.user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!(
|
||||||
|
group.value.members.includes(authorization.state.user.id) || group.value.owner === authorization.state.user.id
|
||||||
|
)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafetyButton timeout={3} onClick={() => resource.destroy()}>
|
||||||
|
<FontAwesomeIcon icon={faTrash} pulse={resource.busy}/> Delete
|
||||||
|
</SafetyButton>
|
||||||
|
)
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
import {SophonResearchProject} from "../../types/SophonTypes"
|
import {SophonResearchProject} from "../../types/SophonTypes"
|
||||||
import {Link} from "../elements/Link"
|
import {Link} from "../elements/Link"
|
||||||
import {ResourcePanel} from "../elements/ResourcePanel"
|
import {ResourcePanel} from "../elements/ResourcePanel"
|
||||||
|
import {ProjectDeleteButton} from "./ProjectDeleteButton"
|
||||||
|
|
||||||
|
|
||||||
export interface ProjectResourcePanelProps {
|
export interface ProjectResourcePanelProps {
|
||||||
|
@ -34,7 +35,7 @@ export function ProjectResourcePanel({resource}: ProjectResourcePanelProps): JSX
|
||||||
|
|
||||||
</ResourcePanel.Text>
|
</ResourcePanel.Text>
|
||||||
<ResourcePanel.Buttons>
|
<ResourcePanel.Buttons>
|
||||||
|
<ProjectDeleteButton resource={resource}/>
|
||||||
</ResourcePanel.Buttons>
|
</ResourcePanel.Buttons>
|
||||||
</ResourcePanel>
|
</ResourcePanel>
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@ export function ProjectRouter({groupPk, ...props}: ProjectRouterProps): JSX.Elem
|
||||||
return (
|
return (
|
||||||
<ViewSetRouter
|
<ViewSetRouter
|
||||||
{...props}
|
{...props}
|
||||||
viewSet={useManagedViewSet<SophonResearchProject>(`/api/projects/by-group/${groupPk}`, "slug")}
|
viewSet={useManagedViewSet<SophonResearchProject>(`/api/projects/by-group/${groupPk}/`, "slug")}
|
||||||
pathSegment={"researchProject"}
|
pathSegment={"researchProject"}
|
||||||
pkKey={"slug"}
|
pkKey={"slug"}
|
||||||
/>
|
/>
|
||||||
|
|
21
frontend/src/contexts/group.tsx
Normal file
21
frontend/src/contexts/group.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import {ManagedResource} from "../hooks/useManagedViewSet"
|
||||||
|
import {WithChildren, WithResource} from "../types/ExtraTypes"
|
||||||
|
import {SophonResearchGroup} from "../types/SophonTypes"
|
||||||
|
|
||||||
|
|
||||||
|
const groupContext = React.createContext<ManagedResource<SophonResearchGroup> | undefined>(undefined)
|
||||||
|
const GroupContext = groupContext
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to access the {@link groupContext}.
|
||||||
|
*/
|
||||||
|
export function useGroupContext(): ManagedResource<SophonResearchGroup> | undefined {
|
||||||
|
return React.useContext(groupContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function GroupProvider({resource, children}: WithResource<SophonResearchGroup> & WithChildren): JSX.Element {
|
||||||
|
return <GroupContext.Provider value={resource} children={children}/>
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import {ManagedResource, ManagedViewSet} from "../hooks/useManagedViewSet"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,3 +16,19 @@ export interface Dict<T> {
|
||||||
export interface WithChildren {
|
export interface WithChildren {
|
||||||
children?: React.ReactNode,
|
children?: React.ReactNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props including the selection key.
|
||||||
|
*/
|
||||||
|
export interface WithResource<T> {
|
||||||
|
resource: ManagedResource<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props including the viewset key.
|
||||||
|
*/
|
||||||
|
export interface WithViewSet<T> {
|
||||||
|
viewSet: ManagedViewSet<T>,
|
||||||
|
}
|
Loading…
Reference in a new issue