Skip to content

Commit

Permalink
feat: implement stringified custom JSON type for processed handwritin…
Browse files Browse the repository at this point in the history
…g 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 <[email protected]>

* Update src/components/questionStructure/Task/variants/HandwritingTask/HandwritingEditor.tsx

Co-authored-by: Ivan Procaccini <[email protected]>

---------

Co-authored-by: Matthew Alex <[email protected]>
Co-authored-by: nick-bolas <[email protected]>
Co-authored-by: Kishan Sambhi <[email protected]>
Co-authored-by: Illia Derevianko <[email protected]>
Co-authored-by: Ivan Procaccini <[email protected]>
  • Loading branch information
6 people authored Jul 19, 2024
1 parent b374904 commit 3d29a0f
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -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, {
Expand All @@ -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()
Expand All @@ -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:
Expand All @@ -44,11 +50,25 @@ const ALLOWED_TOOL_SHORTCUTS = [
'Digit7', // pen
]

const Canvas: React.FC<CanvasProps> = ({ username, onAnswerChange }) => {
const { updateStrokes } = useLiveUpdates(username, onAnswerChange)
const Canvas: React.FC<CanvasProps> = ({ username, onAnswerChange, initialData }) => {
const [excalidrawAPI, setExcalidrawAPI] = useState<ExcalidrawImperativeAPI | null>(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: [] })
Expand Down Expand Up @@ -128,7 +148,10 @@ const Canvas: React.FC<CanvasProps> = ({ username, onAnswerChange }) => {
UIOptions={{ tools: { image: false } }}
gridModeEnabled
excalidrawAPI={setExcalidrawAPI}
// initialData={excalidrawData}
initialData={{
...initialData,
appState: { ...initialData.appState, collaborators: new Map() },
}}
onPaste={pasteHandler}
>
<MainMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HandwritingEditorProps> = ({
username,
latex,
answer,
onAnswerChange,
}) => {
return (
<Flex direction="column" flexGrow="1" align="center" gap="3">
<Card className="mathjax-card">
<Box p="3">
<MathJax>{`\\( ${latex} \\)`}</MathJax>
<MathJax>{`\\( ${answer?.latex ?? ''} \\)`}</MathJax>
</Box>
</Card>
<Card className="excalidraw-canvas-card">
<Canvas username={username} onAnswerChange={onAnswerChange} />
<Canvas
username={username}
onAnswerChange={onAnswerChange}
// @ts-expect-error: Types of initialData and answer are essentially the same but TypeScript doesn't recognize it
initialData={answer?.raw ?? {}}
/>
</Card>
</Flex>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ViewOnlyCanvasProps> = ({ initialData }) => {
const [excalidrawAPI, setExcalidrawAPI] = useState<ExcalidrawImperativeAPI | null>(null)

useEffect(() => {
if (excalidrawAPI && initialData) {
excalidrawAPI.updateScene({
elements: initialData,
})
}
setTimeout(() => {
excalidrawAPI?.scrollToContent(undefined, {
fitToContent: true,
})
})
}, [excalidrawAPI])
}, [excalidrawAPI, initialData])

return initialData && initialData.elements?.length ? (
return (
<Card>
<Box className="excalidraw-view-container excalidraw-box">
<Excalidraw excalidrawAPI={setExcalidrawAPI} viewModeEnabled initialData={initialData} />
<Excalidraw
excalidrawAPI={setExcalidrawAPI}
viewModeEnabled
initialData={{ elements: initialData }}
/>
</Box>
</Card>
) : null
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
}

.excalidraw-box {
margin: '2px';
margin: 2px;
height: calc(100% - 4px);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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<string> {
export interface HandwritingTaskProps extends TaskBaseProps<HandwritingAnswer> {
type: TaskType.PROCESSED_HANDWRITING
}

Expand All @@ -24,13 +26,15 @@ export const HandwritingTask: FC<HandwritingTaskProps> = ({

return (
<Dialog.Root>
<Dialog.Trigger>
<ViewOnlyCanvas initialData={{}} />
</Dialog.Trigger>
{!isEmpty(answer?.raw?.elements) && (
<Dialog.Trigger>
<ViewOnlyCanvas initialData={answer!.raw.elements} />
</Dialog.Trigger>
)}
<Flex gap="3" align="center">
<Card className="latex-preview">
<Flex p="3">
<MathJax>{answer ? `\\( ${answer} \\)` : 'No Answer'}</MathJax>
<MathJax>{answer?.latex ? `\\( ${answer.latex} \\)` : 'No Answer'}</MathJax>
</Flex>
</Card>
<Dialog.Trigger>
Expand All @@ -42,11 +46,7 @@ export const HandwritingTask: FC<HandwritingTaskProps> = ({

<Dialog.Content className="excalidraw-dialog-content">
<Flex direction="column" height="100%" gap="3">
<HandwritingEditor
latex={answer ?? ''}
onAnswerChange={onAnswerUpdate}
username={username}
/>
<HandwritingEditor answer={answer} onAnswerChange={onAnswerUpdate} username={username} />
<Flex justify="end">
<Dialog.Close>
<Button>Save LaTeX</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AppState, 'offsetTop' | 'offsetLeft' | 'width' | 'height'>
}
latex: string
}
2 changes: 2 additions & 0 deletions src/utils/answers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 3d29a0f

Please sign in to comment.