Compare commits
4 Commits
2100daadeb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 34cb733ed7 | |||
| 20618725d5 | |||
| d55b1cb9b7 | |||
| 12546d02c4 |
@@ -155,12 +155,10 @@ export const DockBuilder = () => {
|
|||||||
const dockDataArr = (json.objects || [])
|
const dockDataArr = (json.objects || [])
|
||||||
.filter((object) => object.dockData && !object.snapClone)
|
.filter((object) => object.dockData && !object.snapClone)
|
||||||
.map((object) => object.dockData);
|
.map((object) => object.dockData);
|
||||||
|
|
||||||
if (dockDataArr.length > 0) {
|
if (dockDataArr.length > 0) {
|
||||||
// Download as Excel
|
// Download as Excel
|
||||||
const worksheet = XLSX.utils.json_to_sheet(dockDataArr);
|
downloadExcel(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]);
|
}, [editorMemo]);
|
||||||
|
|
||||||
@@ -414,95 +412,148 @@ export const DockBuilder = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function edgeDetectionAndSnap(options) {
|
function edgeDetectionAndSnap(options) {
|
||||||
if (editorMemo.getZoom() !== 1) {
|
if (this.isZoomed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Edge detection and snap to object within snap range
|
// TODO: Edge detection and snap to object within snap range
|
||||||
const selectedObj = options.target;
|
if (options && options.target) {
|
||||||
if (!selectedObj.dockData) return;
|
const selectedObj = options.target.setCoords();
|
||||||
|
|
||||||
// TODO: Detect if the selected object is in the allowed categories
|
// TODO: Detect if the object is within the snap range of the selected object
|
||||||
const snapCategories = [
|
// This is handled by the edgeDetection function which checks if objects are within snap threshold
|
||||||
DockPanelCategories.Accessories,
|
editorMemo.forEachObject((obj) => {
|
||||||
DockPanelCategories.BoatLift2,
|
if (obj === selectedObj) return;
|
||||||
DockPanelCategories.BoatLift4,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!snapCategories.includes(selectedObj.dockData.itemName)) return;
|
const detectedObjBound = obj.getBoundingRect();
|
||||||
|
const selectedObjBound = selectedObj.getBoundingRect();
|
||||||
|
|
||||||
let closestSnapPoint = null;
|
// Only process image objects that are not snap clones
|
||||||
let minDistance = edgeSnapThreshold;
|
if (!obj.snapClone && obj.type === "image") {
|
||||||
|
// TODO: handle rotation of 0, 90, 180, 270 degrees
|
||||||
|
|
||||||
editorMemo.forEachObject((obj) => {
|
// Handle TOP edge snapping
|
||||||
if (obj === selectedObj || !obj.dockData) return;
|
if (edgeDetection(selectedObj, obj, "top")) {
|
||||||
|
let newTop = selectedObjBound.top;
|
||||||
|
|
||||||
// TODO: Only consider objects in the allowed categories
|
// Handle different rotation combinations for top snapping
|
||||||
if (!snapCategories.includes(obj.dockData.itemName)) return;
|
if ([0, nintyDeg].includes(obj.angle)) {
|
||||||
|
if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) {
|
||||||
|
newTop = obj.oCoords.tl.y;
|
||||||
|
} else {
|
||||||
|
newTop = obj.oCoords.tl.y - selectedObjBound.height;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) {
|
||||||
|
newTop = obj.oCoords.tl.y - detectedObjBound.height;
|
||||||
|
} else {
|
||||||
|
newTop =
|
||||||
|
obj.oCoords.tl.y -
|
||||||
|
(selectedObjBound.height + detectedObjBound.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: handle rotation of 0, 90, 180, 270 degrees
|
// Apply the new position
|
||||||
const rotation = obj.angle % 360;
|
selectedObj.setPositionByOrigin(
|
||||||
const isRotated = rotation !== 0 && rotation !== 180;
|
{ x: selectedObj.oCoords.tl.x, y: newTop },
|
||||||
|
"left",
|
||||||
|
"top"
|
||||||
|
);
|
||||||
|
selectedObj.setCoords();
|
||||||
|
editorMemo.renderAll();
|
||||||
|
}
|
||||||
|
// Handle BOTTOM edge snapping
|
||||||
|
else if (edgeDetection(selectedObj, obj, "bottom")) {
|
||||||
|
let newTop = selectedObjBound.top;
|
||||||
|
|
||||||
const objCenter = obj.getCenterPoint();
|
// Handle different rotation combinations for bottom snapping
|
||||||
const selectedCenter = selectedObj.getCenterPoint();
|
if ([0, nintyDeg].includes(obj.angle)) {
|
||||||
|
if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) {
|
||||||
|
newTop =
|
||||||
|
obj.oCoords.tl.y +
|
||||||
|
(detectedObjBound.height + selectedObjBound.height);
|
||||||
|
} else {
|
||||||
|
newTop = obj.oCoords.tl.y + detectedObjBound.height;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ([oneEightyDeg, twoSeventyDeg].includes(selectedObj.angle)) {
|
||||||
|
newTop = obj.oCoords.tl.y + selectedObjBound.height;
|
||||||
|
} else {
|
||||||
|
newTop = obj.oCoords.tl.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate potential snap points
|
// Apply the new position
|
||||||
const snapPoints = [];
|
selectedObj.setPositionByOrigin(
|
||||||
|
{ x: selectedObj.oCoords.tl.x, y: newTop },
|
||||||
|
"left",
|
||||||
|
"top"
|
||||||
|
);
|
||||||
|
selectedObj.setCoords();
|
||||||
|
editorMemo.renderAll();
|
||||||
|
}
|
||||||
|
// Handle LEFT edge snapping
|
||||||
|
else if (edgeDetection(selectedObj, obj, "left")) {
|
||||||
|
let newLeft = selectedObjBound.left;
|
||||||
|
|
||||||
// Center point
|
// Handle different rotation combinations for left snapping
|
||||||
snapPoints.push(objCenter);
|
if ([0, twoSeventyDeg].includes(obj.angle)) {
|
||||||
|
if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) {
|
||||||
|
newLeft = obj.oCoords.tl.x;
|
||||||
|
} else {
|
||||||
|
newLeft = obj.oCoords.tl.x - selectedObjBound.width;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) {
|
||||||
|
newLeft = obj.oCoords.tl.x - detectedObjBound.width;
|
||||||
|
} else {
|
||||||
|
newLeft =
|
||||||
|
obj.oCoords.tl.x -
|
||||||
|
(selectedObjBound.width + detectedObjBound.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Edge points
|
// Apply the new position
|
||||||
if (!isRotated || rotation === 0 || rotation === 180) {
|
selectedObj.setPositionByOrigin(
|
||||||
// Horizontal edges
|
{ x: newLeft, y: selectedObj.oCoords.tl.y },
|
||||||
snapPoints.push(
|
"left",
|
||||||
new fabric.Point(objCenter.x, obj.getBoundingRect().top)
|
"top"
|
||||||
);
|
);
|
||||||
snapPoints.push(
|
selectedObj.setCoords();
|
||||||
new fabric.Point(
|
editorMemo.renderAll();
|
||||||
objCenter.x,
|
}
|
||||||
obj.getBoundingRect().top + obj.getBoundingRect().height
|
// Handle RIGHT edge snapping
|
||||||
)
|
else if (edgeDetection(selectedObj, obj, "right")) {
|
||||||
);
|
let newLeft = selectedObjBound.left;
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRotated || rotation === 90 || rotation === 270) {
|
// Handle different rotation combinations for right snapping
|
||||||
// Vertical edges
|
if ([0, twoSeventyDeg].includes(obj.angle)) {
|
||||||
snapPoints.push(
|
if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) {
|
||||||
new fabric.Point(obj.getBoundingRect().left, objCenter.y)
|
newLeft =
|
||||||
);
|
obj.oCoords.tl.x +
|
||||||
snapPoints.push(
|
(detectedObjBound.width + selectedObjBound.width);
|
||||||
new fabric.Point(
|
} else {
|
||||||
obj.getBoundingRect().left + obj.getBoundingRect().width,
|
newLeft = obj.oCoords.tl.x + detectedObjBound.width;
|
||||||
objCenter.y
|
}
|
||||||
)
|
} else {
|
||||||
);
|
if ([oneEightyDeg, nintyDeg].includes(selectedObj.angle)) {
|
||||||
}
|
newLeft = obj.oCoords.tl.x + selectedObjBound.width;
|
||||||
|
} else {
|
||||||
|
newLeft = obj.oCoords.tl.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find closest snap point
|
// Apply the new position
|
||||||
snapPoints.forEach((point) => {
|
selectedObj.setPositionByOrigin(
|
||||||
const distance = Math.sqrt(
|
{ x: newLeft, y: selectedObj.oCoords.tl.y },
|
||||||
Math.pow(selectedCenter.x - point.x, 2) +
|
"left",
|
||||||
Math.pow(selectedCenter.y - point.y, 2)
|
"top"
|
||||||
);
|
);
|
||||||
|
selectedObj.setCoords();
|
||||||
if (distance < minDistance) {
|
editorMemo.renderAll();
|
||||||
minDistance = distance;
|
}
|
||||||
closestSnapPoint = point;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Snap to closest point if found
|
|
||||||
if (closestSnapPoint) {
|
|
||||||
const selectedCenter = selectedObj.getCenterPoint();
|
|
||||||
selectedObj.set({
|
|
||||||
left: selectedObj.left + (closestSnapPoint.x - selectedCenter.x),
|
|
||||||
top: selectedObj.top + (closestSnapPoint.y - selectedCenter.y),
|
|
||||||
});
|
|
||||||
selectedObj.setCoords();
|
|
||||||
editorMemo.renderAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
editorMemo.defaultCursor = "default";
|
editorMemo.defaultCursor = "default";
|
||||||
@@ -609,26 +660,45 @@ export const DockBuilder = () => {
|
|||||||
|
|
||||||
const onUndoClick = useCallback(() => {
|
const onUndoClick = useCallback(() => {
|
||||||
const undoResult = stack.undo();
|
const undoResult = stack.undo();
|
||||||
if (undoResult && undoResult.currentState) {
|
if (undoResult.currentCount === -1) {
|
||||||
editorMemo.loadFromJSON(undoResult.currentState, () => {
|
return 0;
|
||||||
editorMemo.renderAll();
|
}
|
||||||
|
if (undoResult.currentCount === 0) {
|
||||||
|
editorMemo.getObjects().forEach((obj) => {
|
||||||
|
if (obj !== editorMemo.backgroundImage) {
|
||||||
|
if (obj.type == "image") {
|
||||||
|
editorMemo.remove(obj).renderAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = editorMemo.toJSON(["dockData"]);
|
||||||
|
localStorage.setItem("dock", JSON.stringify(data));
|
||||||
});
|
});
|
||||||
|
} else if (undoResult.currentCount > 0) {
|
||||||
|
const json = JSON.parse(undoResult.currentState);
|
||||||
|
editorMemo.loadFromJSON(
|
||||||
|
json,
|
||||||
|
() => {
|
||||||
|
editorMemo.renderAll();
|
||||||
|
},
|
||||||
|
(obj) => {}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [editorMemo]);
|
}, [editorMemo]);
|
||||||
|
|
||||||
const onRedoClick = useCallback(() => {
|
const onRedoClick = useCallback(() => {
|
||||||
const redoResult = stack.redo();
|
const redoResult = stack.redo();
|
||||||
if (redoResult && redoResult.currentState) {
|
if (!redoResult.currentState) {
|
||||||
// currentState is an array, get the first element
|
return 0;
|
||||||
const state = Array.isArray(redoResult.currentState)
|
|
||||||
? redoResult.currentState[0]
|
|
||||||
: redoResult.currentState;
|
|
||||||
if (state) {
|
|
||||||
editorMemo.loadFromJSON(state, () => {
|
|
||||||
editorMemo.renderAll();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("dock", redoResult.currentState);
|
||||||
|
editorMemo.loadFromJSON(
|
||||||
|
JSON.parse(redoResult.currentState),
|
||||||
|
() => {
|
||||||
|
editorMemo.renderAll();
|
||||||
|
},
|
||||||
|
(o) => {}
|
||||||
|
);
|
||||||
}, [editorMemo]);
|
}, [editorMemo]);
|
||||||
|
|
||||||
const onPrintScreen = useCallback(() => {
|
const onPrintScreen = useCallback(() => {
|
||||||
@@ -680,33 +750,42 @@ export const DockBuilder = () => {
|
|||||||
const CopySelection = () => {
|
const CopySelection = () => {
|
||||||
const activeObject = editorMemo.getActiveObject();
|
const activeObject = editorMemo.getActiveObject();
|
||||||
if (activeObject) {
|
if (activeObject) {
|
||||||
activeObject.clone(function (cloned) {
|
activeObject.clone(
|
||||||
|
function (cloned) {
|
||||||
|
clipboard = cloned;
|
||||||
|
},
|
||||||
// Ensure dockData is copied
|
// Ensure dockData is copied
|
||||||
if (activeObject.dockData) {
|
["dockData"]
|
||||||
cloned.dockData = { ...activeObject.dockData };
|
);
|
||||||
}
|
|
||||||
clipboard = cloned;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const PasteSelection = () => {
|
const PasteSelection = () => {
|
||||||
if (clipboard) {
|
if (clipboard) {
|
||||||
clipboard.clone(function (clonedObj) {
|
clipboard.clone(
|
||||||
|
function (clonedObj) {
|
||||||
|
// Offset the pasted object so it's visible
|
||||||
|
clonedObj.set({
|
||||||
|
left: clonedObj.left + 20,
|
||||||
|
top: clonedObj.top + 20,
|
||||||
|
evented: true,
|
||||||
|
});
|
||||||
|
// Add support for multiple selected objects
|
||||||
|
if (clonedObj.type === "activeSelection") {
|
||||||
|
clonedObj.canvas = editorMemo;
|
||||||
|
clonedObj.forEachObject(function (obj) {
|
||||||
|
editorMemo.add(obj);
|
||||||
|
});
|
||||||
|
clonedObj.setCoords();
|
||||||
|
} else {
|
||||||
|
editorMemo.add(clonedObj);
|
||||||
|
}
|
||||||
|
editorMemo.setActiveObject(clonedObj);
|
||||||
|
editorMemo.requestRenderAll();
|
||||||
|
},
|
||||||
// Ensure dockData is copied
|
// Ensure dockData is copied
|
||||||
if (clipboard.dockData) {
|
["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();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -806,13 +885,13 @@ export const DockBuilder = () => {
|
|||||||
fabric.devicePixelRatio = 1;
|
fabric.devicePixelRatio = 1;
|
||||||
fabric.Group.prototype.hasControls = true;
|
fabric.Group.prototype.hasControls = true;
|
||||||
fabric.Group.prototype.snapAngle = 45;
|
fabric.Group.prototype.snapAngle = 45;
|
||||||
fabric.Group.prototype.snapThreshold = 5;
|
fabric.Group.prototype.snapThreshold = 10;
|
||||||
fabric.Group.prototype.stroke = "#0f75bc";
|
fabric.Group.prototype.stroke = "#0f75bc";
|
||||||
fabric.Object.prototype.snapAngle = 45;
|
fabric.Object.prototype.snapAngle = 45;
|
||||||
// Optimize object rendering
|
// Optimize object rendering
|
||||||
fabric.Object.prototype.objectCaching = true;
|
fabric.Object.prototype.objectCaching = true;
|
||||||
|
|
||||||
fabric.Object.prototype.snapThreshold = 5;
|
fabric.Object.prototype.snapThreshold = 10;
|
||||||
fabric.Object.prototype.setControlsVisibility({
|
fabric.Object.prototype.setControlsVisibility({
|
||||||
tl: false, //top-left
|
tl: false, //top-left
|
||||||
mt: false, // middle-top
|
mt: false, // middle-top
|
||||||
|
|||||||
Reference in New Issue
Block a user