diff --git a/client/src/enum/ComponentTypes.ts b/client/src/enum/ComponentTypes.ts index 8567e1c82bba8eb42e0d8a705bb6179ce56ba118..c0aa738479a2c081ef0529bb6b4a317570b6841d 100644 --- a/client/src/enum/ComponentTypes.ts +++ b/client/src/enum/ComponentTypes.ts @@ -1,5 +1,5 @@ export enum ComponentTypes { Text = 1, Image, - Checkbox, + QuestionAlternative, } diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts index 347fdbfa514b3f2adb71c4d0c47b19a08fa99376..85933175e45980d46acf8f62dc0af55ca7e3ee23 100644 --- a/client/src/interfaces/ApiModels.ts +++ b/client/src/interfaces/ApiModels.ts @@ -64,6 +64,7 @@ export interface QuestionAlternative { value: number question_id: number } + export interface QuestionAnswer { id: number question_id: number @@ -96,6 +97,11 @@ export interface TextComponent extends Component { } export interface QuestionAlternativeComponent extends Component { - question_alternative_id: number - font: string + data: { + question_id: number + text: string + value: number + question_alternative_id: number + font: string + } } diff --git a/client/src/pages/presentationEditor/components/RndComponent.tsx b/client/src/pages/presentationEditor/components/RndComponent.tsx index 02344a36aa223b245fe7170c9186ffa8b509e6dc..effd3362df2cbb5ae8301c7e731934f6dee7abb6 100644 --- a/client/src/pages/presentationEditor/components/RndComponent.tsx +++ b/client/src/pages/presentationEditor/components/RndComponent.tsx @@ -1,24 +1,31 @@ +import { Button, Card, IconButton, Tooltip, Typography } from '@material-ui/core' import axios from 'axios' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { Rnd } from 'react-rnd' import { ComponentTypes } from '../../../enum/ComponentTypes' import { useAppSelector } from '../../../hooks' -import { Component, ImageComponent, TextComponent } from '../../../interfaces/ApiModels' +import { Component, ImageComponent, QuestionAlternativeComponent, TextComponent } from '../../../interfaces/ApiModels' import { Position, Size } from '../../../interfaces/Components' import CheckboxComponent from './CheckboxComponent' import ImageComponentDisplay from './ImageComponentDisplay' -import { TextComponentContainer } from './styled' +import { HoverContainer } from './styled' +import FormatAlignCenterIcon from '@material-ui/icons/FormatAlignCenter' type ImageComponentProps = { component: Component + width: number + height: number } -const RndComponent = ({ component }: ImageComponentProps) => { +const RndComponent = ({ component, width, height }: ImageComponentProps) => { + //Makes scale close to 1, 800 height is approxemately for a 1920 by 1080 monitor + const scale = height / 800 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 }) const competitionId = useAppSelector((state) => state.editor.competition.id) const slideId = useAppSelector((state) => state.editor.activeSlideId) + const [shiftPressed, setShiftPressed] = useState(false) const handleUpdatePos = (pos: Position) => { axios.put(`/api/competitions/${competitionId}/slides/${slideId}/components/${component.id}`, { x: pos.x, @@ -31,28 +38,69 @@ const RndComponent = ({ component }: ImageComponentProps) => { h: size.h, }) } + const handleCenterHorizontal = () => { + console.log(width, currentSize.w) + const centerX = width / (2 * scale) - currentSize.w / 2 + setCurrentPos({ x: centerX, y: currentPos.y }) + handleUpdatePos({ x: centerX, y: currentPos.y }) + } + const handleCenterVertical = () => { + console.log(height, currentSize.h) + const centerY = height / (2 * scale) - currentSize.h / 2 + setCurrentPos({ x: currentPos.x, y: centerY }) + handleUpdatePos({ x: currentPos.x, y: centerY }) + } + useEffect(() => { + const downHandler = (ev: KeyboardEvent) => { + if (ev.key === 'Shift') setShiftPressed(true) + } + const upHandler = (ev: KeyboardEvent) => { + if (ev.key === 'Shift') setShiftPressed(false) + } + window.addEventListener('keydown', downHandler) + window.addEventListener('keyup', upHandler) + return () => { + window.removeEventListener('keydown', downHandler) + window.removeEventListener('keyup', upHandler) + } + }, []) const renderInnerComponent = () => { switch (component.type_id) { - case ComponentTypes.Checkbox: - return <CheckboxComponent key={component.id} component={component} /> case ComponentTypes.Text: return ( - <TextComponentContainer + <HoverContainer hover={hover} dangerouslySetInnerHTML={{ - __html: `<div style="font-size: 24px;"> ${(component as TextComponent).data.text} </div>`, + __html: `<div style="font-size: ${Math.round(24 * scale)}px;">${ + (component as TextComponent).data.text + }</div>`, }} /> ) case ComponentTypes.Image: return ( - <ImageComponentDisplay - key={component.id} - component={component as ImageComponent} - width={currentSize.w} - height={currentSize.h} - /> + <HoverContainer hover={hover}> + <img + key={component.id} + src={`/static/images/${(component as ImageComponent).data.filename}`} + height={currentSize.h * scale} + width={currentSize.w * scale} + draggable={false} + /> + </HoverContainer> + ) + case ComponentTypes.Image: + return ( + <HoverContainer hover={hover}> + <img + key={component.id} + src={`/static/images/${(component as ImageComponent).data.filename}`} + height={currentSize.h * scale} + width={currentSize.w * scale} + draggable={false} + /> + </HoverContainer> ) default: break @@ -61,33 +109,42 @@ const RndComponent = ({ component }: ImageComponentProps) => { return ( <Rnd - minWidth={50} - minHeight={50} + minWidth={75 * scale} + minHeight={75 * scale} bounds="parent" onDragStop={(e, d) => { - setCurrentPos({ x: d.x, y: d.y }) - handleUpdatePos(d) + //Have to divide by scale since d is position on current screen + setCurrentPos({ x: d.x / scale, y: d.y / scale }) + handleUpdatePos({ x: d.x / scale, y: d.y / scale }) }} + lockAspectRatio={shiftPressed} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} - size={{ width: currentSize.w, height: currentSize.h }} - position={{ x: currentPos.x, y: currentPos.y }} + //Multiply by scale to show components correctly for current screen size + size={{ width: currentSize.w * scale, height: currentSize.h * scale }} + position={{ x: currentPos.x * scale, y: currentPos.y * scale }} onResizeStop={(e, direction, ref, delta, position) => { - setCurrentSize({ - w: ref.offsetWidth, - h: ref.offsetHeight, - }) - setCurrentPos(position) - handleUpdateSize({ w: ref.offsetWidth, h: ref.offsetHeight }) - handleUpdatePos(position) + handleUpdateSize({ w: currentSize.w, h: currentSize.h }) + handleUpdatePos({ x: currentPos.x, y: currentPos.y }) }} - onResize={(e, direction, ref, delta, position) => + onResize={(e, direction, ref, delta, position) => { + //Have to divide by scale since ref has position on current screen setCurrentSize({ - w: ref.offsetWidth, - h: ref.offsetHeight, + w: ref.offsetWidth / scale, + h: ref.offsetHeight / scale, }) - } + }} > + {hover && ( + <Card elevation={6} style={{ position: 'absolute' }}> + <Tooltip title="Centrera horisontellt"> + <IconButton onClick={handleCenterHorizontal}>X</IconButton> + </Tooltip> + <Tooltip title="Centrera Vertikalt"> + <IconButton onClick={handleCenterVertical}>Y</IconButton> + </Tooltip> + </Card> + )} {renderInnerComponent()} </Rnd> ) diff --git a/client/src/pages/presentationEditor/components/SlideEditor.tsx b/client/src/pages/presentationEditor/components/SlideEditor.tsx index 457f903fb09ff958663daed2197516afb33841e6..b385bb2058fe73987f1a649456c6fe0adad10c56 100644 --- a/client/src/pages/presentationEditor/components/SlideEditor.tsx +++ b/client/src/pages/presentationEditor/components/SlideEditor.tsx @@ -1,5 +1,6 @@ -import React from 'react' -import { useAppSelector } from '../../../hooks' +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react' +import { getTypes } from '../../../actions/typesAction' +import { useAppDispatch, useAppSelector } from '../../../hooks' import RndComponent from './RndComponent' import { SlideEditorContainer, SlideEditorContainerRatio, SlideEditorPaper } from './styled' @@ -8,11 +9,33 @@ const SlideEditor: React.FC = () => { (state) => state.editor.competition.slides.find((slide) => slide && slide.id === state.editor.activeSlideId)?.components ) + const dispatch = useAppDispatch() + const editorPaperRef = useRef<HTMLDivElement>(null) + const [width, setWidth] = useState(0) + const [height, setHeight] = useState(0) + useEffect(() => { + dispatch(getTypes()) + }, []) + + useLayoutEffect(() => { + const updateScale = () => { + if (editorPaperRef.current) { + setWidth(editorPaperRef.current.clientWidth) + setHeight(editorPaperRef.current.clientHeight) + } + } + window.addEventListener('resize', updateScale) + updateScale() + return () => window.removeEventListener('resize', updateScale) + }, []) return ( <SlideEditorContainer> <SlideEditorContainerRatio> - <SlideEditorPaper> - {components && components.map((component) => <RndComponent key={component.id} component={component} />)} + <SlideEditorPaper ref={editorPaperRef}> + {components && + components.map((component) => ( + <RndComponent height={height} width={width} key={component.id} component={component} /> + ))} </SlideEditorPaper> </SlideEditorContainerRatio> </SlideEditorContainer> diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index a636d9d2d4d3f7faac3eef6166e5c07544c5a596..2a3959c7edee69c190215af53b91c4ae7f2aebf9 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -140,11 +140,11 @@ export const DeleteTextButton = styled(Button)` margin-bottom: 7px; ` -interface TextComponentContainerProps { +interface HoverContainerProps { hover: boolean } -export const TextComponentContainer = styled.div<TextComponentContainerProps>` +export const HoverContainer = styled.div<HoverContainerProps>` height: 100%; width: 100%; padding: ${(props) => (props.hover ? 0 : 1)}px;