added multiple selected objects

This commit is contained in:
2025-10-25 11:15:08 +05:30
parent 4a3474cb4e
commit 8c01bd6e62
5 changed files with 90 additions and 34 deletions

View File

@@ -31,25 +31,11 @@ export const OutlineList: React.FC<OutlinePanelProps> = ({
search,
data,
}) => {
const [selectedId, setSelectedId] = useState<string | null>(null);
const [isPanelOpen, setIsPanelOpen] = useState(true);
const [draggedNode, setDraggedNode] = useState<AssetGroupChild | null>(null);
const [searchValue, setSearchValue] = useState("");
const [hierarchy, setHierarchy] = useState<AssetGroupChild[]>(data);
useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
const target = event.target as HTMLElement;
if (!target.closest(".outline-card")) {
setSelectedId(null);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
const [selectedObject, setSelectedObject] = useState<AssetGroupChild | null>(null);
const handleDragStart = (item: AssetGroupChild) => {
setDraggedNode(item);
@@ -75,7 +61,7 @@ export const OutlineList: React.FC<OutlinePanelProps> = ({
const y = event.clientY - rect.top;
const dropZone = getDropZone(y);
switch (dropZone || selectedId === null) {
switch (dropZone || selectedObject === null) {
case "above":
hoveredDiv.style.borderTop = "2px solid purple";
break;
@@ -264,7 +250,7 @@ export const OutlineList: React.FC<OutlinePanelProps> = ({
<div
className="outline-overlay"
style={{
justifyContent: panelSide === "left" ? "flex-start" : "flex-end",
justifyContent: panelSide || DEFAULT_PRAMS.panelSide,
}}
>
<div
@@ -313,8 +299,8 @@ export const OutlineList: React.FC<OutlinePanelProps> = ({
onDrop={handleDrop}
onDragOver={handleDragOver}
draggingItem={draggedNode}
selectedObject={selectedId}
setSelectedObject={setSelectedId}
setSelectedObject={setSelectedObject}
selectedObject={selectedObject}
onRename={handleRename}
/>
))}

View File

@@ -44,7 +44,6 @@ const Search: React.FC<SearchProps> = ({
}, [inputValue, debounced, onChange]);
const handleClear = () => {
console.warn("Search field cleared.");
setInputValue("");
onChange?.("");
};

View File

@@ -1,4 +1,4 @@
import { useState } from "react";
import { TouchEventHandler, useEffect, useState } from "react";
import { DEFAULT_PRAMS } from "./OutlineList ";
import clsx from "clsx";
import {
@@ -22,9 +22,9 @@ interface TreeNodeProps {
onRename: (id: string, item: string) => void;
onDrop: (item: AssetGroupChild, parent: AssetGroupChild | null, e: any) => void;
draggingItem: AssetGroupChild | null;
selectedObject: string | null;
selectedObject: AssetGroupChild | null;
onDragOver: (item: AssetGroupChild, parent: AssetGroupChild | null, e: React.DragEvent) => void;
setSelectedObject: React.Dispatch<React.SetStateAction<string | null>>;
setSelectedObject: React.Dispatch<React.SetStateAction<AssetGroupChild | null>>;
}
const TreeNode: React.FC<TreeNodeProps> = ({
item,
@@ -47,6 +47,22 @@ const TreeNode: React.FC<TreeNodeProps> = ({
const [isLocked, setIsLocked] = useState(item.isLocked ?? false);
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState(item.groupName || item.modelName);
const [selectedObjects, setSelectedObjects] = useState<AssetGroupChild[]>([]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
const target = event.target as HTMLElement;
if (!target.closest(".outline-card")) {
setSelectedObjects([]);
setSelectedObject(null);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
const handleDragStart = (e: React.DragEvent) => {
e.stopPropagation();
@@ -82,12 +98,50 @@ const TreeNode: React.FC<TreeNodeProps> = ({
}
};
// Toggle selection (used by mouse click, keyboard and touch)
const toggleSelect = () => {
const currentId = item.modelUuid || item.groupUuid || null;
setSelectedObject((prev) => (prev === currentId ? null : currentId));
const toggleSelect = (event?: React.MouseEvent | React.KeyboardEvent) => {
event?.stopPropagation?.();
const currentId = item.modelUuid || item.groupUuid;
const isMultiSelect =
!!event &&
("ctrlKey" in event || "metaKey" in event) &&
(event.ctrlKey || event.metaKey);
if (isMultiSelect) {
setSelectedObjects((prevSelected) => {
const isAlreadySelected = prevSelected.some(
(obj) => obj.modelUuid === currentId || obj.groupUuid === currentId
);
if (isAlreadySelected) {
return prevSelected.filter(
(obj) => obj.modelUuid !== currentId && obj.groupUuid !== currentId
);
} else {
return [...prevSelected, item];
}
});
setSelectedObject(item);
} else {
setSelectedObject(item);
setSelectedObjects([]);
}
};
const isMultiSelected = (() => {
const totalSelectedItems = selectedObjects.length;
if (totalSelectedItems <= 1) return false;
if (isGroupNode) {
// Group selection
return selectedObjects.some((obj) => obj.groupUuid === item.groupUuid);
} else {
// Asset selection
return selectedObjects.some((obj) => obj.modelUuid === item.modelUuid);
}
})();
const handleDivKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === "Enter") {
e.preventDefault();
@@ -97,7 +151,9 @@ const TreeNode: React.FC<TreeNodeProps> = ({
}
};
const handleTouchStart = () => {
const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
e.stopPropagation();
e.preventDefault();
toggleSelect();
};
@@ -109,7 +165,11 @@ const TreeNode: React.FC<TreeNodeProps> = ({
className={clsx("tree-node-content", {
"group-node": isGroupNode,
"asset-node": !isGroupNode,
selected: selectedObject === item.modelUuid,
"multi-selected": selectedObjects.some(
(obj) =>
obj.modelUuid === item.modelUuid || obj.groupUuid === item.groupUuid
),
selected: selectedObject?.modelUuid === item.modelUuid,
locked: isLocked,
hidden: !isVisible,
dragging: isBeingDragged,

View File

@@ -4,14 +4,14 @@ import { OutlineListData } from "../data/OutlineListData";
const SceneView = () => {
return (
<OutlinePanel
// height="1000px"
// width="120px"
// panelSide="right"
// height="500px"
// width="420px"
panelSide="right"
// textColor=""
// addIconColor=""
// eyeIconColor=""
// backgroundColor=""
search={true}
// search={true}
data={OutlineListData}
/>
);

View File

@@ -10,6 +10,13 @@ $icon-size: 16px;
$node-height: 28px;
$spacing-unit: 8px;
html,
body,
#root {
width: 100%;
height: 100%;
}
// Mixins
@mixin flex-center {
display: flex;
@@ -31,6 +38,7 @@ $spacing-unit: 8px;
}
.outline-overlay {
width: 100%;
padding: 0 4px;
font-family: "Segoe UI", sans-serif;
display: flex;
@@ -227,7 +235,10 @@ $spacing-unit: 8px;
align-items: center;
flex: 1;
}
&.multi-selected {
background: #6f42c1;
border-radius: 50px;
}
&.locked {
opacity: 0.6;
cursor: not-allowed;