refactor: reorganize asset management components and enhance search functionality
This commit is contained in:
@@ -1,474 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Search from "../../ui/inputs/Search";
|
||||
import { getCategoryAsset } from "../../../services/factoryBuilder/asset/assets/getCategoryAsset";
|
||||
import { fetchAssets } from "../../../services/marketplace/fetchAssets";
|
||||
import {
|
||||
useDecalStore,
|
||||
useDroppedDecal,
|
||||
useSelectedItem,
|
||||
} from "../../../store/builder/store";
|
||||
|
||||
// images -------------------
|
||||
import vehicle from "../../../assets/image/categories/vehicles.png";
|
||||
import workStation from "../../../assets/image/categories/workStation.png";
|
||||
import machines from "../../../assets/image/categories/machines.png";
|
||||
import worker from "../../../assets/image/categories/worker.png";
|
||||
import storage from "../../../assets/image/categories/storage.png";
|
||||
import office from "../../../assets/image/categories/office.png";
|
||||
import safety from "../../../assets/image/categories/safety.png";
|
||||
import feneration from "../../../assets/image/categories/feneration.png";
|
||||
import decal from "../../../assets/image/categories/decal.png";
|
||||
import SkeletonUI from "../../templates/SkeletonUI";
|
||||
import {
|
||||
AlertIcon,
|
||||
ArrowIcon,
|
||||
DecalInfoIcon,
|
||||
HangTagIcon,
|
||||
NavigationIcon,
|
||||
} from "../../icons/ExportCommonIcons";
|
||||
import { getCategoryDecals } from "../../../services/factoryBuilder/asset/decals/getCategoryDecals";
|
||||
// -------------------------------------
|
||||
|
||||
interface AssetProp {
|
||||
filename: string;
|
||||
thumbnail?: string;
|
||||
category: string;
|
||||
description?: string;
|
||||
tags: string;
|
||||
url?: string;
|
||||
uploadDate?: number;
|
||||
isArchieve?: boolean;
|
||||
animated?: boolean;
|
||||
price?: number;
|
||||
CreatedBy?: string;
|
||||
}
|
||||
interface CategoryListProp {
|
||||
assetImage?: string;
|
||||
assetName?: string;
|
||||
categoryImage: string;
|
||||
category: string;
|
||||
}
|
||||
const Assets: React.FC = () => {
|
||||
const { setSelectedItem } = useSelectedItem();
|
||||
const { setDroppedDecal } = useDroppedDecal();
|
||||
const [searchValue, setSearchValue] = useState<string>("");
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [categoryAssets, setCategoryAssets] = useState<AssetProp[]>([]);
|
||||
const [decalAsset, setDecalAsset] = useState<any>();
|
||||
const [filtereredAssets, setFiltereredAssets] = useState<AssetProp[] | []>(
|
||||
[]
|
||||
);
|
||||
const [categoryList, setCategoryList] = useState<CategoryListProp[]>([]);
|
||||
const [isLoading, setisLoading] = useState<boolean>(false); // Loading state for assets
|
||||
const { selectedSubCategory, setSelectedSubCategory } = useDecalStore();
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
const searchTerm = searchValue
|
||||
? searchValue.toLowerCase()
|
||||
: value.toLowerCase();
|
||||
setSearchValue(value);
|
||||
|
||||
if (searchTerm.trim() === "" && !selectedCategory) {
|
||||
setCategoryAssets([]);
|
||||
|
||||
return;
|
||||
}
|
||||
if (selectedCategory === "Decals" || selectedSubCategory) {
|
||||
const filteredModels = decalAsset?.filter((model: any) =>
|
||||
model.decalName?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
setCategoryAssets(filteredModels);
|
||||
} else {
|
||||
const filteredModels = filtereredAssets?.filter((model) => {
|
||||
if (!model?.tags || !model?.filename || !model?.category) return false;
|
||||
if (searchTerm.startsWith(":") && searchTerm.length > 1) {
|
||||
const tagSearchTerm = searchTerm.slice(1);
|
||||
return model.tags.toLowerCase().includes(tagSearchTerm);
|
||||
} else if (selectedCategory) {
|
||||
return (
|
||||
model.category
|
||||
.toLowerCase()
|
||||
.includes(selectedCategory.toLowerCase()) &&
|
||||
model.filename.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
} else {
|
||||
return model.filename.toLowerCase().includes(searchTerm);
|
||||
}
|
||||
});
|
||||
|
||||
setCategoryAssets(filteredModels);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCategory === "Decals") return;
|
||||
const filteredAssets = async () => {
|
||||
try {
|
||||
const filt = await fetchAssets();
|
||||
setFiltereredAssets(filt);
|
||||
} catch {
|
||||
echo.error("Filter asset not found");
|
||||
}
|
||||
};
|
||||
filteredAssets();
|
||||
}, [categoryAssets, selectedCategory]);
|
||||
useEffect(() => {
|
||||
if (
|
||||
(searchValue.trim() === "" && selectedCategory === "Decals") ||
|
||||
selectedSubCategory
|
||||
) {
|
||||
const filteredModels = decalAsset?.filter((model: any) =>
|
||||
model.decalName?.toLowerCase().includes(searchValue.toLowerCase())
|
||||
);
|
||||
setCategoryAssets(filteredModels);
|
||||
}
|
||||
}, [selectedSubCategory, decalAsset, searchValue]);
|
||||
|
||||
useEffect(() => {
|
||||
setCategoryList([
|
||||
{ category: "Fenestration", categoryImage: feneration },
|
||||
{ category: "Decals", categoryImage: decal },
|
||||
{ category: "Vehicles", categoryImage: vehicle },
|
||||
{ category: "Workstation", categoryImage: workStation },
|
||||
{ category: "Machines", categoryImage: machines },
|
||||
{ category: "Workers", categoryImage: worker },
|
||||
{ category: "Storage", categoryImage: storage },
|
||||
{ category: "Safety", categoryImage: safety },
|
||||
{ category: "Office", categoryImage: office },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const fetchCategoryAssets = async (asset: any) => {
|
||||
setisLoading(true);
|
||||
setSelectedCategory(asset);
|
||||
try {
|
||||
const res = await getCategoryAsset(asset);
|
||||
setCategoryAssets(res);
|
||||
setFiltereredAssets(res);
|
||||
setisLoading(false); // End loading
|
||||
// eslint-disable-next-line
|
||||
} catch (error) {
|
||||
echo.error("failed to fetch assets");
|
||||
setisLoading(false);
|
||||
}
|
||||
|
||||
if (asset === "Decals") {
|
||||
fetchCategoryDecals("Safety");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCategoryDecals = async (asset: any) => {
|
||||
setisLoading(true);
|
||||
// setSelectedCategory(asset);
|
||||
try {
|
||||
const res = await getCategoryDecals(asset);
|
||||
|
||||
setCategoryAssets(res);
|
||||
setFiltereredAssets(res);
|
||||
setDecalAsset(res);
|
||||
setisLoading(false); // End loading
|
||||
// eslint-disable-next-line
|
||||
} catch (error) {
|
||||
echo.error("failed to fetch assets");
|
||||
setisLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const activeSubcategories = [
|
||||
{ name: "Safety", icon: <AlertIcon /> },
|
||||
{ name: "Navigation", icon: <NavigationIcon /> },
|
||||
{ name: "Branding", icon: <HangTagIcon /> },
|
||||
{ name: "Informational", icon: <DecalInfoIcon /> },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="assets-container-main">
|
||||
<Search onChange={handleSearchChange} value={searchValue} />
|
||||
<div className="assets-list-section">
|
||||
<section>
|
||||
{(() => {
|
||||
if (isLoading) {
|
||||
return <SkeletonUI type="asset" />; // Show skeleton when loading
|
||||
}
|
||||
if (searchValue) {
|
||||
return (
|
||||
<div className="assets-result">
|
||||
<div className="assets-wrapper">
|
||||
<div className="searched-content">
|
||||
<p>
|
||||
Results for{" "}
|
||||
<span className="search-for">'{searchValue}'</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="assets-container">
|
||||
{selectedCategory == "Decals" ? (
|
||||
<>
|
||||
<div className="catogory-asset-filter">
|
||||
{activeSubcategories.map((cat, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`catogory-asset-filter-wrapper ${
|
||||
selectedSubCategory === cat.name
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
fetchCategoryDecals(cat.name);
|
||||
setSelectedSubCategory(cat.name);
|
||||
}}
|
||||
>
|
||||
<div className="sub-catagory">{cat.icon}</div>
|
||||
<div className="sub-catagory">{cat.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset}`}
|
||||
className="assets"
|
||||
id={asset.decalName}
|
||||
title={asset.decalName}
|
||||
>
|
||||
<img
|
||||
src={asset?.decalImage}
|
||||
alt={asset.decalName}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.decalName,
|
||||
id: asset.id,
|
||||
type:
|
||||
asset.type === "undefined"
|
||||
? undefined
|
||||
: asset.type,
|
||||
category: asset.category,
|
||||
// subType: asset.subType,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="asset-name">
|
||||
{asset.decalName
|
||||
.split("_")
|
||||
.map(
|
||||
(word: any) =>
|
||||
word.charAt(0).toUpperCase() +
|
||||
word.slice(1)
|
||||
)
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset.filename}`}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
type:
|
||||
asset.type === "undefined"
|
||||
? undefined
|
||||
: asset.type,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="asset-name">
|
||||
{asset.filename
|
||||
.split("_")
|
||||
.map(
|
||||
(word: any) =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
)
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedCategory) {
|
||||
return (
|
||||
<div className="assets-wrapper">
|
||||
<h2 className="header">
|
||||
{selectedCategory}
|
||||
<button
|
||||
className="back-button"
|
||||
id="asset-backButtom"
|
||||
onClick={() => {
|
||||
setSelectedCategory(null);
|
||||
setSelectedSubCategory(null);
|
||||
setCategoryAssets([]);
|
||||
}}
|
||||
>
|
||||
<div className="back-arrow">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
Back
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
{selectedCategory === "Decals" && (
|
||||
<>
|
||||
<div className="catogory-asset-filter">
|
||||
{activeSubcategories.map((cat, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`catogory-asset-filter-wrapper ${
|
||||
selectedSubCategory === cat.name ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
fetchCategoryDecals(cat.name);
|
||||
setSelectedSubCategory(cat.name);
|
||||
}}
|
||||
>
|
||||
<div className="sub-catagory">{cat.icon}</div>
|
||||
<div className="sub-catagory">{cat.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{selectedCategory !== "Decals" && !selectedSubCategory ? (
|
||||
<div className="assets-container">
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset}`}
|
||||
className="assets"
|
||||
id={asset.filename}
|
||||
title={asset.filename}
|
||||
>
|
||||
<img
|
||||
src={asset?.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
type:
|
||||
asset.type === "undefined"
|
||||
? undefined
|
||||
: asset.type,
|
||||
category: asset.category,
|
||||
subType: asset.subType,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="asset-name">
|
||||
{asset.filename
|
||||
.split("_")
|
||||
.map(
|
||||
(word: any) =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
)
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{categoryAssets.length === 0 && (
|
||||
<div className="no-asset">
|
||||
🚧 The asset shelf is empty. We're working on filling
|
||||
it up!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="assets-container">
|
||||
{categoryAssets?.map((asset: any, index: number) => (
|
||||
<div
|
||||
key={`${index}-${asset}`}
|
||||
className="assets"
|
||||
id={asset.decalName}
|
||||
title={asset.decalName}
|
||||
>
|
||||
<img
|
||||
src={asset?.decalImage}
|
||||
alt={asset.decalName}
|
||||
className="asset-image"
|
||||
onPointerDown={() => {
|
||||
setDroppedDecal({
|
||||
category: asset.category,
|
||||
decalName: asset.decalName,
|
||||
decalImage: asset.decalImage,
|
||||
decalId: asset.id,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="asset-name">
|
||||
{asset.decalName
|
||||
.split("_")
|
||||
.map(
|
||||
(word: any) =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
)
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{categoryAssets.length === 0 && (
|
||||
<div className="no-asset">
|
||||
🚧 The asset shelf is empty. We're working on filling
|
||||
it up!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="assets-wrapper">
|
||||
<h2 className="categories-header">Categories</h2>
|
||||
<div className="categories-container">
|
||||
{Array.from(
|
||||
new Set(categoryList.map((asset) => asset.category))
|
||||
).map((category, index) => {
|
||||
const categoryInfo = categoryList.find(
|
||||
(asset) => asset.category === category
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={`${index}-${category}`}
|
||||
className="category"
|
||||
id={category}
|
||||
onClick={() => {
|
||||
fetchCategoryAssets(category);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={categoryInfo?.categoryImage ?? ""}
|
||||
alt={category}
|
||||
className="category-image"
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="category-name">{category}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Assets;
|
||||
@@ -3,7 +3,7 @@ import ToggleHeader from "../../ui/inputs/ToggleHeader";
|
||||
import Outline from "./Outline";
|
||||
import Header from "./Header";
|
||||
import { useToggleStore } from "../../../store/useUIToggleStore";
|
||||
import Assets from "./Assets";
|
||||
import Assets from "./assetList/Assets";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import Widgets from "./visualization/widgets/Widgets";
|
||||
import Templates from "../../../modules/visualization/template/Templates";
|
||||
|
||||
177
app/src/components/layout/sidebarLeft/assetList/Assets.tsx
Normal file
177
app/src/components/layout/sidebarLeft/assetList/Assets.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { useDecalStore } from "../../../../store/builder/store";
|
||||
import { getFilteredAssets } from "./assetsHelpers/filteredAssetsHelper";
|
||||
import { fetchCategoryDecals } from "./assetsHelpers/fetchDecalsHelper";
|
||||
import {
|
||||
fetchAllAssets,
|
||||
fetchCategoryAssets,
|
||||
} from "./assetsHelpers/fetchAssetsHelper";
|
||||
import Search from "../../../ui/inputs/Search";
|
||||
import SkeletonUI from "../../../templates/SkeletonUI";
|
||||
import { RenderAsset } from "./assetsHelpers/renderAssetHelper";
|
||||
import {
|
||||
ACTIVE_DECAL_SUBCATEGORIES,
|
||||
CATEGORY_LIST,
|
||||
} from "./assetsHelpers/constants";
|
||||
import { ArrowIcon } from "../../../icons/ExportCommonIcons";
|
||||
|
||||
const Assets: React.FC = () => {
|
||||
const { selectedSubCategory, setSelectedSubCategory } = useDecalStore();
|
||||
const [searchValue, setSearchValue] = useState<string | null>(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
|
||||
const [assets, setAssets] = useState<AssetProp[] | DecalProp[]>([]);
|
||||
const [globalResults, setGlobalResults] = useState<(AssetProp | DecalProp)[]>(
|
||||
[]
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const filteredAssets = useMemo(
|
||||
() =>
|
||||
getFilteredAssets({
|
||||
assets,
|
||||
searchValue,
|
||||
selectedCategory,
|
||||
selectedSubCategory,
|
||||
}),
|
||||
[assets, searchValue, selectedCategory, selectedSubCategory]
|
||||
);
|
||||
|
||||
const handleFetchCategory = useCallback(
|
||||
async (category: string) => {
|
||||
setIsLoading(true);
|
||||
setSelectedCategory(category);
|
||||
if (category === "Decals") {
|
||||
const res = await fetchCategoryDecals("Safety");
|
||||
setAssets(res);
|
||||
setSelectedSubCategory("Safety");
|
||||
} else {
|
||||
const res = await fetchCategoryAssets(category);
|
||||
setAssets(res);
|
||||
}
|
||||
setIsLoading(false);
|
||||
},
|
||||
[setSelectedSubCategory]
|
||||
);
|
||||
|
||||
const fetchGlobalSearch = useCallback(async (term: string) => {
|
||||
setIsLoading(true);
|
||||
const allAssets = await fetchAllAssets();
|
||||
const lowerTerm = term.toLowerCase();
|
||||
const matches = allAssets.filter(
|
||||
(a) =>
|
||||
a.filename.toLowerCase().includes(lowerTerm) ||
|
||||
a.tags?.toLowerCase().includes(lowerTerm) ||
|
||||
a.category?.toLowerCase().includes(lowerTerm)
|
||||
);
|
||||
setGlobalResults(matches);
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedCategory && searchValue?.trim())
|
||||
fetchGlobalSearch(searchValue);
|
||||
else setGlobalResults([]);
|
||||
}, [searchValue, selectedCategory, fetchGlobalSearch]);
|
||||
|
||||
return (
|
||||
<div className="assets-container-main">
|
||||
<Search onChange={setSearchValue} value={searchValue} />
|
||||
<div className="assets-list-section">
|
||||
<section>
|
||||
{isLoading ? (
|
||||
<SkeletonUI type="asset" />
|
||||
) : searchValue || selectedCategory ? (
|
||||
<div className="assets-wrapper">
|
||||
{selectedCategory ? (
|
||||
<>
|
||||
<h2 className="header">
|
||||
{selectedCategory}
|
||||
<button
|
||||
className="back-button"
|
||||
onClick={() => {
|
||||
setSelectedCategory(null);
|
||||
setSelectedSubCategory(null);
|
||||
setAssets([]);
|
||||
setSearchValue(null);
|
||||
}}
|
||||
>
|
||||
<div className="back-arrow">
|
||||
<ArrowIcon />
|
||||
</div>{" "}
|
||||
Back
|
||||
</button>
|
||||
</h2>
|
||||
{selectedCategory === "Decals" && (
|
||||
<div className="catogory-asset-filter">
|
||||
{ACTIVE_DECAL_SUBCATEGORIES.map((cat) => (
|
||||
<div
|
||||
key={cat.name}
|
||||
className={`catogory-asset-filter-wrapper ${
|
||||
selectedSubCategory === cat.name ? "active" : ""
|
||||
}`}
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
const res = await fetchCategoryDecals(cat.name);
|
||||
setAssets(res);
|
||||
setSelectedSubCategory(cat.name);
|
||||
setIsLoading(false);
|
||||
}}
|
||||
>
|
||||
<div className="sub-catagory">{cat.icon}</div>
|
||||
<div className="sub-catagory">{cat.name}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="assets-container">
|
||||
{filteredAssets.map((a, i) => (
|
||||
<RenderAsset key={i} asset={a} index={i} />
|
||||
))}
|
||||
{filteredAssets.length === 0 && (
|
||||
<div className="no-asset">🚧 No assets found</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="header">Global Search Results</h2>
|
||||
<div className="assets-container">
|
||||
{globalResults.map((a, i) => (
|
||||
<RenderAsset key={i} asset={a} index={i} />
|
||||
))}
|
||||
{globalResults.length === 0 && (
|
||||
<div className="no-asset">🔎 No matches found</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="assets-wrapper">
|
||||
<h2 className="categories-header">Categories</h2>
|
||||
<div className="categories-container">
|
||||
{CATEGORY_LIST.map((cat) => (
|
||||
<div
|
||||
key={cat.category}
|
||||
className="category"
|
||||
onClick={() => handleFetchCategory(cat.category)}
|
||||
>
|
||||
<img
|
||||
src={cat.categoryImage}
|
||||
alt={cat.category}
|
||||
className="category-image"
|
||||
draggable={false}
|
||||
/>
|
||||
<div className="category-name">{cat.category}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Assets;
|
||||
@@ -0,0 +1,34 @@
|
||||
import vehicle from "../../../../../assets/image/categories/vehicles.png";
|
||||
import workStation from "../../../../../assets/image/categories/workStation.png";
|
||||
import machines from "../../../../../assets/image/categories/machines.png";
|
||||
import worker from "../../../../../assets/image/categories/worker.png";
|
||||
import storage from "../../../../../assets/image/categories/storage.png";
|
||||
import office from "../../../../../assets/image/categories/office.png";
|
||||
import safety from "../../../../../assets/image/categories/safety.png";
|
||||
import feneration from "../../../../../assets/image/categories/feneration.png";
|
||||
import decal from "../../../../../assets/image/categories/decal.png";
|
||||
import {
|
||||
AlertIcon,
|
||||
DecalInfoIcon,
|
||||
HangTagIcon,
|
||||
NavigationIcon,
|
||||
} from "../../../../icons/ExportCommonIcons";
|
||||
|
||||
export const CATEGORY_LIST: CategoryListProp[] = [
|
||||
{ category: "Fenestration", categoryImage: feneration },
|
||||
{ category: "Decals", categoryImage: decal },
|
||||
{ category: "Vehicles", categoryImage: vehicle },
|
||||
{ category: "Workstation", categoryImage: workStation },
|
||||
{ category: "Machines", categoryImage: machines },
|
||||
{ category: "Workers", categoryImage: worker },
|
||||
{ category: "Storage", categoryImage: storage },
|
||||
{ category: "Safety", categoryImage: safety },
|
||||
{ category: "Office", categoryImage: office },
|
||||
];
|
||||
|
||||
export const ACTIVE_DECAL_SUBCATEGORIES = [
|
||||
{ name: "Safety", icon: <AlertIcon /> },
|
||||
{ name: "Navigation", icon: <NavigationIcon /> },
|
||||
{ name: "Branding", icon: <HangTagIcon /> },
|
||||
{ name: "Informational", icon: <DecalInfoIcon /> },
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
import { getCategoryAsset } from "../../../../../services/factoryBuilder/asset/assets/getCategoryAsset";
|
||||
import { fetchAssets } from "../../../../../services/marketplace/fetchAssets";
|
||||
|
||||
export const fetchCategoryAssets = async (category: string): Promise<AssetProp[]> => {
|
||||
if (category === "Decals") return []; // handled separately
|
||||
try {
|
||||
const res = await getCategoryAsset(category);
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch category assets", err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchAllAssets = async (): Promise<AssetProp[]> => {
|
||||
try {
|
||||
return await fetchAssets();
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch all assets", err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { getCategoryDecals } from "../../../../../services/factoryBuilder/asset/decals/getCategoryDecals";
|
||||
|
||||
export const fetchCategoryDecals = async (subcategory: string): Promise<DecalProp[]> => {
|
||||
try {
|
||||
const res = await getCategoryDecals(subcategory);
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch decals", err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
interface FilterProps {
|
||||
assets: AssetProp[] | DecalProp[];
|
||||
searchValue: string | null;
|
||||
selectedCategory: string | null;
|
||||
selectedSubCategory: string | null;
|
||||
}
|
||||
|
||||
export const getFilteredAssets = ({
|
||||
assets,
|
||||
searchValue,
|
||||
selectedCategory,
|
||||
selectedSubCategory,
|
||||
}: FilterProps) => {
|
||||
const term = searchValue?.trim().toLowerCase();
|
||||
if (!term) return assets;
|
||||
|
||||
if (selectedCategory === "Decals" || selectedSubCategory) {
|
||||
return (assets as DecalProp[]).filter((a) =>
|
||||
a.decalName?.toLowerCase().includes(term)
|
||||
);
|
||||
}
|
||||
|
||||
return (assets as AssetProp[]).filter((a) => {
|
||||
const tags = a.tags?.toLowerCase() ?? "";
|
||||
const filename = a.filename?.toLowerCase() ?? "";
|
||||
const category = a.category?.toLowerCase() ?? "";
|
||||
|
||||
if (term.startsWith(":")) return tags.includes(term.slice(1));
|
||||
if (selectedCategory)
|
||||
return category.includes(selectedCategory.toLowerCase()) && filename.includes(term);
|
||||
|
||||
return filename.includes(term);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
import { useDroppedDecal, useSelectedItem } from "../../../../../store/builder/store";
|
||||
|
||||
export const RenderAsset: React.FC<{ asset: AssetProp | DecalProp; index: number }> = ({ asset, index }) => {
|
||||
const { setSelectedItem } = useSelectedItem();
|
||||
const { setDroppedDecal } = useDroppedDecal();
|
||||
|
||||
if ("decalName" in asset) {
|
||||
return (
|
||||
<div key={`${index}-${asset.decalName}`} className="assets" id={asset.decalName} title={asset.decalName}>
|
||||
<img
|
||||
src={asset.decalImage}
|
||||
alt={asset.decalName}
|
||||
className="asset-image"
|
||||
onPointerDown={() =>
|
||||
setDroppedDecal({
|
||||
category: asset.category,
|
||||
decalName: asset.decalName,
|
||||
decalImage: asset.decalImage,
|
||||
decalId: asset.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div className="asset-name">
|
||||
{asset.decalName
|
||||
.split("_")
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={`${index}-${asset.filename}`} className="assets" id={asset.filename} title={asset.filename}>
|
||||
<img
|
||||
src={asset.thumbnail}
|
||||
alt={asset.filename}
|
||||
className="asset-image"
|
||||
onPointerDown={() =>
|
||||
setSelectedItem({
|
||||
name: asset.filename,
|
||||
id: asset.AssetID,
|
||||
type: asset.type === "undefined" ? undefined : asset.type,
|
||||
category: asset.category,
|
||||
subType: asset.subType,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div className="asset-name">
|
||||
{asset.filename
|
||||
.split("_")
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
.join(" ")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { ChangeEvent, useState } from "react";
|
||||
import React, { ChangeEvent, useEffect, useState } from "react";
|
||||
import { CloseIcon, SearchIcon } from "../../icons/ExportCommonIcons";
|
||||
|
||||
interface SearchProps {
|
||||
value?: string; // The current value of the search input
|
||||
value?: string | null; // The current value of the search input
|
||||
placeholder?: string; // Placeholder text for the input
|
||||
onChange: (value: string) => void; // Callback function to handle input changes
|
||||
}
|
||||
@@ -22,7 +22,15 @@ const Search: React.FC<SearchProps> = ({
|
||||
onChange(newValue); // Call the onChange prop with the new value
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (value === null) {
|
||||
setInputValue("");
|
||||
handleBlur();
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleClear = () => {
|
||||
echo.warn("Search field cleared.");
|
||||
setInputValue("");
|
||||
onChange(""); // Clear the input value
|
||||
};
|
||||
@@ -48,7 +56,7 @@ const Search: React.FC<SearchProps> = ({
|
||||
<input
|
||||
type="text"
|
||||
className="search-input"
|
||||
value={inputValue}
|
||||
value={inputValue ?? ""}
|
||||
placeholder={placeholder}
|
||||
onChange={handleInputChange}
|
||||
onFocus={handleFocus}
|
||||
|
||||
@@ -5,6 +5,11 @@ import type { CameraControls } from "@react-three/drei";
|
||||
|
||||
const CameraShortcutsControls = () => {
|
||||
const { camera, controls } = useThree();
|
||||
|
||||
const isTextInput = (element: Element | null): boolean =>
|
||||
element instanceof HTMLInputElement ||
|
||||
element instanceof HTMLTextAreaElement ||
|
||||
element?.getAttribute("contenteditable") === "true";
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -21,6 +26,8 @@ const CameraShortcutsControls = () => {
|
||||
|
||||
const dir = new THREE.Vector3().subVectors(camera.position, target).normalize();
|
||||
|
||||
if (isTextInput(document.activeElement)) return;
|
||||
|
||||
switch (e.key) {
|
||||
case "1": // Front
|
||||
pos = new THREE.Vector3(0, 0, distance).add(target);
|
||||
|
||||
29
app/src/types/uiTypes.d.ts
vendored
Normal file
29
app/src/types/uiTypes.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
interface AssetProp {
|
||||
filename: string;
|
||||
thumbnail?: string;
|
||||
category: string;
|
||||
description?: string;
|
||||
tags: string;
|
||||
url?: string;
|
||||
uploadDate?: number;
|
||||
isArchieve?: boolean;
|
||||
animated?: boolean;
|
||||
price?: number;
|
||||
CreatedBy?: string;
|
||||
AssetID?: string;
|
||||
type?: string;
|
||||
subType?: string;
|
||||
}
|
||||
|
||||
interface DecalProp {
|
||||
id: string;
|
||||
decalName: string;
|
||||
decalImage: string;
|
||||
category: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface CategoryListProp {
|
||||
categoryImage: string;
|
||||
category: string;
|
||||
}
|
||||
Reference in New Issue
Block a user