diff --git a/package.json b/package.json
index ebb301b1..ec18eca7 100644
--- a/package.json
+++ b/package.json
@@ -11,11 +11,13 @@
"husky": "^4.3.0",
"next-qrcode": "^2.4.0",
"react": "^16.13.1",
+ "react-color": "2.17.2",
"react-csv": "^2.2.1",
"react-dom": "^16.13.1",
"react-markdown": "^4.3.1",
"react-scripts": "3.4.1",
"react-select": "^3.1.0",
+ "react-sketch-canvas": "^6.2.0",
"react-spinner": "^0.2.7",
"react-spinners": "^0.9.0",
"styled-components": "^5.1.1",
diff --git a/src/assets/icons/colorpicker.svg b/src/assets/icons/colorpicker.svg
new file mode 100644
index 00000000..ea09b4c8
--- /dev/null
+++ b/src/assets/icons/colorpicker.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/src/assets/icons/redo.svg b/src/assets/icons/redo.svg
new file mode 100644
index 00000000..d199a660
--- /dev/null
+++ b/src/assets/icons/redo.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/assets/icons/trashcan.svg b/src/assets/icons/trashcan.svg
new file mode 100644
index 00000000..1c5dceab
--- /dev/null
+++ b/src/assets/icons/trashcan.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/undo.svg b/src/assets/icons/undo.svg
new file mode 100644
index 00000000..d711d493
--- /dev/null
+++ b/src/assets/icons/undo.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/components/ApplicationForm/Skills.js b/src/components/ApplicationForm/Skills.js
index 9da4d9c7..1b0e3334 100644
--- a/src/components/ApplicationForm/Skills.js
+++ b/src/components/ApplicationForm/Skills.js
@@ -6,6 +6,7 @@ import { Select } from '../Input'
import { FormSpacing, SubHeading } from './'
import { CONTRIBUTION_ROLE_OPTIONS, copyText } from '../../utility/Constants'
import styled from 'styled-components'
+import SketchCanvas from '../Input/SketchCanvas'
const QuestionForm = styled.form`
display: flex;
@@ -275,6 +276,24 @@ export default ({ refs, errors, formInputs, onChange, role, handleResume }) => {
customRef={refs['longAnswers2Ref']}
/>
+
+ question 21
+ Draw your favourite character!
+
+ onChange({
+ longAnswers3: val,
+ })
+ }
+ customRef={refs['longAnswers3Ref']}
+ exportAsBase64
+ />
+
>
)
diff --git a/src/components/Input/SketchCanvas.js b/src/components/Input/SketchCanvas.js
new file mode 100644
index 00000000..6c605354
--- /dev/null
+++ b/src/components/Input/SketchCanvas.js
@@ -0,0 +1,235 @@
+/* eslint-disable import/no-anonymous-default-export */
+import React, { useEffect, useRef, useState } from 'react'
+import { ReactSketchCanvas } from 'react-sketch-canvas'
+import { SketchPicker } from 'react-color'
+import trashcanIcon from '../../assets/icons/trashcan.svg'
+import colorpickerIcon from '../../assets/icons/colorpicker.svg'
+import undoIcon from '../../assets/icons/undo.svg'
+import redoIcon from '../../assets/icons/redo.svg'
+import { mergeRefs, useDebounce } from '../../utility/utilities'
+import styled from 'styled-components'
+
+const CanvasContainer = styled.div`
+ background: #f3f3f3;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+`
+
+const ErrorMessage = styled.p`
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ padding: 5px;
+ padding-left: 8px;
+ padding-right: 8px;
+ margin: 0px;
+ background: #ffaaaa;
+ color: red;
+ z-index: 10;
+ box-sizing: border-box;
+`
+
+const Stroke4Button = styled.div`
+ background: #333333;
+ border-radius: 999px;
+ cursor: pointer;
+ width: 8px;
+ height: 8px;
+`
+
+const Stroke6Button = styled.div`
+ background: #333333;
+ border-radius: 999px;
+ cursor: pointer;
+ width: 12px;
+ height: 12px;
+`
+
+const Stroke8Button = styled.div`
+ background: #333333;
+ border-radius: 999px;
+ cursor: pointer;
+ width: 16px;
+ height: 16px;
+`
+
+const CanvasIcon = styled.img`
+ width: 20px;
+ height: 25px;
+ cursor: pointer;
+`
+
+const HistoryIcons = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 5px;
+ align-items: center;
+`
+
+const ColorPickerWrapper = styled.div`
+ position: absolute;
+ z-index: 100;
+ top: 0;
+ right: 0;
+`
+
+const LeftIcons = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 5px;
+`
+
+const ToolBar = styled.div`
+ display: flex;
+ flex-direction: row;
+ padding: 3px;
+ padding-left: 7px;
+ padding-right: 7px;
+ justify-content: space-between;
+ user-select: none;
+`
+/**
+ * @param width number, width of the entire component
+ * @param height number, height of the entire component
+ * @param invalid boolean, indicates dissatisfaction with current value
+ * @param errorMsg string, indicates some error
+ * @param onChange (result: string) => void, triggers with result (SVG or Base64) on change. Has debouncing.
+ * @param customRef a ref directly to the canvas component
+ * @param exportAsBase64 boolean, onChange() will return a base64 string instead of svg if true
+ */
+export default ({ width, height, invalid, errorMsg, onChange, customRef, exportAsBase64 }) => {
+ const [strokeWidth, setStrokeWidth] = useState(4)
+ const [strokeColor, setStrokeColor] = useState('black')
+ const [showPicker, setShowPicker] = useState(false)
+ const canvasRef = useRef()
+
+ const loadedRef = useRef(false)
+
+ useEffect(() => {
+ loadedRef.current = true
+ return () => {
+ loadedRef.current = false
+ }
+ }, [])
+
+ const debounceUpdate = useDebounce(async () => {
+ if (!loadedRef.current) return
+
+ if (exportAsBase64) {
+ const newBase64 = await canvasRef.current.exportImage('jpeg')
+ onChange(newBase64)
+ } else {
+ const newSVG = await canvasRef.current.exportSvg()
+ onChange(newSVG)
+ }
+ }, 500)
+
+ const handleChange = e => {
+ if (!onChange) return
+
+ debounceUpdate()
+ }
+
+ const handleClear = () => {
+ if (canvasRef.current) canvasRef.current.clearCanvas()
+ }
+
+ const handleUndo = () => {
+ if (canvasRef.current) canvasRef.current.undo()
+ }
+
+ const handleRedo = () => {
+ if (canvasRef.current) canvasRef.current.redo()
+ }
+
+ return (
+ {
+ setShowPicker(false)
+ }}
+ >
+ {errorMsg && {errorMsg}}
+
+
+
+
+
+ {
+ e.stopPropagation()
+ setShowPicker(!showPicker)
+ }}
+ />
+ {
+ setStrokeWidth(4)
+ }}
+ />
+ {
+ setStrokeWidth(6)
+ }}
+ />
+ {
+ setStrokeWidth(8)
+ }}
+ />
+
+
+
+
+
+
+
+
+
+ {showPicker && (
+ {
+ e.stopPropagation()
+ }}
+ >
+ {
+ setStrokeColor(e.hex)
+ }}
+ />
+
+ )}
+
+
+ )
+}
diff --git a/src/utility/utilities.js b/src/utility/utilities.js
index fc8a7646..b5af2cb9 100644
--- a/src/utility/utilities.js
+++ b/src/utility/utilities.js
@@ -144,3 +144,11 @@ export const cutString = (string, maxLength) => {
}
return `${string.substring(0, cut)}...`
}
+
+export const mergeRefs = (...refs) => {
+ return node => {
+ for (const ref of refs) {
+ ref.current = node
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 0db73742..44fb5282 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1303,6 +1303,11 @@
dependencies:
"@hapi/hoek" "^8.3.0"
+"@icons/material@^0.2.4":
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
+ integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==
+
"@jest/console@^24.7.1", "@jest/console@^24.9.0":
version "24.9.0"
resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0"
@@ -6406,6 +6411,11 @@ lodash.uniq@^4.5.0:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+lodash@^4.0.1:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
lodash@^4.17.19:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
@@ -6479,6 +6489,11 @@ markdown-escapes@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
+material-colors@^1.2.1:
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
+ integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
+
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -8133,6 +8148,15 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.4"
+prop-types@^15.5.10:
+ version "15.8.1"
+ resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
+ integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
+ dependencies:
+ loose-envify "^1.4.0"
+ object-assign "^4.1.1"
+ react-is "^16.13.1"
+
prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
@@ -8307,6 +8331,18 @@ react-app-polyfill@^1.0.6:
regenerator-runtime "^0.13.3"
whatwg-fetch "^3.0.0"
+react-color@2.17.2:
+ version "2.17.2"
+ resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.17.2.tgz#ef002cafd5ae8daf3d374b85bb44ac9d36785c0a"
+ integrity sha512-TpT/D3wEQmyCOcIXrUgKOvEXpJtMylEx5yzw0laGP30mfC5yO4CoUsiYvfPiUbbkFjMGurPJjkKOkkOfBBIjFQ==
+ dependencies:
+ "@icons/material" "^0.2.4"
+ lodash "^4.17.11"
+ material-colors "^1.2.1"
+ prop-types "^15.5.10"
+ reactcss "^1.2.0"
+ tinycolor2 "^1.4.1"
+
react-csv@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-csv/-/react-csv-2.2.1.tgz#2ec723dd3ce8069269bffb3a95d968302fc87740"
@@ -8361,7 +8397,7 @@ react-input-autosize@^2.2.2:
dependencies:
prop-types "^15.5.8"
-react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
+react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -8451,6 +8487,11 @@ react-select@^3.1.0:
react-input-autosize "^2.2.2"
react-transition-group "^4.3.0"
+react-sketch-canvas@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/react-sketch-canvas/-/react-sketch-canvas-6.2.0.tgz#4aa1f0b85012f37b8b2ae746a412ab9424badfa9"
+ integrity sha512-Z56RQN0viV9W3Y+DpZfQMi2lf3pO+UB4l+cMwnx7j7pQ9WHkQwN+qZKVFGANgpjXQMyY42NODF/DXjuKPCMf1w==
+
react-spinner@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/react-spinner/-/react-spinner-0.2.7.tgz#ea3ca3375dd7a54edbb5cc01d17496a2e2fc14db"
@@ -8481,6 +8522,13 @@ react@^16.13.1:
object-assign "^4.1.1"
prop-types "^15.6.2"
+reactcss@^1.2.0:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
+ integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==
+ dependencies:
+ lodash "^4.0.1"
+
read-pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
@@ -9694,6 +9742,11 @@ timsort@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
+tinycolor2@^1.4.1:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
+ integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==
+
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"