diff --git a/package-lock.json b/package-lock.json index bba4f14..a2a225c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,15 @@ "@types/node": "^16.18.126", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", + "clsx": "^2.1.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "sass": "^1.93.2" } }, "node_modules/@adobe/css-tools": { @@ -3019,6 +3023,316 @@ "node": ">= 8" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5678,6 +5992,15 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6621,6 +6944,20 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -9211,6 +9548,13 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -11614,6 +11958,14 @@ "tslib": "^2.0.3" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -14522,6 +14874,27 @@ "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==", "license": "CC0-1.0" }, + "node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/sass-loader": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", @@ -14560,6 +14933,36 @@ } } }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", diff --git a/package.json b/package.json index 4b8db03..b80a350 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", + "clsx": "^2.1.1", "react": "^19.2.0", "react-dom": "^19.2.0", "react-scripts": "5.0.1", @@ -40,5 +41,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "sass": "^1.93.2" } } diff --git a/src/App.tsx b/src/App.tsx index a53698a..4765be0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,8 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import SceneView from "./pages/SceneView"; +import "./styles/main.scss"; function App() { - return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); + return ; } export default App; diff --git a/src/components/outlinePanel/OutlineList .tsx b/src/components/outlinePanel/OutlineList .tsx new file mode 100644 index 0000000..934774a --- /dev/null +++ b/src/components/outlinePanel/OutlineList .tsx @@ -0,0 +1,487 @@ +import React, { useState } from "react"; +import { + AddIcon, + ChevronIcon, + CollapseAllIcon, + CubeIcon, + EyeIcon, + FocusIcon, + FolderIcon, + KebebIcon, + LockIcon, +} from "../../icons/ExportIcons"; +import { OutlinePanelProps } from "./OutlinePanel"; +import clsx from "clsx"; + +interface AssetGroupChild { + groupUuid?: string; + groupName?: string; + isExpanded?: boolean; + children?: AssetGroupChild[]; + modelUuid?: string; + modelName?: string; + isVisible?: boolean; + isLocked?: boolean; +} + +interface TreeNodeProps { + item: AssetGroupChild; + level?: number; + textColor?: string; + eyeIconColor?: string; + lockIconColor?: string; + onDragStart: (item: AssetGroupChild) => void; + onDrop: (item: AssetGroupChild, parent: AssetGroupChild | null, e: any) => void; + draggingItem: AssetGroupChild | null; + selectedObject: string | null; + onDragOver: (item: AssetGroupChild, parent: AssetGroupChild | null, e: React.DragEvent) => void; + setSelectedObject: React.Dispatch>; +} + +type DropAction = "above" | "child" | "below" | "none"; + +const DEFAULT_PRAMS = { + backgroundColor: "linear-gradient(to bottom, #1e1e2f, #12121a)", + panelSide: "left", + textColor: "linear-gradient(to bottom, rgba(231, 231, 255, 1), #cacad6ff)", + addIconColor: "white", + lockIconColor: "white", + eyeIconColor: "white", +}; + +const TreeNode: React.FC = ({ + item, + level = 0, + textColor, + eyeIconColor, + lockIconColor, + onDragStart, + onDrop, + draggingItem, + setSelectedObject, + selectedObject, + onDragOver, +}) => { + const isGroupNode = + Array.isArray(item.children) && item.children.length > 0 ? item.children : false; + const [isExpanded, setIsExpanded] = useState(item.isExpanded || false); + const [isVisible, setIsVisible] = useState(item.isVisible ?? true); + const [isLocked, setIsLocked] = useState(item.isLocked ?? false); + const [isEditing, setIsEditing] = useState(false); + const [name, setName] = useState(isGroupNode ? item.groupName : item.modelName); + + const handleDragStart = (e: React.DragEvent) => { + e.stopPropagation(); + onDragStart(item); + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + onDragOver(item, draggingItem, e); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + onDrop(item, draggingItem, e); + }; + + const isBeingDragged = + draggingItem?.groupUuid === item.groupUuid || draggingItem?.modelUuid === item.modelUuid; + + const handleDoubleClick = () => { + setIsEditing(true); + }; + + const handleBlur = () => { + setIsEditing(false); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + setIsEditing(false); + } + }; + + // 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 handleDivKeyDown = (e: React.KeyboardEvent) => { + // support Enter and Space for accessibility + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggleSelect(); + } + }; + + const handleTouchStart = () => { + toggleSelect(); + }; + + return ( +
+
+
+ {isGroupNode && ( + + )} + +
+ {isGroupNode ? : } +
+ +
+ {isEditing ? ( + setName(e.target.value)} + onBlur={handleBlur} + onKeyDown={handleKeyDown} + /> + ) : ( + + {name} + + )} +
+ +
+ + {isGroupNode && item.children?.length ? ( + + ) : null} + + {isGroupNode ? ( + + ) : null} +
+
+
+ {isExpanded && item.children?.length ? ( +
+ {item.children.map((child) => ( + + ))} +
+ ) : null} +
+ ); +}; + +export const OutlineList: React.FC = ({ + backgroundColor = "linear-gradient(to bottom, #1e1e2f, #12121a)", + panelSide = "left", + textColor = "linear-gradient(to bottom, rgba(231, 231, 255, 1), #cacad6ff)", + addIconColor, + lockIconColor, + eyeIconColor, +}) => { + const [selectedObject, setSelectedObject] = useState(null); // store UUID + + const [isOpen, setIsOpen] = useState(true); + const [draggingItem, setDraggingItem] = useState(null); + const [groupHierarchy, setGroupHierarchy] = useState([ + { modelUuid: "a1", modelName: "Asset 1", isVisible: true, isLocked: false, children: [] }, + { modelUuid: "a2", modelName: "Asset 2", isVisible: true, isLocked: false, children: [] }, + { modelUuid: "a3", modelName: "Asset 3", isVisible: true, isLocked: false, children: [] }, + { modelUuid: "a4", modelName: "Asset 4", isVisible: true, isLocked: false, children: [] }, + { modelUuid: "a5", modelName: "Asset 5", isVisible: true, isLocked: false, children: [] }, + { modelUuid: "a6", modelName: "Asset 6", isVisible: true, isLocked: false, children: [] }, + ]); + + const handleDragStart = (item: AssetGroupChild) => { + setDraggingItem(item); // Set the dragged item when dragging starts + }; + + const handleDrop = ( + targetItem: AssetGroupChild, + draggedItem: AssetGroupChild | null, + event: DragEvent | React.DragEvent + ) => { + if (!draggedItem) return; + + const targetId = targetItem.modelUuid; + if (!targetId) return; + + const hoveredDiv = document.getElementById(targetId); + if (!hoveredDiv) return; + + // Calculate drop position + const rect = hoveredDiv.getBoundingClientRect(); + const parentScrollTop = hoveredDiv.parentElement?.scrollTop || 0; + const y = (event as any).clientY - (rect.top + parentScrollTop); + + // Determine drop action + const action = getDropAction(y); + if (action === "none") { + hoveredDiv.style.borderTop = "none"; + hoveredDiv.style.borderBottom = "none"; + hoveredDiv.style.outline = "none"; + hoveredDiv.style.border = "none"; + setDraggingItem(null); + return; + } + + // Update hierarchy + const updatedHierarchy = [...groupHierarchy]; + + if (!removeItemFromHierarchy(draggedItem, updatedHierarchy)) { + return; + } + + if (!insertItemByAction(draggedItem, targetId, action, updatedHierarchy)) { + updatedHierarchy.push(draggedItem); + } + + // Commit changes + setGroupHierarchy(updatedHierarchy); + setDraggingItem(null); + hoveredDiv.style.borderTop = "none"; + hoveredDiv.style.borderBottom = "none"; + hoveredDiv.style.outline = "none"; + hoveredDiv.style.border = "none"; + }; + + const getDropAction = (y: number): DropAction => { + if (y >= 0 && y < 7) return "above"; + if (y >= 7 && y < 19) return "child"; + if (y >= 19 && y < 32) return "below"; + return "none"; + }; + + const removeItemFromHierarchy = ( + item: AssetGroupChild, + hierarchy: AssetGroupChild[] + ): boolean => { + for (let i = 0; i < hierarchy.length; i++) { + const current = hierarchy[i]; + if (current.modelUuid === item.modelUuid) { + hierarchy.splice(i, 1); + return true; + } + if (current.children?.length) { + const removed = removeItemFromHierarchy(item, current.children); + if (removed) return true; + } + } + return false; + }; + + const insertItemByAction = ( + item: AssetGroupChild, + targetId: string, + action: DropAction, + hierarchy: AssetGroupChild[] + ): boolean => { + switch (action) { + case "above": + return insertAsSibling(item, targetId, hierarchy, 0); + case "below": + return insertAsSibling(item, targetId, hierarchy, 1); + case "child": + return addAsChild(targetId, item, hierarchy); + default: + return false; + } + }; + + const insertAsSibling = ( + item: AssetGroupChild, + targetId: string, + hierarchy: AssetGroupChild[], + offset: number // 0 for above, 1 for below + ): boolean => { + for (let i = 0; i < hierarchy.length; i++) { + if (hierarchy[i].modelUuid === targetId) { + hierarchy.splice(i + offset, 0, item); + return true; + } + if (hierarchy[i].children?.length) { + const inserted = insertAsSibling(item, targetId, hierarchy[i].children!, offset); + if (inserted) return true; + } + } + return false; + }; + + const addAsChild = ( + parentId: string, + childItem: AssetGroupChild, + hierarchy: AssetGroupChild[] + ): boolean => { + for (let i = 0; i < hierarchy.length; i++) { + if (hierarchy[i].modelUuid === parentId) { + if (!hierarchy[i].children) hierarchy[i].children = []; + hierarchy[i].children!.push(childItem); + return true; + } + if (hierarchy[i].children?.length) { + const added = addAsChild(parentId, childItem, hierarchy[i].children!); + if (added) return true; + } + } + return false; + }; + + const handleDragOver = ( + targetItem: AssetGroupChild, + draggedItem: AssetGroupChild | null, + event: DragEvent | React.DragEvent + ) => { + event.preventDefault(); + const targetId = targetItem?.modelUuid || targetItem?.groupUuid; + if (!targetId) return; + + const hoveredDiv = document.getElementById(targetId); + if (!hoveredDiv) return; + + // Remove previous outlines before applying new one + hoveredDiv.style.outline = "none"; + hoveredDiv.style.borderTop = "none"; + hoveredDiv.style.borderBottom = "none"; + + const rect = hoveredDiv.getBoundingClientRect(); + const y = (event as any).clientY - rect.top; + + // Determine where the user is hovering + if (y >= 0 && y < 7) { + // Top region + console.log("Top: "); + + hoveredDiv.style.borderTop = "2px solid purple"; + return "above"; + } else if (y >= 19 && y < 32) { + // Bottom region + console.log("Bottom: "); + hoveredDiv.style.borderBottom = "2px solid purple"; + return "below"; + } else { + // Middle region (child) + console.log("Middle: "); + hoveredDiv.style.outline = "2px solid #b188ff"; + return "child"; + } + }; + + return ( +
+
+
+
+

Outline

+
+
+ + + +
+
+ + {isOpen && ( +
+ {groupHierarchy.map((item) => ( + + ))} +
+ )} +
+
+ ); +}; diff --git a/src/components/outlinePanel/OutlinePanel.tsx b/src/components/outlinePanel/OutlinePanel.tsx new file mode 100644 index 0000000..5de1e82 --- /dev/null +++ b/src/components/outlinePanel/OutlinePanel.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { OutlineList } from "./OutlineList "; + +export interface OutlinePanelProps { + textColor?: string; + panelSide?: "left" | "right"; + backgroundColor?: string; + addIconColor?: string; + eyeIconColor?: string; + lockIconColor?: string; +} + +const OutlinePanel: React.FC = (props) => { + return ; +}; + +export default OutlinePanel; diff --git a/src/icons/ExportIcons.tsx b/src/icons/ExportIcons.tsx new file mode 100644 index 0000000..64d5283 --- /dev/null +++ b/src/icons/ExportIcons.tsx @@ -0,0 +1,1742 @@ +export function SearchIcon() { + return ( + + + + ); +} + +export function ArrowIcon() { + return ( + + + + ); +} + +export function FocusIcon() { + return ( + + + + + ); +} + +export function KebebIcon() { + return ( + + + + + + ); +} +interface IconProps { + color?: string; + size?: number; // optional for width/height + isClosed?: boolean; + isLocked?: boolean; +} + +export function AddIcon({ color = "white", size = 12 }: IconProps) { + return ( + + + + ); +} + +export function EyeIcon({ color = "white", isClosed }: IconProps) { + return isClosed ? ( + + + + + + + + + + + ) : ( + + + + + ); +} + +export function LockIcon({ color = "white", isLocked }: IconProps) { + return isLocked ? ( + + + + ) : ( + + + + ); +} +export function CloseIcon() { + return ( + + + + + ); +} + +export function SettingsIcon() { + return ( + + + + + + + + + + + + ); +} + +export function HelpIcon() { + return ( + + + + + + + + + + + ); +} + +export function TrashIcon() { + return ( + + + + + + + + + + + ); +} + +export function FilterIcon() { + return ( + + + + + + + + + ); +} + +export function EyeDroperIcon({ isActive }: { isActive: boolean }) { + return ( + + + + + + ); +} + +export function TickIcon() { + return ( + + + + ); +} + +export function UndoIcon() { + return ( + + + + ); +} + +export function RedoIcon() { + return ( + + + + ); +} + +export function ResizeHeightIcon() { + return ( + + + + + + + + + + + ); +} + +export function RemoveIcon() { + return ( + + + + ); +} + +export function InfoIcon() { + return ( + + + + + ); +} + +export function AIIcon() { + return ( + + + + + + ); +} + +export const KebabIcon = () => { + return ( + + + + + + ); +}; + +export const DublicateIcon = () => { + return ( + + + + ); +}; + +export const DeleteIcon = () => { + return ( + + + + + + + + ); +}; + +export const HourlySimulationIcon = () => { + return ( + + + + ); +}; + +export const DailyProductionIcon = () => { + return ( + + + + + + ); +}; + +export const MonthlyROI = () => { + return ( + + + + ); +}; + +export const ExpandIcon = ({ isActive }: { isActive: boolean }) => { + return isActive ? ( + + + + + + ) : ( + + + + + + ); +}; + +export const ExpandIcon2 = () => { + return ( + + + + + ); +}; + +export const StartIcon = () => { + return ( + + + + + + + ); +}; + +export const EndIcon = () => { + return ( + + + + + + + ); +}; + +export const SpeedIcon = () => { + return ( + + + + ); +}; + +export const LogListIcon = () => { + return ( + + + + ); +}; + +export const LogTickIcon = () => { + return ( + + + + + + + + + + + ); +}; + +export const LogInfoIcon = () => { + return ( + + + + + + + ); +}; + +export const WarningIcon = () => { + return ( + + + + + + + ); +}; + +export const ErrorIcon = () => { + return ( + + + + ); +}; + +export const LocationIcon = () => { + return ( + + + + ); +}; +export const SaveDiskIcon = () => { + return ( + + + + + + ); +}; +export const WalkIcon = () => { + return ( + + + + + + + ); +}; +export const EyeCloseIcon = () => { + return ( + + + + + ); +}; + +export const SaveIcon = () => { + return ( + + + + ); +}; + +export const FolderIcon = ({ isOpen }: { isOpen: boolean }) => ( + + {isOpen ? ( + + ) : ( + + )} + +); + +export const CubeIcon = () => ( + + + +); + +export const ChevronIcon = ({ isOpen }: { isOpen: boolean }) => { + return isOpen ? ( + + + + ) : ( + + + + ); +}; + +export const CollapseAllIcon = () => { + return ( + + + + ); +}; + +export const SaveVersionIcon = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const RenameVersionIcon = () => { + return ( + + + + ); +}; + +export const FinishEditIcon = () => { + return ( + + + + + + + + + ); +}; + +export const PerformanceIcon = () => { + return ( + + + + + + + + + + ); +}; + +export const GreenTickIcon = () => { + return ( + + + + + ); +}; + +export const SuccessIcon = () => { + return ( + + + + + ); +}; + +export const AlertIcon = () => { + return ( + + + + + ); +}; +export const NavigationIcon = () => { + return ( + + + + + + ); +}; +export const HangTagIcon = () => { + return ( + + + + + + + + + + + ); +}; +export const DecalInfoIcon = () => { + return ( + + + + + + ); +}; + +export const LayeringBottomIcon = () => { + return ( + + + + + + ); +}; + +export const LayeringTopIcon = () => { + return ( + + + + + + ); +}; + +export const ValueUpdateIcon = () => { + return ( + + + + + ); +}; + +export const ListTaskIcon = () => { + return ( + + + + ); +}; + +export const LocationPinIcon = () => { + return ( + + + + + ); +}; + +export const ClockThreeIcon = () => { + return ( + + + + ); +}; + +export const SlectedTickIcon = () => { + return ( + + + + + ); +}; + +export const HourGlassIcon = () => { + return ( + + + + ); +}; + +export const TargetIcon = () => { + return ( + + + + ); +}; + +export const RightHalfFillCircleIcon = () => { + return ( + + + + ); +}; + +export const PerformanceStatsIcon = () => { + return ( + + + + ); +}; diff --git a/src/pages/SceneView.tsx b/src/pages/SceneView.tsx new file mode 100644 index 0000000..b701242 --- /dev/null +++ b/src/pages/SceneView.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import OutlinePanel from "../components/outlinePanel/OutlinePanel"; + +const SceneView = () => { + return ( + + ); +}; + +export default SceneView; diff --git a/src/styles/abstracts/_functions.scss b/src/styles/abstracts/_functions.scss new file mode 100644 index 0000000..6f0745d --- /dev/null +++ b/src/styles/abstracts/_functions.scss @@ -0,0 +1,5 @@ +// get rem from pixels + +@function get_rem($px) { + @return calc($px / 16 * 1rem); +} diff --git a/src/styles/abstracts/_mixins.scss b/src/styles/abstracts/_mixins.scss new file mode 100644 index 0000000..4133dc1 --- /dev/null +++ b/src/styles/abstracts/_mixins.scss @@ -0,0 +1,33 @@ +@mixin flex-center { + display: flex; + justify-content: center; + align-items: center; +} + +@mixin flex-space-between { + display: flex; + justify-content: space-between; + align-items: center; +} + +// Array of base colors +$colors: ( + #f5550b, + #1bac1b, + #0099ff, + #d4c927, + #8400ff, + #13e9b3, + #df1dcf +); + +@mixin gradient-by-child($index) { + // Get the color based on the index passed + $base-color: nth($colors, $index); + // Apply gradient using the same color with different alpha values + background: linear-gradient( + 144.19deg, + rgba($base-color, 0.2) 16.62%, // 20% opacity + rgba($base-color, 0.08) 85.81% // 80% opacity + ); +} diff --git a/src/styles/abstracts/_variables.scss b/src/styles/abstracts/_variables.scss new file mode 100644 index 0000000..2ddbfc9 --- /dev/null +++ b/src/styles/abstracts/_variables.scss @@ -0,0 +1,171 @@ +@use "functions"; + +@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Josefin+Sans:ital,wght@0,100..700;1,100..700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"); + +// new variables + +// text colors +// ---------- light mode ---------- +$text-color: #2b3344; +$text-disabled: #b7b7c6; +$input-text-color: #595965; +$highlight-text-color: #6f42c1; +$text-button-color: #f3f3fd; + +// ---------- dark mode ---------- +$text-color-dark: #f3f3fd; +$text-disabled-dark: #6f6f7a; +$input-text-color-dark: #b5b5c8; +$highlight-text-color-dark: #d2baff; +$text-button-color-dark: #f3f3fd; + +// background colors +// ---------- light mode ---------- +$background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%); +$background-color-solid-gradient: linear-gradient(-45deg, #fcfdfd 0%, #fcfdfd 100%); +$background-color-solid: #fcfdfd; +$background-color-secondary: #fcfdfd4d; +$background-color-accent: #6f42c1; +$background-color-button: #6f42c1; +$background-color-drop-down: #6f42c14d; +$background-color-input: #ffffff4d; +$background-color-input-focus: #f2f2f7; +$background-color-drop-down-gradient: linear-gradient(-45deg, #75649366 0%, #40257266 100%); +$background-color-selected: #e0dfff; +$background-radial-gray-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%); +$background-model: #ffffff80; + +// ---------- dark mode ---------- +$background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%); +$background-color-solid-gradient-dark: linear-gradient(-45deg, #333333 0%, #2d2437 100%); +$background-color-solid-dark: #19191d; +$background-color-secondary-dark: #19191d99; +$background-color-accent-dark: #6f42c1; +$background-color-button-dark: #6f42c1; +$background-color-drop-down-dark: #50505080; +$background-color-input-dark: #ffffff33; +$background-color-input-focus-dark: #333333; +$background-color-drop-down-gradient-dark: linear-gradient(-45deg, #8973b166 0%, #53427366 100%); +$background-color-selected-dark: #403e66; +$background-radial-gray-gradient-dark: radial-gradient( + circle, + #31373b 0%, + #48494b 46%, + #52415c 100% +); +$background-model-dark: #00000080; + +// border colors +// ---------- light mode ---------- +$border-color: #e0dfff; +$input-border-color: #d5dddd80; +$border-color-accent: #6f42c1; + +// ---------- dark mode ---------- +$border-color-dark: #564b69; +$input-border-color-dark: #d5dddd80; +$border-color-accent-dark: #6f42c1; + +// highlight colors +// ---------- light mode ---------- +$highlight-accent-color: #e0dfff; +$highlight-secondary-color: #6f42c1; + +// ---------- dark mode ---------- +$highlight-accent-color-dark: #403e6a; +$highlight-secondary-color-dark: #c4abf1; + +// icon colors +// ---------- light mode ---------- +$icon-default-color: #6f42c1; +$icon-default-color-hover: #7f4ddb; +$icon-default-color-active: #f2f2f7; + +// ---------- dark mode ---------- +$icon-default-color-dark: #6f42c1; +$icon-default-color-hover-dark: #7f4ddb; +$icon-default-color-active-dark: #f2f2f7; + +// colors +$color1: #a392cd; +$color2: #7b4cd3; +$color3: #b186ff; +$color4: #8752e8; +$color5: #c7a8ff; + +// log indication colors +// ------------ text ------------- +$log-default-text-color: #6f42c1; +$log-info-text-color: #1773fd; +$log-warn-text-color: #f3a50c; +$log-error-text-color: #fc230f; +$log-success-text-color: #23a84f; +// ----------- dark --------------- +$log-default-text-color-dark: #b18ef1; +$log-info-text-color-dark: #7eb0fa; +$log-warn-text-color-dark: #ffaa00; +$log-error-text-color-dark: #ff887d; +$log-success-text-color-dark: #43ff81; + +// ------------ background ------------- +$log-default-backgroung-color: #6e42c133; +$log-info-background-color: #1773fd5d; +$log-warn-background-color: #f3a50c33; +$log-error-background-color: #fc230f33; +$log-success-background-color: #0ef75b33; + +// old variables +$accent-color: #6f42c1; +$accent-color-dark: #c4abf1; + +$background-color-gray: #f3f3f3; +$background-color-gray-dark: #232323; + +$shadow-color: #3c3c431a; +$shadow-color-dark: #8f8f8f1a; + +$acent-gradient-dark: linear-gradient(90deg, #b392f0 0%, #a676ff 100%); +$acent-gradient: linear-gradient(90deg, #6f42c1 0%, #925df3 100%); + +$faint-gradient: radial-gradient(circle, #bfe0f8 0%, #e9ebff 46%, #e2acff 100%); +$faint-gradient-dark: radial-gradient(circle, #31373b 0%, #48494b 46%, #52415c 100%); + +$font-inter: "Inter", sans-serif; +$font-josefin-sans: "Josefin Sans", sans-serif; +$font-poppins: "Poppins", sans-serif; +$font-roboto: "Roboto", sans-serif; + +$tiny: 0.625rem; +$small: 0.75rem; +$regular: 0.8rem; +$large: 1rem; +$xlarge: 1.125rem; +$xxlarge: 1.5rem; +$xxxlarge: 2rem; + +$thin-weight: 300; +$regular-weight: 400; +$medium-weight: 500; +$bold-weight: 600; + +$z-index-drei-html: 1; +$z-index-default: 1; +$z-index-marketplace: 2; +$z-index-tools: 3; +$z-index-negative: -1; +$z-index-ui-base: 10; +$z-index-ui-overlay: 20; +$z-index-ui-popup: 30; +$z-index-ui-highest: 50; + +$box-shadow-light: 0px 2px 4px $shadow-color; +$box-shadow-medium: 0px 4px 8px $shadow-color; +$box-shadow-heavy: 0px 8px 16px $shadow-color; + +$border-radius-small: 4px; +$border-radius-medium: 6px; +$border-radius-large: 12px; +$border-radius-circle: 50%; +$border-radius-xlarge: 16px; +$border-radius-extra-large: 20px; +$border-radius-xxx: 30px; diff --git a/src/styles/components/outlinePanel.scss b/src/styles/components/outlinePanel.scss new file mode 100644 index 0000000..f0704fc --- /dev/null +++ b/src/styles/components/outlinePanel.scss @@ -0,0 +1,580 @@ +// .outline-overlay { +// padding: 0 4px; +// font-family: "Segoe UI", sans-serif; +// display: flex; +// position: absolute; +// top: 0; +// bottom: 0; +// width: 100vw; +// pointer-events: none; +// .outline-card { +// pointer-events: all; +// } +// } + +// .outline-header { +// display: flex; +// align-items: center; +// justify-content: space-between; +// padding: 10px 16px; +// border-bottom: 1px solid #2e2e3e; + +// .header-title { +// p { +// margin: 0; +// font-weight: 600; +// font-size: 14px; +// color: #ffffff; +// } +// } + +// .outline-toolbar { +// display: flex; +// gap: 8px; + +// .toolbar-button { +// background: none; +// border: none; +// color: #aaa; +// cursor: pointer; +// width: 24px; +// height: 24px; +// border-radius: 4px; +// transition: background 0.3s; + +// &:hover { +// background-color: rgba(255, 255, 255, 0.1); +// color: #a855f7; +// } + +// svg { +// width: 100%; +// height: 100%; +// } +// } + +// .close-button { +// background: none; +// border: none; +// color: #aaa; +// cursor: pointer; +// width: 24px; +// height: 24px; +// border-radius: 4px; +// transition: transform 0.3s ease; + +// &:hover { +// background-color: rgba(255, 255, 255, 0.1); +// transform: rotate(-90deg); +// color: #a855f7; +// } + +// svg { +// width: 100%; +// height: 100%; +// } +// } +// } +// } + +// .outline-content { +// max-height: 52vh; +// overflow-y: auto; +// padding: 8px 0; + +// &::-webkit-scrollbar { +// width: 6px; +// } + +// &::-webkit-scrollbar-thumb { +// background: #a855f7; +// border-radius: 10px; +// } +// } +// .tree-node { +// display: flex; +// flex-direction: column; +// width: 100%; +// outline: 2px solid transparent; +// transition: all 0.3s ease; + +// .tree-node-content { +// display: flex; +// align-items: center; +// width: 100%; +// padding: 6px 10px; +// border-radius: 8px; +// background: transparent; +// transition: background 0.2s ease, outline 0.2s ease; +// box-sizing: border-box; +// position: relative; + +// &:hover { +// background: rgba(255, 255, 255, 0.05); +// } + +// &.selected { +// background: var(--background-color-accent, #6f42c1); +// } + +// &.multi-selected { +// background: rgba(167, 139, 250, 0.2); +// border-left: 3px solid var(--highlight-secondary-color, #a855f7); +// } + +// .expand-button { +// background: none; +// border: none; +// color: #aaa; +// cursor: pointer; +// width: 18px; +// height: 18px; +// flex-shrink: 0; +// display: flex; +// align-items: center; +// justify-content: center; + +// &:hover { +// color: #a855f7; +// } +// } + +// .node-wrapper { +// display: flex; +// align-items: center; +// flex: 1; +// min-width: 0; +// gap: 8px; +// } + +// .node-icon { +// color: #a855f7; +// flex-shrink: 0; +// width: 16px; +// height: 16px; +// display: flex; +// align-items: center; +// justify-content: center; +// } + +// .rename-input { +// flex: 1; +// min-width: 0; +// overflow: hidden; +// white-space: nowrap; +// text-overflow: ellipsis; +// font-size: 14px; +// color: #fff; + +// input.renaming { +// width: 100%; +// border: none; +// outline: none; +// border-radius: 6px; +// background: rgba(255, 255, 255, 0.1); +// color: #fff; +// padding: 3px 6px; +// font-size: 14px; +// box-sizing: border-box; +// } +// } + +// .node-controls { +// display: flex; +// align-items: center; +// gap: 4px; +// flex-shrink: 0; + +// .control-button { +// background: none; +// border: none; +// color: #aaa; +// cursor: pointer; +// border-radius: 4px; +// padding: 4px; +// display: flex; +// align-items: center; +// justify-content: center; + +// &:hover { +// background: rgba(255, 255, 255, 0.1); +// color: #a855f7; +// } + +// svg { +// width: 14px; +// height: 14px; +// } +// } +// } +// } + +// .tree-children { +// padding-left: 16px; +// border-left: 1px dashed rgba(255, 255, 255, 0.1); +// margin-left: 6px; +// } +// } + +// // .tree-node { +// // display: flex; +// // flex-direction: column; +// // outline: 2px solid transparent; +// // transition: background 0.3s; + +// // .tree-node-content { +// // display: flex; +// // align-items: center; +// // padding: 8px 9px; +// // gap: 6px; +// // transition: background 0.2s ease; +// // position: relative; +// // &.selected { +// // background: #6f42c1; +// // border-radius: 45px; +// // } + +// // &:hover { +// // outline: 1px solid pink; +// // border-radius: 80px; +// // } +// // .node-wrapper { +// // display: flex; +// // gap: 40px; +// // } + +// // .expand-button { +// // background: none; +// // border: none; +// // cursor: pointer; +// // width: 20px; +// // height: 20px; +// // padding: 0; +// // color: #aaa; + +// // &:hover { +// // color: #a855f7; +// // } +// // } + +// // .node-icon { +// // color: white; +// // width: 18px; +// // flex-shrink: 0; +// // } + +// // .rename-input { +// // background: transparent; +// // border: none; +// // color: #fff; +// // font-size: 14px; +// // flex: 1; +// // outline: none; +// // padding: 2px 4px; +// // &:focus { +// // border-bottom: 1px solid #a855f7; +// // } +// // .renaming { +// // outline: none; +// // border: none; +// // border-radius: 15px; +// // height: 20px; +// // } +// // } + +// // .node-controls { +// // display: flex; +// // gap: 6px; + +// // .control-button { +// // background: none; +// // border: none; +// // color: #888; +// // cursor: pointer; +// // // padding: 4px; +// // border-radius: 4px; + +// // &:hover { +// // background-color: rgba(255, 255, 255, 0.1); +// // color: #a855f7; +// // } + +// // svg { +// // width: 16px; +// // height: 16px; +// // } +// // } +// // } +// // } +// // } + +// // .tree-children { +// // padding-left: 16px; +// // border-left: 1px dashed rgba(255, 255, 255, 0.05); +// // } + +// Outline Component Styles +// Professional tree/hierarchy view with drag-and-drop support + +.outline-overlay { + padding: 0 4px; + font-family: "Segoe UI", sans-serif; + display: flex; + position: absolute; + top: 0; + bottom: 0; + width: 100%; + pointer-events: none; + z-index: 1000; +} + +.outline-card { + pointer-events: all; + width: 280px; + max-width: 100%; + border-radius: 10px; + border: 1px solid #2e2e3e; + background: linear-gradient(to bottom, #1e1e2f, #12121a); + box-shadow: 0 0 8px rgba(168, 85, 247, 0.2); + display: flex; + flex-direction: column; + overflow: hidden; +} + +.outline-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 16px; + border-bottom: 1px solid #2e2e3e; +} + +.header-title { + p { + margin: 0; + font-weight: 600; + font-size: 14px; + color: #ffffff; + } +} + +.outline-toolbar { + display: flex; + gap: 8px; + + .toolbar-button { + background: none; + border: none; + color: #aaa; + cursor: pointer; + width: 24px; + height: 24px; + border-radius: 4px; + transition: background 0.3s; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + color: #a855f7; + } + + svg { + width: 100%; + height: 100%; + } + } + + .close-button { + background: none; + border: none; + color: #aaa; + cursor: pointer; + width: 24px; + height: 24px; + border-radius: 4px; + transition: transform 0.3s ease; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + transform: rotate(-90deg); + color: #a855f7; + } + + svg { + width: 100%; + height: 100%; + } + } +} + +.outline-content { + max-height: 52vh; + overflow-y: auto; + padding: 8px 0; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-thumb { + background: #a855f7; + border-radius: 10px; + } +} + +.tree-node { + display: flex; + flex-direction: column; + transition: background 0.3s; + + &.dragging { + opacity: 0.5; + } +} + +.tree-node-content { + display: flex; + align-items: center; + padding: 8px 12px; + gap: 8px; + transition: background 0.2s ease; + position: relative; + cursor: pointer; + border-radius: 6px; + margin: 2px 8px; + + &.selected { + background: #6f42c1; + border-radius: 50px; + } + + &:hover { + outline: 2px solid #b188ff; + border-radius: 50px; + } + > div { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + } + + &.locked { + opacity: 0.6; + cursor: not-allowed; + transform: scale(0.98); + background-color: transparent; + outline: none; + border: none; + } + + &.hidden { + opacity: 0.4; + } +} + +.expand-button { + background: none; + border: none; + cursor: pointer; + width: 20px; + height: 20px; + padding: 0; + color: #aaa; + + &:hover { + color: #a855f7; + } +} + +.node-icon { + color: #fff; + width: 18px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 18px; + height: 18px; + } +} + +.rename-input { + background: transparent; + border: none; + color: #fff; + font-size: 14px; + flex: 1; + outline: none; + padding: 2px 4px; + + &:focus { + border-bottom: 1px solid #a855f7; + } + + input { + background: white; + border: none; + color: black; + font-size: 14px; + width: 100%; + outline: none; + + &:focus { + border-bottom: 1px solid white; + border-radius: 30px; + height: 20px; + } + } + + span { + display: block; + } +} + +.node-controls { + display: flex; + gap: 6px; + margin-left: auto; + + .control-button { + background: none; + border: none; + color: #888; + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: all 0.2s; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + color: #a855f7; + } + + svg { + width: 16px; + height: 16px; + } + } +} + +.tree-children { + padding-left: 16px; + border-left: 1px dashed rgba(255, 255, 255, 0.05); +} + +// Responsive adjustments +@media (max-width: 768px) { + .outline-card { + width: 240px; + } + + .outline-overlay { + padding: 0 2px; + } + + .rename-input { + font-size: 13px; + } +} diff --git a/src/styles/main.scss b/src/styles/main.scss new file mode 100644 index 0000000..7ddf49e --- /dev/null +++ b/src/styles/main.scss @@ -0,0 +1 @@ +@use "components/outlinePanel.scss";