mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b176d023e | ||
|
|
eef929c065 | ||
|
|
91ea263b0c |
+64
-62
@@ -1,12 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
TransitionDialogue,
|
||||
LoadingDots,
|
||||
Label,
|
||||
CloseButton,
|
||||
} from "~/components";
|
||||
|
||||
import { LoadingDots } from "~/components";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import { UploadAssetError } from "../UploadModal/UploadAssetError";
|
||||
import { UploadSuccess } from "../UploadModal/UploadSuccess";
|
||||
import { UploadFiles3D } from "./UploadFiles3D";
|
||||
@@ -19,18 +14,21 @@ import {
|
||||
CHARACTER_MIXAMO_FILE_TYPE,
|
||||
IMAGEPLANE_FILE_TYPE,
|
||||
} from "~/enums";
|
||||
import { assetModalVisible } from "~/pages/PageEnigma/signals";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
onSuccess: (category: FilterEngineCategories) => void;
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
titleIcon: IconDefinition;
|
||||
options?: {
|
||||
fileSubtypes?: { [key: string]: string }[];
|
||||
hasLength?: boolean;
|
||||
hasThumbnailUpload?: boolean;
|
||||
};
|
||||
preselectedCategory?: FilterEngineCategories;
|
||||
isSelectVisible: boolean;
|
||||
}
|
||||
|
||||
// Map categories to their allowed file types
|
||||
@@ -43,16 +41,6 @@ const categoryFileTypes: Partial<Record<FilterEngineCategories, string[]>> = {
|
||||
[FilterEngineCategories.LOCATION]: Object.values(OBJECT_FILE_TYPE),
|
||||
[FilterEngineCategories.CREATURE]: Object.values(OBJECT_FILE_TYPE),
|
||||
};
|
||||
|
||||
// Map engine categories to asset modal tabs
|
||||
const categoryToAssetTab: Partial<Record<FilterEngineCategories, string>> = {
|
||||
[FilterEngineCategories.OBJECT]: "objects",
|
||||
[FilterEngineCategories.CHARACTER]: "character",
|
||||
[FilterEngineCategories.IMAGE_PLANE]: "image-planes",
|
||||
[FilterEngineCategories.LOCATION]: "sets",
|
||||
[FilterEngineCategories.CREATURE]: "creatures",
|
||||
};
|
||||
|
||||
// Excluded category options for the upload modal
|
||||
const categoryOptions = Object.entries(FilterEngineCategories)
|
||||
.filter(
|
||||
@@ -82,12 +70,19 @@ export function UploadModal3D({
|
||||
onClose,
|
||||
onSuccess,
|
||||
title,
|
||||
titleIcon,
|
||||
options,
|
||||
}: Props) {
|
||||
preselectedCategory,
|
||||
isSelectVisible,
|
||||
}: Props & { preselectedCategory?: FilterEngineCategories }) {
|
||||
const [uploaderState, setUploaderState] =
|
||||
useState<UploaderState>(initialUploaderState);
|
||||
const [selectedCategory, setSelectedCategory] =
|
||||
useState<FilterEngineCategories>(FilterEngineCategories.OBJECT);
|
||||
useState<FilterEngineCategories>(
|
||||
preselectedCategory !== undefined
|
||||
? preselectedCategory
|
||||
: FilterEngineCategories.OBJECT,
|
||||
);
|
||||
|
||||
const updateUploaderState = (newState: UploaderState) => {
|
||||
setUploaderState(newState);
|
||||
@@ -103,30 +98,51 @@ export function UploadModal3D({
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (preselectedCategory !== undefined) {
|
||||
setSelectedCategory(preselectedCategory);
|
||||
}
|
||||
}, [preselectedCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Preselected category:", preselectedCategory);
|
||||
console.log("Initial selected category:", selectedCategory);
|
||||
}, [preselectedCategory, selectedCategory]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uploaderState.status === UploaderStates.success) {
|
||||
onSuccess(selectedCategory);
|
||||
onClose();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [uploaderState.status]);
|
||||
|
||||
const UploaderModalContent = () => {
|
||||
switch (uploaderState.status) {
|
||||
case UploaderStates.ready:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex w-full flex-col">
|
||||
<Label>Category</Label>
|
||||
<Select
|
||||
id="category-select"
|
||||
options={categoryOptions}
|
||||
value={selectedCategory}
|
||||
onChange={(value) => {
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
Object.values(FilterEngineCategories).includes(
|
||||
value as FilterEngineCategories,
|
||||
)
|
||||
) {
|
||||
setSelectedCategory(value as FilterEngineCategories);
|
||||
}
|
||||
}}
|
||||
placeholder="Select a category"
|
||||
/>
|
||||
</div>
|
||||
{isSelectVisible && (
|
||||
<div className="flex w-full flex-col">
|
||||
<Label className="mb-1">Category</Label>
|
||||
<Select
|
||||
id="category-select"
|
||||
options={categoryOptions}
|
||||
value={selectedCategory}
|
||||
onChange={(value) => {
|
||||
if (
|
||||
typeof value === "string" &&
|
||||
Object.values(FilterEngineCategories).includes(
|
||||
value as FilterEngineCategories,
|
||||
)
|
||||
) {
|
||||
setSelectedCategory(value as FilterEngineCategories);
|
||||
}
|
||||
}}
|
||||
placeholder="Select a category"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<UploadFiles3D
|
||||
title={title}
|
||||
engineCategory={selectedCategory}
|
||||
@@ -146,31 +162,17 @@ export function UploadModal3D({
|
||||
<div className="w-100 text-center opacity-50">Uploading...</div>
|
||||
</>
|
||||
);
|
||||
case UploaderStates.success:
|
||||
case UploaderStates.success: {
|
||||
return (
|
||||
<UploadSuccess
|
||||
title="3D model"
|
||||
onOk={() => {
|
||||
onSuccess();
|
||||
onClose();
|
||||
// Store the selected category and tab in sessionStorage
|
||||
const assetTab = categoryToAssetTab[selectedCategory];
|
||||
if (assetTab) {
|
||||
sessionStorage.setItem(
|
||||
"lastUploadedCategory",
|
||||
selectedCategory,
|
||||
);
|
||||
sessionStorage.setItem("lastUploadedTab", assetTab);
|
||||
// Reopen the asset modal
|
||||
assetModalVisible.value = true;
|
||||
} else {
|
||||
// Clear any existing stored values if we don't have a valid tab
|
||||
sessionStorage.removeItem("lastUploadedCategory");
|
||||
sessionStorage.removeItem("lastUploadedTab");
|
||||
}
|
||||
onSuccess(selectedCategory);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case UploaderStates.assetError:
|
||||
return (
|
||||
<UploadAssetError
|
||||
@@ -199,15 +201,15 @@ export function UploadModal3D({
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionDialogue
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
titleIcon={titleIcon}
|
||||
title={title}
|
||||
className="max-w-xl"
|
||||
showClose={false}
|
||||
showClose={true}
|
||||
>
|
||||
<CloseButton className="absolute right-4 top-4" onClick={onClose} />
|
||||
<UploaderModalContent />
|
||||
</TransitionDialogue>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const appTabs: TabItem[] = [
|
||||
{ id: "2D", label: "2D" },
|
||||
{ id: "3D", label: "3D" },
|
||||
{ id: "2D", label: "2D" },
|
||||
];
|
||||
|
||||
export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => {
|
||||
|
||||
@@ -171,12 +171,12 @@ export const PageEditor = () => {
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<LoginModal
|
||||
{/* <LoginModal
|
||||
onClose={() => {}}
|
||||
videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4"
|
||||
videoSrc3D="/resources/videos/artcraft-3d-demo.mp4"
|
||||
openAiLogo="/resources/images/openai-logo.png"
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { UploadModal3D } from "~/components/reusable/UploadModal3D";
|
||||
import {
|
||||
faPlus,
|
||||
faSearch,
|
||||
faChevronLeft,
|
||||
faLayerGroup,
|
||||
@@ -12,13 +11,13 @@ import {
|
||||
faMountainCity,
|
||||
faDog,
|
||||
faImage,
|
||||
faUpFromLine,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { CloseButton } from "@storyteller/ui-close-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { TabSelector } from "~/components/reusable/TabSelector";
|
||||
import { TabSelector } from "@storyteller/ui-tab-selector";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
@@ -45,6 +44,7 @@ import { isAnyStatusFetching } from "../SidePanelTabs/utilities";
|
||||
type AssetTab = {
|
||||
id: string;
|
||||
label: string;
|
||||
labelSingle?: string;
|
||||
icon: typeof faLayerGroup;
|
||||
engineCategory?: FilterEngineCategories;
|
||||
items: MediaItem[];
|
||||
@@ -81,6 +81,21 @@ const AllTabSection = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const categoryToTabIdMap: Record<FilterEngineCategories, string> = {
|
||||
[FilterEngineCategories.CHARACTER]: "characters",
|
||||
[FilterEngineCategories.CREATURE]: "creatures",
|
||||
[FilterEngineCategories.IMAGE_PLANE]: "image-planes",
|
||||
[FilterEngineCategories.LOCATION]: "sets",
|
||||
[FilterEngineCategories.OBJECT]: "objects",
|
||||
[FilterEngineCategories.SKYBOX]: "skybox",
|
||||
[FilterEngineCategories.ANIMATION]: "all",
|
||||
[FilterEngineCategories.AUDIO]: "all",
|
||||
[FilterEngineCategories.EXPRESSION]: "all",
|
||||
[FilterEngineCategories.SCENE]: "all",
|
||||
[FilterEngineCategories.SET_DRESSING]: "all",
|
||||
[FilterEngineCategories.VIDEO_PLANE]: "all",
|
||||
};
|
||||
|
||||
export const AssetModal = () => {
|
||||
useSignals();
|
||||
const [activeLibraryTab, setActiveLibraryTab] = useState("library");
|
||||
@@ -88,6 +103,9 @@ export const AssetModal = () => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const [selectedCategory, setSelectedCategory] =
|
||||
useState<FilterEngineCategories | null>(null);
|
||||
const [isSelectVisible, setIsSelectVisible] = useState(true);
|
||||
|
||||
const handleReopenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.checked;
|
||||
@@ -101,6 +119,10 @@ export const AssetModal = () => {
|
||||
assetModalVisible.value = false;
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
assetModalVisible.value = true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (assetModalVisible.value) {
|
||||
// Check for stored category and tab from upload
|
||||
@@ -116,7 +138,10 @@ export const AssetModal = () => {
|
||||
const timer = setTimeout(() => {
|
||||
searchInputRef.current?.focus();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
// Ensure no side effects remain
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -232,7 +257,8 @@ export const AssetModal = () => {
|
||||
{ id: "all", label: "All", icon: faLayerGroup, items: [] },
|
||||
{
|
||||
id: "character",
|
||||
label: "Character",
|
||||
label: "Characters",
|
||||
labelSingle: "Character",
|
||||
icon: faUser,
|
||||
engineCategory: FilterEngineCategories.CHARACTER,
|
||||
items:
|
||||
@@ -243,6 +269,7 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "objects",
|
||||
label: "Objects",
|
||||
labelSingle: "Object",
|
||||
icon: faCube,
|
||||
engineCategory: FilterEngineCategories.OBJECT,
|
||||
items:
|
||||
@@ -253,6 +280,7 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "sets",
|
||||
label: "Sets",
|
||||
labelSingle: "Set",
|
||||
icon: faMountainCity,
|
||||
engineCategory: FilterEngineCategories.LOCATION,
|
||||
items:
|
||||
@@ -263,6 +291,7 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "creatures",
|
||||
label: "Creatures",
|
||||
labelSingle: "Creature",
|
||||
icon: faDog,
|
||||
engineCategory: FilterEngineCategories.CREATURE,
|
||||
items:
|
||||
@@ -273,12 +302,14 @@ export const AssetModal = () => {
|
||||
{
|
||||
id: "skybox",
|
||||
label: "Skybox",
|
||||
labelSingle: "Skybox",
|
||||
icon: faSun,
|
||||
items: activeLibraryTab === "library" ? demoSkyboxItems.value : [],
|
||||
},
|
||||
{
|
||||
id: "image-planes",
|
||||
label: "Image Planes",
|
||||
labelSingle: "Image Plane",
|
||||
icon: faImage,
|
||||
engineCategory: FilterEngineCategories.IMAGE_PLANE,
|
||||
items:
|
||||
@@ -397,11 +428,20 @@ export const AssetModal = () => {
|
||||
};
|
||||
|
||||
const handleAddAsset = () => {
|
||||
assetModalVisible.value = false;
|
||||
handleClose();
|
||||
setIsUploadModalOpen(true);
|
||||
setSelectedCategory(null);
|
||||
setIsSelectVisible(true);
|
||||
};
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
const handleAddSpecificAsset = (category: FilterEngineCategories) => {
|
||||
handleClose();
|
||||
setIsUploadModalOpen(true);
|
||||
setSelectedCategory(category);
|
||||
setIsSelectVisible(false);
|
||||
};
|
||||
|
||||
const handleUploadSuccess = (category: FilterEngineCategories) => {
|
||||
if (reopenAfterDragSignal.value) {
|
||||
// Small delay to allow the modal to close and reopen
|
||||
setTimeout(() => {
|
||||
@@ -410,10 +450,32 @@ export const AssetModal = () => {
|
||||
} else {
|
||||
handleClose();
|
||||
}
|
||||
|
||||
// Use the provided category to find the AssetTab ID
|
||||
const lastUploadedTabId = categoryToTabIdMap[category] || "all";
|
||||
|
||||
// Close the upload modal
|
||||
setTimeout(() => {
|
||||
sessionStorage.setItem("lastUploadedTab", lastUploadedTabId);
|
||||
console.log("AssetModal: lastUploadedTabId set to:", lastUploadedTabId);
|
||||
setIsUploadModalOpen(false);
|
||||
handleOpen();
|
||||
}, 300);
|
||||
|
||||
fetchUserCharacters();
|
||||
fetchUserObjects();
|
||||
fetchUserSets();
|
||||
fetchUserCreatures();
|
||||
fetchUserImagePlanes();
|
||||
};
|
||||
|
||||
const clearSearch = () => setSearchTerm("");
|
||||
|
||||
const getArticle = (word: string | undefined) => {
|
||||
if (!word) return "a";
|
||||
return /^[aeiou]/i.test(word) ? "an" : "a";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@@ -429,17 +491,31 @@ export const AssetModal = () => {
|
||||
<h2 className="text-[18px] font-semibold opacity-80">
|
||||
3D Assets
|
||||
</h2>
|
||||
<Tooltip content="Upload model" position="top" delay={200}>
|
||||
{/* <Tooltip content="Upload model" position="top" delay={200}>
|
||||
<Button
|
||||
className="h-6 w-6 rounded-full border-none bg-transparent text-white/70 transition-colors hover:bg-transparent hover:text-white/100"
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md border-none bg-primary/50 text-white/70 transition-colors hover:bg-primary/70 hover:text-white/100"
|
||||
onClick={handleAddAsset}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="text-xl" />
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Tooltip> */}
|
||||
</div>
|
||||
<hr className="my-2 w-full border-white/10" />
|
||||
<div className="space-y-2">
|
||||
<div className="flex h-full flex-col space-y-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
className={twMerge(
|
||||
"w-full justify-start rounded-xl border border-white/[2%] bg-white/[4%] px-3.5 py-2.5 text-left hover:bg-white/15",
|
||||
"border-primary bg-primary/70 hover:bg-primary",
|
||||
)}
|
||||
onClick={handleAddAsset}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faUpFromLine}
|
||||
className="mr-2 text-lg opacity-70"
|
||||
/>
|
||||
Upload an asset
|
||||
</Button>
|
||||
{assetTabs.map((tab) => (
|
||||
<Button
|
||||
key={tab.id}
|
||||
@@ -447,7 +523,7 @@ export const AssetModal = () => {
|
||||
className={twMerge(
|
||||
"w-full justify-start rounded-xl border border-white/[2%] bg-white/[4%] px-3.5 py-2.5 text-left hover:bg-white/15",
|
||||
activeAssetTab === tab.id &&
|
||||
"border-brand-primary bg-brand-primary/10 hover:bg-brand-primary/10",
|
||||
"border-primary bg-primary/10 hover:bg-primary/10",
|
||||
)}
|
||||
onClick={() => setActiveAssetTab(tab.id)}
|
||||
>
|
||||
@@ -479,7 +555,7 @@ export const AssetModal = () => {
|
||||
id="reopen-after-add"
|
||||
checked={reopenAfterDragSignal.value}
|
||||
onChange={handleReopenChange}
|
||||
className="h-4 w-4 cursor-pointer rounded-lg border-gray-300 bg-gray-700 text-brand-primary focus:ring-brand-primary"
|
||||
className="h-4 w-4 cursor-pointer rounded-lg border-gray-300 bg-gray-700 text-primary focus:ring-primary"
|
||||
/>
|
||||
<label
|
||||
htmlFor="reopen-after-add"
|
||||
@@ -529,18 +605,36 @@ export const AssetModal = () => {
|
||||
)}
|
||||
>
|
||||
{activeAssetTab !== "all" && !searchTerm && (
|
||||
<div className="mb-2 flex items-center font-semibold">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center gap-2 border-none bg-transparent px-3 py-1.5 text-sm text-white/70 hover:bg-transparent hover:text-white/100"
|
||||
onClick={() => setActiveAssetTab("all")}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronLeft}
|
||||
className="text-sm font-semibold opacity-70"
|
||||
/>
|
||||
</Button>
|
||||
{currentTab.label}
|
||||
<div className="mb-2 flex items-center justify-between font-semibold">
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center gap-2 border-none bg-transparent px-3 py-1.5 text-sm text-white/70 hover:bg-transparent hover:text-white/100"
|
||||
onClick={() => setActiveAssetTab("all")}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronLeft}
|
||||
className="text-sm font-semibold opacity-70"
|
||||
/>
|
||||
</Button>
|
||||
{currentTab.label}
|
||||
</div>
|
||||
{activeAssetTab !== "skybox" &&
|
||||
activeAssetTab !== "all" && (
|
||||
<Button
|
||||
icon={faUpFromLine}
|
||||
onClick={() =>
|
||||
handleAddSpecificAsset(
|
||||
currentTab.engineCategory ||
|
||||
FilterEngineCategories.OBJECT,
|
||||
)
|
||||
}
|
||||
className="border-primary bg-primary/70 px-2 py-1 text-xs transition-colors hover:bg-primary"
|
||||
>
|
||||
Upload {getArticle(currentTab.labelSingle)}{" "}
|
||||
{currentTab.labelSingle}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{renderContent()}
|
||||
@@ -551,10 +645,15 @@ export const AssetModal = () => {
|
||||
</div>
|
||||
</Modal>
|
||||
<UploadModal3D
|
||||
onClose={() => setIsUploadModalOpen(false)}
|
||||
onSuccess={handleUploadSuccess}
|
||||
onClose={() => {
|
||||
setIsUploadModalOpen(false);
|
||||
}}
|
||||
onSuccess={(category) => handleUploadSuccess(category)}
|
||||
isOpen={isUploadModalOpen}
|
||||
title="Upload 3D Asset"
|
||||
title={`Upload ${selectedCategory ? (selectedCategory.toLowerCase().replace(/_/g, " ") === "location" ? "sets" : selectedCategory.toLowerCase().replace(/_/g, " ")) : "3D Asset"}`}
|
||||
titleIcon={!isSelectVisible ? currentTab.icon : faUpFromLine}
|
||||
preselectedCategory={selectedCategory || FilterEngineCategories.OBJECT}
|
||||
isSelectVisible={isSelectVisible}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,8 @@ export const Controls3D = () => {
|
||||
useEffect(() => {
|
||||
// Check if scene is empty and onboarding helper is not visible
|
||||
const checkSceneEmpty = () => {
|
||||
const isSceneEmpty = outlinerState.items.value.length === 0;
|
||||
const isSceneEmpty =
|
||||
outlinerState.items.value.length === 0 && !assetModalVisible.value;
|
||||
|
||||
setShowEmptySceneTooltip(isSceneEmpty);
|
||||
};
|
||||
@@ -37,7 +38,8 @@ export const Controls3D = () => {
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assetModalVisible.value]);
|
||||
|
||||
const handleModeChange = (value: string) => {
|
||||
selectedMode.value = value;
|
||||
@@ -119,7 +121,7 @@ export const Controls3D = () => {
|
||||
<Tooltip
|
||||
content="Add 3D asset to scene (B)"
|
||||
position="bottom"
|
||||
delay={100}
|
||||
delay={300}
|
||||
closeOnClick
|
||||
className={twMerge(
|
||||
showEmptySceneTooltip ? "hidden" : "block",
|
||||
|
||||
@@ -209,7 +209,7 @@ export function LoginModal({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 cursor-pointer bg-black/60" />
|
||||
<div className="fixed inset-0 cursor-pointer bg-black/80" />
|
||||
</TransitionChild>
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<TransitionChild
|
||||
|
||||
@@ -82,6 +82,7 @@ export const TabSelector: React.FC<TabSelectorProps> = ({
|
||||
className={({ selected }) =>
|
||||
twMerge(
|
||||
"relative z-20 mx-0.5 min-w-max rounded-md border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
|
||||
"focus-visible:outline-none focus-visible:ring-0",
|
||||
selected ? "text-white" : "text-gray-300 hover:text-white",
|
||||
disabled ? "cursor-not-allowed opacity-60" : ""
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user