Add scene to 2d Lightbox modal.

This commit is contained in:
Michael Chung committed 2025-05-14 12:10:37 -04:00
1 parent af08834de7
commit 8ac92b6fe0
5 files changed
+86 -189

No files matched your search

@@ -19,6 +19,7 @@ import { Signal } from "@preact/signals-react";
import { useSignals } from "@preact/signals-react/runtime";
import { setLogoutStates } from "~/signals/authentication/utilities";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import { useAppUiContext } from "~/pages/Page2d/contextSignals/appUi";
function isEditorPath(path: string) {
if (path === "/") return true;
@@ -38,7 +39,12 @@ const appTabs: TabItem[] = [
{ id: "3D", label: "3D" },
];
export const TopBar = ({ pageName, appTabIdSignal, setAppTabId, is3DInitSignal }: Props) => {
export const TopBar = ({
pageName,
appTabIdSignal,
setAppTabId,
is3DInitSignal,
}: Props) => {
useSignals();
const currentLocation = getCurrentLocationWithoutParams(
@@ -55,7 +61,7 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId, is3DInitSignal }
engine3D?.unmountEngine();
}
setAppTabId(tabId);
}
};
const currentAppTabId = appTabIdSignal.value;
const is3DInit = is3DInitSignal.value;
@@ -143,6 +149,39 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId, is3DInitSignal }
activeTab={activeLibraryTab}
onTabChange={setActiveLibraryTab}
onDownloadClicked={downloadFileFromUrl}
onAddToSceneClicked={async (url) => {
console.log("add to scene", url);
// Download the image from the URL and convert to File
const downloadImage = async (url: string): Promise<File | null> => {
try {
// Fetch the image
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.status}`);
}
// Get the blob data
const blob = await response.blob();
// Extract filename from URL or use a default name
const filename = url.split("/").pop() || "image.png";
// Create a File object from the blob
const file = new File([blob], filename, { type: blob.type });
return file;
} catch (error) {
console.error("Error downloading image:", error);
return null;
}
};
const file = await downloadImage(url);
if (file) {
console.log("file", file);
} else {
console.error("Failed to download image");
}
}}
/>
</>
);
@@ -29,6 +29,7 @@ import { PaintModeMenu } from "../../../components/ui/ToolbarMain/PaintModeMenu"
// import { EraseModeMenu } from "../../../components/ui/ToolbarMain/EraseModeMenu";
// import { BackgroundMenu } from "../../../components/ui/ToolbarMain/BackgroundMenu";
// import { bgColor } from "../../../signals/uiEvents/toolbarMain/backgroundMenu";
import { useAppUiContext } from "~/pages/Page2d/contextSignals/appUi";
export const ToolbarMain = ({
disabled = false,
@@ -147,9 +148,6 @@ export const ToolbarMain = ({
<ToolbarButton icon={faImage} buttonProps={buttonProps.ADD_IMAGE}>
<span className="text-[16px]">Add Image</span>
</ToolbarButton>
{/* <ToolbarButton icon={faFilm} buttonProps={buttonProps.ADD_VIDEO}>
Add Video
</ToolbarButton> */}
</PopoverPanel>
</Popover>
</div>
@@ -191,24 +189,6 @@ export const ToolbarMain = ({
tooltip="Paint Brush"
/>
)}
{/* Conditionally show eraser menu on mode selection */}
{/* {buttonProps.ERASER.active ? (
<EraseModeMenu
faIcon={faEraser}
borderStyle="border-2 bg-primary/30 border-2 border-primary hover:bg-primary/30 text-white"
defaultOpen={true}
anchor="right"
anchorGap={12}
closeOnMouseLeave={true}
/>
) : (
<ToolbarButton
icon={faEraser}
buttonProps={buttonProps.ERASER}
tooltip="Eraser"
/>
)} */}
</div>
</div>
@@ -1,150 +0,0 @@
// import { faEnvelope, faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
// import { Button, H1, Input, Link, P } from "~/components";
// import { addToast, authentication, signUp } from "~/signals";
// import { FormEvent, useRef, useState } from "react";
// import { AUTH_STATUS, ToastTypes } from "~/enums";
// import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
// import { useNavigate, useSearchParams } from "react-router-dom";
// const regEx =
// /^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
// export default function SignUpScreen() {
// useSignals();
// const navigate = useNavigate();
// const formRef = useRef<HTMLFormElement | null>(null);
// const [usernameError, setUsernameError] = useState("");
// const [emailError, setEmailError] = useState("");
// const [passwordError, setPasswordError] = useState("");
// const [passwordConfirmationError, setPasswordConfirmationError] =
// useState("");
// const [searchParams] = useSearchParams();
// const { status: authStatus } = authentication;
// const handleOnSubmit = (ev: FormEvent<HTMLFormElement>) => {
// ev.preventDefault();
// if (formRef.current) {
// const form = new FormData(formRef.current);
// const email = form.get("email")?.toString();
// const password = form.get("password")?.toString();
// const passwordConfirmation = form
// .get("password-confirmation")
// ?.toString();
// const username = form.get("username")?.toString();
// setUsernameError("");
// if (!username || username.length < 6) {
// setUsernameError("Username must be at least 6 characters long");
// }
// setEmailError("");
// if (!email || !email.match(regEx)) {
// setEmailError("Email must be a valid email address");
// }
// setPasswordError("");
// if (!password || password.length < 8) {
// setPasswordError("Password must be at least 8 characters long");
// }
// setPasswordConfirmationError("");
// if (!passwordError && passwordConfirmation !== password) {
// setPasswordConfirmationError("Passwords do not match");
// }
// if (
// !usernameError &&
// !passwordError &&
// !emailError &&
// !passwordConfirmationError &&
// signUp
// ) {
// signUp({
// username: username!,
// email: email!,
// password: password!,
// passwordConfirmation: passwordConfirmation!,
// }).then((error) => {
// console.log("Error occured", error);
// if (error) {
// addToast(ToastTypes.ERROR, error);
// }
// });
// }
// }
// }; // end handleOnSubmit
// useSignalEffect(() => {
// const redirectPath = searchParams.get("redirect");
// if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
// navigate(redirectPath ? redirectPath : "/");
// return;
// }
// if (authStatus.value === AUTH_STATUS.NO_ACCESS) {
// window.open("https://storyteller.ai/", "_self");
// return;
// }
// });
// return (
// <div
// className="fixed w-full overflow-scroll"
// style={{ height: "calc(100% - 72px)" }}
// >
// <div className="mx-auto my-6 w-10/12 max-w-2xl">
// <H1 className="text-center">Sign Up to Storyteller</H1>
// </div>
// <div className="mx-auto my-6 w-10/12 max-w-2xl rounded-lg border border-ui-panel-border bg-ui-panel p-6">
// <form ref={formRef} onSubmit={handleOnSubmit}>
// <Input
// label="Username"
// icon={faUser}
// placeholder="Username"
// name="username"
// errorMessage={usernameError}
// required
// />
// <br />
// <Input
// type="email"
// label="Email"
// icon={faEnvelope}
// placeholder="Email"
// name="email"
// errorMessage={emailError}
// required
// />
// <br />
// <Input
// type="password"
// label="Password"
// icon={faKey}
// name="password"
// placeholder="Password"
// autoComplete="current-password"
// errorMessage={passwordError}
// required
// />
// <br />
// <Input
// type="password"
// label="Password Confrimation"
// icon={faKey}
// placeholder="Password Confirmation"
// name="password-confirmation"
// errorMessage={passwordConfirmationError}
// required
// />
// <br />
// <br />
// <Button>Sign up</Button>
// <div className="flex gap-2">
// <P>Back to home</P>
// <Link to="/">Back</Link>
// </div>
// <br />
// <div className="flex gap-2">
// <P>Already have an account?</P>
// <Link to="/login">Log in instead</Link>
// </div>
// </form>
// </div>
// </div>
// );
// }
@@ -40,6 +40,7 @@ interface GalleryModalProps {
activeTab: string;
onTabChange: (tabId: string) => void;
onDownloadClicked?: (url: string) => Promise<void>;
onAddToSceneClicked?: (url: string) => Promise<void>;
}
export const GalleryModal = React.memo(
@@ -55,6 +56,7 @@ export const GalleryModal = React.memo(
activeTab,
onTabChange,
onDownloadClicked,
onAddToSceneClicked,
}: GalleryModalProps) => {
const [groupedItems, setGroupedItems] = useState<GroupedItems>({});
const [loading, setLoading] = useState(false);
@@ -342,6 +344,7 @@ export const GalleryModal = React.memo(
createdAt={lightboxImage?.createdAt}
downloadUrl={imageUrl}
onDownloadClicked={onDownloadClicked}
onAddToSceneClicked={onAddToSceneClicked}
/>
</>
);
@@ -16,6 +16,7 @@ interface LightboxModalProps {
additionalInfo?: React.ReactNode;
downloadUrl?: string;
onDownloadClicked?: (url: string) => Promise<void>;
onAddToSceneClicked?: (url: string) => Promise<void>;
}
export function LightboxModal({
@@ -29,10 +30,12 @@ export function LightboxModal({
additionalInfo,
downloadUrl,
onDownloadClicked,
onAddToSceneClicked,
}: LightboxModalProps) {
return createPortal(
<Transition appear show={isOpen}>
<div className="fixed inset-0 z-[100]">
{/* backdrop */}
<TransitionChild
enter="ease-out duration-300"
enterFrom="opacity-0"
@@ -46,6 +49,8 @@ export function LightboxModal({
onClick={onClose}
/>
</TransitionChild>
{/* dialog container */}
<div
className="fixed inset-0 flex items-center justify-center p-4"
onClick={onClose}
@@ -62,11 +67,15 @@ export function LightboxModal({
className="relative h-[90vh] w-[80vw] rounded-xl bg-[#2C2C2C]"
onClick={(e) => e.stopPropagation()}
>
{/* close */}
<CloseButton
onClick={onClose}
className="absolute right-4 top-4 z-10"
/>
{/* content grid */}
<div className="grid h-full grid-cols-3 gap-6">
{/* image panel */}
<div className="col-span-2 flex h-full items-center justify-center overflow-hidden rounded-l-xl bg-black/40">
{!imageUrl ? (
<div className="flex h-full w-full items-center justify-center bg-gray-800">
@@ -81,6 +90,8 @@ export function LightboxModal({
/>
)}
</div>
{/* info + actions */}
<div className="flex h-full flex-col py-5 pe-5">
<div className="flex-1 space-y-4">
<div className="text-xl font-medium">
@@ -94,30 +105,44 @@ export function LightboxModal({
)}
{additionalInfo}
</div>
{downloadUrl && (
<div className="flex justify-end">
{onDownloadClicked ? (
{/* buttons with spacing */}
{(onAddToSceneClicked && downloadUrl) || downloadUrl ? (
<div className="mt-15 mb-15 flex justify-end gap-2">
{onAddToSceneClicked && downloadUrl && (
<Button
icon={faDownToLine}
onClick={async (e) => {
e.stopPropagation();
await onDownloadClicked(downloadUrl);
await onAddToSceneClicked(downloadUrl);
}}
>
Download
Add to Current Scene
</Button>
) : (
<a
href={downloadUrl}
download
onClick={(e) => e.stopPropagation()}
className="no-underline"
>
<Button icon={faDownToLine}>Download</Button>
</a>
)}
{downloadUrl &&
(onDownloadClicked ? (
<Button
icon={faDownToLine}
onClick={async (e) => {
e.stopPropagation();
await onDownloadClicked(downloadUrl);
}}
>
Download
</Button>
) : (
<a
href={downloadUrl}
download
onClick={(e) => e.stopPropagation()}
className="no-underline"
>
<Button icon={faDownToLine}>Download</Button>
</a>
))}
</div>
)}
) : null}
</div>
</div>
</div>