From 0c63229c61fe8b7fce038ad0af81ddd4eeb0aa69 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Thu, 29 Apr 2021 15:46:27 +0200 Subject: [PATCH 1/3] Copy components and tests the added code --- server/app/apis/components.py | 10 +++++++++ server/app/database/controller/copy.py | 18 ++++++++++++--- server/tests/test_app.py | 31 +++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/server/app/apis/components.py b/server/app/apis/components.py index a1982763..38227a46 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -54,6 +54,16 @@ class ComponentByID(Resource): return {}, codes.NO_CONTENT +@api.route("/<component_id>/copy/<view_type_id>") +@api.param("competition_id, slide_id, component_id, view_type_id") +class ComponentList(Resource): + @protect_route(allowed_roles=["*"]) + def post(self, competition_id, slide_id, component_id, view_type_id): + item_component = dbc.get.component(competition_id, slide_id, component_id) + item = dbc.copy.component(item_component, slide_id, view_type_id) + return item_response(schema.dump(item)) + + @api.route("") @api.param("competition_id, slide_id") class ComponentList(Resource): diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py index 08d74347..48dab2db 100644 --- a/server/app/database/controller/copy.py +++ b/server/app/database/controller/copy.py @@ -9,6 +9,7 @@ from app.database.types import ID_IMAGE_COMPONENT, ID_QUESTION_COMPONENT, ID_TEX def _alternative(item_old, question_id): """Internal function. Makes a copy of the provided question alternative""" + return add.question_alternative(item_old.text, item_old.value, question_id) @@ -39,6 +40,16 @@ def _component(item_component, item_slide_new): Internal function. Makes a copy of the provided component item to the specified slide. """ + + component(item_component, item_slide_new.id, item_component.view_type_id) + + +def component(item_component, slide_id_new, view_type_id): + """ + Makes a copy of the provided component item + to the specified slide and view_type. + """ + data = {} if item_component.type_id == ID_TEXT_COMPONENT: data["text"] = item_component.text @@ -46,10 +57,11 @@ def _component(item_component, item_slide_new): data["media_id"] = item_component.media_id elif item_component.type_id == ID_QUESTION_COMPONENT: data["question_id"] = item_component.question_id - add.component( + + return add.component( item_component.type_id, - item_slide_new.id, - item_component.view_type_id, + slide_id_new, + view_type_id, item_component.x, item_component.y, item_component.w, diff --git a/server/tests/test_app.py b/server/tests/test_app.py index d59428a6..1770b70f 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -112,7 +112,7 @@ def test_competition_api(client): assert response.status_code == codes.OK # Copies competition - for _ in range(10): + for _ in range(3): response, _ = post(client, f"/api/competitions/{competition_id}/copy", headers=headers) assert response.status_code == codes.OK @@ -330,6 +330,35 @@ def test_slide_api(client): assert response.status_code == codes.OK """ + # Get a specific component + CID = 2 + SID = 3 + COMID = 2 + response, c1 = get(client, f"/api/competitions/{CID}/slides/{SID}/components/{COMID}", headers=headers) + assert response.status_code == codes.OK + + # Copy the component to another view + view_type_id = 3 + response, c2 = post( + client, f"/api/competitions/{CID}/slides/{SID}/components/{COMID}/copy/{view_type_id}", headers=headers + ) + # Check that the components metch + assert response.status_code == codes.OK + assert c1 != c2 + assert c1["x"] == c2["x"] + assert c1["y"] == c2["y"] + assert c1["w"] == c2["w"] + assert c1["h"] == c2["h"] + assert c1["slide_id"] == SID + assert c2["slide_id"] == SID + assert c1["type_id"] == c2["type_id"] + if c1["type_id"] == 1: + assert c1["text"] == c2["text"] + elif c1["type_id"] == 2: + assert c1["image_id"] == c2["image_id"] + assert c1["view_type_id"] == 1 + assert c2["view_type_id"] == 3 + def test_question_api(client): add_default_values() -- GitLab From 09be6c2c9af1960340acd1aa35660dd4192ce4af Mon Sep 17 00:00:00 2001 From: Sebastian Karlsson <sebka991@student.liu.se> Date: Thu, 29 Apr 2021 16:09:15 +0200 Subject: [PATCH 2/3] Add context menu for components in editor --- .../components/RndComponent.tsx | 66 ++++++++++++++++++- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/client/src/pages/presentationEditor/components/RndComponent.tsx b/client/src/pages/presentationEditor/components/RndComponent.tsx index 77ed5de3..3b19dcd9 100644 --- a/client/src/pages/presentationEditor/components/RndComponent.tsx +++ b/client/src/pages/presentationEditor/components/RndComponent.tsx @@ -1,9 +1,9 @@ -import { Button, Card, IconButton, Tooltip, Typography } from '@material-ui/core' +import { Button, Card, IconButton, Menu, MenuItem, Tooltip, Typography } from '@material-ui/core' import axios from 'axios' import React, { useEffect, useState } from 'react' import { Rnd } from 'react-rnd' import { ComponentTypes } from '../../../enum/ComponentTypes' -import { useAppSelector } from '../../../hooks' +import { useAppDispatch, useAppSelector } from '../../../hooks' import { Component, ImageComponent, QuestionAlternativeComponent, TextComponent } from '../../../interfaces/ApiModels' import { Position, Size } from '../../../interfaces/Components' import CheckboxComponent from './CheckboxComponent' @@ -11,6 +11,9 @@ import ImageComponentDisplay from './ImageComponentDisplay' import { HoverContainer } from './styled' import FormatAlignCenterIcon from '@material-ui/icons/FormatAlignCenter' import TextComponentDisplay from './TextComponentDisplay' +import { RemoveMenuItem } from '../../admin/styledComp' +import { getEditorCompetition } from '../../../actions/editor' +//import NestedMenuItem from 'material-ui-nested-menu-item' type RndComponentProps = { component: Component @@ -19,6 +22,8 @@ type RndComponentProps = { scale: number } +const initialMenuState = { menuIsOpen: false, mouseX: null, mouseY: null, componentId: null } + const RndComponent = ({ component, width, height, scale }: RndComponentProps) => { const [hover, setHover] = useState(false) const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y }) @@ -29,6 +34,14 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => const typeName = useAppSelector( (state) => state.types.componentTypes.find((componentType) => componentType.id === component.type_id)?.name ) + const [menuState, setMenuState] = useState<{ + menuIsOpen: boolean + mouseX: null | number + mouseY: null | number + componentId: null | number + }>(initialMenuState) + const dispatch = useAppDispatch() + const handleUpdatePos = (pos: Position) => { axios.put(`/api/competitions/${competitionId}/slides/${slideId}/components/${component.id}`, { x: pos.x, @@ -53,6 +66,36 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => setCurrentPos({ x: currentPos.x, y: centerY }) handleUpdatePos({ x: currentPos.x, y: centerY }) } + const handleRightClick = (event: React.MouseEvent<HTMLDivElement>, componentId: number) => { + event.preventDefault() + setMenuState({ + menuIsOpen: true, + mouseX: event.clientX - 2, + mouseY: event.clientY - 4, + componentId: componentId, + }) + } + const handleCloseMenu = () => { + setMenuState(initialMenuState) + } + const handleDuplicateComponent = async (viewTypeId: number) => { + console.log('Duplicate') + await axios + .post( + `/api/competitions/${competitionId}/slides/${slideId}/components/${menuState.componentId}/copy/${viewTypeId}` + ) + .catch(console.log) + setMenuState(initialMenuState) + } + const handleRemoveComponent = async () => { + console.log('Remove') + await axios + .delete(`/api/competitions/${competitionId}/slides/${slideId}/components/${menuState.componentId}`) + .then(() => dispatch(getEditorCompetition(competitionId.toString()))) + .catch(console.log) + setMenuState(initialMenuState) + } + useEffect(() => { const downHandler = (ev: KeyboardEvent) => { if (ev.key === 'Shift') setShiftPressed(true) @@ -106,6 +149,8 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => lockAspectRatio={shiftPressed} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} + //Right click to open menu + onContextMenu={(event: React.MouseEvent<HTMLDivElement>) => handleRightClick(event, component.id)} //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 }} @@ -133,6 +178,23 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => </Card> )} {renderInnerComponent()} + <Menu + keepMounted + open={menuState.menuIsOpen} + onClose={handleCloseMenu} + anchorReference="anchorPosition" + anchorPosition={ + menuState.mouseY !== null && menuState.mouseX !== null + ? { top: menuState.mouseY, left: menuState.mouseX } + : undefined + } + > + {/* <NestedMenuItem label="Duplicera"> */} + <MenuItem onClick={() => handleDuplicateComponent(3)}>Duplicera till åskådarvy</MenuItem> + <MenuItem onClick={() => handleDuplicateComponent(1)}>Duplicera till deltagarvy</MenuItem> + {/* </NestedMenuItem> */} + <RemoveMenuItem onClick={handleRemoveComponent}>Ta bort</RemoveMenuItem> + </Menu> </Rnd> ) } -- GitLab From 00b408ca294e89562bff2fc37a15a8532ec98be7 Mon Sep 17 00:00:00 2001 From: Sebastian Karlsson <sebka991@student.liu.se> Date: Thu, 29 Apr 2021 16:21:48 +0200 Subject: [PATCH 3/3] Update editor state upon component duplication --- client/src/pages/presentationEditor/components/RndComponent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/pages/presentationEditor/components/RndComponent.tsx b/client/src/pages/presentationEditor/components/RndComponent.tsx index 3b19dcd9..5892cbc0 100644 --- a/client/src/pages/presentationEditor/components/RndComponent.tsx +++ b/client/src/pages/presentationEditor/components/RndComponent.tsx @@ -84,6 +84,7 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) => .post( `/api/competitions/${competitionId}/slides/${slideId}/components/${menuState.componentId}/copy/${viewTypeId}` ) + .then(() => dispatch(getEditorCompetition(competitionId.toString()))) .catch(console.log) setMenuState(initialMenuState) } -- GitLab