From 2757cc969ec497d8ce0ceb6e0d2208d2c5f6b67c Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Mon, 12 Apr 2021 15:31:51 +0000
Subject: [PATCH] Implement drag and resize

---
 client/package-lock.json                      | 58 +++++++++++++++++
 client/package.json                           |  2 +
 client/src/enum/ComponentTypes.ts             |  5 ++
 client/src/index.css                          |  9 +++
 client/src/interfaces/Components.ts           |  9 +++
 .../PresentationEditorPage.tsx                | 15 +++--
 .../components/CheckboxComponent.tsx          | 31 +++++++++
 .../components/CompetitionSettings.tsx        |  4 +-
 .../components/ImageComponentDisplay.tsx      | 44 +++++++++++++
 .../components/SlideEditor.tsx                | 39 ++++++++++++
 .../components/SlideSettings.tsx              |  4 +-
 .../components/TextComponentDisplay.tsx       | 63 +++++++++++++++++++
 .../presentationEditor/components/styled.tsx  |  8 +++
 .../src/pages/presentationEditor/styled.tsx   |  4 ++
 client/src/pages/views/styled.tsx             |  1 +
 15 files changed, 286 insertions(+), 10 deletions(-)
 create mode 100644 client/src/enum/ComponentTypes.ts
 create mode 100644 client/src/interfaces/Components.ts
 create mode 100644 client/src/pages/presentationEditor/components/CheckboxComponent.tsx
 create mode 100644 client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx
 create mode 100644 client/src/pages/presentationEditor/components/SlideEditor.tsx
 create mode 100644 client/src/pages/presentationEditor/components/TextComponentDisplay.tsx

diff --git a/client/package-lock.json b/client/package-lock.json
index 133d1ca2..b08413fa 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -2262,6 +2262,15 @@
         "@babel/runtime": "^7.12.5"
       }
     },
+    "@tinymce/tinymce-react": {
+      "version": "3.12.2",
+      "resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-3.12.2.tgz",
+      "integrity": "sha512-M6YQ9e+9rpxrZDOeNPmjgroEfooEKiMVuI4I3+xQtMX1hQQ/t9pGE9nUdS0faZDvqhlc8B/w12GJQNU5ekPo4g==",
+      "requires": {
+        "prop-types": "^15.6.2",
+        "tinymce": "^5.7.1"
+      }
+    },
     "@types/anymatch": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz",
@@ -4635,6 +4644,11 @@
         }
       }
     },
+    "classnames": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+      "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+    },
     "clean-css": {
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
@@ -7211,6 +7225,11 @@
       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
     },
+    "fast-memoize": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz",
+      "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw=="
+    },
     "fastq": {
       "version": "1.10.1",
       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz",
@@ -13382,6 +13401,14 @@
         }
       }
     },
+    "re-resizable": {
+      "version": "6.9.0",
+      "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.0.tgz",
+      "integrity": "sha512-3cUDG81ylyqI0Pdgle/RHwwRYq0ORZzsUaySOCO8IbEtNyaRtrIHYm/jMQ5pjcNiKCxR3vsSymIQZHwJq4gg2Q==",
+      "requires": {
+        "fast-memoize": "^2.5.1"
+      }
+    },
     "react": {
       "version": "17.0.1",
       "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz",
@@ -13525,6 +13552,15 @@
         "scheduler": "^0.20.1"
       }
     },
+    "react-draggable": {
+      "version": "4.4.3",
+      "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.3.tgz",
+      "integrity": "sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==",
+      "requires": {
+        "classnames": "^2.2.5",
+        "prop-types": "^15.6.0"
+      }
+    },
     "react-error-overlay": {
       "version": "6.0.9",
       "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",
@@ -13557,6 +13593,23 @@
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
       "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg=="
     },
+    "react-rnd": {
+      "version": "10.2.4",
+      "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.2.4.tgz",
+      "integrity": "sha512-wseACIsxa1wuZz9XatO3/JAZR748Sddehh0NtJz1Yj3X5BQm5pwRShiadfnWrUajJATurHbN0NVTUn+jEkHkPw==",
+      "requires": {
+        "re-resizable": "6.9.0",
+        "react-draggable": "4.4.3",
+        "tslib": "2.0.3"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
+          "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
+        }
+      }
+    },
     "react-router": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
