"Make 3D" button to not enqueue directly but go to 3D object page

This commit is contained in:
bflatastic committed 2026-03-11 14:17:12 +07:00
1 parent c7744ae545
commit 6d3ec8bfee
5 files changed
+100 -39

No files matched your search

@@ -140,6 +140,12 @@ export const ImageTo3DExperience = ({
const clearPendingExternalImage = useImageTo3DWorldStore(
(s) => s.clearPendingExternalImage,
);
const pendingObjectImage = useImageTo3DStore(
(s) => s.pendingExternalImage,
);
const clearPendingObjectImage = useImageTo3DStore(
(s) => s.clearPendingExternalImage,
);
const results = variant === "object" ? objectResults : worldResults;
const resetResults = variant === "object" ? objectReset : worldReset;
@@ -153,24 +159,34 @@ export const ImageTo3DExperience = ({
useEffect(() => {
// Read from store directly to avoid Strict Mode double-execution adding the image twice
const pending = useImageTo3DWorldStore.getState().pendingExternalImage;
if (variant === "world" && pending) {
setWorldImages((prev) => {
if (prev.length >= MAX_WORLD_IMAGES) return prev;
return [
...prev,
{
id: generateId(),
preview: pending.url,
mediaToken: pending.mediaToken,
name: "Library Image",
isUploading: false,
},
];
});
clearPendingExternalImage();
if (variant === "world") {
const pending = useImageTo3DWorldStore.getState().pendingExternalImage;
if (pending) {
setWorldImages((prev) => {
if (prev.length >= MAX_WORLD_IMAGES) return prev;
return [
...prev,
{
id: generateId(),
preview: pending.url,
mediaToken: pending.mediaToken,
name: "Library Image",
isUploading: false,
},
];
});
clearPendingExternalImage();
}
} else if (variant === "object") {
const pending = useImageTo3DStore.getState().pendingExternalImage;
if (pending) {
setUploadedPreview(pending.url);
setUploadedMediaToken(pending.mediaToken);
setUploadedName("Library Image");
clearPendingObjectImage();
}
}
}, [variant, pendingExternalImage, clearPendingExternalImage]);
}, [variant, pendingExternalImage, clearPendingExternalImage, pendingObjectImage, clearPendingObjectImage]);
useEffect(() => {
const onResize = () => setVh(window.innerHeight);
@@ -63,6 +63,7 @@ import {
is3DSceneLoaded,
set3DPageMounted,
} from "~/pages/PageEnigma/Editor/editor";
import { useImageTo3DStore } from "~/pages/PageImageTo3DObject/ImageTo3DStore";
import { useImageTo3DWorldStore } from "~/pages/PageImageTo3DWorld/ImageTo3DWorldStore";
import { useRemoveBackgroundStore } from "~/pages/PageRemoveBackground/RemoveBackgroundStore";
import { TabId, useTabStore } from "~/pages/Stores/TabState";
@@ -274,6 +275,23 @@ export const TopBar = ({ pageName }: Props) => {
}
};
const handleMake3DObjectFromGallery = async (
url: string,
mediaId?: string,
) => {
try {
if (mediaId) {
useImageTo3DStore.getState().setPendingExternalImage(url, mediaId);
}
useTabStore.getState().setActiveTab("IMAGE_TO_3D_OBJECT");
galleryModalVisibleViewMode.value = false;
galleryModalVisibleDuringDrag.value = false;
galleryModalLightboxVisible.value = false;
} catch (e) {
// no-op
}
};
const handleMake3DWorldFromGallery = async (
url: string,
mediaId?: string,
@@ -620,6 +638,7 @@ export const TopBar = ({ pageName }: Props) => {
onEditClicked={handleEditFromGallery}
onTurnIntoVideoClicked={handleTurnIntoVideoFromGallery}
onRemoveBackgroundClicked={handleRemoveBackgroundFromGallery}
onMake3DObjectClicked={handleMake3DObjectFromGallery}
onMake3DWorldClicked={handleMake3DWorldFromGallery}
/>
@@ -129,6 +129,11 @@ async function captureModelThumbnail(modelUrl: string): Promise<string | null> {
});
}
export interface PendingExternalImage {
url: string;
mediaToken: string;
}
export type ImageTo3DResult = {
id: string;
mode: "image" | "text";
@@ -149,6 +154,9 @@ export const coverImageCache = new Map<string, string>();
type ImageTo3DState = {
results: ImageTo3DResult[];
pendingExternalImage: PendingExternalImage | null;
setPendingExternalImage: (url: string, mediaToken: string) => void;
clearPendingExternalImage: () => void;
startGeneration: (
mode: "image" | "text",
note: string,
@@ -175,6 +183,13 @@ type ImageTo3DState = {
export const useImageTo3DStore = create<ImageTo3DState>((set, get) => ({
results: [],
pendingExternalImage: null,
setPendingExternalImage: (url: string, mediaToken: string) => {
set({ pendingExternalImage: { url, mediaToken } });
},
clearPendingExternalImage: () => {
set({ pendingExternalImage: null });
},
startGeneration: (
mode: "image" | "text",
note: string,
@@ -241,6 +241,10 @@ interface GalleryModalProps {
url: string,
media_id?: string,
) => Promise<void> | void;
onMake3DObjectClicked?: (
url: string,
media_id?: string,
) => Promise<void> | void;
onMake3DWorldClicked?: (
url: string,
media_id?: string,
@@ -356,6 +360,7 @@ export const GalleryModal = React.memo(
onEditClicked,
onTurnIntoVideoClicked,
onRemoveBackgroundClicked,
onMake3DObjectClicked,
onMake3DWorldClicked,
}: GalleryModalProps) => {
const [loading, setLoading] = useState(false);
@@ -1286,6 +1291,7 @@ export const GalleryModal = React.memo(
onEditClicked={onEditClicked}
onTurnIntoVideoClicked={onTurnIntoVideoClicked}
onRemoveBackgroundClicked={onRemoveBackgroundClicked}
onMake3DObjectClicked={onMake3DObjectClicked}
onMake3DWorldClicked={onMake3DWorldClicked}
onNavigatePrev={handleNavigatePrev}
onNavigateNext={handleNavigateNext}
@@ -15,11 +15,7 @@ import {
faVideo,
faWandMagicSparkles,
} from "@fortawesome/pro-solid-svg-icons";
import {
EnqueueImageTo3dObject,
EnqueueImageTo3dObjectModel,
MediaFileDelete,
} from "@storyteller/tauri-api";
import { MediaFileDelete } from "@storyteller/tauri-api";
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
import { Viewer3D } from "@storyteller/ui-viewer-3d";
import {
@@ -85,6 +81,10 @@ interface LightboxModalProps {
url: string,
media_id?: string,
) => Promise<void> | void;
onMake3DObjectClicked?: (
url: string,
media_id?: string,
) => Promise<void> | void;
onMake3DWorldClicked?: (
url: string,
media_id?: string,
@@ -115,6 +115,7 @@ export function LightboxModal({
onEditClicked,
onTurnIntoVideoClicked,
onRemoveBackgroundClicked,
onMake3DObjectClicked,
onMake3DWorldClicked,
batchImageToken,
onNavigatePrev,
@@ -985,23 +986,27 @@ export function LightboxModal({
</Button>
)}
{derivedMediaClass === "image" && (
<Button
className="w-full"
variant="secondary"
icon={faCube}
onClick={async (e) => {
gtagEvent("image_to_3d_clicked");
await EnqueueImageTo3dObject({
image_media_token: selectedMediaToken,
model: EnqueueImageTo3dObjectModel.Hunyuan3d3,
frontend_caller: "mini_app",
});
}}
>
Make 3D
</Button>
)}
{onMake3DObjectClicked &&
actionUrl &&
derivedMediaClass === "image" && (
<Button
className="w-full"
variant="secondary"
icon={faCube}
onClick={async (e) => {
e.stopPropagation();
gtagEvent("image_to_3d_clicked");
await onMake3DObjectClicked(
actionUrl,
selectedMediaToken,
);
onClose();
onCloseGallery();
}}
>
Make 3D
</Button>
)}
{onMake3DWorldClicked &&
actionUrl &&