mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 01:38:44 +00:00
many asset modal changes and sidebar removal
This commit is contained in:
1 parent
534b362cc0
commit
66d5095f69
8 files changed
+133
-36
No files matched your search
@@ -13,6 +13,8 @@ import {
|
||||
overTimeline,
|
||||
sidePanelWidth,
|
||||
timelineHeight,
|
||||
assetModalVisibleDuringDrag,
|
||||
reopenAfterDragSignal,
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
import { pageHeight, pageWidth } from "~/signals";
|
||||
import { addShape } from "~/pages/PageEnigma/signals/shape";
|
||||
@@ -42,6 +44,7 @@ class DndAsset {
|
||||
this.initY = event.pageY;
|
||||
canDrop.value = false;
|
||||
this.notDropText = "";
|
||||
assetModalVisibleDuringDrag.value = false;
|
||||
window.addEventListener("pointerup", this.onPointerUp);
|
||||
window.addEventListener("pointermove", this.onPointerMove);
|
||||
}
|
||||
@@ -54,6 +57,14 @@ class DndAsset {
|
||||
this.overElement = null;
|
||||
overTimeline.value = false;
|
||||
this.notDropText = "";
|
||||
if (reopenAfterDragSignal.value) {
|
||||
assetModalVisibleDuringDrag.value = true;
|
||||
} else {
|
||||
assetModalVisibleDuringDrag.value = true;
|
||||
setTimeout(() => {
|
||||
assetModalVisibleDuringDrag.value = false;
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
import { EditorCanvas } from "./comps/EngineCanvases";
|
||||
import { SceneContainer } from "./comps/SceneContainer";
|
||||
import { AspectRatioMenu } from "./comps/AspectRatioMenu";
|
||||
import { Outliner } from "./comps/Outliner";
|
||||
import { CameraAspectRatio } from "./enums";
|
||||
import { PromptBox } from "./comps/PromptBox";
|
||||
@@ -95,7 +94,6 @@ export const PageEditor = () => {
|
||||
className="flex"
|
||||
style={{
|
||||
height,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<div className="relative w-full overflow-hidden bg-transparent">
|
||||
@@ -136,10 +134,12 @@ export const PageEditor = () => {
|
||||
<Outliner />
|
||||
<PreviewEngineCamera />
|
||||
</div>
|
||||
<PromptBox />
|
||||
|
||||
<ControlPanelSceneObject />
|
||||
</div>
|
||||
|
||||
<PromptBox />
|
||||
|
||||
<LoadingDots
|
||||
className="absolute left-0 top-0"
|
||||
isShowing={editorLoader.value.isShowing}
|
||||
@@ -150,9 +150,9 @@ export const PageEditor = () => {
|
||||
</div>
|
||||
|
||||
{/* Side panel */}
|
||||
<div onClick={handleOverlayClick}>
|
||||
{/* <div onClick={handleOverlayClick}>
|
||||
<SidePanel />
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { faCube, faMagicWandSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, Tooltip } from "~/components";
|
||||
import { useState } from "react";
|
||||
import { AssetModal } from "./AssetModal";
|
||||
import { assetModalVisibleDuringDrag } from "../../signals";
|
||||
|
||||
export const AssetMenu = () => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -11,6 +12,11 @@ export const AssetMenu = () => {
|
||||
console.log("Adding asset...");
|
||||
};
|
||||
|
||||
const handleOpenModal = () => {
|
||||
assetModalVisibleDuringDrag.value = true;
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="glass absolute left-2 top-1/2 flex -translate-y-1/2 flex-col gap-1 rounded-lg p-1">
|
||||
@@ -18,7 +24,7 @@ export const AssetMenu = () => {
|
||||
<Button
|
||||
icon={faCube}
|
||||
className="h-12 w-12 text-lg"
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
onClick={handleOpenModal}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
@@ -31,7 +37,7 @@ export const AssetMenu = () => {
|
||||
className="h-12 w-12 text-lg"
|
||||
variant="secondary"
|
||||
disabled={true}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
onClick={handleOpenModal}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -3,16 +3,30 @@ import {
|
||||
faPlus,
|
||||
faSearch,
|
||||
faChevronRight,
|
||||
faChevronLeft,
|
||||
faLayerGroup,
|
||||
faUser,
|
||||
faSun,
|
||||
faTree,
|
||||
faCube,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Button, CloseButton, Input, Tooltip } from "~/components";
|
||||
import { TabSelector } from "~/components/reusable/TabSelector";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useState, useEffect, useMemo, useRef } from "react";
|
||||
import { ItemElements } from "../SidePanelTabs/sharedComps/ItemElements";
|
||||
import { demoSkyboxItems, demoShapeItems } from "../../signals";
|
||||
import {
|
||||
demoSkyboxItems,
|
||||
demoShapeItems,
|
||||
demoCharacterItems,
|
||||
assetModalVisibleDuringDrag,
|
||||
reopenAfterDragSignal,
|
||||
} from "../../signals";
|
||||
import { MediaItem } from "../../models";
|
||||
import { useUserObjects, useFeaturedObjects } from "../SidePanelTabs/hooks";
|
||||
import { FilterEngineCategories } from "~/enums";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
interface AssetModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -23,6 +37,7 @@ interface AssetModalProps {
|
||||
type AssetTab = {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: typeof faLayerGroup;
|
||||
engineCategory?: FilterEngineCategories;
|
||||
items: MediaItem[];
|
||||
};
|
||||
@@ -36,9 +51,9 @@ const AllTabSection = ({
|
||||
items: MediaItem[];
|
||||
onViewAll: () => void;
|
||||
}) => (
|
||||
<div className="mb-8">
|
||||
<div className="mb-0">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="text-lg font-medium opacity-90">{label}</h3>
|
||||
<h3 className="text-md font-semibold opacity-90">{label}</h3>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1 px-2 py-1 text-sm"
|
||||
@@ -48,7 +63,7 @@ const AllTabSection = ({
|
||||
<FontAwesomeIcon icon={faChevronRight} className="text-xs opacity-70" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-[200px]">
|
||||
<div className="h-[170px]">
|
||||
<ItemElements
|
||||
items={items.slice(0, 4)}
|
||||
busy={false}
|
||||
@@ -63,9 +78,27 @@ export const AssetModal = ({
|
||||
onClose,
|
||||
onAddAsset,
|
||||
}: AssetModalProps) => {
|
||||
useSignals();
|
||||
const [activeLibraryTab, setActiveLibraryTab] = useState("library");
|
||||
const [activeAssetTab, setActiveAssetTab] = useState("all");
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [reopenAfterAdd, setReopenAfterAdd] = useState(true);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Update the signal when the preference changes
|
||||
useEffect(() => {
|
||||
reopenAfterDragSignal.value = reopenAfterAdd;
|
||||
}, [reopenAfterAdd]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
// Small delay to ensure the modal is fully mounted
|
||||
const timer = setTimeout(() => {
|
||||
searchInputRef.current?.focus();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// Fetch objects for different categories
|
||||
const { userObjects: userCharacters } = useUserObjects({
|
||||
@@ -103,25 +136,29 @@ export const AssetModal = ({
|
||||
{ id: "mine", label: "Mine" },
|
||||
];
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const assetTabs: AssetTab[] = [
|
||||
{ id: "all", label: "All", items: [] },
|
||||
{ id: "all", label: "All", icon: faLayerGroup, items: [] },
|
||||
{
|
||||
id: "character",
|
||||
label: "Character",
|
||||
icon: faUser,
|
||||
engineCategory: FilterEngineCategories.CHARACTER,
|
||||
items:
|
||||
activeLibraryTab === "library"
|
||||
? (featuredCharacters ?? [])
|
||||
? [...demoCharacterItems.value, ...(featuredCharacters ?? [])]
|
||||
: (userCharacters ?? []),
|
||||
},
|
||||
{
|
||||
id: "skybox",
|
||||
label: "Skybox",
|
||||
icon: faSun,
|
||||
items: demoSkyboxItems.value,
|
||||
},
|
||||
{
|
||||
id: "nature",
|
||||
label: "Nature",
|
||||
icon: faTree,
|
||||
engineCategory: FilterEngineCategories.SET_DRESSING,
|
||||
items:
|
||||
activeLibraryTab === "library"
|
||||
@@ -131,6 +168,7 @@ export const AssetModal = ({
|
||||
{
|
||||
id: "objects",
|
||||
label: "Objects",
|
||||
icon: faCube,
|
||||
engineCategory: FilterEngineCategories.OBJECT,
|
||||
items:
|
||||
activeLibraryTab === "library"
|
||||
@@ -171,12 +209,13 @@ export const AssetModal = ({
|
||||
if (searchTerm && activeAssetTab !== "all") {
|
||||
setActiveAssetTab("all");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [searchTerm]);
|
||||
|
||||
const renderContent = () => {
|
||||
if (activeAssetTab === "all" && !searchTerm) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="h-full space-y-2 overflow-y-auto">
|
||||
{assetTabs.slice(1).map((tab) => (
|
||||
<AllTabSection
|
||||
key={tab.id}
|
||||
@@ -198,43 +237,72 @@ export const AssetModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
const handleAddAsset = () => {
|
||||
onAddAsset();
|
||||
if (reopenAfterAdd) {
|
||||
// Small delay to allow the modal to close and reopen
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 100);
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionDialogue
|
||||
isOpen={isOpen}
|
||||
isOpen={isOpen && assetModalVisibleDuringDrag.value}
|
||||
onClose={onClose}
|
||||
className="h-[640px] max-w-5xl"
|
||||
className="h-[640px] max-w-4xl"
|
||||
childPadding={false}
|
||||
>
|
||||
<div className="grid h-full grid-cols-12 gap-3">
|
||||
<div className="relative col-span-3 p-3 pt-2 after:absolute after:right-0 after:top-0 after:h-full after:w-px after:bg-gray-200 after:dark:bg-white/10">
|
||||
<div className="relative col-span-3 flex h-full flex-col p-3 pt-2 after:absolute after:right-0 after:top-0 after:h-full after:w-px after:bg-gray-200 after:dark:bg-white/10">
|
||||
<div className="flex items-center justify-between gap-2.5 py-0.5">
|
||||
<h2 className="text-[18px] font-semibold opacity-80">3D Assets</h2>
|
||||
<Tooltip content="Upload model" position="top" delay={200}>
|
||||
<Button
|
||||
className="h-6 w-6 rounded-full border-none bg-transparent text-white/70 transition-colors hover:bg-transparent hover:text-white/100"
|
||||
onClick={onAddAsset}
|
||||
onClick={handleAddAsset}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="text-xl" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<hr className="my-2 w-full border-white/10" />
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-2">
|
||||
{assetTabs.map((tab) => (
|
||||
<Button
|
||||
key={tab.id}
|
||||
variant={activeAssetTab === tab.id ? "primary" : "secondary"}
|
||||
className="w-full justify-start text-left"
|
||||
className={twMerge(
|
||||
"w-full justify-start rounded-xl border border-white/[2%] bg-white/[4%] px-3.5 py-2.5 text-left hover:bg-white/15",
|
||||
activeAssetTab === tab.id &&
|
||||
"border-brand-primary bg-brand-primary/10 hover:bg-brand-primary/10",
|
||||
)}
|
||||
onClick={() => setActiveAssetTab(tab.id)}
|
||||
>
|
||||
<FontAwesomeIcon icon={tab.icon} className="mr-2 opacity-70" />
|
||||
{tab.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-auto flex items-center gap-2 pt-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="reopen-after-add"
|
||||
checked={reopenAfterAdd}
|
||||
onChange={(e) => setReopenAfterAdd(e.target.checked)}
|
||||
className="h-4 w-4 rounded-lg border-gray-300 bg-gray-700 text-brand-primary focus:ring-brand-primary"
|
||||
/>
|
||||
<label htmlFor="reopen-after-add" className="text-sm text-white/70">
|
||||
Reopen after adding
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-9 p-3 ps-0 pt-2">
|
||||
<div className="col-span-9 p-3 pb-0 ps-0 pt-2">
|
||||
<div className="flex h-full flex-col">
|
||||
<div>
|
||||
<div className="h-full">
|
||||
<div className="flex items-center gap-4">
|
||||
<TabSelector
|
||||
tabs={libraryTabs}
|
||||
@@ -243,6 +311,7 @@ export const AssetModal = ({
|
||||
className="w-auto"
|
||||
/>
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
placeholder="Search"
|
||||
className="grow"
|
||||
icon={faSearch}
|
||||
@@ -251,13 +320,25 @@ export const AssetModal = ({
|
||||
/>
|
||||
<CloseButton onClick={onClose} />
|
||||
</div>
|
||||
<div className="mt-4 h-[480px] overflow-y-auto">
|
||||
<div className="mt-4 h-[574px] overflow-hidden">
|
||||
{activeAssetTab !== "all" && !searchTerm && (
|
||||
<div className="mb-3 flex items-center font-semibold">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex items-center gap-2 border-none bg-transparent px-3 py-1.5 text-sm text-white/70 hover:bg-transparent hover:text-white/100"
|
||||
onClick={() => setActiveAssetTab("all")}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronLeft}
|
||||
className="text-sm font-semibold opacity-70"
|
||||
/>
|
||||
</Button>
|
||||
{currentTab.label}
|
||||
</div>
|
||||
)}
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-auto flex justify-end pt-4">
|
||||
<Button onClick={onClose}>Done</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useCallback, useContext } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { pageHeight, pageWidth } from "~/signals";
|
||||
import {
|
||||
editorLetterBox,
|
||||
timelineHeight,
|
||||
sidePanelWidth,
|
||||
sidePanelVisible,
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
import { editorLetterBox, timelineHeight } from "~/pages/PageEnigma/signals";
|
||||
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
|
||||
import { Letterbox } from "./Letterbox";
|
||||
|
||||
@@ -14,8 +9,7 @@ export const SceneContainer = ({ children }: { children: React.ReactNode }) => {
|
||||
useSignals();
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const containerWidth =
|
||||
pageWidth.value - (sidePanelVisible.value ? sidePanelWidth.value : 0) - 84;
|
||||
const containerWidth = pageWidth.value;
|
||||
|
||||
const containerHeight = pageHeight.value - timelineHeight.value - 64;
|
||||
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ export const ItemElement = ({ item }: Props) => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="pointer-events-none w-full select-none truncate py-1.5 text-start text-[13px] transition-all duration-200 ">
|
||||
<div className="pointer-events-none w-full select-none truncate py-1.5 text-start text-[13px] text-white/80 transition-all duration-200">
|
||||
{item.name || item.media_id}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { signal } from "@preact/signals-react";
|
||||
|
||||
export const assetModalVisibleDuringDrag = signal(true);
|
||||
export const reopenAfterDragSignal = signal(true);
|
||||
@@ -18,3 +18,4 @@ export * from "./stylizeTab";
|
||||
export * from "./timing";
|
||||
export * from "./timeline";
|
||||
export * from "./error";
|
||||
export * from "./assetModal";
|
||||
Reference in new issue
Block a user