Skip to content
Snippets Groups Projects
Commit 1cbbb2a1 authored by Max Rüdiger's avatar Max Rüdiger
Browse files

resolve conflics

parents af496bfa 0b78e58d
No related branches found
No related tags found
1 merge request!104139 operator view
Pipeline #42835 passed with warnings
This commit is part of merge request !104. Comments created here will be created in the context of that merge request.
Showing
with 180 additions and 72 deletions
......@@ -18,16 +18,28 @@ export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch
if (getState().editor.activeSlideId === -1 && res.data.slides[0]) {
setEditorSlideId(res.data.slides[0].id)(dispatch)
}
const defaultViewType = getState().types.viewTypes.find((viewType) => viewType.name === 'Audience')
if (getState().editor.activeViewTypeId === -1 && defaultViewType) {
setEditorViewId(defaultViewType.id)(dispatch)
}
})
.catch((err) => {
console.log(err)
})
}
// Set currentSlideId in editor state
// Set activeSlideId in editor state
export const setEditorSlideId = (id: number) => (dispatch: AppDispatch) => {
dispatch({
type: Types.SET_EDITOR_SLIDE_ID,
payload: id,
})
}
// Set activeViewTypeId in editor state
export const setEditorViewId = (id: number) => (dispatch: AppDispatch) => {
dispatch({
type: Types.SET_EDITOR_VIEW_ID,
payload: id,
})
}
......@@ -24,6 +24,7 @@ export default {
SET_COMPETITIONS_COUNT: 'SET_COMPETITIONS_COUNT',
SET_EDITOR_COMPETITION: 'SET_EDITOR_COMPETITION',
SET_EDITOR_SLIDE_ID: 'SET_EDITOR_SLIDE_ID',
SET_EDITOR_VIEW_ID: 'SET_EDITOR_VIEW_ID',
SET_PRESENTATION_COMPETITION: 'SET_PRESENTATION_COMPETITION',
SET_PRESENTATION_SLIDE: 'SET_PRESENTATION_SLIDE',
SET_PRESENTATION_SLIDE_PREVIOUS: 'SET_PRESENTATION_SLIDE_PREVIOUS',
......
......@@ -80,6 +80,8 @@ export interface Component {
w: number
h: number
type_id: number
view_type_id: number
slide_id: number
}
export interface ImageComponent extends Component {
......
......@@ -10,7 +10,7 @@ import axios from 'axios'
import React, { useEffect, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { getCities } from '../../actions/cities'
import { getEditorCompetition, setEditorSlideId } from '../../actions/editor'
import { getEditorCompetition, setEditorSlideId, setEditorViewId } from '../../actions/editor'
import { getTypes } from '../../actions/typesAction'
import { useAppDispatch, useAppSelector } from '../../hooks'
import { RichSlide } from '../../interfaces/ApiRichModels'
......@@ -27,6 +27,7 @@ import {
SlideListItem,
ToolBarContainer,
ViewButton,
ViewButtonClicked,
ViewButtonGroup,
} from './styled'
......@@ -88,6 +89,7 @@ const PresentationEditorPage: React.FC = () => {
const { competitionId }: CompetitionParams = useParams()
const dispatch = useAppDispatch()
const activeSlideId = useAppSelector((state) => state.editor.activeSlideId)
const activeViewTypeId = useAppSelector((state) => state.editor.activeViewTypeId)
const competition = useAppSelector((state) => state.editor.competition)
const competitionLoading = useAppSelector((state) => state.editor.loading)
useEffect(() => {
......@@ -147,6 +149,16 @@ const PresentationEditorPage: React.FC = () => {
})((props: CheckboxProps) => <Checkbox color="default" {...props} />)
const [checkbox, setCheckbox] = useState(false)
const viewTypes = useAppSelector((state) => state.types.viewTypes)
const [activeViewTypeName, setActiveViewTypeName] = useState('')
const changeView = (clickedViewTypeName: string) => {
setActiveViewTypeName(clickedViewTypeName)
const clickedViewTypeId = viewTypes.find((viewType) => viewType.name === clickedViewTypeName)?.id
if (clickedViewTypeId) {
dispatch(setEditorViewId(clickedViewTypeId))
}
}
return (
<PresentationEditorContainer>
<CssBaseline />
......@@ -164,10 +176,20 @@ const PresentationEditorPage: React.FC = () => {
<Typography className={classes.alignCheckboxText} variant="button">
Applicera ändringar på samtliga vyer
</Typography>
<ViewButton variant="contained" color="secondary">
<ViewButton
activeView={activeViewTypeName === 'Audience'}
variant="contained"
color="secondary"
onClick={() => changeView('Audience')}
>
Åskådarvy
</ViewButton>
<ViewButton variant="contained" color="secondary">
<ViewButton
activeView={activeViewTypeName === 'Team'}
variant="contained"
color="secondary"
onClick={() => changeView('Team')}
>
Deltagarvy
</ViewButton>
</ViewButtonGroup>
......@@ -229,7 +251,7 @@ const PresentationEditorPage: React.FC = () => {
<Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}>
<InnerContent>
<SlideDisplay editor />
<SlideDisplay variant="editor" activeViewTypeId={activeViewTypeId} />
</InnerContent>
</Content>
<Menu
......
import { Button, Typography } from '@material-ui/core'
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { getTypes } from '../../../actions/typesAction'
import { useAppDispatch, useAppSelector } from '../../../hooks'
......@@ -8,12 +7,13 @@ import { SlideEditorContainer, SlideEditorContainerRatio, SlideEditorPaper } fro
type SlideDisplayProps = {
//Prop to distinguish between editor and active competition
editor?: boolean | undefined
variant: 'editor' | 'presentation'
activeViewTypeId: number
}
const SlideDisplay = ({ editor }: SlideDisplayProps) => {
const SlideDisplay = ({ variant, activeViewTypeId }: SlideDisplayProps) => {
const components = useAppSelector((state) => {
if (editor)
if (variant === 'editor')
return state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId)?.components
return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide?.id)?.components
})
......@@ -43,21 +43,29 @@ const SlideDisplay = ({ editor }: SlideDisplayProps) => {
<SlideEditorContainerRatio>
<SlideEditorPaper ref={editorPaperRef}>
{components &&
components.map((component) => {
if (editor)
components
.filter((component) => component.view_type_id === activeViewTypeId)
.map((component) => {
if (variant === 'editor')
return (
<RndComponent
height={height}
width={width}
key={component.id}
component={component}
scale={scale}
/>
)
return (
<RndComponent height={height} width={width} key={component.id} component={component} scale={scale} />
<PresentationComponent
height={height}
width={width}
key={component.id}
component={component}
scale={scale}
/>
)
return (
<PresentationComponent
height={height}
width={width}
key={component.id}
component={component}
scale={scale}
/>
)
})}
})}
</SlideEditorPaper>
</SlideEditorContainerRatio>
</SlideEditorContainer>
......
......@@ -24,6 +24,7 @@ const SlideSettings: React.FC = () => {
// Gets the slide with id=activeSlideId from the database.
state.editor.competition.slides.find((slide) => slide && slide.id === state.editor.activeSlideId)
)
const activeViewTypeId = useAppSelector((state) => state.editor.activeViewTypeId)
return (
<PanelContainer>
......@@ -47,9 +48,13 @@ const SlideSettings: React.FC = () => {
<MultipleChoiceAlternatives activeSlide={activeSlide} competitionId={competitionId} />
)}
{activeSlide && <Texts activeSlide={activeSlide} competitionId={competitionId} />}
{activeSlide && (
<Texts activeViewTypeId={activeViewTypeId} activeSlide={activeSlide} competitionId={competitionId} />
)}
{activeSlide && <Images activeSlide={activeSlide} competitionId={competitionId} />}
{activeSlide && (
<Images activeViewTypeId={activeViewTypeId} activeSlide={activeSlide} competitionId={competitionId} />
)}
<SettingsList>
<ListItem button>
......
......@@ -20,6 +20,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => {
const [content, setContent] = useState('')
const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined)
const activeSlideId = useAppSelector((state) => state.editor.activeSlideId)
const activeViewTypeId = useAppSelector((state) => state.editor.activeViewTypeId)
const dispatch = useAppDispatch()
useEffect(() => {
......
......@@ -11,11 +11,12 @@ import { ImageComponent, Media } from '../../../../interfaces/ApiModels'
import { useAppDispatch, useAppSelector } from '../../../../hooks'
type ImagesProps = {
activeViewTypeId: number
activeSlide: RichSlide
competitionId: string
}
const Images = ({ activeSlide, competitionId }: ImagesProps) => {
const Images = ({ activeViewTypeId, activeSlide, competitionId }: ImagesProps) => {
const dispatch = useAppDispatch()
const uploadFile = async (formData: FormData) => {
......@@ -37,6 +38,7 @@ const Images = ({ activeSlide, competitionId }: ImagesProps) => {
y: 0,
media_id: media.id,
type_id: 2,
view_type_id: activeViewTypeId,
}
await axios
.post(`/api/competitions/${competitionId}/slides/${activeSlide?.id}/components`, imageData)
......@@ -94,17 +96,19 @@ const Images = ({ activeSlide, competitionId }: ImagesProps) => {
</Center>
</ListItem>
{images &&
images.map((image) => (
<div key={image.id}>
<ListItem divider button>
<ImportedImage src={`http://localhost:5000/static/images/thumbnail_${image.media?.filename}`} />
<Center>
<ListItemText primary={image.media?.filename} />
</Center>
<CloseIcon onClick={() => handleCloseimageClick(image)} />
</ListItem>
</div>
))}
images
.filter((image) => image.view_type_id === activeViewTypeId)
.map((image) => (
<div key={image.id}>
<ListItem divider button>
<ImportedImage src={`http://localhost:5000/static/images/thumbnail_${image.media?.filename}`} />
<Center>
<ListItemText primary={image.media?.filename} />
</Center>
<CloseIcon onClick={() => handleCloseimageClick(image)} />
</ListItem>
</div>
))}
<ListItem button style={{ padding: 0 }}>
<HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} />
......
......@@ -9,11 +9,12 @@ import axios from 'axios'
import { getEditorCompetition } from '../../../../actions/editor'
type TextsProps = {
activeViewTypeId: number
activeSlide: RichSlide
competitionId: string
}
const Texts = ({ activeSlide, competitionId }: TextsProps) => {
const Texts = ({ activeViewTypeId, activeSlide, competitionId }: TextsProps) => {
const texts = useAppSelector(
(state) =>
state.editor.competition.slides
......@@ -29,6 +30,7 @@ const Texts = ({ activeSlide, competitionId }: TextsProps) => {
text: 'Ny text',
w: 315,
h: 50,
view_type_id: activeViewTypeId,
})
dispatch(getEditorCompetition(competitionId))
}
......@@ -42,12 +44,14 @@ const Texts = ({ activeSlide, competitionId }: TextsProps) => {
</Center>
</ListItem>
{texts &&
texts.map((text) => (
<TextCard elevation={4} key={text.id}>
<TextComponentEdit component={text} />
<Divider />
</TextCard>
))}
texts
.filter((text) => text.view_type_id === activeViewTypeId)
.map((text) => (
<TextCard elevation={4} key={text.id}>
<TextComponentEdit component={text} />
<Divider />
</TextCard>
))}
<ListItem button onClick={handleAddText}>
<Center>
<AddButton variant="button">Lägg till text</AddButton>
......
......@@ -7,8 +7,18 @@ export const ToolBarContainer = styled(Toolbar)`
padding-left: 0;
`
export const ViewButton = styled(Button)`
interface ViewButtonProps {
activeView: boolean
}
export const ViewButton = styled(Button)<ViewButtonProps>`
margin-right: 8px;
background: ${(props) => (props.activeView ? '#5a0017' : undefined)};
`
export const ViewButtonClicked = styled(Button)`
margin-right: 8px;
background: #5a0017;
`
export const ViewButtonGroup = styled.div`
......
import { Typography } from '@material-ui/core'
import React from 'react'
import { useAppSelector } from '../../hooks'
import SlideDisplay from '../presentationEditor/components/SlideDisplay'
import PresentationComponent from './components/PresentationComponent'
import mockedAxios from 'axios'
const AudienceViewPage: React.FC = () => {
const res = {
data: {},
const viewTypes = useAppSelector((state) => state.types.viewTypes)
const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Audience')?.id
if (activeViewTypeId) {
return <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />
}
;(mockedAxios.get as jest.Mock).mockImplementation(() => {
return Promise.resolve(res)
})
return <SlideDisplay />
return <Typography>Error: Åskådarvyn kunde inte laddas</Typography>
}
export default AudienceViewPage
......@@ -51,6 +51,8 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => {
const history = useHistory()
const dispatch = useAppDispatch()
const [activeSlideIndex, setActiveSlideIndex] = useState<number>(0)
const viewTypes = useAppSelector((state) => state.types.viewTypes)
const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Judge')?.id
const teams = useAppSelector((state) => state.presentation.competition.teams)
const slides = useAppSelector((state) => state.presentation.competition.slides)
const currentQuestion = slides[activeSlideIndex]?.questions[0]
......@@ -128,7 +130,7 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => {
<div className={classes.toolbar} />
<Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}>
<InnerContent>
<SlideDisplay />
{activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />}
</InnerContent>
</Content>
</div>
......
......@@ -80,6 +80,8 @@ const OperatorViewPage: React.FC = () => {
const presentation = useAppSelector((state) => state.presentation)
const history = useHistory()
const dispatch = useAppDispatch()
const viewTypes = useAppSelector((state) => state.types.viewTypes)
const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Operator')?.id
useEffect(() => {
dispatch(getPresentationCompetition(id))
......@@ -219,7 +221,7 @@ const OperatorViewPage: React.FC = () => {
<div style={{ height: 0, paddingTop: 120 }} />
<OperatorContent>
<OperatorInnerContent>
<SlideDisplay />
{activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />}
</OperatorInnerContent>
</OperatorContent>
<div style={{ height: 0, paddingTop: 140 }} />
......
......@@ -9,6 +9,8 @@ import { useAppSelector } from '../../hooks'
const TeamViewPage: React.FC = () => {
const history = useHistory()
const code = useAppSelector((state) => state.presentation.code)
const viewTypes = useAppSelector((state) => state.types.viewTypes)
const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Team')?.id
useEffect(() => {
//hides the url so people can't sneak peak
history.push('team')
......@@ -19,7 +21,7 @@ const TeamViewPage: React.FC = () => {
}, [])
return (
<TeamContainer>
<SlideDisplay />
{activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />}
</TeamContainer>
)
}
......
......@@ -5,6 +5,7 @@ import { RichCompetition } from '../interfaces/ApiRichModels'
interface EditorState {
competition: RichCompetition
activeSlideId: number
activeViewTypeId: number
loading: boolean
}
......@@ -19,6 +20,7 @@ const initialState: EditorState = {
background_image: undefined,
},
activeSlideId: -1,
activeViewTypeId: -1,
loading: true,
}
......@@ -35,6 +37,11 @@ export default function (state = initialState, action: AnyAction) {
...state,
activeSlideId: action.payload as number,
}
case Types.SET_EDITOR_VIEW_ID:
return {
...state,
activeViewTypeId: action.payload as number,
}
default:
return state
}
......
......@@ -22,6 +22,7 @@ component_edit_parser.add_argument("media_id", type=str, location="json")
component_create_parser = component_edit_parser.copy()
component_create_parser.add_argument("type_id", type=int, required=True, location="json")
component_create_parser.add_argument("view_type_id", type=int, required=True, location="json")
@api.route("/<component_id>")
......
......@@ -161,6 +161,7 @@ class ComponentSchema(BaseSchema):
h = ma.auto_field()
slide_id = ma.auto_field()
type_id = ma.auto_field()
view_type_id = ma.auto_field()
text = fields.fields.String()
media = fields.Nested(MediaSchema, many=False)
......@@ -9,7 +9,7 @@ logger = logging.getLogger(__name__)
logger.propagate = False
logger.setLevel(logging.INFO)
formatter = logging.Formatter('[%(levelname)s] %(funcName)s: %(message)s')
formatter = logging.Formatter("[%(levelname)s] %(funcName)s: %(message)s")
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
......@@ -44,7 +44,9 @@ def start_presentation(data):
competition_id = data["competition_id"]
if competition_id in presentations:
logger.error(f"Client '{request.sid}' failed to start competition '{competition_id}', presentation already active")
logger.error(
f"Client '{request.sid}' failed to start competition '{competition_id}', presentation already active"
)
return
presentations[competition_id] = {
......@@ -58,16 +60,21 @@ def start_presentation(data):
logger.info(f"Client '{request.sid}' started competition '{competition_id}'")
@sio.on("end_presentation")
def end_presentation(data):
competition_id = data["competition_id"]
if competition_id not in presentations:
logger.error(f"Client '{request.sid}' failed to end presentation '{competition_id}', no such presentation exists")
logger.error(
f"Client '{request.sid}' failed to end presentation '{competition_id}', no such presentation exists"
)
return
if request.sid not in presentations[competition_id]["clients"]:
logger.error(f"Client '{request.sid}' failed to end presentation '{competition_id}', client not in presentation")
logger.error(
f"Client '{request.sid}' failed to end presentation '{competition_id}', client not in presentation"
)
return
if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator":
......@@ -96,11 +103,15 @@ def join_presentation(data):
competition_id = item_code.competition_id
if competition_id not in presentations:
logger.error(f"Client '{request.sid}' failed to join presentation '{competition_id}', no such presentation exists")
logger.error(
f"Client '{request.sid}' failed to join presentation '{competition_id}', no such presentation exists"
)
return
if request.sid in presentations[competition_id]["clients"]:
logger.error(f"Client '{request.sid}' failed to join presentation '{competition_id}', client already in presentation")
logger.error(
f"Client '{request.sid}' failed to join presentation '{competition_id}', client already in presentation"
)
return
# TODO: Write function in database controller to do this
......@@ -120,21 +131,29 @@ def set_slide(data):
slide_order = data["slide_order"]
if competition_id not in presentations:
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', no such presentation exists")
logger.error(
f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', no such presentation exists"
)
return
if request.sid not in presentations[competition_id]["clients"]:
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client not in presentation")
logger.error(
f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client not in presentation"
)
return
if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator":
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client is not operator")
logger.error(
f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client is not operator"
)
return
num_slides = db.session.query(Slide).filter(Slide.competition_id == competition_id).count()
if not (0 <= slide_order < num_slides):
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', slide number {slide_order} does not exist")
logger.error(
f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', slide number {slide_order} does not exist"
)
return
presentations[competition_id]["slide"] = slide_order
......@@ -151,15 +170,21 @@ def set_timer(data):
timer = data["timer"]
if competition_id not in presentations:
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', no such presentation exists")
logger.error(
f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', no such presentation exists"
)
return
if request.sid not in presentations[competition_id]["clients"]:
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client not in presentation")
logger.error(
f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client not in presentation"
)
return
if presentations[competition_id]["clients"][request.sid]["view_type"] != "Operator":
logger.error(f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client is not operator")
logger.error(
f"Client '{request.sid}' failed to set slide in presentation '{competition_id}', client is not operator"
)
return
# TODO: Save timer in presentation, maybe?
......@@ -168,4 +193,3 @@ def set_timer(data):
logger.debug(f"Emitting event 'set_timer' to room {competition_id} including self")
logger.info(f"Client '{request.sid}' set timer '{timer}' in presentation '{competition_id}'")
......@@ -59,7 +59,7 @@ def db_add(item):
return item
def component(type_id, slide_id, x=0, y=0, w=0, h=0, **data):
def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, **data):
"""
Adds a component to the slide at the specified coordinates with the
provided size and data .
......@@ -80,10 +80,10 @@ def component(type_id, slide_id, x=0, y=0, w=0, h=0, **data):
h *= ratio
if type_id == 1:
item = db_add(TextComponent(slide_id, type_id, x, y, w, h))
item = db_add(TextComponent(slide_id, type_id, view_type_id, x, y, w, h))
item.text = data.get("text")
elif type_id == 2:
item = db_add(ImageComponent(slide_id, type_id, x, y, w, h))
item = db_add(ImageComponent(slide_id, type_id, view_type_id, x, y, w, h))
item.media_id = data.get("media_id")
else:
abort(codes.BAD_REQUEST, f"Invalid type_id{type_id}")
......
......@@ -45,6 +45,7 @@ def _component(item_component, item_slide_new):
add.component(
item_component.type_id,
item_slide_new.id,
item_component.view_type_id,
item_component.x,
item_component.y,
item_component.w,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment