939 lines
29 KiB
React
939 lines
29 KiB
React
import React, {
|
|
useEffect,
|
|
useMemo,
|
|
useCallback,
|
|
useRef,
|
|
useState,
|
|
useContext,
|
|
} 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,
|
|
handleIntersection,
|
|
} from "Utils/DockBuilderUtils";
|
|
import {
|
|
CanvasModes,
|
|
deleteIcon,
|
|
DockPanelCategories,
|
|
edgeSnapThreshold,
|
|
nintyDeg,
|
|
oneEightyDeg,
|
|
oneFeet,
|
|
rotateIcon,
|
|
scaleFactor,
|
|
twoSeventyDeg,
|
|
} from "Utils/constants";
|
|
import {
|
|
reScaleXY,
|
|
resolveHeight,
|
|
resolveWidth,
|
|
} 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,
|
|
enableRetinaScaling: true,
|
|
});
|
|
setDockImage(base64);
|
|
setShowEstimateModal(true);
|
|
}, [showEstimateModal, editorMemo, dockImage]);
|
|
|
|
// const onAddRect = useCallback( () => {
|
|
// editor?.canvas?.add( rect );
|
|
// window.counter++;
|
|
// // newleft += 200;
|
|
// }, [ editor ] );
|
|
|
|
const toJSON = () => {
|
|
// Get JSON of editor content, including dockData and snapClone
|
|
const json = editorMemo.toJSON(["dockData", "snapClone"]);
|
|
const data = JSON.stringify(json, null, 2);
|
|
// Save to localStorage
|
|
localStorage.setItem("dock", data);
|
|
const blob = new Blob([data], { type: "application/json" });
|
|
const anchor = document.createElement("a");
|
|
const timestamp = Date.now();
|
|
anchor.href = URL.createObjectURL(blob);
|
|
anchor.download = `paradise_dock_${timestamp}.dock`; // TODO: name the file as paradise_dock_<timestamp here>.dock
|
|
anchor.click();
|
|
};
|
|
|
|
const uploadFile = (e) => {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
const reader = new FileReader();
|
|
reader.onload = function (event) {
|
|
try {
|
|
const json = JSON.parse(event.target.result);
|
|
console.log(json);
|
|
editorMemo.loadFromJSON(json, () => {
|
|
editorMemo.renderAll();
|
|
});
|
|
} catch (err) {
|
|
alert("Invalid file format. Please select a valid .dock file.");
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
// Reset file input to allow reupload of the same file
|
|
e.target.value = "";
|
|
};
|
|
|
|
const downloadImage = useCallback(() => {
|
|
const ext = "png";
|
|
// Generate base64 image of the canvas
|
|
const base64 = editorMemo.toDataURL({
|
|
format: ext,
|
|
enableRetinaScaling: true,
|
|
});
|
|
// Download the image
|
|
const anchor = document.createElement("a");
|
|
const timestamp = Date.now();
|
|
anchor.href = base64;
|
|
anchor.download = `paradise_dock_snapshot_${timestamp}.${ext}`;
|
|
anchor.click();
|
|
|
|
// Extract dockData from all objects (excluding snapClone)
|
|
const json = editorMemo.toJSON(["dockData", "snapClone"]);
|
|
const dockDataArr = (json.objects || [])
|
|
.filter((object) => object.dockData && !object.snapClone)
|
|
.map((object) => object.dockData);
|
|
if (dockDataArr.length > 0) {
|
|
// Download as Excel
|
|
const worksheet = XLSX.utils.json_to_sheet(dockDataArr);
|
|
const workbook = XLSX.utils.book_new();
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Dock Data");
|
|
XLSX.writeFile(workbook, `paradise_dock_data_${timestamp}.xlsx`);
|
|
}
|
|
}, [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,
|
|
scaleY: editorMemo.height / img.height,
|
|
});
|
|
editorMemo.setBackgroundImage(img);
|
|
editorMemo.renderAll();
|
|
// updateModifications( true, )
|
|
},
|
|
{
|
|
crossOrigin: "anonymous",
|
|
}
|
|
);
|
|
}, [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);
|
|
},
|
|
fps: 1080,
|
|
});
|
|
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",
|
|
testLine: true,
|
|
// strokeDashArray: [ 5 ],
|
|
});
|
|
let line2 = new fabric.Line([0, objTop, editorMemo.getWidth(), objTop], {
|
|
stroke: "#AAAAAA",
|
|
testLine: true,
|
|
// 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",
|
|
testLine: true,
|
|
// strokeDashArray: [ 5 ],
|
|
});
|
|
let line2 = new fabric.Line([objLeft, 0, objLeft, editorMemo.getWidth()], {
|
|
stroke: "#AAAAAA",
|
|
testLine: true,
|
|
// 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(() => {
|
|
const undoResult = stack.undo();
|
|
if (undoResult && undoResult.currentState) {
|
|
editorMemo.loadFromJSON(undoResult.currentState, () => {
|
|
editorMemo.renderAll();
|
|
});
|
|
}
|
|
}, [editorMemo]);
|
|
|
|
const onRedoClick = useCallback(() => {
|
|
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();
|
|
});
|
|
}
|
|
}
|
|
}, [editorMemo]);
|
|
|
|
const onPrintScreen = useCallback(() => {
|
|
// 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);
|
|
}, [editorMemo]);
|
|
|
|
const CopySelection = () => {
|
|
const activeObject = editorMemo.getActiveObject();
|
|
if (activeObject) {
|
|
activeObject.clone(function (cloned) {
|
|
// Ensure dockData is copied
|
|
if (activeObject.dockData) {
|
|
cloned.dockData = { ...activeObject.dockData };
|
|
}
|
|
clipboard = cloned;
|
|
});
|
|
}
|
|
};
|
|
|
|
const PasteSelection = () => {
|
|
if (clipboard) {
|
|
clipboard.clone(function (clonedObj) {
|
|
// Ensure dockData is copied
|
|
if (clipboard.dockData) {
|
|
clonedObj.dockData = { ...clipboard.dockData };
|
|
}
|
|
// 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.requestRenderAll();
|
|
});
|
|
}
|
|
};
|
|
|
|
const onDeleteSelection = () => {
|
|
const activeObject = editorMemo.getActiveObject();
|
|
if (activeObject) {
|
|
editorMemo.remove(activeObject);
|
|
editorMemo.discardActiveObject();
|
|
editorMemo.renderAll();
|
|
updateModifications(true, { target: activeObject });
|
|
}
|
|
};
|
|
|
|
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",
|
|
strokeDashArray: [5],
|
|
}
|
|
);
|
|
|
|
let text = new fabric.Text(`${initialFt * multiplier}ft`, {
|
|
// stroke: '#000000',
|
|
fill: "#AAAAAA",
|
|
fontSize: 18,
|
|
left: editorMemo.getWidth() - 40,
|
|
top: oneFeet * i,
|
|
});
|
|
|
|
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",
|
|
payload: false,
|
|
});
|
|
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
|
|
mtr: false, // rotate icon
|
|
});
|
|
// 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,
|
|
touchSizeY: 40,
|
|
});
|
|
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",
|
|
payload: false,
|
|
});
|
|
}
|
|
}
|
|
}, [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,
|
|
scaleY: editorMemo.height / bgImage.height,
|
|
});
|
|
});
|
|
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}
|
|
src=''
|
|
alt=''
|
|
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>
|
|
);
|
|
};
|