Skip to content

Commit

Permalink
Merge pull request #55 from deco-finter/feat/hifi-tutorial-canvas
Browse files Browse the repository at this point in the history
[FAB-133] HiFi Canvas Tutorial
  • Loading branch information
daystram committed Oct 5, 2021
2 parents f087edd + 8e77537 commit 92dc429
Show file tree
Hide file tree
Showing 16 changed files with 544 additions and 57 deletions.
1 change: 1 addition & 0 deletions fableous-fe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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",
Expand Down
17 changes: 10 additions & 7 deletions fableous-fe/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Navbar from "./components/Navbar";
import Routes from "./Routes";
import InjectAxiosRespInterceptor from "./components/InjectAxiosRespInterceptor";
import { colors } from "./colors";
import CustomNavProvider from "./components/CustomNavProvider";

// generated background from https://www.svgbackgrounds.com/
const useStyles = makeStyles({
Expand Down Expand Up @@ -136,13 +137,15 @@ export default function App() {
<React.StrictMode>
<AuthProvider>
<Router>
<InjectAxiosRespInterceptor />
<ThemeProvider theme={theme}>
<Navbar />
<Container className="flex flex-col flex-1 pt-5 pb-12">
<Routes />
</Container>
</ThemeProvider>
<CustomNavProvider>
<InjectAxiosRespInterceptor />
<ThemeProvider theme={theme}>
<Navbar />
<Container className="flex flex-col flex-1 pt-5 pb-12">
<Routes />
</Container>
</ThemeProvider>
</CustomNavProvider>
</Router>
</AuthProvider>
</React.StrictMode>
Expand Down
6 changes: 3 additions & 3 deletions fableous-fe/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { configure } from "axios-hooks";
import { TOKEN_KEY } from "./components/AuthProvider";
import { getLocalStorage } from "./storage";
import { proto as pb } from "./proto/message_pb";

const baseAPI =
Expand All @@ -16,7 +17,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}`,
Expand Down Expand Up @@ -137,13 +138,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}`;
},
},
Expand Down
7 changes: 4 additions & 3 deletions fableous-fe/src/components/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContext, ReactNode, useState } from "react";
import { getLocalStorage, setLocalStorage } from "../storage";

export const TOKEN_KEY = "token";

Expand All @@ -17,7 +18,7 @@ export const AuthContext = createContext<AuthContextType>([
]);

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 (
Expand All @@ -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("");
},
]}
Expand Down
12 changes: 10 additions & 2 deletions fableous-fe/src/components/ChipRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@ import React from "react";
interface ChipRowProps {
chips: (React.ReactNode | ChipProps | string)[];
primary?: boolean;
rootProps?: React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
>;
}