@@ -15912,6 +15965,11 @@
       "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
       "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
     },
+    "tinymce": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.7.1.tgz",
+      "integrity": "sha512-1gY8RClc734srSlkYwY0MQzmkS1j73PuPC+nYtNtrrQVPY9VNcZ4bOiRwzTbdjPPD8GOtv6BAk8Ww/H2RiqKpA=="
+    },
     "tmpl": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
diff --git a/client/package.json b/client/package.json
index 6e4d3573..f41350a6 100644
--- a/client/package.json
+++ b/client/package.json
@@ -10,6 +10,7 @@
     "@testing-library/jest-dom": "^5.11.9",
     "@testing-library/react": "^11.2.5",
     "@testing-library/user-event": "^12.6.3",
+    "@tinymce/tinymce-react": "^3.12.2",
     "@types/jest": "^26.0.20",
     "@types/node": "^12.19.16",
     "@types/react": "^17.0.1",
@@ -21,6 +22,7 @@
     "react-axios": "^2.0.4",
     "react-dom": "^17.0.1",
     "react-redux": "^7.2.2",
+    "react-rnd": "^10.2.4",
     "react-router-dom": "^5.2.0",
     "react-scripts": "4.0.2",
     "redux": "^4.0.5",
diff --git a/client/src/enum/ComponentTypes.ts b/client/src/enum/ComponentTypes.ts
new file mode 100644
index 00000000..c8d194c0
--- /dev/null
+++ b/client/src/enum/ComponentTypes.ts
@@ -0,0 +1,5 @@
+export enum ComponentTypes {
+  Text,
+  Checkbox,
+  Image,
+}
diff --git a/client/src/index.css b/client/src/index.css
index 80bfba85..6b920fab 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -7,6 +7,15 @@ body {
   -moz-osx-font-smoothing: grayscale;
 }
 
+.tox-edit-area__iframe,
+.tox-edit-area {
+  background: transparent !important;
+}
+
+.tox-notifications-container {
+  display: none;
+}
+
 html,
 #root {
   height: 100%;
diff --git a/client/src/interfaces/Components.ts b/client/src/interfaces/Components.ts
new file mode 100644
index 00000000..864b987d
--- /dev/null
+++ b/client/src/interfaces/Components.ts
@@ -0,0 +1,9 @@
+export interface Position {
+  x: number
+  y: number
+}
+
+export interface Size {
+  w: number
+  h: number
+}
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index 5a0dbc99..a5209a16 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -7,8 +7,10 @@ import ListItemText from '@material-ui/core/ListItemText'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import React from 'react'
 import { useParams } from 'react-router-dom'
+import { Content } from '../views/styled'
 import SettingsPanel from './components/SettingsPanel'
