Tabs / Anime Tab and Cartoons Tab (#615)

Tabs/ Anime and Cartoons
- merge original character and animation into 1 tab
- duplicate the Character + Animation combo tab because
- we have anime & cartoon, 2 different types of character&animation
combos

---------

Co-authored-by: wil <wil@storyteller.ai>
This commit is contained in:
wilandwil authored and GitHub committed 2024-07-11 21:50:45 -04:00
1 parent a743199c56
commit 5bb656b980
15 files changed
+319 -101

No files matched your search

+3
View File
@@ -8,6 +8,9 @@ export enum FilterMediaType {
SCENE_JSON = "scene_json",
GLB = "glb",
GLTF = "gltf",
PMX = "pmx",
PMD = "pmd",
VMD = "vmd",
}
export enum FilterEngineCategories {
+8 -6
View File
@@ -1,11 +1,13 @@
export enum TabTitles {
SET_OBJECTS = "Film Sets",
IMAGE_PLANE = "Image Panels",
CHARACTERS = "Character",
CREATURES = "Creatures",
ANIMATION = "Animation",
EXPRESSIONS = "Facial Expression",
OBJECTS = "Set Dressings",
AUDIO = "Audio",
CHARACTERS = "Character",
CHARACTERS_ANIME = "Anime",
CHARACTERS_TOONS = "Cartoons",
EXPRESSIONS = "Facial Expression",
IMAGE_PLANE = "Image Panels",
OBJECTS = "Set Dressings",
OBJECTS_CREATURES = "Creatures",
OBJECTS_SETS = "Film Sets",
STYLIZE = "AI Stylize",
}
@@ -1,27 +1,29 @@
import { ReactNode } from "react";
import { FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
import {
faGlobeSnow,
faPeople,
faRabbitRunning,
faFaceSmileWink,
faCube,
faVolume,
faBrush,
faSpaghettiMonsterFlying,
faCatSpace,
faFaceSmileWink,
faGlobeSnow,
faPresentationScreen,
faRaygun,
faSpaghettiMonsterFlying,
faUserAstronaut,
faVolume,
} from "@fortawesome/pro-solid-svg-icons";
import { TabTitles } from "~/enums";
import { ObjectsTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/ObjectsTab";
import { AnimationTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/AnimationTab";
import { AudioTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/AudioTab";
import { StylizeTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/StylizeTab/StylizeTab";
import { ExpressionTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/ExpressionTab";
import { CharactersTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/CharactersTab";
import { CreaturesTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/CreaturesTab";
import { SetsTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/SetsTab";
import { ImagePlanesTab } from "~/pages/PageEnigma/comps/SidePanelTabs/tabComps/ImagePlanesTab";
import {
AnimeTab,
CartoonsTab,
} from "../SidePanelTabs/tabComps/AnimationCharacterComboTabs";
export interface TabItem {
icon: FontAwesomeIconProps["icon"];
@@ -32,23 +34,23 @@ export interface TabItem {
export const tabList = [
{
icon: faGlobeSnow,
title: TabTitles.SET_OBJECTS,
title: TabTitles.OBJECTS_SETS,
component: <SetsTab />,
},
{
icon: faPeople,
title: TabTitles.CHARACTERS,
component: <CharactersTab />,
},
{
icon: faSpaghettiMonsterFlying,
title: TabTitles.CREATURES,
title: TabTitles.OBJECTS_CREATURES,
component: <CreaturesTab />,
},
{
icon: faRabbitRunning,
title: TabTitles.ANIMATION,
component: <AnimationTab />,
icon: faUserAstronaut,
title: TabTitles.CHARACTERS_ANIME,
component: <AnimeTab />,
},
{
icon: faCatSpace,
title: TabTitles.CHARACTERS_TOONS,
component: <CartoonsTab />,
},
{
icon: faFaceSmileWink,
@@ -56,7 +58,7 @@ export const tabList = [
component: <ExpressionTab />,
},
{
icon: faCube,
icon: faRaygun,
title: TabTitles.OBJECTS,
component: <ObjectsTab />,
},
@@ -45,13 +45,13 @@ export const SidePanelMenu = ({
>
<div className="flex w-full flex-col gap-2">
{(tabs ?? []).map((tab) => {
if (tab.title === TabTitles.SET_OBJECTS && !showSetsTab) {
if (tab.title === TabTitles.OBJECTS_SETS && !showSetsTab) {
return;
}
if (tab.title === TabTitles.IMAGE_PLANE && !showImagePlaneTab) {
return;
}
if (tab.title === TabTitles.CREATURES && !showCreaturesTab) {
if (tab.title === TabTitles.OBJECTS_CREATURES && !showCreaturesTab) {
return;
}
return (
@@ -10,6 +10,22 @@ interface Props {
item: MediaItem;
}
const mapObejctType = (mediaType: string) => {
const typeCased = mediaType.toLowerCase();
switch (typeCased) {
case "fbx":
case "glb": {
return "Mixamo";
}
case "pmx": {
return "MMD";
}
default: {
return typeCased.toUpperCase();
}
}
};
export const ItemElement = ({ item }: Props) => {
useSignals();
const defaultThumb = `/resources/images/default-covers/${(item.imageIndex || 0) % 24}.webp`;
@@ -23,14 +39,9 @@ export const ItemElement = ({ item }: Props) => {
{item.media_type && (
<Badge
label={
item.type === AssetType.OBJECT
? item.media_type.toUpperCase()
: item.media_type === "fbx" ||
item.media_type.toLowerCase() === "glb"
? "Mixamo"
: item.media_type === "pmx"
? "MMD"
: item.media_type.toUpperCase()
item.type === AssetType.CHARACTER
? mapObejctType(item.media_type)
: item.media_type.toUpperCase()
}
className="absolute right-0 mr-[3px] mt-[3px]"
/>
@@ -0,0 +1,54 @@
import { useState } from "react";
import { twMerge } from "tailwind-merge";
import { MediaFileAnimationType, TabTitles } from "~/enums";
import { TabTitle } from "../../sharedComps";
import { AnimationTab } from "./subpageAnimation";
import { CharactersTab } from "./subpageCharacters";
export const AnimeTab = () => {
const [subPage, setSubPage] = useState<
TabTitles.ANIMATION | TabTitles.CHARACTERS
>(TabTitles.CHARACTERS);
return (
<>
<TabTitle title="Anime Characters & Animations" />
<div className="mx-4">
<button
className={twMerge(
"h-10 w-1/2 cursor-pointer rounded-l-lg bg-brand-secondary p-2 text-sm font-medium transition-all",
subPage === TabTitles.CHARACTERS
? "bg-ui-controls-button"
: "hover:bg-ui-controls-button/50",
)}
disabled={subPage === TabTitles.CHARACTERS}
onClick={() => setSubPage(TabTitles.CHARACTERS)}
>
Characters
</button>
<button
className={twMerge(
"h-10 w-1/2 cursor-pointer rounded-r-lg bg-brand-secondary p-2 text-sm font-medium transition-all",
subPage === TabTitles.ANIMATION
? "bg-ui-controls-button"
: "hover:bg-ui-controls-button/50",
)}
disabled={subPage === TabTitles.ANIMATION}
onClick={() => {
setSubPage(TabTitles.ANIMATION);
}}
>
Animations
</button>
</div>
{subPage === TabTitles.ANIMATION && (
<AnimationTab animationType={MediaFileAnimationType.MikuMikuDance} />
)}
{subPage === TabTitles.CHARACTERS && (
<CharactersTab animationType={MediaFileAnimationType.MikuMikuDance} />
)}
</>
);
};
@@ -0,0 +1,63 @@
import { useState } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { twMerge } from "tailwind-merge";
import { MediaFileAnimationType, TabTitles } from "~/enums";
import { TabTitle } from "../../sharedComps";
import { AnimationTab } from "./subpageAnimation";
import { CharactersTab } from "./subpageCharacters";
import { characterItems, animationItems } from "~/pages/PageEnigma/signals";
export const CartoonsTab = () => {
useSignals();
const [subPage, setSubPage] = useState<
TabTitles.ANIMATION | TabTitles.CHARACTERS
>(TabTitles.CHARACTERS);
return (
<>
<TabTitle title="Cartoon Characters & Animations" />
<div className="mx-4">
<button
className={twMerge(
"h-10 w-1/2 cursor-pointer rounded-l-lg bg-brand-secondary p-2 text-sm font-medium transition-all",
subPage === TabTitles.CHARACTERS
? "bg-ui-controls-button"
: "hover:bg-ui-controls-button/50",
)}
disabled={subPage === TabTitles.CHARACTERS}
onClick={() => setSubPage(TabTitles.CHARACTERS)}
>
Characters
</button>
<button
className={twMerge(
"h-10 w-1/2 cursor-pointer rounded-r-lg bg-brand-secondary p-2 text-sm font-medium transition-all",
subPage === TabTitles.ANIMATION
? "bg-ui-controls-button"
: "hover:bg-ui-controls-button/50",
)}
disabled={subPage === TabTitles.ANIMATION}
onClick={() => {
setSubPage(TabTitles.ANIMATION);
}}
>
Animations
</button>
</div>
{subPage === TabTitles.CHARACTERS && (
<CharactersTab
animationType={MediaFileAnimationType.Mixamo}
demoCharacterItems={characterItems.value}
/>
)}
{subPage === TabTitles.ANIMATION && (
<AnimationTab
animationType={MediaFileAnimationType.Mixamo}
demoAnimationItems={animationItems.value}
/>
)}
</>
);
};
@@ -0,0 +1,32 @@
import { FilterMediaType, MediaFileAnimationType } from "~/enums";
import { MediaItem } from "~/pages/PageEnigma/models";
export const filterMMDCharacters = (item: MediaItem) => {
return (
item.maybe_animation_type === MediaFileAnimationType.MikuMikuDance ||
item.media_type === FilterMediaType.PMD ||
item.media_type === FilterMediaType.PMX
);
};
export const filterMixamoCharacters = (item: MediaItem) => {
return (
item.maybe_animation_type === MediaFileAnimationType.Mixamo ||
(item.media_type !== FilterMediaType.PMD &&
item.media_type !== FilterMediaType.PMX)
);
};
export const filterMMDAnimations = (item: MediaItem) => {
return (
item.maybe_animation_type === MediaFileAnimationType.MikuMikuDance ||
item.media_type === FilterMediaType.VMD
);
};
export const filterMixamoAnimations = (item: MediaItem) => {
return (
item.maybe_animation_type === MediaFileAnimationType.Mixamo ||
item.media_type !== FilterMediaType.VMD
);
};
@@ -0,0 +1,2 @@
export * from "./AnimeTab";
export * from "./CartoonsTab";
@@ -1,5 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { useCallback, useEffect, useMemo, useState } from "react";
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
import {
ANIMATION_FILE_TYPE,
@@ -8,7 +7,6 @@ import {
FilterEngineCategories,
MediaFileAnimationType,
} from "~/enums";
import { animationItems } from "~/pages/PageEnigma/signals";
import {
Button,
FilterButtons,
@@ -16,10 +14,7 @@ import {
SearchFilter,
UploadModal,
} from "~/components";
import {
TabTitle,
ItemElements,
} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps";
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps";
import { FetchStatus } from "~/pages/PageEnigma/enums";
import {
fetchFeaturedMediaItems,
@@ -28,15 +23,32 @@ import {
fetchUserMediaItems,
fetchUserMediaItemsSearchResults,
isAnyStatusFetching,
} from "../utilities";
} from "../../utilities";
import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag";
import {
filterMixamoAnimations,
filterMMDAnimations,
} from "./filterCharacterTypes";
import { MediaItem } from "~/pages/PageEnigma/models";
export const AnimationTab = () => {
useSignals();
export const AnimationTab = ({
animationType,
demoAnimationItems = [],
}: {
animationType: MediaFileAnimationType;
demoAnimationItems?: MediaItem[];
}) => {
const showSearchObjectComponent = usePosthogFeatureFlag(
FeatureFlags.SHOW_SEARCH_OBJECTS,
);
const showUploadButton = usePosthogFeatureFlag(FeatureFlags.DEV_ONLY);
const filterAnimationType = useMemo(
() =>
animationType === MediaFileAnimationType.Mixamo
? filterMixamoAnimations
: filterMMDAnimations,
[animationType],
);
const [open, setOpen] = useState(false);
const [searchTermFeatured, setSearchTermFeatured] = useState("");
@@ -76,7 +88,7 @@ export const AnimationTab = () => {
);
const displayedItems =
selectedFilter === AssetFilterOption.FEATURED
? [...(featuredAnimations || []), ...animationItems.value]
? [...(featuredAnimations || []), ...demoAnimationItems]
: userAnimations ?? [];
const [currentPage, setCurrentPage] = useState<number>(0);
@@ -96,10 +108,13 @@ export const AnimationTab = () => {
fetchUserMediaItems({
filterEngineCategories: [FilterEngineCategories.ANIMATION],
setState: (newState: FetchMediaItemStates) => {
const filterNewMediaItems = newState.mediaItems
? newState.mediaItems.filter(filterAnimationType)
: undefined;
setUserFetch((curr) => ({
status: newState.status,
mediaItems: newState.mediaItems
? newState.mediaItems
mediaItems: filterNewMediaItems
? filterNewMediaItems
: curr.mediaItems,
}));
},
@@ -113,10 +128,13 @@ export const AnimationTab = () => {
fetchFeaturedMediaItems({
filterEngineCategories: [FilterEngineCategories.ANIMATION],
setState: (newState: FetchMediaItemStates) => {
const filterNewMediaItems = newState.mediaItems
? newState.mediaItems.filter(filterAnimationType)
: undefined;
setFeaturedFetch((curr) => ({
status: newState.status,
mediaItems: newState.mediaItems
? newState.mediaItems
mediaItems: filterNewMediaItems
? filterNewMediaItems
: curr.mediaItems,
}));
},
@@ -126,7 +144,7 @@ export const AnimationTab = () => {
);
const filterAnimationItems = (searchTerm: string) =>
animationItems.value.filter((item) =>
demoAnimationItems.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
@@ -197,7 +215,7 @@ export const AnimationTab = () => {
return (
<>
<TabTitle title="Animation" />
{/* <TabTitle title="Animation" /> */}
<FilterButtons
value={selectedFilter}
@@ -208,14 +226,16 @@ export const AnimationTab = () => {
/>
<div className="flex w-full flex-col gap-3 px-4">
<Button
icon={faCirclePlus}
variant="action"
onClick={() => setOpen(true)}
className="w-full py-3 text-sm font-medium"
>
Upload Animation
</Button>
{showUploadButton && (
<Button
icon={faCirclePlus}
variant="action"
onClick={() => setOpen(true)}
className="w-full py-3 text-sm font-medium"
>
Upload Animation (Dev Only)
</Button>
)}
{showSearchObjectComponent && (
<SearchFilter
searchTerm={
@@ -268,20 +288,21 @@ export const AnimationTab = () => {
onClose={() => setOpen(false)}
onSuccess={fetchUserAnimations}
isOpen={open}
type={FilterEngineCategories.ANIMATION}
fileTypes={Object.values(ANIMATION_FILE_TYPE)}
title="Upload Animation"
options={{
fileSubtypes: [
{ Mixamo: MediaFileAnimationType.Mixamo },
{ MikuMikuDance: MediaFileAnimationType.MikuMikuDance },
{ MoveAi: MediaFileAnimationType.MoveAi },
{ Rigify: MediaFileAnimationType.Rigify },
{ Rokoko: MediaFileAnimationType.Rokoko },
{ [animationType]: animationType },
// { Mixamo: MediaFileAnimationType.Mixamo },
// { MikuMikuDance: MediaFileAnimationType.MikuMikuDance },
// { MoveAi: MediaFileAnimationType.MoveAi },
// { Rigify: MediaFileAnimationType.Rigify },
// { Rokoko: MediaFileAnimationType.Rokoko },
],
hasLength: true,
hasThumbnailUpload: true,
}}
type={FilterEngineCategories.ANIMATION}
/>
</>
);
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
import {
@@ -9,7 +9,7 @@ import {
MediaFileAnimationType,
} from "~/enums";
import { FetchStatus } from "~/pages/PageEnigma/enums";
import { characterItems as demoCharacterItems } from "~/pages/PageEnigma/signals";
import { MediaItem } from "~/pages/PageEnigma/models";
import {
Button,
FilterButtons,
@@ -17,10 +17,7 @@ import {
SearchFilter,
UploadModal,
} from "~/components";
import {
ItemElements,
TabTitle,
} from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps";
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/sharedComps";
import { usePosthogFeatureFlag } from "~/hooks/usePosthogFeatureFlag";
import {
fetchFeaturedMediaItems,
@@ -29,14 +26,32 @@ import {
fetchUserMediaItems,
fetchUserMediaItemsSearchResults,
isAnyStatusFetching,
} from "../utilities";
} from "../../utilities";
import {
filterMixamoCharacters,
filterMMDCharacters,
} from "./filterCharacterTypes";
export const CharactersTab = () => {
export const CharactersTab = ({
animationType,
demoCharacterItems = [],
}: {
animationType: MediaFileAnimationType;
demoCharacterItems?: MediaItem[];
}) => {
useSignals();
const showSearchObjectComponent = usePosthogFeatureFlag(
FeatureFlags.SHOW_SEARCH_OBJECTS,
);
const showUploadButton = usePosthogFeatureFlag(FeatureFlags.DEV_ONLY);
const filterCharacterType = useMemo(
() =>
animationType === MediaFileAnimationType.Mixamo
? filterMixamoCharacters
: filterMMDCharacters,
[animationType],
);
const [openUploadModal, setOpenUploadModal] = useState(false);
@@ -76,7 +91,7 @@ export const CharactersTab = () => {
AssetFilterOption.FEATURED,
);
const allFeaturedCharacters = [
...demoCharacterItems.value,
...demoCharacterItems,
...(featuredCharacters ?? []),
];
const displayedItems =
@@ -101,16 +116,19 @@ export const CharactersTab = () => {
fetchUserMediaItems({
filterEngineCategories: [FilterEngineCategories.CHARACTER],
setState: (newState: FetchMediaItemStates) => {
const filterNewMediaItems = newState.mediaItems
? newState.mediaItems.filter(filterCharacterType)
: undefined;
setUserFetch((curr) => ({
status: newState.status,
mediaItems: newState.mediaItems
? newState.mediaItems
mediaItems: filterNewMediaItems
? filterNewMediaItems
: curr.mediaItems,
}));
},
defaultErrorMessage: "Unknown Error in Fetching User Characters",
}),
[],
[filterCharacterType],
);
const fetchFeaturedCharacters = useCallback(
@@ -118,20 +136,23 @@ export const CharactersTab = () => {
fetchFeaturedMediaItems({
filterEngineCategories: [FilterEngineCategories.CHARACTER],
setState: (newState: FetchMediaItemStates) => {
const filterNewMediaItems = newState.mediaItems
? newState.mediaItems.filter(filterCharacterType)
: undefined;
setFeaturedFetch((curr) => ({
status: newState.status,
mediaItems: newState.mediaItems
? newState.mediaItems
mediaItems: filterNewMediaItems
? filterNewMediaItems
: curr.mediaItems,
}));
},
defaultErrorMessage: "Unknown Error in Fetching Featured Characters",
}),
[],
[filterCharacterType],
);
const filterCharacterItems = (searchTerm: string) =>
demoCharacterItems.value.filter((item) =>
demoCharacterItems.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
@@ -202,7 +223,7 @@ export const CharactersTab = () => {
return (
<>
<TabTitle title="Characters" />
{/* <TabTitle title="Characters" /> */}
<div>
<FilterButtons
value={selectedFilter}
@@ -213,14 +234,16 @@ export const CharactersTab = () => {
/>
</div>
<div className="flex w-full flex-col gap-3 px-4">
<Button
icon={faCirclePlus}
variant="action"
onClick={() => setOpenUploadModal(true)}
className="w-full py-3 text-sm font-medium"
>
Upload Characters
</Button>
{showUploadButton && (
<Button
icon={faCirclePlus}
variant="action"
onClick={() => setOpenUploadModal(true)}
className="w-full py-3 text-sm font-medium"
>
Upload Character Model (Dev Only)
</Button>
)}
{showSearchObjectComponent && (
<SearchFilter
searchTerm={
@@ -277,11 +300,12 @@ export const CharactersTab = () => {
options={{
hasThumbnailUpload: true,
fileSubtypes: [
{ Mixamo: MediaFileAnimationType.Mixamo },
{ MikuMikuDance: MediaFileAnimationType.MikuMikuDance },
{ MoveAi: MediaFileAnimationType.MoveAi },
{ Rigify: MediaFileAnimationType.Rigify },
{ Rokoko: MediaFileAnimationType.Rokoko },
{ [animationType]: animationType },
// { Mixamo: MediaFileAnimationType.Mixamo },
// { MikuMikuDance: MediaFileAnimationType.MikuMikuDance },
// { MoveAi: MediaFileAnimationType.MoveAi },
// { Rigify: MediaFileAnimationType.Rigify },
// { Rokoko: MediaFileAnimationType.Rokoko },
],
}}
fileTypes={Object.values(CHARACTER_FILE_TYPE)}
@@ -1,4 +1,4 @@
import { FilterEngineCategories, ToastTypes } from "~/enums";
import { FilterEngineCategories, FilterMediaType, ToastTypes } from "~/enums";
import { addToast } from "~/signals";
import { FetchStatus } from "~/pages/PageEnigma/enums";
import { MediaFilesApi } from "~/Classes/ApiManager";
@@ -13,6 +13,7 @@ export interface FetchMediaItemStates {
interface fetchMediaItemsInterface {
setState: ({ mediaItems, status }: FetchMediaItemStates) => void;
filterEngineCategories: FilterEngineCategories[];
filterMediaType?: FilterMediaType[];
defaultErrorMessage?: string;
searchTerm?: string;
}
@@ -40,6 +40,9 @@ export const responseMapping = (
name: item.maybe_title ?? "Unknown",
type: assetType as AssetType,
media_type: item.media_type,
maybe_animation_type: item.maybe_animation_type
? item.maybe_animation_type
: undefined,
length: ((item.maybe_duration_millis ?? 1000) / 1000) * 60,
version: 1,
...(item.cover_image.maybe_cover_image_public_bucket_path
+1 -1
View File
@@ -3,7 +3,7 @@ import { AssetType } from "~/enums";
export interface MediaItem {
version: number;
type: AssetType;
// maybe_animation_type?: string;
maybe_animation_type?: string;
media_type?: string;
media_id: string;
object_uuid?: string;
+1 -1
View File
@@ -12,7 +12,7 @@ export interface MediaInfo {
token: string;
media_type: MediaFileType;
media_class: MediaFileClass | null;
// maybe_animation_type: string | null;
maybe_animation_type: string | null;
maybe_media_subtype: MediaFileSubtype | null;
maybe_engine_extension: string | null;
maybe_batch_token: string;