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",
|
"@use-gesture/react": "^10.3.1",
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
"chartjs-plugin-annotation": "^3.1.0",
|
"chartjs-plugin-annotation": "^3.1.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"dxf-parser": "^1.1.2",
|
"dxf-parser": "^1.1.2",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.12.5",
|
||||||
@@ -8760,6 +8761,7 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"@use-gesture/react": "^10.3.1",
|
"@use-gesture/react": "^10.3.1",
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
"chartjs-plugin-annotation": "^3.1.0",
|
"chartjs-plugin-annotation": "^3.1.0",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
"dxf-parser": "^1.1.2",
|
"dxf-parser": "^1.1.2",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.12.5",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,6 @@ import { recentlyViewedApi } from "../../../services/dashboard/recentlyViewedApi
|
|||||||
import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||||
import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi";
|
import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi";
|
||||||
import { getUserData } from "../../../functions/getUserData";
|
import { getUserData } from "../../../functions/getUserData";
|
||||||
import { Outline } from "../../../modules/builder/testUi/outline";
|
|
||||||
|
|
||||||
function MainScene() {
|
function MainScene() {
|
||||||
const { setMainState, clearComparisonState } = useSimulationState();
|
const { setMainState, clearComparisonState } = useSimulationState();
|
||||||
@@ -186,7 +185,6 @@ function MainScene() {
|
|||||||
<>
|
<>
|
||||||
{!selectedUser && (
|
{!selectedUser && (
|
||||||
<>
|
<>
|
||||||
<Outline />
|
|
||||||
<KeyPressListener />
|
<KeyPressListener />
|
||||||
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
|
||||||
{!isPlaying && (
|
{!isPlaying && (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ToggleHeader from "../../ui/inputs/ToggleHeader";
|
import ToggleHeader from "../../ui/inputs/ToggleHeader";
|
||||||
import Outline from "./Outline";
|
// import Outline from "./Outline";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import { useToggleStore } from "../../../store/ui/useUIToggleStore";
|
import { useToggleStore } from "../../../store/ui/useUIToggleStore";
|
||||||
import Assets from "./assetList/Assets";
|
import Assets from "./assetList/Assets";
|
||||||
@@ -9,6 +9,7 @@ import Widgets from "./visualization/widgets/Widgets";
|
|||||||
import Templates from "../../../modules/visualization/template/Templates";
|
import Templates from "../../../modules/visualization/template/Templates";
|
||||||
import Search from "../../ui/inputs/Search";
|
import Search from "../../ui/inputs/Search";
|
||||||
import { useIsComparing } from "../../../store/builder/store";
|
import { useIsComparing } from "../../../store/builder/store";
|
||||||
|
import { Outline } from "../../../modules/builder/testUi/outline";
|
||||||
|
|
||||||
const SideBarLeft: React.FC = () => {
|
const SideBarLeft: React.FC = () => {
|
||||||
const [activeOption, setActiveOption] = useState("Widgets");
|
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 { useState, useRef, DragEvent, useCallback } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
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 RenameInput from "../../../components/ui/inputs/RenameInput";
|
||||||
import { useSceneContext } from "../../scene/sceneContext";
|
import { useSceneContext } from "../../scene/sceneContext";
|
||||||
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
import { useSocketStore } from "../../../store/socket/useSocketStore";
|
||||||
import useAssetResponseHandler from "../../collaboration/responseHandler/useAssetResponseHandler";
|
import useAssetResponseHandler from "../../collaboration/responseHandler/useAssetResponseHandler";
|
||||||
import "./outline.css";
|
|
||||||
|
|
||||||
import { getUserData } from "../../../functions/getUserData";
|
import { getUserData } from "../../../functions/getUserData";
|
||||||
import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
interface DragState {
|
interface DragState {
|
||||||
draggedItem: AssetGroupChild | null;
|
draggedItem: AssetGroupChild | null;
|
||||||
@@ -51,7 +60,8 @@ const TreeNode = ({
|
|||||||
const isLocked = item.isLocked;
|
const isLocked = item.isLocked;
|
||||||
const isExpanded = isGroupNode ? item.isExpanded : false;
|
const isExpanded = isGroupNode ? item.isExpanded : false;
|
||||||
const isSelected = !isGroupNode ? hasSelectedAsset(item.modelUuid) : 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
|
// Determine the parent group of this item
|
||||||
const getParentGroup = useCallback(
|
const getParentGroup = useCallback(
|
||||||
@@ -76,7 +86,7 @@ const TreeNode = ({
|
|||||||
|
|
||||||
// Highlight if this is the target group or belongs to the target group
|
// Highlight if this is the target group or belongs to the target group
|
||||||
return thisGroupUuid === dragState.targetGroupUuid;
|
return thisGroupUuid === dragState.targetGroupUuid;
|
||||||
}, [dragState, isGroupNode, item, getParentGroup]);
|
}, [dragState, isGroupNode, item]);
|
||||||
|
|
||||||
const handleNodeDragStart = (e: DragEvent) => {
|
const handleNodeDragStart = (e: DragEvent) => {
|
||||||
const parentGroupUuid = getParentGroup(item);
|
const parentGroupUuid = getParentGroup(item);
|
||||||
@@ -112,9 +122,13 @@ const TreeNode = ({
|
|||||||
return (
|
return (
|
||||||
<div className={`tree-node ${shouldShowHighlight ? "drop-target-highlight" : ""}`}>
|
<div className={`tree-node ${shouldShowHighlight ? "drop-target-highlight" : ""}`}>
|
||||||
<div
|
<div
|
||||||
className={`tree-node-content ${isLocked ? "locked" : ""} ${!isVisible ? "hidden" : ""} ${dragState.draggedItem === item ? "dragging" : ""} ${isSelected ? "selected" : ""} ${
|
className={clsx("tree-node-content", {
|
||||||
isMultiSelected ? "multi-selected" : ""
|
locked: isLocked,
|
||||||
}`}
|
hidden: !isVisible,
|
||||||
|
dragging: dragState.draggedItem === item,
|
||||||
|
selected: isSelected,
|
||||||
|
"multi-selected": isMultiSelected,
|
||||||
|
})}
|
||||||
style={{ paddingLeft: `${level * 25 + 8}px` }}
|
style={{ paddingLeft: `${level * 25 + 8}px` }}
|
||||||
draggable={!isLocked}
|
draggable={!isLocked}
|
||||||
onDragStart={handleNodeDragStart}
|
onDragStart={handleNodeDragStart}
|
||||||
@@ -124,12 +138,17 @@ const TreeNode = ({
|
|||||||
onClick={handleNodeClick}
|
onClick={handleNodeClick}
|
||||||
>
|
>
|
||||||
{isGroupNode && (
|
{isGroupNode && (
|
||||||
<button className="expand-button" onClick={() => onToggleExpand(item.groupUuid, !isExpanded)}>
|
<button
|
||||||
|
className="expand-button"
|
||||||
|
onClick={() => onToggleExpand(item.groupUuid, !isExpanded)}
|
||||||
|
>
|
||||||
<ChevronIcon isOpen={isExpanded} />
|
<ChevronIcon isOpen={isExpanded} />
|
||||||
</button>
|
</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} />
|
<RenameInput value={itemName} onRename={() => {}} canEdit={true} />
|
||||||
|
|
||||||
@@ -166,7 +185,14 @@ const TreeNode = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isGroupNode && isExpanded && item.children && (
|
{isGroupNode && isExpanded && item.children && (
|
||||||
<div className="tree-children">
|
<div
|
||||||
|
className="tree-children"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--left": level,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
>
|
||||||
{item.children.map((child) => (
|
{item.children.map((child) => (
|
||||||
<TreeNode
|
<TreeNode
|
||||||
key={isGroup(child) ? child.groupUuid : child.modelUuid}
|
key={isGroup(child) ? child.groupUuid : child.modelUuid}
|
||||||
@@ -200,8 +226,24 @@ export const Outline = () => {
|
|||||||
});
|
});
|
||||||
const [_, forceUpdate] = useState({});
|
const [_, forceUpdate] = useState({});
|
||||||
const { scene, assetGroupStore, assetStore, versionStore, undoRedo3DStore } = useSceneContext();
|
const { scene, assetGroupStore, assetStore, versionStore, undoRedo3DStore } = useSceneContext();
|
||||||
const { addSelectedAsset, clearSelectedAssets, getAssetById, peekToggleVisibility, toggleSelectedAsset, selectedAssets } = assetStore();
|
const {
|
||||||
const { groupHierarchy, isGroup, getGroupsContainingAsset, getFlatGroupChildren, setGroupExpanded, addChildToGroup, removeChildFromGroup, getGroupsContainingGroup } = assetGroupStore();
|
addSelectedAsset,
|
||||||
|
clearSelectedAssets,
|
||||||
|
getAssetById,
|
||||||
|
peekToggleVisibility,
|
||||||
|
toggleSelectedAsset,
|
||||||
|
selectedAssets,
|
||||||
|
} = assetStore();
|
||||||
|
const {
|
||||||
|
groupHierarchy,
|
||||||
|
isGroup,
|
||||||
|
getGroupsContainingAsset,
|
||||||
|
getFlatGroupChildren,
|
||||||
|
setGroupExpanded,
|
||||||
|
addChildToGroup,
|
||||||
|
removeChildFromGroup,
|
||||||
|
getGroupsContainingGroup,
|
||||||
|
} = assetGroupStore();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { push3D } = undoRedo3DStore();
|
const { push3D } = undoRedo3DStore();
|
||||||
const { builderSocket } = useSocketStore();
|
const { builderSocket } = useSocketStore();
|
||||||
@@ -245,7 +287,11 @@ export const Outline = () => {
|
|||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.message || !data.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;
|
return;
|
||||||
}
|
}
|
||||||
if (data.message === "Model updated successfully" && data.data) {
|
if (data.message === "Model updated successfully" && data.data) {
|
||||||
@@ -264,14 +310,22 @@ export const Outline = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateAssetInScene(model, () => {
|
updateAssetInScene(model, () => {
|
||||||
echo.info(`${asset.isVisible ? "Hid" : "Unhid"} asset: ${model.modelName}`);
|
echo.info(
|
||||||
|
`${asset.isVisible ? "Hid" : "Unhid"} asset: ${model.modelName}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
echo.error(`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`);
|
echo.error(
|
||||||
|
`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${
|
||||||
|
asset.modelName
|
||||||
|
}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
echo.error(`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`);
|
echo.error(
|
||||||
|
`Error ${asset.isVisible ? "hiding" : "unhiding"} asset: ${asset.modelName}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -311,7 +365,7 @@ export const Outline = () => {
|
|||||||
e.dataTransfer.effectAllowed = "move";
|
e.dataTransfer.effectAllowed = "move";
|
||||||
forceUpdate({});
|
forceUpdate({});
|
||||||
},
|
},
|
||||||
[isGroup]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDragOver = useCallback(
|
const handleDragOver = useCallback(
|
||||||
@@ -382,7 +436,10 @@ export const Outline = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update target group
|
// 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.targetGroupUuid = targetGroupUuid;
|
||||||
dragStateRef.current.isRootTarget = false;
|
dragStateRef.current.isRootTarget = false;
|
||||||
forceUpdate({});
|
forceUpdate({});
|
||||||
@@ -449,9 +506,15 @@ export const Outline = () => {
|
|||||||
console.log("Dropped:", draggedItem, "into group:", targetGroupUuid);
|
console.log("Dropped:", draggedItem, "into group:", targetGroupUuid);
|
||||||
|
|
||||||
if (isGroup(draggedItem)) {
|
if (isGroup(draggedItem)) {
|
||||||
addChildToGroup(targetGroupUuid, { type: "Group", childrenUuid: draggedItem.groupUuid });
|
addChildToGroup(targetGroupUuid, {
|
||||||
|
type: "Group",
|
||||||
|
childrenUuid: draggedItem.groupUuid,
|
||||||
|
});
|
||||||
} else {
|
} 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
|
// Update last selected reference
|
||||||
const flattened = getFlattenedHierarchy();
|
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 };
|
lastSelectedRef.current = { item, index };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,7 +569,9 @@ export const Outline = () => {
|
|||||||
|
|
||||||
// Update last selected reference
|
// Update last selected reference
|
||||||
const flattened = getFlattenedHierarchy();
|
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 };
|
lastSelectedRef.current = { item, index };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -512,7 +579,9 @@ export const Outline = () => {
|
|||||||
// Shift+Click - range selection
|
// Shift+Click - range selection
|
||||||
else if (isShiftClick) {
|
else if (isShiftClick) {
|
||||||
const flattened = getFlattenedHierarchy();
|
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;
|
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(
|
const handleOptionClick = useCallback(
|
||||||
@@ -620,78 +689,79 @@ export const Outline = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[selectedVersion, builderSocket, projectId, userId, organization]
|
[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 (
|
return (
|
||||||
<div className="outline-overlay" onDragEnd={handleDragEnd}>
|
<>
|
||||||
<div className="outline-card">
|
<div className="outline-overlay" onDragEnd={handleDragEnd}>
|
||||||
<div className="outline-header">
|
<div className="outline-card">
|
||||||
<div className="header-title">
|
<div className="outline-header">
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
<div className="header-title">
|
||||||
<path d="M3 3h6v6H3V3z M11 3h6v6h-6V3z M3 11h6v6H3v-6z M11 11h6v6h-6v-6z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
<p>Scene Hierarchy</p>
|
||||||
</svg>
|
</div>
|
||||||
<h2>Scene Hierarchy</h2>
|
<div className="outline-toolbar">
|
||||||
|
<button className="toolbar-button" title="Add Group">
|
||||||
|
<AddIcon />
|
||||||
|
</button>
|
||||||
|
{/* <button className="toolbar-button" title="Delete">
|
||||||
|
<DeleteIcon />
|
||||||
|
</button> */}
|
||||||
|
<button
|
||||||
|
className="toolbar-button"
|
||||||
|
title="Expand All"
|
||||||
|
onClick={() => {
|
||||||
|
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>
|
||||||
<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">
|
{isOpen && (
|
||||||
<button className="toolbar-button" title="Add Group">
|
<div
|
||||||
<AddIcon />
|
className={`outline-content ${
|
||||||
</button>
|
dragStateRef.current.isRootTarget ? "root-drop-target" : ""
|
||||||
<button className="toolbar-button" title="Delete">
|
}`}
|
||||||
<DeleteIcon />
|
onDragOver={handleRootDragOver}
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="toolbar-button"
|
|
||||||
title="Expand All"
|
|
||||||
onClick={() => {
|
|
||||||
const { assetGroups, setGroupExpanded } = assetGroupStore.getState();
|
|
||||||
assetGroups.forEach((group) => setGroupExpanded(group.groupUuid, true));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ChevronIcon isOpen />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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}
|
|
||||||
item={item}
|
|
||||||
dragState={dragStateRef.current}
|
|
||||||
onDragStart={handleDragStart}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
onDragLeave={handleDragLeave}
|
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
onClick={handleClick}
|
>
|
||||||
onToggleExpand={handleToggleExpand}
|
{groupHierarchy.map((item) => (
|
||||||
onOptionClick={handleOptionClick}
|
<TreeNode
|
||||||
/>
|
key={isGroup(item) ? item.groupUuid : item.modelUuid}
|
||||||
))}
|
item={item}
|
||||||
</div>
|
dragState={dragStateRef.current}
|
||||||
|
onDragStart={handleDragStart}
|
||||||
<div className="outline-footer">
|
onDragOver={handleDragOver}
|
||||||
<span className={`footer-stats ${selectedAssets.length > 1 ? "multi-selection" : ""}`}>
|
onDragLeave={handleDragLeave}
|
||||||
{selectedAssets.length > 1 ? `${selectedAssets.length} items selected` : `${groupHierarchy.length} root items`}
|
onDrop={handleDrop}
|
||||||
</span>
|
onClick={handleClick}
|
||||||
|
onToggleExpand={handleToggleExpand}
|
||||||
|
onOptionClick={handleOptionClick}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
</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/compareLayout";
|
||||||
@use "layout/resourceManagement.scss";
|
@use "layout/resourceManagement.scss";
|
||||||
@use "layout/previewModel";
|
@use "layout/previewModel";
|
||||||
|
@use "layout/assetOutline";
|
||||||
|
|
||||||
// pages
|
// pages
|
||||||
@use "pages/dashboard";
|
@use "pages/dashboard";
|
||||||
|
|||||||
Reference in New Issue
Block a user