Skip to content

feat(scribing): migrate from fabric.js v5 to v7#8287

Merged
adi-herwana-nus merged 1 commit intomasterfrom
adi/scribing-library-updates
Apr 17, 2026
Merged

feat(scribing): migrate from fabric.js v5 to v7#8287
adi-herwana-nus merged 1 commit intomasterfrom
adi/scribing-library-updates

Conversation

@adi-herwana-nus
Copy link
Copy Markdown
Contributor

@adi-herwana-nus adi-herwana-nus commented Apr 6, 2026

Migration of scribing question components from fabric.js v5.3.0 to v7.2.0. This addresses several vulnerabilities associated with the node-tar package (fabric v7 removes this from the dependency tree entirely).

Associated Redux state reducer and several top-level components (ScribingCanvas, ScribingToolbar, LayersComponent) also migrated from JSX class components to TSX functional components.

Sample of new submission with Fabric v7.2.0 objects:

Screenshot 2026-04-06 at 12 25 04

Database view of above:

Screenshot 2026-04-06 at 12 25 18

Sample of existing submission with Fabric v5.3.0 objects opened with Fabric 7.2.0 frontend:

Screenshot 2026-04-06 at 12 25 42

Note that no database migrations were needed to maintain backward compatibility.

ScribingCanvas.jsx → ScribingCanvas.tsx

Function Line (Old) Line (New) Notes
constructor 76 152 Converted to useRef hooks for shape/flag tracking
currentStateIndex getter 89 Read via scribingRef.current.currentStateIndex
canvasStates getter 93 Read via scribingRef.current.canvasStates
componentDidMount 97 1038 Converted to useEffect([answerId])
shouldComponentUpdate 102 1164 Split into multiple targeted useEffect hooks
deleteActiveObjects 179 446
onKeyDown 193 508
onMouseDownCanvas 286 615 Stale closure fixed via sync-ref pattern (scribingRef.current = scribingState[answerId] during render)
onMouseMoveCanvas 462 779
onMouseOut 550 875
onMouseOver 554 879
onMouseUpCanvas 560 885
onObjectMovingCanvas 601 927 Refactored to use getBoundingRect() offset — works for any originX/originY
onObjectSelected 617 1096 Uses unified listener-based model
onSelectionCleared 623 1096 Uses unified listener-based model
onTextChanged 627 950 Added eager scribingRef.current mutation to fix Fabric v7 event ordering (text:editing:exited now fires before mouse:down)
getCanvasPoint 637 330
getFabricObjectsFromJson 651 122 Now async (Fabric v7 fromObject returns a Promise)
getMousePoint 671 342
scribblesAsJson getter 676 235 Converted to function; uses layer.scribbleGroup.set({ visible })
setCopiedCanvasObjectPosition 703 426
rehydrateCanvas 726 273 Preserves backgroundImage across canvas.clear()
setCanvasStateAndUpdateAnswer 746 309 Now async
cloneText 760 404
denormaliseScribble 782 118
disableObjectSelection 786 374
enableObjectSelection 797 382 Rewritten to directly toggle selectable on existing canvas objects — avoids JSON round-trip that caused stale re-render when closing style popovers
generateMouseDragProperties 819 357
initializeCanvas 826 1038 Inlined into useEffect; canvas.dispose() now awaited — Fabric v7 dispose() is async (fixes insertBefore DOM error on first load)
initializeScribblesAndBackground 907 968 Now async
normaliseScribble 979 96
updateAnswer 997 296 Now async
saveScribbles 1005 460 Added isSavingScribbles re-entrant guard — Fabric v7 obj.set() fires object:modified, causing an infinite loop not present in old component
scaleCanvas 1033 1113 Replaced by initializing canvas transforms and canvasZoom state
undo 1039 487
redo 1045 494
render 1057 1302 Converted to component JSX

ScribingToolbar.jsx → ScribingToolbar.tsx

