feat(scribing): migrate from fabric.js v5 to v7#8287
Merged
adi-herwana-nus merged 1 commit intomasterfrom Apr 17, 2026
Merged
Conversation
2d2762d to
542c249
Compare
There was a problem hiding this comment.
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
fabricdependency 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.initializeis dispatched onfetchSubmission, but the scribing state was previously also re-initialized on other submission lifecycle successes (e.g., finalise/unsubmit) when the server returns a freshdata.answerspayload. Consider dispatchingscribingActions.initialize({ answers: data.answers })in those success paths too, otherwise the scribing slice can become stale relative toFETCH_SUBMISSION_SUCCESSpayloads.
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.
1444269 to
570bc97
Compare
ac94243 to
8533ecf
Compare
- 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
8533ecf to
77d40b9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migration of scribing question components from fabric.js v5.3.0 to v7.2.0. This addresses several vulnerabilities associated with the
node-tarpackage (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:
Database view of above:
Sample of existing submission with Fabric v5.3.0 objects opened with Fabric 7.2.0 frontend:
Note that no database migrations were needed to maintain backward compatibility.
ScribingCanvas.jsx → ScribingCanvas.tsx
constructoruseRefhooks for shape/flag trackingcurrentStateIndexgetterscribingRef.current.currentStateIndexcanvasStatesgetterscribingRef.current.canvasStatescomponentDidMountuseEffect([answerId])shouldComponentUpdateuseEffecthooksdeleteActiveObjectsonKeyDownonMouseDownCanvasscribingRef.current = scribingState[answerId]during render)onMouseMoveCanvasonMouseOutonMouseOveronMouseUpCanvasonObjectMovingCanvasgetBoundingRect()offset — works for anyoriginX/originYonObjectSelectedonSelectionClearedonTextChangedscribingRef.currentmutation to fix Fabric v7 event ordering (text:editing:exitednow fires beforemouse:down)getCanvasPointgetFabricObjectsFromJsonfromObjectreturns a Promise)getMousePointscribblesAsJsongetterlayer.scribbleGroup.set({ visible })setCopiedCanvasObjectPositionrehydrateCanvasbackgroundImageacrosscanvas.clear()setCanvasStateAndUpdateAnswercloneTextdenormaliseScribbledisableObjectSelectionenableObjectSelectionselectableon existing canvas objects — avoids JSON round-trip that caused stale re-render when closing style popoversgenerateMouseDragPropertiesinitializeCanvasuseEffect;canvas.dispose()nowawaited — Fabric v7dispose()is async (fixesinsertBeforeDOM error on first load)initializeScribblesAndBackgroundnormaliseScribbleupdateAnswersaveScribblesisSavingScribblesre-entrant guard — Fabric v7obj.set()firesobject:modified, causing an infinite loop not present in old componentscaleCanvascanvasZoomstateundoredorenderScribingToolbar.jsx → ScribingToolbar.tsx
initializeColorDropdownsinitializePopoversconstructoruseStatehooks;forceUpdateviauseReducerfor selection and layer changesonChangeCompleteColoronChangeFontFamilyonChangeFontSizeonChangeSliderThicknessonClickColorPickeronClickDeleteonClickDrawingModeonClickLineModeonClickLineStyleChiponClickMoveModeonClickPopoveronClickRedoonClickSelectionModeonClickShapeModeonClickTypingChevrononClickTypingIcononClickTypingModeonClickUndoonClickZoomInonClickZoomOutonRequestCloseColorPickeronRequestClosePopovergetActiveObjectSelectedLineStylegetSavingStatussetSelectedShapesetToSelectToolrenderOther Significant Changes
Fabric v7 shifts the default
originX,originYvalues fromleft,toptocenter,centerrespectively.There are proposals to deprecate these fields entirely, so we are making the shift now to make future transitions to Fabric v8+ easier.
activeObjectno longer tracked in Redux. Exposed directly viaScribingCanvasRef.getActiveObject(), re-read on each render triggered byonSelectionChange.selection:created/updated/clearedevents drive a pub/subonSelectionChangecallback onScribingCanvasRef, which the toolbar subscribes to viauseEffectto trigger aforceUpdate.layerstracking moved from Redux state to an internaluseRefinScribingCanvas.Lifted to
index.tsx.ScribingCanvasusesforwardRef+useImperativeHandle, exposingScribingCanvasRef(getActiveObject,getCanvasWidth,getLayers,setLayerDisplay,onSelectionChange).All 13 canvas interaction-tracking variables (
mouseDownFlag,line,rect, etc.) converted fromuseState→useRef. Redux state accessed via sync-ref pattern (scribingRef.current = scribingState[answerId]during render).withCanvasHOFGuards every canvas event handler and
useEffectagainst missingcanvas/scribing. Returns a no-op if either is absent. AllowsuseEffect(withCanvas(...), [deps])directly.Dynamic canvas scalingCSSCanceled, as this was found to be nonfunctional in staging environment. Instead a replacement implementation forzoomon canvas wrapper div, updated by aResizeObserver+requestAnimationFramedebounce (avoids "ResizeObserver loop" errors). ReplacesscaleCanvas(). This allows scribing element to resize dynamically.scaleCanvas()has been created.forceUpdateviauseReducerin toolbar triggers re-read ofcanvasRef.getLayers()aftersetLayerDisplay.ColorPickerField)Now restores fill alpha to 1 when "no fill" is unchecked (previously only set alpha to 0 on check). Existing-shape
ShapePopoverderivesnoFillValuefromactiveObject.fillregex rather than the stalescribing.hasNoFillflag.Remaining Work
Ctrl + A to select all objectsdone