Loading frontend/src/clientsettings/components/InstructorTeams.tsx +9 −8 Original line number Diff line number Diff line Loading @@ -6,17 +6,17 @@ import { Section, SectionCard, } from '@blueprintjs/core' import { toggleTeam, unsetTeams, useTeams } from '../vars/teams' import { toggleTeam, unsetTeams, useTeamStateMap } from '../vars/teams' const InstructorTeams = () => { const teamsVar = useTeams() const teamStateMap = useTeamStateMap() const validator = useTeamStateValidator() return ( <Section title={'Instructor Subscribed Teams'} subtitle={`Current amount of synchronized teams: ${ Object.keys(teamsVar).length Object.keys(teamStateMap).length }`} rightElement={ <div Loading Loading @@ -48,7 +48,7 @@ const InstructorTeams = () => { } > <SectionCard> {Object.keys(teamsVar).length === 0 && ( {Object.keys(teamStateMap).length === 0 && ( <div style={{ padding: '1rem' }}> <NonIdealState icon='low-voltage-pole' Loading @@ -56,18 +56,19 @@ const InstructorTeams = () => { /> </div> )} {Object.keys(teamsVar).length > 0 && ( {Object.keys(teamStateMap).length > 0 && ( <div style={{ padding: '0.25rem' }}> {Object.entries(teamsVar).map(([key, team]) => ( {Object.entries(teamStateMap).map(([key, value]) => ( <CheckboxCard key={key} checked={team.show} checked={value.show} onClick={e => { e.preventDefault() toggleTeam(key) }} > <span>{team.teamRole || `Team ${team.teamId}`}</span> {/* TODO: add role */} <span>{value.team.name}</span> </CheckboxCard> ))} </div> Loading frontend/src/clientsettings/vars/teams.ts +22 −21 Original line number Diff line number Diff line import { makeVar, useReactiveVar } from '@inject/graphql/client/reactive' import type { Team } from '@inject/graphql/fragments/Team.generated' export interface Team { exerciseId: string teamRole: string teamId: string export interface TeamState { team: Team show: boolean inactive: boolean } interface TeamState { [key: string]: Team export interface TeamStateMap { [teamId: string]: TeamState } const key = 'team' const initialSettings: TeamState = JSON.parse(localStorage.getItem(key) || '{}') const key = 'teamStateMap' const initialSettings: TeamStateMap = JSON.parse( localStorage.getItem(key) || '{}' ) export const teams = makeVar<TeamState>(initialSettings) export const useTeams = () => useReactiveVar(teams) as TeamState export const teamStateMap = makeVar<TeamStateMap>(initialSettings) export const useTeamStateMap = () => useReactiveVar(teamStateMap) function change(value: TeamState) { function change(value: TeamStateMap) { localStorage.setItem(key, JSON.stringify(value)) } export const toggleTeam = (team: string) => { const prev = teams() as TeamState const chosenTeam = prev[team] teams({ export const toggleTeam = (teamId: string) => { const prev = teamStateMap() as TeamStateMap const chosenTeam = prev[teamId] teamStateMap({ ...prev, [team]: { [teamId]: { ...chosenTeam, show: !chosenTeam.show, }, Loading @@ -35,8 +36,8 @@ export const toggleTeam = (team: string) => { } export const unsetTeams = () => { const prev = teams() as TeamState teams( const prev = teamStateMap() as TeamStateMap teamStateMap( Object.fromEntries( Object.entries(prev).map(([key, value]) => [ key, Loading @@ -46,7 +47,7 @@ export const unsetTeams = () => { ) } teams.onNextChange(function onNext() { change(teams()) teams.onNextChange(onNext) teamStateMap.onNextChange(function onNext() { change(teamStateMap()) teamStateMap.onNextChange(onNext) }) frontend/src/components/Status/index.tsx +15 −2 Original line number Diff line number Diff line Loading @@ -28,14 +28,27 @@ const Status: FC<StatusProps> = ({ team, hideLabel, }) => ( <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center', }} > <div className={header}> {!small && (!exerciseRunning || !showTime) && <span>Backend:</span>} <HealthCheck /> {exerciseRunning && showTime && <TimeLeft />} </div> {team && ( <TeamLabel teamId={team.id} teamName={team.name} hideLabel={hideLabel} /> <TeamLabel teamId={team.id} teamName={team.name} hideLabel={hideLabel} exerciseName={team.exercise.name} teamRole={team.role} /> )} </div> ) Loading frontend/src/components/TeamLabel/index.tsx +74 −14 Original line number Diff line number Diff line import { Classes, Tag } from '@blueprintjs/core' import ColorBox from '@inject/shared/components/ColorBox' import type { FC } from 'react' import { useMemo, type FC } from 'react' interface TeamLabelProps { hideLabel?: boolean teamId: string teamName: string teamRole?: string exerciseName?: string inactive?: boolean } const TeamLabel: FC<TeamLabelProps> = ({ hideLabel, teamId, teamName }) => ( const TeamLabel: FC<TeamLabelProps> = ({ hideLabel, teamId, teamName, teamRole, exerciseName, inactive, }) => { const label = useMemo( () => ( <div> <div>{teamName}</div> {teamRole && ( <div className={Classes.TEXT_MUTED}>{`role: ${teamRole}`}</div> )} {exerciseName && ( <div className={Classes.TEXT_MUTED} >{`exercise: ${exerciseName}`}</div> )} </div> ), [exerciseName, teamName, teamRole] ) const colorbox = useMemo( () => ( <ColorBox style={{ width: '16px', height: '16px', marginRight: hideLabel ? undefined : '7px', }} id={Number(teamId)} /> ), [hideLabel, teamId] ) const content = useMemo(() => { if (hideLabel) { return colorbox } return ( <> {colorbox} {label} {inactive && ( <Tag style={{ marginLeft: '1ch' }} minimal intent='warning'> inactive </Tag> )} </> ) }, [colorbox, hideLabel, inactive, label]) return ( <div style={{ display: 'flex', justifyContent: 'center', justifyContent: hideLabel ? 'center' : 'flex-start', alignItems: 'center', gap: '0.5rem', }} > {!hideLabel && <h3 style={{ fontSize: '1rem', margin: 0 }}>{teamName}</h3>} <ColorBox id={Number(teamId)} title={teamName} /> {content} </div> ) } export default TeamLabel frontend/src/instructor/InstructorTeamSelector/LabelElement.tsxdeleted 100644 → 0 +0 −36 Original line number Diff line number Diff line import type { Team } from '@/clientsettings/vars/teams' import { Tag } from '@blueprintjs/core' import ColorBox from '@inject/shared/components/ColorBox' import type { FC } from 'react' interface LabelElementProps { team: Team hideLabel?: boolean } const LabelElement: FC<LabelElementProps> = ({ team, hideLabel }) => ( <div style={{ display: 'flex', justifyContent: hideLabel ? 'center' : 'flex-start', alignItems: 'center', }} > <ColorBox style={{ width: '16px', height: '16px', marginRight: hideLabel ? undefined : '7px', }} id={Number(team.teamId)} /> {!hideLabel && (team.teamRole || `Team ${team.teamId}`)} {team.inactive && ( <Tag style={{ marginLeft: '1ch' }} minimal intent='warning'> inactive </Tag> )} </div> ) export default LabelElement Loading
frontend/src/clientsettings/components/InstructorTeams.tsx +9 −8 Original line number Diff line number Diff line Loading @@ -6,17 +6,17 @@ import { Section, SectionCard, } from '@blueprintjs/core' import { toggleTeam, unsetTeams, useTeams } from '../vars/teams' import { toggleTeam, unsetTeams, useTeamStateMap } from '../vars/teams' const InstructorTeams = () => { const teamsVar = useTeams() const teamStateMap = useTeamStateMap() const validator = useTeamStateValidator() return ( <Section title={'Instructor Subscribed Teams'} subtitle={`Current amount of synchronized teams: ${ Object.keys(teamsVar).length Object.keys(teamStateMap).length }`} rightElement={ <div Loading Loading @@ -48,7 +48,7 @@ const InstructorTeams = () => { } > <SectionCard> {Object.keys(teamsVar).length === 0 && ( {Object.keys(teamStateMap).length === 0 && ( <div style={{ padding: '1rem' }}> <NonIdealState icon='low-voltage-pole' Loading @@ -56,18 +56,19 @@ const InstructorTeams = () => { /> </div> )} {Object.keys(teamsVar).length > 0 && ( {Object.keys(teamStateMap).length > 0 && ( <div style={{ padding: '0.25rem' }}> {Object.entries(teamsVar).map(([key, team]) => ( {Object.entries(teamStateMap).map(([key, value]) => ( <CheckboxCard key={key} checked={team.show} checked={value.show} onClick={e => { e.preventDefault() toggleTeam(key) }} > <span>{team.teamRole || `Team ${team.teamId}`}</span> {/* TODO: add role */} <span>{value.team.name}</span> </CheckboxCard> ))} </div> Loading
frontend/src/clientsettings/vars/teams.ts +22 −21 Original line number Diff line number Diff line import { makeVar, useReactiveVar } from '@inject/graphql/client/reactive' import type { Team } from '@inject/graphql/fragments/Team.generated' export interface Team { exerciseId: string teamRole: string teamId: string export interface TeamState { team: Team show: boolean inactive: boolean } interface TeamState { [key: string]: Team export interface TeamStateMap { [teamId: string]: TeamState } const key = 'team' const initialSettings: TeamState = JSON.parse(localStorage.getItem(key) || '{}') const key = 'teamStateMap' const initialSettings: TeamStateMap = JSON.parse( localStorage.getItem(key) || '{}' ) export const teams = makeVar<TeamState>(initialSettings) export const useTeams = () => useReactiveVar(teams) as TeamState export const teamStateMap = makeVar<TeamStateMap>(initialSettings) export const useTeamStateMap = () => useReactiveVar(teamStateMap) function change(value: TeamState) { function change(value: TeamStateMap) { localStorage.setItem(key, JSON.stringify(value)) } export const toggleTeam = (team: string) => { const prev = teams() as TeamState const chosenTeam = prev[team] teams({ export const toggleTeam = (teamId: string) => { const prev = teamStateMap() as TeamStateMap const chosenTeam = prev[teamId] teamStateMap({ ...prev, [team]: { [teamId]: { ...chosenTeam, show: !chosenTeam.show, }, Loading @@ -35,8 +36,8 @@ export const toggleTeam = (team: string) => { } export const unsetTeams = () => { const prev = teams() as TeamState teams( const prev = teamStateMap() as TeamStateMap teamStateMap( Object.fromEntries( Object.entries(prev).map(([key, value]) => [ key, Loading @@ -46,7 +47,7 @@ export const unsetTeams = () => { ) } teams.onNextChange(function onNext() { change(teams()) teams.onNextChange(onNext) teamStateMap.onNextChange(function onNext() { change(teamStateMap()) teamStateMap.onNextChange(onNext) })
frontend/src/components/Status/index.tsx +15 −2 Original line number Diff line number Diff line Loading @@ -28,14 +28,27 @@ const Status: FC<StatusProps> = ({ team, hideLabel, }) => ( <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center', }} > <div className={header}> {!small && (!exerciseRunning || !showTime) && <span>Backend:</span>} <HealthCheck /> {exerciseRunning && showTime && <TimeLeft />} </div> {team && ( <TeamLabel teamId={team.id} teamName={team.name} hideLabel={hideLabel} /> <TeamLabel teamId={team.id} teamName={team.name} hideLabel={hideLabel} exerciseName={team.exercise.name} teamRole={team.role} /> )} </div> ) Loading
frontend/src/components/TeamLabel/index.tsx +74 −14 Original line number Diff line number Diff line import { Classes, Tag } from '@blueprintjs/core' import ColorBox from '@inject/shared/components/ColorBox' import type { FC } from 'react' import { useMemo, type FC } from 'react' interface TeamLabelProps { hideLabel?: boolean teamId: string teamName: string teamRole?: string exerciseName?: string inactive?: boolean } const TeamLabel: FC<TeamLabelProps> = ({ hideLabel, teamId, teamName }) => ( const TeamLabel: FC<TeamLabelProps> = ({ hideLabel, teamId, teamName, teamRole, exerciseName, inactive, }) => { const label = useMemo( () => ( <div> <div>{teamName}</div> {teamRole && ( <div className={Classes.TEXT_MUTED}>{`role: ${teamRole}`}</div> )} {exerciseName && ( <div className={Classes.TEXT_MUTED} >{`exercise: ${exerciseName}`}</div> )} </div> ), [exerciseName, teamName, teamRole] ) const colorbox = useMemo( () => ( <ColorBox style={{ width: '16px', height: '16px', marginRight: hideLabel ? undefined : '7px', }} id={Number(teamId)} /> ), [hideLabel, teamId] ) const content = useMemo(() => { if (hideLabel) { return colorbox } return ( <> {colorbox} {label} {inactive && ( <Tag style={{ marginLeft: '1ch' }} minimal intent='warning'> inactive </Tag> )} </> ) }, [colorbox, hideLabel, inactive, label]) return ( <div style={{ display: 'flex', justifyContent: 'center', justifyContent: hideLabel ? 'center' : 'flex-start', alignItems: 'center', gap: '0.5rem', }} > {!hideLabel && <h3 style={{ fontSize: '1rem', margin: 0 }}>{teamName}</h3>} <ColorBox id={Number(teamId)} title={teamName} /> {content} </div> ) } export default TeamLabel
frontend/src/instructor/InstructorTeamSelector/LabelElement.tsxdeleted 100644 → 0 +0 −36 Original line number Diff line number Diff line import type { Team } from '@/clientsettings/vars/teams' import { Tag } from '@blueprintjs/core' import ColorBox from '@inject/shared/components/ColorBox' import type { FC } from 'react' interface LabelElementProps { team: Team hideLabel?: boolean } const LabelElement: FC<LabelElementProps> = ({ team, hideLabel }) => ( <div style={{ display: 'flex', justifyContent: hideLabel ? 'center' : 'flex-start', alignItems: 'center', }} > <ColorBox style={{ width: '16px', height: '16px', marginRight: hideLabel ? undefined : '7px', }} id={Number(team.teamId)} /> {!hideLabel && (team.teamRole || `Team ${team.teamId}`)} {team.inactive && ( <Tag style={{ marginLeft: '1ch' }} minimal intent='warning'> inactive </Tag> )} </div> ) export default LabelElement