added multiple selected objects
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -44,7 +44,6 @@ const Search: React.FC<SearchProps> = ({
|
||||
}, [inputValue, debounced, onChange]);
|
||||
|
||||
const handleClear = () => {
|
||||
console.warn("Search field cleared.");
|
||||
setInputValue("");
|
||||
onChange?.("");
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user