Function Line (Old) Line (New) Notes
initializeColorDropdowns 90 74
initializePopovers 98 82
constructor 107 103 State converted to useState hooks; forceUpdate via useReducer for selection and layer changes
onChangeCompleteColor 119 119
onChangeFontFamily 123 128
onChangeFontSize 126 137
onChangeSliderThickness 129 146
onClickColorPicker 133 156
onClickDelete 143 167
onClickDrawingMode 147 171
onClickLineMode 153 182
onClickLineStyleChip 160 195
onClickMoveMode 166 205
onClickPopover 173 216
onClickRedo 187 231
onClickSelectionMode 191 235
onClickShapeMode 198 246
onClickTypingChevron 205 269
onClickTypingIcon 210 274
onClickTypingMode 214 259
onClickUndo 220 278
onClickZoomIn 224 282
onClickZoomOut 229 287
onRequestCloseColorPicker 234 292
onRequestClosePopover 243 299
getActiveObjectSelectedLineStyle 252 306
getSavingStatus 271 328
setSelectedShape 286 343
setToSelectTool 290 349
render 297 417 Converted to component JSX return

Other Significant Changes

  • Shift in object originX / originY values
    Fabric v7 shifts the default originX,originY values from left,top to center,center respectively.
    There are proposals to deprecate these fields entirely, so we are making the shift now to make future transitions to Fabric v8+ easier.
  • Non-serializable values removed from Redux state
    • activeObject no longer tracked in Redux. Exposed directly via ScribingCanvasRef.getActiveObject(), re-read on each render triggered by onSelectionChange.
    • selection:created/updated/cleared events drive a pub/sub onSelectionChange callback on ScribingCanvasRef, which the toolbar subscribes to via useEffect to trigger a forceUpdate.
    • layers tracking moved from Redux state to an internal useRef in ScribingCanvas.
  • Canvas ref sharing
    Lifted to index.tsx. ScribingCanvas uses forwardRef + useImperativeHandle, exposing ScribingCanvasRef (getActiveObject, getCanvasWidth, getLayers, setLayerDisplay, onSelectionChange).
  • Stale closures
    All 13 canvas interaction-tracking variables (mouseDownFlag, line, rect, etc.) converted from useStateuseRef. Redux state accessed via sync-ref pattern (scribingRef.current = scribingState[answerId] during render).
  • withCanvas HOF
    Guards every canvas event handler and useEffect against missing canvas/scribing. Returns a no-op if either is absent. Allows useEffect(withCanvas(...), [deps]) directly.
  • Dynamic canvas scaling
    CSS zoom on canvas wrapper div, updated by a ResizeObserver + requestAnimationFrame debounce (avoids "ResizeObserver loop" errors). Replaces scaleCanvas(). This allows scribing element to resize dynamically. Canceled, as this was found to be nonfunctional in staging environment. Instead a replacement implementation for scaleCanvas() has been created.
  • Layer display re-render
    forceUpdate via useReducer in toolbar triggers re-read of canvasRef.getLayers() after setLayerDisplay.
  • Shape fill toggle fix (ColorPickerField)
    Now restores fill alpha to 1 when "no fill" is unchecked (previously only set alpha to 0 on check). Existing-shape ShapePopover derives noFillValue from activeObject.fill regex rather than the stale scribing.hasNoFill flag.

Remaining Work

  • Update scribing question form to be in line with other question types
  • Support null images (and add graceful handling of when image failed to load)
  • Add scribing to past answers view (create read-only viewer)
  • Ctrl + A to select all objects done
  • Redesign scribing layer selector

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Migrates the course assessment submission “scribing” feature from fabric.js v5 to v7, refactors the main scribing UI components from class-based JSX to functional TSX, and modernizes scribing state management to a Redux Toolkit slice to support the new Fabric v7 async/object model and reduce dependency vulnerabilities.