export default function ChipRow(props: ChipRowProps) {
const { chips, primary } = props;
const { chips, primary, rootProps } = props;

return (
<div className="flex justify-evenly flex-grow flex-wrap gap-y-4">
<div
className="flex justify-evenly flex-grow flex-wrap gap-y-4"
{...rootProps}
>
{chips.map((chip) =>
React.isValidElement(chip) ? (
chip
Expand All @@ -30,4 +37,5 @@ export default function ChipRow(props: ChipRowProps) {

ChipRow.defaultProps = {
primary: false,
rootProps: {},
};
43 changes: 43 additions & 0 deletions fableous-fe/src/components/CustomNavProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ButtonProps } from "@material-ui/core";
import { createContext, ReactNode, useContext, useState } from "react";

export interface Nav {
icon: string;
label: string;
buttonProps: ButtonProps;
}

export type CustomNavContextType = [
additionalNavs: Nav[],
setAdditionalNavs: React.Dispatch<React.SetStateAction<Nav[]>>,
isLogoClickable: boolean,
setIsLogoClickable: React.Dispatch<React.SetStateAction<boolean>>
];

export const CustomNavContext = createContext<CustomNavContextType>([
[],
(_) => {},
true,
(_) => {},
]);

export const useCustomNav = () => useContext(CustomNavContext);

export default function CustomNavProvider(props: { children: ReactNode }) {
const [additionalNavs, setAdditionalNavs] = useState<Nav[]>([]);
const [isLogoClickable, setIsLogoClickable] = useState(true);
const { children } = props;

return (
<CustomNavContext.Provider
value={[
additionalNavs,
setAdditionalNavs,
isLogoClickable,
setIsLogoClickable,
]}
>
{children}
</CustomNavContext.Provider>
);
}
42 changes: 32 additions & 10 deletions fableous-fe/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ReactNode, useContext } from "react";
import { Icon, makeStyles } from "@material-ui/core";
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 { useContext } from "react";
import { Link, useHistory, useLocation } from "react-router-dom";

import { AuthContext } from "./AuthProvider";
import { useCustomNav } from "./CustomNavProvider";

const useStyles = makeStyles(() => ({
home: {
Expand All @@ -16,23 +17,44 @@ const useStyles = makeStyles(() => ({

export default function Navbar() {
const history = useHistory();
const location = useLocation();
const [, isAuthenticated, , clearToken] = useContext(AuthContext);
const onLogout = () => {
clearToken();
history.push("/");
};
const [additionalNavs, , isLogoClickable] = useCustomNav();
const classes = useStyles();

// navbar will be simplified for students
const isOnStudentPages = location.pathname === "/join";

const logoLinkWrapper = (children: ReactNode) => (
<Link to="/">{children}</Link>
);
const logoElement = (
<Typography variant="h1" className={classes.home}>
Fableous
</Typography>
);

return (
<AppBar position="static">
<Toolbar>
<Link to="/">
<Typography variant="h1" className={classes.home}>
Fableous
</Typography>
</Link>
{isLogoClickable ? logoLinkWrapper(logoElement) : logoElement}
<div className="flex-grow" /> {/* spacer */}
{isAuthenticated ? (
{additionalNavs.map(({ icon, label, buttonProps }) => (
<Button
variant="outlined"
className="text-white"
startIcon={<Icon fontSize="small">{icon}</Icon>}
// eslint-disable-next-line react/jsx-props-no-spreading
{...buttonProps}
>
{label}
</Button>
))}
{isAuthenticated && !isOnStudentPages && (
<>
<Button
variant="outlined"
Expand All @@ -43,7 +65,6 @@ export default function Navbar() {
>
Profile
</Button>

<Button
variant="outlined"
className="ml-4 text-white"
Expand All @@ -53,7 +74,8 @@ export default function Navbar() {
Logout
</Button>
</>
) : (
)}
{!isAuthenticated && !isOnStudentPages && (
<>
<Button
variant="outlined"
Expand Down
6 changes: 4 additions & 2 deletions fableous-fe/src/components/canvas/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface CanvasProps {
setToolMode?: React.Dispatch<React.SetStateAction<ToolMode>>;
toolColor?: string;
toolWidth?: number;
rootId?: string | undefined;
}

const defaultProps = {
Expand All @@ -65,6 +66,7 @@ const defaultProps = {
toolMode: ToolMode.None,
setToolMode: () => {},
toolWidth: 8 * SCALE,
rootId: undefined,
};

interface SimplePointerEventData {
Expand All @@ -74,8 +76,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<ImperativeCanvasRef, CanvasProps>(
(props: CanvasProps, ref) => {
let FRAME_COUNTER = 0;
Expand All @@ -98,6 +98,7 @@ const Canvas = forwardRef<ImperativeCanvasRef, CanvasProps>(
toolColor = defaultProps.toolColor,
toolWidth = defaultProps.toolWidth,
wsConn,
rootId,
} = props;
// useImperativeHandle of type ImperativeCanvasRef defined at bottom
const canvasRef = useRef<HTMLCanvasElement>(
Expand Down Expand Up @@ -911,6 +912,7 @@ const Canvas = forwardRef<ImperativeCanvasRef, CanvasProps>(

return (
<div
id={rootId}
className="relative place-self-center"
style={{
width: offsetWidth,
Expand Down
28 changes: 15 additions & 13 deletions fableous-fe/src/components/canvas/CanvasToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ImperativeCanvasRef } from "./data";
import { proto as pb } from "../../proto/message_pb";
import BrushWidthIcon from "./BrushWidthIcon";
import CanvasToolbarTooltip from "./CanvasToolbarTooltip";
import { TutorialTargetId } from "../../tutorialTargetIds";

interface CanvasToolbarProps {
role: pb.ControllerRole;
Expand Down Expand Up @@ -140,6 +141,7 @@ const CanvasToolbar = forwardRef<ImperativeCanvasRef, CanvasToolbarProps>(
}
>
<IconButton
id={TutorialTargetId.BrushTool}
className="relative"
onClick={() => {
if (toolColor === ERASE_COLOR) {
Expand Down Expand Up @@ -168,6 +170,7 @@ const CanvasToolbar = forwardRef<ImperativeCanvasRef, CanvasToolbarProps>(
</IconButton>
</CanvasToolbarTooltip>
<IconButton
id={TutorialTargetId.EraseTool}
onClick={() => {
setToolColorRememberPrev(ERASE_COLOR);
setToolMode(ToolMode.Paint);
Expand All @@ -178,9 +181,10 @@ const CanvasToolbar = forwardRef<ImperativeCanvasRef, CanvasToolbarProps>(
: "primary"
}
>
<EraserIcon fontSize="medium" />
<EraserIcon fontSize="large" />
</IconButton>
<IconButton
id={TutorialTargetId.FillTool}
onClick={() => {
if (toolColor === ERASE_COLOR) {
setToolColor(prevColor);
Expand Down Expand Up @@ -224,6 +228,7 @@ const CanvasToolbar = forwardRef<ImperativeCanvasRef, CanvasToolbarProps>(
}
>
<IconButton
id={TutorialTargetId.PaletteTool}
onClick={() => setIsColorPickerOpen((prev) => !prev)}
color="primary"
className="relative"
Expand All @@ -239,17 +244,12 @@ const CanvasToolbar = forwardRef<ImperativeCanvasRef, CanvasToolbarProps>(
/>
</IconButton>
</CanvasToolbarTooltip>
<IconButton
onClick={imperativeCanvasRef.current.runUndo}
color="primary"
>
<UndoIcon fontSize="large" />
</IconButton>
</>
)}
{role === pb.ControllerRole.STORY && (
<>
<IconButton
id={TutorialTargetId.TextTool}
onClick={() => setToolMode(ToolMode.Text)}
color={toolMode === ToolMode.Text ? "secondary" : "primary"}
>
Expand All @@ -265,6 +265,7 @@ const CanvasToolbar = forwardRef<ImperativeCanvasRef, CanvasToolbarProps>(
}
>
<IconButton
id={TutorialTargetId.AudioTool}
onClick={() => {
setToolMode(ToolMode.Audio);
setIsRecordingAudio((prev) => {
Expand All @@ -285,14 +286,15 @@ const CanvasToolbar = forwardRef<ImperativeCanvasRef, CanvasToolbarProps>(
)}
</IconButton>
</CanvasToolbarTooltip>
<IconButton
onClick={imperativeCanvasRef.current.runUndo}
color="primary"
>
<UndoIcon fontSize="large" />
</IconButton>
</>
)}
<IconButton
id={TutorialTargetId.UndoTool}
onClick={imperativeCanvasRef.current.runUndo}
color="primary"
>
<UndoIcon fontSize="large" />
</IconButton>
</Paper>
</div>
</div>
Expand Down

0 comments on commit 92dc429

Please sign in to comment.