diff --git a/app/components/ButtonDropdown.tsx b/app/components/ButtonDropdown.tsx index 8dd6f7eddf..d80f977223 100644 --- a/app/components/ButtonDropdown.tsx +++ b/app/components/ButtonDropdown.tsx @@ -9,7 +9,6 @@ import { Button, ButtonPropsI } from "./Button"; import { TransitionDialogue } from "~/components/TransitionDialogue"; type UnionedButtonProps = { label?: string } & ButtonPropsI; -type ModalPropsType = ButtonDropdownProps["options"][number]["dialogProps"]; interface ButtonDropdownProps { label: string; @@ -27,6 +26,7 @@ interface ButtonDropdownProps { confirmButtonProps?: UnionedButtonProps; closeButtonProps?: UnionedButtonProps; showClose?: boolean; + onClose?: () => void; }; }>; } @@ -37,20 +37,31 @@ export const ButtonDropdown = ({ icon, }: ButtonDropdownProps) => { const [isOpen, setIsOpen] = useState(false); - const [modalProps, setModalProps] = useState(null); + const [selectedOptionIndex, setSelectedOptionIndex] = useState( + null, + ); - const closeModal = () => setIsOpen(false); + const closeModal = () => { + setIsOpen(false); + options[selectedOptionIndex!].dialogProps?.onClose?.(); + }; - const handleOptionClick = (option) => { + const handleOptionClick = (index: number) => { + const option = options[index]; if (option.onClick) { option.onClick(); } if (option.dialogProps) { - setModalProps(option.dialogProps); + setSelectedOptionIndex(index); setIsOpen(true); } }; + const currentDialogProps = + selectedOptionIndex !== null + ? options[selectedOptionIndex].dialogProps + : null; + return (
@@ -84,7 +95,7 @@ export const ButtonDropdown = ({ className={`duration-50 bg-brand-secondary font-medium text-white transition-all ${ active ? "bg-ui-controls-button/60" : "" } ${option.disabled ? "pointer-events-none opacity-40" : ""} group flex w-full items-center py-1.5 pl-7 pr-4 text-sm`.trim()} - onClick={() => handleOptionClick(option)}> + onClick={() => handleOptionClick(index)}>
{option.label}
@@ -101,34 +112,35 @@ export const ButtonDropdown = ({
- {modalProps && ( + {currentDialogProps && ( - {modalProps.content} + className={currentDialogProps.className}> + {currentDialogProps.content}
- {modalProps.showClose !== false && modalProps.closeButtonProps && ( - - )} + {currentDialogProps.showClose !== false && + currentDialogProps.closeButtonProps && ( + + )} - {modalProps.confirmButtonProps && ( + {currentDialogProps.confirmButtonProps && ( )}
diff --git a/app/components/TransitionDialogue.tsx b/app/components/TransitionDialogue.tsx index 122a295546..a8fb61efdc 100644 --- a/app/components/TransitionDialogue.tsx +++ b/app/components/TransitionDialogue.tsx @@ -1,6 +1,5 @@ -import { useEffect } from "react"; +import { useEffect, Fragment, ReactNode } from "react"; import { Dialog, Transition } from "@headlessui/react"; -import { Fragment, ReactNode } from "react"; import { twMerge } from "tailwind-merge"; import { disableHotkeyInput, diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx index 3ca9525d87..37ef750f87 100644 --- a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx @@ -8,6 +8,8 @@ import { ToasterContext } from "~/contexts/ToasterContext"; import { TestFeaturesButtons } from "./TestFeaturesButtons"; import { Help } from "./Help"; import { faFile } from "@fortawesome/pro-solid-svg-icons"; +import { LoadScene } from "./LoadScene"; +import { NewSceneFromTemplate } from "./NewSceneFromTemplate"; export const ControlsTopButtons = () => { const editorEngine = useContext(EngineContext); @@ -38,6 +40,11 @@ export const ControlsTopButtons = () => { }); }; + const handleSceneSelection = (token: string) => { + setSceneToken(token); + console.log(`Selected Scene Token is ${token}`); + }; + return (
@@ -53,9 +60,11 @@ export const ControlsTopButtons = () => { content: ( { - setSceneToken(e.target.value); + setSceneName(e.target.value); }} + autoComplete="false" /> ), confirmButtonProps: { @@ -67,6 +76,27 @@ export const ControlsTopButtons = () => { label: "Cancel", }, showClose: true, + onClose: () => setSceneName(""), + }, + }, + { + label: "New scene from template...", + description: "Ctrl+Shift+N", + dialogProps: { + title: "Create a New Scene from Template", + content: ( + + ), + confirmButtonProps: { + label: "Create", + disabled: sceneToken === "", + onClick: handleButtonLoadScene, + }, + closeButtonProps: { + label: "Cancel", + }, + showClose: true, + className: "max-w-7xl w-auto", }, }, { @@ -74,14 +104,7 @@ export const ControlsTopButtons = () => { description: "Ctrl+O", dialogProps: { title: "Load a Scene", - content: ( - { - setSceneToken(e.target.value); - }} - /> - ), + content: , confirmButtonProps: { label: "Load", disabled: sceneToken === "", @@ -91,10 +114,11 @@ export const ControlsTopButtons = () => { label: "Cancel", }, showClose: true, + className: "max-w-7xl w-auto", }, }, - { + disabled: true, // save scene should be disabled if there are no changes label: "Save scene", description: "Ctrl+S", dialogProps: { @@ -119,7 +143,6 @@ export const ControlsTopButtons = () => { }, divider: true, }, - { label: "Save scene as copy", description: "Ctrl+Shift+S", diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/LoadScene/LoadScene.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/LoadScene/LoadScene.tsx new file mode 100644 index 0000000000..1ea75f1beb --- /dev/null +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/LoadScene/LoadScene.tsx @@ -0,0 +1,147 @@ +import { useEffect, useRef, useState } from "react"; +import { ScenePicker, SceneTypes } from "../ScenePicker"; +import { Label } from "~/components"; + +interface LoadSceneProps { + onSceneSelect: (token: string) => void; +} + +export const LoadScene = ({ onSceneSelect }: LoadSceneProps) => { + // Dummy Data - replace with API data + const dummyScenes: SceneTypes[] = [ + { + token: "111", + name: "Dragon", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "222", + name: "Dancing Girl", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "333", + name: "Dragon", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "444", + name: "Home Office", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "555", + name: "Dragon", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "666", + name: "Dancing Girl", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "777", + name: "Dragon", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "888", + name: "Dancing Girl", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "999", + name: "Dragon", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "000", + name: "Home Office", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "0001", + name: "Home Office", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "0002", + name: "Home Office", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "0003", + name: "Home Office", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "0004", + name: "Home Office", + updated_at: "Apr 03, 2024 14:24:48", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + ]; + + const handleSceneSelect = (selectedScene: SceneTypes) => { + onSceneSelect(selectedScene.token); + }; + const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1); + const scrollContainerRef = useRef(null); + + const handleScroll = () => { + const element = scrollContainerRef.current; + if (element) { + const atBottom = + element.scrollHeight - element.scrollTop === element.clientHeight; + const hasOverflow = element.scrollHeight > element.clientHeight; + + setBottomGradientOpacity(hasOverflow && !atBottom ? 1 : 0); + } + }; + + useEffect(() => { + const element = scrollContainerRef.current; + if (element) { + handleScroll(); + element.addEventListener("scroll", handleScroll); + + return () => { + element.removeEventListener("scroll", handleScroll); + }; + } + }, []); + + return ( +
+ +
+
+ +
+
+
+
+ ); +}; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/LoadScene/index.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/LoadScene/index.tsx new file mode 100644 index 0000000000..c9c9d549fd --- /dev/null +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/LoadScene/index.tsx @@ -0,0 +1 @@ +export { LoadScene } from "./LoadScene"; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx new file mode 100644 index 0000000000..6d6659ac73 --- /dev/null +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/NewSceneFromTemplate/NewSceneFromTemplate.tsx @@ -0,0 +1,69 @@ +import { useEffect, useRef, useState } from "react"; +import { ScenePicker, SceneTypes } from "../ScenePicker"; + +interface NewSceneFromTemplateProps { + onSceneSelect: (token: string) => void; +} + +export const NewSceneFromTemplate = ({ + onSceneSelect, +}: NewSceneFromTemplateProps) => { + // Dummy Data - replace with API data + const dummyScenes: SceneTypes[] = [ + { + token: "m_9pf7a7v0138zx58f4x6ejsehjcvfq6", + name: "Dragon", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + { + token: "m_nmzvdqr6kr8eqpmxqdzkqj0yknrjwv", + name: "Dancing Girl", + thumbnail: "/resources/placeholders/scene_placeholder.png", + }, + ]; + + const handleSceneSelect = (selectedScene: SceneTypes) => { + onSceneSelect(selectedScene.token); + }; + const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1); + const scrollContainerRef = useRef(null); + + const handleScroll = () => { + const element = scrollContainerRef.current; + if (element) { + const atBottom = + element.scrollHeight - element.scrollTop === element.clientHeight; + const hasOverflow = element.scrollHeight > element.clientHeight; + + setBottomGradientOpacity(hasOverflow && !atBottom ? 1 : 0); + } + }; + + useEffect(() => { + const element = scrollContainerRef.current; + if (element) { + handleScroll(); + element.addEventListener("scroll", handleScroll); + + return () => { + element.removeEventListener("scroll", handleScroll); + }; + } + }, []); + + return ( +
+
+
+ +
+
+
+
+ ); +}; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/NewSceneFromTemplate/index.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/NewSceneFromTemplate/index.tsx new file mode 100644 index 0000000000..446debe4ef --- /dev/null +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/NewSceneFromTemplate/index.tsx @@ -0,0 +1 @@ +export { NewSceneFromTemplate } from "./NewSceneFromTemplate"; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ScenePicker/ScenePicker.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ScenePicker/ScenePicker.tsx new file mode 100644 index 0000000000..ba30c0bec7 --- /dev/null +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ScenePicker/ScenePicker.tsx @@ -0,0 +1,84 @@ +import React, { useEffect, useState } from "react"; +import { twMerge } from "tailwind-merge"; + +export type SceneTypes = { + token: string; + name: string; + updated_at?: string; + thumbnail: string; +}; + +interface ScenePickerProps { + scenes: SceneTypes[]; + onSceneSelect: (selectedScene: SceneTypes) => void; + showDate?: boolean; +} + +export const ScenePicker: React.FC = ({ + scenes, + onSceneSelect, + showDate, +}) => { + const [selectedSceneId, setSelectedSceneId] = useState(null); + + useEffect(() => { + if (scenes.length > 0 && !selectedSceneId) { + setSelectedSceneId(scenes[0].token); + onSceneSelect(scenes[0]); // Trigger the callback with the first scene + } + }, [scenes, onSceneSelect, selectedSceneId]); + + const handleSelected = (scene: SceneTypes) => { + setSelectedSceneId(scene.token); + onSceneSelect(scene); + }; + + return ( +
+ {scenes.map((scene) => ( + + ))} +
+ ); +}; diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ScenePicker/index.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ScenePicker/index.tsx new file mode 100644 index 0000000000..a607b4e1d4 --- /dev/null +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ScenePicker/index.tsx @@ -0,0 +1,2 @@ +export { ScenePicker } from "./ScenePicker"; +export type { SceneTypes } from "./ScenePicker"; diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/ItemPicker.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/ItemPicker.tsx index b38e1db8d8..81640dbf11 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/ItemPicker.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/itemTabs/StylizeTab/ItemPicker.tsx @@ -27,7 +27,9 @@ export const ItemPicker = ({