diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index af08226..aea7cd2 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -19,8 +19,9 @@ function AisleCreator() { const { toolMode } = useToolMode(); const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); - const { aisleStore } = useSceneContext(); + const { aisleStore, undoRedo2DStore } = useSceneContext(); const { addAisle, getAislePointById } = aisleStore(); + const { push2D } = undoRedo2DStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); @@ -107,7 +108,23 @@ function AisleCreator() { aisleWidth: aisleWidth } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -145,7 +162,23 @@ function AisleCreator() { gapLength: gapLength } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -182,7 +215,23 @@ function AisleCreator() { gapLength: gapLength } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -218,7 +267,23 @@ function AisleCreator() { aisleWidth: aisleWidth } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -256,7 +321,23 @@ function AisleCreator() { gapLength: gapLength } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -293,7 +374,23 @@ function AisleCreator() { isFlipped: isFlipped } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -329,7 +426,23 @@ function AisleCreator() { aisleWidth: aisleWidth } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -366,7 +479,23 @@ function AisleCreator() { isFlipped: isFlipped } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx index c9f5e9b..474eb9e 100644 --- a/app/src/modules/builder/line/line.tsx +++ b/app/src/modules/builder/line/line.tsx @@ -43,6 +43,13 @@ function Line({ points }: Readonly) { const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore(); const { selectedPoints } = useSelectedPoints(); + const [initialPositions, setInitialPositions] = useState<{ + aisles?: Aisle[], + walls?: Wall[], + floors?: Floor[], + zones?: Zone[] + }>({}); + const path = useMemo(() => { const [start, end] = points.map(p => new THREE.Vector3(...p.position)); return new THREE.LineCurve3(start, end); @@ -353,6 +360,17 @@ function Line({ points }: Readonly) { const offset = new THREE.Vector3().subVectors(midPoint, hit); setDragOffset(offset); + + if (points[0].pointType === 'Wall') { + const walls = getWallsByPointId(points[0].pointUuid); + setInitialPositions({ walls }); + } else if (points[0].pointType === 'Floor') { + const floors = getFloorsByPointId(points[0].pointUuid); + setInitialPositions({ floors }); + } else if (points[0].pointType === 'Zone') { + const zones = getZonesByPointId(points[0].pointUuid); + setInitialPositions({ zones }); + } } }; @@ -370,9 +388,7 @@ function Line({ points }: Readonly) { // API - // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall).catch((error) => { - // console.error('Error updating wall:', error); - // }); + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); // SOCKET @@ -386,6 +402,23 @@ function Line({ points }: Readonly) { socket.emit('v1:model-Wall:add', data); }) + + if (initialPositions.walls && initialPositions.walls.length > 0) { + const updatedPoints = initialPositions.walls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } } else if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { const updatedFloors1 = getFloorsByPointId(points[0].pointUuid); @@ -397,9 +430,7 @@ function Line({ points }: Readonly) { // API - // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor).catch((error) => { - // console.error('Error updating floor:', error); - // }); + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); // SOCKET @@ -413,6 +444,23 @@ function Line({ points }: Readonly) { socket.emit('v1:model-Floor:add', data); }) + + if (initialPositions.floors && initialPositions.floors.length > 0) { + const updatedPoints = initialPositions.floors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } } else if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') { const updatedZones1 = getZonesByPointId(points[0].pointUuid); @@ -424,9 +472,7 @@ function Line({ points }: Readonly) { // API - // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone).catch((error) => { - // console.error('Error updating zone:', error); - // }); + // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone); // SOCKET @@ -440,6 +486,23 @@ function Line({ points }: Readonly) { socket.emit('v1:zone:add', data); }) + + if (initialPositions.zones && initialPositions.zones.length > 0) { + const updatedPoints = initialPositions.zones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } } } diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx index fa12f61..8f800f0 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx @@ -36,7 +36,8 @@ function MoveControls2D({ const { projectId } = useParams(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); - const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); + const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); const { setPosition: setAislePosition, getAislesByPointId } = aisleStore(); const { setPosition: setWallPosition, getWallsByPointId } = wallStore(); const { setPosition: setFloorPosition, getFloorsByPointId } = floorStore(); @@ -223,6 +224,8 @@ function MoveControls2D({ const placeMovedAssets = () => { if (movedObjects.length === 0) return; + const undoPoints: UndoRedo2DDataTypeSchema[] = []; + movedObjects.forEach((movedObject: THREE.Object3D) => { if (movedObject.userData.pointUuid) { const point: Point = movedObject.userData as Point; @@ -236,45 +239,83 @@ function MoveControls2D({ // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); - // SOCKET + // SOCKET socket.emit('v1:model-aisle:add', { - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, + userId, + organization, aisleUuid: updatedAisle.aisleUuid, points: updatedAisle.points, type: updatedAisle.type - }) - }) + }); + + const old = initialStates[movedObject.uuid]; + if (old) { + undoPoints.push({ + type: 'Aisle', + lineData: { + ...updatedAisle, + points: [ + updatedAisle.points[0].pointUuid === point.pointUuid + ? { ...updatedAisle.points[0], position: [old.position.x, old.position.y, old.position.z] } + : updatedAisle.points[0], + updatedAisle.points[1].pointUuid === point.pointUuid + ? { ...updatedAisle.points[1], position: [old.position.x, old.position.y, old.position.z] } + : updatedAisle.points[1] + ] as [Point, Point], + }, + newData: updatedAisle, + timeStamp: new Date().toISOString(), + }); + } + }); } } else if (point.pointType === 'Wall') { const updatedWalls = getWallsByPointId(point.pointUuid); - if (updatedWalls && updatedWalls.length > 0 && projectId) { - updatedWalls.forEach((updatedWall) => { + if (updatedWalls?.length && projectId) { + updatedWalls.forEach(updatedWall => { // API // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); - // SOCKET + // SOCKET - const data = { + socket.emit('v1:model-Wall:add', { wallData: updatedWall, - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } + userId, + organization + }); - socket.emit('v1:model-Wall:add', data); + const old = initialStates[movedObject.uuid]; + if (old) { + undoPoints.push({ + type: 'Wall', + lineData: { + ...updatedWall, + points: [ + updatedWall.points[0].pointUuid === point.pointUuid + ? { ...updatedWall.points[0], position: [old.position.x, old.position.y, old.position.z] } + : updatedWall.points[0], + updatedWall.points[1].pointUuid === point.pointUuid + ? { ...updatedWall.points[1], position: [old.position.x, old.position.y, old.position.z] } + : updatedWall.points[1] + ] as [Point, Point], + }, + newData: updatedWall, + timeStamp: new Date().toISOString(), + }); + } }); } } else if (point.pointType === 'Floor') { const updatedFloors = getFloorsByPointId(point.pointUuid); - if (updatedFloors && updatedFloors.length > 0 && projectId) { - updatedFloors.forEach((updatedFloor) => { + if (updatedFloors?.length && projectId) { + updatedFloors.forEach(updatedFloor => { // API @@ -282,21 +323,36 @@ function MoveControls2D({ // SOCKET - const data = { + socket.emit('v1:model-Floor:add', { floorData: updatedFloor, - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } + userId, + organization + }); - socket.emit('v1:model-Floor:add', data); + const old = initialStates[movedObject.uuid]; + if (old) { + undoPoints.push({ + type: 'Floor', + lineData: { + ...updatedFloor, + points: updatedFloor.points.map(p => + p.pointUuid === point.pointUuid + ? { ...p, position: [old.position.x, old.position.y, old.position.z] } + : p + ), + }, + newData: updatedFloor, + timeStamp: new Date().toISOString(), + }); + } }); } } else if (point.pointType === 'Zone') { const updatedZones = getZonesByPointId(point.pointUuid); - if (updatedZones && updatedZones.length > 0 && projectId) { - updatedZones.forEach((updatedZone) => { + if (updatedZones?.length && projectId) { + updatedZones.forEach(updatedZone => { // API @@ -304,23 +360,49 @@ function MoveControls2D({ // SOCKET - const data = { + socket.emit('v1:zone:add', { zoneData: updatedZone, - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } + userId, + organization + }); - socket.emit('v1:zone:add', data); + const old = initialStates[movedObject.uuid]; + if (old) { + undoPoints.push({ + type: 'Zone', + lineData: { + ...updatedZone, + points: updatedZone.points.map(p => + p.pointUuid === point.pointUuid + ? { ...p, position: [old.position.x, old.position.y, old.position.z] } + : p + ), + }, + newData: updatedZone, + timeStamp: new Date().toISOString(), + }); + } }); } } } - }) + }); + + if (undoPoints.length > 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: undoPoints + } + ] + }); + } echo.success("Object moved!"); - clearSelection(); }; diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx index 9d90d3c..a823e55 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx @@ -22,7 +22,7 @@ import MoveControls2D from "./moveControls2D"; // import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; const SelectionControls2D: React.FC = () => { - const { camera, controls, gl, scene, raycaster, pointer } = useThree(); + const { camera, controls, gl, scene, pointer } = useThree(); const { toggleView } = useToggleView(); const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints(); const [movedObjects, setMovedObjects] = useState([]); @@ -38,11 +38,12 @@ const SelectionControls2D: React.FC = () => { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { hoveredLine, hoveredPoint } = useBuilderStore(); - const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); + const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); const { removePoint: removeAislePoint } = aisleStore(); const { removePoint: removeWallPoint } = wallStore(); - const { removePoint: removeFloorPoint } = floorStore(); - const { removePoint: removeZonePoint } = zoneStore(); + const { removePoint: removeFloorPoint, getFloorsByPointId, getFloorById } = floorStore(); + const { removePoint: removeZonePoint, getZonesByPointId, getZoneById } = zoneStore(); const isDragging = useRef(false); const isLeftMouseDown = useRef(false); @@ -223,6 +224,12 @@ const SelectionControls2D: React.FC = () => { const deleteSelection = () => { if (selectedPoints.length > 0 && duplicatedObjects.length === 0) { + const deletedPoints: UndoRedo2DDataTypeSchema[] = []; + const updatedPoints: UndoRedo2DDataTypeSchema[] = []; + + const processedFloors: UndoRedo2DDataTypeSchema[] = []; + const processedZones: UndoRedo2DDataTypeSchema[] = []; + selectedPoints.forEach((selectedPoint) => { if (selectedPoint.userData.pointUuid) { const point: Point = selectedPoint.userData as Point; @@ -249,6 +256,14 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-aisle:delete', data); } }); + + const removedAislesData = removedAisles.map((aisle) => ({ + type: "Aisle" as const, + lineData: aisle, + timeStamp: new Date().toISOString(), + })); + + deletedPoints.push(...removedAislesData); } } if (point.pointType === 'Wall') { @@ -274,9 +289,18 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-Wall:delete', data); } }); + + const removedWallsData = removedWalls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + timeStamp: new Date().toISOString(), + })); + + deletedPoints.push(...removedWallsData); } } if (point.pointType === 'Floor') { + const Floors = getFloorsByPointId(point.pointUuid); const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); if (removedFloors.length > 0) { removedFloors.forEach(floor => { @@ -299,6 +323,14 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-Floor:delete', data); } }); + + const removedFloorsData = removedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + timeStamp: new Date().toISOString(), + })); + + processedFloors.push(...removedFloorsData); } if (updatedFloors.length > 0) { updatedFloors.forEach(floor => { @@ -321,9 +353,19 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-Floor:add', data); } }); + + const updatedFloorsData = updatedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor, + newData: floor, + timeStamp: new Date().toISOString(), + })); + + processedFloors.push(...updatedFloorsData); } } if (point.pointType === 'Zone') { + const Zones = getZonesByPointId(point.pointUuid); const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); if (removedZones.length > 0) { removedZones.forEach(zone => { @@ -346,6 +388,14 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:zone:delete', data); } }); + + const removedZonesData = removedZones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + timeStamp: new Date().toISOString(), + })); + + processedZones.push(...removedZonesData); } if (updatedZones.length > 0) { updatedZones.forEach(zone => { @@ -368,11 +418,129 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:zone:add', data); } }); + + const updatedZonesData = updatedZones.map((zone) => ({ + type: "Zone" as const, + lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone, + newData: zone, + timeStamp: new Date().toISOString(), + })); + + processedZones.push(...updatedZonesData); } } } }) + setTimeout(() => { + if (processedFloors.length > 0) { + const floorMap = new Map(); + + for (const floor of processedFloors) { + if (floor.type !== 'Floor' || !floor.lineData.floorUuid) return; + const uuid = floor.lineData.floorUuid; + if (!floorMap.has(uuid)) { + floorMap.set(uuid, []); + } + floorMap.get(uuid)!.push(floor); + } + + floorMap.forEach((actions, uuid) => { + const hasDelete = actions.some(action => !('newData' in action)); + const hasUpdate = actions.some(action => 'newData' in action); + + if (hasDelete) { + deletedPoints.push({ + type: 'Floor', + lineData: actions[0].lineData as Floor, + timeStamp: new Date().toISOString() + }); + } else if (!hasDelete && hasUpdate) { + const floorData = getFloorById(uuid); + if (floorData) { + updatedPoints.push({ + type: 'Floor', + lineData: actions[0].lineData as Floor, + newData: floorData as Floor, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (processedZones.length > 0) { + const zoneMap = new Map(); + + for (const zone of processedZones) { + if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) return; + const uuid = zone.lineData.zoneUuid; + if (!zoneMap.has(uuid)) { + zoneMap.set(uuid, []); + } + zoneMap.get(uuid)!.push(zone); + } + + zoneMap.forEach((actions, uuid) => { + const hasDelete = actions.some(action => !('newData' in action)); + const hasUpdate = actions.some(action => 'newData' in action); + + if (hasDelete) { + deletedPoints.push({ + type: 'Zone', + lineData: actions[0].lineData as Zone, + timeStamp: new Date().toISOString() + }); + } else if (!hasDelete && hasUpdate) { + const zoneData = getZoneById(uuid); + if (zoneData) { + updatedPoints.push({ + type: 'Zone', + lineData: actions[0].lineData as Zone, + newData: zoneData as Zone, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (deletedPoints.length > 0 && updatedPoints.length > 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: deletedPoints + }, + { + actionType: 'Lines-Update', + points: updatedPoints + } + ] + }); + } else if (deletedPoints.length > 0 && updatedPoints.length === 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: deletedPoints + } + ] + }); + } else if (updatedPoints.length > 0 && deletedPoints.length === 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: updatedPoints + } + ] + }); + } + }, 0); } echo.success("Selected points removed!"); clearSelection(); @@ -380,6 +548,7 @@ const SelectionControls2D: React.FC = () => { return ( <> + diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts index e5f31a5..77ad0d6 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts @@ -296,7 +296,7 @@ function useRedoHandler() { // SOCKET const data = { - aisleData, + ...aisleData, projectId, versionId: selectedVersion?.versionId || '', userId, diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts index 357f36e..de9f3b1 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts @@ -297,7 +297,7 @@ function useUndoHandler() { // SOCKET const data = { - aisleData, + ...aisleData, projectId, versionId: selectedVersion?.versionId || '', userId,