mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Add scene to 2d Lightbox modal.
This commit is contained in:
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");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
+1
-21
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user