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 (