added undo redo for builder (not for simulation data)
This commit is contained in:
@@ -30,11 +30,11 @@ export function useModelEventHandlers({
|
||||
const { toggleView } = useToggleView();
|
||||
const { subModule } = useSubModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const { eventStore, productStore, assetStore } = useSceneContext();
|
||||
const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { removeAsset } = assetStore();
|
||||
const { removeEvent } = eventStore();
|
||||
const { removeEvent, getEventByModelUuid } = eventStore();
|
||||
const { getIsEventInProduct, addPoint, deleteEvent } = productStore();
|
||||
const { getEventByModelUuid } = eventStore();
|
||||
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||
@@ -152,6 +152,21 @@ export function useModelEventHandlers({
|
||||
|
||||
removeAsset(asset.modelUuid);
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Delete",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
assetData: asset,
|
||||
timeStap: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
echo.success("Model Removed!");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import useModuleStore from '../../../../../store/useModuleStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { SkeletonUtils } from 'three-stdlib';
|
||||
|
||||
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
||||
import { getAssetFieldApi } from '../../../../../services/factoryBuilder/asset/floorAsset/getAssetField';
|
||||
import { ModelAnimator } from './animator/modelAnimator';
|
||||
import { useModelEventHandlers } from './eventHandlers/useEventHandlers';
|
||||
|
||||
@@ -26,19 +26,31 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
||||
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
||||
const [isSelected, setIsSelected] = useState(false);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const [ikData, setIkData] = useState<any>();
|
||||
const [fieldData, setFieldData] = useState<any>();
|
||||
const { selectedAssets } = useSelectedAssets();
|
||||
|
||||
useEffect(() => {
|
||||
if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') {
|
||||
getAssetIksApi(asset.assetId).then((data) => {
|
||||
if (data.iks) {
|
||||
const iks: IK[] = data.iks;
|
||||
setIkData(iks);
|
||||
if (!fieldData && asset.eventData) {
|
||||
getAssetFieldApi(asset.assetId).then((data) => {
|
||||
if (data.type === 'ArmBot') {
|
||||
if (data.data) {
|
||||
const fieldData: IK[] = data.data;
|
||||
setFieldData(fieldData);
|
||||
}
|
||||
} else if (data.type === 'Conveyor') {
|
||||
if (data.data) {
|
||||
const fieldData = data.data;
|
||||
setFieldData(fieldData);
|
||||
}
|
||||
} else if (data.type === 'Crane') {
|
||||
if (data.data) {
|
||||
const fieldData = data.data;
|
||||
setFieldData(fieldData);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [asset.modelUuid, ikData])
|
||||
}, [asset.modelUuid, fieldData])
|
||||
|
||||
useEffect(() => {
|
||||
setDeletableFloorItem(null);
|
||||
@@ -157,7 +169,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
||||
position={asset.position}
|
||||
rotation={asset.rotation}
|
||||
visible={asset.isVisible}
|
||||
userData={{ ...asset, iks: ikData }}
|
||||
userData={{ ...asset, fieldData: fieldData }}
|
||||
castShadow
|
||||
receiveShadow
|
||||
onDoubleClick={(e) => {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { getUserData } from "../../../functions/getUserData";
|
||||
|
||||
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
||||
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
|
||||
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
|
||||
|
||||
export default function Controls() {
|
||||
const controlsRef = useRef<CameraControls>(null);
|
||||
@@ -144,6 +145,8 @@ export default function Controls() {
|
||||
|
||||
<UndoRedo2DControls />
|
||||
|
||||
<UndoRedo3DControls />
|
||||
|
||||
<TransformControl />
|
||||
|
||||
</>
|
||||
|
||||
@@ -28,7 +28,8 @@ const CopyPasteControls3D = ({
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
const { socket } = useSocketStore();
|
||||
const { assetStore, eventStore } = useSceneContext();
|
||||
const { assetStore, eventStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { addEvent } = eventStore();
|
||||
const { projectId } = useParams();
|
||||
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
||||
@@ -198,6 +199,9 @@ const CopyPasteControls3D = ({
|
||||
const addPastedObjects = () => {
|
||||
if (pastedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToCopy: AssetData[] = [];
|
||||
|
||||
pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
|
||||
if (pastedAsset) {
|
||||
const assetUuid = pastedAsset.userData.modelUuid;
|
||||
@@ -529,9 +533,45 @@ const CopyPasteControls3D = ({
|
||||
|
||||
updateAsset(asset.modelUuid, asset);
|
||||
}
|
||||
|
||||
assetsToCopy.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
position: [position.x, position.y, position.z],
|
||||
rotation: [pastedAsset.rotation.x, pastedAsset.rotation.y, pastedAsset.rotation.z],
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
isCollidable: false,
|
||||
opacity: 1,
|
||||
eventData: newFloorItem.eventData || undefined
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToCopy.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Copied",
|
||||
asset: assetsToCopy[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Copied",
|
||||
assets: assetsToCopy
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
|
||||
echo.success("Object added!");
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
@@ -26,7 +26,8 @@ const DuplicationControls3D = ({
|
||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
const { socket } = useSocketStore();
|
||||
const { assetStore, eventStore } = useSceneContext();
|
||||
const { assetStore, eventStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { addEvent } = eventStore();
|
||||
const { projectId } = useParams();
|
||||
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
||||
@@ -199,6 +200,9 @@ const DuplicationControls3D = ({
|
||||
const addDuplicatedAssets = () => {
|
||||
if (duplicatedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToDuplicate: AssetData[] = [];
|
||||
|
||||
duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => {
|
||||
if (duplicatedAsset) {
|
||||
const assetUuid = duplicatedAsset.userData.modelUuid;
|
||||
@@ -530,9 +534,45 @@ const DuplicationControls3D = ({
|
||||
|
||||
updateAsset(asset.modelUuid, asset);
|
||||
}
|
||||
|
||||
assetsToDuplicate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
modelUuid: newFloorItem.modelUuid,
|
||||
modelName: newFloorItem.modelName,
|
||||
assetId: newFloorItem.assetId,
|
||||
position: [position.x, position.y, position.z],
|
||||
rotation: [duplicatedAsset.rotation.x, duplicatedAsset.rotation.y, duplicatedAsset.rotation.z],
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
isCollidable: false,
|
||||
opacity: 1,
|
||||
eventData: newFloorItem.eventData || undefined
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToDuplicate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Copied",
|
||||
asset: assetsToDuplicate[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Copied",
|
||||
assets: assetsToDuplicate
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
|
||||
echo.success("Object duplicated!");
|
||||
clearSelection();
|
||||
};
|
||||
|
||||
@@ -37,7 +37,8 @@ function MoveControls3D({
|
||||
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { updateAsset, getAssetById } = assetStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
@@ -284,6 +285,9 @@ function MoveControls3D({
|
||||
const placeMovedAssets = () => {
|
||||
if (movedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
|
||||
movedObjects.forEach(async (movedAsset: THREE.Object3D) => {
|
||||
if (movedAsset) {
|
||||
const assetUuid = movedAsset.userData.modelUuid;
|
||||
@@ -291,6 +295,32 @@ function MoveControls3D({
|
||||
const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
|
||||
if (!asset || !model) return;
|
||||
const position = new THREE.Vector3().copy(model.position);
|
||||
const initialState = initialStates[movedAsset.uuid];
|
||||
|
||||
if (initialState) {
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
...asset,
|
||||
position: [
|
||||
initialState.position.x,
|
||||
initialState.position.y,
|
||||
initialState.position.z
|
||||
],
|
||||
rotation: [
|
||||
initialState.rotation?.x || 0,
|
||||
initialState.rotation?.y || 0,
|
||||
initialState.rotation?.z || 0
|
||||
]
|
||||
},
|
||||
newData: {
|
||||
...asset,
|
||||
position: [position.x, position.y, position.z],
|
||||
rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z]
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
const newFloorItem: Types.FloorItemType = {
|
||||
modelUuid: movedAsset.userData.modelUuid,
|
||||
@@ -368,6 +398,27 @@ function MoveControls3D({
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToUpdate.length > 0) {
|
||||
if (assetsToUpdate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: assetsToUpdate[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Update",
|
||||
assets: assetsToUpdate
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
}
|
||||
|
||||
echo.success("Object moved!");
|
||||
setIsMoving(false);
|
||||
clearSelection();
|
||||
|
||||
@@ -32,7 +32,8 @@ function RotateControls3D({
|
||||
const { socket } = useSocketStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { updateAsset } = assetStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
@@ -222,12 +223,43 @@ function RotateControls3D({
|
||||
const placeRotatedAssets = useCallback(() => {
|
||||
if (rotatedObjects.length === 0) return;
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToUpdate: AssetData[] = [];
|
||||
|
||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||
if (obj && obj.userData.modelUuid) {
|
||||
const asset = assetStore.getState().getAssetById(obj.userData.modelUuid);
|
||||
if (!asset) return;
|
||||
|
||||
const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
|
||||
|
||||
const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z];
|
||||
|
||||
if (initialRotations[obj.uuid] && initialPositions[obj.uuid]) {
|
||||
assetsToUpdate.push({
|
||||
type: "Asset",
|
||||
assetData: {
|
||||
...asset,
|
||||
position: [
|
||||
initialPositions[obj.uuid].x,
|
||||
initialPositions[obj.uuid].y,
|
||||
initialPositions[obj.uuid].z
|
||||
],
|
||||
rotation: [
|
||||
initialRotations[obj.uuid].x,
|
||||
initialRotations[obj.uuid].y,
|
||||
initialRotations[obj.uuid].z
|
||||
]
|
||||
},
|
||||
newData: {
|
||||
...asset,
|
||||
position: positionArray,
|
||||
rotation: rotationArray
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
const newFloorItem: Types.FloorItemType = {
|
||||
modelUuid: obj.userData.modelUuid,
|
||||
modelName: obj.userData.modelName,
|
||||
@@ -305,6 +337,27 @@ function RotateControls3D({
|
||||
}
|
||||
});
|
||||
|
||||
if (assetsToUpdate.length > 0) {
|
||||
if (assetsToUpdate.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: assetsToUpdate[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Update",
|
||||
assets: assetsToUpdate
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
}
|
||||
|
||||
setIsRotating(false);
|
||||
clearSelection();
|
||||
}, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
|
||||
|
||||
@@ -31,8 +31,9 @@ const SelectionControls3D: React.FC = () => {
|
||||
const boundingBoxRef = useRef<THREE.Mesh>();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { socket } = useSocketStore();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { removeAsset } = assetStore();
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { removeAsset, getAssetById } = assetStore();
|
||||
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
|
||||
const { toolMode } = useToolMode();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
@@ -266,12 +267,18 @@ const SelectionControls3D: React.FC = () => {
|
||||
const deleteSelection = () => {
|
||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||
|
||||
const undoActions: UndoRedo3DAction[] = [];
|
||||
const assetsToDelete: AssetData[] = [];
|
||||
|
||||
const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid);
|
||||
|
||||
selectedAssets.forEach((selectedMesh: THREE.Object3D) => {
|
||||
const asset = getAssetById(selectedMesh.userData.modelUuid);
|
||||
if (!asset) return;
|
||||
|
||||
//REST
|
||||
|
||||
// const response = await deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName);
|
||||
// const response = deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
@@ -321,6 +328,31 @@ const SelectionControls3D: React.FC = () => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
assetsToDelete.push({
|
||||
type: "Asset",
|
||||
assetData: asset,
|
||||
timeStap: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
if (assetsToDelete.length === 1) {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Asset-Delete",
|
||||
asset: assetsToDelete[0]
|
||||
});
|
||||
} else {
|
||||
undoActions.push({
|
||||
module: "builder",
|
||||
actionType: "Assets-Delete",
|
||||
assets: assetsToDelete
|
||||
});
|
||||
}
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: undoActions
|
||||
});
|
||||
|
||||
selectedUUIDs.forEach((uuid: string) => {
|
||||
|
||||
@@ -24,7 +24,8 @@ export default function TransformControl() {
|
||||
const { socket } = useSocketStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
||||
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { updateAsset, getAssetById } = assetStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
@@ -137,13 +138,37 @@ export default function TransformControl() {
|
||||
projectId
|
||||
};
|
||||
|
||||
// console.log('data: ', data);
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
|
||||
push3D({
|
||||
type: 'Scene',
|
||||
actions: [
|
||||
{
|
||||
module: "builder",
|
||||
actionType: "Asset-Update",
|
||||
asset: {
|
||||
type: "Asset",
|
||||
assetData: asset,
|
||||
newData: {
|
||||
...asset,
|
||||
position: [selectedFloorItem.position.x, 0, selectedFloorItem.position.z],
|
||||
rotation: [selectedFloorItem.rotation.x, selectedFloorItem.rotation.y, selectedFloorItem.rotation.z],
|
||||
},
|
||||
timeStap: new Date().toISOString()
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const isTextInput = (element: Element | null): boolean =>
|
||||
element instanceof HTMLInputElement ||
|
||||
element instanceof HTMLTextAreaElement ||
|
||||
element?.getAttribute("contenteditable") === "true";
|
||||
if (isTextInput(document.activeElement)) return;
|
||||
const keyCombination = detectModifierKeys(e);
|
||||
if (!selectedFloorItem) return;
|
||||
if (keyCombination === "G") {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store";
|
||||
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
||||
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
||||
|
||||
function useRedoHandler() {
|
||||
function use2DRedoHandler() {
|
||||
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
||||
const { redo2D, peekRedo2D } = undoRedo2DStore();
|
||||
const { addWall, removeWall, updateWall } = wallStore();
|
||||
@@ -352,4 +352,4 @@ function useRedoHandler() {
|
||||
return { handleRedo };
|
||||
}
|
||||
|
||||
export default useRedoHandler;
|
||||
export default use2DRedoHandler;
|
||||
@@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store";
|
||||
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
||||
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
||||
|
||||
function useUndoHandler() {
|
||||
function use2DUndoHandler() {
|
||||
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
||||
const { undo2D, peekUndo2D } = undoRedo2DStore();
|
||||
const { addWall, removeWall, updateWall } = wallStore();
|
||||
@@ -67,38 +67,37 @@ function useUndoHandler() {
|
||||
}
|
||||
|
||||
undo2D();
|
||||
|
||||
};
|
||||
|
||||
const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
|
||||
switch (point.type) {
|
||||
case 'Wall': createWallFromBackend(point.lineData); break;
|
||||
case 'Floor': createFloorFromBackend(point.lineData); break;
|
||||
case 'Zone': createZoneFromBackend(point.lineData); break;
|
||||
case 'Aisle': createAisleFromBackend(point.lineData); break;
|
||||
case 'Wall': createWallToBackend(point.lineData); break;
|
||||
case 'Floor': createFloorToBackend(point.lineData); break;
|
||||
case 'Zone': createZoneToBackend(point.lineData); break;
|
||||
case 'Aisle': createAisleToBackend(point.lineData); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
|
||||
switch (point.type) {
|
||||
case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break;
|
||||
case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break;
|
||||
case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break;
|
||||
case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break;
|
||||
case 'Wall': removeWallToBackend(point.lineData.wallUuid); break;
|
||||
case 'Floor': removeFloorToBackend(point.lineData.floorUuid); break;
|
||||
case 'Zone': removeZoneToBackend(point.lineData.zoneUuid); break;
|
||||
case 'Aisle': removeAisleToBackend(point.lineData.aisleUuid); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
|
||||
switch (point.type) {
|
||||
case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break;
|
||||
case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break;
|
||||
case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break;
|
||||
case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break;
|
||||
case 'Wall': updateWallToBackend(point.lineData.wallUuid, point.lineData); break;
|
||||
case 'Floor': updateFloorToBackend(point.lineData.floorUuid, point.lineData); break;
|
||||
case 'Zone': updateZoneToBackend(point.lineData.zoneUuid, point.lineData); break;
|
||||
case 'Aisle': updateAisleToBackend(point.lineData.aisleUuid, point.lineData); break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const createWallFromBackend = (wallData: Wall) => {
|
||||
const createWallToBackend = (wallData: Wall) => {
|
||||
addWall(wallData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -119,7 +118,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeWallFromBackend = (wallUuid: string) => {
|
||||
const removeWallToBackend = (wallUuid: string) => {
|
||||
removeWall(wallUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -140,7 +139,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => {
|
||||
const updateWallToBackend = (wallUuid: string, updatedData: Wall) => {
|
||||
updateWall(wallUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -161,7 +160,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const createFloorFromBackend = (floorData: Floor) => {
|
||||
const createFloorToBackend = (floorData: Floor) => {
|
||||
addFloor(floorData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -182,7 +181,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeFloorFromBackend = (floorUuid: string) => {
|
||||
const removeFloorToBackend = (floorUuid: string) => {
|
||||
removeFloor(floorUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -203,7 +202,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => {
|
||||
const updateFloorToBackend = (floorUuid: string, updatedData: Floor) => {
|
||||
updateFloor(floorUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -224,7 +223,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const createZoneFromBackend = (zoneData: Zone) => {
|
||||
const createZoneToBackend = (zoneData: Zone) => {
|
||||
addZone(zoneData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -245,7 +244,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeZoneFromBackend = (zoneUuid: string) => {
|
||||
const removeZoneToBackend = (zoneUuid: string) => {
|
||||
removeZone(zoneUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -266,7 +265,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => {
|
||||
const updateZoneToBackend = (zoneUuid: string, updatedData: Zone) => {
|
||||
updateZone(zoneUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -287,7 +286,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const createAisleFromBackend = (aisleData: Aisle) => {
|
||||
const createAisleToBackend = (aisleData: Aisle) => {
|
||||
addAisle(aisleData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -308,7 +307,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const removeAisleFromBackend = (aisleUuid: string) => {
|
||||
const removeAisleToBackend = (aisleUuid: string) => {
|
||||
removeAisle(aisleUuid);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -329,7 +328,7 @@ function useUndoHandler() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => {
|
||||
const updateAisleToBackend = (aisleUuid: string, updatedData: Aisle) => {
|
||||
updateAisle(aisleUuid, updatedData);
|
||||
if (projectId) {
|
||||
// API
|
||||
@@ -353,4 +352,4 @@ function useUndoHandler() {
|
||||
return { handleUndo };
|
||||
}
|
||||
|
||||
export default useUndoHandler;
|
||||
export default use2DUndoHandler;
|
||||
@@ -0,0 +1,318 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../../../functions/getUserData";
|
||||
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../sceneContext";
|
||||
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
|
||||
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi";
|
||||
|
||||
function use3DRedoHandler() {
|
||||
const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext();
|
||||
const { deleteEvent } = productStore();
|
||||
const { addEvent, removeEvent } = eventStore();
|
||||
const { updateAsset, removeAsset, addAsset } = assetStore();
|
||||
const { redo3D, peekRedo3D } = undoRedo3DStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { socket } = useSocketStore();
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleRedo = () => {
|
||||
const redoData = peekRedo3D();
|
||||
if (!redoData) return;
|
||||
|
||||
if (redoData.type === 'Scene') {
|
||||
const { actions } = redoData;
|
||||
|
||||
actions.forEach(action => {
|
||||
const { actionType } = action;
|
||||
|
||||
if ('asset' in action) {
|
||||
const asset = action.asset;
|
||||
|
||||
if (actionType === 'Asset-Add') {
|
||||
handleAdd(asset);
|
||||
} else if (actionType === 'Asset-Delete') {
|
||||
handleDelete(asset);
|
||||
} else if (actionType === 'Asset-Update') {
|
||||
handleUpdate(asset);
|
||||
} else if (actionType === 'Asset-Copied') {
|
||||
handleCopy(asset);
|
||||
} else if (actionType === 'Asset-Duplicated') {
|
||||
handleDuplicate(asset);
|
||||
}
|
||||
|
||||
} else if ('assets' in action) {
|
||||
const assets = action.assets;
|
||||
|
||||
if (actionType === 'Assets-Add') {
|
||||
assets.forEach(handleAdd);
|
||||
} else if (actionType === 'Assets-Delete') {
|
||||
assets.forEach(handleDelete);
|
||||
} else if (actionType === 'Assets-Update') {
|
||||
assets.forEach(handleUpdate);
|
||||
} else if (actionType === 'Assets-Copied') {
|
||||
assets.forEach(handleCopy);
|
||||
} else if (actionType === 'Assets-Duplicated') {
|
||||
assets.forEach(handleDuplicate);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (redoData.type === 'UI') {
|
||||
// Handle UI actions if needed
|
||||
}
|
||||
|
||||
redo3D();
|
||||
};
|
||||
|
||||
|
||||
const handleAdd = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': addAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': addWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': deleteAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = (asset: AssetData) => {
|
||||
if (!asset.newData) return;
|
||||
switch (asset.type) {
|
||||
case 'Asset': updateAssetToBackend(asset.newData.modelUuid, asset.newData); break;
|
||||
case 'WallAsset': updateWallAssetToBackend(asset.newData.modelUuid, asset.newData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': copyAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': copyWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleDuplicate = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': duplicateAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const addAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => {
|
||||
updateAsset(modelUuid, updatedData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: updatedData.modelUuid,
|
||||
modelName: updatedData.modelName,
|
||||
assetId: updatedData.assetId,
|
||||
position: updatedData.position,
|
||||
rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const copyAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const addWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const deleteWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const copyWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const duplicateWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
return { handleRedo };
|
||||
}
|
||||
|
||||
export default use3DRedoHandler;
|
||||
@@ -0,0 +1,323 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../../../functions/getUserData";
|
||||
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../sceneContext";
|
||||
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||
import { useSocketStore } from "../../../../../store/builder/store";
|
||||
|
||||
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||
|
||||
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi";
|
||||
|
||||
function use3DUndoHandler() {
|
||||
const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext();
|
||||
const { deleteEvent } = productStore();
|
||||
const { addEvent, removeEvent } = eventStore();
|
||||
const { updateAsset, removeAsset, addAsset } = assetStore();
|
||||
const { undo3D, peekUndo3D } = undoRedo3DStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { userId, organization } = getUserData();
|
||||
const { projectId } = useParams();
|
||||
const { socket } = useSocketStore();
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productUuid: string,
|
||||
projectId: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productUuid: productUuid,
|
||||
projectId: projectId,
|
||||
eventDatas: eventData,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleUndo = () => {
|
||||
const unDoData = peekUndo3D();
|
||||
if (!unDoData) return;
|
||||
|
||||
if (unDoData.type === 'Scene') {
|
||||
const { actions } = unDoData;
|
||||
|
||||
actions.forEach(action => {
|
||||
const { actionType } = action;
|
||||
|
||||
if ('asset' in action) {
|
||||
const asset = action.asset;
|
||||
|
||||
if (actionType === 'Asset-Add') {
|
||||
handleDelete(asset);
|
||||
} else if (actionType === 'Asset-Delete') {
|
||||
handleAdd(asset);
|
||||
} else if (actionType === 'Asset-Update') {
|
||||
handleUpdate(asset);
|
||||
} else if (actionType === 'Asset-Copied') {
|
||||
handleCopy(asset);
|
||||
} else if (actionType === 'Asset-Duplicated') {
|
||||
handleDuplicate(asset);
|
||||
}
|
||||
|
||||
} else if ('assets' in action) {
|
||||
const assets = action.assets;
|
||||
|
||||
if (actionType === 'Assets-Add') {
|
||||
assets.forEach(handleDelete);
|
||||
} else if (actionType === 'Assets-Delete') {
|
||||
assets.forEach(handleAdd);
|
||||
} else if (actionType === 'Assets-Update') {
|
||||
assets.forEach(handleUpdate);
|
||||
} else if (actionType === 'Assets-Copied') {
|
||||
assets.forEach(handleCopy);
|
||||
} else if (actionType === 'Assets-Duplicated') {
|
||||
assets.forEach(handleDuplicate);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (unDoData.type === 'UI') {
|
||||
// Handle UI actions if needed
|
||||
}
|
||||
|
||||
undo3D();
|
||||
};
|
||||
|
||||
|
||||
const handleAdd = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': addAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': addWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': deleteAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': updateAssetToBackend(asset.assetData.modelUuid, asset.assetData); break;
|
||||
case 'WallAsset': updateWallAssetToBackend(asset.assetData.modelUuid, asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': copyAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': copyWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
const handleDuplicate = (asset: AssetData) => {
|
||||
switch (asset.type) {
|
||||
case 'Asset': duplicateAssetToBackend(asset.assetData); break;
|
||||
case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const addAssetToBackend = (assetData: Asset) => {
|
||||
addAsset(assetData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
assetId: assetData.assetId,
|
||||
position: assetData.position,
|
||||
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
eventData: {},
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
if (assetData.eventData) {
|
||||
data.eventData = assetData.eventData;
|
||||
addEvent(assetData.eventData as EventsSchema);
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => {
|
||||
updateAsset(modelUuid, updatedData);
|
||||
if (projectId) {
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: updatedData.modelUuid,
|
||||
modelName: updatedData.modelName,
|
||||
assetId: updatedData.assetId,
|
||||
position: updatedData.position,
|
||||
rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId,
|
||||
userId
|
||||
};
|
||||
|
||||
// API
|
||||
|
||||
// setAssetsApi(data);
|
||||
|
||||
//SOCKET
|
||||
|
||||
socket.emit("v1:model-asset:add", data);
|
||||
}
|
||||
}
|
||||
|
||||
const copyAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateAssetToBackend = (assetData: Asset) => {
|
||||
//REST
|
||||
|
||||
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modelUuid: assetData.modelUuid,
|
||||
modelName: assetData.modelName,
|
||||
socketId: socket.id,
|
||||
userId,
|
||||
versionId: selectedVersion?.versionId || '',
|
||||
projectId
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:model-asset:delete', data)
|
||||
|
||||
removeEvent(assetData.modelUuid);
|
||||
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||
|
||||
updatedEvents.forEach((event) => {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productUuid,
|
||||
projectId || '',
|
||||
event
|
||||
);
|
||||
})
|
||||
|
||||
if (response) {
|
||||
|
||||
removeAsset(assetData.modelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
const addWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const deleteWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const copyWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
const duplicateWallAssetToBackend = (assetData: WallAsset) => {
|
||||
|
||||
}
|
||||
|
||||
return { handleUndo };
|
||||
}
|
||||
|
||||
export default use3DUndoHandler;
|
||||
@@ -4,21 +4,21 @@ import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModi
|
||||
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
|
||||
import { useVersionContext } from '../../../../builder/version/versionContext';
|
||||
|
||||
import useUndoHandler from '../handlers/useUndoHandler';
|
||||
import useRedoHandler from '../handlers/useRedoHandler';
|
||||
import use2DUndoHandler from '../handlers/use2DUndoHandler';
|
||||
import use2DRedoHandler from '../handlers/use2DRedoHandler';
|
||||
|
||||
function UndoRedo2DControls() {
|
||||
const { undoRedo2DStore } = useSceneContext();
|
||||
const { undoStack, redoStack } = undoRedo2DStore();
|
||||
const { toggleView } = useToggleView();
|
||||
const { handleUndo } = useUndoHandler();
|
||||
const { handleRedo } = useRedoHandler();
|
||||
const { handleUndo } = use2DUndoHandler();
|
||||
const { handleRedo } = use2DRedoHandler();
|
||||
const { socket } = useSocketStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
|
||||
useEffect(() => {
|
||||
console.log(undoStack, redoStack);
|
||||
// console.log(undoStack, redoStack);
|
||||
}, [undoStack, redoStack]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useSceneContext } from '../../../sceneContext'
|
||||
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
|
||||
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
|
||||
import { useVersionContext } from '../../../../builder/version/versionContext';
|
||||
import useModuleStore from '../../../../../store/useModuleStore';
|
||||
|
||||
import use3DUndoHandler from '../handlers/use3DUndoHandler';
|
||||
import use3DRedoHandler from '../handlers/use3DRedoHandler';
|
||||
|
||||
function UndoRedo3DControls() {
|
||||
const { undoRedo3DStore } = useSceneContext();
|
||||
const { undoStack, redoStack } = undoRedo3DStore();
|
||||
const { toggleView } = useToggleView();
|
||||
const { activeModule } = useModuleStore();
|
||||
const { handleUndo } = use3DUndoHandler();
|
||||
const { handleRedo } = use3DRedoHandler();
|
||||
const { socket } = useSocketStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
|
||||
useEffect(() => {
|
||||
console.log(undoStack, redoStack);
|
||||
}, [undoStack, redoStack]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (keyCombination === 'Ctrl+Z') {
|
||||
handleUndo();
|
||||
}
|
||||
|
||||
if (keyCombination === 'Ctrl+Y') {
|
||||
handleRedo();
|
||||
}
|
||||
};
|
||||
|
||||
if (!toggleView) {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [toggleView, undoStack, redoStack, socket, selectedVersion, activeModule]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default UndoRedo3DControls;
|
||||
@@ -8,6 +8,7 @@ import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore
|
||||
import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore';
|
||||
|
||||
import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore';
|
||||
import { createUndoRedo3DStore, UndoRedo3DStoreType } from '../../store/builder/useUndoRedo3DStore';
|
||||
|
||||
import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore';
|
||||
import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore';
|
||||
@@ -31,6 +32,7 @@ type SceneContextValue = {
|
||||
floorStore: FloorStoreType,
|
||||
|
||||
undoRedo2DStore: UndoRedo2DStoreType,
|
||||
undoRedo3DStore: UndoRedo3DStoreType,
|
||||
|
||||
eventStore: EventStoreType,
|
||||
productStore: ProductStoreType,
|
||||
@@ -70,6 +72,7 @@ export function SceneProvider({
|
||||
const floorStore = useMemo(() => createFloorStore(), []);
|
||||
|
||||
const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []);
|
||||
const undoRedo3DStore = useMemo(() => createUndoRedo3DStore(), []);
|
||||
|
||||
const eventStore = useMemo(() => createEventStore(), []);
|
||||
const productStore = useMemo(() => createProductStore(), []);
|
||||
@@ -94,6 +97,7 @@ export function SceneProvider({
|
||||
zoneStore.getState().clearZones();
|
||||
floorStore.getState().clearFloors();
|
||||
undoRedo2DStore.getState().clearUndoRedo2D();
|
||||
undoRedo3DStore.getState().clearUndoRedo3D();
|
||||
eventStore.getState().clearEvents();
|
||||
productStore.getState().clearProducts();
|
||||
materialStore.getState().clearMaterials();
|
||||
@@ -106,7 +110,7 @@ export function SceneProvider({
|
||||
craneStore.getState().clearCranes();
|
||||
humanEventManagerRef.current.humanStates = [];
|
||||
craneEventManagerRef.current.craneStates = [];
|
||||
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]);
|
||||
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, undoRedo3DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]);
|
||||
|
||||
const contextValue = useMemo(() => (
|
||||
{
|
||||
@@ -117,6 +121,7 @@ export function SceneProvider({
|
||||
zoneStore,
|
||||
floorStore,
|
||||
undoRedo2DStore,
|
||||
undoRedo3DStore,
|
||||
eventStore,
|
||||
productStore,
|
||||
materialStore,
|
||||
@@ -132,7 +137,7 @@ export function SceneProvider({
|
||||
clearStores,
|
||||
layout
|
||||
}
|
||||
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]);
|
||||
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, undoRedo3DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]);
|
||||
|
||||
return (
|
||||
<SceneContext.Provider value={contextValue}>
|
||||
|
||||
@@ -22,7 +22,7 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
||||
const trySetup = () => {
|
||||
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
|
||||
|
||||
if (!targetMesh || !targetMesh.userData.iks || targetMesh.userData.iks.length < 1) {
|
||||
if (!targetMesh || !targetMesh.userData.fieldData || targetMesh.userData.fieldData.length < 1) {
|
||||
retryId = setTimeout(trySetup, 100);
|
||||
return;
|
||||
}
|
||||
@@ -34,8 +34,8 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
||||
});
|
||||
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
|
||||
|
||||
const rawIks: IK[] = targetMesh.userData.iks;
|
||||
const iks = rawIks.map((ik) => ({
|
||||
const rawIks: IK[] = targetMesh.userData.fieldData;
|
||||
const fieldData = rawIks.map((ik) => ({
|
||||
target: ik.target,
|
||||
effector: ik.effector,
|
||||
links: ik.links.map((link) => ({
|
||||
@@ -51,10 +51,10 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
||||
minheight: ik.minheight,
|
||||
}));
|
||||
|
||||
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
|
||||
const solver = new CCDIKSolver(OOI.Skinned_Mesh, fieldData);
|
||||
setIkSolver(solver);
|
||||
|
||||
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
|
||||
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, fieldData, 0.05)
|
||||
// scene.add(helper);
|
||||
};
|
||||
|
||||
|
||||
@@ -173,8 +173,8 @@ const ArmBotUI = () => {
|
||||
|
||||
const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || '');
|
||||
|
||||
const iks = targetMesh?.userData?.iks;
|
||||
const firstIK = Array.isArray(iks) && iks.length > 0 ? iks[0] : {};
|
||||
const fieldData = targetMesh?.userData?.fieldData;
|
||||
const firstIK = Array.isArray(fieldData) && fieldData.length > 0 ? fieldData[0] : {};
|
||||
|
||||
const { handlePointerDown } = useDraggableGLTF(
|
||||
updatePointToState,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
export const getAssetIksApi = async (assetId: string) => {
|
||||
export const getAssetFieldApi = async (assetId: string) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${url_Backend_dwinzo}/api/v2/getAssetIks/${assetId}`,
|
||||
`${url_Backend_dwinzo}/api/v2/getAssetField/${assetId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -20,13 +20,13 @@ export const getAssetIksApi = async (assetId: string) => {
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Failed to fetch assetIks");
|
||||
console.error("Failed to fetch asset field");
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
echo.error("Failed to get assetIks");
|
||||
echo.error("Failed to get asset field");
|
||||
if (error instanceof Error) {
|
||||
console.log(error.message);
|
||||
} else {
|
||||
78
app/src/store/builder/useUndoRedo3DStore.ts
Normal file
78
app/src/store/builder/useUndoRedo3DStore.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { create } from 'zustand';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { undoRedoConfig } from '../../types/world/worldConstants';
|
||||
|
||||
type UndoRedo3DStore = {
|
||||
undoStack: UndoRedo3DTypes[];
|
||||
redoStack: UndoRedo3DTypes[];
|
||||
|
||||
push3D: (entry: UndoRedo3DTypes) => void;
|
||||
undo3D: () => UndoRedo3DTypes | undefined;
|
||||
redo3D: () => UndoRedo3DTypes | undefined;
|
||||
clearUndoRedo3D: () => void;
|
||||
|
||||
peekUndo3D: () => UndoRedo3DTypes | undefined;
|
||||
peekRedo3D: () => UndoRedo3DTypes | undefined;
|
||||
};
|
||||
|
||||
export const createUndoRedo3DStore = () => {
|
||||
return create<UndoRedo3DStore>()(
|
||||
immer((set, get) => ({
|
||||
undoStack: [],
|
||||
redoStack: [],
|
||||
|
||||
push3D: (entry) => {
|
||||
set((state) => {
|
||||
state.undoStack.push(entry);
|
||||
|
||||
if (state.undoStack.length > undoRedoConfig.undoRedoCount) {
|
||||
state.undoStack.shift();
|
||||
}
|
||||
|
||||
state.redoStack = [];
|
||||
});
|
||||
},
|
||||
|
||||
undo3D: () => {
|
||||
let lastAction: UndoRedo3DTypes | undefined;
|
||||
set((state) => {
|
||||
lastAction = state.undoStack.pop();
|
||||
if (lastAction) {
|
||||
state.redoStack.unshift(lastAction);
|
||||
}
|
||||
});
|
||||
return lastAction;
|
||||
},
|
||||
|
||||
redo3D: () => {
|
||||
let redoAction: UndoRedo3DTypes | undefined;
|
||||
set((state) => {
|
||||
redoAction = state.redoStack.shift();
|
||||
if (redoAction) {
|
||||
state.undoStack.push(redoAction);
|
||||
}
|
||||
});
|
||||
return redoAction;
|
||||
},
|
||||
|
||||
clearUndoRedo3D: () => {
|
||||
set((state) => {
|
||||
state.undoStack = [];
|
||||
state.redoStack = [];
|
||||
});
|
||||
},
|
||||
|
||||
peekUndo3D: () => {
|
||||
const stack = get().undoStack;
|
||||
return stack.length > 0 ? stack[stack.length - 1] : undefined;
|
||||
},
|
||||
|
||||
peekRedo3D: () => {
|
||||
const stack = get().redoStack;
|
||||
return stack.length > 0 ? stack[0] : undefined;
|
||||
},
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
export type UndoRedo3DStoreType = ReturnType<typeof createUndoRedo3DStore>;
|
||||
65
app/src/types/builderTypes.d.ts
vendored
65
app/src/types/builderTypes.d.ts
vendored
@@ -253,4 +253,69 @@ type UndoRedo2DTypes = UndoRedo2DDraw | UndoRedo2DUi
|
||||
type UndoRedo2D = {
|
||||
undoStack: UndoRedo2DTypes[];
|
||||
redoStack: UndoRedo2DTypes[];
|
||||
};
|
||||
|
||||
|
||||
// Undo/Redo 3D
|
||||
|
||||
type AssetType = {
|
||||
type: "Asset";
|
||||
assetData: Asset;
|
||||
newData?: Asset;
|
||||
eventMetaData?: EventsSchema;
|
||||
timeStap: string;
|
||||
}
|
||||
|
||||
type WallAssetType = {
|
||||
type: "WallAsset";
|
||||
assetData: WallAsset;
|
||||
newData?: WallAsset;
|
||||
timeStap: string;
|
||||
}
|
||||
|
||||
type AssetData = AssetType | WallAssetType;
|
||||
|
||||
type UndoRedo3DActionBuilderSchema = {
|
||||
module: "builder";
|
||||
actionType: "Asset-Add" | "Asset-Delete" | "Asset-Update" | "Asset-Duplicated" | "Asset-Copied" | "Wall-Asset-Add" | "Wall-Asset-Delete" | "Wall-Asset-Update";
|
||||
asset: AssetData;
|
||||
}
|
||||
|
||||
type UndoRedo3DActionSimulationSchema = {
|
||||
module: "simulation";
|
||||
actionType: '';
|
||||
}
|
||||
|
||||
type UndoRedo3DActionSchema = UndoRedo3DActionBuilderSchema | UndoRedo3DActionSimulationSchema;
|
||||
|
||||
type UndoRedo3DActionsBuilderSchema = {
|
||||
module: "builder";
|
||||
actionType: "Assets-Add" | "Assets-Delete" | "Assets-Update" | "Assets-Duplicated" | "Assets-Copied" | "Wall-Assets-Add" | "Wall-Assets-Delete" | "Wall-Assets-Update";
|
||||
assets: AssetData[];
|
||||
}
|
||||
|
||||
type UndoRedo3DActionsSimulationSchema = {
|
||||
module: "simulation";
|
||||
actionType: '';
|
||||
}
|
||||
|
||||
type UndoRedo3DActionsSchema = UndoRedo3DActionsBuilderSchema | UndoRedo3DActionsSimulationSchema;
|
||||
|
||||
type UndoRedo3DAction = UndoRedo3DActionSchema | UndoRedo3DActionsSchema;
|
||||
|
||||
type UndoRedo3DDraw = {
|
||||
type: 'Scene';
|
||||
actions: UndoRedo3DAction[];
|
||||
};
|
||||
|
||||
type UndoRedo3DUi = {
|
||||
type: 'UI';
|
||||
action: any; // Define UI actions as needed
|
||||
}
|
||||
|
||||
type UndoRedo3DTypes = UndoRedo3DDraw | UndoRedo3DUi;
|
||||
|
||||
type UndoRedo3D = {
|
||||
undoStack: UndoRedo3DTypes[];
|
||||
redoStack: UndoRedo3DTypes[];
|
||||
};
|
||||
@@ -189,12 +189,9 @@ const KeyPressListener: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: KeyboardEvent) => {
|
||||
if (isTextInput(document.activeElement)) return;
|
||||
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE")
|
||||
return;
|
||||
if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") return;
|
||||
|
||||
if (keyCombination === "ESCAPE") {
|
||||
setWalkMode(false);
|
||||
|
||||
Reference in New Issue
Block a user