diff --git a/app/src/assets/image/sampleDecal.png b/app/src/assets/image/sampleDecal.png new file mode 100644 index 0000000..ed8c9fd Binary files /dev/null and b/app/src/assets/image/sampleDecal.png differ diff --git a/app/src/components/icons/ExportCommonIcons.tsx b/app/src/components/icons/ExportCommonIcons.tsx index 7cee77f..691f50f 100644 --- a/app/src/components/icons/ExportCommonIcons.tsx +++ b/app/src/components/icons/ExportCommonIcons.tsx @@ -1345,4 +1345,135 @@ export const SuccessIcon = () => { ) +} + + +export const AlertIcon = () => { + return ( + + + + + + ) +} +export const NavigationIcon = () => { + return ( + + + + + + + ) +} +export const HangTagIcon = () => { + return ( + + + + + + + + + + + + ) +} +export const DecalInfoIcon = () => { + return ( + + + + + + + ) +} + + +export const LayeringBottomIcon = () => { + return ( + + + + + + ) +} + +export const LayeringTopIcon = () => { + return ( + + + + + + + ) +} + +export const ValueUpdateIcon = () => { + return ( + + + + + + + ) +} + + +export const ListTaskIcon = () => { + return ( + + + + ) +} + +export const LocationPinIcon = () => { + return ( + + + + + ) +} + +export const ClockThreeIcon = () => { + return ( + + + + + ) +} + +export const SlectedTickIcon = () => { + return ( + + + + + ) +} + +export const HourGlassIcon = () => { + return ( + + + + ) +} + +export const TargetIcon = () => { + return ( + + + + ) } \ No newline at end of file diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 9ea5f1a..bc3a445 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import Search from "../../ui/inputs/Search"; import { getCategoryAsset } from "../../../services/factoryBuilder/asset/assets/getCategoryAsset"; import { fetchAssets } from "../../../services/marketplace/fetchAssets"; -import { useSelectedItem } from "../../../store/builder/store"; +import { useDecalStore, useSelectedItem } from "../../../store/builder/store"; // images ------------------- import vehicle from "../../../assets/image/categories/vehicles.png"; @@ -15,6 +15,7 @@ import safety from "../../../assets/image/categories/safety.png"; import feneration from "../../../assets/image/categories/feneration.png"; import decal from "../../../assets/image/categories/decal.png"; import SkeletonUI from "../../templates/SkeletonUI"; +import { AlertIcon, DecalInfoIcon, HangTagIcon, NavigationIcon } from "../../icons/ExportCommonIcons"; // ------------------------------------- interface AssetProp { @@ -113,6 +114,16 @@ const Assets: React.FC = () => { } }; + + const activeSubcategories = [ + { name: "Safety", icon: }, + { name: "Navigation", icon: }, + { name: "Branding", icon: }, + { name: "Informational", icon: } + ] + + + const { selectedSubCategory, setSelectedSubCategory } = useDecalStore(); return (
@@ -183,6 +194,19 @@ const Assets: React.FC = () => { ← Back + + {selectedCategory === "Decals" && + <> +
+ {activeSubcategories.map((cat, index) => ( +
setSelectedSubCategory(cat.name)}> +
{cat.icon}
+
{cat.name}
+
+ ))} +
+ + }
{categoryAssets?.map((asset: any, index: number) => (
{ @@ -59,6 +63,7 @@ const SideBarRight: React.FC = () => { const { selectedEventSphere } = useSelectedEventSphere(); const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { isVersionSaved } = useSaveVersion(); + const { selectedSubCategory } = useDecalStore(); const [displayComponent, setDisplayComponent] = useState("none"); @@ -119,7 +124,13 @@ const SideBarRight: React.FC = () => { setDisplayComponent("versionHistory"); return; } - if (!selectedFloorItem && !selectedFloor && !selectedWall) { + if (selectedSubCategory) { + setDisplayComponent("decals"); + return; + } + if (!selectedFloorItem && !selectedFloor && !selectedWall && !selectedSubCategory) { + console.log('selectedSubCategory: ', selectedSubCategory); + if (toolMode === "Aisle") { setDisplayComponent("aisleProperties"); return; @@ -143,7 +154,7 @@ const SideBarRight: React.FC = () => { } setDisplayComponent("none"); - }, [viewVersionHistory, activeModule, subModule, isVersionSaved, selectedFloorItem, selectedWall, selectedFloor, selectedAisle, toolMode,]); + }, [viewVersionHistory, activeModule, subModule, isVersionSaved, selectedFloorItem, selectedWall, selectedFloor, selectedAisle, toolMode, selectedSubCategory]); const renderComponent = () => { switch (displayComponent) { @@ -173,6 +184,8 @@ const SideBarRight: React.FC = () => { return ; case "visualization": return ; + case "decals": + return ; default: return null; } @@ -245,6 +258,7 @@ const SideBarRight: React.FC = () => {
{renderComponent()} + {/* */}
)} diff --git a/app/src/components/layout/sidebarRight/decals/DecalTransformation.tsx b/app/src/components/layout/sidebarRight/decals/DecalTransformation.tsx new file mode 100644 index 0000000..39a30d7 --- /dev/null +++ b/app/src/components/layout/sidebarRight/decals/DecalTransformation.tsx @@ -0,0 +1,59 @@ +import React, { useState } from 'react'; +import RotationInput from '../customInput/RotationInput'; +import PositionInput from '../customInput/PositionInputs'; +import { LockIcon } from '../../../icons/RealTimeVisulationIcons'; +import { LayeringBottomIcon, LayeringTopIcon, ValueUpdateIcon } from '../../../icons/ExportCommonIcons'; +import decalImage from "../../../../assets/image/sampleDecal.png" +const DecalTransformation = () => { + + + + return ( +
+ {["position", "rotation", "scale"].map((transformation) => ( +
+
{transformation}
+
+ +
+ +
+ +
+
+
+
+ ))} + +
+
opacity
+
+ +
+ +
+
+ +
+
Layering
+ +
+
+ +
+
+ +
+
+
+ +
+ +
replace
+
+
+ ); +}; + +export default DecalTransformation; + diff --git a/app/src/components/layout/sidebarRight/hrm/Hrm.tsx b/app/src/components/layout/sidebarRight/hrm/Hrm.tsx new file mode 100644 index 0000000..2b4b44e --- /dev/null +++ b/app/src/components/layout/sidebarRight/hrm/Hrm.tsx @@ -0,0 +1,240 @@ +import React, { useState } from 'react' +import Search from '../../../ui/inputs/Search' +import RegularDropDown from '../../../ui/inputs/RegularDropDown' +import { ClockThreeIcon, HourGlassIcon, ListTaskIcon, LocationPinIcon, SlectedTickIcon, TargetIcon } from '../../../icons/ExportCommonIcons' + +const Hrm = () => { + const [selectType, setSelectType] = useState("assetManagement") + const [selectcatogory, setSelectCatogary] = useState("All People") + const employee_details = [ + { + "employee": { + image: "", + "name": "John Doe", + "employee_id": "HR-204", + "status": "Active", + + }, + "task": { + "status": "Ongoing", + "title": "Inspecting Machine X", + "location": { + "floor": 4, + "zone": "B" + }, + "planned_time_hours": 6, + "time_spent_hours": 2, + "total_tasks": 12, + "completed_tasks": 3 + }, + "actions": [ + "Assign Task", + "Reassign Task", + "Pause", + "Emergency Stop" + ], + "location": "Floor 4 . Zone B" + }, + { + "employee": { + image: "", + "name": "Alice Smith", + "employee_id": "HR-205", + "status": "Active", + + }, + "task": { + "status": "Ongoing", + "title": "Calibrating Sensor Y", + "location": { + "floor": 2, + "zone": "A" + }, + "planned_time_hours": 4, + "time_spent_hours": 1.5, + "total_tasks": 10, + "completed_tasks": 2 + }, + "actions": [ + "Assign Task", + "Reassign Task", + "Pause", + "Emergency Stop" + ], + "location": "Floor 4 . Zone B" + }, + { + "employee": { + image: "", + "name": "Michael Lee", + "employee_id": "HR-206", + "status": "Active", + + }, + "task": { + "status": "Ongoing", + "title": "Testing Conveyor Belt Z", + "location": { + "floor": 5, + "zone": "C" + }, + "planned_time_hours": 5, + "time_spent_hours": 3, + "total_tasks": 8, + "completed_tasks": 5 + }, + "actions": [ + "Assign Task", + "Reassign Task", + "Pause", + "Emergency Stop" + ], + "location": "Floor 4 . Zone B" + } + ] + + return ( +
+
+
setSelectType("assetManagement")} + > + Asset Management +
+
setSelectType("peopleOperation")} + > + People Operations +
+
+ +
+ { }} /> +
+ { }} + search={false} + /> +
+
+ +
+ {["All People", "Technician", "Operator", "Supervisor", "Safety Officer"].map((cat, index) => ( +
setSelectCatogary(cat)} + > + {cat} +
+ ))} +
+ +
+ {employee_details.map((employee, index) => ( +
+
+
+
+ +
+
+
+
{employee.employee.name}
+
{employee.employee.employee_id}
+
+
+ +
View more
+
+ +
+ {/*
+
+
+ + Ongoing Task: +
+
{employee.task.title}
+
+ + +
+
+ + Location: +
+
+ Floor {employee.task.location.floor}. Zone {employee.task.location.zone} +
+
+
*/} + +
+
+ +
+ + Planned time: +
+ + {employee.task.planned_time_hours} hr +
+ {/*
+ +
+ + Total Tasks: +
+ + {employee.task.total_tasks} +
+
+ +
+ + Time Spent: +
+ + {employee.task.time_spent_hours} hr +
*/} +
+ +
+ + Completed Tasks: +
+ + {employee.task.completed_tasks} +
+
+ +
+
+
+ +
+
Location:
+
+
{employee.location}
+
+
+ {/* + */} + + +
+
+ +
+ ))} +
+
+ ) +} + +export default Hrm diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 7afa982..a45569f 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -9,6 +9,7 @@ import { useParams } from "react-router-dom"; import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap"; // import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; @@ -35,11 +36,16 @@ const DuplicationControls3D = ({ const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); + const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [isDuplicating, setIsDuplicating] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const { contextAction, setContextAction } = useContextActionStore() + const fineMoveBaseRef = useRef(null); + const lastPointerPositionRef = useRef(null); + const wasShiftHeldRef = useRef(false); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); @@ -85,11 +91,33 @@ const DuplicationControls3D = ({ removeAsset(obj.userData.modelUuid); }); } + setKeyEvent(""); }; const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); + if (isDuplicating && duplicatedObjects.length > 0) { + if (event.key.toLowerCase() === 'x') { + setAxisConstraint(prev => prev === 'x' ? null : 'x'); + event.preventDefault(); + return; + } + if (event.key.toLowerCase() === 'z') { + setAxisConstraint(prev => prev === 'z' ? null : 'z'); + event.preventDefault(); + return; + } + } + + if (keyCombination !== keyEvent) { + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } else { + setKeyEvent(""); + } + } + if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) { duplicateSelection(); } @@ -102,11 +130,34 @@ const DuplicationControls3D = ({ } }; + const onKeyUp = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === "") { + setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + if (duplicatedObjects[0] && keyEvent !== "" && keyCombination === '') { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + if (model) { + const newOffset = calculateDragOffset(model, intersectionPoint); + setDragOffset(newOffset); + } + } + } + }; + if (!toggleView) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); + canvasElement.addEventListener("keyup", onKeyUp); } return () => { @@ -114,8 +165,9 @@ const DuplicationControls3D = ({ canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); + canvasElement.addEventListener("keyup", onKeyUp); }; - }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]); + }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects, keyEvent]); useFrame(() => { if (!isDuplicating || duplicatedObjects.length === 0) return; @@ -135,7 +187,9 @@ const DuplicationControls3D = ({ } if (dragOffset) { - const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => { if (duplicatedObject.userData.modelUuid) { @@ -149,7 +203,7 @@ const DuplicationControls3D = ({ ); const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid); - const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset); + const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; if (model) { @@ -162,6 +216,21 @@ const DuplicationControls3D = ({ } }); + useEffect(() => { + if (duplicatedObjects.length > 0) { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + if (model) { + const newOffset = calculateDragOffset(model, intersectionPoint); + setDragOffset(newOffset); + } + } + } + }, [axisConstraint, camera, duplicatedObjects]) + const duplicateSelection = useCallback(() => { if (selectedAssets.length > 0 && duplicatedObjects.length === 0) { const positions: Record = {}; @@ -593,6 +662,7 @@ const DuplicationControls3D = ({ setSelectedAssets([]); setIsDuplicating(false); setDragOffset(null); + setAxisConstraint(null); }; return null; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts new file mode 100644 index 0000000..00b6924 --- /dev/null +++ b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts @@ -0,0 +1,75 @@ +import * as THREE from "three"; + +export function handleAssetPositionSnap({ + rawBasePosition, + intersectionPoint, + model, + axisConstraint, + keyEvent, + fineMoveBaseRef, + lastPointerPositionRef, + wasShiftHeldRef +}: { + rawBasePosition: THREE.Vector3; + intersectionPoint: THREE.Vector3; + model: THREE.Object3D | undefined; + axisConstraint: "x" | "z" | null; + keyEvent: string; + fineMoveBaseRef: React.MutableRefObject; + lastPointerPositionRef: React.MutableRefObject; + wasShiftHeldRef: React.MutableRefObject; +}): THREE.Vector3 { + const CTRL_DISTANCE = 0.5; + const SHIFT_DISTANCE = 0.05; + const CTRL_SHIFT_DISTANCE = 0.05; + + const isShiftHeld = keyEvent.includes("Shift"); + + // Handle Shift toggle state + if (isShiftHeld !== wasShiftHeldRef.current && model) { + if (isShiftHeld) { + fineMoveBaseRef.current = model.position.clone(); + lastPointerPositionRef.current = intersectionPoint.clone(); + } else { + fineMoveBaseRef.current = null; + } + wasShiftHeldRef.current = isShiftHeld; + } + + // Start from raw + let baseNewPosition = rawBasePosition.clone(); + + // Apply snapping / fine move + if (keyEvent === "Ctrl") { + baseNewPosition.set( + Math.round(baseNewPosition.x / CTRL_DISTANCE) * CTRL_DISTANCE, + baseNewPosition.y, + Math.round(baseNewPosition.z / CTRL_DISTANCE) * CTRL_DISTANCE + ); + } else if (keyEvent === "Ctrl+Shift") { + if (isShiftHeld && fineMoveBaseRef.current && lastPointerPositionRef.current) { + const pointerDelta = new THREE.Vector3().subVectors(intersectionPoint, lastPointerPositionRef.current); + baseNewPosition = fineMoveBaseRef.current.clone().add(pointerDelta.multiplyScalar(SHIFT_DISTANCE)); + } + baseNewPosition.set( + Math.round(baseNewPosition.x / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE, + baseNewPosition.y, + Math.round(baseNewPosition.z / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE + ); + } else if (isShiftHeld && fineMoveBaseRef.current && lastPointerPositionRef.current) { + const pointerDelta = new THREE.Vector3().subVectors(intersectionPoint, lastPointerPositionRef.current); + baseNewPosition = fineMoveBaseRef.current.clone().add(pointerDelta.multiplyScalar(SHIFT_DISTANCE)); + } + + // Apply axis constraint last + if (axisConstraint && model) { + const currentBasePosition = model.position.clone(); + if (axisConstraint === 'x') { + baseNewPosition.set(baseNewPosition.x, currentBasePosition.y, currentBasePosition.z); + } else if (axisConstraint === 'z') { + baseNewPosition.set(currentBasePosition.x, currentBasePosition.y, baseNewPosition.z); + } + } + + return baseNewPosition; +} \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts new file mode 100644 index 0000000..ca16720 --- /dev/null +++ b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts @@ -0,0 +1,74 @@ +import * as THREE from "three"; + +export function handleAssetRotationSnap({ + object, + pointerDeltaX, + keyEvent, + snapBaseRef, + prevRotationRef, + wasShiftHeldRef +}: { + object: THREE.Object3D; + pointerDeltaX: number; + keyEvent: string; + snapBaseRef: React.MutableRefObject; + prevRotationRef: React.MutableRefObject; + wasShiftHeldRef: React.MutableRefObject; +}): number { + const SHIFT_SPEED = 0.5; // Fine rotation speed + const NORMAL_SPEED = 5; // Normal rotation speed + const CTRL_SNAP_DEG = 15; // 15 degrees + const CTRL_SHIFT_SNAP_DEG = 5; // 5 degrees + + const isShiftHeld = keyEvent.includes("Shift"); + const isCtrlHeld = keyEvent.includes("Ctrl"); + + const speedFactor = isShiftHeld ? SHIFT_SPEED : NORMAL_SPEED; + let deltaAngle = pointerDeltaX * speedFactor; + + // Track if modifier changed + const modifierChanged = isShiftHeld !== wasShiftHeldRef.current; + if (modifierChanged) { + wasShiftHeldRef.current = isShiftHeld; + if (isCtrlHeld) snapBaseRef.current = null; + } + + if (isCtrlHeld) { + const snapDeg = isShiftHeld ? CTRL_SHIFT_SNAP_DEG : CTRL_SNAP_DEG; + const snapRad = THREE.MathUtils.degToRad(snapDeg); + + // Get current Y rotation from object's quaternion + const euler = new THREE.Euler().setFromQuaternion(object.quaternion, 'YXZ'); + let currentAngle = euler.y; + + // Initialize snap base on first frame + if (snapBaseRef.current === null) { + snapBaseRef.current = currentAngle; + prevRotationRef.current = currentAngle; + } + + // Accumulate the total rotation from the base + const totalRotation = snapBaseRef.current + deltaAngle; + + // Snap to nearest increment + const snappedAngle = Math.round(totalRotation / snapRad) * snapRad; + + // Calculate the delta needed to reach the snapped angle from current rotation + let targetDelta = snappedAngle - currentAngle; + + // Handle wrapping around 360 degrees + if (Math.abs(targetDelta) > Math.PI) { + targetDelta = targetDelta - Math.sign(targetDelta) * 2 * Math.PI; + } + + // Update snap base for next frame + snapBaseRef.current = totalRotation; + + return targetDelta; + } else { + // Reset snapping when Ctrl is not held + snapBaseRef.current = null; + prevRotationRef.current = null; + return deltaAngle; + } +} \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 234c345..21ddd33 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -5,7 +5,7 @@ import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView import * as Types from "../../../../../types/world/worldTypes"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; -import { snapControls } from "../../../../../utils/handleSnap"; +import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap"; import DistanceFindingControls from "./distanceFindingControls"; import { useParams } from "react-router-dom"; import { useProductContext } from "../../../../simulation/products/productContext"; @@ -34,7 +34,6 @@ function MoveControls3D({ const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { socket } = useSocketStore(); - const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const { userId, organization } = getUserData(); const { projectId } = useParams(); const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext(); @@ -43,12 +42,17 @@ function MoveControls3D({ const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); + const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [initialStates, setInitialStates] = useState>({}); const [isMoving, setIsMoving] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const { contextAction, setContextAction } = useContextActionStore() + const fineMoveBaseRef = useRef(null); + const lastPointerPositionRef = useRef(null); + const wasShiftHeldRef = useRef(false); const updateBackend = ( productName: string, @@ -85,10 +89,21 @@ function MoveControls3D({ }; const onKeyUp = (event: KeyboardEvent) => { - const isModifierKey = (!event.shiftKey && !event.ctrlKey); + const keyCombination = detectModifierKeys(event); - if (isModifierKey && keyEvent !== "") { + if (keyCombination === "") { setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + if (movedObjects[0]) { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint); + setDragOffset(newOffset); + } } }; @@ -113,7 +128,6 @@ function MoveControls3D({ clearSelection(); setMovedObjects([]); } - setKeyEvent(""); }; const onKeyDown = (event: KeyboardEvent) => { @@ -122,6 +136,19 @@ function MoveControls3D({ if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; + if (isMoving && movedObjects.length > 0) { + if (event.key.toLowerCase() === 'x') { + setAxisConstraint(prev => prev === 'x' ? null : 'x'); + event.preventDefault(); + return; + } + if (event.key.toLowerCase() === 'z') { + setAxisConstraint(prev => prev === 'z' ? null : 'z'); + event.preventDefault(); + return; + } + } + if (keyCombination !== keyEvent) { if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { setKeyEvent(keyCombination); @@ -192,9 +219,22 @@ function MoveControls3D({ } } }); - }, 0) + setAxisConstraint(null); + }, 100) }, [movedObjects, initialStates, updateAsset]); + useEffect(() => { + if (movedObjects.length > 0) { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint); + setDragOffset(newOffset); + } + } + }, [axisConstraint, camera, movedObjects]) + useFrame(() => { if (!isMoving || movedObjects.length === 0) return; @@ -213,20 +253,8 @@ function MoveControls3D({ if (dragOffset) { const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - - let moveDistance = keyEvent.includes("Shift") ? 0.05 : 1; - - const initialBasePosition = initialPositions[movedObjects[0].uuid]; - const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition); - - let adjustedDifference = positionDifference.multiplyScalar(moveDistance); - - const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference); - - if (keyEvent.includes("Ctrl")) { - baseNewPosition.x = snapControls(baseNewPosition.x, keyEvent); - baseNewPosition.z = snapControls(baseNewPosition.z, keyEvent); - } + const model = movedObjects[0]; + const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); movedObjects.forEach((movedAsset: THREE.Object3D) => { if (movedAsset.userData.modelUuid) { @@ -430,6 +458,7 @@ function MoveControls3D({ echo.success("Object moved!"); setIsMoving(false); clearSelection(); + setAxisConstraint(null); }; const clearSelection = () => { diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index d3457e2..8ea8e64 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -9,6 +9,8 @@ import { useProductContext } from "../../../../simulation/products/productContex import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; +import { handleAssetRotationSnap } from "./functions/handleAssetRotationSnap"; // import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; @@ -23,7 +25,6 @@ function RotateControls3D({ setDuplicatedObjects }: any) { const { camera, gl, scene, pointer, raycaster } = useThree(); - const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); @@ -38,6 +39,7 @@ function RotateControls3D({ const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const [initialRotations, setInitialRotations] = useState>({}); const [initialPositions, setInitialPositions] = useState>({}); const [isRotating, setIsRotating] = useState(false); @@ -45,6 +47,9 @@ function RotateControls3D({ const rotationCenter = useRef(null); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const { contextAction, setContextAction } = useContextActionStore() + const snapBaseRef = useRef(null); + const prevRotationRef = useRef(null); + const wasShiftHeldRef = useRef(false); const updateBackend = useCallback(( productName: string, @@ -103,6 +108,8 @@ function RotateControls3D({ }; const onKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return; if (event.key.toLowerCase() === "r") { @@ -110,6 +117,15 @@ function RotateControls3D({ rotateAssets(); } } + + if (keyCombination !== keyEvent) { + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } else { + setKeyEvent(""); + } + } + if (event.key.toLowerCase() === "escape") { event.preventDefault(); resetToInitialRotations(); @@ -118,11 +134,24 @@ function RotateControls3D({ } }; + const onKeyUp = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === "") { + setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + const currentPointer = new THREE.Vector2(pointer.x, pointer.y); + prevPointerPosition.current = currentPointer.clone(); + }; + if (!toggleView) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); + canvasElement?.addEventListener("keyup", onKeyUp); } return () => { @@ -130,68 +159,72 @@ function RotateControls3D({ canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); + canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]); + }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]); const resetToInitialRotations = useCallback(() => { - rotatedObjects.forEach((obj: THREE.Object3D) => { - const uuid = obj.uuid; - if (obj.userData.modelUuid) { - const initialRotation = initialRotations[uuid]; - const initialPosition = initialPositions[uuid]; + setTimeout(() => { + rotatedObjects.forEach((obj: THREE.Object3D) => { + const uuid = obj.uuid; + if (obj.userData.modelUuid) { + const initialRotation = initialRotations[uuid]; + const initialPosition = initialPositions[uuid]; - if (initialRotation && initialPosition) { - const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,]; - const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,]; + if (initialRotation && initialPosition) { + const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,]; + const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,]; - updateAsset(obj.userData.modelUuid, { - rotation: rotationArray, - position: positionArray, - }); + updateAsset(obj.userData.modelUuid, { + rotation: rotationArray, + position: positionArray, + }); - obj.rotation.copy(initialRotation); - obj.position.copy(initialPosition); + obj.rotation.copy(initialRotation); + obj.position.copy(initialPosition); + } } - } - }); + }); + }, 100) }, [rotatedObjects, initialRotations, initialPositions, updateAsset]); useFrame(() => { if (!isRotating || rotatedObjects.length === 0) return; - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + const currentPointer = new THREE.Vector2(pointer.x, pointer.y); if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { - if (point) { - prevPointerPosition.current = new THREE.Vector2(point.x, point.z); - } + prevPointerPosition.current = currentPointer.clone(); return; } - if (point && prevPointerPosition.current && rotationCenter.current) { + if (prevPointerPosition.current && rotationCenter.current) { const center = rotationCenter.current; - - const currentAngle = Math.atan2(point.z - center.z, point.x - center.x); - const prevAngle = Math.atan2( - prevPointerPosition.current.y - center.z, - prevPointerPosition.current.x - center.x - ); - const angleDelta = prevAngle - currentAngle; - - const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); + const deltaX = currentPointer.x - prevPointerPosition.current.x; rotatedObjects.forEach((obj: THREE.Object3D) => { if (obj.userData.modelUuid) { + const angleDelta = handleAssetRotationSnap({ + object: obj, + pointerDeltaX: deltaX, + keyEvent, + snapBaseRef, + prevRotationRef, + wasShiftHeldRef + }); + const relativePosition = new THREE.Vector3().subVectors(obj.position, center); + const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); relativePosition.applyMatrix4(rotationMatrix); obj.position.copy(center).add(relativePosition); - obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta); + + const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta); + obj.quaternion.multiply(rotationQuat); + obj.rotation.setFromQuaternion(obj.quaternion); } }); - prevPointerPosition.current = new THREE.Vector2(point.x, point.z); + prevPointerPosition.current = currentPointer.clone(); } }); @@ -213,17 +246,13 @@ function RotateControls3D({ setInitialRotations(rotations); setInitialPositions(positions); - const intersectionPoint = new THREE.Vector3(); raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - prevPointerPosition.current = new THREE.Vector2(point.x, point.z); - } + prevPointerPosition.current = new THREE.Vector2(pointer.x, pointer.y); setRotatedObjects(selectedAssets); setIsRotating(true); - }, [selectedAssets, camera, pointer, raycaster, plane]); + }, [selectedAssets, camera, pointer, raycaster]); const placeRotatedAssets = useCallback(() => { if (rotatedObjects.length === 0) return; diff --git a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts index f48312a..18e8bfd 100644 --- a/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts +++ b/app/src/modules/simulation/actions/storageUnit/actionHandler/useRetrieveHandler.ts @@ -6,7 +6,7 @@ import { useProductContext } from "../../../products/productContext"; import { useHumanEventManager } from "../../../human/eventManager/useHumanEventManager"; export function useRetrieveHandler() { - const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore } = useSceneContext(); + const { materialStore, armBotStore, machineStore, vehicleStore, storageUnitStore, conveyorStore, craneStore, productStore, humanStore, assetStore, humanEventManagerRef } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { addMaterial } = materialStore(); const { getModelUuidByActionUuid, getPointUuidByActionUuid, getEventByModelUuid, getActionByUuid } = productStore(); @@ -464,11 +464,20 @@ export function useRetrieveHandler() { if (action && action.actionType === 'pickAndDrop' && !hasLock && !crane.isCarrying && !crane.isActive && crane.currentLoad < (action?.maxPickUpCount || 0)) { const material = getLastMaterial(storageUnit.modelUuid); if (material) { - incrementCraneLoad(crane.modelUuid, 1); - addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId); - addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId); - - cranePickupLockRef.current.set(crane.modelUuid, true); + if (action.triggers[0].triggeredAsset?.triggeredModel.modelUuid && action.triggers[0].triggeredAsset.triggeredAction?.actionUuid) { + const human = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid); + if (human && human.type === 'human') { + if (!monitoredHumansRef.current.has(human.modelUuid)) { + addHumanToMonitor(human.modelUuid, () => { + incrementCraneLoad(crane.modelUuid, 1); + addCurrentActionToCrane(crane.modelUuid, action.actionUuid, material.materialType, material.materialId); + addCurrentMaterialToCrane(crane.modelUuid, material.materialType, material.materialId); + cranePickupLockRef.current.set(crane.modelUuid, true); + }, action.triggers[0].triggeredAsset.triggeredAction?.actionUuid) + } + monitoredHumansRef.current.add(human.modelUuid); + } + } } } else if (crane.isCarrying && crane.currentPhase === 'pickup-drop' && hasLock) { cranePickupLockRef.current.set(crane.modelUuid, false); diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 6c3c12e..1e00e68 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -638,7 +638,21 @@ export const useSelectedPath = create((set: any) => ({ selectedPath: "auto", setSelectedPath: (x: any) => set({ selectedPath: x }), })); + export const useContextActionStore = create((set: any) => ({ contextAction: null, setContextAction: (x: any) => set({ contextAction: x }), })); + + +// Define the store's state and actions type +interface DecalStore { + selectedSubCategory: string; + setSelectedSubCategory: (subCategory: string) => void; +} + +// Create the Zustand store with types +export const useDecalStore = create((set) => ({ + selectedSubCategory: '', + setSelectedSubCategory: (subCategory: string) => set({ selectedSubCategory: subCategory }), +})); diff --git a/app/src/styles/layout/hrm.scss b/app/src/styles/layout/hrm.scss new file mode 100644 index 0000000..b2f6c43 --- /dev/null +++ b/app/src/styles/layout/hrm.scss @@ -0,0 +1,251 @@ +@use "../abstracts/variables" as *; +@use "../abstracts/mixins" as *; + +.hrm-container { + + + .navigation-wrapper { + @include flex-space-between; + justify-content: space-around; + + .navigation { + + padding: 4px 12px; + border-radius: 20px; + text-wrap: nowrap; + margin: 6px 0; + cursor: pointer; + + &.active { + background: var(--background-color-button); + } + } + + } + + .search-container { + position: relative; + padding: 2px 2px; + + .search-wrapper { + input { + padding-right: 85px; + } + } + + .select-catagory { + position: absolute; + top: 50%; + right: 16px; + transform: translate(0, -50%); + z-index: 10; + + .regularDropdown-container { + padding: 2px 8px; + } + } + } + + .catagories-wrapper { + display: flex; + gap: 12px; + width: 100%; + overflow: auto; + padding: 8px 10px; + + .catagory { + text-wrap: nowrap; + position: relative; + cursor: pointer; + + &.active { + &::after { + content: ""; + position: absolute; + bottom: -6px; + left: 0; + width: 100%; + height: 2px; + border-radius: 100px; + background: var(--background-color-button); + } + + } + } + } + + .analysis-container { + max-height: calc(62vh - 12px); + overflow: auto; + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 7px; + + .analysis-wrapper { + border: 1px solid var(--Color-Hover, #CCACFF); + border-radius: 20px; + padding: 16px; + + display: flex; + flex-direction: column; + gap: 14px; + + header { + position: relative; + @include flex-space-between; + padding: 3px 0; + + .user-details { + display: flex; + gap: 6px; + + .user-image-wrapper { + width: 28px; + height: 28px; + border-radius: 50%; + background-color: #fff; + position: relative; + + .status { + border-radius: 50%; + width: 6px; + height: 6px; + outline: 1px solid #2F2C32; + + position: absolute; + bottom: 0; + right: 0; + + &.Active { + background-color: #44E5C6; + } + } + } + + .details { + .employee-id { + color: #B7B7C6; + font-size: $tiny; + + } + } + } + + .see-more { + color: var(--accent-color); + text-decoration: underline; + cursor: pointer; + } + + &::after { + content: ""; + position: absolute; + bottom: -7px; + left: 0; + width: 100%; + height: 1px; + background-color: #6F6F7A; + } + } + + .content { + display: flex; + flex-direction: column; + gap: 4px; + + .task-info { + padding: 8px 0; + display: flex; + flex-direction: column; + gap: 6px; + + .task-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + + .task-label { + display: flex; + align-items: center; + gap: 3px; + + .label-text { + color: #B7B7C6; + } + } + } + } + + .task-stats { + display: grid; + grid-template-columns: repeat(1, 1fr); // Two equal-width columns + gap: 4px; + + .stat-item { + border-radius: 100px; + @include flex-space-between; + background: linear-gradient(162.53deg, + rgba(51, 51, 51, 0.7) 0%, + rgba(45, 36, 55, 0.7) 106.84%); + border: 1px solid #FFFFFF0D; + padding: 6px; + + .stat-wrapper { + display: flex; + align-items: center; + gap: 4px; + } + + span, + .stat-value { + font-size: 10px; + display: flex; + } + } + } + + .location-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 0; + + .location-header { + display: flex; + gap: 6px; + + .icon { + display: flex; + } + + .header { + font-size: 12px; + color: #B7B7C6; + } + } + } + + .task-actions { + display: grid; + grid-template-columns: repeat(2, 1fr); // Two equal-width columns + gap: 4px; + margin-top: 3px; + + button { + line-height: 133%; + font-size: 11px; + border: 1px solid var(--Linear-Border, #564B69); + border-radius: 100px; + padding: 4px 0; + + &:last-child { + background-color: #CC2C1E; + } + } + } + + } + } + } +} \ No newline at end of file diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index 9030be7..ef5aaee 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -468,6 +468,96 @@ position: relative; width: 304px; + .decal-transformation-container { + display: flex; + flex-direction: column; + gap: 4px; + + .transformation-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 12px; + + .header { + flex: 1; + text-transform: capitalize; + } + + .input-wrapppers { + display: flex; + align-items: center; + gap: 6px; + flex: 1.5; + + svg { + stroke: #CCACFF; + } + + .icon { + display: flex; + justify-content: center; + align-items: center; + } + + input { + min-width: 43px; + } + } + + .layers { + display: flex; + gap: 6px; + align-items: center; + + .icon { + display: flex; + justify-content: center; + align-items: center; + outline: 1px solid var(--border-color); + padding: 4px 16px; + width: 50px; + border-radius: 100px; + } + } + } + + .opacity { + input { + min-width: 190px !important; + } + } + + .preview { + width: 100%; + height: 150px; + border-radius: 20px; + outline: 1px solid var(--border-color); + position: relative; + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + + .replace-btn { + background-color: #6F42C1; + border-radius: 100px; + color: #FFFFFF; + padding: 4px 16px; + width: fit-content; + cursor: pointer; + font-size: 12px; + text-transform: capitalize; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + } + } + .version-history-container { max-height: calc(62vh - 12px); display: flex; @@ -1978,6 +2068,43 @@ } } + .catogory-asset-filter { + display: flex; + flex-wrap: wrap; + gap: 12px; + border: 1px solid #564B69; + padding: 12px 10px; + border-radius: 15px; + + .catogory-asset-filter-wrapper { + display: flex; + align-items: center; + gap: 2px; + border: 1px solid #564B69; + padding: 4px 8px; + border-radius: 100px; + cursor: pointer; + + .sub-catagory { + display: flex; + } + + &.active { + background-color: #6F42C1; + + .sub-catagory { + + color: #FFFFFF; + } + + // svg { + // stroke: white; + // fill: white; + // } + } + } + } + .assets-container { width: 100%; display: flex; diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index b73439f..1c3a1b8 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -37,6 +37,7 @@ @use "layout/skeleton"; @use "layout/compareLayoutPopUp"; @use "layout/compareLayout"; +@use "layout/hrm"; // pages @use "pages/dashboard"; diff --git a/app/src/styles/pages/forgotPassword.scss b/app/src/styles/pages/forgotPassword.scss index 4965029..7af55b4 100644 --- a/app/src/styles/pages/forgotPassword.scss +++ b/app/src/styles/pages/forgotPassword.scss @@ -95,6 +95,18 @@ } } } + + .resend { + span { + color: var(--highlight-text-color); + } + + &.disabled { + span { + color: var(--text-disabled); + } + } + } } } } \ No newline at end of file diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 4db9e8b..6358c81 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -134,3 +134,11 @@ align-items: center; } } + +.stats{ + top: auto !important; + bottom: 36px !important; + left: 12px !important; + border-radius: 6px; + overflow: hidden; +} diff --git a/app/src/utils/handleSnap.ts b/app/src/utils/handleSnap.ts deleted file mode 100644 index bd6a74d..0000000 --- a/app/src/utils/handleSnap.ts +++ /dev/null @@ -1,22 +0,0 @@ -export function snapControls(value: number, event: string): number { - const CTRL_DISTANCE = 1; // Snap to whole numbers when Ctrl is pressed - const SHIFT_DISTANCE = 0.01; // Snap to half-step increments when Shift is pressed - const CTRL_SHIFT_DISTANCE = 0.1; // Snap to fine increments when both Ctrl and Shift are pressed - - switch (event) { - case "Ctrl": - return Math.round(value / CTRL_DISTANCE) * CTRL_DISTANCE; - - case "Shift": - return Math.round(value / SHIFT_DISTANCE) * SHIFT_DISTANCE; - - case "Ctrl+Shift": - const base = Math.floor(value / CTRL_DISTANCE) * CTRL_DISTANCE; - const offset = - Math.round((value - base) / CTRL_SHIFT_DISTANCE) * CTRL_SHIFT_DISTANCE; - return base + offset; - - default: - return value; // No snapping if no modifier key is pressed - } -} diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index f9b8cb8..507e31b 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -84,7 +84,7 @@ const KeyPressListener: React.FC = () => { H: "free-hand", }; const tool = toolMap[key]; - if (tool) { + if (tool && selectedAssets.length === 0) { setActiveTool(tool); setActiveSubTool(tool); }