From 6dec1e5a65188fabf35b3b18cd974d036999edb4 Mon Sep 17 00:00:00 2001 From: Nicolaus Gozali Date: Sat, 2 Oct 2021 20:37:30 +1000 Subject: [PATCH 01/23] chore: install tutorial lib --- fableous-fe/package.json | 1 + fableous-fe/yarn.lock | 91 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/fableous-fe/package.json b/fableous-fe/package.json index 0245e6a1..52ae55e0 100644 --- a/fableous-fe/package.json +++ b/fableous-fe/package.json @@ -23,6 +23,7 @@ "react": "^17.0.2", "react-canvas-confetti": "^1.2.1", "react-dom": "^17.0.2", + "react-joyride": "^2.3.1", "react-resize-detector": "^6.7.6", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", diff --git a/fableous-fe/yarn.lock b/fableous-fe/yarn.lock index d590e707..1c108cd3 100644 --- a/fableous-fe/yarn.lock +++ b/fableous-fe/yarn.lock @@ -4335,6 +4335,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-diff@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26" + integrity sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg== + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -5294,6 +5299,11 @@ execa@^4.0.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +exenv@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -6659,6 +6669,11 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3: resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU= +is-lite@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/is-lite/-/is-lite-0.8.1.tgz#a9bd03c90ea723d450c78c991b84f78e7e3126f9" + integrity sha512-ekSwuewzOmwFnzzAOWuA5fRFPqOeTrLIL3GWT7hdVVi+oLuD+Rau8gCmkb94vH5hjXc1Q/CfIW/y/td1RrNQIg== + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -8202,6 +8217,16 @@ neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +nested-property@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/nested-property/-/nested-property-1.0.1.tgz#2001105b5c69413411b876bba9b86f4316af613f" + integrity sha512-BnBBoo/8bBNRdAnJc7+m79oWk7dXwW1+vCesaEQhfDGVwXGLMvmI4NwYgLTW94R/x+R2s/yr2g/hB/4w/YSAvA== + +nested-property@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/nested-property/-/nested-property-4.0.0.tgz#a67b5a31991e701e03cdbaa6453bc5b1011bb88d" + integrity sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA== + next-tick@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -8897,6 +8922,11 @@ popper.js@1.16.1-lts: resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== +popper.js@^1.16.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== + portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -9995,7 +10025,19 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: +react-floater@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/react-floater/-/react-floater-0.7.3.tgz#f57947960682586866ec21540e73c9049ca9f787" + integrity sha512-d1wAEph+xRxQ0RJ3woMmYLlZHTaCIsja7Bv6JNo2ezsVUgdMan4CxOR4Do4/xgpmRFfsQMdlygexLAZZypWirw== + dependencies: + deepmerge "^4.2.2" + exenv "^1.2.2" + is-lite "^0.8.1" + popper.js "^1.16.0" + react-proptype-conditional-require "^1.0.4" + tree-changes "^0.5.1" + +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -10010,6 +10052,27 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +react-joyride@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/react-joyride/-/react-joyride-2.3.1.tgz#de3c8e8bfd6b58f62e7c6e8b38467e807ef8f90f" + integrity sha512-MmyhECU3V+4kZAJrcDPPXcXxaoTpwc7g+E7Cq6QZ5IqJZrWYSVvpVCfudQcdcf6BsNbgawRhvCvbQyeWoPtNig== + dependencies: + deep-diff "^1.0.2" + deepmerge "^4.2.2" + exenv "^1.2.2" + is-lite "^0.8.1" + nested-property "^4.0.0" + react-floater "^0.7.3" + react-is "^16.13.1" + scroll "^3.0.1" + scrollparent "^2.0.1" + tree-changes "^0.7.1" + +react-proptype-conditional-require@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz#69c2d5741e6df5e08f230f36bbc2944ee1222555" + integrity sha1-acLVdB5t9eCPIw82u8KUTuEiJVU= + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -10704,6 +10767,16 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +scroll@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scroll/-/scroll-3.0.1.tgz#d5afb59fb3592ee3df31c89743e78b39e4cd8a26" + integrity sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg== + +scrollparent@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/scrollparent/-/scrollparent-2.0.1.tgz#715d5b9cc57760fb22bdccc3befb5bfe06b1a317" + integrity sha1-cV1bnMV3YPsivczDvvtb/gaxoxc= + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -11710,6 +11783,22 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +tree-changes@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/tree-changes/-/tree-changes-0.5.1.tgz#e31cc8a0f56c8c401f0a88243d9165dbea4f570c" + integrity sha512-O873xzV2xRZ6N059Mn06QzmGKEE21LlvIPbsk2G+GS9ZX5OCur6PIwuuh0rWpAPvLWQZPj0XObyG27zZyLHUzw== + dependencies: + deep-diff "^1.0.2" + nested-property "1.0.1" + +tree-changes@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/tree-changes/-/tree-changes-0.7.1.tgz#fa8810cbe417e80b9a42c4b018f934c7ad8fa156" + integrity sha512-sPIt8PKDi0OQTglr7lsetcB9DU19Ls/ZgFSjFvK6DWJGisAn4sOxtjpmQfuqjexQE4UU9U53LNmataL1kRJ3Uw== + dependencies: + fast-deep-equal "^3.1.3" + is-lite "^0.8.1" + tryer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" From b1cc75a1b109429c4b5ecb6d1c307de85cfc5ddf Mon Sep 17 00:00:00 2001 From: Nicolaus Gozali Date: Sat, 2 Oct 2021 20:37:58 +1000 Subject: [PATCH 02/23] feat: add id to canvas toolbar tools --- .../src/components/canvas/CanvasToolbar.tsx | 26 ++++++++++--------- .../components/canvas/canvasToolbarIconId.ts | 11 ++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 fableous-fe/src/components/canvas/canvasToolbarIconId.ts diff --git a/fableous-fe/src/components/canvas/CanvasToolbar.tsx b/fableous-fe/src/components/canvas/CanvasToolbar.tsx index 7f8414df..731b7091 100644 --- a/fableous-fe/src/components/canvas/CanvasToolbar.tsx +++ b/fableous-fe/src/components/canvas/CanvasToolbar.tsx @@ -13,6 +13,7 @@ import { ControllerRole, ToolMode } from "../../constant"; import { ImperativeCanvasRef } from "./data"; import BrushWidthIcon from "./BrushWidthIcon"; import CanvasToolbarTooltip from "./CanvasToolbarTooltip"; +import CanvasToolbarIconId from "./canvasToolbarIconId"; interface CanvasToolbarProps { role: ControllerRole; @@ -138,6 +139,7 @@ const CanvasToolbar = forwardRef( } > { if (toolColor === ERASE_COLOR) { @@ -166,6 +168,7 @@ const CanvasToolbar = forwardRef( { setToolColorRememberPrev(ERASE_COLOR); setToolMode(ToolMode.Paint); @@ -179,6 +182,7 @@ const CanvasToolbar = forwardRef( { if (toolColor === ERASE_COLOR) { setToolColor(prevColor); @@ -222,6 +226,7 @@ const CanvasToolbar = forwardRef( } > setIsColorPickerOpen((prev) => !prev)} color="primary" className="relative" @@ -237,17 +242,12 @@ const CanvasToolbar = forwardRef( /> - - - )} {role === ControllerRole.Story && ( <> setToolMode(ToolMode.Text)} color={toolMode === ToolMode.Text ? "secondary" : "primary"} > @@ -263,6 +263,7 @@ const CanvasToolbar = forwardRef( } > { setToolMode(ToolMode.Audio); setIsRecordingAudio((prev) => { @@ -283,14 +284,15 @@ const CanvasToolbar = forwardRef( )} - - - )} + + + diff --git a/fableous-fe/src/components/canvas/canvasToolbarIconId.ts b/fableous-fe/src/components/canvas/canvasToolbarIconId.ts new file mode 100644 index 00000000..38012303 --- /dev/null +++ b/fableous-fe/src/components/canvas/canvasToolbarIconId.ts @@ -0,0 +1,11 @@ +enum CanvasToolbarIconId { + Brush = "canvasToolbarIcon-brush", + Erase = "canvasToolbarIcon-erase", + Fill = "canvasToolbarIcon-fill", + Palette = "canvasToolbarIcon-palette", + Text = "canvasToolbarIcon-text", + Audio = "canvasToolbarIcon-audio", + Undo = "canvasToolbarIcon-undo", +} + +export default CanvasToolbarIconId; From e9130796cf9ac60b7ccd49c33bab5ccd2cc201b0 Mon Sep 17 00:00:00 2001 From: Nicolaus Gozali Date: Sat, 2 Oct 2021 20:40:13 +1000 Subject: [PATCH 03/23] feat: setup tutorial for drawing role --- .../src/containers/ControllerCanvasPage.tsx | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/fableous-fe/src/containers/ControllerCanvasPage.tsx b/fableous-fe/src/containers/ControllerCanvasPage.tsx index f52fd14e..8353fc56 100644 --- a/fableous-fe/src/containers/ControllerCanvasPage.tsx +++ b/fableous-fe/src/containers/ControllerCanvasPage.tsx @@ -20,6 +20,7 @@ import useAxios from "axios-hooks"; import * as yup from "yup"; import { Formik, FormikHelpers } from "formik"; import { useSnackbar } from "notistack"; +import Joyride, { CallBackProps, STATUS } from "react-joyride"; import Canvas from "../components/canvas/Canvas"; import { restAPI, wsAPI } from "../api"; import { @@ -50,6 +51,7 @@ import { ASPECT_RATIO, SCALE } from "../components/canvas/constants"; import useContainRatio from "../hooks/useContainRatio"; import ChipRow from "../components/ChipRow"; import { colors } from "../colors"; +import CanvasToolbarIconId from "../components/canvas/canvasToolbarIconId"; enum ControllerState { JoinForm = "JOIN_FORM", @@ -85,6 +87,7 @@ export default function ControllerCanvasPage() { const [toolColor, setToolColor] = useState("#000000ff"); const [toolMode, setToolMode] = useState(ToolMode.None); const [toolWidth, setToolWidth] = useState(8 * SCALE); + const [isTutorialRunning, setIsTutorialRunning] = useState(false); const canvasContainerRef = useRef( document.createElement("div") ); @@ -226,6 +229,42 @@ export default function ControllerCanvasPage() { setIsDone(true); }; + const drawingTutorialSteps = [ + { + target: `#${CanvasToolbarIconId.Brush}`, + content: "Use brush to draw", + disableBeacon: true, + }, + { + target: `#${CanvasToolbarIconId.Erase}`, + content: "Use eraser to erase", + disableBeacon: true, + }, + { + target: `#${CanvasToolbarIconId.Fill}`, + content: "Use bucket to fill with selected colour", + disableBeacon: true, + }, + { + target: `#${CanvasToolbarIconId.Palette}`, + content: "Use palette to choose a colour", + disableBeacon: true, + }, + { + target: `#${CanvasToolbarIconId.Undo}`, + content: "Use undo to undo a recent action", + disableBeacon: true, + }, + ]; + const handleJoyrideCallback = (data: CallBackProps) => { + const { status } = data; + const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; + + if (finishedStatuses.includes(status)) { + setIsTutorialRunning(false); + } + }; + // setup event listeners on ws connection useEffect(() => { if (!wsConn) { @@ -292,6 +331,20 @@ export default function ControllerCanvasPage() { container className={`grid flex-col flex-1 relative ${classes.disableMobileHoldInteraction}`} > +
+ {controllerState === ControllerState.JoinForm && ( From 9fe86cf9c3ab4c294915cb398ee555f9aa1153ee Mon Sep 17 00:00:00 2001 From: Nicolaus Gozali Date: Sat, 2 Oct 2021 21:35:19 +1000 Subject: [PATCH 04/23] feat: show tutorial button in navbar --- fableous-fe/src/App.tsx | 17 +++++--- .../src/components/AdditionalNavProvider.tsx | 43 +++++++++++++++++++ fableous-fe/src/components/Navbar.tsx | 13 +++++- .../src/containers/ControllerCanvasPage.tsx | 20 +++++++++ 4 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 fableous-fe/src/components/AdditionalNavProvider.tsx diff --git a/fableous-fe/src/App.tsx b/fableous-fe/src/App.tsx index 1ced34f2..e0dd0b46 100644 --- a/fableous-fe/src/App.tsx +++ b/fableous-fe/src/App.tsx @@ -12,6 +12,7 @@ import Navbar from "./components/Navbar"; import Routes from "./Routes"; import InjectAxiosRespInterceptor from "./components/InjectAxiosRespInterceptor"; import { colors } from "./colors"; +import AdditionalNavProvider from "./components/AdditionalNavProvider"; // generated background from https://www.svgbackgrounds.com/ const useStyles = makeStyles({ @@ -136,13 +137,15 @@ export default function App() { - - - - - - - + + + + + + + + + diff --git a/fableous-fe/src/components/AdditionalNavProvider.tsx b/fableous-fe/src/components/AdditionalNavProvider.tsx new file mode 100644 index 00000000..5cac625b --- /dev/null +++ b/fableous-fe/src/components/AdditionalNavProvider.tsx @@ -0,0 +1,43 @@ +import { + createContext, + ReactNode, + useCallback, + useContext, + useState, +} from "react"; + +export interface NavType { + icon: string; + label: string; + onClickHandler: () => void; + disabled: boolean; +} + +export type AdditionalNavContextType = [ + navs: NavType[], + setNavs: (navs: NavType[]) => void, + clearNavs: () => void +]; + +export const AdditionalNavContext = createContext([ + [], + (_) => {}, + () => {}, +]); + +export const useAdditionalNav = () => useContext(AdditionalNavContext); + +export default function AdditionalNavProvider(props: { children: ReactNode }) { + const [navs, setNavs] = useState([]); + const { children } = props; + + const clearNavs = useCallback(() => { + setNavs([]); + }, [setNavs]); + + return ( + + {children} + + ); +} diff --git a/fableous-fe/src/components/Navbar.tsx b/fableous-fe/src/components/Navbar.tsx index fe95483b..ef13c7b5 100644 --- a/fableous-fe/src/components/Navbar.tsx +++ b/fableous-fe/src/components/Navbar.tsx @@ -7,6 +7,7 @@ import { Link, useHistory } from "react-router-dom"; import { useContext } from "react"; import { AuthContext } from "./AuthProvider"; +import { useAdditionalNav } from "./AdditionalNavProvider"; const useStyles = makeStyles(() => ({ home: { @@ -21,6 +22,7 @@ export default function Navbar() { clearToken(); history.push("/"); }; + const [navs] = useAdditionalNav(); const classes = useStyles(); return ( @@ -32,6 +34,16 @@ export default function Navbar() {
{/* spacer */} + {navs.map(({ icon, label, onClickHandler }) => ( + + ))} {isAuthenticated ? ( <> - diff --git a/fableous-fe/src/components/canvas/Canvas.tsx b/fableous-fe/src/components/canvas/Canvas.tsx index 553395f0..a4b023ad 100644 --- a/fableous-fe/src/components/canvas/Canvas.tsx +++ b/fableous-fe/src/components/canvas/Canvas.tsx @@ -55,6 +55,7 @@ interface CanvasProps { setToolMode?: React.Dispatch>; toolColor?: string; toolWidth?: number; + rootId?: string | undefined; } const defaultProps = { @@ -66,6 +67,7 @@ const defaultProps = { toolMode: ToolMode.None, setToolMode: () => {}, toolWidth: 8 * SCALE, + rootId: undefined, }; interface SimplePointerEventData { @@ -99,6 +101,7 @@ const Canvas = forwardRef( toolColor = defaultProps.toolColor, toolWidth = defaultProps.toolWidth, wsConn, + rootId, } = props; // useImperativeHandle of type ImperativeCanvasRef defined at bottom const canvasRef = useRef( @@ -911,6 +914,7 @@ const Canvas = forwardRef( return (
{ @@ -335,8 +375,11 @@ export default function ControllerCanvasPage() { { icon: "help", label: "Tutorial", - onClickHandler: () => setIsTutorialRunning(true), - disabled: isTutorialRunning, + buttonProps: { + id: navbarTutorialButtonId, + onClick: () => setIsTutorialRunning(true), + disabled: isTutorialRunning, + }, }, ]); } @@ -358,6 +401,7 @@ export default function ControllerCanvasPage() { scrollToFirstStep showProgress showSkipButton + disableOverlayClose steps={drawingTutorialSteps} styles={{ options: { @@ -382,12 +426,6 @@ export default function ControllerCanvasPage() { }[controllerState] } - {controllerState === ControllerState.JoinForm && ( @@ -584,6 +622,9 @@ export default function ControllerCanvasPage() { ,
@@ -656,6 +697,7 @@ export default function ControllerCanvasPage() { > Date: Sun, 3 Oct 2021 13:52:35 +1000 Subject: [PATCH 06/23] feat: auto start tutorial if not recently done --- fableous-fe/src/api.ts | 6 ++-- fableous-fe/src/components/AuthProvider.tsx | 7 +++-- .../src/containers/ControllerCanvasPage.tsx | 14 +++++++++ fableous-fe/src/localStorage.ts | 30 +++++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 fableous-fe/src/localStorage.ts diff --git a/fableous-fe/src/api.ts b/fableous-fe/src/api.ts index 87b19863..816a85ef 100644 --- a/fableous-fe/src/api.ts +++ b/fableous-fe/src/api.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; import { configure } from "axios-hooks"; import { TOKEN_KEY } from "./components/AuthProvider"; +import { getLocalStorage } from "./localStorage"; const baseAPI = process.env.NODE_ENV === "development" @@ -15,7 +16,7 @@ const baseWS = const apiClient = axios.create(); apiClient.defaults.baseURL = baseAPI; apiClient.interceptors.request.use((req: AxiosRequestConfig) => { - const token = localStorage.getItem(TOKEN_KEY); + const token = getLocalStorage(TOKEN_KEY); if (token) { req.headers = { authorization: `Bearer ${token}`, @@ -136,13 +137,12 @@ export const restAPI = { method: "delete", }), }, - // @TODO: create POST for story saving } as ApiEndpoints; export const wsAPI = { hub: { main: (classroomId: string) => { - const token = localStorage.getItem(TOKEN_KEY); + const token = getLocalStorage(TOKEN_KEY); return `${baseWS}/ws/hub?token=${token}&classroom_id=${classroomId}`; }, }, diff --git a/fableous-fe/src/components/AuthProvider.tsx b/fableous-fe/src/components/AuthProvider.tsx index 4a84e50c..e4e80141 100644 --- a/fableous-fe/src/components/AuthProvider.tsx +++ b/fableous-fe/src/components/AuthProvider.tsx @@ -1,4 +1,5 @@ import { createContext, ReactNode, useState } from "react"; +import { getLocalStorage, setLocalStorage } from "../localStorage"; export const TOKEN_KEY = "token"; @@ -17,7 +18,7 @@ export const AuthContext = createContext([ ]); export default function AuthProvider(props: { children: ReactNode }) { - const [token, setToken] = useState(localStorage.getItem(TOKEN_KEY) || ""); + const [token, setToken] = useState(getLocalStorage(TOKEN_KEY) || ""); const { children } = props; return ( @@ -26,11 +27,11 @@ export default function AuthProvider(props: { children: ReactNode }) { token, token !== "", (t: string) => { - localStorage.setItem(TOKEN_KEY, t); + setLocalStorage(TOKEN_KEY, t); setToken(t); }, () => { - localStorage.setItem(TOKEN_KEY, ""); + setLocalStorage(TOKEN_KEY, ""); setToken(""); }, ]} diff --git a/fableous-fe/src/containers/ControllerCanvasPage.tsx b/fableous-fe/src/containers/ControllerCanvasPage.tsx index d7ab7c65..a367fc4b 100644 --- a/fableous-fe/src/containers/ControllerCanvasPage.tsx +++ b/fableous-fe/src/containers/ControllerCanvasPage.tsx @@ -58,6 +58,7 @@ import { controllerTopChipRowId, navbarTutorialButtonId, } from "../tutorialTargetIds"; +import { getLocalStorage, ONE_DAY, setLocalStorage } from "../localStorage"; enum ControllerState { JoinForm = "JOIN_FORM", @@ -74,6 +75,8 @@ const useStyles = makeStyles({ }, }); +const CONTROLLER_TUTORIAL_KEY = "controllerTutorial"; + export default function ControllerCanvasPage() { const { enqueueSnackbar } = useSnackbar(); const [controllerState, setControllerState] = useState( @@ -368,6 +371,17 @@ export default function ControllerCanvasPage() { ); }, [controllerState, isDone, role, wsConn]); + // start tutorial at start of drawing session + useEffect(() => { + if ( + controllerState === ControllerState.DrawingSession && + getLocalStorage(CONTROLLER_TUTORIAL_KEY) === null + ) { + setIsTutorialRunning(true); + setLocalStorage(CONTROLLER_TUTORIAL_KEY, "", ONE_DAY); + } + }, [controllerState]); + // show tutorial button in navbar useEffect(() => { if (controllerState === ControllerState.DrawingSession) { diff --git a/fableous-fe/src/localStorage.ts b/fableous-fe/src/localStorage.ts new file mode 100644 index 00000000..160cb9d0 --- /dev/null +++ b/fableous-fe/src/localStorage.ts @@ -0,0 +1,30 @@ +const keyNamespace = "fableous"; + +const getKey = (key: string) => `${keyNamespace}.${key}`; + +export const ONE_DAY = 86400000; + +export const setLocalStorage = (key: string, value: any, ttlInMs?: number) => { + const namespacedKey = getKey(key); + const now = new Date(); + const item = { + value, + ...(ttlInMs !== undefined && { expiry: now.getTime() + ttlInMs }), + }; + localStorage.setItem(namespacedKey, JSON.stringify(item)); +}; + +export const getLocalStorage = (key: string) => { + const namespacedKey = getKey(key); + const itemStr = localStorage.getItem(namespacedKey); + if (!itemStr) { + return null; + } + const item = JSON.parse(itemStr); + const now = new Date(); + if (item.expiry !== undefined && now.getTime() > item.expiry) { + localStorage.removeItem(namespacedKey); + return null; + } + return item.value; +}; From 9f5e7a375a1b492473dacc3ad119210275826900 Mon Sep 17 00:00:00 2001 From: Nicolaus Gozali Date: Sun, 3 Oct 2021 13:59:24 +1000 Subject: [PATCH 07/23] feat: start tutorial manually registers to localstorage --- fableous-fe/src/containers/ControllerCanvasPage.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fableous-fe/src/containers/ControllerCanvasPage.tsx b/fableous-fe/src/containers/ControllerCanvasPage.tsx index a367fc4b..a9cea34a 100644 --- a/fableous-fe/src/containers/ControllerCanvasPage.tsx +++ b/fableous-fe/src/containers/ControllerCanvasPage.tsx @@ -378,7 +378,6 @@ export default function ControllerCanvasPage() { getLocalStorage(CONTROLLER_TUTORIAL_KEY) === null ) { setIsTutorialRunning(true); - setLocalStorage(CONTROLLER_TUTORIAL_KEY, "", ONE_DAY); } }, [controllerState]); @@ -403,6 +402,13 @@ export default function ControllerCanvasPage() { }; }, [controllerState, isTutorialRunning, setNavs, clearNavs]); + // remember tutorial use and do not auto start it for one day + useEffect(() => { + if (isTutorialRunning) { + setLocalStorage(CONTROLLER_TUTORIAL_KEY, "", ONE_DAY); + } + }, [isTutorialRunning]); + return ( Date: Sun, 3 Oct 2021 14:00:26 +1000 Subject: [PATCH 08/23] chore: remove addressed todo comments --- fableous-fe/src/components/canvas/Canvas.tsx | 2 -- fableous-fe/src/containers/HubCanvasPage.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/fableous-fe/src/components/canvas/Canvas.tsx b/fableous-fe/src/components/canvas/Canvas.tsx index a4b023ad..905626d9 100644 --- a/fableous-fe/src/components/canvas/Canvas.tsx +++ b/fableous-fe/src/components/canvas/Canvas.tsx @@ -77,8 +77,6 @@ interface SimplePointerEventData { onLeave: boolean; } -// TODO after width of canvas DOM element is dynamic, attempt to make canvas drawing scaling dynamic -// that is, resizing screen allows drawing without issue (no translation error) const Canvas = forwardRef( (props: CanvasProps, ref) => { let FRAME_COUNTER = 0; diff --git a/fableous-fe/src/containers/HubCanvasPage.tsx b/fableous-fe/src/containers/HubCanvasPage.tsx index 7efa3da6..aed4e8ef 100644 --- a/fableous-fe/src/containers/HubCanvasPage.tsx +++ b/fableous-fe/src/containers/HubCanvasPage.tsx @@ -416,8 +416,6 @@ export default function HubCanvasPage() { // go back to session form once all pages in story completed useEffect(() => { if (currentPageIdx && story && currentPageIdx > story.pages) { - // TODO send canvas result to backend here - // assume backend will close ws conn enqueueSnackbar("Story completed!", { variant: "success" }); setHubState(HubState.SessionForm); achievementReset(); From 291503b616d786e0528aaff5d159c6594e444921 Mon Sep 17 00:00:00 2001 From: Nicolaus Gozali Date: Sun, 3 Oct 2021 14:17:31 +1000 Subject: [PATCH 09/23] feat: add tutorial variant for story role --- .../src/containers/ControllerCanvasPage.tsx | 173 +++++++++++------- 1 file changed, 109 insertions(+), 64 deletions(-) diff --git a/fableous-fe/src/containers/ControllerCanvasPage.tsx b/fableous-fe/src/containers/ControllerCanvasPage.tsx index a9cea34a..e00b1702 100644 --- a/fableous-fe/src/containers/ControllerCanvasPage.tsx +++ b/fableous-fe/src/containers/ControllerCanvasPage.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, useEffect, useCallback } from "react"; +import { useRef, useState, useEffect, useCallback, useMemo } from "react"; import { Button, Card, @@ -239,68 +239,113 @@ export default function ControllerCanvasPage() { setIsDone(true); }; - const drawingTutorialSteps: Step[] = [ - { - target: `#${navbarTutorialButtonId}`, - content: - "Do you want to go through the tutorial? You can access it anytime by clicking the help icon.", - placement: "bottom", - disableBeacon: true, - // wierdly, close behavior is like next page, unsure on how to fix it - hideCloseButton: true, - }, - { - target: `#${controllerTopChipRowId}`, - content: - "You will be assigned a role and collaboratively draw a story based on a theme.", - placement: "bottom", - disableBeacon: true, - hideCloseButton: true, - }, - { - target: `#${controllerCanvasId}`, - content: - "You will only see your own drawing here, see teacher's hub screen for the combined drawing.", - placement: "auto", - disableBeacon: true, - hideCloseButton: true, - }, - { - target: `#${CanvasToolbarIconId.Brush}`, - content: "Use brush to draw", - placement: "right", - disableBeacon: true, - hideCloseButton: true, - }, - { - target: `#${CanvasToolbarIconId.Erase}`, - content: "Use eraser to erase", - placement: "right", - disableBeacon: true, - hideCloseButton: true, - }, - { - target: `#${CanvasToolbarIconId.Fill}`, - content: "Use bucket to fill with selected colour", - placement: "right", - disableBeacon: true, - hideCloseButton: true, - }, - { - target: `#${CanvasToolbarIconId.Palette}`, - content: "Use palette to choose a colour", - placement: "right", - disableBeacon: true, - hideCloseButton: true, - }, - { - target: `#${CanvasToolbarIconId.Undo}`, - content: "Use undo to undo a recent action", - placement: "right", - disableBeacon: true, - hideCloseButton: true, - }, - ]; + const commonTutorialSteps: Step[] = useMemo( + () => [ + { + target: `#${navbarTutorialButtonId}`, + content: + "Do you want to go through the tutorial? You can access it anytime by clicking the help icon.", + placement: "bottom", + disableBeacon: true, + // wierdly, close behavior is like next step, unsure on how to fix it + hideCloseButton: true, + }, + { + target: `#${controllerTopChipRowId}`, + content: + "You will be assigned a role and collaboratively draw a story based on a theme.", + placement: "bottom", + disableBeacon: true, + hideCloseButton: true, + }, + { + target: `#${controllerCanvasId}`, + content: + "You will only see your own drawing here, see teacher's hub screen for the combined drawing.", + placement: "auto", + disableBeacon: true, + hideCloseButton: true, + }, + ], + [] + ); + + const drawingTutorialSteps: Step[] = useMemo( + () => [ + { + target: `#${CanvasToolbarIconId.Brush}`, + content: "Use brush to draw", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + { + target: `#${CanvasToolbarIconId.Erase}`, + content: "Use eraser to erase", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + { + target: `#${CanvasToolbarIconId.Fill}`, + content: "Use bucket to fill with selected colour", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + { + target: `#${CanvasToolbarIconId.Palette}`, + content: "Use palette to choose a colour", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + { + target: `#${CanvasToolbarIconId.Undo}`, + content: "Use undo to undo a recent action", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + ], + [] + ); + + const storyTutorialSteps: Step[] = useMemo( + () => [ + { + target: `#${CanvasToolbarIconId.Text}`, + content: "Use text to write a story using keyboard", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + { + target: `#${CanvasToolbarIconId.Audio}`, + content: "Use microphone to record a story", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + { + target: `#${CanvasToolbarIconId.Undo}`, + content: "Use undo to undo a recent action", + placement: "right", + disableBeacon: true, + hideCloseButton: true, + }, + ], + [] + ); + + const tutorialSteps = useMemo( + () => + role === ControllerRole.Story + ? commonTutorialSteps.concat(storyTutorialSteps) + : commonTutorialSteps.concat(drawingTutorialSteps), + [role, commonTutorialSteps, storyTutorialSteps, drawingTutorialSteps] + ); + const handleJoyrideCallback = (data: CallBackProps) => { const { status } = data; const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; @@ -422,7 +467,7 @@ export default function ControllerCanvasPage() { showProgress showSkipButton disableOverlayClose - steps={drawingTutorialSteps} + steps={tutorialSteps} styles={{ options: { zIndex: 10000, From e92528f056f223c776c6774a76fd239827334592 Mon Sep 17 00:00:00 2001 From: Nicolaus Gozali Date: Sun, 3 Oct 2021 14:21:41 +1000 Subject: [PATCH 10/23] feat: hide login register navbar button on /join --- fableous-fe/src/components/Navbar.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fableous-fe/src/components/Navbar.tsx b/fableous-fe/src/components/Navbar.tsx index b46ad751..d894c7a1 100644 --- a/fableous-fe/src/components/Navbar.tsx +++ b/fableous-fe/src/components/Navbar.tsx @@ -3,7 +3,7 @@ import AppBar from "@material-ui/core/AppBar"; import Toolbar from "@material-ui/core/Toolbar"; import Typography from "@material-ui/core/Typography"; import Button from "@material-ui/core/Button"; -import { Link, useHistory } from "react-router-dom"; +import { Link, useHistory, useLocation } from "react-router-dom"; import { useContext } from "react"; import { AuthContext } from "./AuthProvider"; @@ -17,6 +17,7 @@ const useStyles = makeStyles(() => ({ export default function Navbar() { const history = useHistory(); + const location = useLocation(); const [, isAuthenticated, , clearToken] = useContext(AuthContext); const onLogout = () => { clearToken(); @@ -45,7 +46,7 @@ export default function Navbar() { {label} ))} - {isAuthenticated ? ( + {isAuthenticated && ( <> ))} - {isAuthenticated && ( + {isAuthenticated && !isOnStudentPages && ( <>