2025-04-03 17:05:59 +01:00
import React , {
useEffect ,
useMemo ,
useCallback ,
useRef ,
useState ,
2025-07-02 15:49:49 +01:00
useContext ,
2025-04-03 17:05:59 +01:00
} from "react" ;
import { FabricJSCanvas , useFabricJSEditor } from "fabricjs-react" ;
import { fabric } from "fabric" ;
import { DockSidebar } from "Components/DockSidebar" ;
import { ActionButtons } from "Components/ActionButtons" ;
import MkdSDK from "Utils/MkdSDK" ;
import { Stack } from "Utils/utils" ;
import { EstimateModal } from "Components/EstimateModal" ;
import * as XLSX from "xlsx" ;
import { BuildCanvasFromLocalModal } from "Components/BuildCanvasFromLocalModal" ;
import { GlobalContext } from "../../globalContext" ;
import { clearClone , clone } from "Utils/DockBuilderUtils/clone" ;
import {
edgeDetection ,
handleEdgeDetection ,
2025-07-02 15:49:49 +01:00
handleIntersection ,
2025-04-03 17:05:59 +01:00
} from "Utils/DockBuilderUtils" ;
import {
CanvasModes ,
deleteIcon ,
DockPanelCategories ,
edgeSnapThreshold ,
nintyDeg ,
oneEightyDeg ,
oneFeet ,
rotateIcon ,
scaleFactor ,
2025-07-02 15:49:49 +01:00
twoSeventyDeg ,
2025-04-03 17:05:59 +01:00
} from "Utils/constants" ;
import {
reScaleXY ,
resolveHeight ,
2025-07-02 15:49:49 +01:00
resolveWidth ,
2025-04-03 17:05:59 +01:00
} from "Utils/DockBuilderUtils/edgeDetection" ;
import { DeleteIcon , RotateIcon } from "Assets/svgs" ;
import { capitalize } from "Utils/helper" ;
const sdk = new MkdSDK ( ) ;
const stack = new Stack ( ) ;
export const DockBuilder = ( ) => {
let clipboard = null ;
let mouseDownPoint = null ;
let shiftKeyDown = false ;
window . counter = 0 ;
let prevPointer = null ;
let isDragging = false ;
let isZoomed = false ;
let startX , startY ;
const { dispatch } = useContext ( GlobalContext ) ;
const { editor , onReady } = useFabricJSEditor ( { selection : true } ) ;
const canvasModeRef = useRef ( null ) ;
const fileRef = useRef ( ) ;
const imageRef = useRef ( ) ;
const editorMemo = useMemo ( ( ) => editor ? . canvas , [ editor ? . canvas ] ) ;
// const { dispatch } = React.useContext( AuthContext );
const [ objHovered , setObjHovered ] = useState ( false ) ;
const [ selectedItems , setSelectedItems ] = useState ( [ ] ) ;
const [ estimateLoading , setEstimateLoading ] = useState ( false ) ;
const [ showEstimateModal , setShowEstimateModal ] = useState ( false ) ;
const [ showBuildCanvasFromLocalModal , setShowBuildCanvasFromLocalModal ] =
useState ( false ) ;
const [ initialLoad , setInitialLoad ] = useState ( true ) ;
const [ linesAdded , setLinesAdded ] = useState ( false ) ;
const [ dockImage , setDockImage ] = useState ( null ) ;
const [ editorReady , setEditorReady ] = useState ( false ) ;
const [ dockLabel , setDockLabel ] = useState ( null ) ;
const [ dockDimensions , setDockDimensions ] = useState ( null ) ;
const deleteImg = document . createElement ( "img" ) ;
deleteImg . src = deleteIcon ;
const rotateImg = document . createElement ( "img" ) ;
rotateImg . src = rotateIcon ;
const onEstimateModalClose = useCallback ( ( ) => {
setShowEstimateModal ( false ) ;
} , [ showEstimateModal ] ) ;
const onEstimateModalOpen = useCallback ( ( ) => {
const ext = "png" ;
const base64 = editorMemo . toDataURL ( {
format : ext ,
2025-07-02 15:49:49 +01:00
enableRetinaScaling : true ,
2025-04-03 17:05:59 +01:00
} ) ;
setDockImage ( base64 ) ;
setShowEstimateModal ( true ) ;
} , [ showEstimateModal , editorMemo , dockImage ] ) ;
// const onAddRect = useCallback( () => {
// editor?.canvas?.add( rect );
// window.counter++;
// // newleft += 200;
// }, [ editor ] );
const toJSON = ( ) => {
// TODO: download the json file
// TODO: get json of editor content
// TODO: Ensure dockData is included in the json
// TODO: save the json to the local storage as dock
// TODO: name the file as paradise_dock_<timestamp here>.dock
} ;
const uploadFile = ( e ) => {
// TODO: Our own upload the file we must have downloaded previously
// TODO: extract the json from the file
// TODO: load the json to the editor
// TODO: render all
} ;
const downloadImage = useCallback ( ( ) => {
// // console.log( 'Download' )
const ext = "png" ;
// TODO: download the image
// TODO: get the json of the editor content
// TODO: extract the dockData from the json
// TODO: filter the dockData to ensure it does not contain snapClone
// TODO: generate the base64 image
// TODO: download the image and name it as paradise_dock_snapshot_<timestamp here>.${ext}
// TODO: download the excel file of the dockData you extracted
// TODO: check if dockData is empty
// TODO: generate the base64 image
// TODO: trigger download of the dockData you extracted in excel format
} , [ editorMemo ] ) ;
const renderBg = useCallback ( ( ) => {
const imageURL =
"https://s3.us-east-2.amazonaws.com/com.nds.images/download.png" ;
new fabric . Image . fromURL (
imageURL ,
function ( img ) {
img . set ( {
scaleX : editorMemo . width / img . width ,
2025-07-02 15:49:49 +01:00
scaleY : editorMemo . height / img . height ,
2025-04-03 17:05:59 +01:00
} ) ;
editorMemo . setBackgroundImage ( img ) ;
editorMemo . renderAll ( ) ;
// updateModifications( true, )
} ,
{
2025-07-02 15:49:49 +01:00
crossOrigin : "anonymous" ,
2025-04-03 17:05:59 +01:00
}
) ;
} , [ editorMemo ] ) ;
const downloadExcel = useCallback ( ( data ) => {
const worksheet = XLSX . utils . json _to _sheet ( data ) ;
const workbook = XLSX . utils . book _new ( ) ;
XLSX . utils . book _append _sheet ( workbook , worksheet , "Selected Items" ) ;
XLSX . writeFile ( workbook , "Paradise_dock_selected_items.xlsx" ) ;
} , [ ] ) ;
function updateZoom ( opt ) {
let delta = opt . e . deltaY ;
let pointer = editorMemo . getPointer ( opt . e ) ;
let zoom = editorMemo . getZoom ( ) ;
if ( delta > 0 ) {
zoom *= 1.1 ;
} else {
zoom /= 1.1 ;
}
if ( zoom > 5 ) zoom = 5 ;
if ( zoom < 1 ) zoom = 1 ;
// console.log( zoom )
// editorMemo.setBackgroundImageStretch=false
var vpt = editorMemo . viewportTransform ;
if ( zoom === 1 ) {
vpt [ 4 ] = 0 ;
vpt [ 5 ] = 0 ;
editorMemo . selection = true ;
this . isZoomed = false ;
}
if ( zoom > 1 ) {
this . isZoomed = true ;
}
fabric . util . animate ( {
startValue : editorMemo . getZoom ( ) ,
endValue : zoom ,
// duration: 0,
onChange : function ( value ) {
editorMemo . zoomToPoint ( { x : pointer . x , y : pointer . y } , value ) ;
} ,
2025-07-02 15:49:49 +01:00
fps : 1080 ,
2025-04-03 17:05:59 +01:00
} ) ;
opt . e . preventDefault ( ) ;
opt . e . stopPropagation ( ) ;
}
function handleZoomMouseDown ( options ) {
var pointer = editorMemo . getPointer ( options . e , true ) ;
mouseDownPoint = new fabric . Point ( pointer . x , pointer . y ) ;
if ( options . target === null && this . isZoomed ) {
editorMemo . upperCanvasEl . style . cursor = "grabbing" ;
editorMemo . selection = false ;
// this.lastPosX = options.e.movementX;
// this.lastPosY = options.e.movementY;
this . lastPosX = options . e . clientX ;
this . lastPosY = options . e . clientY ;
//
}
// else if ( options.target !== null && this.isZoomed ) {
// var activeObject = editorMemo.getActiveObject();
// if ( activeObject ) {
// activeObject.lockMovementX = true;
// activeObject.lockMovementY = true;
// activeObject.lockScalingX = true;
// activeObject.lockScalingY = true;
// }
// }
}
const handleZoomMouseUp = useCallback ( ( ) => {
editorMemo . defaultCursor = "default" ;
mouseDownPoint = null ;
editorMemo . selection = true ;
} , [ editor ] ) ;
function handleZoomMouseMove ( options ) {
if ( options . target === null && mouseDownPoint && this . isZoomed ) {
editorMemo . selection = false ;
editorMemo . upperCanvasEl . style . cursor = "grabbing" ;
var delta = new fabric . Point ( options . e . movementX , options . e . movementY ) ;
editorMemo . relativePan ( delta ) ;
editorMemo . viewportTransform [ 4 ] = Math . max (
Math . min ( editorMemo . viewportTransform [ 4 ] , 0 ) ,
editorMemo . getWidth ( ) - editorMemo . getWidth ( ) * editorMemo . getZoom ( )
) ;
editorMemo . viewportTransform [ 5 ] = Math . max (
Math . min ( editorMemo . viewportTransform [ 5 ] , 0 ) ,
editorMemo . getHeight ( ) - editorMemo . getHeight ( ) * editorMemo . getZoom ( )
) ;
// var vpt = this.viewportTransform;
// vpt[ 4 ] += options.e.clientX - this.lastPosX;
// vpt[ 5 ] += options.e.clientY - this.lastPosY;
requestAnimationFrame ( function ( ) {
editorMemo . renderAll ( ) ;
this . requestRenderAll ( ) ;
} ) ;
// this.lastPosX = options.e.movementX;
// this.lastPosY = options.e.movementY;
// this.lastPosX = options.e.clientX;
// this.lastPosY = options.e.clientY;
}
}
const addEdgeDetectionClone = useCallback (
( options ) => {
objectMoving = true ;
if ( options ) {
const selectedObj = options . target . setCoords ( ) ;
editorMemo . defaultCursor = "grabbing" ;
if (
selectedObj . dockData &&
selectedObj . dockData . itemName === DockPanelCategories . Accessories
) {
return ;
}
editorMemo . forEachObject ( ( obj ) => {
const pointer = { x : options . e . clientX , y : options . e . clientY } ;
if ( obj === selectedObj ) return ;
// canvas.setCursor('grabbing');
// // console.log(pointer.x, pointer.y);
if ( ! obj . snapClone && obj . type === "image" ) {
// // console.log( pointer, prevPointer )
if (
prevPointer &&
( prevPointer . x !== pointer . x || prevPointer . y !== pointer . y )
) {
switch ( true ) {
case edgeDetection ( selectedObj , obj , "left" ) :
clone ( selectedObj , editorMemo , obj , "left" ) ;
break ;
case edgeDetection ( selectedObj , obj , "right" ) :
clone ( selectedObj , editorMemo , obj , "right" ) ;
break ;
case edgeDetection ( selectedObj , obj , "top" ) :
clone ( selectedObj , editorMemo , obj , "top" ) ;
break ;
case edgeDetection ( selectedObj , obj , "bottom" ) :
clone ( selectedObj , editorMemo , obj , "bottom" ) ;
break ;
case edgeDetection ( selectedObj , obj , "neutral" ) :
clearClone ( editorMemo ) ;
break ;
default :
// console.log( "edge detection switch default" )
}
// prevPointer = null;
}
prevPointer = pointer ;
// // console.log( pointer, prevPointer )
}
// if ( edgeDetection( selectedObj, obj, "left" ) ) {
// clone( selectedObj, editorMemo, obj, "left" )
// } else {
// // clearClone( editorMemo )
// }
// if ( edgeDetection( selectedObj, obj, 'right' ) ) {
// clone( selectedObj, editorMemo, obj, "right" )
// } else {
// // clearClone( editorMemo )
// }
// if ( edgeDetection( selectedObj, obj, 'top' ) ) {
// clone( selectedObj, editorMemo, obj, "top" )
// } else {
// // clearClone( editorMemo )
// }
// if ( edgeDetection( selectedObj, obj, 'bottom' ) ) {
// clone( selectedObj, editorMemo, obj, "bottom" )
// } else {
// // clearClone( editorMemo )
// }
} ) ;
}
} ,
[ editor , editorMemo ]
) ;
const horizontalIndicatorLines = ( newTop , objTop ) => {
editorMemo . getObjects ( "line" ) . forEach ( ( obj ) => {
if ( obj . testLine ) {
editorMemo . remove ( obj ) ;
}
} ) ;
let line = new fabric . Line ( [ 0 , newTop , editorMemo . getWidth ( ) , newTop ] , {
stroke : "#AAAAAA" ,
2025-07-02 15:49:49 +01:00
testLine : true ,
2025-04-03 17:05:59 +01:00
// strokeDashArray: [ 5 ],
} ) ;
let line2 = new fabric . Line ( [ 0 , objTop , editorMemo . getWidth ( ) , objTop ] , {
stroke : "#AAAAAA" ,
2025-07-02 15:49:49 +01:00
testLine : true ,
2025-04-03 17:05:59 +01:00
// strokeDashArray: [ 5 ],
} ) ;
editorMemo . add ( line ) ;
editorMemo . add ( line2 ) ;
editorMemo . renderAll ( ) ;
} ;
const verticleIndicatorLines = ( newLeft , objLeft ) => {
editorMemo . getObjects ( "line" ) . forEach ( ( obj ) => {
if ( obj . testLine ) {
editorMemo . remove ( obj ) ;
}
} ) ;
let line = new fabric . Line ( [ newLeft , 0 , newLeft , editorMemo . getHeight ( ) ] , {
stroke : "#AAAAAA" ,
2025-07-02 15:49:49 +01:00
testLine : true ,
2025-04-03 17:05:59 +01:00
// strokeDashArray: [ 5 ],
} ) ;
let line2 = new fabric . Line ( [ objLeft , 0 , objLeft , editorMemo . getWidth ( ) ] , {
stroke : "#AAAAAA" ,
2025-07-02 15:49:49 +01:00
testLine : true ,
2025-04-03 17:05:59 +01:00
// strokeDashArray: [ 5 ],
} ) ;
editorMemo . add ( line ) ;
editorMemo . add ( line2 ) ;
editorMemo . renderAll ( ) ;
} ;
function edgeDetectionAndSnap ( options ) {
if ( this . isZoomed ) {
return ;
}
// TODO: Edge detection and snap to object within snap range
// TODO: Detect if the object is within the snap range of the selected object
// TODO: detect if the selected object contains dockData and dockData contains itemName of DockPanelCategories.Accessories || DockPanelCategories.BoatLift2 || DockPanelCategories.BoatLift4
// TODO: handle rotation of 0, 90, 180, 270 degrees
editorMemo . defaultCursor = "default" ;
prevPointer = null ;
clearClone ( editorMemo ) ;
}
const showHighlight = useCallback (
( event ) => {
if ( event . target ) {
if ( event . target . type === "image" ) {
setObjHovered ( true ) ;
// console.log(event.target.dockData)
if (
event . target &&
event . target . dockData &&
event . target . dockData . image
) {
imageRef . current . src = event . target . dockData . image ;
setDockLabel (
` ${
event . target . dockData . materials
? capitalize ( event . target . dockData . itemName )
: event . target . dockData . itemName
} ( ${
event . target . dockData . materials
? event . target . dockData . materials
: event . target . dockData . model
} ) `
) ;
setDockDimensions (
` ${ event . target . dockData . width } ' x ${ event . target . dockData . length } ' `
) ;
} else if (
event . target &&
event . target . dockData &&
event . target . dockData . thumbnail
) {
setDockLabel (
` ${ capitalize ( event . target . dockData . itemName ) } ( ${
event . target . dockData . name
} ) `
) ;
setDockDimensions (
` ${ event . target . dockData . width } ' x ${ event . target . dockData . length } ' `
) ;
imageRef . current . src = event . target . dockData . thumbnail ;
}
}
}
if ( ! event . target && objHovered ) {
setObjHovered ( false ) ;
setDockLabel ( null ) ;
setDockDimensions ( null ) ;
setDockDimensions ( null ) ;
imageRef . current . src = "" ;
}
} ,
[ objHovered ]
) ;
const hideHighlight = useCallback ( ( ) => {
if ( objHovered ) {
setObjHovered ( false ) ;
setDockLabel ( null ) ;
setDockDimensions ( null ) ;
imageRef . current . src = "" ;
}
} , [ objHovered ] ) ;
const updateModifications = useCallback (
( savehistory , event ) => {
if ( savehistory === true ) {
if ( ( event && event === "update" ) || event . target . dockData ) {
const json = editor ? . canvas ? . toJSON ( [ "dockData" , "snapClone" ] ) ;
const data = JSON . stringify ( json ) ;
stack . updateStack ( data ) ;
updateLocalstore ( ) ;
}
}
} ,
[ selectedItems , editor ]
) ;
const updateLocalstore = useCallback ( ( ) => {
const json = editor ? . canvas ? . toJSON ( [ "dockData" , "snapClone" ] ) ;
const newObjects = json . objects
. filter ( ( object ) => ! object . snapClone )
. filter ( Boolean ) ;
json . objects = newObjects ;
const items = json . objects
. map ( ( object ) => {
if ( object . dockData && ! object . snapClone ) {
return object . dockData ;
}
} )
. filter ( Boolean ) ;
setSelectedItems ( ( ) => [ ... items ] ) ;
const data = JSON . stringify ( json ) ;
localStorage . setItem ( "dock" , data ) ;
} , [ editor , selectedItems ] ) ;
const onUndoClick = useCallback ( ( ) => {
2025-07-02 17:17:20 +01:00
const undoResult = stack . undo ( ) ;
if ( undoResult && undoResult . currentState ) {
editorMemo . loadFromJSON ( undoResult . currentState , ( ) => {
editorMemo . renderAll ( ) ;
} ) ;
}
2025-04-03 17:05:59 +01:00
} , [ editorMemo ] ) ;
const onRedoClick = useCallback ( ( ) => {
2025-07-02 16:56:13 +01:00
const redoResult = stack . redo ( ) ;
if ( redoResult && redoResult . currentState ) {
// currentState is an array, get the first element
const state = Array . isArray ( redoResult . currentState )
? redoResult . currentState [ 0 ]
: redoResult . currentState ;
if ( state ) {
editorMemo . loadFromJSON ( state , ( ) => {
editorMemo . renderAll ( ) ;
} ) ;
}
}
2025-04-03 17:05:59 +01:00
} , [ editorMemo ] ) ;
const onPrintScreen = useCallback ( ( ) => {
2025-07-02 17:23:00 +01:00
// Save current background
const originalBgColor = editorMemo . backgroundColor ;
const originalBgImage = editorMemo . backgroundImage ;
// Set background to white for printing
editorMemo . setBackgroundColor (
"#fff" ,
editorMemo . renderAll . bind ( editorMemo )
) ;
editorMemo . setBackgroundImage ( null , editorMemo . renderAll . bind ( editorMemo ) ) ;
// Give time for background to update
setTimeout ( ( ) => {
const dataUrl = editorMemo . toDataURL ( {
format : "png" ,
enableRetinaScaling : true ,
} ) ;
const printWindow = window . open ( "" , "_blank" ) ;
if ( printWindow ) {
printWindow . document . write (
'<html><head><title>Print Canvas</title></head><body style="margin:0"><img src="' +
dataUrl +
'" style="width:100vw;max-width:100%"/></body></html>'
) ;
printWindow . document . close ( ) ;
printWindow . focus ( ) ;
printWindow . onload = function ( ) {
printWindow . print ( ) ;
printWindow . close ( ) ;
} ;
}
// Restore original background
editorMemo . setBackgroundColor (
originalBgColor ,
editorMemo . renderAll . bind ( editorMemo )
) ;
if ( originalBgImage ) {
editorMemo . setBackgroundImage (
originalBgImage ,
editorMemo . renderAll . bind ( editorMemo )
) ;
}
} , 200 ) ;
2025-04-03 17:05:59 +01:00
} , [ editorMemo ] ) ;
const CopySelection = ( ) => {
// TODO: Copy selection
2025-07-02 16:02:47 +01:00
const activeObject = editorMemo . getActiveObject ( ) ;
if ( activeObject ) {
activeObject . clone ( function ( cloned ) {
clipboard = cloned ;
} ) ;
}
2025-04-03 17:05:59 +01:00
} ;
const PasteSelection = ( ) => {
// TODO: Paste selection
2025-07-02 16:50:40 +01:00
if ( clipboard ) {
clipboard . clone ( function ( clonedObj ) {
// Offset the pasted object so it's visible
clonedObj . set ( {
left : ( clonedObj . left || 0 ) + 20 ,
top : ( clonedObj . top || 0 ) + 20 ,
evented : true ,
} ) ;
editorMemo . add ( clonedObj ) ;
editorMemo . setActiveObject ( clonedObj ) ;
editorMemo . renderAll ( ) ;
} ) ;
}
2025-04-03 17:05:59 +01:00
} ;
const onDeleteSelection = ( ) => {
// TODO: Delete selection
} ;
const addLines = useCallback ( ( ) => {
// Initiate a line instance
const editorHeight = editorMemo . getHeight ( ) ;
const division = editorHeight / oneFeet ;
if ( ! linesAdded ) {
let initialFt = 8 ;
let multiplier = 1 ;
for ( let i = division - 3 ; i > 0 ; i -- ) {
if ( multiplier > division - 4 ) {
break ;
}
let line = new fabric . Line (
[ 0 , oneFeet * i , editorMemo . getWidth ( ) , oneFeet * i ] ,
{
stroke : "#AAAAAA" ,
2025-07-02 15:49:49 +01:00
strokeDashArray : [ 5 ] ,
2025-04-03 17:05:59 +01:00
}
) ;
let text = new fabric . Text ( ` ${ initialFt * multiplier } ft ` , {
// stroke: '#000000',
fill : "#AAAAAA" ,
fontSize : 18 ,
left : editorMemo . getWidth ( ) - 40 ,
2025-07-02 15:49:49 +01:00
top : oneFeet * i ,
2025-04-03 17:05:59 +01:00
} ) ;
multiplier ++ ;
// Render the rectangle in canvas
editorMemo . add ( line ) . sendToBack ( line ) ;
editorMemo . add ( text ) . sendToBack ( text ) ;
editorMemo . renderAll ( ) ;
}
setLinesAdded ( true ) ;
}
} , [ editorMemo , linesAdded ] ) ;
const removeLines = useCallback ( ( ) => {
editorMemo . getObjects ( "line" ) . forEach ( ( line ) => {
editorMemo . remove ( line ) ;
} ) ;
editorMemo . getObjects ( "text" ) . forEach ( ( text ) => {
editorMemo . remove ( text ) ;
} ) ;
setLinesAdded ( false ) ;
} , [ editorMemo , linesAdded ] ) ;
const onCloseBuildFromLocalModal = useCallback ( ( ) => {
setShowBuildCanvasFromLocalModal ( false ) ;
dispatch ( {
type : "DOCK_LOADING" ,
2025-07-02 15:49:49 +01:00
payload : false ,
2025-04-03 17:05:59 +01:00
} ) ;
setInitialLoad ( false ) ;
} , [ editorMemo , showBuildCanvasFromLocalModal , dispatch , initialLoad ] ) ;
function renderIcon ( icon ) {
return function renderIcon ( ctx , left , top , styleOverride , fabricObject ) {
var size = this . cornerSize ;
ctx . save ( ) ;
ctx . translate ( left , top ) ;
ctx . rotate ( fabric . util . degreesToRadians ( fabricObject . angle ) ) ;
ctx . drawImage ( icon , - size / 2 , - size / 2 , size , size ) ;
ctx . restore ( ) ;
} ;
}
function deleteObject ( eventData , transform ) {
var target = transform . target ;
var canvas = target . canvas ;
canvas . remove ( target ) ;
canvas . requestRenderAll ( ) ;
}
useEffect ( ( ) => {
if ( editor ) {
editorMemo . backgroundColor = "#011e23" ;
editorMemo . enableRetinaScaling = false ;
// Use hardware acceleration
// editorMemo.renderOnAddRemove = false;
editorMemo . skipOffscreen = true ;
// Lower render quality setting
fabric . devicePixelRatio = 1 ;
fabric . Group . prototype . hasControls = true ;
fabric . Group . prototype . snapAngle = 45 ;
fabric . Group . prototype . snapThreshold = 5 ;
fabric . Group . prototype . stroke = "#0f75bc" ;
fabric . Object . prototype . snapAngle = 45 ;
// Optimize object rendering
fabric . Object . prototype . objectCaching = true ;
fabric . Object . prototype . snapThreshold = 5 ;
fabric . Object . prototype . setControlsVisibility ( {
tl : false , //top-left
mt : false , // middle-top
tr : true , //top-right
ml : false , //middle-left
mr : false , //middle-right
bl : false , // bottom-left
mb : false , //middle-bottom
br : false , //bottom-right
2025-07-02 15:49:49 +01:00
mtr : false , // rotate icon
2025-04-03 17:05:59 +01:00
} ) ;
// fabric.Object.prototype.controls.deleteControl = new fabric.Control( {
// x: -0.8,
// y: -1,
// offsetY: 16,
// offsetX: 16,
// cursorStyle: 'pointer',
// mouseUpHandler: deleteObject,
// render: renderIcon( deleteImg ),
// cornerPadding: 0,
// cornerSize: 24
// } );
fabric . Object . prototype . controls . tr = new fabric . Control ( {
x : 0.5 ,
y : - 0.5 ,
cursorStyle : "rotate" ,
offsetY : - 16 ,
offsetX : 16 ,
cornerPadding : 0 ,
actionHandler : fabric . controlsUtils . rotationWithSnapping ,
cursorStyleHandler : fabric . controlsUtils . rotationStyleHandler ,
withConnection : true ,
render : renderIcon ( rotateImg ) ,
cornerSize : 20 ,
actionName : "rotate" ,
sizeX : 40 ,
sizeY : 40 ,
touchSizeX : 40 ,
2025-07-02 15:49:49 +01:00
touchSizeY : 40 ,
2025-04-03 17:05:59 +01:00
} ) ;
fabric . Object . prototype . hasControls = true ;
fabric . Object . prototype . lockScalingX = true ;
fabric . Object . prototype . lockScalingY = true ;
fabric . Object . prototype . cornerSize = 5 ;
fabric . Line . prototype . hasBorders = false ;
fabric . Line . prototype . cornerSize = 0 ;
fabric . Line . prototype . borderColor = "transparent" ;
fabric . Line . prototype . hasRotatingPoint = false ;
fabric . Line . prototype . hasControls = false ;
fabric . Line . prototype . lockRotation = true ;
fabric . Line . prototype . hoverCursor = "default" ;
fabric . Line . prototype . selectable = false ;
fabric . Line . prototype . lockMovementX = true ;
fabric . Line . prototype . lockMovementY = true ;
fabric . Text . prototype . hasBorders = false ;
fabric . Text . prototype . cornerSize = 0 ;
fabric . Text . prototype . borderColor = "transparent" ;
fabric . Text . prototype . hasRotatingPoint = false ;
fabric . Text . prototype . hoverCursor = "default" ;
fabric . Text . prototype . selectable = false ;
fabric . Text . prototype . hasControls = false ;
fabric . Text . prototype . lockRotation = true ;
fabric . Text . prototype . lockMovementX = true ;
fabric . Text . prototype . lockMovementY = true ;
// fabric.Text.prototype.viewportCenter = true
setEditorReady ( true ) ;
editorMemo . setHeight ( window . innerHeight ) ;
editorMemo . setWidth ( window . innerWidth ) ;
fabric . Object . prototype . cornerColor = "#B9C0D4" ;
renderBg ( ) ;
addLines ( ) ;
// Intersection
// TODO: Edge detection and snap to object within snap range
editorMemo . on ( "object:moving" , edgeDetectionAndSnap ) ;
editorMemo . on ( "mouse:over" , showHighlight ) ;
editorMemo . on ( "mouse:out" , hideHighlight ) ;
editorMemo . on ( "object:modified" , ( e ) => updateModifications ( true , e ) ) ;
editorMemo . on ( "object:added" , ( e ) => updateModifications ( true , e ) ) ;
// Listen for the delete key event
document . onkeydown = ( event ) => {
if ( event . keyCode === 46 ) {
// 46 is the keyCode for the delete key
onDeleteSelection ( ) ;
}
} ;
const savedDock = localStorage . getItem ( "dock" )
? JSON . parse ( localStorage . getItem ( "dock" ) )
: null ;
if ( savedDock ) {
if ( initialLoad ) {
setShowBuildCanvasFromLocalModal ( true ) ;
}
} else if ( initialLoad ) {
setInitialLoad ( false ) ;
dispatch ( {
type : "DOCK_LOADING" ,
2025-07-02 15:49:49 +01:00
payload : false ,
2025-04-03 17:05:59 +01:00
} ) ;
}
}
} , [ editor ] ) ;
useEffect ( ( ) => {
if ( editor ) {
const handleResize = ( ) => {
// let canvas = canvasRef.current;
editorMemo . setWidth ( window . innerWidth ) ;
editorMemo . setHeight ( window . innerHeight ) ;
editorMemo . setBackgroundImage ( editorMemo . backgroundImage , ( bgImage ) => {
bgImage . set ( {
scaleX : editorMemo . width / bgImage . width ,
2025-07-02 15:49:49 +01:00
scaleY : editorMemo . height / bgImage . height ,
2025-04-03 17:05:59 +01:00
} ) ;
} ) ;
removeLines ( ) ;
addLines ( ) ;
editorMemo . renderAll ( ) ;
} ;
window . addEventListener ( "resize" , handleResize ) ;
handleResize ( ) ;
return ( ) => {
window . removeEventListener ( "resize" , handleResize ) ;
} ;
}
} , [ ] ) ;
return (
< div className = { ` relative ` } >
< >
< DockSidebar
onDownloadImage = { downloadImage }
onDownloadFile = { toJSON }
onUploadFile = { uploadFile }
fileRef = { fileRef }
editor = { editorMemo }
updateModifications = { updateModifications }
selectedItems = { selectedItems }
onEstimateModalOpen = { onEstimateModalOpen }
/ >
< FabricJSCanvas className = { ` floor-canvas ` } onReady = { onReady } / >
< ActionButtons
className = { ` absolute w-fit top-4 right-8 ` }
onCopy = { CopySelection }
onPaste = { PasteSelection }
onRedoClick = { onRedoClick }
onUndoClick = { onUndoClick }
onPrintScreen = { onPrintScreen }
onDeleteSelection = { onDeleteSelection }
/ >
< div
className = { ` rounded w-[10.875rem] absolute top-0 inset-x-0 m-auto ${
objHovered ? "" : "hidden"
} ` }
>
{ dockLabel ? (
< div
className = { ` w-full shadow text-white text-center font-medium text-[1.125rem] leading-[1.6875rem] tracking-[2%] font-['Rajdhani'] ` }
>
{ dockLabel }
< / div >
) : null }
< img
ref = { imageRef }
2025-07-02 15:49:49 +01:00
src = ''
alt = ''
2025-04-03 17:05:59 +01:00
width = { 174 }
height = { 116 }
className = { ` rounded relative ${ objHovered ? "" : "hidden" } ` }
/ >
{ dockDimensions ? (
< div
className = { ` w-full shadow text-white text-center font-medium text-[1.125rem] leading-[1.6875rem] tracking-[2%] font-['Rajdhani'] ` }
>
{ dockDimensions }
< / div >
) : null }
< / div >
< / >
< EstimateModal
loading = { estimateLoading }
modalCloseClick = { onEstimateModalClose }
showEstimateModal = { showEstimateModal }
dockImage = { dockImage }
selectedItems = { selectedItems }
/ >
< BuildCanvasFromLocalModal
modalCloseClick = { onCloseBuildFromLocalModal }
showBuildCanvasFromLocalModal = { showBuildCanvasFromLocalModal }
editor = { editorMemo }
/ >
< / div >
) ;
} ;