mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
replaced MediaFile instances of BucketConfig with server provided media links
This commit is contained in:
1 parent
ffdb90e3aa
commit
a68776f487
45 files changed
+604
-1173
No files matched your search
@@ -1,11 +1,11 @@
|
||||
import { FetchStatus } from "./_common/SharedFetchTypes";
|
||||
import { MediaFileType, GetMediaByUser } from "./media_files";
|
||||
import { MediaFile, GetMediaByUser } from "./media_files";
|
||||
import { Weight, ListWeights, ListFeaturedWeights } from "./weights";
|
||||
import { GetBookmarksByUser } from "./bookmarks/GetBookmarksByUser";
|
||||
import { MediaFileClass } from "./enums/MediaFileClass";
|
||||
import { BucketConfig } from "./BucketConfig";
|
||||
|
||||
export type { MediaFileType, Weight };
|
||||
export type { MediaFile, Weight };
|
||||
export {
|
||||
BucketConfig,
|
||||
FetchStatus,
|
||||
|
||||
@@ -10,10 +10,28 @@ import {
|
||||
WeightCategory,
|
||||
} from "@storyteller/components/src/api/_common/enums";
|
||||
|
||||
export interface MediaVideoPreviews {
|
||||
animated: string;
|
||||
animated_thumbnail_template: string;
|
||||
still: string;
|
||||
still_thumbnail_template: string;
|
||||
}
|
||||
|
||||
// export type MediaThumbFn = (width: number) => string;
|
||||
|
||||
export interface ResponseMediaLinks {
|
||||
cdn_url: string;
|
||||
// path for image media thumbnails
|
||||
maybe_thumbnail_template: string | null;
|
||||
// previews for video media
|
||||
maybe_video_previews: MediaVideoPreviews | null;
|
||||
}
|
||||
|
||||
export interface MediaFile {
|
||||
token: string;
|
||||
media_type: MediaFileType;
|
||||
media_class: MediaFileClass | null;
|
||||
media_links: ResponseMediaLinks;
|
||||
maybe_engine_category: EngineCategory | null;
|
||||
maybe_animation_type: AnimationType | null;
|
||||
maybe_media_subtype: MediaFileSubtype | null;
|
||||
@@ -49,6 +67,15 @@ export interface MediaFile {
|
||||
maybe_text_transcript?: string | undefined;
|
||||
}
|
||||
|
||||
// export interface MediaFile extends ResponseMediaFile {
|
||||
// frontendURLs: {
|
||||
// imageThumb: MediaThumbFn;
|
||||
// main: string;
|
||||
// videoStill: MediaThumbFn;
|
||||
// videoAnimated: MediaThumbFn;
|
||||
// };
|
||||
// }
|
||||
|
||||
export interface MediaFileModeratorFields {
|
||||
maybe_style_transfer_source_media_file_token?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ResponseMediaLinks } from "./GetMedia";
|
||||
|
||||
const MediaLinker = (baseUrl: string) => (width: number) =>
|
||||
baseUrl.replace("{WIDTH}", width.toString());
|
||||
|
||||
export type MediaLinkFn = (width: number) => string;
|
||||
|
||||
export interface MediaLinkUtility {
|
||||
mainURL: string;
|
||||
fixedVideoStill: string;
|
||||
fixedVideoAnimated: string;
|
||||
imageThumb: MediaLinkFn | null;
|
||||
videoStill: MediaLinkFn | null;
|
||||
videoAnimated: MediaLinkFn | null;
|
||||
}
|
||||
|
||||
export function MediaLinks(mediaLinks?: ResponseMediaLinks): MediaLinkUtility {
|
||||
return {
|
||||
mainURL: mediaLinks?.cdn_url || "",
|
||||
fixedVideoStill: mediaLinks?.maybe_video_previews?.still || "",
|
||||
fixedVideoAnimated: mediaLinks?.maybe_video_previews?.animated || "",
|
||||
imageThumb: mediaLinks?.maybe_thumbnail_template
|
||||
? MediaLinker(mediaLinks.maybe_thumbnail_template)
|
||||
: null,
|
||||
videoStill: mediaLinks?.maybe_video_previews
|
||||
? MediaLinker(mediaLinks.maybe_video_previews.still_thumbnail_template)
|
||||
: null,
|
||||
videoAnimated: mediaLinks?.maybe_video_previews
|
||||
? MediaLinker(mediaLinks.maybe_video_previews.animated_thumbnail_template)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { MediaFile } from "./GetMediaFile";
|
||||
import { GetMediaByUser } from "./GetMediaByUser";
|
||||
import {
|
||||
GetMedia,
|
||||
GetMediaRequest,
|
||||
GetMediaResponse,
|
||||
MediaFile,
|
||||
} from "./GetMedia";
|
||||
import { GetMediaByUser } from "./GetMediaByUser";
|
||||
import { MediaLinks, MediaLinkUtility } from "./MediaLinks";
|
||||
|
||||
|
||||
export type{
|
||||
MediaFile as MediaFileType
|
||||
}
|
||||
export {
|
||||
GetMediaByUser
|
||||
}
|
||||
export { GetMedia, GetMediaByUser, MediaLinks };
|
||||
export type { GetMediaRequest, GetMediaResponse, MediaFile, MediaLinkUtility };
|
||||
@@ -1,32 +1,28 @@
|
||||
import { MediaFileType } from "@storyteller/components/src/api/_common/enums/MediaFileType";
|
||||
import { MediaFile as MediaFileOne } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import { MediaFile as MediaFileTwo } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
|
||||
// Support multiple "Media File" API response payloads.
|
||||
type MediaFile = MediaFileOne | MediaFileTwo ;
|
||||
|
||||
export function GetMediaFileTitle(mediaFile?: MediaFile) : string {
|
||||
export function GetMediaFileTitle(mediaFile?: MediaFile): string {
|
||||
if (mediaFile === undefined) {
|
||||
return "Untitled file";
|
||||
}
|
||||
|
||||
let mediaType = undefined;
|
||||
|
||||
|
||||
switch (mediaFile?.media_type) {
|
||||
case MediaFileType.Audio:
|
||||
case MediaFileType.Video:
|
||||
case MediaFileType.Image:
|
||||
mediaType = mediaFile?.media_type.toLocaleLowerCase();
|
||||
break;
|
||||
mediaType = mediaFile?.media_type.toLocaleLowerCase();
|
||||
break;
|
||||
case MediaFileType.BVH:
|
||||
case MediaFileType.GLTF:
|
||||
case MediaFileType.GLB:
|
||||
case MediaFileType.FBX:
|
||||
mediaType = mediaFile?.media_type.toUpperCase();
|
||||
break;
|
||||
mediaType = mediaFile?.media_type.toUpperCase();
|
||||
break;
|
||||
case MediaFileType.SceneRon:
|
||||
mediaType = "RON";
|
||||
break;
|
||||
mediaType = "RON";
|
||||
break;
|
||||
}
|
||||
|
||||
let title = "";
|
||||
@@ -44,7 +40,7 @@ export function GetMediaFileTitle(mediaFile?: MediaFile) : string {
|
||||
}
|
||||
|
||||
if (title.length < 4) {
|
||||
const maybeSuffix = (!!mediaType) ? ` (${mediaType} file)` : "";
|
||||
const maybeSuffix = !!mediaType ? ` (${mediaType} file)` : "";
|
||||
return `${title}${maybeSuffix}`;
|
||||
} else {
|
||||
return title;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { faPlay, faPause } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Button from "components/common/Button";
|
||||
import "./AudioPlayer.scss";
|
||||
@@ -20,8 +19,6 @@ export default function AudioPlayer({ src, id }: AudioPlayerProps) {
|
||||
const isPlayerOnScreen = useOnScreen(waveformRef); //This stops the audio from playing when the player is not on screen
|
||||
|
||||
useEffect(() => {
|
||||
const audioLink = new BucketConfig().getGcsUrl(src);
|
||||
|
||||
if (!waveSurferRef.current && waveformRef.current) {
|
||||
const wavesurferInstance = WaveSurfer.create({
|
||||
container: waveformRef.current,
|
||||
@@ -36,7 +33,7 @@ export default function AudioPlayer({ src, id }: AudioPlayerProps) {
|
||||
|
||||
waveSurferRef.current = wavesurferInstance;
|
||||
|
||||
waveSurferRef.current.load(audioLink);
|
||||
waveSurferRef.current.load(src);
|
||||
|
||||
waveSurferRef.current.on("pause", () => {
|
||||
setIsPlaying(false);
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function CameraInput({
|
||||
const { open } = useModal();
|
||||
const camera = useCameraState(true);
|
||||
const [token, tokenSet] = useState("");
|
||||
const { bucketUrl } = useMedia({ mediaToken: token });
|
||||
const { links } = useMedia({ mediaToken: token });
|
||||
const cameraSupported = MediaRecorder.isTypeSupported("video/mp4");
|
||||
|
||||
const cameraClick = () =>
|
||||
@@ -83,7 +83,7 @@ export default function CameraInput({
|
||||
<video
|
||||
controls
|
||||
{...{
|
||||
src: bucketUrl,
|
||||
src: links.mainURL,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
||||
+6
-40
@@ -7,11 +7,13 @@ import { CardFooter } from "components/entities";
|
||||
import Badge from "components/common/Badge";
|
||||
import { faThumbsUp, faWaveformLines } from "@fortawesome/pro-solid-svg-icons";
|
||||
import useWeightTypeInfo from "hooks/useWeightTypeInfo/useWeightTypeInfo";
|
||||
import WeightCoverImage from "components/common/WeightCoverImage";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import getCardUrl from "../getCardUrl";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Stat from "components/common/Stat/Stat";
|
||||
import {
|
||||
// MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
// import getCardUrl from "../getCardUrl";
|
||||
|
||||
interface AudioCardProps {
|
||||
@@ -41,6 +43,7 @@ export default function AudioCard({
|
||||
onResultSelect,
|
||||
onResultBookmarkSelect,
|
||||
}: AudioCardProps) {
|
||||
const { mainURL } = MediaLinks(data.media_links);
|
||||
const linkUrl = getCardUrl(data, source, type);
|
||||
|
||||
const handleSelectModalResultSelect = () => {
|
||||
@@ -62,28 +65,6 @@ export default function AudioCard({
|
||||
data.weight_type || data.details?.maybe_weight_data?.weight_type
|
||||
);
|
||||
|
||||
const bucketConfig = new BucketConfig();
|
||||
let coverImage = undefined;
|
||||
|
||||
if (type === "media") {
|
||||
coverImage = bucketConfig.getCdnUrl(data.public_bucket_path, 400, 100);
|
||||
} else if (type === "weights") {
|
||||
if (data?.cover_image?.maybe_cover_image_public_bucket_path) {
|
||||
coverImage = bucketConfig.getCdnUrl(
|
||||
data.cover_image.maybe_cover_image_public_bucket_path,
|
||||
110,
|
||||
100
|
||||
);
|
||||
}
|
||||
if (data.details?.maybe_weight_data?.maybe_cover_image_public_bucket_path) {
|
||||
coverImage = bucketConfig.getCdnUrl(
|
||||
data.details?.maybe_weight_data?.maybe_cover_image_public_bucket_path,
|
||||
110,
|
||||
100
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const card = (
|
||||
<Card
|
||||
padding={true}
|
||||
@@ -113,13 +94,7 @@ export default function AudioCard({
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<AudioPlayer
|
||||
src={
|
||||
data.details?.maybe_media_file_data?.public_bucket_path ||
|
||||
data.public_bucket_path
|
||||
}
|
||||
id={data.token}
|
||||
/>
|
||||
<AudioPlayer src={mainURL} id={data.token} />
|
||||
<CardFooter
|
||||
{...{
|
||||
creator:
|
||||
@@ -138,15 +113,6 @@ export default function AudioCard({
|
||||
{type === "weights" && (
|
||||
<>
|
||||
<div className="d-flex">
|
||||
{showCover && (
|
||||
<WeightCoverImage
|
||||
src={coverImage}
|
||||
height={100}
|
||||
width={100}
|
||||
coverIndex={data?.cover_image?.default_cover?.image_index}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex-grow-1">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="flex-grow-1">
|
||||
|
||||
+12
-4
@@ -8,6 +8,10 @@ import Button from "components/common/Button";
|
||||
import { faArrowRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import useWeightTypeInfo from "hooks/useWeightTypeInfo/useWeightTypeInfo";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import {
|
||||
// MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
// import useToken from "hooks/useToken";
|
||||
import getCardUrl from "../getCardUrl";
|
||||
|
||||
@@ -38,6 +42,8 @@ export default function ImageCard({
|
||||
// const { setToken, setWeightTitle } = useToken();
|
||||
const linkUrl = getCardUrl(data, source, type);
|
||||
|
||||
const { imageThumb } = MediaLinks(data.media_links);
|
||||
|
||||
const handleSelectModalResultSelect = () => {
|
||||
console.log("handleSelectModalResultSelect");
|
||||
if (inSelectModal) {
|
||||
@@ -63,10 +69,10 @@ export default function ImageCard({
|
||||
);
|
||||
|
||||
const bucketConfig = new BucketConfig();
|
||||
let coverImage = undefined;
|
||||
let coverImage = "";
|
||||
|
||||
if (type === "media") {
|
||||
coverImage = bucketConfig.getCdnUrl(data.details?.maybe_media_file_data?.public_bucket_path || data.public_bucket_path, 600, 100);
|
||||
if (imageThumb && type === "media") {
|
||||
coverImage = imageThumb(600);
|
||||
} else if (type === "weights") {
|
||||
coverImage = `/images/default-covers/${
|
||||
data?.cover_image?.default_cover.image_index || 0
|
||||
@@ -121,7 +127,9 @@ export default function ImageCard({
|
||||
</div>
|
||||
<CardFooter
|
||||
{...{
|
||||
creator: data?.maybe_creator || data.details?.maybe_media_file_data?.maybe_creator,
|
||||
creator:
|
||||
data?.maybe_creator ||
|
||||
data.details?.maybe_media_file_data?.maybe_creator,
|
||||
entityToken: data.details?.entity_token || data.token,
|
||||
entityType: "media_file",
|
||||
makeBookmarksProps: bookmarks?.makeProps,
|
||||
|
||||
+12
-102
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import Card from "../Card";
|
||||
import useTimeAgo from "hooks/useTimeAgo";
|
||||
import {
|
||||
@@ -7,10 +7,14 @@ import {
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import Badge from "components/common/Badge";
|
||||
import Button from "components/common/Button";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { useHover } from "hooks";
|
||||
import { Link } from "react-router-dom";
|
||||
import getCardUrl from "../getCardUrl";
|
||||
import { STYLES_BY_KEY } from "common/StyleOptions";
|
||||
import {
|
||||
// MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
|
||||
interface VideoCardProps {
|
||||
bookmarks?: any;
|
||||
@@ -24,16 +28,15 @@ interface VideoCardProps {
|
||||
}
|
||||
|
||||
export default function VideoCard({
|
||||
bookmarks,
|
||||
data,
|
||||
ratings,
|
||||
showCreator,
|
||||
source = "",
|
||||
type,
|
||||
inSelectModal = false,
|
||||
onResultSelect,
|
||||
}: VideoCardProps) {
|
||||
const linkUrl = getCardUrl(data, source, type);
|
||||
const { videoAnimated, videoStill } = MediaLinks(data.media_links);
|
||||
const [hover, hoverProps] = useHover({});
|
||||
|
||||
const handleSelectModalResultSelect = () => {
|
||||
if (inSelectModal && onResultSelect) {
|
||||
@@ -42,16 +45,7 @@ export default function VideoCard({
|
||||
};
|
||||
|
||||
const timeAgo = useTimeAgo(data.created_at);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [imageSrc, setImageSrc] = useState("");
|
||||
const [gifExists, setGifExists] = useState(false);
|
||||
const [staticImageExists, setStaticImageExists] = useState(false);
|
||||
|
||||
const publicBucketPath = data.details
|
||||
? data.details.maybe_media_file_data.public_bucket_path
|
||||
: data.public_bucket_path;
|
||||
|
||||
const bucketConfig = new BucketConfig();
|
||||
//video doesnt have random cover image endpoint or thumbnails yet
|
||||
const defaultImageUrl = `/images/default-covers/${
|
||||
data?.cover_image?.default_cover.image_index || 0
|
||||
@@ -59,91 +53,8 @@ export default function VideoCard({
|
||||
|
||||
// We are checking the existence of the bucket gif files because it seems as though we can't check the cdn file's existence without running into cors issues
|
||||
// CDN URLS
|
||||
const staticImageUrl = publicBucketPath
|
||||
? bucketConfig.getCdnUrl(publicBucketPath + "-thumb.jpg", 600, 100)
|
||||
: defaultImageUrl;
|
||||
const gifUrl = publicBucketPath
|
||||
? bucketConfig.getCdnUrl(publicBucketPath + "-thumb.gif", 360, 20)
|
||||
: null;
|
||||
|
||||
// BUCKET URLS
|
||||
const bucketGifUrl = publicBucketPath
|
||||
? bucketConfig.getGcsUrl(publicBucketPath + "-thumb.gif")
|
||||
: null;
|
||||
const bucketImageUrl = publicBucketPath
|
||||
? bucketConfig.getGcsUrl(publicBucketPath + "-thumb.jpg")
|
||||
: null;
|
||||
|
||||
// const bucketUrl = publicBucketPath
|
||||
// ? new BucketConfig().getGcsUrl(publicBucketPath)
|
||||
// : "";
|
||||
|
||||
const checkGifExists = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url, { method: "HEAD" });
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const checkImageExists = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url, { method: "HEAD" });
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Preload images and check if the GIF exists when the component mounts
|
||||
useEffect(() => {
|
||||
// Check if the static image exists
|
||||
if (bucketImageUrl === null) return;
|
||||
checkImageExists(bucketImageUrl).then(staticExists => {
|
||||
setStaticImageExists(staticExists);
|
||||
if (staticExists) {
|
||||
setImageSrc(bucketImageUrl);
|
||||
} else {
|
||||
setImageSrc(defaultImageUrl);
|
||||
}
|
||||
});
|
||||
|
||||
// Check and preload the GIF if it exists
|
||||
if (bucketGifUrl === null) return;
|
||||
checkGifExists(bucketGifUrl).then(gifExists => {
|
||||
setGifExists(gifExists);
|
||||
if (gifExists) {
|
||||
const imgGif = new Image();
|
||||
imgGif.src = bucketGifUrl;
|
||||
}
|
||||
});
|
||||
}, [bucketGifUrl, staticImageUrl, defaultImageUrl, bucketImageUrl, data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isHovered && gifExists && bucketGifUrl && staticImageExists) {
|
||||
setImageSrc(gifUrl!);
|
||||
} else if (staticImageExists) {
|
||||
setImageSrc(bucketImageUrl!);
|
||||
} else {
|
||||
setImageSrc(defaultImageUrl);
|
||||
}
|
||||
}, [
|
||||
isHovered,
|
||||
gifExists,
|
||||
gifUrl,
|
||||
bucketGifUrl,
|
||||
staticImageUrl,
|
||||
staticImageExists,
|
||||
defaultImageUrl,
|
||||
bucketImageUrl,
|
||||
data,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setImageSrc(defaultImageUrl);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data]);
|
||||
const staticImageUrl = videoStill && videoStill(600);
|
||||
const gifUrl = videoAnimated && videoAnimated(360);
|
||||
|
||||
const styleLabel = STYLES_BY_KEY.has(data.maybe_style_name)
|
||||
? STYLES_BY_KEY.get(data.maybe_style_name)?.label
|
||||
@@ -169,16 +80,15 @@ export default function VideoCard({
|
||||
|
||||
const card = (
|
||||
<Card
|
||||
{...{ ...hoverProps }}
|
||||
padding={false}
|
||||
canHover={true}
|
||||
onClick={handleSelectModalResultSelect}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{type === "media" && (
|
||||
<>
|
||||
<img
|
||||
src={imageSrc}
|
||||
src={gifUrl && hover ? gifUrl : staticImageUrl || defaultImageUrl}
|
||||
alt={data.weight_name}
|
||||
className="card-video"
|
||||
loading="lazy"
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
@import "scss/colours.scss";
|
||||
@import "scss/site-refresh.scss";
|
||||
|
||||
|
||||
.fy-slide-frame {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fy-mocap-input {
|
||||
@include panel;
|
||||
border-radius: .5rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
// background-color: $panel;
|
||||
}
|
||||
|
||||
.fy-media-input-square {
|
||||
aspect-ratio: 1/1;
|
||||
height: 300px;
|
||||
|
||||
.fy-media-picker-empty-controls {}
|
||||
}
|
||||
|
||||
.fy-media-input-landscape {
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
max-height: 66vh;
|
||||
}
|
||||
|
||||
.fy-media-picker-empty, .fy-media-picker-full {
|
||||
// position: relative;
|
||||
// overflow: hidden;
|
||||
|
||||
.fy-media-picker-icon {
|
||||
position: absolute;
|
||||
height: 80%;
|
||||
opacity: 75%;
|
||||
// background-color: blue;
|
||||
margin: auto;
|
||||
inset: 0;
|
||||
|
||||
path { fill: $panel-inner-border}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.fy-media-picker-label-icon {
|
||||
margin-right: .5rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.fy-media-picker-mocap-preview {
|
||||
position: absolute;
|
||||
border-radius: .5rem;
|
||||
border: 1px solid rgba(#fff,.2);
|
||||
display: block;
|
||||
vertical-align: bottom;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fy-media-picker-full-controls {
|
||||
position: absolute;
|
||||
background-color: rgba(#000, .5);
|
||||
padding: 1rem;
|
||||
// backdrop-filter: blur(2px);
|
||||
overflow: hidden;
|
||||
border-radius: .5rem .5rem 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fy-media-picker-empty-controls {
|
||||
// background-color: red;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
|
||||
.fy-media-picker-row {
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fy-media-picker-button {
|
||||
background-color: rgba(#000,.15);
|
||||
border-radius: .5rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
transition: .25s all ease-in-out;
|
||||
color: rgba(#fff, .66);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(#000,.1);
|
||||
color: rgba(#fff, 1);
|
||||
}
|
||||
|
||||
label { }
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { a, useTransition } from "@react-spring/web";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import Iframe from "react-iframe";
|
||||
import { MediaBrowser } from "components/modals";
|
||||
import { FileWrapper, Label, Spinner } from "components/common";
|
||||
import { EntityType, MediaFilterProp } from "components/entities/EntityTypes";
|
||||
import { useFile, useMedia, useModal, useSession } from "hooks";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { faFileArrowUp, faGrid, faPersonWalking } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
|
||||
import { UploadMedia, UploadMediaResponse } from "@storyteller/components/src/api/media_files/UploadMedia";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import "./MocapInput.scss"
|
||||
|
||||
interface Props {
|
||||
label?: string,
|
||||
onChange?: any,
|
||||
type?: MediaFilterProp,
|
||||
aspectRatio?: "square" | "landscape" | "portrait"
|
||||
}
|
||||
|
||||
interface SlideProps {
|
||||
media?: MediaFile
|
||||
};
|
||||
|
||||
interface EmptySlideProps extends SlideProps {
|
||||
inputProps?: any,
|
||||
onSelect: any,
|
||||
open: any,
|
||||
type: MediaFilterProp,
|
||||
user: any
|
||||
};
|
||||
|
||||
interface AniProps {
|
||||
animating: boolean,
|
||||
className: string,
|
||||
isLeaving: boolean,
|
||||
render: any,
|
||||
style: any
|
||||
}
|
||||
|
||||
const MediaBusy = () => {
|
||||
return <Spinner/>;
|
||||
};
|
||||
|
||||
const MocapInputFull = ({ media }: SlideProps) => {
|
||||
const bucketConfig = new BucketConfig();
|
||||
const bvhUrl = media?.public_bucket_path ? bucketConfig.getGcsUrl(media.public_bucket_path) : "";
|
||||
|
||||
return <>
|
||||
<Iframe
|
||||
{...{
|
||||
url: `https://engine.fakeyou.com?mode=viewer&bvh=${bvhUrl}`,
|
||||
className: "fy-media-picker-mocap-preview",
|
||||
}}
|
||||
/>
|
||||
<div {...{ className: "fy-media-picker-full-controls" }}>
|
||||
Your file
|
||||
</div>
|
||||
</>;
|
||||
};
|
||||
|
||||
const MediaPickerEmpty = ({ media, onSelect, open, inputProps, type, user }: EmptySlideProps) => {
|
||||
const browserClick = () => open({
|
||||
component: MediaBrowser,
|
||||
props: { entityType: EntityType.media, onSelect, type, username: user?.username || "" }
|
||||
});
|
||||
|
||||
return <>
|
||||
<Icon {...{ className: "fy-media-picker-icon", icon: faPersonWalking }}/>
|
||||
<div {...{ className: "fy-media-picker-empty-controls" }}>
|
||||
<FileWrapper {...{ containerClass: "fy-media-picker-row", panelClass: "fy-media-picker-button", noStyle: true, ...inputProps }}>
|
||||
<>
|
||||
<Icon {...{ className: "fy-media-picker-label-icon", icon: faFileArrowUp }}/>
|
||||
Upload, click or drag here
|
||||
</>
|
||||
</FileWrapper>
|
||||
<div {...{ className: "fy-media-picker-row fy-media-picker-button", onClick: browserClick }}>
|
||||
<Icon {...{ className: "fy-media-picker-label-icon", icon: faGrid }}/>
|
||||
Choose from your media
|
||||
</div>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
const AniMod = ({ animating, className, isLeaving, render: Render, style, ...rest }: AniProps) => <a.div {...{
|
||||
className: `fy-slide-frame${ className ? " " + className : "" }`,
|
||||
style
|
||||
}}>
|
||||
<Render {...{ ...rest, animating }} />
|
||||
</a.div>;
|
||||
|
||||
export default function MocapInput({ aspectRatio = "square", label, onChange, type }: Props) {
|
||||
const { search } = useLocation();
|
||||
const presetToken = search ? new URLSearchParams(search).get("preset_token") : "";
|
||||
const [mediaToken,mediaTokenSet] = useState(presetToken || "");
|
||||
const { media, mediaSet } = useMedia({ mediaToken });
|
||||
const { user } = useSession();
|
||||
const { open } = useModal();
|
||||
const { clear, inputProps } = useFile({
|
||||
onChange: (inputFile: any) => {
|
||||
if (inputFile) UploadMedia({
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
file: inputFile,
|
||||
source: "file",
|
||||
})
|
||||
.then((res: UploadMediaResponse) => {
|
||||
if ("media_file_token" in res) {
|
||||
clear();
|
||||
mediaTokenSet(res.media_file_token);
|
||||
}
|
||||
});
|
||||
else console.log("🥺 no file");
|
||||
}
|
||||
});
|
||||
const onSelect = (data: MediaFile) => {
|
||||
mediaSet(data)
|
||||
onChange({ target: { name: "temp", value: data.token } });
|
||||
}
|
||||
const busy = false;
|
||||
const index = busy ? 0 : media ? 1 : 2;
|
||||
const [animating,animatingSet] = useState(false);
|
||||
|
||||
const transitions = useTransition(index, {
|
||||
config: { mass: 1, tension: 80, friction: 10 },
|
||||
from: { opacity: 0, transform: `translateX(${ 5 }rem)` },
|
||||
enter: { opacity: 1, transform: `translateX(0)` },
|
||||
leave: { opacity: 0, transform: `translateX(${ 5 }rem)` },
|
||||
onRest: () => animatingSet(false),
|
||||
onStart: () => animatingSet(true)
|
||||
});
|
||||
|
||||
|
||||
return <>
|
||||
<Label {...{ label }}/>
|
||||
<div {...{ className: `fy-mocap-input panel-inner${ aspectRatio ? " fy-media-input-" + aspectRatio : "" }`, }}>
|
||||
{
|
||||
// media ? <MocapInputFull {...{ media }}/> : <MediaPickerEmpty {...{ inputProps, media, onSelect, open, user }}/>
|
||||
transitions((style: any, i: number, state: any) => {
|
||||
let isLeaving = state.phase === "leave";
|
||||
let sharedProps = { animating, isLeaving, media, style };
|
||||
|
||||
return [
|
||||
<AniMod {...{ render: MediaBusy, className: "fy-media-picker-busy", ...sharedProps }}/>,
|
||||
<AniMod {...{ render: MocapInputFull, className: "fy-media-picker-full", ...sharedProps }}/>,
|
||||
<AniMod {...{ render: MediaPickerEmpty, className: "fy-media-picker-empty", inputProps, onSelect, open, type, user, ...sharedProps }}/>
|
||||
][i];
|
||||
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</>;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./MocapInput";
|
||||
@@ -95,6 +95,11 @@ describe("media files", () => {
|
||||
token: "MEDIA_FILE_TOKEN",
|
||||
media_type: MediaFileType.GLB,
|
||||
media_class: MediaFileClass.Unknown,
|
||||
media_links: {
|
||||
cdn_url: "",
|
||||
maybe_thumbnail_template: null,
|
||||
maybe_video_previews: null,
|
||||
},
|
||||
maybe_engine_category: null,
|
||||
maybe_animation_type: null,
|
||||
maybe_media_subtype: null,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { MediaFile as MediaFileOne } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import { MediaFile as MediaFileTwo } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import { EngineMode } from "./EngineMode";
|
||||
import { MediaFileSubtype } from "@storyteller/components/src/api/enums/MediaFileSubtype";
|
||||
import { MediaFileType } from "@storyteller/components/src/api/_common/enums/MediaFileType";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { MediaFileClass } from "@storyteller/components/src/api/enums/MediaFileClass";
|
||||
|
||||
// Storyteller Engine parameters
|
||||
@@ -11,69 +12,69 @@ import { MediaFileClass } from "@storyteller/components/src/api/enums/MediaFileC
|
||||
// https://www.notion.so/storytellerai/Studio-Iframe-Query-Params-a748a9929ec3404780c3884e7fb89bdb
|
||||
|
||||
interface GetEngineUrlArgs {
|
||||
mode: EngineMode,
|
||||
mode: EngineMode;
|
||||
|
||||
// override the URL for local development
|
||||
overrideURL?: string,
|
||||
overrideURL?: string;
|
||||
|
||||
// Polymorphic type of asset we're loading into the engine.
|
||||
asset: LoadableAsset
|
||||
asset: LoadableAsset;
|
||||
|
||||
// Optional skybox color (hex) or name
|
||||
// - Predefined skyboxes, eg. `gum_trees_4k`, `kloofendal_28d_misty_4k`, `meadow_4k`, `promenade_de_vidy_4k`, `scythian_tombs_4k`
|
||||
// - Hex colors, eg. `ff0000` or `1a1a27`.
|
||||
skybox?: string,
|
||||
skybox?: string;
|
||||
}
|
||||
|
||||
// Support multiple "Media File" API response payloads.
|
||||
type MediaFile = MediaFileOne | MediaFileTwo ;
|
||||
// type MediaFile = MediaFileOne | MediaFileTwo ;
|
||||
|
||||
// Used to load raw built-in objects into the engine.
|
||||
interface EngineObject {
|
||||
objectId: string;
|
||||
}
|
||||
|
||||
// Used to load media files that are known a priori to be
|
||||
// Used to load media files that are known a priori to be
|
||||
// Storyteller Engine scenes (scene.ron files).
|
||||
interface StorytellerSceneMediaFileToken {
|
||||
storytellerSceneMediaFileToken: string;
|
||||
}
|
||||
|
||||
// Used for loading media tokens of non-".scn.ron" files.
|
||||
// The extension must be provided to the engine, and the
|
||||
// The extension must be provided to the engine, and the
|
||||
// extension must include the leading period.
|
||||
interface SceneImportTokenAndExtension {
|
||||
// Media file token for the asset
|
||||
sceneImportToken: string;
|
||||
|
||||
// Extension, eg. ".glb", ".gltf", ".bvh".
|
||||
// Extension, eg. ".glb", ".gltf", ".bvh".
|
||||
// The extension must have a leading period.
|
||||
extension: string;
|
||||
}
|
||||
|
||||
export type LoadableAsset = MediaFile | EngineObject | StorytellerSceneMediaFileToken | SceneImportTokenAndExtension;
|
||||
export type LoadableAsset =
|
||||
| MediaFile
|
||||
| EngineObject
|
||||
| StorytellerSceneMediaFileToken
|
||||
| SceneImportTokenAndExtension;
|
||||
|
||||
const ENGINE_BASE_URL = "https://engine.fakeyou.com";
|
||||
|
||||
export function GetEngineUrl(args: GetEngineUrlArgs) : string {
|
||||
export function GetEngineUrl(args: GetEngineUrlArgs): string {
|
||||
let engineUrl = `${args.overrideURL || ENGINE_BASE_URL}/?mode=${args.mode}`;
|
||||
|
||||
|
||||
if (args.skybox) {
|
||||
engineUrl += `&skybox=${args.skybox}`;
|
||||
}
|
||||
|
||||
if (isMediaFile(args.asset)) {
|
||||
|
||||
const bucketAssetUrl = new BucketConfig().getGcsUrl(args.asset.public_bucket_path);
|
||||
const { mainURL } = MediaLinks(args.asset.media_links);
|
||||
|
||||
if (
|
||||
args.asset.media_type === MediaFileType.GLB &&
|
||||
args.asset.media_class === MediaFileClass.Scene &&
|
||||
(
|
||||
args.asset.maybe_media_subtype === MediaFileSubtype.StorytellerScene ||
|
||||
args.asset.maybe_media_subtype === MediaFileSubtype.SceneImport
|
||||
)
|
||||
(args.asset.maybe_media_subtype === MediaFileSubtype.StorytellerScene ||
|
||||
args.asset.maybe_media_subtype === MediaFileSubtype.SceneImport)
|
||||
) {
|
||||
// GLB Scenes
|
||||
// NB: Not sure if `maybe_media_subtype` is required to be "storyteller_scene".
|
||||
@@ -81,7 +82,7 @@ export function GetEngineUrl(args: GetEngineUrlArgs) : string {
|
||||
const sceneUrlRef = `remote://${args.asset.token}.glb`;
|
||||
engineUrl += `&sceneImport=${sceneUrlRef}`;
|
||||
} else if (
|
||||
args.asset.maybe_media_subtype === MediaFileSubtype.StorytellerScene ||
|
||||
args.asset.maybe_media_subtype === MediaFileSubtype.StorytellerScene ||
|
||||
args.asset.media_type === MediaFileType.SceneRon
|
||||
) {
|
||||
// Storyteller Engine Scenes
|
||||
@@ -90,27 +91,21 @@ export function GetEngineUrl(args: GetEngineUrlArgs) : string {
|
||||
// The engine, does, however, need a `.scn.ron` file extension.
|
||||
const sceneUrlRef = `remote://${args.asset.token}.scn.ron`;
|
||||
engineUrl += `&scene=${sceneUrlRef}`;
|
||||
} else if (args.asset.maybe_media_subtype === MediaFileSubtype.Mixamo) {
|
||||
engineUrl += `&mixamo=${mainURL}`;
|
||||
} else if (args.asset.maybe_media_subtype === MediaFileSubtype.MocapNet) {
|
||||
engineUrl += `&bvh=${mainURL}`;
|
||||
} else if (
|
||||
args.asset.maybe_media_subtype === MediaFileSubtype.Mixamo
|
||||
args.asset.media_type === MediaFileType.BVH &&
|
||||
(args.asset.maybe_media_subtype === null ||
|
||||
args.asset.maybe_media_subtype === undefined)
|
||||
) {
|
||||
engineUrl += `&mixamo=${bucketAssetUrl}`;
|
||||
} else if (
|
||||
args.asset.maybe_media_subtype === MediaFileSubtype.MocapNet
|
||||
) {
|
||||
engineUrl += `&bvh=${bucketAssetUrl}`;
|
||||
} else if (
|
||||
args.asset.media_type === MediaFileType.BVH && (
|
||||
args.asset.maybe_media_subtype === null ||
|
||||
args.asset.maybe_media_subtype === undefined
|
||||
)
|
||||
){
|
||||
// NB: Older BVH files that do not specify a subtype are MocapNet,
|
||||
// NB: Older BVH files that do not specify a subtype are MocapNet,
|
||||
// which will take a bare "bvh" argument.
|
||||
engineUrl += `&bvh=${bucketAssetUrl}`;
|
||||
engineUrl += `&bvh=${mainURL}`;
|
||||
} else {
|
||||
engineUrl += `&sceneImport=${bucketAssetUrl}`;
|
||||
engineUrl += `&sceneImport=${mainURL}`;
|
||||
}
|
||||
|
||||
} else if (isStorytellerSceneMediaFileToken(args.asset)) {
|
||||
// Storyteller Engine Scenes
|
||||
// NB: Storyteller Engine makes the API call to load the scene.
|
||||
@@ -118,11 +113,9 @@ export function GetEngineUrl(args: GetEngineUrlArgs) : string {
|
||||
// The engine, does, however, need a `.scn.ron` file extension.
|
||||
const sceneUrlRef = `remote://${args.asset.storytellerSceneMediaFileToken}.scn.ron`;
|
||||
engineUrl += `&scene=${sceneUrlRef}`;
|
||||
|
||||
} else if (isSceneImportTokenAndExtension(args.asset)) {
|
||||
const sceneUrlRef = `remote://${args.asset.sceneImportToken}${args.asset.extension}`;
|
||||
engineUrl += `&sceneImport=${sceneUrlRef}`;
|
||||
|
||||
} else if (isEngineObject(args.asset)) {
|
||||
// NB: This is an engine built-in, eg. `couch.gltf` or `sample-room.gltf`.
|
||||
engineUrl += `&objectId=${args.asset.objectId}`;
|
||||
@@ -132,22 +125,25 @@ export function GetEngineUrl(args: GetEngineUrlArgs) : string {
|
||||
}
|
||||
|
||||
// Type guard for media files
|
||||
const isMediaFile = (loadable: LoadableAsset) : loadable is MediaFile => {
|
||||
const isMediaFile = (loadable: LoadableAsset): loadable is MediaFile => {
|
||||
return "token" in loadable;
|
||||
}
|
||||
};
|
||||
|
||||
// Type guard for engine objects
|
||||
const isEngineObject = (loadable: LoadableAsset) : loadable is EngineObject => {
|
||||
const isEngineObject = (loadable: LoadableAsset): loadable is EngineObject => {
|
||||
return "objectId" in loadable;
|
||||
}
|
||||
};
|
||||
|
||||
// Type guard for .scn.ron media tokens
|
||||
const isStorytellerSceneMediaFileToken = (loadable: LoadableAsset) : loadable is StorytellerSceneMediaFileToken => {
|
||||
const isStorytellerSceneMediaFileToken = (
|
||||
loadable: LoadableAsset
|
||||
): loadable is StorytellerSceneMediaFileToken => {
|
||||
return "storytellerSceneMediaFileToken" in loadable;
|
||||
}
|
||||
};
|
||||
|
||||
// Type guard for loadable media tokens (not .scn.ron)
|
||||
const isSceneImportTokenAndExtension = (loadable: LoadableAsset) : loadable is SceneImportTokenAndExtension => {
|
||||
const isSceneImportTokenAndExtension = (
|
||||
loadable: LoadableAsset
|
||||
): loadable is SceneImportTokenAndExtension => {
|
||||
return "sceneImportToken" in loadable;
|
||||
}
|
||||
|
||||
};
|
||||
+1
-1
@@ -4,7 +4,7 @@ import MasonryGrid from "../MasonryGrid/MasonryGrid";
|
||||
import { useListContent, useSession } from "hooks";
|
||||
import prepFilter from "resources/prepFilter";
|
||||
import MediaCards from "../Card/MediaCards";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import { GetMediaByUser } from "@storyteller/components/src/api/media_files/GetMediaByUser";
|
||||
import SkeletonCard from "components/common/Card/SkeletonCard";
|
||||
import Pagination from "components/common/Pagination";
|
||||
|
||||
+58
-36
@@ -1,45 +1,54 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { Button, TempSelect } from "components/common";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import { faPlay, faPause, faRepeat, faArrowRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import {
|
||||
faPlay,
|
||||
faPause,
|
||||
faRepeat,
|
||||
faArrowRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
interface Props {
|
||||
actions?: any
|
||||
actions?: any;
|
||||
mediaFile?: MediaFile;
|
||||
}
|
||||
|
||||
export default function TempAudioPlayer({ actions = [], mediaFile }: Props) {
|
||||
const ref = useRef(null);
|
||||
const [finished,finishedSet] = useState(false);
|
||||
const [initialized,initializedSet] = useState(false);
|
||||
const [playing,playingSet] = useState(false);
|
||||
const [repeat,repeatSet] = useState(false);
|
||||
const [speed,speedSet] = useState(1);
|
||||
const [finished, finishedSet] = useState(false);
|
||||
const [initialized, initializedSet] = useState(false);
|
||||
const [playing, playingSet] = useState(false);
|
||||
const [repeat, repeatSet] = useState(false);
|
||||
const [speed, speedSet] = useState(1);
|
||||
const waveSurferRef = useRef<any>({ playing: () => false });
|
||||
const { mainURL } = MediaLinks(mediaFile?.media_links);
|
||||
|
||||
const options = [
|
||||
{ label: "x.5", value: .5 },
|
||||
{ label: "x.5", value: 0.5 },
|
||||
{ label: "x1", value: 1 },
|
||||
{ label: "x1.5", value: 1.5 }
|
||||
{ label: "x1.5", value: 1.5 },
|
||||
];
|
||||
|
||||
const baseActions = [
|
||||
{
|
||||
icon: playing ? faPause : faPlay ,
|
||||
const baseActions = [
|
||||
{
|
||||
icon: playing ? faPause : faPlay,
|
||||
onClick: () => {
|
||||
playingSet(!waveSurferRef.current.isPlaying());
|
||||
waveSurferRef.current.playPause();
|
||||
},
|
||||
square: true
|
||||
},
|
||||
{
|
||||
icon: repeat ? faArrowRight : faRepeat,
|
||||
onClick: () => { repeatSet(!repeat) },
|
||||
square: true,
|
||||
variant: "secondary"
|
||||
},
|
||||
{
|
||||
icon: repeat ? faArrowRight : faRepeat,
|
||||
onClick: () => {
|
||||
repeatSet(!repeat);
|
||||
},
|
||||
square: true,
|
||||
variant: "secondary",
|
||||
},
|
||||
{
|
||||
options,
|
||||
@@ -47,12 +56,11 @@ export default function TempAudioPlayer({ actions = [], mediaFile }: Props) {
|
||||
waveSurferRef.current.setPlaybackRate(target.value);
|
||||
speedSet(target.value);
|
||||
},
|
||||
value: speed
|
||||
value: speed,
|
||||
},
|
||||
...actions
|
||||
...actions,
|
||||
];
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized && ref.current && mediaFile) {
|
||||
initializedSet(true);
|
||||
@@ -66,23 +74,37 @@ export default function TempAudioPlayer({ actions = [], mediaFile }: Props) {
|
||||
cursorWidth: 2,
|
||||
normalize: true,
|
||||
});
|
||||
waveSurfer.load(new BucketConfig().getGcsUrl(mediaFile?.public_bucket_path));
|
||||
waveSurfer.on('ready', () => { waveSurferRef.current = waveSurfer });
|
||||
waveSurfer.on('finish', () => { finishedSet(true); });
|
||||
waveSurfer.load(mainURL);
|
||||
waveSurfer.on("ready", () => {
|
||||
waveSurferRef.current = waveSurfer;
|
||||
});
|
||||
waveSurfer.on("finish", () => {
|
||||
finishedSet(true);
|
||||
});
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
finishedSet(false);
|
||||
if (!repeat) { playingSet(false); }
|
||||
else { waveSurferRef.current.playPause(); }
|
||||
if (!repeat) {
|
||||
playingSet(false);
|
||||
} else {
|
||||
waveSurferRef.current.playPause();
|
||||
}
|
||||
}
|
||||
}, [finished, initialized, mainURL, mediaFile, repeat]);
|
||||
|
||||
},[finished,initialized,mediaFile,repeat]);
|
||||
|
||||
return <div {...{ className: "fy-audio-player" }}>
|
||||
return (
|
||||
<div {...{ className: "fy-audio-player" }}>
|
||||
<div {...{ ref }}></div>
|
||||
<div className="d-flex justify-content-center gap-2 mt-3">
|
||||
{ baseActions.map((action,key) => action.options ? <TempSelect { ...{ ...action, key } }/> : <Button { ...{ ...action, key } }/>) }
|
||||
{baseActions.map((action, key) =>
|
||||
action.options ? (
|
||||
<TempSelect {...{ ...action, key }} />
|
||||
) : (
|
||||
<Button {...{ ...action, key }} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import JobQueueTicker from "./JobQueueTicker";
|
||||
import Label from "./Label";
|
||||
import LikeButton from "./LikeButton";
|
||||
import LoginBlock from "./LoginBlock";
|
||||
import MocapInput from "./MocapInput";
|
||||
import MasonryGrid from "./MasonryGrid/MasonryGrid";
|
||||
import Modal, { ModalUtilities } from "./Modal";
|
||||
import Notification from "./Notification";
|
||||
@@ -106,7 +105,6 @@ export {
|
||||
Label,
|
||||
LikeButton,
|
||||
LoginBlock,
|
||||
MocapInput,
|
||||
MasonryGrid,
|
||||
Modal,
|
||||
NonRouteTabs,
|
||||
|
||||
@@ -1,31 +1,59 @@
|
||||
import React from "react";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome"; // for now
|
||||
import { faPersonWalking } from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
|
||||
export const ImagePreview = ({ data }: { data: any }) => {
|
||||
const bucketConfig = new BucketConfig();
|
||||
const coverImage = bucketConfig.getCdnUrl(data.public_bucket_path, 600, 100);
|
||||
export const ImagePreview = ({ data }: { data: MediaFile }) => {
|
||||
const { mainURL, imageThumb } = MediaLinks(data.media_links);
|
||||
|
||||
return <img {...{ alt: data.weight_name, className: "card-img", src: coverImage }} />;
|
||||
return (
|
||||
<img
|
||||
{...{
|
||||
alt: "",
|
||||
className: "card-img",
|
||||
src: imageThumb ? imageThumb(600) : mainURL || "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const VideoPreview = ({ data }: { data: any }) => {
|
||||
export const VideoPreview = ({
|
||||
data,
|
||||
hover,
|
||||
}: {
|
||||
data: MediaFile;
|
||||
hover: boolean;
|
||||
}) => {
|
||||
const { videoAnimated, videoStill } = MediaLinks(data.media_links);
|
||||
const { default_cover } = data?.cover_image || {};
|
||||
|
||||
const { default_cover, maybe_cover_image_public_bucket_path } = data?.cover_image || {};
|
||||
|
||||
const bucketConfig = new BucketConfig();
|
||||
//video doesnt have random cover image endpoint or thumbnails yet
|
||||
let coverImage = `/images/default-covers/${ default_cover?.image_index || 0 }.webp`;
|
||||
const coverImage = `/images/default-covers/${
|
||||
default_cover?.image_index || 0
|
||||
}.webp`;
|
||||
|
||||
if (maybe_cover_image_public_bucket_path) {
|
||||
coverImage = bucketConfig.getCdnUrl(maybe_cover_image_public_bucket_path, 600, 100);
|
||||
}
|
||||
|
||||
return <img {...{ alt: data.weight_name, className: "card-video", src: coverImage }} />;
|
||||
return (
|
||||
<img
|
||||
{...{
|
||||
alt: "",
|
||||
className: "card-video",
|
||||
src:
|
||||
hover && videoAnimated
|
||||
? videoAnimated(360)
|
||||
: videoStill
|
||||
? videoStill(600)
|
||||
: coverImage,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const MocapPreview = () => <Icon {...{ className: "card-img", icon: faPersonWalking }}/>;
|
||||
export const MocapPreview = () => (
|
||||
<Icon {...{ className: "card-img", icon: faPersonWalking }} />
|
||||
);
|
||||
|
||||
// export const ImagePreview = previwImg("card-img");
|
||||
// export const VideoPreview = previwImg("card-video");
|
||||
// export const VideoPreview = previwImg("card-video");
|
||||
+7
-4
@@ -4,12 +4,14 @@ import getCardUrl from "components/common/Card/getCardUrl";
|
||||
import "./CardWrapper.scss";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faStar } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useHover } from "hooks";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files";
|
||||
|
||||
interface Props {
|
||||
canHover?: boolean;
|
||||
card: React.ElementType;
|
||||
|
||||
data: any;
|
||||
data: MediaFile;
|
||||
onClick?: (e: any) => any;
|
||||
padding?: boolean;
|
||||
preview: React.ElementType;
|
||||
@@ -29,14 +31,15 @@ export default function CardWrapper({
|
||||
featured,
|
||||
...rest
|
||||
}: Props) {
|
||||
const [hover, hoverProps] = useHover({});
|
||||
const linkUrl = getCardUrl(data, source, type);
|
||||
const cardProps = { data, source, type, ...rest };
|
||||
const cardProps = { data, hover, source, type, ...rest };
|
||||
const className = `card ${padding ? "p-3" : ""} ${
|
||||
featured ? "card-featured" : ""
|
||||
} ${onClick || canHover ? "card-clickable" : ""}`.trim();
|
||||
|
||||
return onClick ? (
|
||||
<div {...{ className, onClick: () => onClick(data) }}>
|
||||
<div {...{ className, onClick: () => onClick(data), ...hoverProps }}>
|
||||
<Card {...cardProps} />
|
||||
{featured && (
|
||||
<div className="card-featured-badge">
|
||||
@@ -46,7 +49,7 @@ export default function CardWrapper({
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Link {...{ className, to: linkUrl }}>
|
||||
<Link {...{ className, to: linkUrl, ...hoverProps }}>
|
||||
<Card {...cardProps} />
|
||||
</Link>
|
||||
);
|
||||
|
||||
+15
-14
@@ -2,7 +2,10 @@ import React, { useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { a, useTransition } from "@react-spring/web";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import Iframe from "react-iframe";
|
||||
import { Area, Point } from "react-easy-crop";
|
||||
import {
|
||||
@@ -20,7 +23,6 @@ import {
|
||||
} from "components/entities/EntityTypes";
|
||||
import { WorkIndicator } from "components/svg";
|
||||
import { useMedia, useMediaUploader } from "hooks";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { Prompt } from "@storyteller/components/src/api/prompts/GetPrompts";
|
||||
import EntityInputEmpty from "./EntityInputEmpty";
|
||||
import EntityInputSidePanel from "./EntityInputSidePanel";
|
||||
@@ -112,15 +114,13 @@ const MediaBusy = ({ uploaderBusy, uploadProgress }: SlideProps) => {
|
||||
};
|
||||
|
||||
const EntityInputFull = ({ clear, cropProps, isNarrow, media }: SlideProps) => {
|
||||
const bucketConfig = new BucketConfig();
|
||||
const mediaUrl = media?.public_bucket_path
|
||||
? bucketConfig.getGcsUrl(media.public_bucket_path)
|
||||
: "";
|
||||
const mediaType = mediaCategoryfromString(media?.media_type || "");
|
||||
|
||||
const [crop, cropSet] = useState<Point>({ x: 0, y: 0 });
|
||||
const [zoom, zoomSet] = useState(1);
|
||||
|
||||
const { mainURL } = MediaLinks(media?.media_links);
|
||||
|
||||
const zoomSliderChange = ({ target }: ZoomSliderOnChangeEvent) =>
|
||||
zoomSet(target.value);
|
||||
|
||||
@@ -133,7 +133,7 @@ const EntityInputFull = ({ clear, cropProps, isNarrow, media }: SlideProps) => {
|
||||
crop,
|
||||
cropProps,
|
||||
cropSet,
|
||||
image: mediaUrl,
|
||||
image: mainURL,
|
||||
zoom,
|
||||
zoomSet,
|
||||
}}
|
||||
@@ -159,7 +159,7 @@ const EntityInputFull = ({ clear, cropProps, isNarrow, media }: SlideProps) => {
|
||||
crop,
|
||||
cropProps,
|
||||
cropSet,
|
||||
video: mediaUrl,
|
||||
video: mainURL,
|
||||
zoom,
|
||||
zoomSet,
|
||||
}}
|
||||
@@ -182,7 +182,7 @@ const EntityInputFull = ({ clear, cropProps, isNarrow, media }: SlideProps) => {
|
||||
<>
|
||||
<Iframe
|
||||
{...{
|
||||
url: `https://engine.fakeyou.com?mode=viewer&${media?.media_type}=${mediaUrl}`,
|
||||
url: `https://engine.fakeyou.com?mode=viewer&${media?.media_type}=${mainURL}`,
|
||||
className: "fy-entity-input-mocap-preview",
|
||||
}}
|
||||
/>
|
||||
@@ -272,9 +272,9 @@ export default function EntityInput({
|
||||
onError: () => {
|
||||
// @ts-ignore
|
||||
window.dataLayer.push({
|
||||
"event": "upload_failure",
|
||||
"page": GApage || "/",
|
||||
"user_id": "$user_id"
|
||||
event: "upload_failure",
|
||||
page: GApage || "/",
|
||||
user_id: "$user_id",
|
||||
});
|
||||
},
|
||||
onSuccess: (res: UploaderResponse) => {
|
||||
@@ -330,8 +330,9 @@ export default function EntityInput({
|
||||
<Label {...{ label }} />
|
||||
<div
|
||||
{...{
|
||||
className: `fy-entity-input ${className ? " " + className : ""}${isNarrow ? " fy-entity-input-narrow" : " fy-entity-input-wide"
|
||||
}`,
|
||||
className: `fy-entity-input ${className ? " " + className : ""}${
|
||||
isNarrow ? " fy-entity-input-narrow" : " fy-entity-input-wide"
|
||||
}`,
|
||||
ref: outerRef,
|
||||
}}
|
||||
>
|
||||
|
||||
+42
-24
@@ -5,32 +5,50 @@ import CardBadge from "../CardBadge";
|
||||
import CardFooter from "../CardFooter";
|
||||
|
||||
interface Props {
|
||||
data: any,
|
||||
makeRatingsProps?: MakeRatingsProps,
|
||||
preview: React.ElementType,
|
||||
showCreator?: boolean
|
||||
data: any;
|
||||
hover: boolean;
|
||||
makeRatingsProps?: MakeRatingsProps;
|
||||
preview: React.ElementType;
|
||||
showCreator?: boolean;
|
||||
}
|
||||
|
||||
export default function OverlayCard({ data, makeRatingsProps, preview: Preview, showCreator }: Props) {
|
||||
export default function OverlayCard({
|
||||
data,
|
||||
hover,
|
||||
makeRatingsProps,
|
||||
preview: Preview,
|
||||
showCreator,
|
||||
}: Props) {
|
||||
const timeCreated = moment(data.created_at || "").fromNow();
|
||||
|
||||
return <>
|
||||
<Preview {...{ data }}/>
|
||||
<div {...{ className: "card-img-overlay" }}>
|
||||
<div className="card-img-gradient" />
|
||||
<CardBadge {...{ className: `fy-entity-type${ data.media_type ? "-" + data.media_type : "" }`, label: data.media_type }}/>
|
||||
</div>
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
<p className="fs-7 opacity-75 mb-0">{ timeCreated }</p>
|
||||
return (
|
||||
<>
|
||||
<Preview {...{ data, hover }} />
|
||||
<div {...{ className: "card-img-overlay" }}>
|
||||
<div className="card-img-gradient" />
|
||||
<CardBadge
|
||||
{...{
|
||||
className: `fy-entity-type${
|
||||
data.media_type ? "-" + data.media_type : ""
|
||||
}`,
|
||||
label: data.media_type,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<CardFooter {...{
|
||||
creator: data?.maybe_creator,
|
||||
entityToken: data.token,
|
||||
entityType: "media_file",
|
||||
makeRatingsProps,
|
||||
showCreator
|
||||
}}/>
|
||||
</div>
|
||||
</>;
|
||||
};
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
<p className="fs-7 opacity-75 mb-0">{timeCreated}</p>
|
||||
</div>
|
||||
<CardFooter
|
||||
{...{
|
||||
creator: data?.maybe_creator,
|
||||
entityToken: data.token,
|
||||
entityType: "media_file",
|
||||
makeRatingsProps,
|
||||
showCreator,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+11
-8
@@ -5,12 +5,15 @@ import {
|
||||
FrontendInferenceJobType,
|
||||
InferenceJob,
|
||||
} from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinkUtility,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import {
|
||||
CancelJob,
|
||||
CancelJobResponse,
|
||||
} from "@storyteller/components/src/api/jobs/CancelJob";
|
||||
import { MediaURLs, useHover, useMedia, useSlides } from "hooks";
|
||||
import { useHover, useMedia, useSlides } from "hooks";
|
||||
import {
|
||||
JobState,
|
||||
jobStateCanChange,
|
||||
@@ -33,35 +36,35 @@ interface JobListItem extends InferenceJob {
|
||||
const BaseAction = ({
|
||||
canStop = false,
|
||||
hover = false,
|
||||
links,
|
||||
mediaFile,
|
||||
maybeResultToken = "",
|
||||
success = false,
|
||||
toggleSlide = () => {},
|
||||
urls,
|
||||
}: {
|
||||
canStop: boolean;
|
||||
hover: boolean;
|
||||
links: MediaLinkUtility;
|
||||
mediaFile: MediaFile;
|
||||
maybeResultToken: string;
|
||||
success: boolean;
|
||||
toggleSlide: () => void;
|
||||
urls: MediaURLs;
|
||||
}) =>
|
||||
canStop || success ? (
|
||||
<>
|
||||
<JobResultPreview
|
||||
{...{
|
||||
hover,
|
||||
links,
|
||||
mediaFile,
|
||||
mediaToken: maybeResultToken,
|
||||
show: success,
|
||||
urls,
|
||||
}}
|
||||
/>
|
||||
{success && (
|
||||
<Button
|
||||
{...{
|
||||
href: urls.file,
|
||||
href: links.mainURL,
|
||||
icon: faArrowDownToLine,
|
||||
onClick: (e: any) => {
|
||||
e.stopPropagation();
|
||||
@@ -206,7 +209,7 @@ export default function JobItem({
|
||||
}
|
||||
};
|
||||
|
||||
const { mediaFile, urls } = useMedia({ mediaToken: maybeResultToken || "" });
|
||||
const { links, mediaFile } = useMedia({ mediaToken: maybeResultToken || "" });
|
||||
|
||||
const slides = useSlides({
|
||||
index,
|
||||
@@ -216,11 +219,11 @@ export default function JobItem({
|
||||
props: {
|
||||
canStop: canStop(),
|
||||
hover,
|
||||
links,
|
||||
maybeResultToken,
|
||||
mediaFile,
|
||||
success,
|
||||
toggleSlide,
|
||||
urls,
|
||||
},
|
||||
},
|
||||
{ component: StopConfirm, props: { stopClick, toggleSlide } },
|
||||
|
||||
+8
-7
@@ -1,23 +1,24 @@
|
||||
import React from "react";
|
||||
import { MediaURLs } from "hooks";
|
||||
// import { MediaFileClass } from "../enums/MediaFileClass";
|
||||
import { MediaFileClass } from "@storyteller/components/src/api";
|
||||
|
||||
import { a, TransitionFn, useTransition } from "@react-spring/web";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinkUtility,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
|
||||
interface JobResultPreviewProps {
|
||||
hover: boolean;
|
||||
links: MediaLinkUtility;
|
||||
mediaFile: MediaFile;
|
||||
show?: boolean;
|
||||
urls: MediaURLs;
|
||||
}
|
||||
|
||||
export default function JobResultPreview({
|
||||
hover,
|
||||
links,
|
||||
mediaFile,
|
||||
show,
|
||||
urls,
|
||||
}: JobResultPreviewProps) {
|
||||
const transitions: TransitionFn<boolean, { opacity: number }> = useTransition(
|
||||
hover,
|
||||
@@ -39,7 +40,7 @@ export default function JobResultPreview({
|
||||
<a.img
|
||||
{...{
|
||||
className: "fy-inference-job-preview-static",
|
||||
src: urls.thumb(),
|
||||
src: links.videoStill ? links.videoStill(100) : "",
|
||||
}}
|
||||
/>
|
||||
{transitions((style, isHovering) =>
|
||||
@@ -47,7 +48,7 @@ export default function JobResultPreview({
|
||||
<a.img
|
||||
{...{
|
||||
className: "fy-inference-job-preview-gif",
|
||||
src: urls.gif(),
|
||||
src: links.videoAnimated ? links.videoAnimated(100) : "",
|
||||
style,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2,14 +2,14 @@ import { useEffect, useState } from "react";
|
||||
import {
|
||||
GetMedia,
|
||||
MediaFile,
|
||||
} from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import {
|
||||
GetPrompts,
|
||||
Prompt,
|
||||
} from "@storyteller/components/src/api/prompts/GetPrompts";
|
||||
import { DeleteMedia } from "@storyteller/components/src/api/media_files/DeleteMedia";
|
||||
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
|
||||
type ThumbFn = (width?: number, quality?: number) => string;
|
||||
|
||||
@@ -50,19 +50,7 @@ export default function useMedia({
|
||||
status === FetchStatus.in_progress ||
|
||||
writeStatus === FetchStatus.in_progress;
|
||||
|
||||
const bucketConfig = new BucketConfig();
|
||||
|
||||
const publicBucketPath = media?.public_bucket_path || "";
|
||||
|
||||
const bucketUrl = bucketConfig.getGcsUrl(publicBucketPath);
|
||||
|
||||
const urls = {
|
||||
file: bucketConfig.getGcsUrl(publicBucketPath),
|
||||
gif: (width = 360, quality = 20) =>
|
||||
bucketConfig.getCdnUrl(publicBucketPath + "-thumb.gif", width, quality),
|
||||
thumb: (width = 600, quality = 100) =>
|
||||
bucketConfig.getCdnUrl(publicBucketPath + "-thumb.jpg", width, quality),
|
||||
};
|
||||
const links = MediaLinks(media?.media_links);
|
||||
|
||||
useEffect(() => {
|
||||
// this condidition handles all media file fetches
|
||||
@@ -103,7 +91,8 @@ export default function useMedia({
|
||||
|
||||
return {
|
||||
busy,
|
||||
bucketUrl,
|
||||
// bucketUrl,
|
||||
links,
|
||||
media,
|
||||
mediaFile: media,
|
||||
mediaSet,
|
||||
@@ -112,7 +101,7 @@ export default function useMedia({
|
||||
reload,
|
||||
status,
|
||||
statusSet,
|
||||
urls,
|
||||
// urls,
|
||||
writeStatus,
|
||||
};
|
||||
}
|
||||
+15
-22
@@ -1,7 +1,10 @@
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import SceneCard from "./SceneCard";
|
||||
import Marquee from "react-fast-marquee";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
|
||||
interface ScrollingSceneCarouselProps {
|
||||
small?: boolean;
|
||||
@@ -11,15 +14,6 @@ interface ScrollingSceneCarouselProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface MediaItem {
|
||||
token: string;
|
||||
public_bucket_path: string;
|
||||
maybe_creator?: {
|
||||
username: string;
|
||||
};
|
||||
maybe_title?: string;
|
||||
}
|
||||
|
||||
export default function ScrollingSceneCarousel({
|
||||
small,
|
||||
showGradient = true,
|
||||
@@ -27,8 +21,7 @@ export default function ScrollingSceneCarousel({
|
||||
pauseOnHover = false,
|
||||
className,
|
||||
}: ScrollingSceneCarouselProps) {
|
||||
const [mediaItems, setMediaItems] = useState<MediaItem[]>([]);
|
||||
const bucketConfig = new BucketConfig();
|
||||
const [mediaItems, setMediaItems] = useState<MediaFile[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMediaItems();
|
||||
@@ -79,11 +72,11 @@ export default function ScrollingSceneCarousel({
|
||||
{firstHalf.map((item, index) => (
|
||||
<div key={index}>
|
||||
<SceneCard
|
||||
image={bucketConfig.getCdnUrl(
|
||||
item.public_bucket_path + "-thumb.gif",
|
||||
360,
|
||||
20
|
||||
)}
|
||||
image={
|
||||
MediaLinks(item.media_links).videoAnimated
|
||||
? MediaLinks(item?.media_links).videoAnimated!(360)
|
||||
: ""
|
||||
}
|
||||
alt={`Scene ${index}`}
|
||||
title={item.maybe_title || "Scene"}
|
||||
token={item.token}
|
||||
@@ -105,11 +98,11 @@ export default function ScrollingSceneCarousel({
|
||||
{secondHalf.map((item, index) => (
|
||||
<div key={index}>
|
||||
<SceneCard
|
||||
image={bucketConfig.getCdnUrl(
|
||||
item.public_bucket_path + "-thumb.gif",
|
||||
360,
|
||||
20
|
||||
)}
|
||||
image={
|
||||
MediaLinks(item.media_links).videoAnimated
|
||||
? MediaLinks(item?.media_links).videoAnimated!(360)
|
||||
: ""
|
||||
}
|
||||
alt={`Scene ${index}`}
|
||||
title={item.maybe_title || "Scene"}
|
||||
token={item.token}
|
||||
|
||||
+10
-19
@@ -1,24 +1,19 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./PrelaunchLanding.scss";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import { MainButton } from "./MainButton";
|
||||
|
||||
interface MediaItem {
|
||||
token: string;
|
||||
public_bucket_path: string;
|
||||
maybe_creator: {
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
function PrelaunchLanding(props: Props) {
|
||||
const totalGrids = 63;
|
||||
const [mediaItems, setMediaItems] = useState<MediaItem[]>([]);
|
||||
const [mediaItems, setMediaItems] = useState<MediaFile[]>([]);
|
||||
const [blankGrids, setBlankGrids] = useState<number[]>([15, 16, 17, 22, 23]);
|
||||
const [screenSize, setScreenSize] = useState("lg");
|
||||
|
||||
@@ -133,14 +128,8 @@ function PrelaunchLanding(props: Props) {
|
||||
}
|
||||
|
||||
const mediaItem = mediaItems[i - 1];
|
||||
const bucketConfig = new BucketConfig();
|
||||
const gifUrl = mediaItem
|
||||
? bucketConfig.getCdnUrl(
|
||||
mediaItem.public_bucket_path + "-thumb.gif",
|
||||
360,
|
||||
20
|
||||
)
|
||||
: "";
|
||||
const { videoAnimated } = MediaLinks(mediaItem.media_links);
|
||||
const gifUrl = videoAnimated ? videoAnimated(360) : "";
|
||||
|
||||
gridItems.push(
|
||||
<div
|
||||
@@ -164,7 +153,9 @@ function PrelaunchLanding(props: Props) {
|
||||
}
|
||||
src={gifUrl}
|
||||
className="w-100 h-100 object-fit-cover"
|
||||
alt={`Video by ${mediaItem.maybe_creator.username}`}
|
||||
alt={`Video by ${
|
||||
mediaItem.maybe_creator_user?.username || "user"
|
||||
}`}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,11 @@ import {
|
||||
import { useDebounce, useInferenceJobs, useLocalize, useModal } from "hooks";
|
||||
import LipsyncAudioPlayer from "./LipsyncAudioPlayer";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { GetMedia } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
GetMedia,
|
||||
GetMediaResponse,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import { SessionTtsInferenceResultList } from "v2/view/_common/SessionTtsInferenceResultsList";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { useHistory } from "react-router-dom";
|
||||
@@ -185,18 +189,20 @@ export const GenerateTts = ({
|
||||
if (maybeResultToken) {
|
||||
const fetchMedia = async () => {
|
||||
try {
|
||||
const response = await GetMedia(maybeResultToken, {});
|
||||
const response: GetMediaResponse = await GetMedia(
|
||||
maybeResultToken,
|
||||
{}
|
||||
);
|
||||
if (
|
||||
response &&
|
||||
response.media_file &&
|
||||
response.media_file.public_bucket_path &&
|
||||
response.media_file.maybe_text_transcript
|
||||
) {
|
||||
const audioLink = new BucketConfig().getGcsUrl(
|
||||
response.media_file.public_bucket_path
|
||||
);
|
||||
if (audioLink && currentAudioUrl === null) {
|
||||
setCurrentAudioUrl(audioLink);
|
||||
const { mainURL } = MediaLinks(response.media_file.media_links);
|
||||
|
||||
if (mainURL && currentAudioUrl === null) {
|
||||
setCurrentAudioUrl(mainURL);
|
||||
setTranscript(response.media_file.maybe_text_transcript || "");
|
||||
if (onResultToken) {
|
||||
onResultToken(response.media_file.token);
|
||||
|
||||
@@ -32,9 +32,12 @@ import { useInferenceJobs, useLocalize, useSession } from "hooks";
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { AITools } from "components/marketing";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import LoadingSpinner from "components/common/LoadingSpinner";
|
||||
import { GetMedia } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
GetMedia,
|
||||
// MediaFile, please use this type where applicabale
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useDocumentTitle } from "@storyteller/components/src/hooks/UseDocumentTitle";
|
||||
import { useHistory } from "react-router-dom";
|
||||
@@ -238,7 +241,7 @@ export default function Lipsync({ sessionSubscriptionsWrapper }: LipsyncProps) {
|
||||
};
|
||||
|
||||
const selectedSourceMediaLink = selectedSourceMedia?.public_bucket_path
|
||||
? new BucketConfig().getGcsUrl(selectedSourceMedia.public_bucket_path)
|
||||
? MediaLinks(selectedSourceMedia.media_links).mainURL
|
||||
: null;
|
||||
|
||||
const handleJobProgress = (progressPercentage: number | null) => {
|
||||
@@ -266,12 +269,10 @@ export default function Lipsync({ sessionSubscriptionsWrapper }: LipsyncProps) {
|
||||
response.media_file &&
|
||||
response.media_file.public_bucket_path
|
||||
) {
|
||||
const mediaLink = new BucketConfig().getGcsUrl(
|
||||
response.media_file.public_bucket_path
|
||||
);
|
||||
const { mainURL } = MediaLinks(response.media_file.media_links);
|
||||
|
||||
if (jobToken === lastEnqueuedJobToken) {
|
||||
setGeneratedVideoSrc(mediaLink);
|
||||
setGeneratedVideoSrc(mainURL);
|
||||
setIsGenerating(false);
|
||||
setJobPercentage(null);
|
||||
|
||||
|
||||
+7
-7
@@ -40,9 +40,11 @@ import {
|
||||
InferenceJob,
|
||||
} from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { AITools } from "components/marketing";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import SessionLpInferenceResultsList from "./SessionLpInferenceResultsList";
|
||||
import { GetMedia } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
GetMedia,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { LivePortraitDetails } from "@storyteller/components/src/api/model_inference/GetModelInferenceJobStatus";
|
||||
import { useDocumentTitle } from "@storyteller/components/src/hooks/UseDocumentTitle";
|
||||
@@ -359,9 +361,7 @@ export default function LivePortrait() {
|
||||
response.media_file &&
|
||||
response.media_file.public_bucket_path
|
||||
) {
|
||||
const mediaLink = new BucketConfig().getGcsUrl(
|
||||
response.media_file.public_bucket_path
|
||||
);
|
||||
const { mainURL } = MediaLinks(response.media_file.media_links);
|
||||
|
||||
const sourceIndex = sourceTokens.indexOf(
|
||||
livePortraitDetails.source_media_file_token
|
||||
@@ -375,7 +375,7 @@ export default function LivePortrait() {
|
||||
motionIndex,
|
||||
sourceToken: livePortraitDetails.source_media_file_token,
|
||||
motionToken: livePortraitDetails.face_driver_media_file_token,
|
||||
videoSrc: mediaLink,
|
||||
videoSrc: mainURL,
|
||||
jobToken,
|
||||
createdAt,
|
||||
};
|
||||
@@ -392,7 +392,7 @@ export default function LivePortrait() {
|
||||
selectedSourceIndex === newGeneratedVideo.sourceIndex &&
|
||||
selectedMotionIndex === newGeneratedVideo.motionIndex
|
||||
) {
|
||||
setGeneratedVideoSrc(mediaLink);
|
||||
setGeneratedVideoSrc(mainURL);
|
||||
setIsGenerating(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,10 +40,12 @@ import {
|
||||
} from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { AITools } from "components/marketing";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import LoadingSpinner from "components/common/LoadingSpinner";
|
||||
import SessionLpInferenceResultsList from "./SessionLpInferenceResultsList";
|
||||
import { GetMedia } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
GetMedia,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { LivePortraitDetails } from "@storyteller/components/src/api/model_inference/GetModelInferenceJobStatus";
|
||||
import { useDocumentTitle } from "@storyteller/components/src/hooks/UseDocumentTitle";
|
||||
@@ -543,9 +545,8 @@ export default function LivePortrait({
|
||||
setSelectedSourceMedia(media);
|
||||
};
|
||||
|
||||
const selectedSourceMediaLink = selectedSourceMedia?.public_bucket_path
|
||||
? new BucketConfig().getGcsUrl(selectedSourceMedia.public_bucket_path)
|
||||
: null;
|
||||
const selectedSourceMediaLink =
|
||||
MediaLinks(selectedSourceMedia.media_links).mainURL || null;
|
||||
|
||||
const handleJobTokens = async (
|
||||
maybeResultToken: string,
|
||||
@@ -569,9 +570,7 @@ export default function LivePortrait({
|
||||
response.media_file &&
|
||||
response.media_file.public_bucket_path
|
||||
) {
|
||||
const mediaLink = new BucketConfig().getGcsUrl(
|
||||
response.media_file.public_bucket_path
|
||||
);
|
||||
const { mainURL } = MediaLinks(response.media_file.media_links);
|
||||
|
||||
const sourceIndex = sourceTokens.indexOf(
|
||||
livePortraitDetails.source_media_file_token
|
||||
@@ -585,7 +584,7 @@ export default function LivePortrait({
|
||||
motionIndex,
|
||||
sourceToken: livePortraitDetails.source_media_file_token,
|
||||
motionToken: livePortraitDetails.face_driver_media_file_token,
|
||||
videoSrc: mediaLink,
|
||||
videoSrc: mainURL,
|
||||
jobToken,
|
||||
createdAt,
|
||||
};
|
||||
@@ -602,7 +601,7 @@ export default function LivePortrait({
|
||||
selectedSourceIndex === newGeneratedVideo.sourceIndex &&
|
||||
selectedMotionIndex === newGeneratedVideo.motionIndex
|
||||
) {
|
||||
setGeneratedVideoSrc(mediaLink);
|
||||
setGeneratedVideoSrc(mainURL);
|
||||
setIsGenerating(false);
|
||||
}
|
||||
|
||||
|
||||
+10
-14
@@ -2,12 +2,12 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Badge, Button } from "components/common";
|
||||
import FaceCropper from "./FaceCropper";
|
||||
import ThumbnailItem from "./ThumbnailItem";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import LoadingSpinner from "components/common/LoadingSpinner";
|
||||
import {
|
||||
GetMedia,
|
||||
MediaFile,
|
||||
} from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import {
|
||||
faUpload,
|
||||
faChevronLeft,
|
||||
@@ -116,9 +116,7 @@ const ThumbnailMediaPicker: React.FC<ThumbnailMediaPickerProps> = React.memo(
|
||||
}, [selectedIndex]);
|
||||
|
||||
const selectedMedia = mediaData[mediaTokens[selectedIndex]];
|
||||
const mediaLink = selectedMedia?.public_bucket_path
|
||||
? new BucketConfig().getGcsUrl(selectedMedia.public_bucket_path)
|
||||
: null;
|
||||
const { mainURL } = MediaLinks(selectedMedia.media_links);
|
||||
|
||||
useEffect(() => {
|
||||
if (onSelectedMediaChange) {
|
||||
@@ -188,9 +186,9 @@ const ThumbnailMediaPicker: React.FC<ThumbnailMediaPickerProps> = React.memo(
|
||||
|
||||
{cropper && cropArea ? (
|
||||
<>
|
||||
{mediaLink ? (
|
||||
{mainURL ? (
|
||||
<FaceCropper
|
||||
videoSrc={mediaLink}
|
||||
videoSrc={mainURL}
|
||||
onCropComplete={onCropComplete}
|
||||
showGrid={isCropping ? true : false}
|
||||
zoomWithScroll={isCropping ? true : false}
|
||||
@@ -213,11 +211,11 @@ const ThumbnailMediaPicker: React.FC<ThumbnailMediaPickerProps> = React.memo(
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<>
|
||||
{mediaLink ? (
|
||||
{mainURL ? (
|
||||
selectedMedia?.media_type === "image" ? (
|
||||
<img
|
||||
key={selectedIndex}
|
||||
src={mediaLink}
|
||||
src={mainURL}
|
||||
alt="Selected media"
|
||||
/>
|
||||
) : (
|
||||
@@ -232,7 +230,7 @@ const ThumbnailMediaPicker: React.FC<ThumbnailMediaPickerProps> = React.memo(
|
||||
preload="auto"
|
||||
draggable="false"
|
||||
>
|
||||
<source src={mediaLink} type="video/mp4" />
|
||||
<source src={mainURL} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
)
|
||||
@@ -262,9 +260,7 @@ const ThumbnailMediaPicker: React.FC<ThumbnailMediaPickerProps> = React.memo(
|
||||
<div className="row g-2 order-2 order-lg-3 position-relative">
|
||||
{paginatedMediaTokens.map((token, index) => {
|
||||
const media = mediaData[token];
|
||||
const mediaLink = media?.public_bucket_path
|
||||
? new BucketConfig().getGcsUrl(media.public_bucket_path)
|
||||
: null;
|
||||
const { mainURL: itemMainUrl } = MediaLinks(media.media_links);
|
||||
|
||||
return (
|
||||
<ThumbnailItem
|
||||
@@ -272,7 +268,7 @@ const ThumbnailMediaPicker: React.FC<ThumbnailMediaPickerProps> = React.memo(
|
||||
index={index + currentPage * itemsPerPage}
|
||||
selectedIndex={selectedIndex}
|
||||
handleThumbnailClick={handleThumbnailClick}
|
||||
poster={mediaLink || ""}
|
||||
poster={itemMainUrl || ""}
|
||||
mediaType={media?.media_type}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,10 @@ import React, { useState } from "react";
|
||||
import { useMedia, useSession } from "hooks";
|
||||
import { Button, Container, Panel } from "components/common";
|
||||
import { EntityInput } from "components/entities";
|
||||
import { EditCoverImage, EditCoverImageResponse } from "@storyteller/components/src/api/media_files/EditCoverImage";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import {
|
||||
EditCoverImage,
|
||||
EditCoverImageResponse,
|
||||
} from "@storyteller/components/src/api/media_files/EditCoverImage";
|
||||
import "./EditCoverImage.scss";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
||||
@@ -12,63 +14,75 @@ export default function EditCoverImagePage() {
|
||||
const { studioAccessCheck } = useSession();
|
||||
const history = useHistory();
|
||||
const [coverToken, coverTokenSet] = useState("");
|
||||
const { media } = useMedia({ mediaToken: targetToken });
|
||||
const bucketConfig = new BucketConfig();
|
||||
const currentSrc = bucketConfig.getGcsUrl(media?.cover_image?.maybe_cover_image_public_bucket_path || "");
|
||||
const {
|
||||
media,
|
||||
links: { mainURL },
|
||||
} = useMedia({ mediaToken: targetToken });
|
||||
|
||||
const onClick = () => {
|
||||
if (coverToken) {
|
||||
EditCoverImage(targetToken,{
|
||||
EditCoverImage(targetToken, {
|
||||
cover_image_media_file_token: coverToken,
|
||||
})
|
||||
.then((res: EditCoverImageResponse) => {
|
||||
}).then((res: EditCoverImageResponse) => {
|
||||
history.push(`/media/${targetToken}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return studioAccessCheck(<Container {...{
|
||||
className: "cover-image-page mt-5",
|
||||
type: "panel"
|
||||
}}>
|
||||
return studioAccessCheck(
|
||||
<Container
|
||||
{...{
|
||||
className: "cover-image-page mt-5",
|
||||
type: "panel",
|
||||
}}
|
||||
>
|
||||
<Panel {...{ padding: true }}>
|
||||
<header className="d-flex gap-3 flex-wrap">
|
||||
<h1 className="fw-semibold">
|
||||
Edit Cover Image
|
||||
</h1>
|
||||
<Button {...{
|
||||
disabled: !coverToken,
|
||||
label: "Update cover image",
|
||||
onClick
|
||||
}}/>
|
||||
<h1 className="fw-semibold">Edit Cover Image</h1>
|
||||
<Button
|
||||
{...{
|
||||
disabled: !coverToken,
|
||||
label: "Update cover image",
|
||||
onClick,
|
||||
}}
|
||||
/>
|
||||
</header>
|
||||
<div {...{
|
||||
className: "cover-image-panel"
|
||||
}}>
|
||||
<div
|
||||
{...{
|
||||
className: "cover-image-panel",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
Current cover image
|
||||
{
|
||||
media?.cover_image?.maybe_cover_image_public_bucket_path ?
|
||||
<img {...{
|
||||
alt: "cover art",
|
||||
className: "cover-image-current",
|
||||
src: currentSrc
|
||||
}}/> :
|
||||
{media?.cover_image?.maybe_cover_image_public_bucket_path ? (
|
||||
<img
|
||||
{...{
|
||||
alt: "cover art",
|
||||
className: "cover-image-current",
|
||||
src: mainURL,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div {...{ className: "cover-image-empty" }}>No cover image</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<EntityInput {...{
|
||||
<EntityInput
|
||||
{...{
|
||||
accept: ["image"],
|
||||
aspectRatio: "square",
|
||||
label: "Choose a cover image",
|
||||
name: "coverToken",
|
||||
value: coverToken,
|
||||
onChange: ({ target }: { target: any }) => { coverTokenSet(target.value) },
|
||||
type: "media"
|
||||
}}/>
|
||||
onChange: ({ target }: { target: any }) => {
|
||||
coverTokenSet(target.value);
|
||||
},
|
||||
type: "media",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Container>);
|
||||
};
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from "react";
|
||||
import MediaAudioPlayer from "./MediaAudioPlayer";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
|
||||
interface MediaAudioComponentProps {
|
||||
mediaFile: MediaFile;
|
||||
}
|
||||
|
||||
export default function MediaAudioComponent({
|
||||
mediaFile,
|
||||
}: MediaAudioComponentProps) {
|
||||
return (
|
||||
<div className="w-100">
|
||||
<MediaAudioPlayer mediaFile={mediaFile} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
import React, { useEffect, useState, memo } from "react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import {
|
||||
faPlay,
|
||||
faPause,
|
||||
faRepeat,
|
||||
faArrowRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import Button from "components/common/Button";
|
||||
|
||||
enum PlaybackSpeed {
|
||||
@@ -42,14 +44,13 @@ const MediaAudioPlayer = memo(({ mediaFile }: MediaAudioPlayerProps) => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const audioLink = new BucketConfig().getGcsUrl(
|
||||
mediaFile.public_bucket_path
|
||||
);
|
||||
const { mainURL } = MediaLinks(mediaFile.media_links);
|
||||
|
||||
if (waveSurfer) {
|
||||
waveSurfer.load(audioLink);
|
||||
waveSurfer.load(mainURL);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [waveSurfer, mediaFile.public_bucket_path, BucketConfig]);
|
||||
}, [waveSurfer, mediaFile.public_bucket_path]);
|
||||
|
||||
useEffect(() => {
|
||||
if (waveSurfer) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import React from "react";
|
||||
|
||||
interface MediaImageComponentProps {
|
||||
mediaFile: MediaFile;
|
||||
}
|
||||
|
||||
export default function MediaImageComponent({
|
||||
mediaFile,
|
||||
}: MediaImageComponentProps) {
|
||||
const imagePath = new BucketConfig().getGcsUrl(mediaFile.public_bucket_path);
|
||||
return (
|
||||
<div>
|
||||
<img src={imagePath} alt="test" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import MediaAudioPlayer from "./MediaAudioPlayer";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import Container from "components/common/Container";
|
||||
import Panel from "components/common/Panel";
|
||||
import PageHeader from "components/layout/PageHeader";
|
||||
@@ -58,6 +61,7 @@ export default function MediaPage({
|
||||
animationType,
|
||||
animationTypeChange,
|
||||
bookmarkButtonProps,
|
||||
bucketUrl,
|
||||
canAccessStudio,
|
||||
canBanUsers,
|
||||
canEdit,
|
||||
@@ -89,7 +93,7 @@ export default function MediaPage({
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
const mediaItems = response.results.map(result => ({
|
||||
url: bucketConfig.getGcsUrl(result.public_bucket_path),
|
||||
url: MediaLinks(result.media_links).mainURL,
|
||||
token: result.token,
|
||||
}));
|
||||
setImages(mediaItems);
|
||||
@@ -184,7 +188,7 @@ export default function MediaPage({
|
||||
if (mediaFile.public_bucket_path) {
|
||||
sdMediaImage = [
|
||||
{
|
||||
url: bucketConfig.getGcsUrl(mediaFile.public_bucket_path),
|
||||
url: bucketUrl,
|
||||
token: mediaFile?.token,
|
||||
},
|
||||
];
|
||||
@@ -266,8 +270,7 @@ export default function MediaPage({
|
||||
? mediaTypeLabels[mediaFile?.media_type]
|
||||
: "";
|
||||
|
||||
let downloadLink =
|
||||
activeSlide.url || bucketConfig.getGcsUrl(mediaFile?.public_bucket_path);
|
||||
let downloadLink = activeSlide.url || bucketUrl;
|
||||
|
||||
const sharePath = `/media/${activeSlide.token || mediaFile?.token || ""}`;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
EngineCategory,
|
||||
MediaFileType,
|
||||
} from "@storyteller/components/src/api/_common/enums";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files";
|
||||
import { Prompt } from "@storyteller/components/src/api/prompts/GetPrompts";
|
||||
import { CreateFeaturedItem } from "@storyteller/components/src/api/featured_items/CreateFeaturedItem";
|
||||
import { DeleteFeaturedItem } from "@storyteller/components/src/api/featured_items/DeleteFeaturedItem";
|
||||
@@ -69,7 +69,7 @@ export default function MediaPageSwitch() {
|
||||
const [engineCategory, engineCategorySet] =
|
||||
useState<EngineCategoryOptions>(null);
|
||||
|
||||
const { bucketUrl, mediaFile, prompt, remove, status } = useMedia({
|
||||
const { links, mediaFile, prompt, remove, status } = useMedia({
|
||||
mediaToken: urlToken,
|
||||
onSuccess: (res: any) => {
|
||||
isFeaturedSet(res.is_featured);
|
||||
@@ -80,6 +80,7 @@ export default function MediaPageSwitch() {
|
||||
bookmarks.gather({ res, key: "token" });
|
||||
},
|
||||
});
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
const canEdit = canEditMediaFile(mediaFile?.maybe_creator_user?.user_token);
|
||||
@@ -181,7 +182,7 @@ export default function MediaPageSwitch() {
|
||||
const subViewProps: MediaSubViewProps = {
|
||||
animationType,
|
||||
animationTypeChange,
|
||||
bucketUrl,
|
||||
bucketUrl: links.mainURL,
|
||||
bookmarkButtonProps,
|
||||
canAccessStudio,
|
||||
canBanUsers,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import React from "react";
|
||||
|
||||
interface MediaVideoComponentProps {
|
||||
@@ -9,13 +11,11 @@ interface MediaVideoComponentProps {
|
||||
export default function MediaVideoComponent({
|
||||
mediaFile,
|
||||
}: MediaVideoComponentProps) {
|
||||
let mediaLink = mediaFile
|
||||
? new BucketConfig().getGcsUrl(mediaFile.public_bucket_path)
|
||||
: "";
|
||||
const { mainURL } = MediaLinks(mediaFile?.media_links);
|
||||
|
||||
return mediaFile && mediaFile.public_bucket_path ? (
|
||||
<video className="rounded" controls width="100%" height="auto">
|
||||
<source src={mediaLink} />
|
||||
<source src={mainURL} />
|
||||
Your browser does not support the video element.
|
||||
</video>
|
||||
) : null;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Prompt } from "@storyteller/components/src/api/prompts/GetPrompts";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import {
|
||||
MediaFile,
|
||||
MediaLinks,
|
||||
} from "@storyteller/components/src/api/media_files";
|
||||
import { Button, Panel } from "components/common";
|
||||
import { STYLES_BY_KEY } from "common/StyleOptions";
|
||||
import { faCopy } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
|
||||
interface PromptViewerProps {
|
||||
isModerator: boolean;
|
||||
@@ -21,6 +23,8 @@ export default function PromptViewer({
|
||||
const [copyPositiveButtonText, setCopyPositiveButtonText] = useState("Copy");
|
||||
const [copyNegativeButtonText, setCopyNegativeButtonText] = useState("Copy");
|
||||
|
||||
const { mainURL } = MediaLinks(mediaFile?.media_links);
|
||||
|
||||
const copyToClipboard = async (
|
||||
text: string,
|
||||
copyTextSet: React.Dispatch<React.SetStateAction<string>>
|
||||
@@ -45,14 +49,7 @@ export default function PromptViewer({
|
||||
</div>
|
||||
<div className="panel-inner p-2 rounded">
|
||||
<p className="fs-7">
|
||||
<a
|
||||
href={new BucketConfig().getGcsUrl(
|
||||
mediaFile?.public_bucket_path.replace(
|
||||
".mp4",
|
||||
".no_watermark.mp4"
|
||||
)
|
||||
)}
|
||||
>
|
||||
<a href={mainURL.replace(".mp4", ".no_watermark.mp4")}>
|
||||
Download Without Watermark
|
||||
</a>{" "}
|
||||
(Staff Only)
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import Scene3D from "components/common/Scene3D/Scene3D";
|
||||
import { EngineMode } from "components/common/Scene3D/EngineMode";
|
||||
|
||||
@@ -12,10 +12,10 @@ const SKYBOX = "333348"; // Looks good (lighter)
|
||||
//const SKYBOX = "3f3f55"; // too light
|
||||
|
||||
export interface EngineMediaPanelArgs {
|
||||
mediaFile: MediaFile,
|
||||
mediaFile: MediaFile;
|
||||
}
|
||||
|
||||
export function EngineMediaPanel({ mediaFile } : EngineMediaPanelArgs) {
|
||||
export function EngineMediaPanel({ mediaFile }: EngineMediaPanelArgs) {
|
||||
return (
|
||||
<Scene3D
|
||||
mode={EngineMode.Viewer}
|
||||
@@ -23,6 +23,6 @@ export function EngineMediaPanel({ mediaFile } : EngineMediaPanelArgs) {
|
||||
fullScreen={false}
|
||||
className="fy-studio-frame"
|
||||
asset={mediaFile}
|
||||
/>
|
||||
/>
|
||||
);
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
export { default } from "./pageWorkflowControls";
|
||||
-282
@@ -1,282 +0,0 @@
|
||||
import React, {useState, useRef} from "react";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import { useMedia } from "hooks";
|
||||
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { EnqueueVideoWorkflow } from "@storyteller/components/src/api/video_workflow";
|
||||
import { states, Action, State } from "../../videoWorkflowReducer";
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
ErrorMessage,
|
||||
InputSeed,
|
||||
Panel,
|
||||
SelectModal,
|
||||
Spinner,
|
||||
TextArea
|
||||
} from "components/common";
|
||||
|
||||
import SelectModalWeightsTabs from "components/common/SelectModalWeightsTabs";
|
||||
|
||||
import SectionControlNets, {ControlNetsInitialValues as CnIvs} from "./sectionControlNets";
|
||||
import SectionVideoSettings, {VideoSettingsInitialValues as VideoIvs} from "./sectionVideoSettings";
|
||||
import SectionAdvanceOptions, {AdvanceOptionsInitialValues as AdvIvs} from "./sectionAdvanceOptions";
|
||||
import {
|
||||
isInputValid,
|
||||
mapRequest,
|
||||
WorkflowValuesType
|
||||
} from "./helpers";
|
||||
|
||||
export default function PageFilterControls({
|
||||
debug, t, pageState, dispatchPageState, parentPath
|
||||
}: {
|
||||
debug?: boolean;
|
||||
t: Function;
|
||||
pageState: State;
|
||||
parentPath: string;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const { mediaToken } = useParams<any>();
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
useMedia({
|
||||
mediaToken: pageState.mediaFileToken || mediaToken,
|
||||
onSuccess: (res: any) => {
|
||||
dispatchPageState({
|
||||
type: 'loadFileSuccess',
|
||||
payload: {
|
||||
mediaFile: res,
|
||||
mediaFileToken: pageState.mediaFileToken || mediaToken
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [workflowValues, setWorkflowValues] = useState<WorkflowValuesType>({
|
||||
fileToken: pageState.mediaFileToken || mediaToken,
|
||||
outputPath: "",
|
||||
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
|
||||
seed: "",
|
||||
sdModelToken: "",
|
||||
sdModelTitle:"",
|
||||
loraModelToken: "",
|
||||
loraModelTitle: "",
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
...VideoIvs,
|
||||
...AdvIvs,
|
||||
...CnIvs
|
||||
});
|
||||
|
||||
const handleOnChange = (val:{[key: string]: number|string|boolean|undefined}) => {
|
||||
setWorkflowValues((curr)=>({...curr, ...val}));
|
||||
}
|
||||
const handleReset = ()=>{
|
||||
setWorkflowValues((curr)=>({
|
||||
fileToken: pageState.mediaFileToken || mediaToken,
|
||||
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
|
||||
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
|
||||
seed: curr.seed,
|
||||
sdModelToken: "",
|
||||
sdModelTitle: "",
|
||||
loraModelToken: "",
|
||||
loraModelTitle: "",
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
...VideoIvs,
|
||||
width: curr.width,
|
||||
height: curr.height,
|
||||
maxFrames: curr.maxFrames,
|
||||
...AdvIvs,
|
||||
...CnIvs
|
||||
}));
|
||||
}
|
||||
const history = useHistory();
|
||||
const handleGenerate = ()=>{
|
||||
if(debug) console.log(workflowValues)
|
||||
|
||||
if (isInputValid(workflowValues)){
|
||||
const request = mapRequest(workflowValues);
|
||||
console.log(request);
|
||||
EnqueueVideoWorkflow(request).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
dispatchPageState({
|
||||
type: 'enqueueFilterSuccess',
|
||||
payload: {
|
||||
inferenceJobToken: res.inference_job_token
|
||||
}
|
||||
})
|
||||
}else{
|
||||
console.log(res);
|
||||
}
|
||||
})
|
||||
dispatchPageState({type: 'enqueueFilter'})
|
||||
history.push(`${parentPath}/jobs`);
|
||||
}else{
|
||||
alert("you must pick an sd weight and input a prompt");
|
||||
}
|
||||
}
|
||||
|
||||
if (pageState.mediaFile){
|
||||
const mediaLink = new BucketConfig().getGcsUrl(pageState.mediaFile.public_bucket_path);
|
||||
|
||||
if (mediaLink)
|
||||
return(
|
||||
<>
|
||||
<Panel>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-6">
|
||||
<div className="fy-basic-video">
|
||||
<video controls ref={videoRef}
|
||||
style={{
|
||||
maxHeight: "500px",
|
||||
objectFit: "contain"
|
||||
}}>
|
||||
<source src={mediaLink} type="video/mp4"/>
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<SectionVideoSettings
|
||||
workflowValues={workflowValues}
|
||||
onChange={handleOnChange}
|
||||
videoElement={videoRef?.current}
|
||||
/>
|
||||
<div className="d-flex my-3 justify-content-end">
|
||||
<Button
|
||||
className="me-3"
|
||||
label={t("button.previewWorkflow")}
|
||||
tooltip="feature not available yet"
|
||||
variant="primary"
|
||||
disabled
|
||||
/>
|
||||
<Button
|
||||
className="me-3"
|
||||
label={t("button.resetToDefault")}
|
||||
tooltip="Reset all variables to default values"
|
||||
variant="primary"
|
||||
onClick={handleReset}
|
||||
/>
|
||||
<Button
|
||||
className="me-3"
|
||||
label={t("button.enqueueWorkflow")}
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
<Accordion className="mt-4">
|
||||
<Accordion.Item title={"Basics"} defaultOpen>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModalWeightsTabs
|
||||
modalTitle="Select a Stable Diffusion Weight"
|
||||
inputLabel="Select a Stable Diffusion Weight"
|
||||
weightType="sd_1.5"
|
||||
onSelect={({title,token})=>{
|
||||
handleOnChange({
|
||||
"sdModelTitle": title,
|
||||
"sdModelToken": token,
|
||||
});
|
||||
}}
|
||||
value={{
|
||||
token:workflowValues.sdModelToken,
|
||||
title:workflowValues.sdModelTitle,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModal
|
||||
modalTitle="Select a LoRA Weight"
|
||||
label="Additional LoRA Weight"
|
||||
onSelect={({title,token})=>{
|
||||
console.log(`calling from select modal result select ${token}`);
|
||||
handleOnChange({
|
||||
"loraModelTitle": title,
|
||||
"loraModelToken": token
|
||||
});
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All LoRA Weights",
|
||||
tabKey: "allLoraWeights",
|
||||
typeFilter: "loRA",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedLoraWeights",
|
||||
typeFilter: "loRA",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
label: "Prompt",
|
||||
placeholder: "Enter a prompt",
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange({"posPrompt": e.target.value}),
|
||||
value: workflowValues.posPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
label: "Negative Prompt",
|
||||
placeholder: "Enter Negative Prompt",
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange({"negPrompt": e.target.value}),
|
||||
value: workflowValues.negPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<InputSeed label="Seed" onChange={
|
||||
(val:string)=>handleOnChange({seed: val})
|
||||
}/>
|
||||
</div>
|
||||
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title="Advance" defaultOpen>
|
||||
<SectionAdvanceOptions
|
||||
workflowValues={workflowValues}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title="Control Nets" defaultOpen>
|
||||
<SectionControlNets
|
||||
workflowValues={workflowValues}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
<div className="row g-3 py-3">
|
||||
<div className="col-12 d-flex justify-content-end">
|
||||
<Button
|
||||
label={t("button.enqueueWorkflow")}
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}else if (pageState.status <= states.FILE_LOADING){
|
||||
return (
|
||||
<Panel>
|
||||
<p>Loading Files</p>
|
||||
<Spinner />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
return <ErrorMessage />;
|
||||
}
|
||||
+52
-51
@@ -1,6 +1,6 @@
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
|
||||
export enum states{
|
||||
export enum states {
|
||||
NO_FILE,
|
||||
FILE_STAGED,
|
||||
FILE_SELECTED,
|
||||
@@ -9,7 +9,7 @@ export enum states{
|
||||
FILE_LOADING,
|
||||
FILE_LOADED,
|
||||
WORKFLOW_ENQUEUEING,
|
||||
WORKFLOW_ENQUEUED
|
||||
WORKFLOW_ENQUEUED,
|
||||
}
|
||||
|
||||
export type State = {
|
||||
@@ -17,71 +17,72 @@ export type State = {
|
||||
mediaFileToken?: string;
|
||||
mediaFile?: MediaFile;
|
||||
inferenceJobToken?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type Action =
|
||||
| {type: 'reset'}
|
||||
| {type: 'stagedFile'}
|
||||
| {type: 'clearedFile'}
|
||||
| {type: 'selectedFile', payload: { mediaFileToken: string}}
|
||||
| {type: 'uploadFile'}
|
||||
| {type: 'uploadFileSuccess', payload:{ mediaFileToken: string}}
|
||||
| {type: 'loadFile'}
|
||||
| {type: 'loadFileSuccess', payload:{
|
||||
mediaFileToken: string, mediaFile: MediaFile
|
||||
}}
|
||||
| {type: 'enqueueFilter'}
|
||||
| {type: 'enqueueFilterSuccess', payload: {inferenceJobToken: string|undefined}}
|
||||
export type Action =
|
||||
| { type: "reset" }
|
||||
| { type: "stagedFile" }
|
||||
| { type: "clearedFile" }
|
||||
| { type: "selectedFile"; payload: { mediaFileToken: string } }
|
||||
| { type: "uploadFile" }
|
||||
| { type: "uploadFileSuccess"; payload: { mediaFileToken: string } }
|
||||
| { type: "loadFile" }
|
||||
| {
|
||||
type: "loadFileSuccess";
|
||||
payload: {
|
||||
mediaFileToken: string;
|
||||
mediaFile: MediaFile;
|
||||
};
|
||||
}
|
||||
| { type: "enqueueFilter" }
|
||||
| {
|
||||
type: "enqueueFilterSuccess";
|
||||
payload: { inferenceJobToken: string | undefined };
|
||||
};
|
||||
|
||||
|
||||
export function reducer (state: State, action: Action): State {
|
||||
|
||||
switch(action.type){
|
||||
case 'stagedFile':
|
||||
return {...state, status: states.FILE_STAGED}
|
||||
case 'clearedFile':
|
||||
return {...state,
|
||||
status: states.NO_FILE,
|
||||
mediaFileToken: ""
|
||||
}
|
||||
case 'selectedFile':
|
||||
export function reducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case "stagedFile":
|
||||
return { ...state, status: states.FILE_STAGED };
|
||||
case "clearedFile":
|
||||
return { ...state, status: states.NO_FILE, mediaFileToken: "" };
|
||||
case "selectedFile":
|
||||
return {
|
||||
...state,
|
||||
status: states.FILE_SELECTED,
|
||||
mediaFileToken: action.payload.mediaFileToken
|
||||
}
|
||||
case 'uploadFile':
|
||||
return {...state,status: states.FILE_UPLOADING};
|
||||
case 'uploadFileSuccess':
|
||||
mediaFileToken: action.payload.mediaFileToken,
|
||||
};
|
||||
case "uploadFile":
|
||||
return { ...state, status: states.FILE_UPLOADING };
|
||||
case "uploadFileSuccess":
|
||||
return {
|
||||
...state,
|
||||
status: states.FILE_UPLOADED,
|
||||
mediaFileToken: action.payload.mediaFileToken
|
||||
}
|
||||
case 'loadFile':
|
||||
return {...state, status: states.FILE_LOADING}
|
||||
case 'loadFileSuccess':
|
||||
mediaFileToken: action.payload.mediaFileToken,
|
||||
};
|
||||
case "loadFile":
|
||||
return { ...state, status: states.FILE_LOADING };
|
||||
case "loadFileSuccess":
|
||||
console.log("LOAD FILE SUCCESS");
|
||||
return {
|
||||
...state,
|
||||
status: states.FILE_LOADED,
|
||||
mediaFile: action.payload.mediaFile,
|
||||
mediaFileToken : action.payload.mediaFileToken,
|
||||
}
|
||||
case 'enqueueFilter':
|
||||
mediaFileToken: action.payload.mediaFileToken,
|
||||
};
|
||||
case "enqueueFilter":
|
||||
return {
|
||||
...state,
|
||||
status: states.WORKFLOW_ENQUEUEING
|
||||
}
|
||||
case 'enqueueFilterSuccess':
|
||||
status: states.WORKFLOW_ENQUEUEING,
|
||||
};
|
||||
case "enqueueFilterSuccess":
|
||||
return {
|
||||
...state,
|
||||
status: states.WORKFLOW_ENQUEUED,
|
||||
inferenceJobToken: action.payload.inferenceJobToken
|
||||
}
|
||||
case 'reset':
|
||||
inferenceJobToken: action.payload.inferenceJobToken,
|
||||
};
|
||||
case "reset":
|
||||
default:
|
||||
return {status: states.NO_FILE};
|
||||
|
||||
return { status: states.NO_FILE };
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-26
@@ -1,50 +1,60 @@
|
||||
import React from "react";
|
||||
import {Switch, Route, useRouteMatch, Redirect, useHistory} from "react-router-dom";
|
||||
import PageWorkflowControls from "./components/pageWorkflowControls";
|
||||
import {
|
||||
Switch,
|
||||
Route,
|
||||
useRouteMatch,
|
||||
Redirect,
|
||||
useHistory,
|
||||
} from "react-router-dom";
|
||||
import PageVideoProvision from "./components/pageVideoProvision";
|
||||
import PageWorkflowJoblist from "./components/pageWorkflowJoblist";
|
||||
import {states,State,Action} from "./videoWorkflowReducer";
|
||||
import { states, State, Action } from "./videoWorkflowReducer";
|
||||
|
||||
export default function SubRoutes ({
|
||||
debug, t, pageState, dispatchPageState
|
||||
}:{
|
||||
debug?:boolean,
|
||||
t:Function,
|
||||
pageState:State,
|
||||
dispatchPageState: (action: Action) => void
|
||||
}){
|
||||
export default function SubRoutes({
|
||||
debug,
|
||||
t,
|
||||
pageState,
|
||||
dispatchPageState,
|
||||
}: {
|
||||
debug?: boolean;
|
||||
t: Function;
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const { path } = useRouteMatch();
|
||||
const history = useHistory();
|
||||
history.listen(({pathname}, action) => {
|
||||
if ((pathname === path || pathname.match(/upload|select-media/g)) && pageState.status >= states.FILE_LOADING){
|
||||
history.listen(({ pathname }, action) => {
|
||||
if (
|
||||
(pathname === path || pathname.match(/upload|select-media/g)) &&
|
||||
pageState.status >= states.FILE_LOADING
|
||||
) {
|
||||
//reset page if landed on page "Freshly"
|
||||
dispatchPageState({type: 'reset'})
|
||||
dispatchPageState({ type: "reset" });
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
const commonPageProps = {
|
||||
debug, parentPath: path, t, pageState, dispatchPageState
|
||||
debug,
|
||||
parentPath: path,
|
||||
t,
|
||||
pageState,
|
||||
dispatchPageState,
|
||||
};
|
||||
|
||||
return(
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path={`${path}/load/:mediaToken`} >
|
||||
<PageWorkflowControls {...commonPageProps}/>
|
||||
</Route>
|
||||
<Route exact path={`${path}/upload`}>
|
||||
<PageVideoProvision {...commonPageProps}/>
|
||||
<PageVideoProvision {...commonPageProps} />
|
||||
</Route>
|
||||
<Route exact path={`${path}/select-media`}>
|
||||
<PageVideoProvision {...commonPageProps}/>
|
||||
<PageVideoProvision {...commonPageProps} />
|
||||
</Route>
|
||||
<Route exact path={`${path}/jobs`}>
|
||||
<PageWorkflowJoblist {...commonPageProps}/>
|
||||
<PageWorkflowJoblist {...commonPageProps} />
|
||||
</Route>
|
||||
<Route path={`${path}`}>
|
||||
<Redirect to={`${path}/upload`} />
|
||||
</Route>
|
||||
</Switch>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user