From b4b62fde8a7f9e625876fbff8c6cd20dfe10b0f5 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Tue, 20 Apr 2021 10:45:28 +0200 Subject: [PATCH 01/13] Upload file from presentation editor --- .vscode/settings.json | 1 + .../components/SlideSettings.tsx | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 99228c86..549bb40e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -48,4 +48,5 @@ "search.exclude": { "**/env": true }, + "python.pythonPath": "server\\env\\Scripts\\python.exe", } \ No newline at end of file diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index c61d478e..fcaa028a 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -131,19 +131,22 @@ const SlideSettings: React.FC = () => { .catch(console.log) } - const handleFileSelected = (e: React.ChangeEvent<HTMLInputElement>): void => { + const uploadFile = async (formData: FormData) => { + await axios + .post(`/media/images`, formData) + .then(() => { + dispatch(getEditorCompetition(id)) + }) + .catch(console.log) + } + + const handleFileSelected = async (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files !== null && e.target.files[0]) { const files = Array.from(e.target.files) const file = files[0] - const reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = function () { - console.log(reader.result) - // TODO: Send image to back-end (remove console.log) - } - reader.onerror = function (error) { - console.log('Error: ', error) - } + const formData = new FormData() + formData.append('image', file) + await uploadFile(formData) } } -- GitLab From b0173a80896287507317247c80cc5727bd195a85 Mon Sep 17 00:00:00 2001 From: Sebastian Karlsson <sebka991@student.liu.se> Date: Wed, 21 Apr 2021 14:38:21 +0200 Subject: [PATCH 02/13] Add thumbnail --- .../components/SlideSettings.tsx | 75 +++++++++++++------ .../components/TextComponentDisplay.tsx | 2 +- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index fcaa028a..11eea562 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -16,7 +16,7 @@ import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/s import CloseIcon from '@material-ui/icons/Close' import MoreHorizOutlinedIcon from '@material-ui/icons/MoreHorizOutlined' import axios from 'axios' -import React, { useState } from 'react' +import React from 'react' import { useParams } from 'react-router-dom' import { getEditorCompetition } from '../../../actions/editor' import { useAppDispatch, useAppSelector } from '../../../hooks' @@ -101,14 +101,26 @@ const SlideSettings: React.FC = () => { ?.components.filter((component) => component.type_id === 1) as TextComponent[] ) - const pictureList = [ - { id: 'picture1', name: 'Picture1.jpeg' }, - { id: 'picture2', name: 'Picture2.jpeg' }, - ] - const handleClosePictureClick = (id: string) => { - setPictures(pictures.filter((item) => item.id !== id)) //Will not be done like this when api is used - } - const [pictures, setPictures] = useState(pictureList) + /*const pictures = useAppSelector( + (state) => + state.editor.competition.slides + .find((slide) => slide.id === state.editor.activeSlideId) + ?.components.filter((component) => component.type_id === 2) as ImageComponent[] + )*/ + + //const [pictures, setPictures] = useState(pictureList) + //pictures.map((picture) => competition.slides[0].components.push(picture)) + + //const handleClosePictureClick = (id: number) => { + // setPictures(pictures.filter((item) => item.id !== id)) //Will not be done like this when api is used + //Ta bort från servern + //Ta bort Media + //Ta bort ImageComponent + //} + + const pictures = [{ id: 'picture1', name: 'Wallgren.png' }] + + //const [pictures, setPictures] = useState(pictureList) const updateSlideType = async (event: React.ChangeEvent<{ value: unknown }>) => { await axios @@ -140,6 +152,15 @@ const SlideSettings: React.FC = () => { .catch(console.log) } + /*const createImageComponent = async () => { + await axios + .post(`/competitions/`, formData) + .then(() => { + dispatch(getEditorCompetition(id)) + }) + .catch(console.log) + }*/ + const handleFileSelected = async (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files !== null && e.target.files[0]) { const files = Array.from(e.target.files) @@ -147,6 +168,11 @@ const SlideSettings: React.FC = () => { const formData = new FormData() formData.append('image', file) await uploadFile(formData) + + //createImageComponent() + // Skapa en ImageComponent + // Lägg in filnamnet i data hos komponenten + // } } @@ -154,6 +180,9 @@ const SlideSettings: React.FC = () => { console.log('Add text component') // TODO: post the new text] // setTexts([...texts, { id: 'newText', name: 'New Text' }]) + console.log(texts) + console.log('--------') + console.log(pictures) } const GreenCheckbox = withStyles({ @@ -254,19 +283,21 @@ const SlideSettings: React.FC = () => { <ListItem divider> <ListItemText className={classes.textCenter} primary="Bilder" /> </ListItem> - {pictures.map((picture) => ( - <div key={picture.id}> - <ListItem divider button> - <img - id="temp source, todo: add image source to elements of pictureList" - src="https://i1.wp.com/stickoutmedia.se/wp-content/uploads/2021/01/placeholder-3.png?ssl=1" - className={classes.importedImage} - /> - <ListItemText className={classes.textCenter} primary={picture.name} /> - <CloseIcon onClick={() => handleClosePictureClick(picture.id)} /> - </ListItem> - </div> - ))} + {pictures && + pictures.map((picture) => ( + <div key={picture.id}> + <ListItem divider button> + <img + id="temp source, todo: add image source to elements of pictureList" + src={`http://localhost:5000/static/images/${picture.name}`} + //{picture.data.media_id} + className={classes.importedImage} + /> + <ListItemText className={classes.textCenter} primary={picture.name} /> + <CloseIcon /*onClick={() => handleClosePictureClick(picture.id)}*/ /> + </ListItem> + </div> + ))} <ListItem className={classes.center} button> <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} /> diff --git a/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx index 4fb34650..c84e8aee 100644 --- a/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx +++ b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx @@ -15,7 +15,7 @@ const TextComponentDisplay = ({ component }: ImageComponentProps) => { 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) - if (component.id === 1) console.log(component) + //if (component.id === 1) console.log(component) const handleEditorChange = (e: any) => { console.log('Content was updated:', e.target.getContent()) axios.put(`/competitions/${competitionId}/slides/${slideId}/components/${component.id}`, { -- GitLab From 2878298926fa149c363bd44396ebabd49b8dafd9 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Wed, 21 Apr 2021 15:12:42 +0000 Subject: [PATCH 03/13] Update __init__.py --- server/app/database/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py index 7deab335..784c006b 100644 --- a/server/app/database/__init__.py +++ b/server/app/database/__init__.py @@ -45,7 +45,7 @@ class Dictionary(TypeDecorator): def process_bind_param(self, value, dialect): if value is not None: - value = json.dumps(value).replace("'", '"') + value = json.dumps(value) return value -- GitLab From 8e175e1fa224e27e2dd7d682244c816f9dd170ee Mon Sep 17 00:00:00 2001 From: Sebastian Karlsson <sebka991@student.liu.se> Date: Wed, 21 Apr 2021 17:15:43 +0200 Subject: [PATCH 04/13] Start adding media state (probably in a bad way) --- client/src/actions/types.ts | 4 +++ client/src/interfaces/ApiRichModels.ts | 3 +- client/src/reducers/allReducers.ts | 2 ++ client/src/reducers/mediaReducer.ts | 39 ++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 client/src/reducers/mediaReducer.ts diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index a417f6af..e500f2c8 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -29,4 +29,8 @@ export default { SET_CITIES_TOTAL: 'SET_CITIES_TOTAL', SET_CITIES_COUNT: 'SET_CITIES_COUNT', SET_TYPES: 'SET_TYPES', + SET_MEDIA_ID: 'SET_MEDIA_ID', + SET_MEDIA_FILENAME: 'SET_MEDIA_ID', + SET_MEDIA_TYPE_ID: 'SET_MEDIA_TYPE_ID', + SET_MEDIA_USER_ID: 'SET_MEDIA_USER_ID', } diff --git a/client/src/interfaces/ApiRichModels.ts b/client/src/interfaces/ApiRichModels.ts index 99ee4ec2..c0d2de7c 100644 --- a/client/src/interfaces/ApiRichModels.ts +++ b/client/src/interfaces/ApiRichModels.ts @@ -1,4 +1,4 @@ -import { Component, QuestionAlternative, QuestionAnswer, QuestionType } from './ApiModels' +import { Component, Media, QuestionAlternative, QuestionAnswer, QuestionType } from './ApiModels' export interface RichCompetition { name: string @@ -17,6 +17,7 @@ export interface RichSlide { competition_id: number components: Component[] questions: RichQuestion[] + medias: Media[] } export interface RichTeam { diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts index 2aee4697..2a25bad7 100644 --- a/client/src/reducers/allReducers.ts +++ b/client/src/reducers/allReducers.ts @@ -4,6 +4,7 @@ import { combineReducers } from 'redux' import citiesReducer from './citiesReducer' import competitionsReducer from './competitionsReducer' import editorReducer from './editorReducer' +import mediaReducer from './mediaReducer' import presentationReducer from './presentationReducer' import rolesReducer from './rolesReducer' import searchUserReducer from './searchUserReducer' @@ -22,5 +23,6 @@ const allReducers = combineReducers({ roles: rolesReducer, searchUsers: searchUserReducer, types: typesReducer, + media: mediaReducer, }) export default allReducers diff --git a/client/src/reducers/mediaReducer.ts b/client/src/reducers/mediaReducer.ts new file mode 100644 index 00000000..ad5f3b46 --- /dev/null +++ b/client/src/reducers/mediaReducer.ts @@ -0,0 +1,39 @@ +import { AnyAction } from 'redux' +import Types from '../actions/types' + +interface MediaState { + id: number + filename: string + mediatype_id: number + user_id: number +} +const initialState: MediaState = { + id: 0, + filename: '', + mediatype_id: 1, + user_id: 0, +} + +export default function (state = initialState, action: AnyAction) { + switch (action.type) { + case Types.SET_MEDIA_ID: + return { ...state, id: action.payload as number } + case Types.SET_MEDIA_FILENAME: + return { + ...state, + filename: action.payload as string, + } + case Types.SET_MEDIA_TYPE_ID: + return { + ...state, + mediatype_id: action.payload as number, + } + case Types.SET_MEDIA_USER_ID: + return { + ...state, + user_id: action.payload as number, + } + default: + return state + } +} -- GitLab From c09a5322b2aee63eebe4fbd5abc27c99f25f719a Mon Sep 17 00:00:00 2001 From: Sebastian Karlsson <sebka991@student.liu.se> Date: Thu, 22 Apr 2021 16:18:10 +0200 Subject: [PATCH 05/13] Add image component to database --- .../components/SlideSettings.tsx | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index 11eea562..2c5ede66 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -144,22 +144,32 @@ const SlideSettings: React.FC = () => { } const uploadFile = async (formData: FormData) => { - await axios + return await axios .post(`/media/images`, formData) - .then(() => { - dispatch(getEditorCompetition(id)) - }) + .then((response) => response.data.id as number) .catch(console.log) + // Fånga upp media_id från detta. Hur? } - /*const createImageComponent = async () => { + const createImageComponent = async (media_id: number) => { + const imageData = { + x: 0, + y: 0, + w: 400, + h: 400, + data: { + media_id: media_id, + }, + type_id: 2, + } + await axios - .post(`/competitions/`, formData) + .post(`/competitions/${id}/slides/${currentSlide?.order}/components`, imageData) .then(() => { dispatch(getEditorCompetition(id)) }) .catch(console.log) - }*/ + } const handleFileSelected = async (e: React.ChangeEvent<HTMLInputElement>) => { if (e.target.files !== null && e.target.files[0]) { @@ -167,12 +177,11 @@ const SlideSettings: React.FC = () => { const file = files[0] const formData = new FormData() formData.append('image', file) - await uploadFile(formData) + const response = await uploadFile(formData) - //createImageComponent() - // Skapa en ImageComponent - // Lägg in filnamnet i data hos komponenten - // + if (response) { + const newComponent = createImageComponent(response) + } } } -- GitLab From edbafad02179b4b23dba4b9e0fb42a124dd0a558 Mon Sep 17 00:00:00 2001 From: Sebastian Karlsson <sebka991@student.liu.se> Date: Thu, 22 Apr 2021 17:34:07 +0200 Subject: [PATCH 06/13] Add ImageComponents to state --- .../components/SlideSettings.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index 2c5ede66..cbf2a8aa 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -20,7 +20,7 @@ import React from 'react' import { useParams } from 'react-router-dom' import { getEditorCompetition } from '../../../actions/editor' import { useAppDispatch, useAppSelector } from '../../../hooks' -import { TextComponent } from '../../../interfaces/ApiModels' +import { ImageComponent, TextComponent } from '../../../interfaces/ApiModels' import { HiddenInput } from './styled' const useStyles = makeStyles((theme: Theme) => @@ -101,12 +101,12 @@ const SlideSettings: React.FC = () => { ?.components.filter((component) => component.type_id === 1) as TextComponent[] ) - /*const pictures = useAppSelector( + const pictures = useAppSelector( (state) => state.editor.competition.slides .find((slide) => slide.id === state.editor.activeSlideId) ?.components.filter((component) => component.type_id === 2) as ImageComponent[] - )*/ + ) //const [pictures, setPictures] = useState(pictureList) //pictures.map((picture) => competition.slides[0].components.push(picture)) @@ -118,7 +118,7 @@ const SlideSettings: React.FC = () => { //Ta bort ImageComponent //} - const pictures = [{ id: 'picture1', name: 'Wallgren.png' }] + //const pictures = [{ id: 'picture1', name: 'Wallgren.png' }] //const [pictures, setPictures] = useState(pictureList) @@ -144,11 +144,15 @@ const SlideSettings: React.FC = () => { } const uploadFile = async (formData: FormData) => { + // Uploads the file to the server and creates a Media object in database + // Returns media id return await axios .post(`/media/images`, formData) - .then((response) => response.data.id as number) + .then((response) => { + dispatch(getEditorCompetition(id)) + return response.data.id as number + }) .catch(console.log) - // Fånga upp media_id från detta. Hur? } const createImageComponent = async (media_id: number) => { @@ -298,11 +302,11 @@ const SlideSettings: React.FC = () => { <ListItem divider button> <img id="temp source, todo: add image source to elements of pictureList" - src={`http://localhost:5000/static/images/${picture.name}`} + //src={`http://localhost:5000/static/images/${picture.data.media_id}`} //{picture.data.media_id} className={classes.importedImage} /> - <ListItemText className={classes.textCenter} primary={picture.name} /> + <ListItemText className={classes.textCenter} /*primary={picture.name}*/ /> <CloseIcon /*onClick={() => handleClosePictureClick(picture.id)}*/ /> </ListItem> </div> -- GitLab From 1027a2f94cfb23311d83928670c86115a8cf8c1d Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Fri, 23 Apr 2021 09:33:35 +0200 Subject: [PATCH 07/13] Add images to slide in interface --- client/src/enum/ComponentTypes.ts | 2 +- client/src/interfaces/ApiModels.ts | 1 + .../components/ImageComponentDisplay.tsx | 43 +++++-------------- .../components/RndComponent.tsx | 9 +++- .../components/SlideSettings.tsx | 9 ++-- 5 files changed, 25 insertions(+), 39 deletions(-) diff --git a/client/src/enum/ComponentTypes.ts b/client/src/enum/ComponentTypes.ts index 8acb2fd9..8567e1c8 100644 --- a/client/src/enum/ComponentTypes.ts +++ b/client/src/enum/ComponentTypes.ts @@ -1,5 +1,5 @@ export enum ComponentTypes { Text = 1, - Checkbox, Image, + Checkbox, } diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts index 650814f1..347fdbfa 100644 --- a/client/src/interfaces/ApiModels.ts +++ b/client/src/interfaces/ApiModels.ts @@ -84,6 +84,7 @@ export interface Component { export interface ImageComponent extends Component { data: { media_id: number + filename: string } } diff --git a/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx b/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx index cffba9e5..7886a9b1 100644 --- a/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx +++ b/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx @@ -1,43 +1,20 @@ -import React, { useState } from 'react' -import { Rnd } from 'react-rnd' +import React from 'react' import { ImageComponent } from '../../../interfaces/ApiModels' -import { Position, Size } from '../../../interfaces/Components' type ImageComponentProps = { component: ImageComponent + width: number + height: number } -const ImageComponentDisplay = ({ component }: ImageComponentProps) => { - const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y }) - const [currentSize, setCurrentSize] = useState<Size>({ w: component.w, h: component.h }) +const ImageComponentDisplay = ({ component, width, height }: ImageComponentProps) => { return ( - <Rnd - minWidth={50} - minHeight={50} - bounds="parent" - onDragStop={(e, d) => { - setCurrentPos({ x: d.x, y: d.y }) - }} - size={{ width: currentSize.w, height: currentSize.h }} - position={{ x: currentPos.x, y: currentPos.y }} - onResize={(e, direction, ref, delta, position) => { - setCurrentSize({ - w: ref.offsetWidth, - h: ref.offsetHeight, - }) - setCurrentPos(position) - }} - onResizeStop={() => { - console.log('Skicka data till server') - }} - > - <img - src="https://365psd.com/images/previews/c61/cartoon-cow-52394.png" - height={currentSize.h} - width={currentSize.w} - draggable={false} - /> - </Rnd> + <img + src={`http://localhost:5000/static/images/${component.data.filename}`} + height={height} + width={width} + draggable={false} + /> ) } diff --git a/client/src/pages/presentationEditor/components/RndComponent.tsx b/client/src/pages/presentationEditor/components/RndComponent.tsx index 890e5160..11875755 100644 --- a/client/src/pages/presentationEditor/components/RndComponent.tsx +++ b/client/src/pages/presentationEditor/components/RndComponent.tsx @@ -44,7 +44,14 @@ const RndComponent = ({ component }: ImageComponentProps) => { /> ) case ComponentTypes.Image: - return <ImageComponentDisplay key={component.id} component={component as ImageComponent} /> + return ( + <ImageComponentDisplay + key={component.id} + component={component as ImageComponent} + width={currentSize.w} + height={currentSize.h} + /> + ) default: break } diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index b48c7f6a..f105914c 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -22,7 +22,7 @@ import { green, grey } from '@material-ui/core/colors' import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles' import CloseIcon from '@material-ui/icons/Close' import axios from 'axios' -import { ImageComponent, TextComponent, QuestionAlternative } from '../../../interfaces/ApiModels' +import { ImageComponent, TextComponent, QuestionAlternative, Media } from '../../../interfaces/ApiModels' import React, { useEffect, useState } from 'react' import { useParams } from 'react-router-dom' import { getEditorCompetition } from '../../../actions/editor' @@ -238,19 +238,20 @@ const SlideSettings: React.FC = () => { .post(`/media/images`, formData) .then((response) => { dispatch(getEditorCompetition(id)) - return response.data.id as number + return response.data as Media }) .catch(console.log) } - const createImageComponent = async (media_id: number) => { + const createImageComponent = async (media: Media) => { const imageData = { x: 0, y: 0, w: 400, h: 400, data: { - media_id: media_id, + media_id: media.id, + filename: media.filename, }, type_id: 2, } -- GitLab From 6b77ba90124125354ae1395a17051c308b5a3279 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Fri, 23 Apr 2021 15:52:24 +0200 Subject: [PATCH 08/13] Delete images from editor --- .../components/SlideSettings.tsx | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index f105914c..33f96d4c 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -117,26 +117,32 @@ const SlideSettings: React.FC = () => { ?.components.filter((component) => component.type_id === 1) as TextComponent[] ) - const pictures = useAppSelector( + const images = useAppSelector( (state) => state.editor.competition.slides .find((slide) => slide.id === state.editor.activeSlideId) ?.components.filter((component) => component.type_id === 2) as ImageComponent[] ) - //const [pictures, setPictures] = useState(pictureList) - //pictures.map((picture) => competition.slides[0].components.push(picture)) + const handleCloseimageClick = async (image: ImageComponent) => { + await axios + .delete(`/media/images/${image.data.media_id}`) + .then(() => { + dispatch(getEditorCompetition(id)) + }) + .catch(console.log) - //const handleClosePictureClick = (id: number) => { - // setPictures(pictures.filter((item) => item.id !== id)) //Will not be done like this when api is used - //Ta bort från servern - //Ta bort Media - //Ta bort ImageComponent - //} + await axios + .delete(`/competitions/${id}/slides/${activeSlide?.id}/components/${image.id}`) + .then(() => { + dispatch(getEditorCompetition(id)) + }) + .catch(console.log) + } - //const pictures = [{ id: 'picture1', name: 'Wallgren.png' }] + //const images = [{ id: 'image1', name: 'Wallgren.png' }] - //const [pictures, setPictures] = useState(pictureList) + //const [images, setimages] = useState(imageList) const updateSlideType = async () => { closeSlideTypeDialog() @@ -451,18 +457,16 @@ const SlideSettings: React.FC = () => { <ListItem divider> <ListItemText className={classes.textCenter} primary="Bilder" /> </ListItem> - {pictures && - pictures.map((picture) => ( - <div key={picture.id}> + {images && + images.map((image) => ( + <div key={image.id}> <ListItem divider button> <img - id="temp source, todo: add image source to elements of pictureList" - //src={`http://localhost:5000/static/images/${picture.data.media_id}`} - //{picture.data.media_id} + src={`http://localhost:5000/static/images/thumbnail_${image.data.filename}`} className={classes.importedImage} /> - <ListItemText className={classes.textCenter} /*primary={picture.name}*/ /> - <CloseIcon /*onClick={() => handleClosePictureClick(picture.id)}*/ /> + <ListItemText className={classes.textCenter} primary={image.data.filename} /> + <CloseIcon onClick={() => handleCloseimageClick(image)} /> </ListItem> </div> ))} @@ -477,7 +481,7 @@ const SlideSettings: React.FC = () => { <ListItem button> <img - id="temp source, todo: add image source to elements of pictureList" + id="temp source, todo: add image source to elements of imageList" src="https://i1.wp.com/stickoutmedia.se/wp-content/uploads/2021/01/placeholder-3.png?ssl=1" className={classes.importedImage} /> -- GitLab From 7bbcb4465379deef002d495bcf328c1cb39fc1a6 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Fri, 23 Apr 2021 16:54:35 +0200 Subject: [PATCH 09/13] Autosize added images --- .../components/SlideSettings.tsx | 6 ------ server/app/database/controller/add.py | 21 ++++++++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index 33f96d4c..1d8aad5f 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -140,10 +140,6 @@ const SlideSettings: React.FC = () => { .catch(console.log) } - //const images = [{ id: 'image1', name: 'Wallgren.png' }] - - //const [images, setimages] = useState(imageList) - const updateSlideType = async () => { closeSlideTypeDialog() if (activeSlide) { @@ -253,8 +249,6 @@ const SlideSettings: React.FC = () => { const imageData = { x: 0, y: 0, - w: 400, - h: 400, data: { media_id: media.id, filename: media.filename, diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 1dbcf442..778f28ac 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -2,10 +2,15 @@ This file contains functionality to add data to the database. """ +import os +from PIL import Image +from flask.globals import current_app +from sqlalchemy.orm import relation from sqlalchemy.orm.session import sessionmaker +from app.apis.media import PHOTO_PATH import app.core.http_codes as codes from app.core import db -from app.database.controller import utils +from app.database.controller import get, search, utils from app.database.models import ( Blacklist, City, @@ -98,6 +103,20 @@ def component(type_id, slide_id, data, x=0, y=0, w=0, h=0): provided size and data . """ + if type_id == 2: # 2 is image + item_image = get.one(Media, data["media_id"]) + filename = item_image.filename + path = os.path.join(PHOTO_PATH, filename) + with Image.open(path) as im: + h = im.height + w = im.width + + largest = max(w, h) + if largest > 600: + ratio = 600 / largest + w *= ratio + h *= ratio + return db_add(Component(slide_id, type_id, data, x, y, w, h)) -- GitLab From 306ba16fa45c23d7b603e107163ff3092caa4227 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Fri, 23 Apr 2021 17:12:15 +0200 Subject: [PATCH 10/13] Fix populate --- server/populate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/populate.py b/server/populate.py index 9ca3c95e..69ca46b4 100644 --- a/server/populate.py +++ b/server/populate.py @@ -1,6 +1,5 @@ import random -import app.database.controller as dbc from app import create_app, db from app.database.models import City, QuestionType, Role @@ -100,6 +99,8 @@ if __name__ == "__main__": app, _ = create_app("configmodule.DevelopmentConfig") with app.app_context(): + import app.database.controller as dbc # Media needs app_context to work + db.drop_all() db.create_all() _add_items() -- GitLab From e0085de3e397e3425b1570ce482476d495d2fa35 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Fri, 23 Apr 2021 17:30:30 +0200 Subject: [PATCH 11/13] Fix front-end tests --- .../components/CompetitionSettings.test.tsx | 10 ---------- .../components/ImageComponentDisplay.test.tsx | 8 +++++++- .../components/SettingsPanel.test.tsx | 3 ++- 3 files changed, 9 insertions(+), 12 deletions(-) delete mode 100644 client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx deleted file mode 100644 index a655a30f..00000000 --- a/client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { render } from '@testing-library/react' -import React from 'react' -import { BrowserRouter } from 'react-router-dom' -import ImageComponentDisplay from './ImageComponentDisplay' - -it('renders image component display', () => { - render( - <ImageComponentDisplay component={{ id: 0, x: 0, y: 0, w: 0, h: 0, type: 0, media_id: 0 }} /> - ) -}) diff --git a/client/src/pages/presentationEditor/components/ImageComponentDisplay.test.tsx b/client/src/pages/presentationEditor/components/ImageComponentDisplay.test.tsx index b726023b..9e78f8e1 100644 --- a/client/src/pages/presentationEditor/components/ImageComponentDisplay.test.tsx +++ b/client/src/pages/presentationEditor/components/ImageComponentDisplay.test.tsx @@ -3,5 +3,11 @@ import React from 'react' import ImageComponentDisplay from './ImageComponentDisplay' it('renders competition settings', () => { - render(<ImageComponentDisplay component={{ id: 0, x: 0, y: 0, w: 0, h: 0, media_id: 0, type: 2 }} />) + render( + <ImageComponentDisplay + component={{ id: 0, x: 0, y: 0, w: 0, h: 0, data: { media_id: 0, filename: '' }, type_id: 2 }} + width={0} + height={0} + /> + ) }) diff --git a/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx b/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx index a7a71e25..a17569ec 100644 --- a/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx +++ b/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx @@ -6,6 +6,7 @@ import { BrowserRouter } from 'react-router-dom' import store from '../../../store' import CompetitionSettings from './CompetitionSettings' import SettingsPanel from './SettingsPanel' +import SlideSettings from './SlideSettings' it('renders settings panel', () => { render( @@ -28,5 +29,5 @@ it('renders slide settings tab', () => { const tabs = wrapper.find('.MuiTabs-flexContainer') expect(wrapper.find(CompetitionSettings).length).toEqual(1) tabs.children().at(1).simulate('click') - expect(wrapper.text().includes('2')).toBe(true) //TODO: check that SlideSettings exists + expect(wrapper.find(SlideSettings).length).toEqual(1) }) -- GitLab From 90b977403ee9479377753dfe0b96321b0a5d579e Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Fri, 23 Apr 2021 17:30:51 +0200 Subject: [PATCH 12/13] Fix back-end tests --- server/app/database/controller/add.py | 11 ++++++----- server/populate.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 778f28ac..216160cd 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -3,11 +3,7 @@ This file contains functionality to add data to the database. """ import os -from PIL import Image -from flask.globals import current_app -from sqlalchemy.orm import relation -from sqlalchemy.orm.session import sessionmaker -from app.apis.media import PHOTO_PATH + import app.core.http_codes as codes from app.core import db from app.database.controller import get, search, utils @@ -30,8 +26,12 @@ from app.database.models import ( User, ViewType, ) +from flask.globals import current_app from flask_restx import abort +from PIL import Image from sqlalchemy import exc +from sqlalchemy.orm import relation +from sqlalchemy.orm.session import sessionmaker def db_add(item): @@ -102,6 +102,7 @@ def component(type_id, slide_id, data, x=0, y=0, w=0, h=0): Adds a component to the slide at the specified coordinates with the provided size and data . """ + from app.apis.media import PHOTO_PATH if type_id == 2: # 2 is image item_image = get.one(Media, data["media_id"]) diff --git a/server/populate.py b/server/populate.py index 69ca46b4..703e96c2 100644 --- a/server/populate.py +++ b/server/populate.py @@ -1,5 +1,6 @@ import random +import app.database.controller as dbc from app import create_app, db from app.database.models import City, QuestionType, Role @@ -99,7 +100,6 @@ if __name__ == "__main__": app, _ = create_app("configmodule.DevelopmentConfig") with app.app_context(): - import app.database.controller as dbc # Media needs app_context to work db.drop_all() db.create_all() -- GitLab From 6f53a9b5cca60e706f89a999d396fb4cb216a118 Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Fri, 23 Apr 2021 17:42:26 +0200 Subject: [PATCH 13/13] Add api to paths --- client/src/pages/admin/AdminPage.test.tsx | 2 +- .../admin/competitions/CompetitionManager.test.tsx | 2 +- .../components/SlideSettings.tsx | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/src/pages/admin/AdminPage.test.tsx b/client/src/pages/admin/AdminPage.test.tsx index ab694cee..445373a0 100644 --- a/client/src/pages/admin/AdminPage.test.tsx +++ b/client/src/pages/admin/AdminPage.test.tsx @@ -40,7 +40,7 @@ it('renders admin view', () => { }, } ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => { - if (path === '/misc/cities') return Promise.resolve(cityRes) + if (path === '/api/misc/cities') return Promise.resolve(cityRes) else return Promise.resolve(rolesRes) }) render( diff --git a/client/src/pages/admin/competitions/CompetitionManager.test.tsx b/client/src/pages/admin/competitions/CompetitionManager.test.tsx index f47d8045..7af04abb 100644 --- a/client/src/pages/admin/competitions/CompetitionManager.test.tsx +++ b/client/src/pages/admin/competitions/CompetitionManager.test.tsx @@ -47,7 +47,7 @@ it('renders competition manager', () => { } ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => { - if (path === '/competitions/search') return Promise.resolve(compRes) + if (path === '/api/competitions/search') return Promise.resolve(compRes) else return Promise.resolve(cityRes) }) render( diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index e06f1ce3..ffa95333 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -101,7 +101,7 @@ const SlideSettings: React.FC = () => { if (activeSlide && activeSlide.questions[0]) { await axios .delete( - `/competitions/${id}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}` + `/api/competitions/${id}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}` ) .then(() => { dispatch(getEditorCompetition(id)) @@ -126,14 +126,14 @@ const SlideSettings: React.FC = () => { const handleCloseimageClick = async (image: ImageComponent) => { await axios - .delete(`/media/images/${image.data.media_id}`) + .delete(`/api/media/images/${image.data.media_id}`) .then(() => { dispatch(getEditorCompetition(id)) }) .catch(console.log) await axios - .delete(`/competitions/${id}/slides/${activeSlide?.id}/components/${image.id}`) + .delete(`/api/competitions/${id}/slides/${activeSlide?.id}/components/${image.id}`) .then(() => { dispatch(getEditorCompetition(id)) }) @@ -195,7 +195,7 @@ const SlideSettings: React.FC = () => { console.log('newValue: ' + newValue) await axios .put( - `/competitions/${id}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`, + `/api/competitions/${id}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`, { value: newValue } ) .then(() => { @@ -209,7 +209,7 @@ const SlideSettings: React.FC = () => { if (activeSlide && activeSlide.questions[0]) { await axios .put( - `/competitions/${id}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`, + `/api/competitions/${id}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`, { text: newText } ) .then(() => { @@ -240,7 +240,7 @@ const SlideSettings: React.FC = () => { // Uploads the file to the server and creates a Media object in database // Returns media id return await axios - .post(`/media/images`, formData) + .post(`/api/media/images`, formData) .then((response) => { dispatch(getEditorCompetition(id)) return response.data as Media @@ -260,7 +260,7 @@ const SlideSettings: React.FC = () => { } await axios - .post(`/competitions/${id}/slides/${activeSlide?.id}/components`, imageData) + .post(`/api/competitions/${id}/slides/${activeSlide?.id}/components`, imageData) .then(() => { dispatch(getEditorCompetition(id)) }) -- GitLab