Refactor Outline Component and Styles
- Removed the Outline component from MainScene and SideBarLeft. - Deleted outline.css and integrated styles into a new _assetOutline.scss file. - Updated Outline component to use new styles and improved structure. - Added functionality for collapsing all groups in the Outline. - Enhanced drag-and-drop functionality for asset management. - Improved accessibility and usability of the Outline component.
This commit is contained in:
2
app/package-lock.json
generated
2
app/package-lock.json
generated
@@ -29,6 +29,7 @@
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"chart.js": "^4.4.8",
|
||||
"chartjs-plugin-annotation": "^3.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dxf-parser": "^1.1.2",
|
||||
"glob": "^11.0.0",
|
||||
"gsap": "^3.12.5",
|
||||
@@ -8760,6 +8761,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"chart.js": "^4.4.8",
|
||||
"chartjs-plugin-annotation": "^3.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dxf-parser": "^1.1.2",
|
||||
"glob": "^11.0.0",
|
||||
"gsap": "^3.12.5",
|
||||
|
||||
@@ -43,13 +43,7 @@ export function FocusIcon() {
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<circle
|
||||
cx="6"
|
||||
cy="5.87999"
|
||||
r="0.95"
|
||||
stroke="var(--text-color)"
|
||||
strokeWidth="0.5"
|
||||
/>
|
||||
<circle cx="6" cy="5.87999" r="0.95" stroke="var(--text-color)" strokeWidth="0.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -278,26 +272,10 @@ export function FilterIcon() {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.86655 10V3.33333"
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M6.86655 16.6666V13.3333"
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M13.0667 5.83336V3.33336"
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M13.0667 16.6666V9.16664"
|
||||
stroke="var(--text-color)"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path d="M6.86655 10V3.33333" stroke="var(--text-color)" strokeLinecap="round" />
|
||||
<path d="M6.86655 16.6666V13.3333" stroke="var(--text-color)" strokeLinecap="round" />
|
||||
<path d="M13.0667 5.83336V3.33336" stroke="var(--text-color)" strokeLinecap="round" />
|
||||
<path d="M13.0667 16.6666V9.16664" stroke="var(--text-color)" strokeLinecap="round" />
|
||||
<path
|
||||
d="M6.86666 13.3333C7.78714 13.3333 8.53333 12.5871 8.53333 11.6667C8.53333 10.7462 7.78714 10 6.86666 10C5.94619 10 5.2 10.7462 5.2 11.6667C5.2 12.5871 5.94619 13.3333 6.86666 13.3333Z"
|
||||
stroke="var(--text-color)"
|
||||
@@ -323,9 +301,7 @@ export function EyeDroperIcon({ isActive }: { isActive: boolean }) {
|
||||
>
|
||||
<path
|
||||
d="M6.5625 3.5L10.0625 7"
|
||||
stroke={
|
||||
isActive ? "var(--icon-default-color-active)" : "var(--text-color)"
|
||||
}
|
||||
stroke={isActive ? "var(--icon-default-color-active)" : "var(--text-color)"}
|
||||
strokeWidth="0.875"
|
||||
strokeMiterlimit="10"
|
||||
strokeLinecap="round"
|
||||
@@ -333,9 +309,7 @@ export function EyeDroperIcon({ isActive }: { isActive: boolean }) {
|
||||
/>
|
||||
<path
|
||||
d="M7.4375 4.37266L10.0625 1.74766C10.5437 1.26641 11.3312 1.26641 11.8125 1.74766C12.2937 2.22891 12.2937 3.01641 11.8125 3.49766L9.1875 6.12266"
|
||||
stroke={
|
||||
isActive ? "var(--icon-default-color-active)" : "var(--text-color)"
|
||||
}
|
||||
stroke={isActive ? "var(--icon-default-color-active)" : "var(--text-color)"}
|
||||
strokeWidth="0.875"
|
||||
strokeMiterlimit="10"
|
||||
strokeLinecap="round"
|
||||
@@ -343,9 +317,7 @@ export function EyeDroperIcon({ isActive }: { isActive: boolean }) {
|
||||
/>
|
||||
<path
|
||||
d="M7.30625 4.50391L2.49375 9.31641C2.23125 9.57891 2.14375 9.88516 2.14375 10.2352C1.925 10.3664 1.75 10.6289 1.75 10.9352C1.75 11.4164 2.14375 11.8102 2.625 11.8102C2.93125 11.8102 3.19375 11.6352 3.36875 11.4164C3.675 11.4164 4.025 11.2852 4.2875 11.0664L9.1 6.25391"
|
||||
stroke={
|
||||
isActive ? "var(--icon-default-color-active)" : "var(--text-color)"
|
||||
}
|
||||
stroke={isActive ? "var(--icon-default-color-active)" : "var(--text-color)"}
|
||||
strokeWidth="0.875"
|
||||
strokeMiterlimit="10"
|
||||
strokeLinecap="round"
|
||||
@@ -507,27 +479,9 @@ export const KebabIcon = () => {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<ellipse
|
||||
cx="1.54798"
|
||||
cy="1.35112"
|
||||
rx="1.4993"
|
||||
ry="1.27477"
|
||||
fill="var(--text-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="6.04868"
|
||||
cy="1.35131"
|
||||
rx="1.4993"
|
||||
ry="1.27477"
|
||||
fill="var(--text-color)"
|
||||
/>
|
||||
<ellipse
|
||||
cx="10.5476"
|
||||
cy="1.35131"
|
||||
rx="1.4993"
|
||||
ry="1.27477"
|
||||
fill="var(--text-color)"
|
||||
/>
|
||||
<ellipse cx="1.54798" cy="1.35112" rx="1.4993" ry="1.27477" fill="var(--text-color)" />
|
||||
<ellipse cx="6.04868" cy="1.35131" rx="1.4993" ry="1.27477" fill="var(--text-color)" />
|
||||
<ellipse cx="10.5476" cy="1.35131" rx="1.4993" ry="1.27477" fill="var(--text-color)" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1082,31 +1036,75 @@ export const SaveIcon = () => {
|
||||
export const FolderIcon = ({ isOpen }: { isOpen: boolean }) => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="hierarchy-icon">
|
||||
{isOpen ? (
|
||||
<path d="M2 4h12v9a1 1 0 01-1 1H3a1 1 0 01-1-1V4z M2 4V3a1 1 0 011-1h3l1 1h6a1 1 0 011 1v1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||
<path
|
||||
d="M2 4h12v9a1 1 0 01-1 1H3a1 1 0 01-1-1V4z M2 4V3a1 1 0 011-1h3l1 1h6a1 1 0 011 1v1"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
) : (
|
||||
<path d="M2 3a1 1 0 011-1h3l1 1h6a1 1 0 011 1v8a1 1 0 01-1 1H3a1 1 0 01-1-1V3z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||
<path
|
||||
d="M2 3a1 1 0 011-1h3l1 1h6a1 1 0 011 1v8a1 1 0 01-1 1H3a1 1 0 01-1-1V3z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CubeIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="hierarchy-icon">
|
||||
<path d="M8 2l5 3v6l-5 3-5-3V5l5-3z M8 2v6 M3 5l5 3 M13 5l-5 3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M8 2l5 3v6l-5 3-5-3V5l5-3z M8 2v6 M3 5l5 3 M13 5l-5 3"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ChevronIcon = ({ isOpen }: { isOpen: boolean }) => {
|
||||
return isOpen ? (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className="hierarchy-chevron">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M3 4.5L6 7.5L9 4.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" className="hierarchy-chevron">
|
||||
<path d="M4.5 3L7.5 6L4.5 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path
|
||||
d="M4.5 3L7.5 6L4.5 9"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const CollapseAllIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.28508 7.8945H7.37508C7.66983 7.8945 7.90908 7.6365 7.90908 7.3215C7.90908 7.0065 7.66983 6.7485 7.37508 6.7485H1.28508C0.989582 6.7485 0.750332 7.0065 0.750332 7.3215C0.750332 7.6365 0.990332 7.8945 1.28508 7.8945ZM1.28508 5.2215H7.37508C7.66983 5.2215 7.90908 4.96425 7.90908 4.6485C7.90908 4.3335 7.66983 4.0755 7.37508 4.0755H1.28508C0.989582 4.0755 0.750332 4.3335 0.750332 4.6485C0.750332 4.9635 0.990332 5.2215 1.28508 5.2215ZM1.32333 2.6445H10.6488C10.9638 2.6445 11.2413 2.406 11.2503 2.09175C11.2528 2.01494 11.2399 1.93841 11.2122 1.86671C11.1845 1.79501 11.1427 1.72961 11.0893 1.6744C11.0358 1.61919 10.9718 1.5753 10.901 1.54534C10.8303 1.51538 10.7542 1.49996 10.6773 1.5H1.35183C1.03683 1.5 0.759332 1.73925 0.750332 2.05275C0.747714 2.12962 0.7606 2.20623 0.788222 2.27802C0.815844 2.34981 0.857636 2.4153 0.911107 2.47059C0.964578 2.52588 1.02863 2.56984 1.09945 2.59985C1.17028 2.62985 1.24642 2.64529 1.32333 2.64525V2.6445ZM8.58633 7.8855V4.086L11.2503 5.98575L8.58633 7.8855C8.58633 7.8945 8.58633 7.8945 8.58633 7.8855ZM10.6773 9.32625H1.35183C1.03683 9.32625 0.759332 9.56625 0.750332 9.87975C0.747817 9.95656 0.760779 10.0331 0.788447 10.1048C0.816115 10.1765 0.857923 10.2419 0.911384 10.2971C0.964844 10.3523 1.02886 10.3962 1.09964 10.4262C1.17041 10.4561 1.24648 10.4715 1.32333 10.4715H10.6488C10.9638 10.4715 11.2413 10.2322 11.2503 9.918C11.2528 9.84119 11.2399 9.76466 11.2122 9.69296C11.1845 9.62126 11.1427 9.55586 11.0893 9.50065C11.0358 9.44544 10.9718 9.40155 10.901 9.37159C10.8303 9.34163 10.7542 9.32621 10.6773 9.32625Z"
|
||||
fill="var(--text-color)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const SaveVersionIcon = () => {
|
||||
return (
|
||||
@@ -1208,17 +1206,8 @@ export const SaveVersionIcon = () => {
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<clipPath
|
||||
id="bgblur_0_4816_8890_clip_path"
|
||||
transform="translate(38.7816 60.596)"
|
||||
>
|
||||
<rect
|
||||
x="22.4204"
|
||||
y="0.60596"
|
||||
width="139.371"
|
||||
height="112.709"
|
||||
rx="10.3013"
|
||||
/>
|
||||
<clipPath id="bgblur_0_4816_8890_clip_path" transform="translate(38.7816 60.596)">
|
||||
<rect x="22.4204" y="0.60596" width="139.371" height="112.709" rx="10.3013" />
|
||||
</clipPath>
|
||||
<filter
|
||||
id="filter1_f_4816_8890"
|
||||
|
||||
@@ -33,7 +33,6 @@ import { recentlyViewedApi } from "../../../services/dashboard/recentlyViewedApi
|
||||
import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import { Outline } from "../../../modules/builder/testUi/outline";
|
||||
|
||||
function MainScene() {
|
||||
const { setMainState, clearComparisonState } = useSimulationState();
|
||||
@@ -186,7 +185,6 @@ function MainScene() {
|
||||
<>
|
||||
{!selectedUser && (
|
||||
<>
|
||||
<Outline />
|
||||
<KeyPressListener />
|
||||
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
||||
{!isPlaying && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ToggleHeader from "../../ui/inputs/ToggleHeader";
|
||||
import Outline from "./Outline";
|
||||
// import Outline from "./Outline";
|
||||
import Header from "./Header";
|
||||
import { useToggleStore } from "../../../store/ui/useUIToggleStore";
|
||||
import Assets from "./assetList/Assets";
|
||||
@@ -9,6 +9,7 @@ import Widgets from "./visualization/widgets/Widgets";
|
||||
import Templates from "../../../modules/visualization/template/Templates";
|
||||
import Search from "../../ui/inputs/Search";
|
||||
import { useIsComparing } from "../../../store/builder/store";
|
||||
import { Outline } from "../../../modules/builder/testUi/outline";
|
||||
|
||||
const SideBarLeft: React.FC = () => {
|
||||
const [activeOption, setActiveOption] = useState("Widgets");
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
/* Hierarchy Overlay Styles */
|
||||
|
||||
.outline-overlay {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", sans-serif;
|
||||
}
|
||||
|
||||
.outline-card {
|
||||
width: 320px;
|
||||
background: rgba(15, 15, 25, 0.98);
|
||||
backdrop-filter: blur(30px);
|
||||
border-radius: 16px;
|
||||
border: 1px solid hsl(262 83% 58% / 0.3);
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 60px hsl(262 83% 68% / 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 0 0 1px hsl(262 83% 58% / 0.1);
|
||||
overflow: hidden;
|
||||
animation: slideIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.outline-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, hsl(262 83% 68% / 0.05), transparent 50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.outline-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, hsl(262 83% 58% / 0.15), hsl(262 83% 58% / 0.05));
|
||||
border-bottom: 1px solid hsl(262 83% 58% / 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.outline-header::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, hsl(262 83% 68% / 0.5) 50%, transparent);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #e0e0ff;
|
||||
}
|
||||
|
||||
.header-title svg {
|
||||
color: hsl(262 83% 68%);
|
||||
filter: drop-shadow(0 0 4px hsl(262 83% 68% / 0.5));
|
||||
}
|
||||
|
||||
.header-title h2 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
background: hsl(262 83% 58% / 0.2);
|
||||
color: #e0e0ff;
|
||||
transform: rotate(90deg);
|
||||
box-shadow: 0 0 8px hsl(262 83% 68% / 0.3);
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
.outline-toolbar {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 1px solid hsl(262 83% 58% / 0.1);
|
||||
}
|
||||
|
||||
.toolbar-button {
|
||||
background: hsl(262 83% 58% / 0.1);
|
||||
border: 1px solid hsl(262 83% 58% / 0.2);
|
||||
color: hsl(262 83% 68%);
|
||||
cursor: pointer;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.toolbar-button:hover {
|
||||
background: hsl(262 83% 58% / 0.2);
|
||||
border-color: hsl(262 83% 58% / 0.4);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px hsl(262 83% 58% / 0.3), 0 0 16px hsl(262 83% 68% / 0.2);
|
||||
}
|
||||
|
||||
.toolbar-button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.outline-content {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.outline-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.outline-content::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.outline-content::-webkit-scrollbar-thumb {
|
||||
background: hsl(262 83% 58% / 0.4);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 4px hsl(262 83% 68% / 0.2);
|
||||
}
|
||||
|
||||
.outline-content::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(262 83% 58% / 0.6);
|
||||
box-shadow: 0 0 8px hsl(262 83% 68% / 0.4);
|
||||
}
|
||||
|
||||
/* Tree Node */
|
||||
.tree-node {
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.tree-node.drop-target-highlight {
|
||||
background: hsl(262 83% 58% / 0.12);
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 0 2px hsl(262 83% 58% / 0.4), 0 0 20px hsl(262 83% 68% / 0.3), 0 4px 12px hsl(262 83% 58% / 0.2);
|
||||
}
|
||||
|
||||
.tree-node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 12px;
|
||||
margin: 0px 8px;
|
||||
border-radius: 8px;
|
||||
cursor: grab;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tree-node-content:hover {
|
||||
background: hsl(262 83% 58% / 0.15);
|
||||
}
|
||||
|
||||
.tree-node-content.selected {
|
||||
background: hsl(262 83% 58% / 0.3);
|
||||
}
|
||||
|
||||
.tree-node-content.dragging {
|
||||
opacity: 0.5;
|
||||
background: hsl(262 83% 58% / 0.25);
|
||||
cursor: grabbing;
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4px 12px hsl(262 83% 58% / 0.3);
|
||||
}
|
||||
|
||||
.tree-node-content.locked {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tree-node-content.hidden {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.expand-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
color: #9ca3af;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.expand-button:hover {
|
||||
background: hsl(262 83% 58% / 0.2);
|
||||
color: hsl(262 83% 68%);
|
||||
box-shadow: 0 0 8px hsl(262 83% 68% / 0.2);
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: hsl(262 83% 68%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
flex: 1;
|
||||
color: #e5e7eb;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.node-controls {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
background: hsl(262 83% 58% / 0.2);
|
||||
color: hsl(262 83% 68%);
|
||||
box-shadow: 0 0 6px hsl(262 83% 68% / 0.3);
|
||||
}
|
||||
|
||||
.tree-children {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tree-children::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 24px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: linear-gradient(to bottom, hsl(262 83% 58% / 0.3), hsl(262 83% 58% / 0.1));
|
||||
box-shadow: 0 0 4px hsl(262 83% 68% / 0.2);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.outline-footer {
|
||||
padding: 12px 16px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-top: 1px solid hsl(262 83% 58% / 0.1);
|
||||
}
|
||||
|
||||
.footer-stats {
|
||||
color: #9ca3af;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Toggle Button */
|
||||
.outline-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
background: linear-gradient(135deg, hsl(262 83% 58% / 0.95), hsl(262 83% 58% / 0.85));
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid hsl(262 83% 58% / 0.4);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
box-shadow: 0 10px 30px hsl(262 83% 58% / 0.4), 0 0 25px hsl(262 83% 68% / 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.outline-toggle:hover {
|
||||
transform: scale(1.08) rotate(2deg);
|
||||
box-shadow: 0 15px 45px hsl(262 83% 58% / 0.5), 0 0 40px hsl(262 83% 68% / 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.outline-toggle:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Enhanced Glow Effect */
|
||||
@keyframes cardGlow {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.outline-card:hover {
|
||||
border-color: hsl(262 83% 58% / 0.5);
|
||||
box-shadow: 0 25px 70px rgba(0, 0, 0, 0.7), 0 0 80px hsl(262 83% 68% / 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 0 0 1px hsl(262 83% 58% / 0.2);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.outline-overlay {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.outline-card {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.outline-content {
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Root Drop Target Highlight */
|
||||
.outline-content.root-drop-target {
|
||||
background: hsl(262 83% 58% / 0.12);
|
||||
box-shadow: inset 0 0 0 2px hsl(262 83% 58% / 0.4), inset 0 0 20px hsl(262 83% 68% / 0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.outline-content.root-drop-target::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: hsl(262 83% 68%);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
pointer-events: none;
|
||||
text-shadow: 0 0 8px hsl(262 83% 68% / 0.5);
|
||||
z-index: 10;
|
||||
background: rgba(15, 15, 25, 0.8);
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid hsl(262 83% 58% / 0.4);
|
||||
}
|
||||
|
||||
/* Multi-selection styling */
|
||||
.tree-node-content.multi-selected {
|
||||
background: hsl(262 83% 58% / 0.25);
|
||||
}
|
||||
|
||||
.tree-node-content.multi-selected::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background: linear-gradient(to bottom, hsl(262 83% 68% / 0.8), hsl(262 83% 68% / 0.4));
|
||||
box-shadow: 0 0 8px hsl(262 83% 68% / 0.4);
|
||||
}
|
||||
|
||||
.tree-node-content.multi-selected:hover {
|
||||
background: hsl(262 83% 58% / 0.35);
|
||||
}
|
||||
|
||||
/* Selection count indicator (optional - add to footer) */
|
||||
.footer-stats.multi-selection {
|
||||
color: hsl(262 83% 68%);
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -1,14 +1,23 @@
|
||||
import { useState, useRef, DragEvent, useCallback } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { EyeIcon, LockIcon, FolderIcon, ChevronIcon, CubeIcon, AddIcon, DeleteIcon, KebebIcon } from "../../../components/icons/ExportCommonIcons";
|
||||
import {
|
||||
EyeIcon,
|
||||
LockIcon,
|
||||
FolderIcon,
|
||||
ChevronIcon,
|
||||
CubeIcon,
|
||||
AddIcon,
|
||||
KebebIcon,
|
||||
CollapseAllIcon,
|
||||
} from "../../../components/icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../components/ui/inputs/RenameInput";
|
||||
import { useSceneContext } from "../../scene/sceneContext";
|
||||
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
||||
import useAssetResponseHandler from "../../collaboration/responseHandler/useAssetResponseHandler";
|
||||
import "./outline.css";
|
||||
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface DragState {
|
||||
draggedItem: AssetGroupChild | null;
|
||||
@@ -51,7 +60,8 @@ const TreeNode = ({
|
||||
const isLocked = item.isLocked;
|
||||
const isExpanded = isGroupNode ? item.isExpanded : false;
|
||||
const isSelected = !isGroupNode ? hasSelectedAsset(item.modelUuid) : false;
|
||||
const isMultiSelected = !isGroupNode && selectedAssets.length > 1 && hasSelectedAsset(item.modelUuid);
|
||||
const isMultiSelected =
|
||||
!isGroupNode && selectedAssets.length > 1 && hasSelectedAsset(item.modelUuid);
|
||||
|
||||
// Determine the parent group of this item
|
||||
const getParentGroup = useCallback(
|
||||
@@ -76,7 +86,7 @@ const TreeNode = ({
|
||||
|
||||
// Highlight if this is the target group or belongs to the target group
|
||||
return thisGroupUuid === dragState.targetGroupUuid;
|
||||
}, [dragState, isGroupNode, item, getParentGroup]);
|
||||
}, [dragState, isGroupNode, item]);
|
||||
|
||||
const handleNodeDragStart = (e: DragEvent) => {
|
||||
const parentGroupUuid = getParentGroup(item);
|
||||
@@ -112,9 +122,13 @@ const TreeNode = ({
|
||||
return (
|
||||
<div className={`tree-node ${shouldShowHighlight ? "drop-target-highlight" : ""}`}>
|
||||
<div
|
||||
className={`tree-node-content ${isLocked ? "locked" : ""} ${!isVisible ? "hidden" : ""} ${dragState.draggedItem === item ? "dragging" : ""} ${isSelected ? "selected" : ""} ${
|
||||
isMultiSelected ? "multi-selected" : ""
|
||||
}`}
|
||||
className={clsx("tree-node-content", {
|
||||
locked: isLocked,
|
||||
hidden: !isVisible,
|
||||
dragging: dragState.draggedItem === item,
|
||||
selected: isSelected,
|
||||
"multi-selected": isMultiSelected,
|
||||
})}
|
||||
style={{ paddingLeft: `${level * 25 + 8}px` }}
|
||||
draggable={!isLocked}
|
||||
onDragStart={handleNodeDragStart}
|
||||
@@ -124,12 +138,17 @@ const TreeNode = ({
|
||||
onClick={handleNodeClick}
|
||||
>
|
||||
{isGroupNode && (
|
||||
<button className="expand-button" onClick={() => onToggleExpand(item.groupUuid, !isExpanded)}>
|
||||
<button
|
||||
className="expand-button"
|
||||
onClick={() => onToggleExpand(item.groupUuid, !isExpanded)}
|
||||
>
|
||||
<ChevronIcon isOpen={isExpanded} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="node-icon">{isGroupNode ? <FolderIcon isOpen={isExpanded} /> : <CubeIcon />}</div>
|
||||
<div className="node-icon">
|
||||
{isGroupNode ? <FolderIcon isOpen={isExpanded} /> : <CubeIcon />}
|
||||
</div>
|
||||
|
||||
<RenameInput value={itemName} onRename={() => {}} canEdit={true} />
|
||||
|
||||
@@ -166,7 +185,14 @@ const TreeNode = ({
|
||||
</div>
|
||||
|
||||
{isGroupNode && isExpanded && item.children && (
|
||||
<div className="tree-children">
|
||||
<div
|
||||
className="tree-children"
|
||||
style={
|
||||
{
|
||||
"--left": level,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{item.children.map((child) => (
|
||||
<TreeNode
|
||||
key={isGroup(child) ? child.groupUuid : child.modelUuid}
|
||||
@@ -200,8 +226,24 @@ export const Outline = () => {
|
||||
});
|
||||
const [_, forceUpdate] = useState({});
|
||||
const { scene, assetGroupStore, assetStore, versionStore, undoRedo3DStore } = useSceneContext();
|
||||
const { addSelectedAsset, clearSelectedAssets, getAssetById, peekToggleVisibility, toggleSelectedAsset, selectedAssets } = assetStore();
|
||||
const { groupHierarchy, isGroup, getGroupsContainingAsset, getFlatGroupChildren, setGroupExpanded, addChildToGroup, removeChildFromGroup, getGroupsContainingGroup } = assetGroupStore();
|
||||
const {
|
||||
addSelectedAsset,
|
||||
clearSelectedAssets,
|
||||
getAssetById,
|
||||
peekToggleVisibility,
|
||||
toggleSelectedAsset,
|
||||
selectedAssets,
|
||||
} = assetStore();
|
||||
const {
|
||||
groupHierarchy,
|
||||
isGroup,
|
||||
getGroupsContainingAsset,
|
||||
getFlatGroupChildren,
|
||||
setGroupExpanded,
|
||||
addChildToGroup,
|
||||
removeChildFromGroup,
|
||||
getGroupsContainingGroup,
|
||||
} = assetGroupStore();
|
||||
const { projectId } = useParams();
|
||||
const { push3D } = undoRedo3DStore();
|
||||
const { builderSocket } = useSocketStore();
|
||||
@@ -245,7 +287,11 @@ export const Outline = () => {
|
||||
})
|
||||
.then((data) => {
|
||||
if (!data.message || !data.data) {
|
||||
echo.error(`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`);
|
||||
echo.error(
|
||||
`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${
|
||||
asset.modelName
|
||||
}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (data.message === "Model updated successfully" && data.data) {
|
||||
@@ -264,14 +310,22 @@ export const Outline = () => {
|
||||
};
|
||||
|
||||
updateAssetInScene(model, () => {
|
||||
echo.info(`${asset.isVisible ? "Hid" : "Unhid"} asset: ${model.modelName}`);
|
||||
echo.info(
|
||||
`${asset.isVisible ? "Hid" : "Unhid"} asset: ${model.modelName}`
|
||||
);
|
||||
});
|
||||
} else {
|
||||
echo.error(`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`);
|
||||
echo.error(
|
||||
`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${
|
||||
asset.modelName
|
||||
}`
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
echo.error(`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`);
|
||||
echo.error(
|
||||
`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`
|
||||
);
|
||||
});
|
||||
} else {
|
||||
const data = {
|
||||
@@ -311,7 +365,7 @@ export const Outline = () => {
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
forceUpdate({});
|
||||
},
|
||||
[isGroup]
|
||||
[]
|
||||
);
|
||||
|
||||
const handleDragOver = useCallback(
|
||||
@@ -382,7 +436,10 @@ export const Outline = () => {
|
||||
}
|
||||
|
||||
// Update target group
|
||||
if (dragStateRef.current.targetGroupUuid !== targetGroupUuid || dragStateRef.current.isRootTarget !== false) {
|
||||
if (
|
||||
dragStateRef.current.targetGroupUuid !== targetGroupUuid ||
|
||||
dragStateRef.current.isRootTarget !== false
|
||||
) {
|
||||
dragStateRef.current.targetGroupUuid = targetGroupUuid;
|
||||
dragStateRef.current.isRootTarget = false;
|
||||
forceUpdate({});
|
||||
@@ -449,9 +506,15 @@ export const Outline = () => {
|
||||
console.log("Dropped:", draggedItem, "into group:", targetGroupUuid);
|
||||
|
||||
if (isGroup(draggedItem)) {
|
||||
addChildToGroup(targetGroupUuid, { type: "Group", childrenUuid: draggedItem.groupUuid });
|
||||
addChildToGroup(targetGroupUuid, {
|
||||
type: "Group",
|
||||
childrenUuid: draggedItem.groupUuid,
|
||||
});
|
||||
} else {
|
||||
addChildToGroup(targetGroupUuid, { type: "Asset", childrenUuid: draggedItem.modelUuid });
|
||||
addChildToGroup(targetGroupUuid, {
|
||||
type: "Asset",
|
||||
childrenUuid: draggedItem.modelUuid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +553,9 @@ export const Outline = () => {
|
||||
|
||||
// Update last selected reference
|
||||
const flattened = getFlattenedHierarchy();
|
||||
const index = flattened.findIndex((flatItem) => getItemId(flatItem) === getItemId(item));
|
||||
const index = flattened.findIndex(
|
||||
(flatItem) => getItemId(flatItem) === getItemId(item)
|
||||
);
|
||||
lastSelectedRef.current = { item, index };
|
||||
}
|
||||
}
|
||||
@@ -504,7 +569,9 @@ export const Outline = () => {
|
||||
|
||||
// Update last selected reference
|
||||
const flattened = getFlattenedHierarchy();
|
||||
const index = flattened.findIndex((flatItem) => getItemId(flatItem) === getItemId(item));
|
||||
const index = flattened.findIndex(
|
||||
(flatItem) => getItemId(flatItem) === getItemId(item)
|
||||
);
|
||||
lastSelectedRef.current = { item, index };
|
||||
}
|
||||
}
|
||||
@@ -512,7 +579,9 @@ export const Outline = () => {
|
||||
// Shift+Click - range selection
|
||||
else if (isShiftClick) {
|
||||
const flattened = getFlattenedHierarchy();
|
||||
const clickedIndex = flattened.findIndex((flatItem) => getItemId(flatItem) === getItemId(item));
|
||||
const clickedIndex = flattened.findIndex(
|
||||
(flatItem) => getItemId(flatItem) === getItemId(item)
|
||||
);
|
||||
|
||||
if (clickedIndex === -1) return;
|
||||
|
||||
@@ -558,7 +627,7 @@ export const Outline = () => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[scene.current, isGroup, getFlattenedHierarchy, clearSelectedAssets, addSelectedAsset, toggleSelectedAsset]
|
||||
[scene, isGroup, clearSelectedAssets, addSelectedAsset, getFlattenedHierarchy, toggleSelectedAsset]
|
||||
);
|
||||
|
||||
const handleOptionClick = useCallback(
|
||||
@@ -620,56 +689,52 @@ export const Outline = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[selectedVersion, builderSocket, projectId, userId, organization]
|
||||
);
|
||||
|
||||
if (!isOpen) {
|
||||
return (
|
||||
<button className="outline-toggle" onClick={() => setIsOpen(true)}>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M3 6h14 M3 10h14 M3 14h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="outline-overlay" onDragEnd={handleDragEnd}>
|
||||
<div className="outline-card">
|
||||
<div className="outline-header">
|
||||
<div className="header-title">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M3 3h6v6H3V3z M11 3h6v6h-6V3z M3 11h6v6H3v-6z M11 11h6v6h-6v-6z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
<h2>Scene Hierarchy</h2>
|
||||
<p>Scene Hierarchy</p>
|
||||
</div>
|
||||
<button className="close-button" onClick={() => setIsOpen(false)}>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M5 5l10 10 M15 5L5 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="outline-toolbar">
|
||||
<button className="toolbar-button" title="Add Group">
|
||||
<AddIcon />
|
||||
</button>
|
||||
<button className="toolbar-button" title="Delete">
|
||||
{/* <button className="toolbar-button" title="Delete">
|
||||
<DeleteIcon />
|
||||
</button>
|
||||
</button> */}
|
||||
<button
|
||||
className="toolbar-button"
|
||||
title="Expand All"
|
||||
onClick={() => {
|
||||
const { assetGroups, setGroupExpanded } = assetGroupStore.getState();
|
||||
assetGroups.forEach((group) => setGroupExpanded(group.groupUuid, true));
|
||||
const { assetGroups, setGroupExpanded } =
|
||||
assetGroupStore.getState();
|
||||
assetGroups.forEach((group) =>
|
||||
setGroupExpanded(group.groupUuid, true)
|
||||
);
|
||||
}}
|
||||
>
|
||||
<CollapseAllIcon />
|
||||
</button>
|
||||
<button className="close-button" onClick={() => setIsOpen(!isOpen)}>
|
||||
<ChevronIcon isOpen />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`outline-content ${dragStateRef.current.isRootTarget ? "root-drop-target" : ""}`} onDragOver={handleRootDragOver} onDrop={handleDrop}>
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`outline-content ${
|
||||
dragStateRef.current.isRootTarget ? "root-drop-target" : ""
|
||||
}`}
|
||||
onDragOver={handleRootDragOver}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
{groupHierarchy.map((item) => (
|
||||
<TreeNode
|
||||
key={isGroup(item) ? item.groupUuid : item.modelUuid}
|
||||
@@ -685,13 +750,18 @@ export const Outline = () => {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="outline-footer">
|
||||
<span className={`footer-stats ${selectedAssets.length > 1 ? "multi-selection" : ""}`}>
|
||||
{selectedAssets.length > 1 ? `${selectedAssets.length} items selected` : `${groupHierarchy.length} root items`}
|
||||
<span
|
||||
className={`footer-stats ${selectedAssets.length > 1 ? "multi-selection" : ""}`}
|
||||
>
|
||||
{selectedAssets.length > 1
|
||||
? `${selectedAssets.length} items selected`
|
||||
: `${groupHierarchy.length} root items`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
344
app/src/styles/layout/_assetOutline.scss
Normal file
344
app/src/styles/layout/_assetOutline.scss
Normal file
@@ -0,0 +1,344 @@
|
||||
@use "../abstracts/variables" as *;
|
||||
@use "../abstracts/mixins" as *;
|
||||
|
||||
.outline-overlay {
|
||||
padding: 0 4px;
|
||||
}
|
||||
.outline-card {
|
||||
border-radius: $border-radius-extra-large;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--background-color);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: var(--box-shadow-medium);
|
||||
}
|
||||
|
||||
// Header
|
||||
.outline-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 16px;
|
||||
padding-right: 12px;
|
||||
position: relative;
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--text-color);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
@include flex-center;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--icon-default-color);
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
min-width: 18px;
|
||||
border-radius: $border-radius-small;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--background-color-solid);
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toolbar
|
||||
.outline-toolbar {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
|
||||
.toolbar-button {
|
||||
@include flex-center;
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
min-width: 18px;
|
||||
border-radius: $border-radius-small;
|
||||
font-size: 13px;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
&:hover {
|
||||
background: var(--background-color-solid);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
.outline-content {
|
||||
max-height: 52vh;
|
||||
overflow-y: auto;
|
||||
border-radius: $border-radius-medium;
|
||||
position: relative;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: var(--background-color-secondary);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--accent-color);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 4px var(--accent-color);
|
||||
|
||||
&:hover {
|
||||
background: var(--accent-color);
|
||||
box-shadow: 0 0 8px var(--accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.root-drop-target {
|
||||
background: var(--background-color-selected);
|
||||
box-shadow: inset 0 0 0 2px var(--border-color-accent),
|
||||
inset 0 0 20px var(--highlight-accent-color);
|
||||
border-radius: $border-radius-medium;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: var(--accent-color);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
pointer-events: none;
|
||||
text-shadow: 0 0 8px var(--accent-color);
|
||||
z-index: 10;
|
||||
background: var(--background-color-drop-down);
|
||||
padding: 8px 16px;
|
||||
border-radius: $border-radius-medium;
|
||||
border: 1px solid var(--border-color-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tree Node
|
||||
.tree-node {
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
&.drop-target-highlight {
|
||||
background: var(--background-color-selected);
|
||||
border-radius: $border-radius-medium;
|
||||
box-shadow: inset 0 0 0 2px var(--border-color-accent),
|
||||
0 0 20px var(--highlight-accent-color), 0 4px 12px var(--background-color-selected);
|
||||
}
|
||||
|
||||
.tree-node-content {
|
||||
@include flex-center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: $border-radius-medium;
|
||||
cursor: grab;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: transparent;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: var(--background-color-accent);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--background-color-selected);
|
||||
}
|
||||
|
||||
&.dragging {
|
||||
opacity: 0.5;
|
||||
background: var(--background-color-selected);
|
||||
cursor: grabbing;
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4px 12px var(--highlight-accent-color);
|
||||
}
|
||||
|
||||
&.locked {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&.multi-selected {
|
||||
background: var(--background-color-selected);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
background: var(--highlight-secondary-color);
|
||||
box-shadow: 0 0 8px var(--highlight-secondary-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--background-color-accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.expand-button {
|
||||
@include flex-center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: $border-radius-small;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--icon-default-color);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--background-color-selected);
|
||||
color: var(--accent-color);
|
||||
box-shadow: 0 0 8px var(--highlight-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
@include flex-center;
|
||||
color: var(--accent-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
flex: 1;
|
||||
color: var(--text-color);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.node-controls {
|
||||
display: flex;
|
||||
|
||||
.control-button {
|
||||
@include flex-center;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--icon-default-color);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: $border-radius-small;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--background-color-selected);
|
||||
color: var(--accent-color);
|
||||
box-shadow: 0 0 6px var(--highlight-accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tree children lines
|
||||
.tree-children {
|
||||
position: relative;
|
||||
--left: 1;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: calc(12px + (var(--left) * 26px));
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
var(--accent-color),
|
||||
var(--background-color-selected)
|
||||
);
|
||||
box-shadow: 0 0 4px var(--highlight-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
.outline-footer {
|
||||
position: fixed;
|
||||
padding: 8px 16px;
|
||||
width: 270px;
|
||||
background: var(--background-color-solid);
|
||||
bottom: -36px;
|
||||
border-radius: 16px;
|
||||
|
||||
.footer-stats {
|
||||
color: var(--text-color);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.multi-selection {
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle Button
|
||||
.outline-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: $z-index-tools;
|
||||
background: var(--background-color-button);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid var(--border-color-accent);
|
||||
border-radius: $border-radius-extra-large;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
color: var(--text-button-color);
|
||||
box-shadow: var(--box-shadow-medium);
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
animation: fadeIn 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08) rotate(2deg);
|
||||
box-shadow: var(--box-shadow-heavy);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes cardGlow {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@
|
||||
@use "layout/compareLayout";
|
||||
@use "layout/resourceManagement.scss";
|
||||
@use "layout/previewModel";
|
||||
@use "layout/assetOutline";
|
||||
|
||||
// pages
|
||||
@use "pages/dashboard";
|
||||
|
||||
Reference in New Issue
Block a user