diff --git a/frontend/editor3d/app/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx b/frontend/editor3d/app/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx index 80f7d69283..6b04a87a31 100644 --- a/frontend/editor3d/app/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx +++ b/frontend/editor3d/app/pages/PageEnigma/comps/AssetMenu/AssetModal.tsx @@ -1,9 +1,18 @@ import { TransitionDialogue } from "~/components/reusable/TransitionDialogue"; -import { faPlus, faSearch, faXmark } from "@fortawesome/pro-solid-svg-icons"; +import { + faPlus, + faSearch, + faChevronRight, +} from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, CloseButton, Input, Tooltip } from "~/components"; import { TabSelector } from "~/components/reusable/TabSelector"; -import { useState } from "react"; +import { useState, useEffect, useMemo } from "react"; +import { ItemElements } from "../SidePanelTabs/sharedComps/ItemElements"; +import { demoSkyboxItems, demoShapeItems } from "../../signals"; +import { MediaItem } from "../../models"; +import { useUserObjects, useFeaturedObjects } from "../SidePanelTabs/hooks"; +import { FilterEngineCategories } from "~/enums"; interface AssetModalProps { isOpen: boolean; @@ -11,23 +20,189 @@ interface AssetModalProps { onAddAsset: () => void; } +type AssetTab = { + id: string; + label: string; + engineCategory?: FilterEngineCategories; + items: MediaItem[]; +}; + +const AllTabSection = ({ + label, + items, + onViewAll, +}: { + label: string; + items: MediaItem[]; + onViewAll: () => void; +}) => ( +
+
+

{label}

+ +
+
+ +
+
+); + export const AssetModal = ({ isOpen, onClose, onAddAsset, }: AssetModalProps) => { - const [activeTab, setActiveTab] = useState("details"); + const [activeLibraryTab, setActiveLibraryTab] = useState("library"); + const [activeAssetTab, setActiveAssetTab] = useState("all"); + const [searchTerm, setSearchTerm] = useState(""); - const tabs = [ + // Fetch objects for different categories + const { userObjects: userCharacters } = useUserObjects({ + filterEngineCategories: [FilterEngineCategories.CHARACTER], + defaultErrorMessage: "Error fetching user characters", + }); + + const { userObjects: userObjects } = useUserObjects({ + filterEngineCategories: [FilterEngineCategories.OBJECT], + defaultErrorMessage: "Error fetching user objects", + }); + + const { userObjects: userNature } = useUserObjects({ + filterEngineCategories: [FilterEngineCategories.SET_DRESSING], + defaultErrorMessage: "Error fetching user nature assets", + }); + + const { featuredObjects: featuredCharacters } = useFeaturedObjects({ + filterEngineCategories: [FilterEngineCategories.CHARACTER], + defaultErrorMessage: "Error fetching featured characters", + }); + + const { featuredObjects: featuredObjects } = useFeaturedObjects({ + filterEngineCategories: [FilterEngineCategories.OBJECT], + defaultErrorMessage: "Error fetching featured objects", + }); + + const { featuredObjects: featuredNature } = useFeaturedObjects({ + filterEngineCategories: [FilterEngineCategories.SET_DRESSING], + defaultErrorMessage: "Error fetching featured nature assets", + }); + + const libraryTabs = [ { id: "library", label: "Library" }, { id: "mine", label: "Mine" }, ]; + const assetTabs: AssetTab[] = [ + { id: "all", label: "All", items: [] }, + { + id: "character", + label: "Character", + engineCategory: FilterEngineCategories.CHARACTER, + items: + activeLibraryTab === "library" + ? (featuredCharacters ?? []) + : (userCharacters ?? []), + }, + { + id: "skybox", + label: "Skybox", + items: demoSkyboxItems.value, + }, + { + id: "nature", + label: "Nature", + engineCategory: FilterEngineCategories.SET_DRESSING, + items: + activeLibraryTab === "library" + ? (featuredNature ?? []) + : (userNature ?? []), + }, + { + id: "objects", + label: "Objects", + engineCategory: FilterEngineCategories.OBJECT, + items: + activeLibraryTab === "library" + ? [...demoShapeItems.value, ...(featuredObjects ?? [])] + : (userObjects ?? []), + }, + ]; + + // Combine all items for the "All" tab + const allItems = useMemo(() => { + return assetTabs + .filter((tab) => tab.id !== "all") + .flatMap((tab) => tab.items); + }, [assetTabs]); + + // Update the items array for the "All" tab + assetTabs[0].items = allItems; + + const currentTab = + assetTabs.find((tab) => tab.id === activeAssetTab) || assetTabs[0]; + + // Filter items based on search term + const displayedItems = useMemo(() => { + if (!searchTerm) { + return currentTab.items; + } + + const searchLower = searchTerm.toLowerCase(); + return currentTab.items.filter( + (item) => + item.name?.toLowerCase().includes(searchLower) || + item.description?.toLowerCase().includes(searchLower), + ); + }, [currentTab.items, searchTerm]); + + // Switch to "All" tab when searching + useEffect(() => { + if (searchTerm && activeAssetTab !== "all") { + setActiveAssetTab("all"); + } + }, [searchTerm]); + + const renderContent = () => { + if (activeAssetTab === "all" && !searchTerm) { + return ( +
+ {assetTabs.slice(1).map((tab) => ( + setActiveAssetTab(tab.id)} + /> + ))} +
+ ); + } + + return ( + + ); + }; + return (
@@ -44,23 +219,40 @@ export const AssetModal = ({

-
{/* Asset list will go here */}
+
+ {assetTabs.map((tab) => ( + + ))} +
- + setSearchTerm(e.target.value)} + />
-
- {/* Asset details will go here */} +
+ {renderContent()}
diff --git a/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElement.tsx b/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElement.tsx index e221099886..1a2eec9581 100644 --- a/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElement.tsx +++ b/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElement.tsx @@ -39,7 +39,7 @@ export const ItemElement = ({ item }: Props) => { return (
DndAsset.onPointerDown(event, item)} > {item.media_type && ( @@ -55,7 +55,7 @@ export const ItemElement = ({ item }: Props) => { /> )} -
+
{item.thumbnail && !imageError && ( { /> )}
-
+
{item.name || item.media_id}
diff --git a/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElements.tsx b/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElements.tsx index 934b6f32c6..4658d9093c 100644 --- a/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElements.tsx +++ b/frontend/editor3d/app/pages/PageEnigma/comps/SidePanelTabs/sharedComps/ItemElements.tsx @@ -1,6 +1,5 @@ import { ItemElement } from "./ItemElement"; import { MediaItem } from "~/pages/PageEnigma/models"; -import { dndSidePanelWidth, sidePanelWidth } from "~/pages/PageEnigma/signals"; import { H4, P, LoadingDots } from "~/components"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faEmptySet } from "@fortawesome/pro-solid-svg-icons"; @@ -24,29 +23,12 @@ export const ItemElements = ({ onLoadMore, hasMore, }: Props) => { - const displayWidth = - dndSidePanelWidth.value > -1 - ? dndSidePanelWidth.value - : sidePanelWidth.value; + const GRID_GAP = 12; + const ASPECT_RATIO = 16 / 12; + const TEXT_HEIGHT = 32; + const COLUMN_COUNT = 4; - const GRID_GAP = 8; - const ASPECT_RATIO = 4.5 / 5; - const TEXT_HEIGHT = 24; - - const columnCount = getGridColumns(displayWidth); - const rowCount = Math.ceil(items.length / columnCount); - - function getGridColumns(displayWidth: number): number { - if (displayWidth <= 280) { - return 2; - } else if (displayWidth <= 360) { - return 3; - } else if (displayWidth <= 440) { - return 4; - } else { - return 4; - } - } + const rowCount = Math.ceil(items.length / COLUMN_COUNT); const cellRenderer = ({ columnIndex, @@ -59,14 +41,20 @@ export const ItemElements = ({ key: string; style: React.CSSProperties; }) => { - const index = rowIndex * columnCount + columnIndex; + const index = rowIndex * COLUMN_COUNT + columnIndex; if (index >= items.length) return null; + // Calculate dimensions for the container + const containerWidth = (style.width as number) - GRID_GAP; + const containerHeight = (style.height as number) - GRID_GAP; + const adjustedStyle = { ...style, - padding: GRID_GAP / 2, - height: style.height as number, - boxSizing: "border-box" as const, + left: `${(style.left as number) + GRID_GAP / 2}px`, + top: `${(style.top as number) + GRID_GAP / 2}px`, + width: `${containerWidth}px`, + height: `${containerHeight}px`, + padding: 0, }; return ( @@ -119,23 +107,23 @@ export const ItemElements = ({
{({ height, width }) => { - const availableWidth = width - GRID_GAP * (columnCount - 1); - const cellWidth = Math.floor(availableWidth / columnCount); + // Calculate cell width accounting for gaps + const cellWidth = Math.floor( + (width - GRID_GAP * (COLUMN_COUNT + 1)) / COLUMN_COUNT, + ); const imageHeight = Math.floor(cellWidth / ASPECT_RATIO); - const cellHeight = imageHeight + TEXT_HEIGHT; + const cellHeight = imageHeight + TEXT_HEIGHT + GRID_GAP; return (