feat: Enhance duplication and movement controls with axis constraints and snapping functionality
- Added axis constraint toggling for duplication and movement controls (X and Z axes). - Implemented snapping functionality for asset positioning based on modifier keys (Ctrl, Shift). - Created utility functions for handling asset position and rotation snapping. - Refactored moveControls3D and rotateControls3D to utilize new snapping functions. - Introduced state management for key events and axis constraints. - Updated styles for new UI components related to asset filtering and transformation. - Removed deprecated snapControls utility function.
This commit is contained in:
BIN
app/src/assets/image/sampleDecal.png
Normal file
BIN
app/src/assets/image/sampleDecal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -1345,4 +1345,135 @@ export const SuccessIcon = () => {
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const AlertIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.454 14.797H6.54597C6.1917 14.8122 5.83973 14.7329 5.5262 14.5673C5.21266 14.4017 4.94885 14.1556 4.76177 13.8544C4.5747 13.5531 4.47112 13.2075 4.46165 12.8531C4.45219 12.4986 4.53719 12.148 4.70792 11.8372L8.16189 5.83436C8.35337 5.51828 8.62315 5.25693 8.94513 5.07553C9.2671 4.89413 9.63038 4.79883 9.99993 4.79883C10.3695 4.79883 10.7328 4.89413 11.0548 5.07553C11.3768 5.25693 11.6466 5.51828 11.838 5.83436L15.292 11.8372C15.4627 12.148 15.5478 12.4986 15.5383 12.8531C15.5288 13.2075 15.4253 13.5531 15.2382 13.8544C15.0511 14.1556 14.7872 14.4017 14.4737 14.5673C14.1602 14.7329 13.8082 14.8122 13.454 14.797Z" stroke="var(--text-color)" stroke-width="0.832956" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M10.0015 12.1777C10.0679 12.1791 10.1324 12.1997 10.187 12.2373C10.2417 12.275 10.2842 12.328 10.3091 12.3896C10.3339 12.4515 10.3402 12.5197 10.3267 12.585C10.3131 12.6501 10.2802 12.7099 10.2329 12.7568C10.1857 12.8035 10.1254 12.8357 10.0601 12.8486C9.99488 12.8615 9.92713 12.8544 9.86572 12.8291C9.83506 12.8165 9.80623 12.8 9.78076 12.7793L9.71436 12.7061L9.67139 12.6172C9.66175 12.5864 9.65628 12.5541 9.65576 12.5215C9.65579 12.4763 9.66481 12.4314 9.68213 12.3896C9.69961 12.3477 9.72603 12.3093 9.7583 12.2773C9.79045 12.2455 9.82857 12.2203 9.87061 12.2031C9.91219 12.1862 9.95664 12.1776 10.0015 12.1777ZM9.85596 10.998L9.77783 8.09961V8.08887L9.77686 8.07812V8.03125C9.77846 8.01596 9.78187 8.00098 9.78662 7.98633C9.7962 7.95676 9.81179 7.92933 9.83252 7.90625C9.84285 7.89476 9.85427 7.88407 9.8667 7.875L9.90674 7.85156C9.93523 7.83888 9.96642 7.83203 9.99756 7.83203C10.013 7.83203 10.0284 7.83375 10.0435 7.83691L10.0884 7.85156C10.1166 7.86421 10.1418 7.88315 10.1626 7.90625C10.1834 7.92943 10.199 7.95689 10.2085 7.98633C10.2181 8.01596 10.2215 8.04735 10.2183 8.07812L10.2173 8.08887V8.10059L10.145 10.999V11.0059C10.145 11.0441 10.1291 11.0804 10.1021 11.1074C10.0749 11.1345 10.0386 11.1504 10.0005 11.1504C9.96219 11.1504 9.92502 11.1345 9.89795 11.1074C9.87115 11.0804 9.85598 11.0439 9.85596 11.0059V10.998Z" fill="black" stroke="var(--text-color)" stroke-width="0.555304" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
export const NavigationIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.99988 4.58398C7.0132 4.58398 4.5835 7.01369 4.5835 10.0004C4.5835 12.9872 7.0132 15.4173 9.99988 15.4173C12.9867 15.4173 15.4168 12.9872 15.4168 10.0004C15.4168 7.01369 12.9867 4.58398 9.99988 4.58398ZM9.99988 14.7764C7.36664 14.7764 5.22402 12.634 5.22402 10.0004C5.22402 7.36713 7.3666 5.22451 9.99988 5.22451C12.6335 5.22451 14.7759 7.36709 14.7759 10.0004C14.7759 12.634 12.6335 14.7764 9.99988 14.7764Z" fill="var(--text-color)" fill-opacity="0.85" />
|
||||
<path d="M9.92024 6.74255L8.37659 12.8023C8.36662 12.8418 8.38635 12.8823 8.42392 12.8985C8.46109 12.9147 8.50461 12.9018 8.52685 12.8671L10.0191 10.5407L11.4739 12.8667C11.4894 12.8915 11.5166 12.9055 11.544 12.9055C11.5548 12.9055 11.5655 12.9033 11.5764 12.8991C11.6142 12.8829 11.6341 12.8419 11.6244 12.8023L10.0807 6.74255C10.0619 6.66915 9.93845 6.66915 9.92024 6.74255ZM10.0899 10.3422C10.0747 10.3184 10.0487 10.3039 10.0203 10.3039H10.0201C9.99177 10.3039 9.96531 10.3178 9.95019 10.3416L8.66357 12.3474L10.0001 7.09867L11.3321 12.3286L10.0899 10.3422Z" fill="var(--text-color)" fill-opacity="0.85" />
|
||||
<path d="M11.5438 12.9321C11.5066 12.9321 11.4708 12.9126 11.4514 12.8814L10.0187 10.59L8.54867 12.8817C8.52069 12.9257 8.46184 12.9438 8.41374 12.9235C8.3643 12.9019 8.33822 12.8484 8.35165 12.7966L9.89512 6.73667C9.90622 6.69241 9.94861 6.66211 10.0003 6.66211C10.0519 6.66211 10.0946 6.69199 10.1059 6.73667L11.649 12.7966C11.6624 12.8488 11.6362 12.9024 11.5865 12.9235C11.5723 12.9293 11.5584 12.9321 11.5438 12.9321ZM10.0189 10.4928L10.0411 10.5279L11.4957 12.8537C11.5097 12.8765 11.5396 12.8863 11.5664 12.8754C11.5917 12.8645 11.6057 12.8365 11.5988 12.8095L10.0553 6.7495C10.0486 6.72325 10.0216 6.71384 10.0002 6.71384C9.9787 6.71384 9.95185 6.72325 9.94515 6.7495L8.4019 12.8095C8.39461 12.8365 8.40842 12.8645 8.4339 12.8754C8.45842 12.8852 8.48985 12.8766 8.50444 12.8537L10.0189 10.4928ZM8.59871 12.4969L9.99995 6.99367L11.3991 12.4844L10.0678 10.3568C10.0465 10.3226 9.99244 10.3244 9.97215 10.3562L8.59871 12.4969Z" fill="var(--text-color)" fill-opacity="0.85" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
export const HangTagIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5895_2753)">
|
||||
<path d="M14.7002 5.2998V9.37598L9.5 14.5762L5.42383 10.5L10.624 5.2998H14.7002ZM12 7.2002C11.5581 7.2002 11.2002 7.55806 11.2002 8C11.2002 8.44194 11.5581 8.7998 12 8.7998C12.4419 8.7998 12.7998 8.44194 12.7998 8C12.7998 7.55806 12.4419 7.2002 12 7.2002Z" stroke="var(--text-color)" stroke-opacity="0.85" stroke-width="0.6" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5895_2753">
|
||||
<rect width="20" height="20" fill="var(--text-color)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
export const DecalInfoIcon = () => {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0002 15.4173C12.9917 15.4173 15.4168 12.9922 15.4168 10.0007C15.4168 7.00911 12.9917 4.58398 10.0002 4.58398C7.00862 4.58398 4.5835 7.00911 4.5835 10.0007C4.5835 12.9922 7.00862 15.4173 10.0002 15.4173Z" stroke="var(--text-color)" stroke-opacity="0.85" stroke-width="0.8125" />
|
||||
<path d="M10 12.709V9.45898" stroke="var(--text-color)" stroke-opacity="0.85" stroke-width="0.8125" stroke-linecap="round" />
|
||||
<path d="M10.0002 7.29167C10.2993 7.29167 10.5418 7.53418 10.5418 7.83333C10.5418 8.13249 10.2993 8.375 10.0002 8.375C9.70101 8.375 9.4585 8.13249 9.4585 7.83333C9.4585 7.53418 9.70101 7.29167 10.0002 7.29167Z" fill="var(--text-color)" fill-opacity="0.85" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const LayeringBottomIcon = () => {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.857138" y="5.39229" width="10.9233" height="9.75071" stroke="var(--text-color)" stroke-width="0.714277" />
|
||||
<path d="M5.49121 0.599609H14.3867C14.9559 0.599609 15.3994 1.01267 15.3994 1.5V9.5C15.3993 9.98728 14.9559 10.3994 14.3867 10.3994H5.49121C4.92207 10.3994 4.47858 9.98728 4.47852 9.5V1.5C4.47852 1.01268 4.92203 0.599609 5.49121 0.599609Z" fill="#6F42C1" stroke="white" stroke-width="0.2" />
|
||||
<path d="M7.87686 6.85212L9.54491 8.3521C9.5966 8.39897 9.65809 8.43616 9.72585 8.46155C9.7936 8.48693 9.86628 8.5 9.93968 8.5C10.0131 8.5 10.0858 8.48693 10.1535 8.46155C10.2213 8.43616 10.2828 8.39897 10.3345 8.3521L12.0025 6.85212C12.1072 6.75797 12.166 6.63027 12.166 6.49713C12.166 6.36398 12.1072 6.23628 12.0025 6.14213C11.8978 6.04798 11.7558 5.99509 11.6077 5.99509C11.4597 5.99509 11.3177 6.04798 11.213 6.14213L10.4957 6.79212V2.99717C10.4957 2.86456 10.4371 2.73739 10.3328 2.64362C10.2286 2.54985 10.0871 2.49718 9.93968 2.49718C9.79222 2.49718 9.65079 2.54985 9.54652 2.64362C9.44224 2.73739 9.38366 2.86456 9.38366 2.99717V6.79212L8.6664 6.14213C8.61472 6.09527 8.55322 6.05807 8.48546 6.03269C8.41771 6.0073 8.34503 5.99423 8.27163 5.99423C8.19823 5.99423 8.12556 6.0073 8.0578 6.03269C7.99005 6.05807 7.92855 6.09527 7.87686 6.14213C7.82475 6.18861 7.78338 6.24391 7.75516 6.30484C7.72693 6.36577 7.71239 6.43112 7.71239 6.49713C7.71239 6.56313 7.72693 6.62848 7.75516 6.68941C7.78338 6.75034 7.82475 6.80564 7.87686 6.85212Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const LayeringTopIcon = () => {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.857138" y="5.39229" width="10.9233" height="9.75071" stroke="var(--text-color)" stroke-width="0.714277" />
|
||||
<path d="M5.49121 0.599609H14.3867C14.9559 0.599609 15.3994 1.01267 15.3994 1.5V9.5C15.3993 9.98728 14.9559 10.3994 14.3867 10.3994H5.49121C4.92207 10.3994 4.47858 9.98728 4.47852 9.5V1.5C4.47852 1.01268 4.92203 0.599609 5.49121 0.599609Z" fill="#6F42C1" stroke="white" stroke-width="0.2" />
|
||||
<path d="M12.002 4.14397L10.334 2.64399C10.2823 2.59713 10.2208 2.55993 10.1531 2.53455C10.0853 2.50916 10.0126 2.49609 9.93923 2.49609C9.86583 2.49609 9.79315 2.50916 9.7254 2.53455C9.65764 2.55993 9.59614 2.59713 9.54446 2.64399L7.87641 4.14397C7.77171 4.23812 7.71289 4.36582 7.71289 4.49897C7.71289 4.63212 7.77171 4.75981 7.87641 4.85396C7.98111 4.94811 8.12311 5.00101 8.27118 5.00101C8.41925 5.00101 8.56125 4.94811 8.66595 4.85396L9.38321 4.20397V7.99892C9.38321 8.13153 9.44179 8.25871 9.54606 8.35247C9.65034 8.44624 9.79176 8.49892 9.93923 8.49892C10.0867 8.49892 10.2281 8.44624 10.3324 8.35247C10.4367 8.25871 10.4952 8.13153 10.4952 7.99892V4.20397L11.2125 4.85396C11.2642 4.90083 11.3257 4.93802 11.3934 4.96341C11.4612 4.98879 11.5339 5.00186 11.6073 5.00186C11.6807 5.00186 11.7533 4.98879 11.8211 4.96341C11.8889 4.93802 11.9504 4.90083 12.002 4.85396C12.0542 4.80748 12.0955 4.75218 12.1238 4.69125C12.152 4.63033 12.1665 4.56497 12.1665 4.49897C12.1665 4.43296 12.152 4.36761 12.1238 4.30668C12.0955 4.24575 12.0542 4.19045 12.002 4.14397Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export const ValueUpdateIcon = () => {
|
||||
return (
|
||||
<svg width="9" height="12" viewBox="0 0 9 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.30511 4.5C1.50348 4.5 1.10202 3.53079 1.66886 2.96396L3.80771 0.825099C4.15911 0.473707 4.72882 0.473708 5.08021 0.825099L7.21907 2.96396C7.78591 3.5308 7.38445 4.5 6.58282 4.5L2.30511 4.5Z" fill="#B7B7C6" />
|
||||
<path d="M2.30511 7.5C1.50348 7.5 1.10202 8.46921 1.66886 9.03604L3.80771 11.1749C4.15911 11.5263 4.72882 11.5263 5.08021 11.1749L7.21907 9.03604C7.78591 8.4692 7.38445 7.5 6.58282 7.5L2.30511 7.5Z" fill="#B7B7C6" />
|
||||
</svg>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const ListTaskIcon = () => {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.00008 8.66667H12.6667C13.0349 8.66667 13.3334 8.96514 13.3334 9.33333C13.3334 9.70152 13.0349 10 12.6667 10H6.00008C5.63189 10 5.33341 9.70152 5.33341 9.33333C5.33341 8.96514 5.63189 8.66667 6.00008 8.66667ZM6.00008 11.3333H12.6667C13.0349 11.3333 13.3334 11.6318 13.3334 12C13.3334 12.3682 13.0349 12.6667 12.6667 12.6667H6.00008C5.63189 12.6667 5.33341 12.3682 5.33341 12C5.33341 11.6318 5.63189 11.3333 6.00008 11.3333ZM10.0001 6H12.6667C13.0349 6 13.3334 6.29848 13.3334 6.66667C13.3334 7.03486 13.0349 7.33333 12.6667 7.33333H10.0001C9.63189 7.33333 9.33342 7.03486 9.33342 6.66667C9.33342 6.29848 9.63189 6 10.0001 6ZM5.16184 7.27614L2.66675 4.78105L3.60956 3.83824L5.16184 5.39052L8.55237 2L9.49518 2.94281L5.16184 7.27614Z" fill="#B7B7C6" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const LocationPinIcon = () => {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99992 14C10.3333 11.6 12.6666 9.45093 12.6666 6.8C12.6666 4.14903 10.5773 2 7.99992 2C5.42259 2 3.33325 4.14903 3.33325 6.8C3.33325 9.45093 5.66659 11.6 7.99992 14Z" stroke="#B7B7C6" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M8 8.66602C9.1046 8.66602 10 7.77062 10 6.66602C10 5.56145 9.1046 4.66602 8 4.66602C6.8954 4.66602 6 5.56145 6 6.66602C6 7.77062 6.8954 8.66602 8 8.66602Z" stroke="#B7B7C6" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const ClockThreeIcon = () => {
|
||||
return (
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.5 3.20833V5.5H6.875M9.625 5.5C9.625 7.77819 7.77819 9.625 5.5 9.625C3.22183 9.625 1.375 7.77819 1.375 5.5C1.375 3.22183 3.22183 1.375 5.5 1.375C7.77819 1.375 9.625 3.22183 9.625 5.5Z" stroke="#FCFDFD" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export const SlectedTickIcon = () => {
|
||||
return (
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.90747 6.87464C4.98667 6.94724 5.11867 6.94724 5.19787 6.86804L7.67287 4.31384C7.75207 4.23464 7.75207 4.10264 7.66627 4.02344C7.58707 3.94424 7.45507 3.94424 7.37587 4.03004L4.90087 6.58424L5.19127 6.57764L3.68647 5.19824C3.60067 5.11904 3.47527 5.12564 3.39607 5.21144C3.31687 5.29724 3.32347 5.42264 3.40927 5.50184L4.90747 6.87464Z" fill="#FCFDFD" />
|
||||
<path d="M5.53442 9.57422C3.25742 9.57422 1.40942 7.72622 1.40942 5.44922C1.40942 3.17222 3.25742 1.32422 5.53442 1.32422C7.81142 1.32422 9.65942 3.17222 9.65942 5.44922C9.65942 7.72622 7.81142 9.57422 5.53442 9.57422ZM5.53442 1.72022C3.47522 1.72022 1.80542 3.39002 1.80542 5.44922C1.80542 7.50842 3.47522 9.17822 5.53442 9.17822C7.59362 9.17822 9.26342 7.50842 9.26342 5.44922C9.26342 3.39002 7.59362 1.72022 5.53442 1.72022Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const HourGlassIcon = () => {
|
||||
return (
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.98324 5.5C5.98324 6.21872 6.39671 6.55406 6.79659 6.87775C7.29742 7.28394 7.86133 7.74109 7.91957 9.13971H3.07633C3.13456 7.74109 3.69847 7.28394 4.19929 6.87775C4.59918 6.55406 5.01265 6.21872 5.01265 5.5C5.01265 4.78128 4.59918 4.44594 4.19929 4.12225C3.69847 3.71606 3.13456 3.25891 3.07633 1.86029H7.91956C7.86133 3.25891 7.29741 3.71606 6.79659 4.12225C6.39671 4.44594 5.98324 4.78128 5.98324 5.5ZM7.10233 4.49884C7.68468 4.02665 8.40971 3.43896 8.40971 1.61765V1.375H2.58618V1.61764C2.58618 3.43896 3.31121 4.02664 3.89357 4.49884C4.275 4.80846 4.52735 5.01276 4.52735 5.5C4.52735 5.98724 4.275 6.19154 3.89357 6.50116C3.31121 6.97335 2.58618 7.56104 2.58618 9.38235V9.625H8.40971V9.38236C8.40971 7.56104 7.68468 6.97336 7.10233 6.50116C6.72089 6.19154 6.46853 5.98724 6.46853 5.5C6.46853 5.01276 6.72088 4.80846 7.10233 4.49884ZM5.38706 6.85208L4.65814 7.44365C4.33882 7.70231 4.06366 7.92506 3.91613 8.41181H7.07976C6.93223 7.92506 6.65707 7.70231 6.33775 7.44365L5.60884 6.85208C5.54429 6.79967 5.4516 6.79967 5.38706 6.85208Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const TargetIcon = () => {
|
||||
return (
|
||||
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.625 5.5C9.625 7.77815 7.77815 9.625 5.5 9.625M9.625 5.5C9.625 3.22182 7.77815 1.375 5.5 1.375M9.625 5.5H7.975M5.5 9.625C3.22182 9.625 1.375 7.77815 1.375 5.5M5.5 9.625V7.975M5.5 1.375C3.22182 1.375 1.375 3.22182 1.375 5.5M5.5 1.375V3.025M1.375 5.5H3.025" stroke="#FCFDFD" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -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: <AlertIcon /> },
|
||||
{ name: "Navigation", icon: <NavigationIcon /> },
|
||||
{ name: "Branding", icon: <HangTagIcon /> },
|
||||
{ name: "Informational", icon: <DecalInfoIcon /> }
|
||||
]
|
||||
|
||||
|
||||
const { selectedSubCategory, setSelectedSubCategory } = useDecalStore();
|
||||
return (
|
||||
<div className="assets-container-main">
|
||||
<Search onChange={handleSearchChange} />
|
||||
@@ -183,6 +194,19 @@ const Assets: React.FC = () => {
|
||||
← Back
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
{selectedCategory === "Decals" &&
|
||||
<>
|
||||
<div className="catogory-asset-filter">
|
||||
{activeSubcategories.map((cat, index) => (
|
||||
<div className={`catogory-asset-filter-wrapper ${selectedSubCategory === cat.name ? "active" : ""}`} onClick={() => setSelectedSubCategory(cat.name)}>
|
||||
<div className="sub-catagory">{cat.icon}</div>
|
||||
<div className="sub-catagory">{cat.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
<div className="assets-container">
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
|
||||
@@ -12,6 +12,7 @@ import Visualization from "./visualization/Visualization";
|
||||
import Analysis from "./analysis/Analysis";
|
||||
import Simulations from "./simulation/Simulations";
|
||||
import useVersionHistoryVisibleStore, {
|
||||
useDecalStore,
|
||||
useSaveVersion,
|
||||
useSelectedFloorItem,
|
||||
useToolMode,
|
||||
@@ -31,6 +32,8 @@ import WallProperties from "./properties/WallProperties";
|
||||
import FloorProperties from "./properties/FloorProperties";
|
||||
import SelectedWallProperties from "./properties/SelectedWallProperties";
|
||||
import SelectedFloorProperties from "./properties/SelectedFloorProperties";
|
||||
import DecalTransformation from "./decals/DecalTransformation";
|
||||
import Hrm from "./hrm/Hrm";
|
||||
|
||||
type DisplayComponent =
|
||||
| "versionHistory"
|
||||
@@ -46,6 +49,7 @@ type DisplayComponent =
|
||||
| "mechanics"
|
||||
| "analysis"
|
||||
| "visualization"
|
||||
| "decals"
|
||||
| "none";
|
||||
|
||||
const SideBarRight: React.FC = () => {
|
||||
@@ -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<DisplayComponent>("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 <Analysis />;
|
||||
case "visualization":
|
||||
return <Visualization />;
|
||||
case "decals":
|
||||
return <DecalTransformation />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -245,6 +258,7 @@ const SideBarRight: React.FC = () => {
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
{renderComponent()}
|
||||
{/* <Hrm /> */}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
<div className='decal-transformation-container'>
|
||||
{["position", "rotation", "scale"].map((transformation) => (
|
||||
<div className="transformation-wrapper">
|
||||
<div className="header">{transformation}</div>
|
||||
<div className="input-wrapppers">
|
||||
<input type="number" name="" id="" />
|
||||
<div className="icon"><ValueUpdateIcon /></div>
|
||||
<input type="number" name="" id="" />
|
||||
<div className="icon"><ValueUpdateIcon /></div>
|
||||
<input type="number" name="" id="" />
|
||||
<div className="icon"><ValueUpdateIcon /></div>
|
||||
<div className="icon"><LockIcon /></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="transformation-wrapper opacity">
|
||||
<div className="header">opacity</div>
|
||||
<div className="input-wrapppers">
|
||||
<input type="number" name="" id="" />
|
||||
<div className="icon"><ValueUpdateIcon /></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="transformation-wrapper opacity">
|
||||
<div className="header">Layering</div>
|
||||
|
||||
<div className="layers">
|
||||
<div className="icon">
|
||||
<LayeringBottomIcon />
|
||||
</div>
|
||||
<div className="icon">
|
||||
<LayeringTopIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="preview">
|
||||
<img src={decalImage} alt="" />
|
||||
<div className="replace-btn">replace</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DecalTransformation;
|
||||
|
||||
240
app/src/components/layout/sidebarRight/hrm/Hrm.tsx
Normal file
240
app/src/components/layout/sidebarRight/hrm/Hrm.tsx
Normal file
@@ -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 (
|
||||
<div className='hrm-container'>
|
||||
<div className="navigation-wrapper">
|
||||
<div
|
||||
className={`navigation ${selectType === "assetManagement" ? "active" : ""}`}
|
||||
onClick={() => setSelectType("assetManagement")}
|
||||
>
|
||||
Asset Management
|
||||
</div>
|
||||
<div
|
||||
className={`navigation ${selectType === "peopleOperation" ? "active" : ""}`}
|
||||
onClick={() => setSelectType("peopleOperation")}
|
||||
>
|
||||
People Operations
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="search-container">
|
||||
<Search onChange={() => { }} />
|
||||
<div className="select-catagory">
|
||||
<RegularDropDown
|
||||
header={"floor"}
|
||||
options={["floor"]} // Pass layout names as options
|
||||
onSelect={() => { }}
|
||||
search={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="catagories-wrapper">
|
||||
{["All People", "Technician", "Operator", "Supervisor", "Safety Officer"].map((cat, index) => (
|
||||
<div
|
||||
key={cat}
|
||||
className={`catagory ${selectcatogory === cat ? "active" : ''}`}
|
||||
onClick={() => setSelectCatogary(cat)}
|
||||
>
|
||||
{cat}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="analysis-container">
|
||||
{employee_details.map((employee, index) => (
|
||||
<div className="analysis-wrapper">
|
||||
<header>
|
||||
<div className="user-details">
|
||||
<div className="user-image-wrapper">
|
||||
<img className='user-image' src={employee.employee.image} alt="" />
|
||||
<div className={`status ${employee.employee.status}`}></div>
|
||||
</div>
|
||||
<div className="details">
|
||||
<div className="employee-name">{employee.employee.name}</div>
|
||||
<div className="employee-id">{employee.employee.employee_id}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="see-more">View more</div>
|
||||
</header>
|
||||
|
||||
<div className="content">
|
||||
{/* <div className="task-info">
|
||||
<div className="task-wrapper">
|
||||
<div className="task-label">
|
||||
<span className='label-icon'><ListTaskIcon /></span>
|
||||
<span className='label-text'>Ongoing Task:</span>
|
||||
</div>
|
||||
<div className="task-title">{employee.task.title}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="task-wrapper">
|
||||
<div className="task-label">
|
||||
<span className='label-icon'><LocationPinIcon /></span>
|
||||
<span className='label-text'>Location:</span>
|
||||
</div>
|
||||
<div className="task-title">
|
||||
Floor {employee.task.location.floor}. Zone {employee.task.location.zone}
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="task-stats">
|
||||
<div className="stat-item">
|
||||
|
||||
<div className="stat-wrapper">
|
||||
<span className="stat-icon"><ClockThreeIcon /></span>
|
||||
<span>Planned time:</span>
|
||||
</div>
|
||||
|
||||
<span className='stat-value'>{employee.task.planned_time_hours} hr</span>
|
||||
</div>
|
||||
{/* <div className="stat-item">
|
||||
|
||||
<div className="stat-wrapper">
|
||||
<span className="stat-icon"><SlectedTickIcon /></span>
|
||||
<span>Total Tasks:</span>
|
||||
</div>
|
||||
|
||||
<span className='stat-value'>{employee.task.total_tasks}</span>
|
||||
</div>
|
||||
<div className="stat-item">
|
||||
|
||||
<div className="stat-wrapper">
|
||||
<span className="stat-icon"><HourGlassIcon /></span>
|
||||
<span>Time Spent:</span>
|
||||
</div>
|
||||
|
||||
<span className='stat-value'>{employee.task.time_spent_hours} hr</span>
|
||||
</div> */}
|
||||
<div className="stat-item">
|
||||
|
||||
<div className="stat-wrapper">
|
||||
<span className="stat-icon"><TargetIcon /></span>
|
||||
<span>Completed Tasks:</span>
|
||||
</div>
|
||||
|
||||
<span className='stat-value'>{employee.task.completed_tasks}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="location-wrapper">
|
||||
<div className="location-header">
|
||||
<div className="icon">
|
||||
<LocationPinIcon />
|
||||
</div>
|
||||
<div className="header">Location:</div>
|
||||
</div>
|
||||
<div className="location-value">{employee.location}</div>
|
||||
</div>
|
||||
<div className="task-actions">
|
||||
{/* <button className="btn btn-default">Assign Task</button>
|
||||
<button className="btn btn-default">Reassign Task</button> */}
|
||||
<button className="btn btn-default">Pause</button>
|
||||
<button className="btn btn-danger">Emergency Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Hrm
|
||||
@@ -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<THREE.Vector3 | null>(null);
|
||||
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
|
||||
const [isDuplicating, setIsDuplicating] = useState(false);
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
const fineMoveBaseRef = useRef<THREE.Vector3 | null>(null);
|
||||
const lastPointerPositionRef = useRef<THREE.Vector3 | null>(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<string, THREE.Vector3> = {};
|
||||
@@ -593,6 +662,7 @@ const DuplicationControls3D = ({
|
||||
setSelectedAssets([]);
|
||||
setIsDuplicating(false);
|
||||
setDragOffset(null);
|
||||
setAxisConstraint(null);
|
||||
};
|
||||
|
||||
return null;
|
||||
|
||||
@@ -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<THREE.Vector3 | null>;
|
||||
lastPointerPositionRef: React.MutableRefObject<THREE.Vector3 | null>;
|
||||
wasShiftHeldRef: React.MutableRefObject<boolean>;
|
||||
}): 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;
|
||||
}
|
||||
@@ -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<number | null>;
|
||||
prevRotationRef: React.MutableRefObject<number | null>;
|
||||
wasShiftHeldRef: React.MutableRefObject<boolean>;
|
||||
}): 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;
|
||||
}
|
||||
}
|
||||
@@ -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<THREE.Vector3 | null>(null);
|
||||
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
|
||||
const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
|
||||
const [isMoving, setIsMoving] = useState(false);
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
const fineMoveBaseRef = useRef<THREE.Vector3 | null>(null);
|
||||
const lastPointerPositionRef = useRef<THREE.Vector3 | null>(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 = () => {
|
||||
|
||||
@@ -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<Record<string, THREE.Euler>>({});
|
||||
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
|
||||
const [isRotating, setIsRotating] = useState(false);
|
||||
@@ -45,6 +47,9 @@ function RotateControls3D({
|
||||
const rotationCenter = useRef<THREE.Vector3 | null>(null);
|
||||
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
|
||||
const { contextAction, setContextAction } = useContextActionStore()
|
||||
const snapBaseRef = useRef<number | null>(null);
|
||||
const prevRotationRef = useRef<number | null>(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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -638,7 +638,21 @@ export const useSelectedPath = create<any>((set: any) => ({
|
||||
selectedPath: "auto",
|
||||
setSelectedPath: (x: any) => set({ selectedPath: x }),
|
||||
}));
|
||||
|
||||
export const useContextActionStore = create<any>((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<DecalStore>((set) => ({
|
||||
selectedSubCategory: '',
|
||||
setSelectedSubCategory: (subCategory: string) => set({ selectedSubCategory: subCategory }),
|
||||
}));
|
||||
|
||||
251
app/src/styles/layout/hrm.scss
Normal file
251
app/src/styles/layout/hrm.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
@use "layout/skeleton";
|
||||
@use "layout/compareLayoutPopUp";
|
||||
@use "layout/compareLayout";
|
||||
@use "layout/hrm";
|
||||
|
||||
// pages
|
||||
@use "pages/dashboard";
|
||||
|
||||
@@ -95,6 +95,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resend {
|
||||
span {
|
||||
color: var(--highlight-text-color);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
span {
|
||||
color: var(--text-disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,3 +134,11 @@
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.stats{
|
||||
top: auto !important;
|
||||
bottom: 36px !important;
|
||||
left: 12px !important;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user