Loading frontend/src/components/ExerciseList/ExerciseCard.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -97,6 +97,7 @@ const ExerciseCard: FC<ExerciseCardProps> = ({ title={ exercise.running ? 'Cannot delete a running exercise' : undefined } active={alertOpen} > Delete </Button> Loading @@ -111,6 +112,7 @@ const ExerciseCard: FC<ExerciseCardProps> = ({ </> ), [ alertOpen, details, exercise.exerciseStart, exercise.finished, Loading frontend/src/pages/instructor/+definitionUploader.tsx→frontend/src/exercisepanel/DefinitionManager/DefinitionUploader.tsx +16 −8 Original line number Diff line number Diff line import { useModals } from '@/router' import { Button, Classes, Loading @@ -19,14 +18,24 @@ import { import { dialog } from '@inject/shared/css/dialog' import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyContext' import csrfFetch from '@inject/shared/utils/csrfFetch' import type { ChangeEvent } from 'react' import type { ChangeEvent, Dispatch, FC, SetStateAction } from 'react' import { useCallback, useMemo, useState } from 'react' const DefinitionUploader = () => { const [open, setOpen] = useState<boolean>(true) const { close } = useModals() interface DefinitionUploaderProps { open: boolean setOpen: Dispatch<SetStateAction<boolean>> } const DefinitionUploader: FC<DefinitionUploaderProps> = ({ open, setOpen }) => { const [name, setName] = useState<string>('') const [file, setFile] = useState<File | undefined>() const reset = useCallback(() => { setOpen(false) setName('') setFile(undefined) }, [setOpen]) const { notify } = useNotifyContext() const host = useHost() const client = useApolloClient() Loading Loading @@ -63,7 +72,7 @@ const DefinitionUploader = () => { notify(res.detail, { intent: 'danger' }) return } setOpen(false) reset() client.refetchQueries({ include: [GetDefinitionsDocument] }) }) .finally(() => setLoadingSubmit(false)) Loading Loading @@ -103,8 +112,7 @@ const DefinitionUploader = () => { <Dialog className={dialog} isOpen={open} onClose={() => setOpen(false)} onClosed={() => close()} onClose={reset} icon='upload' title='Upload a definition' > Loading frontend/src/exercisepanel/DefinitionManager/components/Definition.tsx +6 −1 Original line number Diff line number Diff line Loading @@ -54,7 +54,12 @@ const DefinitionComp: FC<DefinitionProps> = ({ definition }) => { alignText='left' style={{ marginTop: '1rem', alignSelf: 'end' }} > <Button type='button' onClick={() => setAlertOpen(true)} icon='trash'> <Button type='button' active={alertOpen} onClick={() => setAlertOpen(true)} icon='trash' > Delete </Button> <LinkButton Loading frontend/src/exercisepanel/DefinitionManager/index.tsx +41 −37 Original line number Diff line number Diff line import { useModals } from '@/router' import { Button, ButtonGroup, Loading @@ -10,7 +9,8 @@ import Reloader from '@inject/graphql/components/Reloader' import { useGetDefinitions } from '@inject/graphql/queries/GetDefinitions.generated' import CardList from '@inject/shared/components/CardList' import notEmpty from '@inject/shared/utils/notEmpty' import type { FC } from 'react' import { useState, type FC } from 'react' import DefinitionUploader from './DefinitionUploader' import Definition from './components/Definition' interface DefinitionManagerProps { Loading @@ -19,18 +19,20 @@ interface DefinitionManagerProps { const DefinitionManager: FC<DefinitionManagerProps> = ({ className }) => { const { data, refetch } = useGetDefinitions() const { open } = useModals() const [uploadOpen, setUploadOpen] = useState(false) const definitions = (data?.definitions || []).filter(notEmpty) return ( <> <Section title='Definitions' rightElement={ <ButtonGroup onClick={e => e.stopPropagation()}> {/* TODO: set as active when uploader is open */} <Button active={uploadOpen} onClick={() => { open('/instructor/definitionUploader') setUploadOpen(true) }} icon='upload' > Loading @@ -39,7 +41,6 @@ const DefinitionManager: FC<DefinitionManagerProps> = ({ className }) => { <Reloader onRefetch={refetch} /> </ButtonGroup> } collapsible > <SectionCard> {definitions.length === 0 ? ( Loading @@ -57,6 +58,9 @@ const DefinitionManager: FC<DefinitionManagerProps> = ({ className }) => { )} </SectionCard> </Section> <DefinitionUploader open={uploadOpen} setOpen={setUploadOpen} /> </> ) } Loading frontend/src/pages/instructor/+exerciseCreator.tsx→frontend/src/exercisepanel/ExerciseManager/ExerciseCreator.tsx +20 −12 Original line number Diff line number Diff line import { useModals } from '@/router' import { Button, Classes, Loading @@ -18,19 +17,31 @@ import { GetExercisesDocument } from '@inject/graphql/queries/GetExercises.gener import { dialog } from '@inject/shared/css/dialog' import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyContext' import notEmpty from '@inject/shared/utils/notEmpty' import type { Dispatch, FC, SetStateAction } from 'react' import { useCallback, useState } from 'react' const TEAMS_MAX = 20 const ExerciseCreator = () => { const [open, setOpen] = useState<boolean>(true) interface ExerciseCreatorProps { open: boolean setOpen: Dispatch<SetStateAction<boolean>> } const ExerciseCreator: FC<ExerciseCreatorProps> = ({ open, setOpen }) => { const [name, setName] = useState<string>('') const { close } = useModals() const [definition, setDefinition] = useState<Definition>() const [count, setCount] = useState<undefined | number>() const reset = useCallback(() => { setName('') setDefinition(undefined) setCount(undefined) setOpen(false) }, [setOpen]) const [addExercise, { loading }] = useCreateExercises() const { data: definitionData } = useGetDefinitions() const { notify } = useNotifyContext() const [definition, setDefinition] = useState<Definition>() const [count, setCount] = useState<undefined | number>() const handleSubmit = useCallback(() => { if (!definition || !count) return Loading @@ -42,23 +53,20 @@ const ExerciseCreator = () => { name, }, refetchQueries: [GetExercisesDocument], onCompleted: () => { setOpen(false) }, onCompleted: reset, onError: err => { notify(err.message, { intent: 'danger', }) }, }) }, [definition, count, addExercise, name, notify]) }, [definition, count, addExercise, name, reset, notify]) return ( <Dialog className={dialog} isOpen={open} onClose={() => setOpen(false)} onClosed={() => close()} onClose={reset} icon='add' title='Create an exercise' > Loading Loading
frontend/src/components/ExerciseList/ExerciseCard.tsx +2 −0 Original line number Diff line number Diff line Loading @@ -97,6 +97,7 @@ const ExerciseCard: FC<ExerciseCardProps> = ({ title={ exercise.running ? 'Cannot delete a running exercise' : undefined } active={alertOpen} > Delete </Button> Loading @@ -111,6 +112,7 @@ const ExerciseCard: FC<ExerciseCardProps> = ({ </> ), [ alertOpen, details, exercise.exerciseStart, exercise.finished, Loading
frontend/src/pages/instructor/+definitionUploader.tsx→frontend/src/exercisepanel/DefinitionManager/DefinitionUploader.tsx +16 −8 Original line number Diff line number Diff line import { useModals } from '@/router' import { Button, Classes, Loading @@ -19,14 +18,24 @@ import { import { dialog } from '@inject/shared/css/dialog' import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyContext' import csrfFetch from '@inject/shared/utils/csrfFetch' import type { ChangeEvent } from 'react' import type { ChangeEvent, Dispatch, FC, SetStateAction } from 'react' import { useCallback, useMemo, useState } from 'react' const DefinitionUploader = () => { const [open, setOpen] = useState<boolean>(true) const { close } = useModals() interface DefinitionUploaderProps { open: boolean setOpen: Dispatch<SetStateAction<boolean>> } const DefinitionUploader: FC<DefinitionUploaderProps> = ({ open, setOpen }) => { const [name, setName] = useState<string>('') const [file, setFile] = useState<File | undefined>() const reset = useCallback(() => { setOpen(false) setName('') setFile(undefined) }, [setOpen]) const { notify } = useNotifyContext() const host = useHost() const client = useApolloClient() Loading Loading @@ -63,7 +72,7 @@ const DefinitionUploader = () => { notify(res.detail, { intent: 'danger' }) return } setOpen(false) reset() client.refetchQueries({ include: [GetDefinitionsDocument] }) }) .finally(() => setLoadingSubmit(false)) Loading Loading @@ -103,8 +112,7 @@ const DefinitionUploader = () => { <Dialog className={dialog} isOpen={open} onClose={() => setOpen(false)} onClosed={() => close()} onClose={reset} icon='upload' title='Upload a definition' > Loading
frontend/src/exercisepanel/DefinitionManager/components/Definition.tsx +6 −1 Original line number Diff line number Diff line Loading @@ -54,7 +54,12 @@ const DefinitionComp: FC<DefinitionProps> = ({ definition }) => { alignText='left' style={{ marginTop: '1rem', alignSelf: 'end' }} > <Button type='button' onClick={() => setAlertOpen(true)} icon='trash'> <Button type='button' active={alertOpen} onClick={() => setAlertOpen(true)} icon='trash' > Delete </Button> <LinkButton Loading
frontend/src/exercisepanel/DefinitionManager/index.tsx +41 −37 Original line number Diff line number Diff line import { useModals } from '@/router' import { Button, ButtonGroup, Loading @@ -10,7 +9,8 @@ import Reloader from '@inject/graphql/components/Reloader' import { useGetDefinitions } from '@inject/graphql/queries/GetDefinitions.generated' import CardList from '@inject/shared/components/CardList' import notEmpty from '@inject/shared/utils/notEmpty' import type { FC } from 'react' import { useState, type FC } from 'react' import DefinitionUploader from './DefinitionUploader' import Definition from './components/Definition' interface DefinitionManagerProps { Loading @@ -19,18 +19,20 @@ interface DefinitionManagerProps { const DefinitionManager: FC<DefinitionManagerProps> = ({ className }) => { const { data, refetch } = useGetDefinitions() const { open } = useModals() const [uploadOpen, setUploadOpen] = useState(false) const definitions = (data?.definitions || []).filter(notEmpty) return ( <> <Section title='Definitions' rightElement={ <ButtonGroup onClick={e => e.stopPropagation()}> {/* TODO: set as active when uploader is open */} <Button active={uploadOpen} onClick={() => { open('/instructor/definitionUploader') setUploadOpen(true) }} icon='upload' > Loading @@ -39,7 +41,6 @@ const DefinitionManager: FC<DefinitionManagerProps> = ({ className }) => { <Reloader onRefetch={refetch} /> </ButtonGroup> } collapsible > <SectionCard> {definitions.length === 0 ? ( Loading @@ -57,6 +58,9 @@ const DefinitionManager: FC<DefinitionManagerProps> = ({ className }) => { )} </SectionCard> </Section> <DefinitionUploader open={uploadOpen} setOpen={setUploadOpen} /> </> ) } Loading
frontend/src/pages/instructor/+exerciseCreator.tsx→frontend/src/exercisepanel/ExerciseManager/ExerciseCreator.tsx +20 −12 Original line number Diff line number Diff line import { useModals } from '@/router' import { Button, Classes, Loading @@ -18,19 +17,31 @@ import { GetExercisesDocument } from '@inject/graphql/queries/GetExercises.gener import { dialog } from '@inject/shared/css/dialog' import { useNotifyContext } from '@inject/shared/notification/contexts/NotifyContext' import notEmpty from '@inject/shared/utils/notEmpty' import type { Dispatch, FC, SetStateAction } from 'react' import { useCallback, useState } from 'react' const TEAMS_MAX = 20 const ExerciseCreator = () => { const [open, setOpen] = useState<boolean>(true) interface ExerciseCreatorProps { open: boolean setOpen: Dispatch<SetStateAction<boolean>> } const ExerciseCreator: FC<ExerciseCreatorProps> = ({ open, setOpen }) => { const [name, setName] = useState<string>('') const { close } = useModals() const [definition, setDefinition] = useState<Definition>() const [count, setCount] = useState<undefined | number>() const reset = useCallback(() => { setName('') setDefinition(undefined) setCount(undefined) setOpen(false) }, [setOpen]) const [addExercise, { loading }] = useCreateExercises() const { data: definitionData } = useGetDefinitions() const { notify } = useNotifyContext() const [definition, setDefinition] = useState<Definition>() const [count, setCount] = useState<undefined | number>() const handleSubmit = useCallback(() => { if (!definition || !count) return Loading @@ -42,23 +53,20 @@ const ExerciseCreator = () => { name, }, refetchQueries: [GetExercisesDocument], onCompleted: () => { setOpen(false) }, onCompleted: reset, onError: err => { notify(err.message, { intent: 'danger', }) }, }) }, [definition, count, addExercise, name, notify]) }, [definition, count, addExercise, name, reset, notify]) return ( <Dialog className={dialog} isOpen={open} onClose={() => setOpen(false)} onClosed={() => close()} onClose={reset} icon='add' title='Create an exercise' > Loading