From 3d29a0feaea26c3bd6739351b4f009ef55bb7fa4 Mon Sep 17 00:00:00 2001 From: Alexander Biraben-Renard Date: Fri, 19 Jul 2024 16:20:55 +0100 Subject: [PATCH] feat: implement stringified custom JSON type for processed handwriting task answer (#21) * feat: change answers type for maths single answer tasks * feat: wrap onAnswerChange for updating latex * feat: show updated handwriting in ViewOnlyCanvas * feat: display strokes from database in canvas * feat: scroll view-only canvas content into view on change * feat: save app state to db * chore: run formtter * refactor: move display condition out of ViewOnlyCanvas * chore: remove unnecessary styling * refactor: rename excalidraw field to raw * fix: can't render a dialog trigger with nothing inside Co-authored-by: Alexander Biraben-Renard * Update src/components/questionStructure/Task/variants/HandwritingTask/HandwritingEditor.tsx Co-authored-by: Ivan Procaccini --------- Co-authored-by: Matthew Alex Co-authored-by: nick-bolas Co-authored-by: Kishan Sambhi Co-authored-by: Illia Derevianko Co-authored-by: Ivan Procaccini --- .../Task/variants/HandwritingTask/Canvas.tsx | 29 +++++++++++++++++-- .../HandwritingTask/HandwritingEditor.tsx | 14 ++++++--- .../HandwritingTask/ViewOnlyCanvas.tsx | 25 ++++++++++------ .../Task/variants/HandwritingTask/index.scss | 2 +- .../Task/variants/HandwritingTask/index.tsx | 20 ++++++------- .../Task/variants/HandwritingTask/types.ts | 10 +++++++ src/utils/answers.ts | 2 ++ 7 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 src/components/questionStructure/Task/variants/HandwritingTask/types.ts diff --git a/src/components/questionStructure/Task/variants/HandwritingTask/Canvas.tsx b/src/components/questionStructure/Task/variants/HandwritingTask/Canvas.tsx index 14080d6..d130cf8 100644 --- a/src/components/questionStructure/Task/variants/HandwritingTask/Canvas.tsx +++ b/src/components/questionStructure/Task/variants/HandwritingTask/Canvas.tsx @@ -1,5 +1,6 @@ import { Excalidraw, MainMenu } from '@excalidraw/excalidraw' import { ClipboardData } from '@excalidraw/excalidraw/types/clipboard' +import { ExcalidrawElement } from '@excalidraw/excalidraw/types/element/types' import { AppState, ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types/types' import { Box } from '@radix-ui/themes' import React, { @@ -13,6 +14,7 @@ import React, { import { ConfirmDialog } from '../../../../ConfirmDialog' import useLiveUpdates from './live-updates.hook' +import { HandwritingAnswer } from './types' const stopEvent = (e: SyntheticEvent | Event) => { e.preventDefault() @@ -22,6 +24,10 @@ const stopEvent = (e: SyntheticEvent | Event) => { interface CanvasProps { username: string onAnswerChange: (value: string) => void + initialData: { + elements?: readonly ExcalidrawElement[] + appState?: AppState + } } // Excalidraw keyboard shortcuts we allow in the canvas: @@ -44,11 +50,25 @@ const ALLOWED_TOOL_SHORTCUTS = [ 'Digit7', // pen ] -const Canvas: React.FC = ({ username, onAnswerChange }) => { - const { updateStrokes } = useLiveUpdates(username, onAnswerChange) +const Canvas: React.FC = ({ username, onAnswerChange, initialData }) => { const [excalidrawAPI, setExcalidrawAPI] = useState(null) const [clearDialogOpen, setClearDialogOpen] = useState(false) + const updateHandwriting = useCallback( + (latex: string) => { + const result: HandwritingAnswer = { + latex, + raw: { + elements: excalidrawAPI?.getSceneElements() ?? [], + appState: excalidrawAPI?.getAppState(), + }, + } + onAnswerChange(JSON.stringify(result)) + }, + [excalidrawAPI, onAnswerChange] + ) + const { updateStrokes } = useLiveUpdates(username, updateHandwriting) + const clearCanvas = useCallback(() => { updateStrokes({ elements: [] }) excalidrawAPI?.updateScene({ elements: [] }) @@ -128,7 +148,10 @@ const Canvas: React.FC = ({ username, onAnswerChange }) => { UIOptions={{ tools: { image: false } }} gridModeEnabled excalidrawAPI={setExcalidrawAPI} - // initialData={excalidrawData} + initialData={{ + ...initialData, + appState: { ...initialData.appState, collaborators: new Map() }, + }} onPaste={pasteHandler} > diff --git a/src/components/questionStructure/Task/variants/HandwritingTask/HandwritingEditor.tsx b/src/components/questionStructure/Task/variants/HandwritingTask/HandwritingEditor.tsx index 86ca382..763ba2b 100644 --- a/src/components/questionStructure/Task/variants/HandwritingTask/HandwritingEditor.tsx +++ b/src/components/questionStructure/Task/variants/HandwritingTask/HandwritingEditor.tsx @@ -5,27 +5,33 @@ import React from 'react' import Markdown from '../../../../Markdown' import Canvas from './Canvas' import './handwritingEditor.css' +import { HandwritingAnswer } from './types' interface HandwritingEditorProps { username: string - latex: string + answer?: HandwritingAnswer onAnswerChange: (value: string) => void } const HandwritingEditor: React.FC = ({ username, - latex, + answer, onAnswerChange, }) => { return ( - {`\\( ${latex} \\)`} + {`\\( ${answer?.latex ?? ''} \\)`} - + ) diff --git a/src/components/questionStructure/Task/variants/HandwritingTask/ViewOnlyCanvas.tsx b/src/components/questionStructure/Task/variants/HandwritingTask/ViewOnlyCanvas.tsx index 5a1780f..af3a710 100644 --- a/src/components/questionStructure/Task/variants/HandwritingTask/ViewOnlyCanvas.tsx +++ b/src/components/questionStructure/Task/variants/HandwritingTask/ViewOnlyCanvas.tsx @@ -1,31 +1,38 @@ import { Excalidraw } from '@excalidraw/excalidraw' -import { - ExcalidrawImperativeAPI, - ExcalidrawInitialDataState, -} from '@excalidraw/excalidraw/types/types' +import { ExcalidrawElement } from '@excalidraw/excalidraw/types/element/types' +import { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types/types' import { Box, Card } from '@radix-ui/themes' import React, { useEffect, useState } from 'react' interface ViewOnlyCanvasProps { - initialData: ExcalidrawInitialDataState + initialData: readonly ExcalidrawElement[] } export const ViewOnlyCanvas: React.FC = ({ initialData }) => { const [excalidrawAPI, setExcalidrawAPI] = useState(null) useEffect(() => { + if (excalidrawAPI && initialData) { + excalidrawAPI.updateScene({ + elements: initialData, + }) + } setTimeout(() => { excalidrawAPI?.scrollToContent(undefined, { fitToContent: true, }) }) - }, [excalidrawAPI]) + }, [excalidrawAPI, initialData]) - return initialData && initialData.elements?.length ? ( + return ( - + - ) : null + ) } diff --git a/src/components/questionStructure/Task/variants/HandwritingTask/index.scss b/src/components/questionStructure/Task/variants/HandwritingTask/index.scss index 278437c..4d69fce 100644 --- a/src/components/questionStructure/Task/variants/HandwritingTask/index.scss +++ b/src/components/questionStructure/Task/variants/HandwritingTask/index.scss @@ -62,7 +62,7 @@ } .excalidraw-box { - margin: '2px'; + margin: 2px; height: calc(100% - 4px); } diff --git a/src/components/questionStructure/Task/variants/HandwritingTask/index.tsx b/src/components/questionStructure/Task/variants/HandwritingTask/index.tsx index fef2c58..abc8e48 100644 --- a/src/components/questionStructure/Task/variants/HandwritingTask/index.tsx +++ b/src/components/questionStructure/Task/variants/HandwritingTask/index.tsx @@ -1,6 +1,7 @@ import { Pencil2Icon } from '@radix-ui/react-icons' import { Button, Card, Dialog, Flex } from '@radix-ui/themes' import { MathJax } from 'better-react-mathjax' +import { isEmpty } from 'lodash' import { FC } from 'react' import { useParams } from 'react-router-dom' @@ -10,8 +11,9 @@ import { TaskBaseProps } from '../../types' import HandwritingEditor from './HandwritingEditor' import { ViewOnlyCanvas } from './ViewOnlyCanvas' import './index.scss' +import { HandwritingAnswer } from './types' -export interface HandwritingTaskProps extends TaskBaseProps { +export interface HandwritingTaskProps extends TaskBaseProps { type: TaskType.PROCESSED_HANDWRITING } @@ -24,13 +26,15 @@ export const HandwritingTask: FC = ({ return ( - - - + {!isEmpty(answer?.raw?.elements) && ( + + + + )} - {answer ? `\\( ${answer} \\)` : 'No Answer'} + {answer?.latex ? `\\( ${answer.latex} \\)` : 'No Answer'} @@ -42,11 +46,7 @@ export const HandwritingTask: FC = ({ - + diff --git a/src/components/questionStructure/Task/variants/HandwritingTask/types.ts b/src/components/questionStructure/Task/variants/HandwritingTask/types.ts new file mode 100644 index 0000000..b7298c1 --- /dev/null +++ b/src/components/questionStructure/Task/variants/HandwritingTask/types.ts @@ -0,0 +1,10 @@ +import { ExcalidrawElement } from '@excalidraw/excalidraw/types/element/types' +import { AppState } from '@excalidraw/excalidraw/types/types' + +export interface HandwritingAnswer { + raw: { + elements: readonly ExcalidrawElement[] + appState?: Omit + } + latex: string +} diff --git a/src/utils/answers.ts b/src/utils/answers.ts index a01384d..84becc9 100644 --- a/src/utils/answers.ts +++ b/src/utils/answers.ts @@ -26,6 +26,8 @@ export function parseAnswer(answer: string, targetTaskType: TaskType) { return answer === '' ? answer : Number(answer) case TaskType.MULTIPLE_CHOICE_SELECT_SEVERAL: return answer === '' ? [] : answer.split(',') + case TaskType.PROCESSED_HANDWRITING: + return answer === '' ? { excalidraw: null, latex: '' } : JSON.parse(answer) default: return answer }