Remove the Wizard

This commit is contained in:
CrossPr0duct committed 2025-05-23 16:27:41 -04:00
1 parent ea76917264
commit cca897d24d
17 files changed
-1243

No files matched your search

@@ -119,7 +119,6 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
/>
<GenerateModals />
<ErrorDialog />
{/* <Wizard /> */}
</EngineProvider>
<EditorLoadingBar />
<Toaster offsetTop={70} offsetRight={12} />
@@ -1,100 +0,0 @@
import { WizardItem } from "~/pages/PageEnigma/Wizard/Wizard";
import { ObjectSelectionButton } from "~/pages/PageEnigma/Wizard/ObjectSelectionButton";
import { BucketConfig } from "~/api/BucketConfig";
import { MediaInfo } from "~/pages/PageEnigma/models";
import { AssetType, FilterEngineCategories, ToastTypes } from "~/enums";
import { useCallback, useEffect, useState } from "react";
import { MediaFilesApi } from "~/Classes/ApiManager";
import { addToast } from "~/signals";
import {
backgroundWizardItems,
currentStep,
selectedBackground,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { useSignals } from "@preact/signals-react/runtime";
import { BackgroundList } from "~/pages/PageEnigma/Wizard/BackgroundList";
export const Background = () => {
useSignals();
const item = currentStep.value as WizardItem;
const [showList, setShowList] = useState(false);
const responseMapping = (data: MediaInfo[]) => {
return data
.filter(
(item) =>
[
"Sci-fi City",
"Cherry Blossom Petals",
"Barbie Dreamland Test",
"Space Station",
"graveyard_2",
"sand tile",
"Tiny Island",
"dock",
].indexOf(item.maybe_title ?? "") > -1,
)
.map((item) => {
const bucketConfig = new BucketConfig();
const itemThumb = bucketConfig.getCdnUrl(
item.cover_image.maybe_cover_image_public_bucket_path ?? "",
600,
100,
);
return {
colorIndex: item.cover_image.default_cover.color_index,
imageIndex: item.cover_image.default_cover.image_index,
media_id: item.token,
name: item.maybe_title ?? "Unknown",
type: AssetType.OBJECT,
media_type: item.media_type,
version: 1,
...(item.cover_image.maybe_cover_image_public_bucket_path
? {
thumbnail: itemThumb,
}
: {}),
};
});
};
const fetchFeaturedObjects = useCallback(async () => {
const mediaFilesApi = new MediaFilesApi();
const response = await mediaFilesApi.ListFeaturedMediaFiles({
filter_engine_categories: [FilterEngineCategories.OBJECT],
});
if (response.success && response.data) {
backgroundWizardItems.value = responseMapping(response.data);
return;
}
addToast(
ToastTypes.ERROR,
response.errorMessage || "Unknown Error in Fetching Featured Objects",
);
}, []);
useEffect(() => {
fetchFeaturedObjects();
}, [fetchFeaturedObjects]);
if (showList) {
return <BackgroundList onClose={() => setShowList(false)} />;
}
return (
<div>
<div>{item.label}</div>
<ObjectSelectionButton
onClick={() => setShowList(true)}
label={
selectedBackground.value ? "Selected Background" : "Select Background"
}
busy={!backgroundWizardItems.value}
imageSrc={
selectedBackground.value?.thumbnail ??
backgroundWizardItems.value?.[0]?.thumbnail ??
""
}
/>
</div>
);
};
@@ -1,52 +0,0 @@
import { useContext } from "react";
import { ItemElement } from "./ItemElement";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { QueueNames } from "~/pages/PageEnigma/Queue";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
import { objectGroup } from "~/pages/PageEnigma/signals";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import {
backgroundWizardItems,
selectedBackground,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { useSignals } from "@preact/signals-react/runtime";
interface Props {
onClose: () => void;
}
export const BackgroundList = ({ onClose }: Props) => {
useSignals();
const editorEngine = useContext(EngineContext);
return (
<div className="flex h-4/5 flex-wrap gap-4 overflow-y-auto">
{(backgroundWizardItems.value ?? []).map((item) => (
<button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
const existingBackground = objectGroup.value.objects.find(
(object) => object.name === selectedBackground.value?.name,
);
if (existingBackground) {
editorEngine?.deleteObject(existingBackground?.object_uuid ?? "");
}
selectedBackground.value = item;
Queue.publish({
queueName: QueueNames.TO_ENGINE,
action: toEngineActions.ADD_OBJECT,
data: item,
});
onClose();
}}
key={item.object_uuid}
className="w-[144px]"
>
<ItemElement item={item} />
</button>
))}
</div>
);
};
@@ -1,72 +0,0 @@
import { Clip, MediaItem } from "~/pages/PageEnigma/models";
import { useEffect, useRef } from "react";
import { ItemElement } from "./ItemElement";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { QueueNames } from "~/pages/PageEnigma/Queue";
import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
import { AssetType } from "~/enums";
import {
characterWizardItems,
selectedCharacters,
showList,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
import { useSignals } from "@preact/signals-react/runtime";
export const CharacterList = () => {
useSignals();
const addingObject = useRef<MediaItem | null>(null);
useEffect(() => {
Queue.subscribe(QueueNames.FROM_ENGINE, "wizard-character", (message) => {
switch (message.action) {
case fromEngineActions.UPDATE_CHARACTER_ID:
if (addingObject.current) {
const data = message.data as Clip;
selectedCharacters.value = [
...selectedCharacters.value,
{
...addingObject.current,
version: data.version,
type: data.type.toString() as AssetType,
media_id: data.media_id,
name: data.name,
object_uuid: data.object_uuid,
},
];
}
break;
case fromEngineActions.DELETE_OBJECT:
selectedCharacters.value = selectedCharacters.value.filter(
(object) =>
object.object_uuid !== (message.data as Clip).object_uuid,
);
break;
}
});
}, []);
return (
<div className="flex h-4/5 flex-wrap gap-4 overflow-y-auto">
{(characterWizardItems.value ?? []).map((item) => (
<button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
addingObject.current = item;
showList.value = null;
Queue.publish({
queueName: QueueNames.TO_ENGINE,
action: toEngineActions.ADD_CHARACTER,
data: item,
});
}}
key={item.object_uuid}
className="w-[144px]"
>
<ItemElement item={item} />
</button>
))}
</div>
);
};
@@ -1,106 +0,0 @@
import { WizardItem, WizardType } from "~/pages/PageEnigma/Wizard/Wizard";
import { ObjectSelectionButton } from "~/pages/PageEnigma/Wizard/ObjectSelectionButton";
import { BucketConfig } from "~/api/BucketConfig";
import { MediaInfo } from "~/pages/PageEnigma/models";
import { AssetType, FilterEngineCategories, ToastTypes } from "~/enums";
import { useCallback, useContext, useEffect } from "react";
import { MediaFilesApi } from "~/Classes/ApiManager";
import { addToast } from "~/signals";
import { ItemElement } from "./ItemElement";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import {
characterWizardItems,
currentStep,
selectedCharacters,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { useSignals } from "@preact/signals-react/runtime";
import { demoCharacterItems } from "~/pages/PageEnigma/signals";
export const Characters = () => {
useSignals();
const item = currentStep.value as WizardItem;
const editorEngine = useContext(EngineContext);
const responseMapping = (data: MediaInfo[]) => {
return data.map((item) => {
const bucketConfig = new BucketConfig();
const itemThumb = bucketConfig.getCdnUrl(
item.cover_image.maybe_cover_image_public_bucket_path ?? "",
600,
100,
);
return {
object_uuid: item.token,
colorIndex: item.cover_image.default_cover.color_index,
imageIndex: item.cover_image.default_cover.image_index,
media_id: item.token,
name: item.maybe_title ?? "Unknown",
type: AssetType.OBJECT,
media_type: item.media_type,
version: 1,
...(item.cover_image.maybe_cover_image_public_bucket_path
? {
thumbnail: itemThumb,
}
: {}),
};
});
};
const fetchFeaturedCharacters = useCallback(async () => {
const mediaFilesApi = new MediaFilesApi();
const response = await mediaFilesApi.ListFeaturedMediaFiles({
filter_engine_categories: [FilterEngineCategories.CHARACTER],
});
if (response.success && response.data) {
characterWizardItems.value = [
...responseMapping(response.data),
...demoCharacterItems.value,
];
return;
}
addToast(
ToastTypes.ERROR,
response.errorMessage || "Unknown Error in Fetching Featured Objects",
);
}, []);
useEffect(() => {
fetchFeaturedCharacters();
}, [fetchFeaturedCharacters]);
console.log(characterWizardItems.value?.[0]?.thumbnail ?? "");
const initialCharacter = characterWizardItems.value?.[0] ?? {
imageIndex: 0,
thumbnail: "",
};
const defaultThumb = `/resources/images/default-covers/${(initialCharacter.imageIndex || 0) % 24}.webp`;
const thumbnail = initialCharacter.thumbnail
? initialCharacter.thumbnail
: defaultThumb;
return (
<div>
<div>{item.label}</div>
<div className="mb-2 flex flex-wrap gap-2">
{selectedCharacters.value.map((item) => (
<button
key={item.object_uuid}
className="w-[80px]"
onClick={() => {
editorEngine?.deleteObject(item.object_uuid!);
}}
>
<ItemElement item={item} showDelete />
</button>
))}
</div>
<ObjectSelectionButton
onClick={() => {}}
label="Selected Character"
busy={!characterWizardItems.value}
imageSrc={thumbnail}
/>
</div>
);
};
@@ -1,56 +0,0 @@
import { MediaItem } from "~/pages/PageEnigma/models";
import { useSignals } from "@preact/signals-react/runtime";
import { SyntheticEvent } from "react";
import { Badge } from "~/components";
import { AssetType } from "~/enums";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTrash } from "@fortawesome/pro-solid-svg-icons";
interface Props {
item: MediaItem;
showDelete?: boolean;
}
export const ItemElement = ({ item, showDelete }: Props) => {
useSignals();
const defaultThumb = `/resources/images/default-covers/${(item.imageIndex || 0) % 24}.webp`;
const thumbnail = item.thumbnail ? item.thumbnail : defaultThumb;
return (
<div className="group relative w-full cursor-pointer select-none overflow-hidden rounded-lg transition-all duration-200 hover:brightness-110">
{item.media_type && !showDelete && (
<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()
}
className="absolute right-0 mr-[3px] mt-[3px]"
/>
)}
{showDelete && (
<div className="absolute right-0 mr-[3px] mt-[3px] text-brand-primary">
<FontAwesomeIcon icon={faTrash} />
</div>
)}
<img
crossOrigin="anonymous"
src={thumbnail}
onError={(e: SyntheticEvent<HTMLImageElement>) => {
e.currentTarget.src = defaultThumb;
}}
alt={item.name}
className="pointer-events-none aspect-[4.5/5] w-full select-none bg-gradient-to-b from-[#CCCCCC] to-[#A0A0A0] object-cover object-center"
/>
<div className="pointer-events-none w-full select-none truncate bg-ui-controls px-2 py-1 text-center text-[12px] transition-all duration-200 group-hover:bg-ui-controls-button/50">
{item.name || item.media_id}
</div>
</div>
);
};
@@ -1,89 +0,0 @@
import { useSignals } from "@preact/signals-react/runtime";
import {
currentStep,
showWizard,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight } from "@fortawesome/pro-solid-svg-icons";
import { Fragment } from "react";
export const MainPage = () => {
useSignals();
const step = currentStep.value;
return (
<div>
<div className="mb-3 font-bold">How would you like to start?</div>
<div className="flex gap-5">
{(step?.options ?? []).map((option) => (
<Fragment key={option.value}>
{option.label === "Remix" && (
<button
className={twMerge(
"group relative flex h-[249px] w-full flex-col items-center rounded-lg border border-white/5 bg-brand-secondary/50 px-2 pt-[58px] transition-all duration-150 hover:border-brand-primary hover:bg-brand-primary/20",
)}
onClick={() => (showWizard.value = option.value as string)}
>
<svg
width="36"
height="33"
viewBox="0 0 36 33"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.74336 8.12213C14.2972 3.56828 21.6551 3.54632 26.2382 8.04892L23.2219 11.058C22.7167 11.5631 22.5703 12.3172 22.8412 12.9762C23.112 13.6351 23.7563 14.0597 24.4665 14.0597H33.2154H33.8377C34.8115 14.0597 35.5949 13.2763 35.5949 12.3026V2.93133C35.5949 2.22117 35.1702 1.57689 34.5113 1.306C33.8524 1.03512 33.0983 1.18154 32.5931 1.68671L29.5475 4.73237C23.134 -1.60055 12.8037 -1.57859 6.42681 4.80558C4.64042 6.59198 3.35187 8.69319 2.56117 10.9408C2.12921 12.1635 2.77349 13.496 3.98882 13.9279C5.20416 14.3599 6.54396 13.7156 6.97591 12.5003C7.53965 10.9042 8.45481 9.40336 9.74336 8.12213ZM0.452637 20.5024V21.0589V21.1101V29.8737C0.452637 30.5839 0.877272 31.2281 1.53619 31.499C2.19511 31.7699 2.9492 31.6235 3.45437 31.1183L6.50003 28.0727C12.9135 34.4056 23.2438 34.3836 29.6207 27.9995C31.4071 26.2131 32.7029 24.1118 33.4936 21.8715C33.9256 20.6489 33.2813 19.3164 32.066 18.8844C30.8507 18.4525 29.5109 19.0968 29.0789 20.3121C28.5152 21.9081 27.6 23.409 26.3115 24.6902C21.7576 29.2441 14.3997 29.266 9.81658 24.7634L12.8256 21.7471C13.3308 21.2419 13.4772 20.4878 13.2063 19.8289C12.9354 19.17 12.2912 18.7453 11.581 18.7453H2.82474H2.77349H2.20975C1.23602 18.7453 0.452637 19.5287 0.452637 20.5024Z"
fill="white"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17.9759 11.2148C18.6202 11.2148 19.1414 11.7361 19.1414 12.3803V14.956H21.717C22.3613 14.956 22.8825 15.4772 22.8825 16.1214C22.8825 16.7657 22.3613 17.2869 21.717 17.2869H19.1414V19.8625C19.1414 20.5068 18.6202 21.028 17.9759 21.028C17.3317 21.028 16.8104 20.5068 16.8104 19.8625V17.2869H14.2348C13.5906 17.2869 13.0693 16.7657 13.0693 16.1214C13.0693 15.4772 13.5906 14.956 14.2348 14.956H16.8104V12.3803C16.8104 11.7361 17.3317 11.2148 17.9759 11.2148Z"
fill="white"
/>
</svg>
<div className="mt-4 text-xl font-bold">Remix</div>
<div className="mt-4 px-8 text-sm text-white/70">
Create a scene from an existing scene
</div>
<div className="absolute bottom-[8px] right-[12px] text-lg text-white opacity-50 group-hover:opacity-100">
<FontAwesomeIcon icon={faArrowRight} />
</div>
</button>
)}
{option.label === "Blank Scene" && (
<button
className={twMerge(
"group relative flex h-[249px] w-full flex-col items-center rounded-lg border border-white/5 bg-brand-secondary/50 px-2 pt-[58px] transition-all duration-150 hover:border-brand-primary hover:bg-brand-primary/20",
)}
onClick={() => (showWizard.value = option.value as string)}
>
<svg
width="26"
height="34"
viewBox="0 0 26 34"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.494629 4.18421C0.494629 1.87636 2.32929 0 4.58586 0H14.8139V8.36842C14.8139 9.52562 15.7281 10.4605 16.8595 10.4605H25.042V29.2895C25.042 31.5973 23.2073 33.4737 20.9508 33.4737H4.58586C2.32929 33.4737 0.494629 31.5973 0.494629 29.2895V4.18421ZM25.042 8.36842H16.8595V0L25.042 8.36842Z"
fill="white"
/>
</svg>
<div className="mt-4 text-xl font-bold">Blank Scene</div>
<div className="mt-4 px-8 text-sm text-white/70">
If you’re already familiar with Storyteller Studio.
</div>
<div className="absolute bottom-[8px] right-[12px] text-lg text-white opacity-50 group-hover:opacity-100">
<FontAwesomeIcon icon={faArrowRight} />
</div>
</button>
)}
</Fragment>
))}
</div>
</div>
);
};
@@ -1,72 +0,0 @@
import { Clip, MediaItem } from "~/pages/PageEnigma/models";
import { AssetType } from "~/enums";
import { useEffect, useRef } from "react";
import { ItemElement } from "./ItemElement";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { QueueNames } from "~/pages/PageEnigma/Queue";
import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
import {
objectWizardItems,
selectedObjects,
showList,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { useSignals } from "@preact/signals-react/runtime";
export const ObjectList = () => {
useSignals();
const addingObject = useRef<MediaItem | null>(null);
useEffect(() => {
Queue.subscribe(QueueNames.FROM_ENGINE, "wizard-objects", (message) => {
switch (message.action) {
case fromEngineActions.ADD_OBJECT:
if (addingObject.current) {
const data = message.data as Clip;
selectedObjects.value = [
...selectedObjects.value,
{
...addingObject.current,
version: data.version,
type: data.type.toString() as AssetType,
media_id: data.media_id,
name: data.name,
object_uuid: data.object_uuid,
},
];
}
break;
case fromEngineActions.DELETE_OBJECT:
selectedObjects.value = selectedObjects.value.filter(
(object) =>
object.object_uuid !== (message.data as Clip).object_uuid,
);
break;
}
});
}, []);
return (
<div className="flex h-4/5 flex-wrap gap-4 overflow-y-auto">
{(objectWizardItems.value ?? []).map((item) => (
<button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
addingObject.current = item;
showList.value = null;
Queue.publish({
queueName: QueueNames.TO_ENGINE,
action: toEngineActions.ADD_OBJECT,
data: item,
});
}}
key={item.object_uuid}
className="w-[144px]"
>
<ItemElement item={item} />
</button>
))}
</div>
);
};
@@ -1,43 +0,0 @@
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { H4, LoadingSpinner } from "~/components";
interface ObjectSelectionButtonProps {
onClick: () => void;
label: string;
imageSrc: string;
busy: boolean;
disabled?: boolean;
}
export function ObjectSelectionButton({
onClick,
label,
imageSrc,
busy,
disabled,
}: ObjectSelectionButtonProps) {
return (
<div className="flex flex-col">
<button
className="flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-white/30 bg-ui-controls-button/70 p-2 pr-3 text-start transition-all hover:border-brand-primary hover:bg-ui-controls-button/60"
onClick={onClick}
disabled={disabled}
>
<div className="aspect-video w-20 overflow-hidden rounded-md bg-ui-controls-button/100">
{busy ? (
<LoadingSpinner />
) : (
<img crossOrigin="anonymous" src={imageSrc} alt={label} />
)}
</div>
<div className="grow">
<>
<H4>{label}</H4>
</>
</div>
<FontAwesomeIcon icon={faChevronRight} className="text-xl opacity-60" />
</button>
</div>
);
}
@@ -1,106 +0,0 @@
import { WizardItem, WizardType } from "~/pages/PageEnigma/Wizard/Wizard";
import { ObjectSelectionButton } from "~/pages/PageEnigma/Wizard/ObjectSelectionButton";
import { BucketConfig } from "~/api/BucketConfig";
import { MediaInfo } from "~/pages/PageEnigma/models";
import { AssetType, FilterEngineCategories, ToastTypes } from "~/enums";
import { useCallback, useContext, useEffect } from "react";
import { MediaFilesApi } from "~/Classes/ApiManager";
import { addToast } from "~/signals";
import { ItemElement } from "./ItemElement";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import {
currentStep,
objectWizardItems,
selectedObjects,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { useSignals } from "@preact/signals-react/runtime";
export const Objects = () => {
useSignals();
const item = currentStep.value as WizardItem;
const editorEngine = useContext(EngineContext);
const responseMapping = (data: MediaInfo[]) => {
return data
.filter(
(item) =>
[
"Sci-fi City",
"Cherry Blossom Petals",
"Barbie Dreamland Test",
"Space Station",
"graveyard_2",
"sand tile",
"Tiny Island",
"dock",
].indexOf(item.maybe_title ?? "") === -1,
)
.map((item) => {
const bucketConfig = new BucketConfig();
const itemThumb = bucketConfig.getCdnUrl(
item.cover_image.maybe_cover_image_public_bucket_path ?? "",
600,
100,
);
return {
object_uuid: item.token,
colorIndex: item.cover_image.default_cover.color_index,
imageIndex: item.cover_image.default_cover.image_index,
media_id: item.token,
name: item.maybe_title ?? "Unknown",
type: AssetType.OBJECT,
media_type: item.media_type,
version: 1,
...(item.cover_image.maybe_cover_image_public_bucket_path
? {
thumbnail: itemThumb,
}
: {}),
};
});
};
const fetchFeaturedObjects = useCallback(async () => {
const mediaFilesApi = new MediaFilesApi();
const response = await mediaFilesApi.ListFeaturedMediaFiles({
filter_engine_categories: [FilterEngineCategories.OBJECT],
});
if (response.success && response.data) {
objectWizardItems.value = responseMapping(response.data);
return;
}
addToast(
ToastTypes.ERROR,
response.errorMessage || "Unknown Error in Fetching Featured Objects",
);
}, []);
useEffect(() => {
fetchFeaturedObjects();
}, [fetchFeaturedObjects]);
return (
<div>
<div>{item.label}</div>
<div className="mb-2 flex flex-wrap gap-2">
{selectedObjects.value.map((item) => (
<button
key={item.object_uuid}
className="w-[80px]"
onClick={() => {
editorEngine?.deleteObject(item.object_uuid!);
}}
>
<ItemElement item={item} showDelete />
</button>
))}
</div>
<ObjectSelectionButton
onClick={() => {}}
label="Selected Object"
busy={!objectWizardItems.value}
imageSrc={objectWizardItems.value?.[0]?.thumbnail ?? ""}
/>
</div>
);
};
@@ -1,20 +0,0 @@
import { WizardItem } from "~/pages/PageEnigma/Wizard/Wizard";
import { useSignals } from "@preact/signals-react/runtime";
import { Select } from "~/components";
import {
currentStep,
showWizard,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
export const Question = () => {
useSignals();
const item = currentStep.value as WizardItem;
return (
<Select
options={item.options!}
onChange={(value) => (showWizard.value = value)}
placeholder={item.label}
/>
);
};
@@ -1,100 +0,0 @@
import { useSignals } from "@preact/signals-react/runtime";
import { useEffect, useState } from "react";
import { LoadingSpinner } from "~/components";
import { RemixVideo } from "~/pages/PageEnigma/Wizard/RemixVideo";
const cards = [
{
title: "Space Station",
defaultVideo: "/resources/videos/remix/space_station_result.mp4",
hoverVideo: "/resources/videos/remix/space_station_raw.mp4",
token: "m_ezkyxc4fqe6s8rmq7352zrbvgwq4w8",
text: "",
},
{
title: "Walking Down the Dock",
defaultVideo: "/resources/videos/remix/dock_walk_result.mp4",
hoverVideo: "/resources/videos/remix/dock_walk_raw.mp4",
token: "m_bcmc0k8ny4meywdmdy2vn2eafeyr0r",
text: "",
},
{
title: "The Beach",
defaultVideo: "/resources/videos/remix/beach_result.mp4",
hoverVideo: "/resources/videos/remix/beach_raw.mp4",
token: "m_k10hh6qvb8zfctk901avmnv2brqpyw",
text: "",
},
{
title: "The Market",
defaultVideo: "/resources/videos/remix/market_result.mp4",
hoverVideo: "/resources/videos/remix/market_raw.mp4",
token: "m_pp7f78gpyd7kmxkbsp6x9vngqg12gc",
text: "",
},
{
title: "Cherry Blossom",
defaultVideo: "/resources/videos/remix/cherry_blossom_result.mp4",
hoverVideo: "/resources/videos/remix/cherry_blossom_raw.mp4",
token: "m_97xhpc7dt7xnfwj7paxg94mt95dshv",
text: "",
},
{
title: "Pirate Ship",
defaultVideo: "/resources/videos/remix/pirate_ship_result.mp4",
hoverVideo: "/resources/videos/remix/pirate_ship_raw.mp4",
token: "m_tz8vm3vw3xsk5z5qvpq1y9cczdn2vp",
text: "",
},
{
title: "Cute Ghost",
defaultVideo: "/resources/videos/remix/cute_ghost_result.mp4",
hoverVideo: "/resources/videos/remix/cute_ghost_raw.mp4",
token: "m_6vcp2d5k2k9k0xmz6hm6r5vtr1vexx",
text: "",
},
{
title: "Japanese Schoolyard",
defaultVideo: "/resources/videos/remix/japanese_schoolyard_result.mp4",
hoverVideo: "/resources/videos/remix/japanese_schoolyard_raw.mp4",
token: "m_ftp3v0jgegtp6rsck4v8x5wm4vwvr1",
text: "",
},
{
title: "Desert Walk",
defaultVideo: "/resources/videos/remix/desert_walk_result.mp4",
hoverVideo: "/resources/videos/remix/desert_walk_raw.mp4",
token: "m_nhh5fym68sab83yg3a0z7hhm40p4jc",
text: "",
},
];
export const Remix = () => {
useSignals();
const [loading, setLoading] = useState(true);
// This is a 500ms delay fix for the layout shifting
useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 500);
return () => clearTimeout(timer);
}, []);
return (
<div>
{loading ? (
<div className="flex h-52 w-full items-center justify-center gap-3 font-medium">
<LoadingSpinner />
Loading Scenes...
</div>
) : (
<div className="grid grid-cols-3 gap-4 overflow-y-auto">
{cards.map((card) => (
<RemixVideo card={card} key={card.token} />
))}
</div>
)}
</div>
);
};
@@ -1,138 +0,0 @@
import { useSignals } from "@preact/signals-react/runtime";
import { twMerge } from "tailwind-merge";
import { useEffect, useRef, useState } from "react";
import { selectedRemixCard } from "~/pages/PageEnigma/Wizard/signals/wizard";
import { LoadingSpinner } from "~/components";
export const RemixVideo = ({
card,
}: {
card: {
title: string;
text: string;
defaultVideo: string;
hoverVideo: string;
token: string;
};
}) => {
useSignals();
const [hover, setHover] = useState(false);
const defaultVideoRef = useRef<HTMLVideoElement>(null);
const hoverVideoRef = useRef<HTMLVideoElement>(null);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
const defaultVideo = defaultVideoRef.current;
const hoverVideo = hoverVideoRef.current;
if (defaultVideo && hoverVideo) {
const onCanPlayThrough = () => {
if (defaultVideo.readyState >= 4 && hoverVideo.readyState >= 4) {
setIsReady(true);
defaultVideo.play();
hoverVideo.play();
}
};
defaultVideo.addEventListener("canplaythrough", onCanPlayThrough);
hoverVideo.addEventListener("canplaythrough", onCanPlayThrough);
return () => {
defaultVideo.removeEventListener("canplaythrough", onCanPlayThrough);
hoverVideo.removeEventListener("canplaythrough", onCanPlayThrough);
};
}
}, []);
const handleMouseEnter = () => {
if (defaultVideoRef.current && hoverVideoRef.current) {
hoverVideoRef.current.currentTime = defaultVideoRef.current.currentTime;
setHover(true);
}
};
const handleMouseLeave = () => {
setHover(false);
};
return (
<div>
<button
key={card.token}
className={twMerge(
"relative block aspect-video w-[352px] overflow-hidden rounded-lg border transition-all duration-150",
card.token === selectedRemixCard.value?.token
? "border-brand-primary"
: "border-white/5 hover:border-white/25",
)}
onClick={() => (selectedRemixCard.value = card)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div
className="absolute bottom-0 left-0 z-10 h-1/4 w-full object-cover"
style={{
background:
"linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent)",
}}
/>
<video
className="absolute left-0 top-0 h-full w-full object-cover"
ref={defaultVideoRef}
src={card.defaultVideo}
crossOrigin="anonymous"
autoPlay
loop
muted
style={{ opacity: hover ? 0 : 1 }}
/>
<video
className="absolute left-0 top-0 h-full w-full object-cover"
ref={hoverVideoRef}
src={card.hoverVideo}
crossOrigin="anonymous"
autoPlay
loop
muted
style={{ opacity: hover ? 1 : 0 }}
/>
{!isReady && (
<div className="absolute left-0 top-0 h-full w-full bg-black/10 object-cover">
<div className="flex h-full w-full items-center justify-center">
<LoadingSpinner />
</div>
</div>
)}
<div
className="text-md absolute bottom-[8px] left-[8px] z-10 font-medium"
style={{
textShadow: "0px 0px 10px rgba(0, 0, 0, 0.4)",
}}
>
{card.title}
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className={`absolute right-1.5 top-1.5 h-[22px] w-[22px] transition-opacity duration-200 ease-in-out ${
card.token === selectedRemixCard.value?.token
? "opacity-100"
: "opacity-0"
}`}
>
<path
opacity="1"
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c-9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
fill="#FC6B68"
/>
<path
d="M369 175c-9.4 9.4-9.4 24.6 0 33.9L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c-9.4-9.4 24.6-9.4 33.9 0z"
fill="#FFFFFF"
/>
</svg>
</button>
<p className="mt-2 text-xs text-white/70">{card.text}</p>
</div>
);
};
@@ -1,13 +0,0 @@
import { Input } from "~/components";
import { textInput } from "~/pages/PageEnigma/Wizard/signals/wizard";
export const TextField = () => {
return (
<div>
<Input
value={textInput.value}
onChange={(event) => (textInput.value = event.target.value)}
/>
</div>
);
};
@@ -1,177 +0,0 @@
import { SelectOption, TransitionDialogue } from "~/components";
import {
currentStep,
selectedRemixCard,
showWizard,
textInput,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
import { WizardStep } from "~/pages/PageEnigma/Wizard/WizardStep";
import { resetSceneGenerationMetadata } from "~/pages/PageEnigma/signals";
import { CameraAspectRatio } from "~/pages/PageEnigma/enums";
import { useContext, useMemo } from "react";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
export enum WizardType {
BACKGROUND = "background",
CHARACTER = "character",
OBJECT = "object",
QUESTION = "question",
MAIN_PAGE = "main-page",
REMIX = "remix",
TEXT_FIELD = "text_field",
NONE = "none",
}
export interface WizardItem {
label: string;
type: WizardType;
options?: SelectOption[];
showNext?: () => boolean;
nextTitle?: string;
showPrior?: () => boolean;
priorTitle?: string;
onPrior?: string;
nextPage?: string;
onNext?: (() => void) | string;
width?: number;
}
export const Wizard = () => {
useSignals();
const editorEngine = useContext(EngineContext);
const wizardItems: Record<string, WizardItem> = useMemo(
() => ({
initial: {
label: "Welcome to Storyteller Studio",
type: WizardType.MAIN_PAGE,
showPrior: () => false,
showNext: () => false,
options: [
{
label: "Remix",
value: "remix",
},
{
label: "Blank Scene",
value: "end",
},
],
width: 593,
},
new_scene: {
label: "Create a New Scene",
type: WizardType.MAIN_PAGE,
showPrior: () => false,
showNext: () => false,
options: [
{
label: "Remix",
value: "remix",
},
{
label: "Blank Scene",
value: "new_scene_title",
},
],
width: 593,
},
new_scene_title: {
label: "Add New Scene Title",
type: WizardType.TEXT_FIELD,
showNext: () => !!textInput.value,
nextTitle: "Add New Scene",
onNext: () => {
resetSceneGenerationMetadata();
editorEngine?.changeRenderCameraAspectRatio(
CameraAspectRatio.HORIZONTAL_3_2,
);
editorEngine?.newScene(textInput.value);
showWizard.value = "end";
},
priorTitle: "Cancel",
onPrior: "new_scene",
},
remix: {
label: "Choose a Scene to Remix",
type: WizardType.REMIX,
nextTitle: "Remix Scene",
showNext: () => !!selectedRemixCard.value,
priorTitle: "Cancel",
onPrior: "initial",
onNext: () => {
if (editorEngine && selectedRemixCard.value) {
editorEngine.loadScene(selectedRemixCard.value?.token);
showWizard.value = "end";
}
},
onBack: () => {
showWizard.value = "initial";
},
width: 1152,
},
wizard: {
label: "What kind of scene do you want?",
type: WizardType.QUESTION,
showNext: () => false,
options: [
{
label: "Dance",
value: "dance",
},
{
label: "Monologue",
value: "mono",
},
],
},
dance: {
label: "Pick a background",
type: WizardType.BACKGROUND,
nextPage: "danceCharacter",
},
danceCharacter: {
label: "Pick a character",
type: WizardType.CHARACTER,
nextPage: "end",
},
mono: {
label: "Pick a character",
type: WizardType.CHARACTER,
nextPage: "end",
},
}),
[editorEngine],
);
useSignalEffect(() => {
if (showWizard.value === "end") {
return;
}
currentStep.value = wizardItems[showWizard.value];
});
if (!currentStep.value) {
return null;
}
return (
<TransitionDialogue
isOpen={showWizard.value !== "end"}
onClose={() => (showWizard.value = "end")}
title={currentStep.value.label}
width={currentStep.value.width ?? 672}
titleIcon={showWizard.value === "remix" ? faChevronLeft : undefined}
titleIconClassName="text-white/60 hover:text-white/80 transition-colors duration-150"
onTitleIconClick={
showWizard.value === "remix"
? () => (showWizard.value = "initial")
: undefined
}
>
<WizardStep />
</TransitionDialogue>
);
};
@@ -1,74 +0,0 @@
import { Background } from "./Background";
import { Button } from "@storyteller/ui-button";
import { Objects } from "./Objects";
import { Characters } from "./Characters";
import {
currentStep,
showWizard,
} from "~/pages/PageEnigma/Wizard/signals/wizard";
import { useSignals } from "@preact/signals-react/runtime";
import { Question } from "~/pages/PageEnigma/Wizard/Question";
import { WizardItem, WizardType } from "~/pages/PageEnigma/Wizard/Wizard";
import { MainPage } from "~/pages/PageEnigma/Wizard/MainPage";
import { Remix } from "~/pages/PageEnigma/Wizard/Remix";
import { TextField } from "~/pages/PageEnigma/Wizard/TextField";
export const WizardStep = () => {
useSignals();
const step = currentStep.value as WizardItem;
if (!step.nextTitle) {
step.nextTitle = "Next";
}
if (!step.priorTitle) {
step.priorTitle = "Prior";
}
const wizardComponents: Record<WizardType, (() => JSX.Element) | null> = {
[WizardType.BACKGROUND]: Background,
[WizardType.CHARACTER]: Characters,
[WizardType.OBJECT]: Objects,
[WizardType.QUESTION]: Question,
[WizardType.MAIN_PAGE]: MainPage,
[WizardType.REMIX]: Remix,
[WizardType.TEXT_FIELD]: TextField,
[WizardType.NONE]: null,
};
const Component = wizardComponents[step.type];
if (Component === null) {
return null;
}
return (
<div>
<Component />
<div className="flex justify-end gap-2 pt-4">
{(!step.showPrior || step.showPrior()) && (
<Button
variant="action"
onClick={() => {
showWizard.value = step.onPrior!;
}}
>
{step.priorTitle}
</Button>
)}
{(!step.showNext || step.showNext()) && (
<Button
onClick={() => {
if (typeof step.onNext === "string") {
showWizard.value = step.onNext;
return;
}
if (step.onNext) {
step.onNext();
}
}}
>
{step.nextTitle}
</Button>
)}
</div>
</div>
);
};
@@ -1,24 +0,0 @@
import { signal } from "@preact/signals-core";
import { MediaItem } from "~/pages/PageEnigma/models";
import { WizardItem } from "~/pages/PageEnigma/Wizard/Wizard";
export const showWizard = signal("");
export const currentStep = signal<WizardItem | null>(null);
export const textInput = signal("");
export const selectedAudios = signal<MediaItem[]>([]);
export const selectedBackground = signal<MediaItem | null>(null);
export const selectedCharacters = signal<MediaItem[]>([]);
export const selectedObjects = signal<MediaItem[]>([]);
export const audioWizardItems = signal<MediaItem[] | null>(null);
export const backgroundWizardItems = signal<MediaItem[] | null>(null);
export const characterWizardItems = signal<MediaItem[] | null>(null);
export const objectWizardItems = signal<MediaItem[] | null>(null);
export const selectedRemixCard = signal<{
text: string;
defaultVideo: string;
hoverVideo: string;
token: string;
} | null>(null);