mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-11 20:32:12 +00:00
fixed characters upload and gallery dnd + added 3d meme models (#827)
- added 6 memes models and custom thumbnails - upload modal now has checkbox to allow character uploads - gallery drag and drop now works properly for characters
This commit is contained in:
1 parent
5093361f2c
commit
0163504aad
15 files changed
+154
-26
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -82,6 +82,18 @@ export const UploadFiles3D = ({
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Reset subtype when fileSubtypes set changes (e.g., toggling character)
|
||||
if (!fileSubtypes || fileSubtypes.length === 0) {
|
||||
setSubtype(undefined);
|
||||
return;
|
||||
}
|
||||
const nextDefault = Object.values(fileSubtypes[0])[0] as
|
||||
| MediaFileAnimationType
|
||||
| undefined;
|
||||
setSubtype(nextDefault);
|
||||
}, [fileSubtypes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (canvasRef.current && assetFile.value) {
|
||||
loadPreviewOnCanvas({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { UploadAssetError, UploadSuccess } from "@storyteller/ui-upload-modal";
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FilterEngineCategories,
|
||||
UploaderStates,
|
||||
OBJECT_FILE_TYPE,
|
||||
MediaFileAnimationType,
|
||||
} from "../../../enums";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
@@ -34,9 +35,26 @@ export function UploadModal3D(props: Props) {
|
||||
const { isOpen, onClose, onSuccess, title, titleIcon, options } = props;
|
||||
const [uploaderState, setUploaderState] =
|
||||
useState<UploaderState>(initialUploaderState);
|
||||
const [isCharacter, setIsCharacter] = useState(false);
|
||||
|
||||
// Category fixed to OBJECT
|
||||
const selectedCategory = FilterEngineCategories.OBJECT;
|
||||
const selectedCategory = isCharacter
|
||||
? FilterEngineCategories.CHARACTER
|
||||
: FilterEngineCategories.OBJECT;
|
||||
|
||||
const characterAnimationOptions = useMemo(() => {
|
||||
if (!isCharacter) return undefined;
|
||||
const values = Object.values(MediaFileAnimationType);
|
||||
const sorted = values.sort((a, b) =>
|
||||
a === MediaFileAnimationType.MixamoArKit
|
||||
? -1
|
||||
: b === MediaFileAnimationType.MixamoArKit
|
||||
? 1
|
||||
: a.localeCompare(b),
|
||||
);
|
||||
const toLabel = (v: string) =>
|
||||
v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
return sorted.map((v) => ({ [toLabel(v)]: v }));
|
||||
}, [isCharacter]);
|
||||
|
||||
const updateUploaderState = (newState: UploaderState) => {
|
||||
setUploaderState(newState);
|
||||
@@ -49,6 +67,7 @@ export function UploadModal3D(props: Props) {
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
resetModalState();
|
||||
setIsCharacter(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
@@ -69,11 +88,23 @@ export function UploadModal3D(props: Props) {
|
||||
case UploaderStates.ready:
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
id="upload-as-character"
|
||||
type="checkbox"
|
||||
checked={isCharacter}
|
||||
onChange={(e) => setIsCharacter(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="upload-as-character">Upload as Character</label>
|
||||
</div>
|
||||
<UploadFiles3D
|
||||
title={title}
|
||||
engineCategory={selectedCategory}
|
||||
fileTypes={objectFileTypes}
|
||||
options={options}
|
||||
options={{
|
||||
...(options ?? {}),
|
||||
fileSubtypes: characterAnimationOptions,
|
||||
}}
|
||||
onClose={onClose}
|
||||
onUploadProgress={updateUploaderState}
|
||||
/>
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ export const uploadAsset = async ({
|
||||
fileName: file.name,
|
||||
engine_category: engineCategory,
|
||||
maybe_title: title,
|
||||
maybe_animation_type: animationType,
|
||||
uuid: uuidv4(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
} from "~/components/signaled/TopBar/TopBar";
|
||||
import { uploadPlaneFromMediaToken } from "~/components/reusable/UploadModalMedia/uploadPlane";
|
||||
import { addObject } from "./signals/objectGroup/addObject";
|
||||
import { addCharacter } from "./signals/characterGroups/addCharacter";
|
||||
import { AssetType, AUTH_STATUS } from "~/enums";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { MediaItem } from "~/pages/PageEnigma/models";
|
||||
@@ -77,7 +78,7 @@ import { useTabStore } from "../Stores/TabState";
|
||||
import PageEdit from "../PageEdit/PageEdit";
|
||||
import { ImageModel } from "@storyteller/model-list";
|
||||
|
||||
const PAGE_ID : ModelPage = ModelPage.Stage3D;
|
||||
const PAGE_ID: ModelPage = ModelPage.Stage3D;
|
||||
|
||||
export const PageEditor = () => {
|
||||
useSignals();
|
||||
@@ -103,7 +104,8 @@ export const PageEditor = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const selectedImageModel : ImageModel | undefined = getSelectedImageModel(PAGE_ID);
|
||||
const selectedImageModel: ImageModel | undefined =
|
||||
getSelectedImageModel(PAGE_ID);
|
||||
|
||||
const height =
|
||||
dndTimelineHeight.value > -1
|
||||
@@ -305,11 +307,12 @@ export const PageEditor = () => {
|
||||
try {
|
||||
if (item.mediaClass === "dimensional") {
|
||||
// Handle 3D models
|
||||
const isCharacter = item.assetType === "character";
|
||||
const mediaItem: MediaItem = {
|
||||
version: 1,
|
||||
type: AssetType.OBJECT,
|
||||
type: isCharacter ? AssetType.CHARACTER : AssetType.OBJECT,
|
||||
media_id: item.id || uuidv4(),
|
||||
name: item.label || "3D Object",
|
||||
name: item.label || (isCharacter ? "Character" : "3D Object"),
|
||||
...(worldPosition && {
|
||||
position: {
|
||||
x: worldPosition.x,
|
||||
@@ -318,7 +321,11 @@ export const PageEditor = () => {
|
||||
},
|
||||
}),
|
||||
};
|
||||
addObject(mediaItem);
|
||||
if (isCharacter) {
|
||||
addCharacter(mediaItem);
|
||||
} else {
|
||||
addObject(mediaItem);
|
||||
}
|
||||
} else {
|
||||
// Handle images (create image planes)
|
||||
const mediaItem: MediaItem = {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
faChevronRight,
|
||||
faMountainCity,
|
||||
faDog,
|
||||
faFaceGrinStars,
|
||||
faUpFromLine,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
demoSkyboxItems,
|
||||
demoShapeItems,
|
||||
demoCharacterItems,
|
||||
demoMemeItems,
|
||||
assetModalVisibleDuringDrag,
|
||||
reopenAfterDragSignal,
|
||||
assetModalVisible,
|
||||
@@ -246,10 +248,10 @@ export const AssetModal = () => {
|
||||
featuredImagePlanesFetchStatus,
|
||||
]);
|
||||
|
||||
const libraryTabs = [
|
||||
{ id: "library", label: "Library" },
|
||||
{ id: "mine", label: "Mine" },
|
||||
];
|
||||
// const libraryTabs = [
|
||||
// { id: "library", label: "Library" },
|
||||
// { id: "mine", label: "Mine" },
|
||||
// ];
|
||||
|
||||
const assetTabs = useMemo<AssetTab[]>(
|
||||
() => [
|
||||
@@ -276,6 +278,14 @@ export const AssetModal = () => {
|
||||
? [...demoShapeItems.value, ...(featuredObjects ?? [])]
|
||||
: (userObjects ?? []),
|
||||
},
|
||||
{
|
||||
id: "memes",
|
||||
label: "Memes",
|
||||
labelSingle: "Meme",
|
||||
icon: faFaceGrinStars,
|
||||
engineCategory: FilterEngineCategories.CHARACTER,
|
||||
items: activeLibraryTab === "library" ? demoMemeItems.value : [],
|
||||
},
|
||||
{
|
||||
id: "sets",
|
||||
label: "Sets",
|
||||
@@ -323,6 +333,7 @@ export const AssetModal = () => {
|
||||
demoCharacterItems.value,
|
||||
demoShapeItems.value,
|
||||
demoSkyboxItems.value,
|
||||
demoMemeItems.value,
|
||||
featuredCharacters,
|
||||
featuredCreatures,
|
||||
featuredObjects,
|
||||
@@ -426,19 +437,19 @@ export const AssetModal = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleAddAsset = () => {
|
||||
handleClose();
|
||||
setIsUploadModalOpen(true);
|
||||
setSelectedCategory(null);
|
||||
setIsSelectVisible(true);
|
||||
};
|
||||
// const handleAddAsset = () => {
|
||||
// handleClose();
|
||||
// setIsUploadModalOpen(true);
|
||||
// setSelectedCategory(null);
|
||||
// setIsSelectVisible(true);
|
||||
// };
|
||||
|
||||
const handleAddSpecificAsset = (category: FilterEngineCategories) => {
|
||||
handleClose();
|
||||
setIsUploadModalOpen(true);
|
||||
setSelectedCategory(category);
|
||||
setIsSelectVisible(false);
|
||||
};
|
||||
// const handleAddSpecificAsset = (category: FilterEngineCategories) => {
|
||||
// handleClose();
|
||||
// setIsUploadModalOpen(true);
|
||||
// setSelectedCategory(category);
|
||||
// setIsSelectVisible(false);
|
||||
// };
|
||||
|
||||
const handleUploadSuccess = (category: FilterEngineCategories) => {
|
||||
if (reopenAfterDragSignal.value) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
import { MediaItem } from "~/pages/PageEnigma/models";
|
||||
import { AssetType } from "~/enums";
|
||||
|
||||
export const demoMemeItems = signal<MediaItem[]>([
|
||||
{
|
||||
version: 1,
|
||||
media_id: "m_yb02j1b0dn6ashn14ap2e2vnvy1jhz",
|
||||
media_type: "glb",
|
||||
type: AssetType.CHARACTER,
|
||||
name: "Toothless",
|
||||
thumbnail: "/resources/memes/toothless.jpg",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: "m_jgrzn7f66dvvw90qqttjn3m0dk8gef",
|
||||
media_type: "glb",
|
||||
type: AssetType.CHARACTER,
|
||||
name: "Doge",
|
||||
thumbnail: "/resources/memes/doge.jpg",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: "m_h8gk5m59252nbza07p08dyn1fnhnsf",
|
||||
media_type: "glb",
|
||||
type: AssetType.CHARACTER,
|
||||
name: "Grimace",
|
||||
thumbnail: "/resources/memes/grimace.jpg",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: "m_pzjn9583k1hqcetcfv1a2y04hkaspq",
|
||||
media_type: "glb",
|
||||
type: AssetType.CHARACTER,
|
||||
name: "Roblox",
|
||||
thumbnail: "/resources/memes/roblox.jpg",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: "m_xh0aj0wmdhvyf0pv5jj1wj3ap7p1rf",
|
||||
media_type: "glb",
|
||||
type: AssetType.CHARACTER,
|
||||
name: "JD Vance",
|
||||
thumbnail: "/resources/memes/jd_vance.jpg",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
media_id: "m_v8mkb44wy4r1ed7g5z8bq8n6bynerg",
|
||||
media_type: "glb",
|
||||
type: AssetType.CHARACTER,
|
||||
name: "Lil JD",
|
||||
thumbnail: "/resources/memes/lil_jd.jpg",
|
||||
},
|
||||
]);
|
||||
+1
-1
@@ -8,7 +8,7 @@ export const demoSkyboxItems = signal<MediaItem[]>([
|
||||
media_id: "SKY::m_1",
|
||||
type: AssetType.SKYBOX,
|
||||
name: "Empty Skybox",
|
||||
thumbnail: "/resources/skybox/empty.jpg",
|
||||
thumbnail: "/resources/placeholders/placeholder.png",
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from "./demoAssets";
|
||||
export * from "./demoAudioItems";
|
||||
export * from "./demoCharacterItems";
|
||||
export * from "./demoSkyboxItems";
|
||||
export * from "./demoMemeItems";
|
||||
@@ -50,6 +50,7 @@ export interface GalleryItem {
|
||||
fullImage?: string | null;
|
||||
createdAt: string;
|
||||
mediaClass?: string;
|
||||
assetType?: string;
|
||||
}
|
||||
|
||||
interface GroupedItems {
|
||||
@@ -256,6 +257,16 @@ export const GalleryModal = React.memo(
|
||||
(item.filter_media_classes
|
||||
? item.filter_media_classes[0]
|
||||
: "image"),
|
||||
// assetType for 3D assets so drop handlers can decide between character vs object
|
||||
assetType:
|
||||
item.media_class === "dimensional"
|
||||
? item.maybe_animation_type ||
|
||||
item.origin_product_category === "character" ||
|
||||
(item.origin &&
|
||||
item.origin.product_category === "character")
|
||||
? "character"
|
||||
: "object"
|
||||
: undefined,
|
||||
}));
|
||||
setAllItems((prev) => (reset ? newItems : [...prev, ...newItems]));
|
||||
// Pagination logic
|
||||
|
||||
Reference in new issue
Block a user