Loading frontend/src/components/MoveTimeButton/index.tsx +13 −10 Original line number Diff line number Diff line Loading @@ -8,9 +8,9 @@ import { NumericInput, } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import type { ExerciseState } from '@inject/graphql' import { MoveTime, useLoopStatus, useTypedMutation, type CustomOperationContext, } from '@inject/graphql' Loading @@ -30,7 +30,7 @@ interface MoveTimeButtonProps { hideLabel: boolean exerciseId?: string disabledTitle?: string teamId?: string status?: ExerciseState['status'] | 'onDemand' | undefined } // TODO: only allow for non-on-demand exercises Loading @@ -39,14 +39,13 @@ export const MoveTimeButton: FC<MoveTimeButtonProps> = ({ hideLabel, exerciseId, disabledTitle, teamId, status, }) => { const [open, setOpen] = useState(false) const [minutesText, setMinutesText] = useState('') const [minutes, setMinutes] = useState<number | undefined>(undefined) const [error, setError] = useState('') const [{ fetching: loading }, moveTime] = useTypedMutation(MoveTime) const { running } = useLoopStatus({ teamId }) const { beginWiggling, wiggling } = useWiggle() const handleSubmit = useCallback( Loading Loading @@ -89,11 +88,13 @@ export const MoveTimeButton: FC<MoveTimeButtonProps> = ({ active={open} style={{ whiteSpace: 'nowrap' }} title={ exerciseId && running status === 'onDemand' ? 'Cannot move time of on-demand exercise' : exerciseId && status === 'RUNNING' ? 'Move time' : disabledTitle || 'The exercise has to be running to move time' } disabled={!exerciseId || !running} disabled={!exerciseId || status !== 'RUNNING'} /> <Dialog Loading Loading @@ -158,8 +159,10 @@ export const MoveTimeButton: FC<MoveTimeButtonProps> = ({ <Button type='submit' intent='primary' disabled={!exerciseId || !running || wiggling} title={exerciseId && running ? 'Move time' : undefined} disabled={!exerciseId || status !== 'RUNNING' || wiggling} title={ exerciseId && status !== 'RUNNING' ? 'Move time' : undefined } loading={loading} className={cx({ [wiggleClass]: wiggling, Loading frontend/src/components/Status/index.tsx +68 −46 Original line number Diff line number Diff line Loading @@ -25,11 +25,7 @@ const header = css` const vertical = css` flex-direction: column; row-gap: 0.5rem; & > #second { flex-direction: column; row-gap: 0.5rem; } row-gap: 0.2rem; ` interface StatusProps { Loading @@ -39,6 +35,7 @@ interface StatusProps { hideLabel?: boolean getStatus?: () => ExerciseState['status'] onDemand?: boolean teamId?: string } export const ExerciseStatus: FC<StatusProps> = ({ Loading @@ -47,7 +44,11 @@ export const ExerciseStatus: FC<StatusProps> = ({ hideLabel, onDemand, getStatus, }) => ( teamId, }) => { const localTeamId = teamId ? teamId : team?.id return ( <div className={wrapper}> <div className={cx({ Loading @@ -74,7 +75,18 @@ export const ExerciseStatus: FC<StatusProps> = ({ </div> <div id='second' className={cx( css` display: flex; align-items: center; column-gap: 0.4rem; `, { [vertical]: !!showTime && !onDemand && !!localTeamId, } )} > <div className={css` display: flex; align-items: center; Loading @@ -85,11 +97,21 @@ export const ExerciseStatus: FC<StatusProps> = ({ {getStatus && ( <ExerciseTags getStatus={getStatus} onDemand={onDemand} /> )} {showTime && team?.id && getStatus?.() === 'RUNNING' && ( <TimeLeft teamId={team.id} /> )} <ColorModeMini fill={hideLabel} /> </div> <div className={css` display: flex; align-items: center; `} > {showTime && !onDemand && localTeamId && ( <TimeLeft teamId={localTeamId} /> )} </div> </div> </div> </div> ) } frontend/src/views/InstructorView/index.tsx +8 −1 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ export const InstructorView: FC<InstructorViewProps> = ({ ? () => synchronousExerciseState(exerciseStatus).status : undefined } teamId={exerciseStatus?.states[0].teams[0].id} onDemand={exerciseStatus?.onDemand} /> </div> Loading @@ -112,7 +113,13 @@ export const InstructorView: FC<InstructorViewProps> = ({ disabledTitle={ exerciseId ? undefined : 'A team has to be selected to move time' } teamId={teamId} status={ exerciseStatus ? exerciseStatus.onDemand ? 'onDemand' : synchronousExerciseState(exerciseStatus).status : undefined } /> {exerciseId && !teamId && ( <MilestonesButton Loading graphql/urql/worker/cacheConsumer.ts +6 −2 Original line number Diff line number Diff line Loading @@ -533,8 +533,12 @@ const cache: Exchange = offlineExchange<GraphCacheConfig>({ cache.link(...params, [...link, cache.keyOfEntity(newTag)]) } }, moveTime(_, __, cache) { cache.invalidate('Query', 'exerciseTimeLeft') moveTime(parent, __, cache) { if (parent.moveTime?.exercise) { parent.moveTime?.exercise.teams.forEach(team => cache.invalidate('Query', 'exerciseTimeLeft', { teamId: team.id }) ) } }, modifyMilestone() { // TODO: should return the updated milestone state (backend) => then, proper invalidation can be done of the state, LA, LO, and team (score, achieved) Loading Loading
frontend/src/components/MoveTimeButton/index.tsx +13 −10 Original line number Diff line number Diff line Loading @@ -8,9 +8,9 @@ import { NumericInput, } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import type { ExerciseState } from '@inject/graphql' import { MoveTime, useLoopStatus, useTypedMutation, type CustomOperationContext, } from '@inject/graphql' Loading @@ -30,7 +30,7 @@ interface MoveTimeButtonProps { hideLabel: boolean exerciseId?: string disabledTitle?: string teamId?: string status?: ExerciseState['status'] | 'onDemand' | undefined } // TODO: only allow for non-on-demand exercises Loading @@ -39,14 +39,13 @@ export const MoveTimeButton: FC<MoveTimeButtonProps> = ({ hideLabel, exerciseId, disabledTitle, teamId, status, }) => { const [open, setOpen] = useState(false) const [minutesText, setMinutesText] = useState('') const [minutes, setMinutes] = useState<number | undefined>(undefined) const [error, setError] = useState('') const [{ fetching: loading }, moveTime] = useTypedMutation(MoveTime) const { running } = useLoopStatus({ teamId }) const { beginWiggling, wiggling } = useWiggle() const handleSubmit = useCallback( Loading Loading @@ -89,11 +88,13 @@ export const MoveTimeButton: FC<MoveTimeButtonProps> = ({ active={open} style={{ whiteSpace: 'nowrap' }} title={ exerciseId && running status === 'onDemand' ? 'Cannot move time of on-demand exercise' : exerciseId && status === 'RUNNING' ? 'Move time' : disabledTitle || 'The exercise has to be running to move time' } disabled={!exerciseId || !running} disabled={!exerciseId || status !== 'RUNNING'} /> <Dialog Loading Loading @@ -158,8 +159,10 @@ export const MoveTimeButton: FC<MoveTimeButtonProps> = ({ <Button type='submit' intent='primary' disabled={!exerciseId || !running || wiggling} title={exerciseId && running ? 'Move time' : undefined} disabled={!exerciseId || status !== 'RUNNING' || wiggling} title={ exerciseId && status !== 'RUNNING' ? 'Move time' : undefined } loading={loading} className={cx({ [wiggleClass]: wiggling, Loading
frontend/src/components/Status/index.tsx +68 −46 Original line number Diff line number Diff line Loading @@ -25,11 +25,7 @@ const header = css` const vertical = css` flex-direction: column; row-gap: 0.5rem; & > #second { flex-direction: column; row-gap: 0.5rem; } row-gap: 0.2rem; ` interface StatusProps { Loading @@ -39,6 +35,7 @@ interface StatusProps { hideLabel?: boolean getStatus?: () => ExerciseState['status'] onDemand?: boolean teamId?: string } export const ExerciseStatus: FC<StatusProps> = ({ Loading @@ -47,7 +44,11 @@ export const ExerciseStatus: FC<StatusProps> = ({ hideLabel, onDemand, getStatus, }) => ( teamId, }) => { const localTeamId = teamId ? teamId : team?.id return ( <div className={wrapper}> <div className={cx({ Loading @@ -74,7 +75,18 @@ export const ExerciseStatus: FC<StatusProps> = ({ </div> <div id='second' className={cx( css` display: flex; align-items: center; column-gap: 0.4rem; `, { [vertical]: !!showTime && !onDemand && !!localTeamId, } )} > <div className={css` display: flex; align-items: center; Loading @@ -85,11 +97,21 @@ export const ExerciseStatus: FC<StatusProps> = ({ {getStatus && ( <ExerciseTags getStatus={getStatus} onDemand={onDemand} /> )} {showTime && team?.id && getStatus?.() === 'RUNNING' && ( <TimeLeft teamId={team.id} /> )} <ColorModeMini fill={hideLabel} /> </div> <div className={css` display: flex; align-items: center; `} > {showTime && !onDemand && localTeamId && ( <TimeLeft teamId={localTeamId} /> )} </div> </div> </div> </div> ) }
frontend/src/views/InstructorView/index.tsx +8 −1 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ export const InstructorView: FC<InstructorViewProps> = ({ ? () => synchronousExerciseState(exerciseStatus).status : undefined } teamId={exerciseStatus?.states[0].teams[0].id} onDemand={exerciseStatus?.onDemand} /> </div> Loading @@ -112,7 +113,13 @@ export const InstructorView: FC<InstructorViewProps> = ({ disabledTitle={ exerciseId ? undefined : 'A team has to be selected to move time' } teamId={teamId} status={ exerciseStatus ? exerciseStatus.onDemand ? 'onDemand' : synchronousExerciseState(exerciseStatus).status : undefined } /> {exerciseId && !teamId && ( <MilestonesButton Loading
graphql/urql/worker/cacheConsumer.ts +6 −2 Original line number Diff line number Diff line Loading @@ -533,8 +533,12 @@ const cache: Exchange = offlineExchange<GraphCacheConfig>({ cache.link(...params, [...link, cache.keyOfEntity(newTag)]) } }, moveTime(_, __, cache) { cache.invalidate('Query', 'exerciseTimeLeft') moveTime(parent, __, cache) { if (parent.moveTime?.exercise) { parent.moveTime?.exercise.teams.forEach(team => cache.invalidate('Query', 'exerciseTimeLeft', { teamId: team.id }) ) } }, modifyMilestone() { // TODO: should return the updated milestone state (backend) => then, proper invalidation can be done of the state, LA, LO, and team (score, achieved) Loading