From a8eba37113c666d059ba0d64af944525428fec4c Mon Sep 17 00:00:00 2001 From: Sebastian Karlsson <sebka991@student.liu.se> Date: Mon, 24 May 2021 12:57:52 +0000 Subject: [PATCH] Resolve "Comment presentation editor" --- .../PresentationEditorPage.tsx | 27 ++++++++++++-- .../components/BackgroundImageSelect.tsx | 35 ++++++++++++------- .../components/CompetitionSettings.tsx | 15 ++++++-- .../components/ImageComponentDisplay.tsx | 6 ++++ .../components/QuestionComponentDisplay.tsx | 6 ++-- .../components/RndComponent.tsx | 33 ++++++++++++++++- .../components/SettingsPanel.tsx | 7 ++++ .../components/SlideDisplay.tsx | 15 +++++++- .../components/SlideSettings.tsx | 7 +++- .../presentationEditor/components/Teams.tsx | 13 +++++++ .../components/TextComponentDisplay.tsx | 6 ++++ .../components/TextComponentEdit.tsx | 10 ++++++ .../slideSettingsComponents/Texts.tsx | 11 ++++++ .../slideSettingsComponents/Timer.tsx | 13 ++++++- .../presentationEditor/components/styled.tsx | 3 ++ 15 files changed, 183 insertions(+), 24 deletions(-) diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx index 9b040b9d..d67fc328 100644 --- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx +++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx @@ -1,3 +1,10 @@ +/** + * This file contains the PresentationEditorPage function, which returns the presentation editor page component. + * This component is used when editing a presentation, and allows creating, modifying and deleting + * slides, questions, text components, image components, teams, and any other data relating to a competition. + * + */ + import { Button, ButtonGroup, CircularProgress, Divider, Menu, MenuItem } from '@material-ui/core' import CssBaseline from '@material-ui/core/CssBaseline' import ListItemText from '@material-ui/core/ListItemText' @@ -48,9 +55,11 @@ interface CompetitionParams { competitionId: string } +/** Creates and renders the presentation editor page */ const PresentationEditorPage: React.FC = () => { const { competitionId }: CompetitionParams = useParams() const dispatch = useAppDispatch() + //Available state variables: const [sortedSlides, setSortedSlides] = useState<RichSlide[]>([]) const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) const activeViewTypeId = useAppSelector((state) => state.editor.activeViewTypeId) @@ -66,24 +75,28 @@ const PresentationEditorPage: React.FC = () => { setSortedSlides(competition.slides.sort((a, b) => (a.order > b.order ? 1 : -1))) }, [competition]) + /** Sets active slide ID and updates the state */ const setActiveSlideId = (id: number) => { dispatch(setEditorSlideId(id)) dispatch(getEditorCompetition(competitionId)) } + /** Creates API call to create a new slide in the database, and updates the state */ const createNewSlide = async () => { await axios.post(`/api/competitions/${competitionId}/slides`, { title: 'Ny sida' }) dispatch(getEditorCompetition(competitionId)) } + /** State used by the right-click context menu */ const [contextState, setContextState] = React.useState<{ mouseX: null | number mouseY: null | number slideId: null | number }>(initialState) + /** Shows context menu when right clicking a slide in the slide list */ const handleRightClick = (event: React.MouseEvent<HTMLDivElement>, slideId: number) => { - event.preventDefault() + event.preventDefault() //Prevents the standard browser context menu from opening setContextState({ mouseX: event.clientX - 2, mouseY: event.clientY - 4, @@ -91,16 +104,19 @@ const PresentationEditorPage: React.FC = () => { }) } + /** Closes the context menu */ const handleClose = () => { setContextState(initialState) } + /** Creates API call to remove a slide from the database, updates the state, and closes the menu */ const handleRemoveSlide = async () => { await axios.delete(`/api/competitions/${competitionId}/slides/${contextState.slideId}`) dispatch(getEditorCompetition(competitionId)) setContextState(initialState) } + /** Creates API call to duplicate a slide in the database, updates the state, and closes the menu */ const handleDuplicateSlide = async () => { await axios.post(`/api/competitions/${competitionId}/slides/${contextState.slideId}/copy`) dispatch(getEditorCompetition(competitionId)) @@ -109,6 +125,8 @@ const PresentationEditorPage: React.FC = () => { const viewTypes = useAppSelector((state) => state.types.viewTypes) const [activeViewTypeName, setActiveViewTypeName] = useState('Audience') + + /** Changes active view type */ const changeView = (clickedViewTypeName: string) => { setActiveViewTypeName(clickedViewTypeName) const clickedViewTypeId = viewTypes.find((viewType) => viewType.name === clickedViewTypeName)?.id @@ -118,8 +136,9 @@ const PresentationEditorPage: React.FC = () => { dispatch(getEditorCompetition(competitionId)) } + /** Changes slide order */ const onDragEnd = async (result: DropResult) => { - // dropped outside the list or same place + // if dropped outside the list or same place, do nothing if (!result.destination || result.destination.index === result.source.index) { return } @@ -138,6 +157,7 @@ const PresentationEditorPage: React.FC = () => { return ( <PresentationEditorContainer> <CssBaseline /> + {/** Top toolbar */} <AppBarEditor $leftDrawerWidth={leftDrawerWidth} $rightDrawerWidth={rightDrawerWidth} position="fixed"> <ToolBarContainer> <Button component={Link} to="/admin/competition-manager" style={{ padding: 0 }}> @@ -165,6 +185,7 @@ const PresentationEditorPage: React.FC = () => { </ButtonGroup> </ToolBarContainer> </AppBarEditor> + {/** Left slide list */} <LeftDrawer $leftDrawerWidth={leftDrawerWidth} $rightDrawerWidth={undefined} variant="permanent" anchor="left"> <FillLeftContainer $leftDrawerWidth={leftDrawerWidth} $rightDrawerWidth={undefined}> <ToolbarMargin /> @@ -208,6 +229,7 @@ const PresentationEditorPage: React.FC = () => { </FillLeftContainer> </LeftDrawer> <ToolbarMargin /> + {/** Right settings panel */} <RightDrawer $leftDrawerWidth={undefined} $rightDrawerWidth={rightDrawerWidth} variant="permanent" anchor="right"> <FillRightContainer $leftDrawerWidth={undefined} $rightDrawerWidth={rightDrawerWidth}> <RightPanelScroll> @@ -227,6 +249,7 @@ const PresentationEditorPage: React.FC = () => { <SlideDisplay variant="editor" activeViewTypeId={activeViewTypeId} /> </InnerContent> </Content> + {/** Context menu which opens when right clicking a slide in the slide list */} <Menu keepMounted open={contextState.mouseY !== null} diff --git a/client/src/pages/presentationEditor/components/BackgroundImageSelect.tsx b/client/src/pages/presentationEditor/components/BackgroundImageSelect.tsx index 14dbaa63..eed166ac 100644 --- a/client/src/pages/presentationEditor/components/BackgroundImageSelect.tsx +++ b/client/src/pages/presentationEditor/components/BackgroundImageSelect.tsx @@ -1,28 +1,34 @@ +/** + * This file contains the BackgroundImageSelect function, which returns a component used to select a background image. + * This component is used to set a background for either the entire competition, or for a specific slide. + * It is used in CompetitionSettings and in SlideSettings. + */ import { ListItem, ListItemText, Typography } from '@material-ui/core' -import React, { useState } from 'react' +import CloseIcon from '@material-ui/icons/Close' +import axios from 'axios' +import React from 'react' +import { getEditorCompetition } from '../../../actions/editor' import { useAppDispatch, useAppSelector } from '../../../hooks' +import { uploadFile } from '../../../utils/uploadImage' import { - AddButton, AddBackgroundButton, + AddButton, Center, HiddenInput, - ImportedImage, - SettingsList, ImageNameText, ImageTextContainer, + ImportedImage, + SettingsList, } from './styled' -import CloseIcon from '@material-ui/icons/Close' -import axios from 'axios' -import { Media } from '../../../interfaces/ApiModels' -import { getEditorCompetition } from '../../../actions/editor' -import { uploadFile } from '../../../utils/uploadImage' type BackgroundImageSelectProps = { variant: 'competition' | 'slide' } +/** Creates and renders a background image selection component */ const BackgroundImageSelect = ({ variant }: BackgroundImageSelectProps) => { const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) + /** Gets a background image from either the competition state or the slide state, depending on variant */ const backgroundImage = useAppSelector((state) => { if (variant === 'competition') return state.editor.competition.background_image else return state.editor.competition.slides.find((slide) => slide.id === activeSlideId)?.background_image @@ -30,8 +36,8 @@ const BackgroundImageSelect = ({ variant }: BackgroundImageSelectProps) => { const competitionId = useAppSelector((state) => state.editor.competition.id) const dispatch = useAppDispatch() + /** Creates a new background image component for the competition or slide on the database using API call. */ const updateBackgroundImage = async (mediaId: number) => { - // Creates a new image component on the database using API call. if (variant === 'competition') { await axios .put(`/api/competitions/${competitionId}`, { background_image_id: mediaId }) @@ -49,8 +55,8 @@ const BackgroundImageSelect = ({ variant }: BackgroundImageSelectProps) => { } } + /** Removes background image media and from competition using API calls. */ const removeBackgroundImage = async () => { - // Removes background image media and from competition using API calls. await axios.delete(`/api/media/images/${backgroundImage?.id}`).catch(console.log) if (variant === 'competition') { await axios @@ -69,9 +75,10 @@ const BackgroundImageSelect = ({ variant }: BackgroundImageSelectProps) => { } } + /** Reads the selected image file and uploads it to the server. + * Creates a new background image component containing the file. + */ const handleFileSelected = async (e: React.ChangeEvent<HTMLInputElement>) => { - // Reads the selected image file and uploads it to the server. - // Creates a new image component containing the file. if (e.target.files !== null && e.target.files[0]) { const files = Array.from(e.target.files) const file = files[0] @@ -86,6 +93,7 @@ const BackgroundImageSelect = ({ variant }: BackgroundImageSelectProps) => { return ( <SettingsList> + {/** Choose an image */} {!backgroundImage && ( <ListItem button style={{ padding: 0 }}> <HiddenInput @@ -102,6 +110,7 @@ const BackgroundImageSelect = ({ variant }: BackgroundImageSelectProps) => { </AddBackgroundButton> </ListItem> )} + {/** Display thumbnail for chosen image, and remove image */} {backgroundImage && ( <> <ListItem divider> diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx index 64b4a820..bcd77da5 100644 --- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx +++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx @@ -1,3 +1,8 @@ +/** + * This file contains the CompetitionSettings function, which returns the right hand side competition settings panel. + * This component is used to change settings which apply to the entire competition. + * It is contained in the SettingsPanel, alongside the SlideSettings. + */ import { Divider, FormControl, InputLabel, ListItem, MenuItem, Select, TextField, Typography } from '@material-ui/core' import axios from 'axios' import React, { useState } from 'react' @@ -13,6 +18,7 @@ interface CompetitionParams { competitionId: string } +/** Creates and renders a competition settings component */ const CompetitionSettings: React.FC = () => { const { competitionId }: CompetitionParams = useParams() const [nameErrorText, setNameErrorText] = useState<string | undefined>(undefined) @@ -20,6 +26,7 @@ const CompetitionSettings: React.FC = () => { const competition = useAppSelector((state) => state.editor.competition) const cities = useAppSelector((state) => state.cities.cities) + /** Sets the name of the competition in the database */ const updateCompetitionName = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { await axios .put(`/api/competitions/${competitionId}`, { name: event.target.value }) @@ -32,6 +39,7 @@ const CompetitionSettings: React.FC = () => { }) } + /** Sets the city of the competition in the database */ const updateCompetitionCity = async (city: City) => { await axios .put(`/api/competitions/${competitionId}`, { city_id: city.id }) @@ -53,6 +61,7 @@ const CompetitionSettings: React.FC = () => { return ( <PanelContainer> <SettingsList> + {/** Text field for setting the competition name */} <FirstItem> <ListItem> <TextField @@ -68,7 +77,7 @@ const CompetitionSettings: React.FC = () => { </ListItem> </FirstItem> <Divider /> - + {/** Set region */} <ListItem> <FormControl fullWidth variant="outlined"> <InputLabel>Region</InputLabel> @@ -86,9 +95,9 @@ const CompetitionSettings: React.FC = () => { </FormControl> </ListItem> </SettingsList> - + {/** Set teams */} <Teams competitionId={competitionId} /> - + {/** Set background image */} <BackgroundImageSelect variant="competition" /> </PanelContainer> ) diff --git a/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx b/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx index 273748f3..6afe7121 100644 --- a/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx +++ b/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx @@ -1,3 +1,8 @@ +/** + * This file contains the ImageComponentDisplay function, which returns an image component. + * This component is used to display images in an active competition presentation or in the editor. + * When used in the editor, the image component is wrapped in an Rnd component, to make it editable. + */ import React from 'react' import { ImageComponent } from '../../../interfaces/ApiModels' @@ -7,6 +12,7 @@ type ImageComponentProps = { height: number } +/** Creates and renders and image component */ const ImageComponentDisplay = ({ component, width, height }: ImageComponentProps) => { return ( <img diff --git a/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx b/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx index 75690ea6..e8b220f3 100644 --- a/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx +++ b/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx @@ -25,7 +25,9 @@ type QuestionComponentProps = { currentSlideId?: number } +/** Creates and renders a question component */ const QuestionComponentDisplay = ({ variant, currentSlideId }: QuestionComponentProps) => { + /** Gets the slide from the relevant state (presentation or editor) */ const activeSlide = useAppSelector((state) => { if (variant === 'presentation' && currentSlideId) return state.presentation.competition.slides.find((slide) => slide.id === currentSlideId) @@ -35,7 +37,7 @@ const QuestionComponentDisplay = ({ variant, currentSlideId }: QuestionComponent }) const timer = activeSlide?.timer - const total_score = activeSlide?.questions[0].total_score + const total_score = activeSlide?.questions[0].total_score //[0] is because each slide can only have one question currently. const questionName = activeSlide?.questions[0].name const questionTypeId = activeSlide?.questions[0].type_id @@ -110,7 +112,7 @@ const QuestionComponentDisplay = ({ variant, currentSlideId }: QuestionComponent </div> </AppBar> <Divider /> - {getAlternatives()} + {getAlternatives() /*This displays the actual alternatives for the question*/} </Card> ) } diff --git a/client/src/pages/presentationEditor/components/RndComponent.tsx b/client/src/pages/presentationEditor/components/RndComponent.tsx index 3b40ad74..740f982a 100644 --- a/client/src/pages/presentationEditor/components/RndComponent.tsx +++ b/client/src/pages/presentationEditor/components/RndComponent.tsx @@ -1,3 +1,8 @@ +/** + * This file contains the RndComponent function, which returns a resizable and draggable component. + * This component is used by text, image and question components in the presentation editor. + * It uses the React-Rnd library. + */ import { Card, IconButton, Menu, MenuItem, Tooltip } from '@material-ui/core' import axios from 'axios' import React, { useEffect, useState } from 'react' @@ -21,9 +26,12 @@ type RndComponentProps = { scale: number } +/** State for right click menu; closed initially */ const initialMenuState = { menuIsOpen: false, mouseX: null, mouseY: null, componentId: null } +/** Creates and renders a resizable and draggable component */ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => { + //States const [hover, setHover] = useState(false) const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y }) const [currentSize, setCurrentSize] = useState<Size>({ w: component.w, h: component.h }) @@ -33,6 +41,7 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => const typeName = useAppSelector( (state) => state.types.componentTypes.find((componentType) => componentType.id === component.type_id)?.name ) + /** State for right click menu */ const [menuState, setMenuState] = useState<{ menuIsOpen: boolean mouseX: null | number @@ -41,30 +50,39 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => }>(initialMenuState) const dispatch = useAppDispatch() + /** Sets position of the component in the database */ const handleUpdatePos = (pos: Position) => { axios.put(`/api/competitions/${competitionId}/slides/${slideId}/components/${component.id}`, { x: pos.x, y: pos.y, }) } + + /** Sets size of the component in the database */ const handleUpdateSize = (size: Size) => { axios.put(`/api/competitions/${competitionId}/slides/${slideId}/components/${component.id}`, { w: size.w, h: size.h, }) } + + /** Positions the component centered horizontally */ const handleCenterHorizontal = () => { const centerX = width / (2 * scale) - currentSize.w / 2 setCurrentPos({ x: centerX, y: currentPos.y }) handleUpdatePos({ x: centerX, y: currentPos.y }) } + + /** Positions the component centered vertically */ const handleCenterVertical = () => { const centerY = height / (2 * scale) - currentSize.h / 2 setCurrentPos({ x: currentPos.x, y: centerY }) handleUpdatePos({ x: currentPos.x, y: centerY }) } + + /** Opens right click context menu */ const handleRightClick = (event: React.MouseEvent<HTMLDivElement>, componentId: number) => { - event.preventDefault() + event.preventDefault() //Prevents browser-native context menu from being opened setMenuState({ menuIsOpen: true, mouseX: event.clientX - 2, @@ -72,9 +90,13 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => componentId: componentId, }) } + + /** Closes right click context menu */ const handleCloseMenu = () => { setMenuState(initialMenuState) } + + /** Creates a copy of the component in the database. The copy will be placed in the supplied slide type (e.g. viewer, participant). */ const handleDuplicateComponent = async (viewTypeId: number) => { console.log('Duplicate') await axios @@ -85,6 +107,8 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => .catch(console.log) setMenuState(initialMenuState) } + + /** Deletes the component from the database */ const handleRemoveComponent = async () => { console.log('Remove') await axios @@ -94,6 +118,9 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => setMenuState(initialMenuState) } + /** Handles key presses: + * -Holding down shift retains the aspect ratio of the component when resizing + */ useEffect(() => { const downHandler = (ev: KeyboardEvent) => { if (ev.key === 'Shift') setShiftPressed(true) @@ -109,6 +136,7 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => } }, []) + /** Renders the contained text, image och question component */ const renderInnerComponent = () => { switch (component.type_id) { case ComponentTypes.Text: @@ -139,6 +167,7 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => } return ( + /** Renders an Rnd component from the react-rnd library */ <Rnd minWidth={75 * scale} minHeight={75 * scale} @@ -171,6 +200,7 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => setCurrentPos({ x: position.x / scale, y: position.y / scale }) }} > + {/** Buttons for centering the component */} {hover && ( <Card elevation={6} style={{ position: 'absolute', zIndex: 10 }}> <Tooltip title="Centrera horisontellt"> @@ -182,6 +212,7 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => </Card> )} {renderInnerComponent()} + {/** Context menu */} <Menu keepMounted open={menuState.menuIsOpen} diff --git a/client/src/pages/presentationEditor/components/SettingsPanel.tsx b/client/src/pages/presentationEditor/components/SettingsPanel.tsx index 2b214728..fac5eb1e 100644 --- a/client/src/pages/presentationEditor/components/SettingsPanel.tsx +++ b/client/src/pages/presentationEditor/components/SettingsPanel.tsx @@ -1,3 +1,8 @@ +/** + * This file contains the SettingsPanel function, which returns the right hand side settings panel. + * This component contains both the SlideSettings and the CompetitionSettings components. + */ + import { Tabs } from '@material-ui/core' import AppBar from '@material-ui/core/AppBar' import React from 'react' @@ -9,6 +14,7 @@ interface TabPanelProps { activeTab: number } +/** Returns either the competition settings or the slide settings panel, depending on the selected tab */ function TabContent(props: TabPanelProps) { const { activeTab } = props if (activeTab === 0) { @@ -17,6 +23,7 @@ function TabContent(props: TabPanelProps) { return <SlideSettings /> } +/** Creates and renders the settings panel component */ const SettingsPanel: React.FC = () => { const [activeTab, setActiveTab] = React.useState(0) return ( diff --git a/client/src/pages/presentationEditor/components/SlideDisplay.tsx b/client/src/pages/presentationEditor/components/SlideDisplay.tsx index 05c87d30..26a0b19f 100644 --- a/client/src/pages/presentationEditor/components/SlideDisplay.tsx +++ b/client/src/pages/presentationEditor/components/SlideDisplay.tsx @@ -1,3 +1,7 @@ +/** + * This file contains the SlideDisplay function, which returns the slide component. + * It is used both in the presentation editor and in active competitions. + */ import { Card, Typography } from '@material-ui/core' import TimerIcon from '@material-ui/icons/Timer' import React, { useEffect, useLayoutEffect, useRef, useState } from 'react' @@ -16,7 +20,9 @@ type SlideDisplayProps = { currentSlideId?: number } +/** Creates and renders a slide component */ const SlideDisplay = ({ variant, activeViewTypeId, currentSlideId }: SlideDisplayProps) => { + /** Returns a slide from either the editor or presentation (competition) state */ const slide = useAppSelector((state) => { if (currentSlideId && variant === 'presentation') return state.presentation.competition.slides.find((slide) => slide.id === currentSlideId) @@ -24,11 +30,15 @@ const SlideDisplay = ({ variant, activeViewTypeId, currentSlideId }: SlideDispla return state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId) return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.activeSlideId) }) + + /** Returns the number of slides */ const totalSlides = useAppSelector((state) => { if (variant === 'presentation') return state.presentation.competition.slides.length return state.editor.competition.slides.length }) const components = slide?.components + + /** Returns the background image */ const competitionBackgroundImage = useAppSelector((state) => { if (variant === 'editor') return state.editor.competition.background_image return state.presentation.competition.background_image @@ -39,12 +49,13 @@ const SlideDisplay = ({ variant, activeViewTypeId, currentSlideId }: SlideDispla const editorPaperRef = useRef<HTMLDivElement>(null) const [width, setWidth] = useState(0) const [height, setHeight] = useState(0) - //Makes scale close to 1, 800 height is approxemately for a 1920 by 1080 monitor + //Makes scale close to 1, 800 height is approximately for a 1920 by 1080 monitor const scale = height / 800 useEffect(() => { dispatch(getTypes()) }, []) + /** Resizes the slide if the browser window is resized */ useLayoutEffect(() => { const updateScale = () => { if (editorPaperRef.current) { @@ -56,6 +67,7 @@ const SlideDisplay = ({ variant, activeViewTypeId, currentSlideId }: SlideDispla updateScale() return () => window.removeEventListener('resize', updateScale) }, []) + return ( <SlideEditorContainer> <SlideEditorContainerRatio> @@ -81,6 +93,7 @@ const SlideDisplay = ({ variant, activeViewTypeId, currentSlideId }: SlideDispla draggable={false} /> )} + {/** Renders editable components if in the editor, and non-editable if in a competition */} {components && components .filter((component) => component.view_type_id === activeViewTypeId) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index 68abd240..8d341d3c 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -1,4 +1,6 @@ -/* This file compiles and renders the right hand slide settings bar, under the tab "SIDA". +/** + * This file contains the SlideSettings function, which returns the right hand slide settings bar. + * This component is used to edit settings associated to a slide, such as question, image and text components. */ import { Divider } from '@material-ui/core' import React from 'react' @@ -20,6 +22,7 @@ interface CompetitionParams { competitionId: string } +/** Creates and renders the slide settings component */ const SlideSettings: React.FC = () => { const { competitionId }: CompetitionParams = useParams() @@ -59,10 +62,12 @@ const SlideSettings: React.FC = () => { <MatchAlternatives activeSlide={activeSlide} competitionId={competitionId} /> )} + {/** Text components */} {activeSlide && ( <Texts activeViewTypeId={activeViewTypeId} activeSlide={activeSlide} competitionId={competitionId} /> )} + {/** Image components */} {activeSlide && ( <Images activeViewTypeId={activeViewTypeId} activeSlide={activeSlide} competitionId={competitionId} /> )} diff --git a/client/src/pages/presentationEditor/components/Teams.tsx b/client/src/pages/presentationEditor/components/Teams.tsx index 8e4a47b4..1666fcb8 100644 --- a/client/src/pages/presentationEditor/components/Teams.tsx +++ b/client/src/pages/presentationEditor/components/Teams.tsx @@ -1,3 +1,8 @@ +/** + * This file contains the Teams function, which returns the part of the editor used to edit teams. + * This component is shown in the PresentationEditorPage as a part of CompetitionSettings. + */ + import { Button, Dialog, @@ -31,10 +36,13 @@ type TeamsProps = { competitionId: string } +/** Creates and renders a teams component */ const Teams = ({ competitionId }: TeamsProps) => { const dispatch = useAppDispatch() const competition = useAppSelector((state) => state.editor.competition) const [errorActive, setErrorActive] = React.useState(false) + + /** Adds or edits a team connected to the competition in the database, depending on the state */ const editTeam = async () => { if (editTeamState.variant === 'Add') { await axios @@ -44,6 +52,7 @@ const Teams = ({ competitionId }: TeamsProps) => { }) .catch(console.log) } else if (editTeamState.team) { + // Edit existing team await axios .put(`/api/competitions/${competitionId}/teams/${editTeamState.team.id}`, { name: selectedTeamName }) .then(() => { @@ -62,6 +71,7 @@ const Teams = ({ competitionId }: TeamsProps) => { selectedTeamName = event.target.value } + /** Removes the team with teamId=tid */ const removeTeam = async (tid: number) => { await axios .delete(`/api/competitions/${competitionId}/teams/${tid}`) @@ -78,6 +88,7 @@ const Teams = ({ competitionId }: TeamsProps) => { <ListItemText primary="Lag" /> </Center> </ListItem> + {/** One list item for each team in the competition */} {competition.teams && competition.teams.map((team) => ( <div key={team.id}> @@ -92,11 +103,13 @@ const Teams = ({ competitionId }: TeamsProps) => { </ListItem> </div> ))} + {/** Button to add team */} <ListItem button onClick={() => setEditTeamState({ variant: 'Add', open: true })}> <Center> <AddButton variant="button">Lägg till lag</AddButton> </Center> </ListItem> + {/** Dialog box which opens when adding or editing a team */} <Dialog open={editTeamState.open} onClose={() => setEditTeamState({ open: false })}> <DialogTitle> {editTeamState.variant === 'Edit' && editTeamState.team diff --git a/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx index 5cc87ec9..861e044e 100644 --- a/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx +++ b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx @@ -1,6 +1,12 @@ +/** + * This file contains the TextComponentDisplay function, which returns the text component used in competitions. + * This component only displays the text, it cannot be edited. + * When a text component is displayed in the editor, the file TextComponentEdit is used instead. + */ import React from 'react' import { TextComponent } from '../../../interfaces/ApiModels' +/** Creates and renders an image component displaying a text */ type TextComponentDisplayProps = { component: TextComponent scale: number diff --git a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx index 61dc1284..51ccf87d 100644 --- a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx +++ b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx @@ -1,3 +1,9 @@ +/** + * This file contains the TextComponentEdit function, which returns the text component used in the editor. + * This component is used when editing a text component. It uses TinyMCE. + * When a text component is displayed in a competition, the file TextComponentDisplay is used instead. + */ + import { Editor } from '@tinymce/tinymce-react' import axios from 'axios' import React, { useEffect, useState } from 'react' @@ -15,6 +21,7 @@ interface CompetitionParams { competitionId: string } +/** Creates and renders an editable text component */ const TextComponentEdit = ({ component }: ImageComponentProps) => { const { competitionId }: CompetitionParams = useParams() const [content, setContent] = useState('') @@ -27,6 +34,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { setContent(component.text) }, []) + /** Saves the supplied text to the database */ const handleSaveText = async (newText: string) => { setContent(newText) if (timerHandle) { @@ -44,6 +52,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { ) } + /** Deletes the text component */ const handleDeleteText = async (componentId: number) => { await axios.delete(`/api/competitions/${competitionId}/slides/${activeSlideId}/components/${componentId}`) dispatch(getEditorCompetition(competitionId)) @@ -51,6 +60,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { return ( <> + {/** TinyMCE component */} <Editor value={content || ''} init={{ diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx index dd934e60..4fd7c2d3 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx @@ -1,3 +1,8 @@ +/** + * This file contains the Texts function, which returns a component containing text components. + * This component is used to edit and add text components in a slide. + * It is used in SlideSettings. + */ import { Divider, ListItem, ListItemText } from '@material-ui/core' import axios from 'axios' import React from 'react' @@ -14,7 +19,9 @@ type TextsProps = { competitionId: string } +/** Creates and renders a texts component */ const Texts = ({ activeViewTypeId, activeSlide, competitionId }: TextsProps) => { + /** Gets all text components from the slide state */ const texts = useAppSelector( (state) => state.editor.competition.slides @@ -23,6 +30,8 @@ const Texts = ({ activeViewTypeId, activeSlide, competitionId }: TextsProps) => ) const dispatch = useAppDispatch() + + /** Adds a new text component to the slide using API call */ const handleAddText = async () => { if (activeSlide) { await axios.post(`/api/competitions/${competitionId}/slides/${activeSlide?.id}/components`, { @@ -43,6 +52,7 @@ const Texts = ({ activeViewTypeId, activeSlide, competitionId }: TextsProps) => <ListItemText primary="Text" /> </Center> </ListItem> + {/** Shows list of all text components in the slide */} {texts && texts .filter((text) => text.view_type_id === activeViewTypeId) @@ -52,6 +62,7 @@ const Texts = ({ activeViewTypeId, activeSlide, competitionId }: TextsProps) => <Divider /> </TextCard> ))} + {/** Button to create new text component */} <ListItem button onClick={handleAddText}> <Center> <AddButton variant="button">Lägg till text</AddButton> diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx index 1cc012b8..ca5c8c7e 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx @@ -1,3 +1,8 @@ +/** + * This file contains the Timer function, which returns the timer component. + * This component is used to set the allowed time for a slide. + * It is used in SlideSettings. + */ import { ListItem, TextField } from '@material-ui/core' import axios from 'axios' import React, { useEffect, useState } from 'react' @@ -11,20 +16,24 @@ type TimerProps = { competitionId: string } +/** Creates and renders a timer component */ const Timer = ({ activeSlide, competitionId }: TimerProps) => { const maxTime = 1000000 // ms const dispatch = useAppDispatch() const [timerHandle, setTimerHandle] = useState<number | undefined>(undefined) + + /** Handles changes in the timer text field. Updates the timer every 300 ms */ const handleChangeTimer = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { if (timerHandle) { clearTimeout(timerHandle) setTimerHandle(undefined) } - //Only updates slide and api 300s after last input was made + //Only updates slide and api 300 ms after last input was made setTimerHandle(window.setTimeout(() => updateTimer(event), 300)) setTimer(+event.target.value) } + /** Updates the database with the new timer value. */ const updateTimer = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { /** If timer value is above the max value, set the timer value to max value to not overflow the server */ // Sets score to event.target.value if it's between 0 and max @@ -44,10 +53,12 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { useEffect(() => { setTimer(activeSlide?.timer) }, [activeSlide]) + return ( <ListItem> <Center> <SettingsItemContainer> + {/** Text files to change timer value */} <TextField id="standard-number" fullWidth={true} diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index 106e360e..6f6a868f 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -1,3 +1,6 @@ +/** + * This file supplies CSS styles to the presentation editor components. + */ import { Button, Card, List, ListItemText, Tab, TextField, Typography } from '@material-ui/core' import styled from 'styled-components' -- GitLab