Merge remote-tracking branch 'origin/ui' into main-demo

This commit is contained in:
2025-08-25 18:16:51 +05:30
11 changed files with 120 additions and 39 deletions

View File

@@ -55,6 +55,7 @@ interface ShortcutHelperProps {
const ShortcutHelper: React.FC<ShortcutHelperProps> = ({ const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
setShowShortcuts, setShowShortcuts,
}) => { }) => {
const shortcuts: ShortcutGroup[] = [ const shortcuts: ShortcutGroup[] = [
// Essential // Essential
{ {
@@ -310,6 +311,7 @@ const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
> >
<CloseIcon /> <CloseIcon />
</button> </button>
<div className="header"> <div className="header">
<div className="header-wrapper"> <div className="header-wrapper">
{shortcuts.map((group) => ( {shortcuts.map((group) => (
@@ -326,8 +328,7 @@ const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
</div> </div>
<div <div
className={`shortcut-wrapper ${ className={`shortcut-wrapper ${activeShortcuts.length === 1 ? "single-item" : ""
activeShortcuts.length === 1 ? "single-item" : ""
}`} }`}
> >
{activeShortcuts.map((item) => ( {activeShortcuts.map((item) => (

View File

@@ -212,7 +212,11 @@ function MainScene() {
{activeModule !== "market" && !selectedUser && <Footer />} {activeModule !== "market" && !selectedUser && <Footer />}
<VersionSaved /> <VersionSaved />
{(commentPositionState !== null || selectedComment !== null) && <ThreadChat />}
{
(commentPositionState !== null || selectedComment !== null) &&
<ThreadChat />
}
</> </>
); );

View File

@@ -231,7 +231,10 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
<div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div> <div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div>
</div> </div>
{(val as Reply).creatorId === userId && ( {(val as Reply).creatorId === userId && (
<div className="more-options"> <div
className="more-options"
onMouseLeave={() => setOpenOptions(false)}
>
<button <button
className="more-options-button" className="more-options-button"
onClick={() => { onClick={() => {
@@ -240,35 +243,41 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
> >
<KebabIcon /> <KebabIcon />
</button> </button>
{openOptions && ( {openOptions && (
<div className="options-list"> <div className="options-list">
<button <button
className="option" className="option"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
setMode && setMode("edit") setMode && setMode("edit");
setOpenOptions(false); setOpenOptions(false);
setEditedThread && setEditedThread(true); setEditedThread && setEditedThread(true);
setIsEditComment(true) setIsEditComment(true);
}} }}
> >
Edit Edit
</button> </button>
{!(isEditableThread) && <button
{!isEditableThread && (
<button
className="option" className="option"
onClick={() => { onClick={() => {
handleDeleteAction((val as Reply).replyId); handleDeleteAction((val as Reply).replyId);
}} }}
> >
Delete Delete
</button>} </button>
)}
</div> </div>
)} )}
</div> </div>
)} )}
<div className="message"> <div className="message">
{"comment" in val ? val.comment : val.threadTitle} {"comment" in val ? val.comment : val.threadTitle}
</div> </div>
</div> </div>
</div > </div >
)} )}

View File

@@ -131,9 +131,10 @@ const ThreadChat: React.FC = () => {
if (dragging) updatePosition(e, true); if (dragging) updatePosition(e, true);
}; };
useEffect(() => { // Commented this useEffect to prevent offset after user saved the comment
updatePosition({ clientX: position.x, clientY: position.y }, true); // useEffect(() => {
}, [selectedComment]); // updatePosition({ clientX: position.x, clientY: position.y }, true);
// }, [selectedComment]);
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => { const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
@@ -144,6 +145,10 @@ const ThreadChat: React.FC = () => {
}; };
const handleCreateComments = async (e: any) => { const handleCreateComments = async (e: any) => {
// Continue send or create message only there is only value avalibale
// To prevent empty value
if (!value) return;
e.preventDefault(); e.preventDefault();
try { try {
// const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "")/ // const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "")/
@@ -163,6 +168,7 @@ const ThreadChat: React.FC = () => {
// } // }
if (threadSocket && mode === "create") { if (threadSocket && mode === "create") {
const addComment = { const addComment = {
versionId: selectedVersion?.versionId || "", versionId: selectedVersion?.versionId || "",
@@ -190,7 +196,7 @@ const ThreadChat: React.FC = () => {
// removeComment(selectedComment?.threadId) // removeComment(selectedComment?.threadId)
// setSelectedComment([]) // setSelectedComment([])
// } // }
console.log('threadSocket:threadChat ', threadSocket);
if (threadSocket) { if (threadSocket) {
// projectId, userId, organization, threadId // projectId, userId, organization, threadId
const deleteThread = { const deleteThread = {
@@ -258,7 +264,7 @@ const ThreadChat: React.FC = () => {
}; };
if (threadSocket) { if (threadSocket) {
console.log('createThread: ', createThread);
setInputActive(false); setInputActive(false);
threadSocket.emit("v1:thread:create", createThread); threadSocket.emit("v1:thread:create", createThread);

View File

@@ -0,0 +1,47 @@
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import type { CameraControls } from "@react-three/drei";
export const useCameraShortcuts = (controlsRef: React.RefObject<CameraControls>) => {
const { camera } = useThree();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!controlsRef.current) return;
// get current distance from camera to target
const target = new THREE.Vector3();
controlsRef.current.getTarget(target);
const distance = camera.position.distanceTo(target);
let pos: THREE.Vector3 | null = null;
switch (e.key) {
case "1": // Front
pos = new THREE.Vector3(0, 0, distance).add(target);
break;
case "3": // Right
pos = new THREE.Vector3(distance, 0, 0).add(target);
break;
case "7": // Top
pos = new THREE.Vector3(0, distance, 0).add(target);
break;
case "9": // Back
pos = new THREE.Vector3(0, 0, -distance).add(target);
break;
}
if (pos) {
controlsRef.current.setLookAt(
pos.x, pos.y, pos.z, // camera position
target.x, target.y, target.z, // keep same target
true // smooth transition
);
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [controlsRef, camera]);
};

View File

@@ -57,6 +57,7 @@ export default function Builder() {
const { setHoveredPoint, setHoveredLine } = useBuilderStore(); const { setHoveredPoint, setHoveredLine } = useBuilderStore();
const { userId, organization } = getUserData(); const { userId, organization } = getUserData();
useEffect(() => { useEffect(() => {
if (!toggleView) { if (!toggleView) {
setHoveredLine(null); setHoveredLine(null);

View File

@@ -18,6 +18,7 @@ import ContextControls from "./contextControls/contextControls";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls"; import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
import { useCameraShortcuts } from "../../../hooks/useCameraShortcuts";
export default function Controls() { export default function Controls() {
const controlsRef = useRef<CameraControls>(null); const controlsRef = useRef<CameraControls>(null);
@@ -116,6 +117,7 @@ export default function Controls() {
stopInterval(); stopInterval();
}; };
}, [toggleView, state, socket]); }, [toggleView, state, socket]);
useCameraShortcuts(controlsRef);
return ( return (
<> <>

View File

@@ -241,10 +241,8 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
{Object.keys(zonesData).length !== 0 ? ( {Object.keys(zonesData).length !== 0 ? (
<> <>
{Object.values(zonesData).map((zone, index) => ( {Object.values(zonesData).map((zone, index) => (
<>
{ }
<div <div
key={index} key={`${index}_${zone.zoneName}`}
className={`zone ${selectedZone.zoneUuid === zone.zoneUuid ? "active" : "" className={`zone ${selectedZone.zoneUuid === zone.zoneUuid ? "active" : ""
}`} }`}
onClick={() => { onClick={() => {
@@ -255,7 +253,6 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
> >
{zone.zoneName} {zone.zoneName}
</div> </div>
</>
))} ))}
</> </>
) : ( ) : (

View File

@@ -516,6 +516,7 @@
width: 50px; width: 50px;
border-radius: 100px; border-radius: 100px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
outline: 1px solid var(--accent-color); outline: 1px solid var(--accent-color);
} }
@@ -586,6 +587,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 8px; border-radius: 8px;
&:hover { &:hover {
background: var(--background-color); background: var(--background-color);
outline: 1px solid #aaaaaa29; outline: 1px solid #aaaaaa29;
@@ -600,6 +602,7 @@
.kebab-icon { .kebab-icon {
display: flex; display: flex;
svg { svg {
transform: rotate(90deg) scale(0.8); transform: rotate(90deg) scale(0.8);
} }
@@ -1434,6 +1437,11 @@
padding: 12px; padding: 12px;
border-radius: #{$border-radius-large}; border-radius: #{$border-radius-large};
outline: 1px solid var(--border-color);
outline-offset: -1px;
border-radius: 12px;
background: var(--background-color);
.compare-simulations-header { .compare-simulations-header {
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
} }
@@ -1604,6 +1612,7 @@
} }
.animations-list-wrapper { .animations-list-wrapper {
padding: 0 4px; padding: 0 4px;
.animations-list { .animations-list {
margin: 2px 0; margin: 2px 0;
padding: 4px 12px; padding: 4px 12px;
@@ -1793,6 +1802,7 @@
padding: 14px; padding: 14px;
padding-bottom: 0; padding-bottom: 0;
margin-bottom: 8px; margin-bottom: 8px;
.input-toggle-container { .input-toggle-container {
padding: 4px 0; padding: 4px 0;
margin-bottom: 8px; margin-bottom: 8px;

View File

@@ -171,8 +171,8 @@
.messages-wrapper { .messages-wrapper {
padding: 12px; padding: 12px;
padding-top: 0; padding-top: 0;
max-height: 50vh; max-height: 36vh;
overflow-y: auto; overflow: auto;
.edit-container { .edit-container {
.input-container { .input-container {
textarea { textarea {

View File

@@ -221,8 +221,12 @@ const KeyPressListener: React.FC = () => {
// Shortcuts specific for sidebar visibility toggle and others specific to sidebar if added // Shortcuts specific for sidebar visibility toggle and others specific to sidebar if added
handleSidebarShortcuts(keyCombination); handleSidebarShortcuts(keyCombination);
// Active module selection (builder, simulation, etc.) // Active module selection (builder, simulation, etc.)
if (event.location === 0) { // Location 0 = standard keyboard (not numpad)
handleModuleSwitch(keyCombination); handleModuleSwitch(keyCombination);
}
// Common editing tools: cursor | delete | free-hand // Common editing tools: cursor | delete | free-hand
handlePrimaryTools(keyCombination); handlePrimaryTools(keyCombination);
// Shortcuts specific to the builder module (e.g., drawing and measurement tools) // Shortcuts specific to the builder module (e.g., drawing and measurement tools)