resolve conflict

This commit is contained in:
wil committed 2024-02-01 15:24:23 -05:00
commit e31d22ec7f
56 files changed
+1500 -121

No files matched your search

@@ -0,0 +1,16 @@
import MakeRequest from "../MakeRequest";
export interface EnqueueEngineCompositingRequest {
uuid_idempotency_token: string,
video_source: string
}
export interface EnqueueEngineCompositingResponse {
success: boolean,
inference_job_token?: string
}
export const EnqueueEngineCompositing = MakeRequest<string, EnqueueEngineCompositingRequest, EnqueueEngineCompositingResponse,{}>({
method: "POST",
routingFunction: () => `/v1/compositor/enqueue_engine_compositing`,
});
@@ -11,6 +11,7 @@ export enum FrontendInferenceJobType {
ImageGeneration,
VideoMotionCapture,
ConvertFbxtoGltf,
EngineComposition
}
// NB: Many of these fields are optional despite the response payload containing them
+8 -1
View File
@@ -60,7 +60,11 @@ import { VoiceConversionModelUploadJob } from "@storyteller/components/src/jobs/
import { VoiceConversionModelListItem } from "@storyteller/components/src/api/voice_conversion/ListVoiceConversionModels";
import HttpBackend from "i18next-http-backend";
import { InferenceJobs, SessionProvider } from "components/providers";
import {
InferenceJobs,
ModalProvider,
SessionProvider,
} from "components/providers";
// NB: We're transitioning over to this instance of i18n-next that loads translations over HTTP from Json Files.
// The old i18n-next instance (see below) bakes in translations into the compiled javascript blob.
@@ -832,8 +836,10 @@ class App extends React.Component<Props, State> {
*/}
<div className="migrationComponentWrapper">
<ModalProvider>
<InferenceJobs
{...{
enqueue: this.enqueueInferenceJob,
byCategory: this.state.inferenceJobsByCategory,
}}
>
@@ -954,6 +960,7 @@ class App extends React.Component<Props, State> {
</Switch>
</SessionProvider>
</InferenceJobs>
</ModalProvider>
</div>
</div>
</div>
+13
View File
@@ -329,6 +329,19 @@ $headings-font-family: "Fira Sans", sans-serif;
height: 100%;
pointer-events: none;
}
.to-top-button-off-screen {
position: fixed;
width: fit-content;
white-space: nowrap;
top: 5rem;
margin: 0 auto;
left: 0;
right: 0;
z-index: 3;
box-shadow: 0px 5px 20px rgba($black, 0.3);
}
}
@media screen and (max-height: 740px) {
@@ -7,6 +7,7 @@ import Tippy from "@tippyjs/react";
import "tippy.js/dist/tippy.css";
export default function Button({
buttonRef,
label,
icon,
small,
@@ -81,7 +82,7 @@ export default function Button({
{ButtonContent}
</a>
) : (
<button type="button" {...commonProps} {...rest}>
<button {...{ ...commonProps, ...rest, type: "button", ref: buttonRef }}>
{ButtonContent}
</button>
);
@@ -2,6 +2,7 @@ import { ButtonHTMLAttributes } from "react";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
export default interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
buttonRef?: any;
label?: string | null;
icon?: IconDefinition;
small?: boolean;
@@ -25,6 +25,7 @@ interface AudioCardProps {
type: "media" | "weights";
inSelectModal?: boolean;
onResultSelect?: (data:{token: string, title:string}) => void;
onClick?: (e:any) => any;
}
export default function AudioCard({
@@ -36,6 +37,7 @@ export default function AudioCard({
source = "",
type,
inSelectModal = false,
onClick: inClick,
onResultSelect,
}: AudioCardProps) {
// const { setToken, setWeightTitle } = useToken();
@@ -166,7 +168,7 @@ export default function AudioCard({
<div className="d-flex flex-grow-1">
<Badge label={weightBadgeLabel} color={weightBadgeColor} />
</div>
{inSelectModal ? (
{inClick ? (
<Button
icon={faArrowRight}
iconFlip={true}
@@ -260,7 +262,7 @@ export default function AudioCard({
return (
<>
{inSelectModal ? (
{inClick ? (
<>{card}</>
) : (
<Link
@@ -13,6 +13,7 @@ import useMediaFileTypeInfo from "hooks/useMediaFileTypeInfo";
interface BVHCardProps {
bookmarks: any;
data: any;
onClick?: (e:any) => any;
ratings: any;
showCreator?: boolean;
source?: string;
@@ -22,6 +23,7 @@ interface BVHCardProps {
export default function BVHCard({
bookmarks,
data,
onClick: inClick,
showCreator,
source = "",
ratings,
@@ -40,12 +42,12 @@ export default function BVHCard({
data.media_type || data.details?.maybe_media_data?.media_type
);
const Wrapper = ({ children }: { children: any }) => inClick ? <div {...{ onClick: () => { inClick(data) } }}>{ children }</div> : <Link {...{ to: linkUrl }}>{ children }</Link>;
// const bucketConfig = new BucketConfig();
return (
<Link
{...{
to: linkUrl,
}}
>
<Wrapper>
<Card padding={false} canHover={true}>
<div className="card-img d-flex align-items-center justify-content-center">
<FontAwesomeIcon icon={faPersonWalking} className="card-img-icon" />
@@ -92,21 +94,18 @@ export default function BVHCard({
/>
</div>
)}
<div>
<LikeButton
{...{
...ratings.makeProps({
entityToken: data.token,
entityType: "media_file",
}),
}}
/>
</div>
<LikeButton
{...{
...ratings.makeProps({
entityToken: data.token,
entityType: "media_file",
}),
}}
/>
</div>
</div>
</div>
</Card>
</Link>
</Wrapper>
);
}
@@ -1,6 +1,6 @@
@import "scss/colours.scss";
.fy-uploader {
.fy-uploader, .fy-uploader-no-style {
[type="file"] {
border: 0;
clip: rect(0, 0, 0, 0);
@@ -13,12 +13,6 @@
+ label {
cursor: pointer;
border: dashed 2px rgba(255, 255, 255, 0.12) !important;
border-radius: 0.5rem;
transition: 0.1s all;
&:hover {
background-color: lighten($panel, 6%);
}
}
}
.fy-uploader-layout {
@@ -32,3 +26,17 @@
}
}
}
.fy-uploader {
[type="file"] {
+ label {
border: dashed 2px rgba(255, 255, 255, 0.12) !important;
border-radius: 0.5rem;
transition: 0.1s all;
&:hover {
background-color: lighten($panel, 6%);
}
}
}
}
@@ -7,12 +7,13 @@ interface Props {
containerClass?: string;
fileTypes?: string[];
inputRef: any;
noStyle?: boolean;
onChange: (file?: any) => void;
panelClass?: string;
[x:string]: any;
};
export default function FileWrapper({ children, containerClass, fileTypes = [], inputRef, onChange, panelClass, ...rest }: Props) {
export default function FileWrapper({ children, containerClass, fileTypes = [], inputRef, noStyle, onChange, panelClass, ...rest }: Props) {
const id = 'file-input-' + useId();
const accept = fileTypes.map((type,i) => `.${ type.toLowerCase() }`).join(",");
@@ -32,9 +33,12 @@ export default function FileWrapper({ children, containerClass, fileTypes = [],
}
};
return <div {...{ className: `fy-uploader${ containerClass ? " " + containerClass : "" }`, onDragLeave: onDragEvent(1), onDragOver: onDragEvent(0), onDrop }}>
const containerBaseClass = noStyle ? "-no-style" : "";
const panelBaseClass = noStyle ? "" : "panel panel-inner d-flex align-items-center";
return <div {...{ className: `fy-uploader${ containerBaseClass }${ containerClass ? " " + containerClass : "" }`, onDragLeave: onDragEvent(1), onDragOver: onDragEvent(0), onDrop }}>
<input { ...{ accept, onChange: fileChange, type: "file", id, ref: inputRef, ...rest }} />
<label {...{ className: `panel panel-inner d-flex align-items-center${ panelClass ? " " + panelClass : "" }`, htmlFor: id }} >
<label {...{ className: `${ panelBaseClass }${ panelClass ? " " + panelClass : "" }`, htmlFor: id }} >
{ children }
</label>
</div>;
@@ -0,0 +1,107 @@
@import "scss/colours.scss";
@import "scss/site-refresh.scss";
.fy-slide-frame {
position: absolute;
height: 100%;
width: 100%;
}
.fy-mocap-input {
@include panel;
height: 300px;
border-radius: .5rem;
overflow: hidden;
position: relative;
// background-color: $panel;
}
.fy-media-input-square {
aspect-ratio: 1/1;
.fy-media-picker-empty-controls {}
}
.fy-media-input-landscape {
height: 300px;
}
.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 { }
}
}
@@ -0,0 +1,155 @@
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 { 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: { 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>
</>;
};
@@ -0,0 +1 @@
export { default } from "./MocapInput";
@@ -63,3 +63,11 @@
}
}
}
.fy-core-modal {
.modal-content {
width: 85vw;
height: 85vh;
padding: 1rem;
}
}
@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import Button from "../Button";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useSpring, a } from "@react-spring/web";
@@ -6,11 +6,13 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import "./Modal.scss";
interface ModalProps {
content: React.ElementType;
className?: string;
content?: React.ElementType | null;
contentProps?: any;
show: boolean;
handleClose: () => void;
noHeader?: boolean;
omitBody?: boolean;
onCancel?: (e: React.MouseEvent<HTMLElement>) => any;
onConfirm?: (e: React.MouseEvent<HTMLElement>) => any;
title?: string;
@@ -23,12 +25,18 @@ interface ModalProps {
mobileFullscreen?: boolean;
}
const ModalBody = ({ children, omitBody, padding }: { children: any, omitBody?: boolean, padding?: boolean }) => omitBody ? children : <div {...{ className: `modal-body ${padding ? "p-3" : ""}` }}>
{ children }
</div>;
const Modal: React.FC<ModalProps> = ({
autoWidth,
className,
content: Content,
contentProps,
handleClose,
noHeader,
omitBody,
onCancel: cancelEvent,
onConfirm: confirmEvent,
icon,
@@ -44,6 +52,7 @@ const Modal: React.FC<ModalProps> = ({
opacity: show ? 1 : 0,
config: { duration: 80, easing: t => t },
});
const [loaded,loadedSet] = useState(false);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
@@ -53,6 +62,10 @@ const Modal: React.FC<ModalProps> = ({
}
};
if (!loaded) {
loadedSet(true);
}
if (show) {
document.addEventListener("mousedown", handleClickOutside);
}
@@ -60,7 +73,7 @@ const Modal: React.FC<ModalProps> = ({
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [show, handleClose]);
}, [show, handleClose, loaded]);
const onCancel = (e: React.MouseEvent<HTMLElement>) => {
if (cancelEvent) cancelEvent(e);
@@ -78,7 +91,7 @@ const Modal: React.FC<ModalProps> = ({
return (
<a.div style={fadeIn} className="modal-backdrop">
<div className="modal" role="dialog">
<div {...{ className: `modal${ className ? " " + className : "" }`, role: "dialog" }}>
<div
className={`modal-dialog ${
position === "center" ? "modal-dialog-centered" : ""
@@ -101,9 +114,9 @@ const Modal: React.FC<ModalProps> = ({
/>
</header>
)}
<div {...{ className: `modal-body ${padding ? "p-3" : ""}` }}>
{Content && <Content {...{ handleClose }} />}
</div>
<ModalBody {...{ omitBody, padding }}>
{Content && <Content {...{ ...contentProps, handleClose }} />}
</ModalBody>
{showButtons && (
<div className="modal-footer">
<Button variant="secondary" label="Cancel" onClick={onCancel} />
@@ -1,27 +0,0 @@
.fy-select {
margin-bottom: 1rem !important;
&:only-child,
&:last-child {
margin-bottom: 0 !important;
}
}
.d-flex:not(.flex-column) > .fy-select {
margin-bottom: 0 !important;
margin-right: 0.5rem;
&:only-child,
&:last-child {
margin-bottom: 0 !important;
}
}
.gap-1 > .fy-select,
.gap-2 > .fy-select,
.gap-3 > .fy-select,
.gap-4 > .fy-select,
.gap-5 > .fy-select {
margin-bottom: 0 !important;
margin-right: 0 !important;
}
@@ -0,0 +1,3 @@
.fy-temp-select { // NO !IMPORTANT
margin-bottom: 1rem;
}
@@ -1,9 +1,8 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import "../Select/Select.scss";
import ReactSelect, { Props as ReactSelectProps } from "react-select";
import "./Select.scss";
import "./TempSelect.scss";
export interface Option {
value: string | number | boolean;
@@ -54,7 +53,7 @@ export default function Select({
return (
// Changed fragment to div here just so that it can be laid out with bootstrap easily using d-flex, flex-column and responsive gaps which requires grouping.
<div className="fy-select">
<div className="fy-temp-select">
{label && (
<label className={`sub-title ${required ? "required" : ""}`}>
{label}
@@ -14,6 +14,7 @@ import FileLabel from "./FileLabel";
import ImageInput from "./ImageInput";
import Input from "./Input";
import Label from "./Label";
import MocapInput from "./MocapInput";
import Modal from "./Modal";
import NumberSlider from "./NumberSlider";
import NumberSliderV2 from "./NumberSliderV2";
@@ -53,6 +54,7 @@ export {
ImageInput,
Input,
Label,
MocapInput,
Modal,
NumberSlider,
NumberSliderV2,
@@ -0,0 +1,10 @@
import React from 'react';
// import { CardWrapper } from "components/entities";
interface Props {
value?: any;
}
export default function AudioCard({ value }: Props) {
return {};
};
@@ -0,0 +1,72 @@
import React from "react";
import useTimeAgo from "hooks/useTimeAgo";
import { faPersonWalking } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
import CreatorName from "components/common/Card/CreatorName";
import Badge from "components/common/Badge";
interface Props {
data: any,
showCreator?: boolean
}
export default function BvhCard({ data, showCreator }: Props) {
const timeAgo = useTimeAgo(data.created_at);
const handleInnerClick = (event: any) => event.stopPropagation();
return <>
<Icon {...{ className: "card-img", icon: faPersonWalking }}/>
<div className="card-img-overlay">
<div className="card-img-gradient" />
<div className="d-flex align-items-center">
<div className="d-flex flex-grow-1">
<Badge label="BVH" color="pink" overlay={true} />
</div>
</div>
<div className="card-img-overlay-text">
<div>
<p className="fs-7 opacity-75 mb-0">{timeAgo}</p>
</div>
<hr className="my-2" />
<div
className="d-flex align-items-center gap-2"
onClick={handleInnerClick}
>
{showCreator && (
<div className="flex-grow-1">
<CreatorName
displayName={
data.maybe_creator?.display_name || "Anonymous"
}
gravatarHash={data.maybe_creator?.gravatar_hash || null}
avatarIndex={
data.maybe_creator?.default_avatar.image_index || 0
}
backgroundIndex={
data.maybe_creator?.default_avatar.color_index || 0
}
username={data.maybe_creator?.username || "anonymous"}
/>
</div>
)}
{
// <div>
// <LikeButton
// {...{
// ...ratings.makeProps({
// entityToken: data.token,
// entityType: "media_file",
// }),
// }}
// />
// </div>
}
</div>
</div>
</div>
</>;
};
@@ -0,0 +1 @@
export { default } from "./BvhCard";
@@ -0,0 +1,27 @@
import React from "react";
import { Link } from "react-router-dom";
import getCardUrl from "components/common/Card/getCardUrl";
interface Props {
canHover?: boolean,
card: any,
data: any,
onClick?: (e:any) => any,
padding?: boolean,
source?: string,
type: "media" | "weights"
}
export default function CardWrapper({ canHover, card: Card, data, onClick, padding, source = "", type, ...rest }: Props) {
const linkUrl = getCardUrl(data,source,type);
const cardProps = { data, source, type, ...rest }
const className = `card ${ padding ? "p-3" : "" }${ onClick || canHover ? " card-clickable" : "" }`;
return onClick ?
<div {...{ className, onClick }}>
<Card { ...cardProps }/>
</div> :
<Link {...{ className, to: linkUrl }}>
<Card {...cardProps}/>
</Link>;
};
@@ -0,0 +1 @@
export { default } from "./CardWrapper";
@@ -0,0 +1,18 @@
export enum MediaFilters {
all,
audio,
image,
video,
bvh,
gltf,
fbx
}
export type MediaFilterProp = keyof typeof MediaFilters;
export const MediaFilterOptions = (t = (v:string) => v) => Object.values(MediaFilters)
.filter(val => isNaN(Number(val)))
.map((value) => {
if (typeof value === "string") return { value, label: t(value) }
return { label: "", value: "" };
});
@@ -0,0 +1,52 @@
import React, { useRef } from "react";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
// import MediaCards from "components/common/Card/MediaCards";
import AudioCard from "components/common/Card/AudioCard";
import ImageCard from "components/common/Card/ImageCard";
import VideoCard from "components/common/Card/VideoCard";
import { BvhCard, CardWrapper } from "components/entities";
interface MediaCardsProps {
props: any,
type: string
}
interface Props {
list: MediaFile[],
success?: boolean
}
const MediaCards = ({ props, type }: MediaCardsProps) => {
switch (type) {
case "audio":
return <AudioCard {...props} />;
case "image":
return <ImageCard {...props} />;
case "video":
return <VideoCard {...props} />;
case "bvh":
return <CardWrapper {...{ ...props, card: BvhCard }}/>
default:
return <div>Unsupported media type</div>;
}
};
export default function MediaList({ list, success, ...rest }: Props) {
const gridRef = useRef<HTMLDivElement | null>(null);
return list.length === 0 && success ?
<div className="text-center mt-4 opacity-75">
No media created yet.
</div> : <MasonryGrid {...{ gridRef }}>
{ list.map((data: MediaFile, key: number) => {
let props = { data, type: "media", ...rest };
return <div {...{
className: "col-12 col-sm-6 col-xl-4 grid-item",
key,
}}>
<MediaCards {...{ type: data.media_type, props }} />
</div>;
}) }
</MasonryGrid>;
};
@@ -0,0 +1 @@
export { default } from "./MediaList";
@@ -0,0 +1,5 @@
import BvhCard from "./BvhCard";
import CardWrapper from "./CardWrapper";
import MediaList from "./MediaList";
export { BvhCard, CardWrapper, MediaList };
@@ -0,0 +1,43 @@
import React from "react";
interface Props {
byQueue: any,
t: any,
ttsQueuedCount: number
}
export default function SideNavJobs({ byQueue, t, ttsQueuedCount }: Props) {
return <div className="d-none d-lg-block">
<div className="sidebar-heading">Jobs Queue</div>
<div className="ps-4 mb-3">
<div>
{t("queueTts")}:{" "}
<span className="text-red">{ ttsQueuedCount }</span>
</div>
<div>
{t("queueRvc")}:{" "}
<span className="text-red">
{ byQueue.pending_rvc_jobs }
</span>
</div>
<div>
{t("queueSvc")}:{" "}
<span className="text-red">
{ byQueue.pending_svc_jobs }
</span>
</div>
<div>
{t("queueFaceAnimator")}:{" "}
<span className="text-red">
{ byQueue.pending_face_animation_jobs }
</span>
</div>
<div>
Voice Designer:{" "}
<span className="text-red">
{ byQueue.pending_voice_designer }
</span>
</div>
</div>
</div>;
};
@@ -0,0 +1,42 @@
.fy-media-browser-tools {
display: flex;
align-items: center;
gap: 1rem;
justify-content: space-between;
.pagination {
margin: auto 0 auto auto;
}
.fy-temp-select { margin-bottom: 0 }
}
.fy-media-browser-header{
h3 { margin-bottom: 0 }
> * {
margin-bottom: 1rem;
}
.icon-close-button {
cursor: pointer;
height: 1.5rem;
opacity: .33;
// transition: .25s all ease-in-out;
&:hover {
opacity: 1;
}
}
}
.fy-media-browser-list {
flex-grow: 1;
overflow-y: scroll;
overflow-x: hidden;
height: 50vh;
}
.fy-media-browser-footer {
margin-top: 1rem;
}
@@ -0,0 +1,119 @@
import React, { useState } from "react";
import { MediaList } from "components/entities";
import { MediaFilterOptions, MediaFilterProp } from "components/entities/EntityTypes";
import { TempSelect } from "components/common";
import AudioPlayerProvider from "components/common/AudioPlayer/AudioPlayerContext";
import SkeletonCard from "components/common/Card/SkeletonCard";
import Pagination from "components/common/Pagination";
import { GetMediaByUser } from "@storyteller/components/src/api/media_files/GetMediaByUser";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import { useListContent, useRatings } from "hooks";
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
import { faArrowDownWideShort, faFilter, faXmark } from "@fortawesome/pro-solid-svg-icons";
import prepFilter from "resources/prepFilter";
import "./MediaBrowser.scss";
interface Props {
handleClose: any,
mediaToken: string,
onSelect?: any,
type?: MediaFilterProp,
username: string,
}
export default function MediaBrowser({ mediaToken, handleClose = () => {}, onSelect, type, username }: Props) {
const ratings = useRatings();
const [showMasonryGrid, setShowMasonryGrid] = useState(true);
const [mediaType, mediaTypeSet] = useState(type || "all");
const [list, listSet] = useState<MediaFile[]>([]);
const media = useListContent({
addQueries: {
page_size: 24,
...prepFilter(mediaType, "filter_media_type"),
},
addSetters: { mediaTypeSet },
// debug: "profile media",
fetcher: GetMediaByUser,
list,
listSet,
onInputChange: () => setShowMasonryGrid(false),
onSuccess: (res) => {
// bookmarks.gather({ res, key: "token" });
ratings.gather({ res, key: "token" });
setShowMasonryGrid(true);
},
requestList: true,
urlParam: username,
urlUpdate: false
});
const handlePageClick = (selectedItem: { selected: number }) => {
media.pageChange(selectedItem.selected);
};
const paginationProps = {
onPageChange: handlePageClick,
pageCount: media.pageCount,
currentPage: media.page,
};
const sortOptions = [
{ value: false, label: "Newest" },
{ value: true, label: "Oldest" },
// { value: "mostliked", label: "Most Liked" },
];
const onClick = (data: any) => {
onSelect(data);
handleClose();
};
return <>
<header {...{ className: "fy-media-browser-header" }}>
<div {...{ className: "fy-media-browser-tools" }}>
<h3>Media</h3>
<Icon {...{ className: "icon-close-button", icon: faXmark, onClick: () => handleClose() }}/>
</div>
<div {...{ className: "fy-media-browser-tools" }}>
<TempSelect
{...{
icon: faArrowDownWideShort,
options: sortOptions,
name: "sort",
onChange: media.onChange,
value: media.sort,
}}
/>
{ (!type || type === "all") && <TempSelect {...{
icon: faFilter,
options: MediaFilterOptions(),
name: "mediaType",
onChange: media.onChange,
value: mediaType,
}}/> }
<Pagination {...paginationProps} />
</div>
</header>
<AudioPlayerProvider>
{ media.isLoading ? (
<div {...{ className: "row gx-3 gy-3" }}>
{Array.from({ length: 12 }).map((_, index) => (
<SkeletonCard key={index} />
))}
</div>
) : (
<>
{ showMasonryGrid && (
<div {...{ className: "fy-media-browser-list" }}>
<MediaList {...{ list: media.list, success: media.status === 3, onClick }}/>
</div>
) }
</>
)}
</AudioPlayerProvider>
<footer {...{ className: "fy-media-browser-footer fy-media-browser-tools" }}>
<Pagination {...paginationProps} />
</footer>
</>;
};
@@ -0,0 +1 @@
export { default } from "./MediaBrowser";
@@ -0,0 +1,3 @@
import MediaBrowser from "./MediaBrowser";
export { MediaBrowser };
@@ -3,11 +3,12 @@ import { InferenceJobsContext } from 'context';
interface Props {
children?: any;
enqueue?: any;
byCategory?: any;
}
export default function InferenceJobs({ children, byCategory }: Props) {
return <InferenceJobsContext.Provider {...{ value: { byCategory } }}>
export default function InferenceJobs({ byCategory, children, enqueue }: Props) {
return <InferenceJobsContext.Provider {...{ value: { byCategory, enqueue } }}>
{ children }
</InferenceJobsContext.Provider>
};
@@ -0,0 +1,38 @@
import React, { useState } from "react";
import { ModalContext } from "context";
import { Modal } from "components/common";
interface Props {
children?: any,
}
interface ModalConfig {
component: React.ElementType,
props?: any
}
export default function ModalProvider({ children }: Props) {
const [modalState,modalStateSet] = useState<ModalConfig | null>(null);
const open = (cfg: ModalConfig) => {
console.log("🪬",cfg);
modalStateSet(cfg);
};
const close = () => modalStateSet(null);
return <ModalContext.Provider {...{ value: { close, open, modalState } }}>
{ children }
{
<Modal {...{
autoWidth: true,
className: "fy-core-modal",
content: modalState?.component,
contentProps: modalState?.props,
handleClose: close,
noHeader: true,
omitBody: true,
show: !!modalState?.component,
showButtons: false,
}}/>
}
</ModalContext.Provider>
};
@@ -0,0 +1 @@
export { default } from "./ModalProvider";
@@ -1,5 +1,6 @@
import AccountModalProvider from "./AccountModalProvider";
import InferenceJobs from "./InferenceJobs";
import ModalProvider from "./ModalProvider";
import SessionProvider from "./SessionProvider";
export { AccountModalProvider, InferenceJobs, SessionProvider };
export { AccountModalProvider, InferenceJobs, ModalProvider, SessionProvider };
@@ -1,5 +1,5 @@
import { createContext } from 'react';
interface InferenceJobsContext { byCategory?: any }
interface InferenceJobsContext { byCategory?: any, enqueue?: any }
export default createContext<InferenceJobsContext>({ });
@@ -0,0 +1,20 @@
import { createContext } from 'react';
export enum ModalView {
Closed,
Signup,
Login
}
interface ModalConfig {
component: React.ElementType,
props?: any
}
interface AccountModalContext { close: () => void, modalState: ModalConfig | null, open: (comp: any) => void }
export default createContext<AccountModalContext>({
close: () => {},
open: () => {},
modalState: null,
});
+2 -1
View File
@@ -1,5 +1,6 @@
import AccountModalContext from "./AccountModalContext";
import InferenceJobsContext from "./InferenceJobsContext";
import ModalContext from "./ModalContext";
import SessionContext from "./SessionContext";
export { AccountModalContext, InferenceJobsContext, SessionContext };
export { AccountModalContext, InferenceJobsContext, ModalContext, SessionContext };
+4
View File
@@ -12,6 +12,8 @@ import useLazyLists from './useLazyLists';
import useListContent from './useListContent';
import useLocalize from './useLocalize';
import useMedia from './useMedia';
import useModal from './useModal';
import useOnScreen from "./useOnScreen";
import useRatings from './useRatings';
import useSdUpload from './useSdUpload';
import useSession from './useSession';
@@ -33,6 +35,8 @@ export {
useListContent,
useLocalize,
useMedia,
useModal,
useOnScreen,
useRatings,
useSdUpload,
useSession,
@@ -6,10 +6,9 @@ interface Props {
debug?: any;
onChange?: (file: any) => void;
onClear?: (x?: any) => void;
onSubmit?: (file: any) => Promise<boolean | undefined> | Promise<void>;
}
export default function useFile({ debug, onChange = n, onClear = n, onSubmit = n }: Props) {
export default function useFile({ debug, onChange = n, onClear = n }: Props) {
const [file, fileSet] = useState<any>(undefined);
const [blob, blobSet] = useState<string>();
const inputRef = useRef<HTMLInputElement>(null);
@@ -4,8 +4,9 @@ import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/Infer
import { InferenceJobsContext } from 'context';
export default function useInferenceJobs(jobType: FrontendInferenceJobType) {
const { byCategory } = useContext(InferenceJobsContext);
const { byCategory, enqueue } = useContext(InferenceJobsContext);
return {
enqueue,
inferenceJobs: (byCategory?.get(jobType) || []),
jobStatusDescription: (jobState: JobState) => Object.keys(JobState).filter(key => isNaN(Number(key)))[jobState]
};
@@ -45,6 +45,9 @@ export default function useLazyLists({
status === FetchStatus.ready || status === FetchStatus.in_progress;
const fetchError = status === FetchStatus.error;
const [goingtToTop,goingtToTopSet] = useState(false);
const [y,ySet] = useState(0);
const getMore = () => {
if (next) statusSet(1);
};
@@ -67,6 +70,8 @@ export default function useLazyLists({
nextSet("");
previousSet("");
statusSet(FetchStatus.ready);
window.scrollTo(0,0);
goingtToTopSet(true);
}
useEffect(() => {
@@ -76,7 +81,15 @@ export default function useLazyLists({
...(sort ? { sort_ascending: true } : {}),
};
if (status === FetchStatus.ready) {
const adjustY = () => ySet(window.pageYOffset);
if (goingtToTop) {
window.addEventListener('scroll', adjustY, { passive: true });
if (y === 0) setTimeout(() => goingtToTopSet(false),500);
}
else window.removeEventListener('scroll',adjustY);
if (status === FetchStatus.ready && !goingtToTop) {
let search = new URLSearchParams(queries).toString();
statusSet(FetchStatus.in_progress);
!disableUrlQueries && history.replace({ pathname, search });
@@ -114,6 +127,7 @@ export default function useLazyLists({
addQueries,
debug,
fetcher,
goingtToTop,
history,
listKeys,
listSet,
@@ -124,6 +138,7 @@ export default function useLazyLists({
status,
totalKeys,
disableUrlQueries,
y
]);
return {
@@ -14,6 +14,7 @@ interface Props {
pagePreset?: number;
requestList?: boolean;
urlParam: string;
urlUpdate?: boolean;
}
const n = () => {};
@@ -30,6 +31,7 @@ export default function useListContent({
pagePreset = 0,
requestList = false,
urlParam = "",
urlUpdate = true
}: Props) {
const { pathname, search: locSearch } = useLocation();
const history = useHistory();
@@ -76,7 +78,7 @@ export default function useListContent({
if (status === FetchStatus.ready) {
let search = new URLSearchParams(queries).toString();
statusSet(FetchStatus.in_progress);
history.replace({ pathname, search });
if (urlUpdate) history.replace({ pathname, search });
fetcher(urlParam,{},queries).then((res: any) => {
if (debug)
console.log(`🪲 useListContent success debug at: ${debug}`, res);
@@ -101,6 +103,7 @@ export default function useListContent({
sort,
status,
urlParam,
urlUpdate
]);
return {
@@ -7,7 +7,7 @@ export default function useMedia({ mediaToken = "", onSuccess = (res:MediaFile)
const [media,mediaSet] = useState<MediaFile | undefined>();
useEffect(() => {
if (mediaToken && status === FetchStatus.ready) {
if (!media && mediaToken && status === FetchStatus.ready) {
statusSet(FetchStatus.in_progress);
GetMedia(mediaToken,{})
.then((res) => {
@@ -22,6 +22,6 @@ export default function useMedia({ mediaToken = "", onSuccess = (res:MediaFile)
});
}
},[mediaToken, onSuccess, status, statusSet]);
return { media, status };
},[media, mediaToken, onSuccess, status, statusSet]);
return { media, mediaSet, status };
};
@@ -0,0 +1 @@
export { default } from "./useModal";
@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ModalContext } from 'context';
export default function useInferenceJobs() {
return useContext(ModalContext);
};
@@ -3,14 +3,4 @@ import { SessionContext } from 'context';
export default function useInferenceJobs() {
return useContext(SessionContext);
// const { querySession, querySubscriptions, sessionFetched, sessionWrapper } = useContext(SessionContext);
// const { logged_in: loggedIn, user } = sessionWrapper?.sessionStateResponse || { logged_in: false };
// return {
// loggedIn,
// querySession,
// querySubscriptions,
// sessionFetched,
// user
// };
};
+18 -14
View File
@@ -11,6 +11,23 @@ styles with important tags:
//
// Once the rollout is complete, this outer wrapper class can be removed.
@mixin panel {
background-color: $panel;
border-color: #ffffff12 !important;
border: 1px solid;
border-left: none;
border-right: none;
border-radius: 0px;
box-shadow:
0 0 0 1px rgba(0 0 0 / 16%),
0 70px 65px rgb(0 0 0 / 8%),
0 30px 30px rgb(0 0 0 / 7%),
0 15px 15px rgb(0 0 0 / 5%),
0 10px 8px rgb(0 0 0 / 3%),
0 4px 4px rgb(0 0 0 / 2%),
0 2px 2px rgb(0 0 0 / 1%) !important;
}
.fakeyou-refresh {
//- BS5 classnames -//
.object-fit-cover {
@@ -315,22 +332,9 @@ styles with important tags:
}
.panel {
@include panel;
position: relative;
width: 100%;
background-color: $panel;
border-color: #ffffff12 !important;
border: 1px solid;
border-left: none;
border-right: none;
border-radius: 0px;
box-shadow:
0 0 0 1px rgba(0 0 0 / 16%),
0 70px 65px rgb(0 0 0 / 8%),
0 30px 30px rgb(0 0 0 / 7%),
0 15px 15px rgb(0 0 0 / 5%),
0 10px 8px rgb(0 0 0 / 3%),
0 4px 4px rgb(0 0 0 / 2%),
0 2px 2px rgb(0 0 0 / 1%);
.video-bg {
background-color: $black;
@@ -97,6 +97,7 @@ import VoiceDesignerUseVoicePage from "./pages/voice_designer/VoiceDesignerUseVo
import { PasswordResetEmailPage } from "./pages/password_reset/PasswordResetEmailPage";
import { PasswordResetVerificationPage } from "./pages/password_reset/PasswordResetVerificationPage";
import DevUpload from "./pages/dev_upload/DevUpload";
import EngineCompositor from "./pages/EngineCompositor";
import { NewProfilePage } from "./pages/profile/profile_view/NewProfilePage";
import { ModerationJobControlPage } from "./pages/moderation/job_control/ModerationJobControlPage";
import WeightPage from "./pages/weight/WeightPage";
@@ -212,8 +213,6 @@ class PageContainer extends React.Component<
> {
constructor(props: Props & RouteComponentProps) {
super(props);
console.log("💰");
this.state = {};
}
@@ -963,14 +962,485 @@ class PageContainer extends React.Component<
/>
</Route>
<Route path="/" exact={true}>
<LandingPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
/>
</Route>
<Route path="/tts/:token">
<TtsModelViewPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
enqueueTtsJob={this.props.enqueueTtsJob}
inferenceJobs={this.props.inferenceJobs}
ttsInferenceJobs={this.props.ttsInferenceJobs}
textBuffer={this.props.textBuffer}
setTextBuffer={this.props.setTextBuffer}
clearTextBuffer={this.props.clearTextBuffer}
/>
</Route>
<Route path="/w2l/result/:token/edit">
<W2lResultEditPage sessionWrapper={this.props.sessionWrapper} />
</Route>
<Route path="/w2l/result/:token/delete">
<W2lResultDeletePage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/w2l/result/:token">
<W2lResultViewPage sessionWrapper={this.props.sessionWrapper} />
</Route>
<Route path="/w2l/:templateToken/edit">
<W2lTemplateEditPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/w2l/:templateToken/approval">
<W2lTemplateApprovePage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/w2l/:templateToken/delete">
<W2lTemplateDeletePage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/w2l/:templateSlug">
<W2lTemplateViewPage
sessionWrapper={this.props.sessionWrapper}
enqueueW2lJob={this.props.enqueueW2lJob}
w2lInferenceJobs={this.props.w2lInferenceJobs}
/>
</Route>
<Route path="/video">
<W2lTemplateListPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/upload/w2l_photo">
<UploadW2lPhotoPage
sessionWrapper={this.props.sessionWrapper}
w2lTemplateUploadJobs={this.props.w2lTemplateUploadJobs}
enqueueW2lTemplateUploadJob={
this.props.enqueueW2lTemplateUploadJob
}
/>
</Route>
<Route path="/upload/w2l_video">
<UploadW2lVideoPage
sessionWrapper={this.props.sessionWrapper}
w2lTemplateUploadJobs={this.props.w2lTemplateUploadJobs}
enqueueW2lTemplateUploadJob={
this.props.enqueueW2lTemplateUploadJob
}
/>
</Route>
<Route path="/upload/tts">
<UploadTtsModelPage
sessionWrapper={this.props.sessionWrapper}
ttsModelUploadJobs={this.props.ttsModelUploadJobs}
enqueueTtsModelUploadJob={this.props.enqueueTtsModelUploadJob}
/>
</Route>
<Route path="/upload/voice_conversion">
<UploadVoiceConversionModel
sessionWrapper={this.props.sessionWrapper}
voiceConversionModelUploadJobs={
this.props.voiceConversionModelUploadJobs
}
enqueueVoiceConversionModelUploadJob={
this.props.enqueueVoiceConversionModelUploadJob
}
/>
</Route>
<Route path="/upload/vocoder" exact={true}>
<UploadVocoderPage
sessionWrapper={this.props.sessionWrapper}
vocoderUploadJobs={this.props.vocoderUploadJobs}
enqueueVocoderUploadJob={this.props.enqueueVocoderUploadJob}
/>
</Route>
<Route path="/contribute">
<ContributeIndexPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/category/create">
<CreateCategoryPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/user/list">
<ModerationUserListFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/ip_bans/:ipAddress">
<ModerationViewIpBanFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/ip_bans">
<ModerationIpBanListFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/voice_stats">
<ModerationVoiceStatsFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/job_stats">
<ModerationJobStatsFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/job_control">
<ModerationJobControlPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/tts_category/list">
<ModerationTtsCategoryListPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/tts_category/edit/:token">
<ModerationTtsCategoryEditPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/category/delete/:token">
<ModerationCategoryDeletePage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/approve/w2l_templates">
<ModerationPendingW2lTemplatesFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation">
<ModerationFc sessionWrapper={this.props.sessionWrapper} />
</Route>
<Route exact={true} path="/clone">
<VoiceCloneRequestPage />
</Route>
<Route path="/patrons">
<PatronPage sessionWrapper={this.props.sessionWrapper} />
</Route>
<Route path="/product-usage">
<ProductUsageInfoPage
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/voice-conversion/:token/delete">
<VcModelDeletePage sessionWrapper={this.props.sessionWrapper} />
</Route>
<Route path="/voice-conversion/:token/edit">
<VcModelEditPage sessionWrapper={this.props.sessionWrapper} />
</Route>
<Route path="/voice-conversion/:token">
<VcModelViewPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
inferenceJobsByCategory={this.props.inferenceJobsByCategory}
setMaybeSelectedInferenceJob={
this.props.maybeSelectedVoiceConversionModel
}
/>
</Route>
<Route path="/voice-conversion">
<VcModelListPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
voiceConversionModels={this.props.voiceConversionModels}
setVoiceConversionModels={this.props.setVoiceConversionModels}
maybeSelectedVoiceConversionModel={
this.props.maybeSelectedVoiceConversionModel
}
setMaybeSelectedVoiceConversionModel={
this.props.setMaybeSelectedVoiceConversionModel
}
enqueueInferenceJob={this.props.enqueueInferenceJob}
inferenceJobs={this.props.inferenceJobs}
inferenceJobsByCategory={this.props.inferenceJobsByCategory}
/>
</Route>
<Route path="/about">
<AboutPage />
</Route>
<Route path="/face-animator/:mediaToken?">
<FaceAnimator
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
inferenceJobs: this.props.inferenceJobs,
inferenceJobsByCategory: this.props.inferenceJobsByCategory,
}}
/>
</Route>
<Route path="/studio">
<StorytellerStudioListPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
/>
</Route>
<Route path="/commissions">
<CommunityCommissionsPage />
</Route>
<Route path="/terms">
<TermsPage />
</Route>
<Route path="/privacy">
<PrivacyPage />
</Route>
<Route path="/guide">
<GuidePage />
</Route>
<Route path="/old">
<VocodesPage />
</Route>
<Route path="/dev-upload">
<DevUpload />
</Route>
{/* Route for initial voice creation */}
<Route exact path="/voice-designer/create">
<VoiceDesignerFormPage
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionWrapper: this.props.sessionWrapper,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
}}
/>
</Route>
{/* Route for editing the dataset details */}
<Route exact path="/voice-designer/dataset/:dataset_token/edit">
<VoiceDesignerFormPage
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionWrapper: this.props.sessionWrapper,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
}}
/>
</Route>
{/* Route for handling dataset token for uploading samples */}
<Route exact path="/voice-designer/dataset/:dataset_token/upload">
<VoiceDesignerFormPage
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionWrapper: this.props.sessionWrapper,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
}}
/>
</Route>
<Route path="/voice-designer/voice/:voice_token/edit">
<VoiceDesignerVoiceEditPage />
</Route>
<Route path="/voice-designer/voice/:voice_token">
<VoiceDesignerUseVoicePage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
inferenceJobs={this.props.inferenceJobs}
enqueueInferenceJob={this.props.enqueueInferenceJob}
inferenceJobsByCategory={this.props.inferenceJobsByCategory}
ttsInferenceJobs={this.props.ttsInferenceJobs}
/>
</Route>
<Route path="/engine-compositor">
<EngineCompositor />
</Route>
<Route path="/voice-designer">
<VoiceDesignerMainPage />
</Route>
<Route path="/generate-speech">
<GenerateSpeechPage />
</Route>
<Route path="/video-mocap/:mediaToken?">
<VideoMocap
{...{
enqueueInferenceJob: this.props.enqueueInferenceJob,
sessionSubscriptionsWrapper:
this.props.sessionSubscriptionsWrapper,
inferenceJobs: this.props.inferenceJobs,
inferenceJobsByCategory: this.props.inferenceJobsByCategory,
}}
/>
</Route>
<Route path="/character/donald-trump">
<TrumpTtsPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
enqueueTtsJob={this.props.enqueueTtsJob}
inferenceJobs={this.props.inferenceJobs}
ttsInferenceJobs={this.props.ttsInferenceJobs}
ttsModelUploadJobs={this.props.ttsModelUploadJobs}
w2lInferenceJobs={this.props.w2lInferenceJobs}
w2lTemplateUploadJobs={this.props.w2lTemplateUploadJobs}
textBuffer={this.props.textBuffer}
setTextBuffer={this.props.setTextBuffer}
clearTextBuffer={this.props.clearTextBuffer}
ttsModels={this.props.ttsModels}
setTtsModels={this.props.setTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsModelsByTokenMap={this.props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={this.props.ttsModelsByCategoryToken}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={this.props.setDropdownCategories}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={this.props.setSelectedCategories}
maybeSelectedTtsModel={this.props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={this.props.setMaybeSelectedTtsModel}
selectedTtsLanguageScope={this.props.selectedTtsLanguageScope}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
/>
</Route>
<Route path="/tts" exact={true}>
<TtsModelListPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
isShowingVocodesNotice={this.props.isShowingVocodesNotice}
clearVocodesNotice={this.props.clearVocodesNotice}
isShowingLanguageNotice={this.props.isShowingLangaugeNotice}
clearLanguageNotice={this.props.clearLanguageNotice}
displayLanguage={this.props.displayLanguage}
isShowingTwitchTtsNotice={this.props.isShowingTwitchTtsNotice}
clearTwitchTtsNotice={this.props.clearTwitchTtsNotice}
isShowingPleaseFollowNotice={
this.props.isShowingPleaseFollowNotice
}
clearPleaseFollowNotice={this.props.clearPleaseFollowNotice}
isShowingBootstrapLanguageNotice={
this.props.isShowingBootstrapLanguageNotice
}
clearBootstrapLanguageNotice={
this.props.clearBootstrapLanguageNotice
}
enqueueInferenceJob={this.props.enqueueInferenceJob}
inferenceJobs={this.props.inferenceJobs}
inferenceJobsByCategory={this.props.inferenceJobsByCategory}
enqueueTtsJob={this.props.enqueueTtsJob}
ttsInferenceJobs={this.props.ttsInferenceJobs}
ttsModelUploadJobs={this.props.ttsModelUploadJobs}
w2lInferenceJobs={this.props.w2lInferenceJobs}
w2lTemplateUploadJobs={this.props.w2lTemplateUploadJobs}
textBuffer={this.props.textBuffer}
setTextBuffer={this.props.setTextBuffer}
clearTextBuffer={this.props.clearTextBuffer}
ttsModels={this.props.ttsModels}
setTtsModels={this.props.setTtsModels}
allTtsCategories={this.props.allTtsCategories}
setAllTtsCategories={this.props.setAllTtsCategories}
allTtsCategoriesByTokenMap={
this.props.allTtsCategoriesByTokenMap
}
computedTtsCategoryAssignments={
this.props.computedTtsCategoryAssignments
}
setComputedTtsCategoryAssignments={
this.props.setComputedTtsCategoryAssignments
}
allTtsModelsByTokenMap={this.props.allTtsModelsByTokenMap}
ttsModelsByCategoryToken={this.props.ttsModelsByCategoryToken}
dropdownCategories={this.props.dropdownCategories}
setDropdownCategories={this.props.setDropdownCategories}
selectedCategories={this.props.selectedCategories}
setSelectedCategories={this.props.setSelectedCategories}
maybeSelectedTtsModel={this.props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={this.props.setMaybeSelectedTtsModel}
selectedTtsLanguageScope={this.props.selectedTtsLanguageScope}
setSelectedTtsLanguageScope={
this.props.setSelectedTtsLanguageScope
}
/>
</Route>
<Route path="/" exact={true}>
<LandingPage
sessionWrapper={this.props.sessionWrapper}
sessionSubscriptionsWrapper={
this.props.sessionSubscriptionsWrapper
}
/>
</Route>
</Switch>
</div>
<FooterNav sessionWrapper={this.props.sessionWrapper} />
@@ -0,0 +1,29 @@
.fy-engine-compositor {
// margin: 1rem;
padding: 1rem;
.engine-compositor-container {
padding: 1rem;
header {
// background: blue;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
h2 { margin-bottom: 0; }
}
.compositor-content {
display: flex;
gap: 1rem;
// padding: 1rem;
> * {
width: 100%;
// background: red;
}
}
}
@@ -0,0 +1,58 @@
import React, { useState } from "react";
import { Button, MocapInput } from "components/common";
import { useInferenceJobs } from "hooks";
import InferenceJobsList from "components/layout/InferenceJobsList";
import { EnqueueEngineCompositing } from "@storyteller/components/src/api/engine_compositor/EnqueueEngineCompositing";
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
import { v4 as uuidv4 } from "uuid";
import "./EngineCompositor.scss"
interface Props {
value?: any;
}
export default function EngineCompositor({ value }: Props) {
const [mediaToken,mediaTokenSet] = useState();
const onChange = ({ target }: any) => mediaTokenSet(target.value);
const inferenceJobs = useInferenceJobs(FrontendInferenceJobType.EngineComposition);
const onClick = () => {
EnqueueEngineCompositing("",{
uuid_idempotency_token: uuidv4(),
video_source: mediaToken || "",
})
.then((res: any) => {
if (res && res.success) {
console.log("🚛 success!",{ mediaToken, res });
inferenceJobs.enqueue( // I need to pass this
res.inference_job_token,
FrontendInferenceJobType.EngineComposition
);
}
});
}
const failures = (fail = "") => {
switch (fail) {
default:
return "Uknown failure";
}
};
return <div {...{ className: "fy-engine-compositor"}}>
<div {...{ className: "panel engine-compositor-container" }}>
<header>
<h2>Engine Compositor</h2>
<Button {...{ label: "Enqueue", onClick, variant: "primary" }}/>
</header>
<MocapInput {...{ aspectRatio: "landscape", label: "Choose 3D data", onChange, type: "bvh" }}/>
</div>
<InferenceJobsList
{...{
failures,
jobType: FrontendInferenceJobType.EngineComposition,
}}
/>
</div>;
};
@@ -0,0 +1 @@
export { default } from "./EngineCompositor";
@@ -11,7 +11,7 @@ import AudioPlayerProvider from "components/common/AudioPlayer/AudioPlayerContex
import SkeletonCard from "components/common/Card/SkeletonCard";
import { ListMediaFiles } from "@storyteller/components/src/api/media_files/ListMediaFiles";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import { useBookmarks, useLazyLists, useRatings } from "hooks";
import { useBookmarks, useLazyLists, useOnScreen, useRatings } from "hooks";
import InfiniteScroll from "react-infinite-scroll-component";
import prepFilter from "resources/prepFilter";
@@ -20,6 +20,9 @@ export default function MediaTab() {
const urlQueries = new URLSearchParams(search);
const bookmarks = useBookmarks();
const ratings = useRatings();
const toTopBtnRef = useRef<HTMLDivElement | null>(null);
const onScreen = useOnScreen(toTopBtnRef,"0px");
const gridContainerRef = useRef<HTMLDivElement | null>(null);
const [mediaType, mediaTypeSet] = useState(
urlQueries.get("filter_media_type") || "all"
@@ -59,8 +62,6 @@ export default function MediaTab() {
// { value: "mostliked", label: "Most Liked" },
];
console.log("💎",media.urlCursor);
return (
<>
<div className="d-flex flex-wrap gap-3 mb-3">
@@ -84,8 +85,20 @@ export default function MediaTab() {
}}
/>
</div>
{ media.urlCursor ? <Button {...{ label: "Back to top", onClick: () => media.reset() }}/> : null }
{ media.urlCursor ?
<Button {...{
className: `to-top-button`,
buttonRef: toTopBtnRef,
label: "Back to top",
onClick: () => media.reset(),
}}/> : null }
</div>
{ media.urlCursor && !onScreen ?
<Button {...{
className: `to-top-button-off-screen`,
label: "Back to top",
onClick: () => media.reset(),
}}/> : null }
<AudioPlayerProvider>
{media.isLoading && !media.list.length ? (
<div className="row gx-3 gy-3">
@@ -94,6 +107,7 @@ export default function MediaTab() {
))}
</div>
) : (
<InfiniteScroll
dataLength={media.list.length}
next={media.getMore}
@@ -12,7 +12,7 @@ import SkeletonCard from "components/common/Card/SkeletonCard";
import { ListWeights } from "@storyteller/components/src/api/weights/ListWeights";
import { Weight } from "@storyteller/components/src/api/weights/GetWeight";
import InfiniteScroll from "react-infinite-scroll-component";
import { useBookmarks, useLazyLists, useRatings } from "hooks";
import { useBookmarks, useLazyLists, useOnScreen, useRatings } from "hooks";
import prepFilter from "resources/prepFilter";
export default function WeightsTab() {
@@ -25,6 +25,8 @@ export default function WeightsTab() {
const [weightCategory, weightCategorySet] = useState(urlQueries.get("maybe_scoped_weight_category") || "all");
const [showMasonryGrid, setShowMasonryGrid] = useState(true);
const [list, listSet] = useState<Weight[]>([]);
const toTopBtnRef = useRef<HTMLDivElement | null>(null);
const onScreen = useOnScreen(toTopBtnRef,"0px");
const weights = useLazyLists({
addQueries: {
page_size: 24,
@@ -145,8 +147,20 @@ export default function WeightsTab() {
/>
)} */}
</div>
{ weights.urlCursor ? <Button {...{ label: "Back to top", onClick: () => weights.reset() }}/> : null }
{ weights.urlCursor ?
<Button {...{
className: `to-top-button`,
buttonRef: toTopBtnRef,
label: "Back to top",
onClick: () => weights.reset(),
}}/> : null }
</div>
{ weights.urlCursor && !onScreen ?
<Button {...{
className: `to-top-button-off-screen`,
label: "Back to top",
onClick: () => weights.reset(),
}}/> : null }
<AudioPlayerProvider>
{weights.isLoading && !weights.list.length ? (
<div className="row gx-3 gy-3">