mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
Api2.5/ Listing Scenes (#501)
- list user scenes done - list featured scenes done Co-authored-by: wil <wil@storyteller.ai>
This commit is contained in:
1 parent
a40173bc74
commit
efa223d795
6 files changed
+88
-280
No files matched your search
@@ -1,170 +0,0 @@
|
||||
import { useEffect, useRef, useState, useLayoutEffect } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { MediaInfo } from "~/pages/PageEnigma/models";
|
||||
import { MediaFileType } from "~/pages/PageEnigma/enums";
|
||||
import { authentication } from "~/signals";
|
||||
|
||||
import {
|
||||
GetMediaByUser,
|
||||
GetMediaListResponse,
|
||||
} from "~/api/media_files/GetMediaByUser";
|
||||
import {
|
||||
ListFeaturedMediaFiles,
|
||||
ListFeaturedMediaFilesResponse,
|
||||
} from "~/api/media_files/ListFeaturedMediaFiles";
|
||||
|
||||
import { ScenePicker, SceneTypes } from "../ScenePicker";
|
||||
import { Label, LoadingSpinner } from "~/components";
|
||||
|
||||
interface LoadSceneProps {
|
||||
onSceneSelect: (token: string) => void;
|
||||
}
|
||||
|
||||
export enum FetchStatus {
|
||||
paused,
|
||||
// ready triggers a new fetch
|
||||
ready,
|
||||
in_progress,
|
||||
success,
|
||||
error,
|
||||
}
|
||||
|
||||
export enum Filters {
|
||||
Featured,
|
||||
Mine,
|
||||
Bookmarked,
|
||||
}
|
||||
|
||||
export const LoadScene = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
useSignals();
|
||||
const { userInfo } = authentication;
|
||||
|
||||
const [scenes, setScenes] = useState<SceneTypes[]>([]);
|
||||
const [featured, featuredSet] = useState<SceneTypes[]>([]);
|
||||
|
||||
const sceneLoading = useRef(false);
|
||||
const [isSceneLoading, setIsSceneLoading] = useState(true);
|
||||
const [featuredStatus, featuredStatusSet] = useState(FetchStatus.ready);
|
||||
useEffect(() => {
|
||||
if (!userInfo.value || scenes.length || sceneLoading.current) {
|
||||
return;
|
||||
}
|
||||
sceneLoading.current = true;
|
||||
|
||||
const updateScenes = (results: MediaInfo[]) =>
|
||||
results.map((scene: MediaInfo) => ({
|
||||
token: scene.token,
|
||||
name: scene.maybe_title ?? "Untitled",
|
||||
updated_at: dayjs(scene.updated_at).format("MMM D, YYYY HH:mm:ss"),
|
||||
thumbnail: scene.cover_image.maybe_cover_image_public_bucket_path
|
||||
? scene.cover_image.maybe_cover_image_public_bucket_path
|
||||
: undefined,
|
||||
}));
|
||||
// console.log("load scene");
|
||||
GetMediaByUser(
|
||||
userInfo.value.username,
|
||||
{},
|
||||
{
|
||||
filter_engine_categories: MediaFileType.Scene,
|
||||
},
|
||||
)
|
||||
.then((res: GetMediaListResponse) => {
|
||||
if (res.success && res.results) {
|
||||
setScenes(updateScenes(res.results));
|
||||
setIsSceneLoading(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
success: false,
|
||||
error_reason: "Unknown error",
|
||||
};
|
||||
});
|
||||
if (featuredStatus === FetchStatus.ready) {
|
||||
featuredStatusSet(FetchStatus.in_progress);
|
||||
ListFeaturedMediaFiles(
|
||||
"",
|
||||
{},
|
||||
{
|
||||
filter_engine_categories: "scene",
|
||||
page_size: 100,
|
||||
},
|
||||
).then((res: ListFeaturedMediaFilesResponse) => {
|
||||
if (res.success && res.results) {
|
||||
featuredStatusSet(FetchStatus.success);
|
||||
featuredSet(updateScenes(res.results));
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [featuredStatus, scenes, userInfo.value]);
|
||||
|
||||
const handleSceneSelect = (selectedScene: SceneTypes) => {
|
||||
onSceneSelect(selectedScene.token);
|
||||
};
|
||||
const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleScroll = () => {
|
||||
const element = scrollContainerRef.current;
|
||||
if (element) {
|
||||
const atBottom =
|
||||
element.scrollHeight - element.scrollTop <= element.clientHeight + 1;
|
||||
const hasOverflow = element.scrollHeight > element.clientHeight;
|
||||
|
||||
setBottomGradientOpacity(hasOverflow && !atBottom ? 1 : 0);
|
||||
}
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const element = scrollContainerRef.current;
|
||||
if (element) {
|
||||
handleScroll();
|
||||
element.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}
|
||||
}, [isSceneLoading]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<Label>My Scenes</Label>
|
||||
<div className="relative flex max-h-[500px] min-h-[140px] flex-col">
|
||||
{isSceneLoading ? (
|
||||
<div className="flex items-center justify-center gap-3 py-12">
|
||||
<LoadingSpinner />
|
||||
<span className="font-medium opacity-70">Loading scenes...</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{scenes.length !== 0 || featured.length !== 0 ? (
|
||||
<div
|
||||
className="overflow-y-auto overflow-x-hidden"
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
<ScenePicker
|
||||
scenes={[...scenes, ...featured]}
|
||||
onSceneSelect={handleSceneSelect}
|
||||
showDate={true}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center gap-3 py-12">
|
||||
<span className="font-medium opacity-50">
|
||||
You have no saved scenes yet.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-10 bg-gradient-to-t from-ui-panel to-transparent transition-opacity duration-200"
|
||||
style={{ opacity: bottomGradientOpacity }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { LoadScene } from "./LoadScene";
|
||||
@@ -1,16 +1,23 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { MediaInfo } from "~/pages/PageEnigma/models";
|
||||
import { ToastTypes } from "~/enums";
|
||||
import { FilterEngineCategories, ToastTypes } from "~/enums";
|
||||
|
||||
import { addToast } from "~/signals";
|
||||
|
||||
import { GetScenesByUser } from "./utilities";
|
||||
import { ScenePicker, SceneTypes } from "../ScenePicker";
|
||||
import { Label, LoadingSpinner } from "~/components";
|
||||
|
||||
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
|
||||
|
||||
interface LoadSceneProps {
|
||||
onSceneSelect: (token: string) => void;
|
||||
}
|
||||
@@ -35,12 +42,7 @@ export const LoadUserScenes = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (scenes) {
|
||||
//only call once on mount
|
||||
return;
|
||||
}
|
||||
|
||||
const getScenesByUser = useCallback(async () => {
|
||||
const modMediaInfoToScenes = (results: MediaInfo[]) =>
|
||||
results.map((scene: MediaInfo) => ({
|
||||
token: scene.token,
|
||||
@@ -51,14 +53,27 @@ export const LoadUserScenes = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
GetScenesByUser().then((res) => {
|
||||
if ("results" in res) {
|
||||
setScenes(modMediaInfoToScenes(res.results));
|
||||
return;
|
||||
}
|
||||
addToast(ToastTypes.ERROR, res.error_reason);
|
||||
const mediaFilesApi = new MediaFilesApi();
|
||||
const response = await mediaFilesApi.ListUserMediaFiles({
|
||||
filter_engine_categories: [FilterEngineCategories.SCENE],
|
||||
});
|
||||
}, [scenes]);
|
||||
if (response.success && response.data) {
|
||||
setScenes(modMediaInfoToScenes(response.data));
|
||||
return;
|
||||
}
|
||||
addToast(
|
||||
ToastTypes.ERROR,
|
||||
response.errorMessage || "Unknown Error in Loading User Scenes",
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (scenes) {
|
||||
//only call once on mount
|
||||
return;
|
||||
}
|
||||
getScenesByUser();
|
||||
}, [scenes, getScenesByUser]);
|
||||
|
||||
const handleSceneSelect = (selectedScene: SceneTypes) => {
|
||||
onSceneSelect(selectedScene.token);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { GetMediaListResponse } from "~/pages/PageEnigma/models";
|
||||
import { authentication, environmentVariables } from "~/signals";
|
||||
|
||||
const getMediaFilesByUsername = (username: string) =>
|
||||
`${environmentVariables.value.BASE_API}/v1/media_files/list/user/${username}?filter_media_type=scene_json`;
|
||||
|
||||
export const GetScenesByUser = () => {
|
||||
const unknownError = {
|
||||
success: false,
|
||||
error_reason: "Unknown error in Loading User Scenes",
|
||||
};
|
||||
if (!authentication.userInfo.value) {
|
||||
return new Promise<typeof unknownError>((resolve) => {
|
||||
resolve(unknownError);
|
||||
});
|
||||
}
|
||||
|
||||
const endpoint = getMediaFilesByUsername(
|
||||
authentication.userInfo.value.username,
|
||||
);
|
||||
|
||||
return fetch(endpoint, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res: GetMediaListResponse) => {
|
||||
return res;
|
||||
})
|
||||
.catch(() => {
|
||||
return unknownError;
|
||||
});
|
||||
};
|
||||
+57
-29
@@ -1,10 +1,20 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { MediaInfo } from "~/pages/PageEnigma/models";
|
||||
import { FilterEngineCategories, ToastTypes } from "~/enums";
|
||||
|
||||
import { ScenePicker, SceneTypes } from "../ScenePicker";
|
||||
import { ListFeaturedScenes } from "./utilities";
|
||||
import { LoadingSpinner } from "~/components";
|
||||
|
||||
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
|
||||
import { addToast } from "~/signals";
|
||||
|
||||
interface NewSceneFromTemplateProps {
|
||||
onSceneSelect: (token: string) => void;
|
||||
@@ -13,16 +23,16 @@ interface NewSceneFromTemplateProps {
|
||||
export const NewSceneFromTemplate = ({
|
||||
onSceneSelect,
|
||||
}: NewSceneFromTemplateProps) => {
|
||||
const [featuredScenes, setFeaturedScenes] = useState<SceneTypes[]>([]);
|
||||
const [featuredScenes, setFeaturedScenes] = useState<
|
||||
SceneTypes[] | undefined
|
||||
>(undefined);
|
||||
const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (featuredScenes.length > 0) {
|
||||
//only call once on mount
|
||||
return;
|
||||
}
|
||||
const listFeaturedScenes = useCallback(async () => {
|
||||
const mediaFilesApi = new MediaFilesApi();
|
||||
// Hard coded scenes
|
||||
|
||||
const dummyScenes: SceneTypes[] = [
|
||||
{
|
||||
token: "m_nmzvdqr6kr8eqpmxqdzkqj0yknrjwv",
|
||||
@@ -30,7 +40,6 @@ export const NewSceneFromTemplate = ({
|
||||
thumbnail: "/resources/placeholders/scene_placeholder.png",
|
||||
},
|
||||
];
|
||||
|
||||
const modMediaInfoToScenes = (results: MediaInfo[]) =>
|
||||
results.map((scene: MediaInfo) => ({
|
||||
token: scene.token,
|
||||
@@ -40,18 +49,29 @@ export const NewSceneFromTemplate = ({
|
||||
? scene.cover_image.maybe_cover_image_public_bucket_path
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
ListFeaturedScenes().then((res) => {
|
||||
if (res.success && "results" in res) {
|
||||
setFeaturedScenes([
|
||||
...modMediaInfoToScenes(res.results),
|
||||
...dummyScenes,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
setFeaturedScenes(dummyScenes);
|
||||
const response = await mediaFilesApi.ListFeaturedMediaFiles({
|
||||
filter_engine_categories: [FilterEngineCategories.SCENE],
|
||||
});
|
||||
}, [featuredScenes]);
|
||||
if (response.success && response.data) {
|
||||
setFeaturedScenes([
|
||||
...modMediaInfoToScenes(response.data),
|
||||
...dummyScenes,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
setFeaturedScenes(dummyScenes);
|
||||
addToast(
|
||||
ToastTypes.ERROR,
|
||||
response.errorMessage || "Unknown Error in Listing Feature Scenes",
|
||||
);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (featuredScenes) {
|
||||
//only call once on mount
|
||||
return;
|
||||
}
|
||||
listFeaturedScenes();
|
||||
}, [featuredScenes, listFeaturedScenes]);
|
||||
|
||||
const handleSceneSelect = (selectedScene: SceneTypes) => {
|
||||
onSceneSelect(selectedScene.token);
|
||||
@@ -83,15 +103,23 @@ export const NewSceneFromTemplate = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="relative flex max-h-[500px] min-h-[140px] flex-col">
|
||||
<div
|
||||
className="overflow-y-auto overflow-x-hidden"
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
<ScenePicker
|
||||
scenes={featuredScenes}
|
||||
onSceneSelect={handleSceneSelect}
|
||||
/>
|
||||
</div>
|
||||
{!featuredScenes && (
|
||||
<div className="flex items-center justify-center gap-3 py-12">
|
||||
<LoadingSpinner />
|
||||
<span className="font-medium opacity-70">Loading scenes...</span>
|
||||
</div>
|
||||
)}
|
||||
{featuredScenes && (
|
||||
<div
|
||||
className="overflow-y-auto overflow-x-hidden"
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
<ScenePicker
|
||||
scenes={featuredScenes}
|
||||
onSceneSelect={handleSceneSelect}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-10 bg-gradient-to-t from-ui-panel to-transparent transition-opacity duration-200"
|
||||
style={{ opacity: bottomGradientOpacity }}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { ListFeaturedMediaFilesResponse } from "~/api/media_files/ListFeaturedMediaFiles";
|
||||
import { environmentVariables } from "~/signals";
|
||||
|
||||
const listFeaturedScenes = () =>
|
||||
`${environmentVariables.value.BASE_API}/v1/media_files/list_featured?filter_media_type=scene_json`;
|
||||
|
||||
export const ListFeaturedScenes = () => {
|
||||
const unknownError = {
|
||||
success: false,
|
||||
error_reason: "Unknown error in Loading Featured Scenes",
|
||||
};
|
||||
const endpoint = listFeaturedScenes();
|
||||
|
||||
return fetch(endpoint, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res: ListFeaturedMediaFilesResponse) => {
|
||||
return res;
|
||||
})
|
||||
.catch(() => {
|
||||
return unknownError;
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user