Fix healthchecks: install curl in backend and frontend images
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
import React, { useEffect, useRef, forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { fabric } from 'fabric';
|
||||
|
||||
const CADCanvas = forwardRef(({ activeTool, setActiveTool, onCanvasReady }, ref) => {
|
||||
const canvasEl = useRef(null);
|
||||
const fabricRef = useRef(null);
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const isDrawing = useRef(false);
|
||||
const startPoint = useRef(null);
|
||||
const tempShape = useRef(null);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getCanvas: () => fabricRef.current,
|
||||
toJSON: () => fabricRef.current?.toJSON(),
|
||||
loadFromJSON: (json) => {
|
||||
if (fabricRef.current) {
|
||||
fabricRef.current.loadFromJSON(json, fabricRef.current.renderAll.bind(fabricRef.current));
|
||||
}
|
||||
},
|
||||
addSvgObject: (svgStr) => {
|
||||
if (!fabricRef.current) return;
|
||||
fabric.loadSVGFromString(svgStr, (objects, options) => {
|
||||
const group = fabric.util.groupSVGElements(objects, options);
|
||||
group.set({ left: 100, top: 100, scaleX: 0.5, scaleY: 0.5 });
|
||||
fabricRef.current.add(group);
|
||||
fabricRef.current.setActiveObject(group);
|
||||
fabricRef.current.renderAll();
|
||||
});
|
||||
},
|
||||
addObjects: (objects) => {
|
||||
if (!fabricRef.current) return;
|
||||
objects.forEach(obj => {
|
||||
let shape;
|
||||
if (obj.type === 'line') {
|
||||
shape = new fabric.Line([obj.x1, obj.y1, obj.x2, obj.y2], { stroke: obj.stroke || '#000', strokeWidth: obj.strokeWidth || 1 });
|
||||
} else if (obj.type === 'circle') {
|
||||
shape = new fabric.Circle({ left: obj.left, top: obj.top, radius: obj.radius, fill: obj.fill || 'transparent', stroke: obj.stroke || '#000', strokeWidth: obj.strokeWidth || 1 });
|
||||
} else if (obj.type === 'polygon') {
|
||||
shape = new fabric.Polygon(obj.points.map(p => ({ x: p.x, y: p.y })), { fill: obj.fill || 'transparent', stroke: obj.stroke || '#000', strokeWidth: obj.strokeWidth || 1 });
|
||||
} else if (obj.type === 'rect') {
|
||||
shape = new fabric.Rect({ left: obj.left, top: obj.top, width: obj.width, height: obj.height, fill: obj.fill || 'transparent', stroke: obj.stroke || '#000', strokeWidth: obj.strokeWidth || 1 });
|
||||
}
|
||||
if (shape) fabricRef.current.add(shape);
|
||||
});
|
||||
fabricRef.current.renderAll();
|
||||
}
|
||||
}));
|
||||
|
||||
// Initialize fabric canvas
|
||||
useEffect(() => {
|
||||
if (canvasEl.current && !fabricRef.current) {
|
||||
fabricRef.current = new fabric.Canvas(canvasEl.current, {
|
||||
width: canvasEl.current.parentElement.clientWidth,
|
||||
height: canvasEl.current.parentElement.clientHeight,
|
||||
backgroundColor: '#fff',
|
||||
selection: true,
|
||||
});
|
||||
|
||||
if (onCanvasReady) onCanvasReady(fabricRef.current);
|
||||
|
||||
const handleResize = () => {
|
||||
if (canvasEl.current && fabricRef.current) {
|
||||
fabricRef.current.setWidth(canvasEl.current.parentElement.clientWidth);
|
||||
fabricRef.current.setHeight(canvasEl.current.parentElement.clientHeight);
|
||||
fabricRef.current.renderAll();
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (fabricRef.current) {
|
||||
fabricRef.current.dispose();
|
||||
fabricRef.current = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Zoom with mouse wheel
|
||||
useEffect(() => {
|
||||
if (!fabricRef.current) return;
|
||||
const canvas = fabricRef.current;
|
||||
const handleWheel = (opt) => {
|
||||
const delta = opt.e.deltaY;
|
||||
let zoom = canvas.getZoom();
|
||||
zoom *= 0.999 ** delta;
|
||||
if (zoom > 20) zoom = 20;
|
||||
if (zoom < 0.01) zoom = 0.01;
|
||||
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
|
||||
opt.e.preventDefault();
|
||||
opt.e.stopPropagation();
|
||||
setZoom(Math.round(zoom * 100) / 100);
|
||||
};
|
||||
canvas.on('mouse:wheel', handleWheel);
|
||||
return () => canvas.off('mouse:wheel', handleWheel);
|
||||
}, []);
|
||||
|
||||
// Drawing tool behavior
|
||||
useEffect(() => {
|
||||
if (!fabricRef.current) return;
|
||||
const canvas = fabricRef.current;
|
||||
|
||||
canvas.off('mouse:down');
|
||||
canvas.off('mouse:move');
|
||||
canvas.off('mouse:up');
|
||||
|
||||
if (activeTool === 'select') {
|
||||
canvas.isDrawingMode = false;
|
||||
canvas.selection = true;
|
||||
canvas.forEachObject(obj => obj.selectable = true);
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.selection = false;
|
||||
canvas.forEachObject(obj => obj.selectable = false);
|
||||
|
||||
if (activeTool === 'freehand') {
|
||||
canvas.isDrawingMode = true;
|
||||
canvas.freeDrawingBrush.width = 2;
|
||||
canvas.freeDrawingBrush.color = '#000';
|
||||
return;
|
||||
}
|
||||
|
||||
canvas.isDrawingMode = false;
|
||||
|
||||
canvas.on('mouse:down', (opt) => {
|
||||
isDrawing.current = true;
|
||||
const pointer = canvas.getPointer(opt.e);
|
||||
startPoint.current = { x: pointer.x, y: pointer.y };
|
||||
|
||||
if (activeTool === 'line') {
|
||||
tempShape.current = new fabric.Line(
|
||||
[pointer.x, pointer.y, pointer.x, pointer.y],
|
||||
{ stroke: '#000', strokeWidth: 2 }
|
||||
);
|
||||
} else if (activeTool === 'rect') {
|
||||
tempShape.current = new fabric.Rect({
|
||||
left: pointer.x,
|
||||
top: pointer.y,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: 'transparent',
|
||||
stroke: '#000',
|
||||
strokeWidth: 2,
|
||||
});
|
||||
} else if (activeTool === 'circle') {
|
||||
tempShape.current = new fabric.Circle({
|
||||
left: pointer.x,
|
||||
top: pointer.y,
|
||||
radius: 0,
|
||||
fill: 'transparent',
|
||||
stroke: '#000',
|
||||
strokeWidth: 2,
|
||||
});
|
||||
} else if (activeTool === 'text') {
|
||||
const text = new fabric.IText('Text', {
|
||||
left: pointer.x,
|
||||
top: pointer.y,
|
||||
fontSize: 20,
|
||||
fill: '#000',
|
||||
});
|
||||
canvas.add(text);
|
||||
canvas.setActiveObject(text);
|
||||
isDrawing.current = false;
|
||||
return;
|
||||
} else if (activeTool === 'dimension') {
|
||||
const line = new fabric.Line([pointer.x, pointer.y, pointer.x, pointer.y], {
|
||||
stroke: '#e74c3c', strokeWidth: 1, strokeDashArray: [5, 5]
|
||||
});
|
||||
const tick1 = new fabric.Line([pointer.x, pointer.y, pointer.x+10, pointer.y], { stroke: '#e74c3c', strokeWidth: 1 });
|
||||
const tick2 = new fabric.Line([pointer.x+10, pointer.y, pointer.x+10, pointer.y+10], { stroke: '#e74c3c', strokeWidth: 1 });
|
||||
const text = new fabric.Text('0', {
|
||||
left: pointer.x, top: pointer.y - 20, fontSize: 12, fill: '#e74c3c',
|
||||
});
|
||||
const group = new fabric.Group([line, tick1, tick2, text], {
|
||||
selectable: false, hasControls: false, hasBorders: false,
|
||||
});
|
||||
tempShape.current = { group, line, text, tick1, tick2, start: {x: pointer.x, y: pointer.y} };
|
||||
canvas.add(group);
|
||||
}
|
||||
if (tempShape.current && !(activeTool === 'dimension')) {
|
||||
canvas.add(tempShape.current);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on('mouse:move', (opt) => {
|
||||
if (!isDrawing.current || !tempShape.current) return;
|
||||
const pointer = canvas.getPointer(opt.e);
|
||||
|
||||
if (activeTool === 'line') {
|
||||
tempShape.current.set({ x2: pointer.x, y2: pointer.y });
|
||||
} else if (activeTool === 'rect') {
|
||||
const start = startPoint.current;
|
||||
let left = Math.min(start.x, pointer.x);
|
||||
let top = Math.min(start.y, pointer.y);
|
||||
let width = Math.abs(start.x - pointer.x);
|
||||
let height = Math.abs(start.y - pointer.y);
|
||||
tempShape.current.set({ left, top, width, height });
|
||||
} else if (activeTool === 'circle') {
|
||||
const start = startPoint.current;
|
||||
const radius = Math.sqrt(
|
||||
(pointer.x - start.x) ** 2 + (pointer.y - start.y) ** 2
|
||||
) / 2;
|
||||
tempShape.current.set({
|
||||
left: start.x - radius,
|
||||
top: start.y - radius,
|
||||
radius: radius,
|
||||
});
|
||||
} else if (activeTool === 'dimension') {
|
||||
const { group, line, text, tick1, tick2, start } = tempShape.current;
|
||||
// Update line
|
||||
line.set({ x2: pointer.x, y2: pointer.y });
|
||||
// Compute distance
|
||||
const dx = pointer.x - start.x;
|
||||
const dy = pointer.y - start.y;
|
||||
const dist = Math.sqrt(dx*dx + dy*dy).toFixed(1);
|
||||
text.set({ text: dist });
|
||||
// Update text position to middle of line
|
||||
const midX = (start.x + pointer.x) / 2;
|
||||
const midY = (start.y + pointer.y) / 2;
|
||||
text.set({ left: midX - 15, top: midY - 20 });
|
||||
// Update tick positions (simplified: put at start and end)
|
||||
tick1.set({ x1: start.x, y1: start.y, x2: start.x+10, y2: start.y });
|
||||
tick2.set({ x1: pointer.x, y1: pointer.y, x2: pointer.x-10, y2: pointer.y });
|
||||
// Add corner tick lines (just simple markers)
|
||||
// Refresh group
|
||||
group.addWithUpdate();
|
||||
}
|
||||
canvas.renderAll();
|
||||
});
|
||||
|
||||
canvas.on('mouse:up', () => {
|
||||
if (isDrawing.current && activeTool === 'dimension' && tempShape.current) {
|
||||
// Make dimension group selectable after placement
|
||||
tempShape.current.group.set({ selectable: true, hasControls: false, hasBorders: true });
|
||||
}
|
||||
isDrawing.current = false;
|
||||
tempShape.current = null;
|
||||
});
|
||||
}, [activeTool]);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%' }}>
|
||||
<canvas ref={canvasEl} />
|
||||
<div style={{ position: 'absolute', bottom: 10, right: 10, background: 'rgba(255,255,255,0.8)', padding: '4px 8px', borderRadius: 4, fontSize: 12 }}>
|
||||
Zoom: {Math.round(zoom * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default CADCanvas;
|
||||
Reference in New Issue
Block a user