-import { SlideListItem, ToolBarContainer, ViewButton, ViewButtonGroup } from './styled'
+import SlideEditor from './components/SlideEditor'
+import { PresentationEditorContainer, SlideListItem, ToolBarContainer, ViewButton, ViewButtonGroup } from './styled'
 
 function createSlide(name: string) {
   return { name }
@@ -28,9 +30,6 @@ const rightDrawerWidth = 390
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
-    root: {
-      display: 'flex',
-    },
     appBar: {
       width: `calc(100% - ${rightDrawerWidth}px)`,
       marginLeft: leftDrawerWidth,
@@ -71,7 +70,7 @@ const PresentationEditorPage: React.FC = () => {
   const classes = useStyles()
   const params: CompetitionParams = useParams()
   return (
-    <div className={classes.root}>
+    <PresentationEditorContainer>
       <CssBaseline />
       <AppBar position="fixed" className={classes.appBar}>
         <ToolBarContainer>
@@ -120,7 +119,11 @@ const PresentationEditorPage: React.FC = () => {
       >
         <SettingsPanel></SettingsPanel>
       </Drawer>
-    </div>
+
+      <Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}>
+        <SlideEditor />
+      </Content>
+    </PresentationEditorContainer>
   )
 }
 
diff --git a/client/src/pages/presentationEditor/components/CheckboxComponent.tsx b/client/src/pages/presentationEditor/components/CheckboxComponent.tsx
new file mode 100644
index 00000000..cb264712
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/CheckboxComponent.tsx
@@ -0,0 +1,31 @@
+import { Checkbox } from '@material-ui/core'
+import React, { useState } from 'react'
+import { Rnd } from 'react-rnd'
+import { Component } from '../../../interfaces/ApiModels'
+import { Position } from '../../../interfaces/Components'
+
+type CheckboxComponentProps = {
+  component: Component
+}
+
+const CheckboxComponent = ({ component }: CheckboxComponentProps) => {
+  const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y })
+  return (
+    <Rnd
+      bounds="parent"
+      onDragStop={(e, d) => {
+        setCurrentPos({ x: d.x, y: d.y })
+      }}
+      position={{ x: currentPos.x, y: currentPos.y }}
+    >
+      <Checkbox
+        disableRipple
+        style={{
+          transform: 'scale(3)',
+        }}
+      />
+    </Rnd>
+  )
+}
+
+export default CheckboxComponent
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
index a9a1d767..7446c6d1 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -48,9 +48,9 @@ const CompetitionSettings: React.FC = () => {
   return (
     <div className={classes.textInputContainer}>
       <form noValidate autoComplete="off">
-        <TextField className={classes.textInput} id="outlined-basic" label="Tävlingsnamn" variant="outlined" />
+        <TextField className={classes.textInput} label="Tävlingsnamn" variant="outlined" />
         <Divider />
-        <TextField className={classes.textInput} id="outlined-basic" label="Stad" variant="outlined" />
+        <TextField className={classes.textInput} label="Stad" variant="outlined" />
       </form>
 
       <List>
diff --git a/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx b/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx
new file mode 100644
index 00000000..cffba9e5
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/ImageComponentDisplay.tsx
@@ -0,0 +1,44 @@
+import React, { useState } from 'react'
+import { Rnd } from 'react-rnd'
+import { ImageComponent } from '../../../interfaces/ApiModels'
+import { Position, Size } from '../../../interfaces/Components'
+
+type ImageComponentProps = {
+  component: ImageComponent
+}
+
+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 })
+  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>
+  )
+}
+
+export default ImageComponentDisplay
diff --git a/client/src/pages/presentationEditor/components/SlideEditor.tsx b/client/src/pages/presentationEditor/components/SlideEditor.tsx
new file mode 100644
index 00000000..22a73ef3
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/SlideEditor.tsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import { ComponentTypes } from '../../../enum/ComponentTypes'
+import CheckboxComponent from './CheckboxComponent'
+import ImageComponentDisplay from './ImageComponentDisplay'
+import { SlideEditorContainer } from './styled'
+import TextComponentDisplay from './TextComponentDisplay'
+
+const SlideEditor: React.FC = () => {
+  // const components =  useAppSelector(state => state.editor.slide.components) // get the current RichSlide
+  const components: any[] = [
+    { id: 0, x: 15, y: 150, w: 200, h: 300, type: ComponentTypes.Checkbox },
+    { id: 1, x: 15, y: 250, w: 200, h: 300, type: ComponentTypes.Checkbox },
+    { id: 2, x: 15, y: 350, w: 200, h: 300, type: ComponentTypes.Checkbox },
+    { id: 3, x: 300, y: 500, w: 100, h: 300, type: ComponentTypes.Text, text: 'text component', font: 'arial' },
+    { id: 4, x: 250, y: 100, w: 200, h: 300, type: ComponentTypes.Image },
+    { id: 5, x: 350, y: 100, w: 200, h: 300, type: ComponentTypes.Image },
+  ]
+  return (
+    <SlideEditorContainer>
+      {components.map((component) => {
+        switch (component.type) {
+          case ComponentTypes.Checkbox:
+            return <CheckboxComponent key={component.id} component={component} />
+            break
+          case ComponentTypes.Text:
+            return <TextComponentDisplay key={component.id} component={component} />
+            break
+          case ComponentTypes.Image:
+            return <ImageComponentDisplay key={component.id} component={component} />
+            break
+          default:
+            break
+        }
+      })}
+    </SlideEditorContainer>
+  )
+}
+
+export default SlideEditor
diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx
index 936f3d8b..4b094021 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx
@@ -137,7 +137,7 @@ const SlideSettings: React.FC = () => {
         {answers.map((answer) => (
           <div key={answer.id}>
             <ListItem divider>
-              <TextField className={classes.textInput} id="outlined-basic" label={answer.name} variant="outlined" />
+              <TextField className={classes.textInput} label={answer.name} variant="outlined" />
               <Checkbox color="default" />
               <CloseIcon className={classes.clickableIcon} onClick={() => handleCloseAnswerClick(answer.id)} />
             </ListItem>
@@ -155,7 +155,7 @@ const SlideSettings: React.FC = () => {
         {texts.map((text) => (
           <div key={text.id}>
             <ListItem divider>
-              <TextField className={classes.textInput} id="outlined-basic" label={text.name} variant="outlined" />
+              <TextField className={classes.textInput} label={text.name} variant="outlined" />
               <MoreHorizOutlinedIcon className={classes.clickableIcon} />
               <CloseIcon className={classes.clickableIcon} onClick={() => handleCloseTextClick(text.id)} />
             </ListItem>
diff --git a/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx
new file mode 100644
index 00000000..30426b9d
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/TextComponentDisplay.tsx
@@ -0,0 +1,63 @@
+import { Editor } from '@tinymce/tinymce-react'
+import React, { useState } from 'react'
+import { Rnd } from 'react-rnd'
+import { TextComponent } from '../../../interfaces/ApiModels'
+import { Position, Size } from '../../../interfaces/Components'
+
+type ImageComponentProps = {
+  component: TextComponent
+}
+
+const TextComponentDisplay = ({ 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 handleEditorChange = (e: any) => {
+    console.log('Content was updated:', e.target.getContent())
+    //TODO: axios.post
+  }
+  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('skickar till server')
+      }}
+    >
+      <div style={{ height: '100%', width: '100%' }}>
+        <Editor
+          initialValue={component.text}
+          init={{
+            body_class: 'mceBlackBody',
+            height: '100%',
+            menubar: false,
+            plugins: [
+              'advlist autolink lists link image charmap print preview anchor',
+              'searchreplace visualblocks code fullscreen',
+              'insertdatetime media table paste code help wordcount',
+            ],
+            toolbar:
+              'undo redo | formatselect | fontselect | bold italic backcolor | \
+             alignleft aligncenter alignright alignjustify | \
+             bullist numlist outdent indent | removeformat | help',
+          }}
+          onChange={handleEditorChange}
+        />
+      </div>
+    </Rnd>
+  )
+}
+
+export default TextComponentDisplay
diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx
index 81459a54..62239c5b 100644
--- a/client/src/pages/presentationEditor/components/styled.tsx
+++ b/client/src/pages/presentationEditor/components/styled.tsx
@@ -5,3 +5,11 @@ export const SettingsTab = styled(Tab)`
   height: 64px;
   min-width: 195px;
 `
+
+export const SlideEditorContainer = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+`
diff --git a/client/src/pages/presentationEditor/styled.tsx b/client/src/pages/presentationEditor/styled.tsx
index 6ba71760..462acf2e 100644
--- a/client/src/pages/presentationEditor/styled.tsx
+++ b/client/src/pages/presentationEditor/styled.tsx
@@ -19,3 +19,7 @@ export const SlideListItem = styled(ListItem)`
   text-align: center;
   height: 60px;
 `
+
+export const PresentationEditorContainer = styled.div`
+  height: 100%;
+`
diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx
index 261727b9..d4a9cf9e 100644
--- a/client/src/pages/views/styled.tsx
+++ b/client/src/pages/views/styled.tsx
@@ -87,4 +87,5 @@ export const Content = styled.div<ContentProps>`
   margin-left: ${(props) => (props ? props.leftDrawerWidth : 0)}px;
   margin-right: ${(props) => (props ? props.rightDrawerWidth : 0)}px;
   width: calc(100% - ${(props) => (props ? props.leftDrawerWidth + props.rightDrawerWidth : 0)}px);
+  height: calc(100% - 64px);
 `
-- 
GitLab