Changes:

  • Upgrade fabric dependency to v7.2.0 and update scribing canvas/toolbar implementations for Fabric v7 APIs and event ordering.
  • Replace legacy scribing reducer/action patterns with a Redux Toolkit slice (scribingActions) and wire explicit initialization into submission fetch.
  • Convert scribing UI components (Canvas/Toolbar/Layers/Fields) to TSX functional components and introduce a shared imperative ScribingCanvasRef.

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
client/tailwind.config.ts Adds a custom Tailwind utility used by the new scribing layout.
client/package.json Pins fabric.js to v7.2.0.
client/app/types/course/assessment/submission/answer/scribing.ts Introduces explicit TS interfaces for scribing answer payloads.
client/app/bundles/course/assessment/submission/reducers/scribing/index.ts New RTK slice for scribing state + actions.
client/app/bundles/course/assessment/submission/reducers/scribing.js Removes legacy Immer-based scribing reducer.
client/app/bundles/course/assessment/submission/propTypes.js Removes legacy scribing PropTypes shapes (now TS-driven).
client/app/bundles/course/assessment/submission/constants.ts Reworks scribing constants into typed as const arrays + union types.
client/app/bundles/course/assessment/submission/components/ScribingView/ScribingToolbar.tsx New TSX functional toolbar using hooks + canvas ref.
client/app/bundles/course/assessment/submission/components/ScribingView/ScribingToolbar.jsx Removes legacy class-based toolbar.
client/app/bundles/course/assessment/submission/components/ScribingView/ScribingCanvas.tsx New TSX functional canvas with Fabric v7 async hydration + imperative handle.
client/app/bundles/course/assessment/submission/components/ScribingView/ScribingCanvas.jsx Removes legacy class-based canvas.
client/app/bundles/course/assessment/submission/components/ScribingView/LayersComponent.tsx New TSX functional layers popover component.
client/app/bundles/course/assessment/submission/components/ScribingView/LayersComponent.jsx Removes legacy class-based layers component.
client/app/bundles/course/assessment/submission/components/ScribingView/index.tsx Lifts and shares the canvas imperative ref; lazy-loads new TSX components.
client/app/bundles/course/assessment/submission/components/ScribingView/index.jsx Removes legacy ScribingView component wrapper.
client/app/bundles/course/assessment/submission/components/ScribingView/fields/ShapeField.tsx Converts shape selector field to TSX + typed shape unions.
client/app/bundles/course/assessment/submission/components/ScribingView/fields/ShapeField.jsx Removes legacy JSX shape field.
client/app/bundles/course/assessment/submission/components/ScribingView/fields/ColorPickerField.jsx Fixes “no fill” toggle to correctly restore alpha on uncheck.
client/app/bundles/course/assessment/submission/components/ScribingView/test/index.test.tsx Updates test to use new scribingActions API.
client/app/bundles/course/assessment/submission/actions/index.js Dispatches scribingActions.initialize during submission fetch.
client/app/bundles/course/assessment/submission/actions/answers/scribing.ts New thunk for updating scribing answer via API using RTK slice status flags.
client/app/bundles/course/assessment/submission/actions/answers/scribing.js Removes legacy scribing action creators module.
Comments suppressed due to low confidence (1)

client/app/bundles/course/assessment/submission/actions/index.js:104

  • scribingActions.initialize is dispatched on fetchSubmission, but the scribing state was previously also re-initialized on other submission lifecycle successes (e.g., finalise/unsubmit) when the server returns a fresh data.answers payload. Consider dispatching scribingActions.initialize({ answers: data.answers }) in those success paths too, otherwise the scribing slice can become stale relative to FETCH_SUBMISSION_SUCCESS payloads.
        dispatch(
          historyActions.initSubmissionHistory({
            submissionId: data.submission.id,
            questionHistories: data.history.questions,
            questions: data.questions,
          }),
        );
        dispatch(scribingActions.initialize({ answers: data.answers }));
        dispatch(initiateAnswerFlagsForAnswers({ answers: data.answers }));
        dispatch(
          initiateLiveFeedbackChatPerQuestion({
            answerIds: data.answers.map((answer) => answer.id),
          }),
        );
      })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread client/app/bundles/course/assessment/submission/constants.ts
@adi-herwana-nus adi-herwana-nus force-pushed the adi/scribing-library-updates branch 3 times, most recently from 1444269 to 570bc97 Compare April 13, 2026 10:47
@adi-herwana-nus adi-herwana-nus force-pushed the adi/scribing-library-updates branch 3 times, most recently from ac94243 to 8533ecf Compare April 17, 2026 23:14
- ported top-level components and Redux state reducer from JS to TS
- minor improvements to component CSS
- implemented new CSS zoom-based canvas scaling
- fixed LayersComponent
- fixed toggling between fill / no fill for existing shapes
@adi-herwana-nus adi-herwana-nus force-pushed the adi/scribing-library-updates branch from 8533ecf to 77d40b9 Compare April 17, 2026 23:17
@adi-herwana-nus adi-herwana-nus merged commit f61a7e5 into master Apr 17, 2026
14 checks passed
@adi-herwana-nus adi-herwana-nus deleted the adi/scribing-library-updates branch April 17, 2026 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants