diff --git a/crates/desktop/artcraft/tauri.artcraft_3d.no_dev_windows.conf.json b/crates/desktop/artcraft/tauri.artcraft_3d.no_dev_windows.conf.json new file mode 100644 index 0000000000..29720ce7a7 --- /dev/null +++ b/crates/desktop/artcraft/tauri.artcraft_3d.no_dev_windows.conf.json @@ -0,0 +1,8 @@ +{ + "build": { + "frontendDist": "../../../frontend/apps/editor3d/dist", + "beforeDevCommand": "echo 'no dev'", + "devUrl": "http://localhost:5173", + "beforeBuildCommand": "npx nx run editor3d:build" + } +} diff --git a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx index b0f979daf9..ba6ec6b13f 100644 --- a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx +++ b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx @@ -12,6 +12,7 @@ import { getCurrentLocationWithoutParams } from "~/utilities"; import { Activity } from "~/pages/PageEnigma/comps/GenerateModals/Activity"; import { GalleryModal, + galleryModalVisibleViewMode, galleryModalVisibleDuringDrag, } from "@storyteller/ui-gallery-modal"; import { SettingsModal } from "@storyteller/ui-settings-modal"; @@ -110,6 +111,7 @@ export const TopBar = ({ }; const handleOpenGalleryModal = () => { + galleryModalVisibleViewMode.value = true; galleryModalVisibleDuringDrag.value = true; }; @@ -185,8 +187,6 @@ export const TopBar = ({ /> (galleryModalVisibleDuringDrag.value = false)} mode="view" onDownloadClicked={downloadFileFromUrl} onAddToSceneClicked={handleAddToScene} diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx index 6eef0f1cdd..9d283a78f9 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx @@ -62,6 +62,22 @@ import { v4 as uuidv4 } from "uuid"; import { MediaItem } from "~/pages/PageEnigma/models"; import { GalleryItem } from "../../../../../../libs/components/gallery-modal/src/lib/gallery-modal"; import { UploaderState } from "~/models"; +import * as THREE from "three"; + +// Utility to convert screen (client) coordinates to world coordinates for the image drop +function screenToWorld( + x: number, + y: number, + camera: THREE.Camera, + renderer: THREE.WebGLRenderer, +): THREE.Vector3 { + const rect = renderer.domElement.getBoundingClientRect(); + const ndcX = ((x - rect.left) / rect.width) * 2 - 1; + const ndcY = -((y - rect.top) / rect.height) * 2 + 1; + const vector = new THREE.Vector3(ndcX, ndcY, 0.5); // z=0.5 for middle of the frustum + vector.unproject(camera); + return vector; +} export const PageEditor = () => { useSignals(); @@ -78,6 +94,7 @@ export const PageEditor = () => { }; }, []); + // Image drop from gallery/library modal useEffect(() => { setOnImageDrop( async (item: GalleryItem, _position: { x: number; y: number }) => { @@ -91,12 +108,30 @@ export const PageEditor = () => { }, }); console.log("Upload complete, adding object to scene"); + + // Calculate world position from cursor + let worldPosition = undefined; + if (editorEngine?.camera && editorEngine?.renderer && _position) { + worldPosition = screenToWorld( + _position.x, + _position.y, + editorEngine.camera, + editorEngine.renderer, + ); + } + const mediaItem: MediaItem = { version: 1, type: AssetType.OBJECT, media_id: item.id || uuidv4(), name: item.label || "Image Plane", - // Add other fields as needed + ...(worldPosition && { + position: { + x: worldPosition.x, + y: worldPosition.y, + z: worldPosition.z, + }, + }), }; addObject(mediaItem); console.log("Object added to scene:", mediaItem); diff --git a/frontend/apps/editor3d/tsconfig.json b/frontend/apps/editor3d/tsconfig.json index b89dbb548a..3f7d159b59 100644 --- a/frontend/apps/editor3d/tsconfig.json +++ b/frontend/apps/editor3d/tsconfig.json @@ -37,9 +37,6 @@ { "path": "../../libs/components/close-button" }, - { - "path": "../../libs/tauri-api" - }, { "path": "../../libs/components/toaster" }, @@ -61,6 +58,9 @@ { "path": "../../libs/common" }, + { + "path": "../../libs/tauri-api" + }, { "path": "../../libs/components/tab-selector" }, @@ -84,9 +84,6 @@ }, { "path": "../../libs/tauri-utils" - }, - { - "path": "../../libs/build-env" } ] } diff --git a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx index a2e89456b5..4b5db498c4 100644 --- a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx +++ b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx @@ -15,6 +15,7 @@ import { useSignals } from "@preact/signals-react/runtime"; import { galleryModalVisibleDuringDrag, galleryReopenAfterDragSignal, + galleryModalVisibleViewMode, } from "./galleryModalSignals"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { @@ -46,8 +47,7 @@ interface GroupedItems { type ModalMode = "select" | "view"; interface GalleryModalProps { - isOpen: boolean; - onClose: () => void; + onClose?: () => void; mode: ModalMode; selectedItemIds?: string[]; onSelectItem?: (id: string) => void; @@ -58,11 +58,11 @@ interface GalleryModalProps { url: string, media_id: string | undefined ) => Promise; + isOpen?: boolean; } export const GalleryModal = React.memo( ({ - isOpen, onClose, mode = "view", selectedItemIds = [], @@ -71,6 +71,7 @@ export const GalleryModal = React.memo( onUseSelected, onDownloadClicked, onAddToSceneClicked, + isOpen, }: GalleryModalProps) => { const [loading, setLoading] = useState(false); const [lightboxImage, setLightboxImage] = useState( @@ -145,10 +146,10 @@ export const GalleryModal = React.memo( setUsername(session.data.user.username); } }; - if (isOpen) { + if (isOpen || (mode === "view" && galleryModalVisibleViewMode.value)) { getUsername(); } - }, [usersApi, isOpen]); + }, [usersApi, mode, galleryModalVisibleViewMode.value, isOpen]); // filter state const FILTERS = [ @@ -286,7 +287,12 @@ export const GalleryModal = React.memo( // Soft refresh on open: fetch first page and prepend new items useEffect(() => { - if (isOpen && allItems.length > 0 && username) { + if ( + mode === "view" && + galleryModalVisibleViewMode.value && + allItems.length > 0 && + username + ) { const fetchLatest = async () => { const filterMediaClasses = getFilterMediaClass(activeFilter); const response = await api.listUserMediaFiles({ @@ -327,17 +333,26 @@ export const GalleryModal = React.memo( fetchLatest(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isOpen]); + }, [mode, galleryModalVisibleViewMode.value]); return ( <> { + if (mode === "view") { + onClose?.() || (galleryModalVisibleViewMode.value = false); + } else { + onClose?.(); + } + }} className={twMerge( "h-[620px] max-w-4xl", mode === "view" && "h-[640px] max-w-4xl" @@ -440,7 +455,10 @@ export const GalleryModal = React.memo( showIconsInList={true} /> {mode === "view" && } - + (galleryModalVisibleViewMode.value = false)} + className="relative z-[51]" + /> @@ -541,7 +559,11 @@ export const GalleryModal = React.memo( + mode === "view" + ? (galleryModalVisibleViewMode.value = false) + : onClose?.() + } imageUrl={imageUrl} imageAlt={lightboxImage?.label || ""} onImageError={() => imageUrl && handleImageError(imageUrl)} diff --git a/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts b/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts index 236c96bbd7..2347f2bcdc 100644 --- a/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts +++ b/frontend/libs/components/gallery-modal/src/lib/galleryModalSignals.ts @@ -1,4 +1,5 @@ import { signal } from "@preact/signals-react"; -export const galleryModalVisibleDuringDrag = signal(false); +export const galleryModalVisibleDuringDrag = signal(true); export const galleryReopenAfterDragSignal = signal(false); +export const galleryModalVisibleViewMode = signal(false); diff --git a/frontend/libs/components/gallery-modal/tsconfig.json b/frontend/libs/components/gallery-modal/tsconfig.json index 7f7e55b2cd..9cf3865d06 100644 --- a/frontend/libs/components/gallery-modal/tsconfig.json +++ b/frontend/libs/components/gallery-modal/tsconfig.json @@ -2,6 +2,12 @@ "files": [], "include": [], "references": [ + { + "path": "../slider-v2" + }, + { + "path": "../popover" + }, { "path": "../../api" }, @@ -14,15 +20,15 @@ { "path": "../button" }, - { - "path": "../tab-selector" - }, { "path": "../lightbox-modal" }, { "path": "../modal" }, + { + "path": "../tooltip" + }, { "path": "./tsconfig.lib.json" }, diff --git a/frontend/libs/components/gallery-modal/tsconfig.lib.json b/frontend/libs/components/gallery-modal/tsconfig.lib.json index 89d6f254d7..5f8a8fdaea 100644 --- a/frontend/libs/components/gallery-modal/tsconfig.lib.json +++ b/frontend/libs/components/gallery-modal/tsconfig.lib.json @@ -43,6 +43,12 @@ "src/**/*.tsx" ], "references": [ + { + "path": "../slider-v2/tsconfig.lib.json" + }, + { + "path": "../popover/tsconfig.lib.json" + }, { "path": "../../api/tsconfig.lib.json" }, @@ -55,14 +61,14 @@ { "path": "../button/tsconfig.lib.json" }, - { - "path": "../tab-selector/tsconfig.lib.json" - }, { "path": "../lightbox-modal/tsconfig.lib.json" }, { "path": "../modal/tsconfig.lib.json" + }, + { + "path": "../tooltip/tsconfig.lib.json" } ] } diff --git a/frontend/libs/components/lightbox-modal/tsconfig.json b/frontend/libs/components/lightbox-modal/tsconfig.json index e38858143d..dcc83ffd8e 100644 --- a/frontend/libs/components/lightbox-modal/tsconfig.json +++ b/frontend/libs/components/lightbox-modal/tsconfig.json @@ -2,6 +2,9 @@ "files": [], "include": [], "references": [ + { + "path": "../../tauri-api" + }, { "path": "../button" }, diff --git a/frontend/libs/components/lightbox-modal/tsconfig.lib.json b/frontend/libs/components/lightbox-modal/tsconfig.lib.json index 3f524fa65c..515cb5e4a6 100644 --- a/frontend/libs/components/lightbox-modal/tsconfig.lib.json +++ b/frontend/libs/components/lightbox-modal/tsconfig.lib.json @@ -43,6 +43,9 @@ "src/**/*.tsx" ], "references": [ + { + "path": "../../tauri-api/tsconfig.lib.json" + }, { "path": "../button/tsconfig.lib.json" }, diff --git a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx index 8fa3de7ee1..314973c738 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBox2D.tsx @@ -82,7 +82,6 @@ export const PromptBox2D = ({ const [prompt, setPrompt] = useState(""); const [isEnqueueing, setIsEnqueueing] = useState(false); const [useSystemPrompt, setUseSystemPrompt] = useState(true); - const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false); const [selectedGalleryImages, setSelectedGalleryImages] = useState( [] ); @@ -111,6 +110,8 @@ export const PromptBox2D = ({ const fileInputRef = useRef(null); const textareaRef = useRef(null); + const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false); + useEffect(() => { if (textareaRef.current) { textareaRef.current.style.height = "auto"; @@ -185,9 +186,7 @@ export const PromptBox2D = ({ fileInputRef.current?.click(); }; - const handleGallerySelect = () => { - setIsGalleryModalOpen(true); - }; + const handleGallerySelect = () => setIsGalleryModalOpen(true); const handleGalleryClose = () => { setIsGalleryModalOpen(false); @@ -638,7 +637,7 @@ export const PromptBox2D = ({ ( [] ); + const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false); useEffect(() => { if (textareaRef.current) { @@ -257,10 +257,7 @@ export const PromptBox3D = ({ fileInputRef.current?.click(); }; - const handleGallerySelect = () => { - setIsGalleryModalOpen(true); - }; - + const handleGallerySelect = () => setIsGalleryModalOpen(true); const handleGalleryClose = () => { setIsGalleryModalOpen(false); setSelectedGalleryImages([]); @@ -521,7 +518,7 @@ export const PromptBox3D = ({ } }; - const getCurrentSoraRemixAspectRatio = () : SoraImageRemixAspectRatio => { + const getCurrentSoraRemixAspectRatio = (): SoraImageRemixAspectRatio => { switch (cameraAspectRatio.value) { case CameraAspectRatio.HORIZONTAL_3_2: return SoraImageRemixAspectRatio.Wide; @@ -532,7 +529,7 @@ export const PromptBox3D = ({ default: return SoraImageRemixAspectRatio.Square; } - } + }; const handleSaveFrame = async () => { if (!snapshotCurrentFrame) { @@ -828,7 +825,7 @@ export const PromptBox3D = ({ /> ( [] ); @@ -132,10 +131,9 @@ export const PromptBoxVideo = ({ } }; - const handleGallerySelect = () => { - setIsGalleryModalOpen(true); - }; + const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false); + const handleGallerySelect = () => setIsGalleryModalOpen(true); const handleGalleryClose = () => { setIsGalleryModalOpen(false); setSelectedGalleryImages([]); @@ -411,7 +409,7 @@ export const PromptBoxVideo = ({