prep for weight card revisions

This commit is contained in:
Victor committed 2024-02-06 17:07:24 -08:00
1 parent a8c884d630
commit c0c98b4e35
10 files changed
+346 -37

No files matched your search

@@ -5,7 +5,7 @@ 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 { 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";
@@ -65,7 +65,7 @@ const MocapInputFull = ({ media }: SlideProps) => {
const MediaPickerEmpty = ({ media, onSelect, open, inputProps, type, user }: EmptySlideProps) => {
const browserClick = () => open({
component: MediaBrowser,
props: { onSelect, type, username: user?.username || "" }
props: { entityType: EntityType.media, onSelect, type, username: user?.username || "" }
});
return <>
@@ -0,0 +1,109 @@
@import "scss/colours.scss";
@import "scss/site-refresh.scss";
.fy-slide-frame {
position: absolute;
height: 100%;
width: 100%;
}
.fy-entity-input {
@include panel;
border-radius: .5rem;
overflow: hidden;
position: relative;
// background-color: $panel;
}
.fy-entity-input-square {
aspect-ratio: 1/1;
height: 300px;
.fy-entity-input-empty-controls {}
}
.fy-entity-input-landscape {
width: 100%;
aspect-ratio: 4/3;
max-height: 66vh;
}
.fy-entity-input-empty, .fy-entity-input-full {
// position: relative;
// overflow: hidden;
.fy-entity-input-icon {
position: absolute;
height: 80%;
opacity: 75%;
// background-color: blue;
margin: auto;
inset: 0;
path { fill: $panel-inner-border}
}
}
.fy-entity-input-label-icon {
margin-right: .5rem;
height: 1.25rem;
}
.fy-entity-input-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-entity-input-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-entity-input-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-entity-input-row {
height: 100%;
}
.fy-entity-input-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,159 @@
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, WeightFilterProp } 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 "./EntityInput.scss";
interface Props {
label?: string,
onChange?: any,
mediaType?: MediaFilterProp,
aspectRatio?: "square" | "landscape" | "portrait",
weightType?: WeightFilterProp,
}
interface SlideProps {
media?: MediaFile
};
interface EmptySlideProps extends SlideProps {
entityType: EntityType,
filterType: MediaFilterProp | WeightFilterProp,
inputProps?: any,
onSelect: any,
open: any,
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-entity-input-mocap-preview",
}}
/>
<div {...{ className: "fy-entity-input-full-controls" }}>
Your file
</div>
</>;
};
const MediaPickerEmpty = ({ entityType, filterType, media, onSelect, open, inputProps, user }: EmptySlideProps) => {
const browserClick = () => open({
component: MediaBrowser,
props: { entityType, filterType, onSelect, username: user?.username || "" }
});
return <>
<Icon {...{ className: "fy-entity-input-icon", icon: faPersonWalking }}/>
<div {...{ className: "fy-entity-input-empty-controls" }}>
<FileWrapper {...{ containerClass: "fy-entity-input-row", panelClass: "fy-entity-input-button", noStyle: true, ...inputProps }}>
<>
<Icon {...{ className: "fy-entity-input-label-icon", icon: faFileArrowUp }}/>
Upload, click or drag here
</>
</FileWrapper>
<div {...{ className: "fy-entity-input-row fy-entity-input-button", onClick: browserClick }}>
<Icon {...{ className: "fy-entity-input-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 EntityInput({ aspectRatio = "square", label, onChange, mediaType, weightType }: Props) {
const entityType = mediaType ? EntityType.media : weightType ? EntityType.weights : EntityType.unknown;
const filterType = mediaType || weightType || "all";
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-entity-input panel-inner${ aspectRatio ? " fy-entity-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-entity-input-busy", ...sharedProps }}/>,
<AniMod {...{ render: MocapInputFull, className: "fy-entity-input-full", ...sharedProps }}/>,
<AniMod {...{ render: MediaPickerEmpty, className: "fy-entity-input-empty", entityType, filterType, inputProps, onSelect, open, user, ...sharedProps }}/>
][i];
})
}
</div>
</>;
};
@@ -0,0 +1 @@
export { default } from "./EntityInput";
@@ -8,11 +8,33 @@ export enum MediaFilters {
fbx
}
export type MediaFilterProp = keyof typeof MediaFilters;
export enum WeightsFilters {
all,
hifigan_tt2,
sd_1,
"sd_1.5",
sdxl,
so_vits_svc,
tt2,
loRA,
vall_e
}
export const MediaFilterOptions = (t = (v:string) => v) => Object.values(MediaFilters)
export type MediaFilterProp = keyof typeof MediaFilters;
export type WeightFilterProp = keyof typeof WeightsFilters;
export enum EntityType {
unknown,
media,
weights
}
export const EntityFilterOptions = ( mode: EntityType, t = (v:string) => v) => {
const filters = [{},MediaFilters,WeightsFilters][mode];
return mode ? Object.values(filters)
.filter(val => isNaN(Number(val)))
.map((value) => {
if (typeof value === "string") return { value, label: t(value) }
return { label: "", value: "" };
});
}) : [];
};
@@ -1,10 +1,9 @@
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 { ImagePreview, MocapPreview, VideoPreview } from '../CardPreviews';
import { AudioCard, OverlayCard, CardWrapper } from "components/entities";
import { EntityType } from "components/entities/EntityTypes";
interface MediaCardsProps {
props: any,
@@ -12,6 +11,7 @@ interface MediaCardsProps {
}
interface Props {
entityType: EntityType,
list: MediaFile[],
success?: boolean
}
@@ -31,20 +31,32 @@ const MediaCards = ({ props, type }: MediaCardsProps) => {
}
};
export default function MediaList({ list, success, ...rest }: Props) {
const WeightsCards = ({ props, type }: MediaCardsProps) => {
switch (type) {
default:
return <div>Unsupported media type</div>;
}
};
export default function MediaList({ entityType, 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) => {
{ list.map((data: any, 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 }} />
{
[ null,
<MediaCards {...{ type: data.media_type, props }} />,
<WeightsCards {...{ type: data.weight_type, props }} />
][entityType]
}
</div>;
}) }
</MasonryGrid>;
@@ -1,6 +1,7 @@
import AudioCard from "./AudioCard";
import CardWrapper from "./CardWrapper";
import EntityInput from "./EntityInput";
import MediaList from "./MediaList";
import OverlayCard from "./OverlayCard";
export { AudioCard, CardWrapper, MediaList, OverlayCard };
export { AudioCard, CardWrapper, EntityInput, MediaList, OverlayCard };
@@ -1,12 +1,14 @@
import React, { useState } from "react";
import { MediaList } from "components/entities";
import { MediaFilterOptions, MediaFilterProp } from "components/entities/EntityTypes";
import { EntityType, EntityFilterOptions, MediaFilterProp, WeightFilterProp } 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 { GetWeightsByUser } from "@storyteller/components/src/api/weights/GetWeightsByUser";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import { Weight } from "@storyteller/components/src/api/weights/GetWeight";
import { useListContent, useRatings } from "hooks";
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
import { faArrowDownWideShort, faFilter, faXmark } from "@fortawesome/pro-solid-svg-icons";
@@ -14,26 +16,27 @@ import prepFilter from "resources/prepFilter";
import "./MediaBrowser.scss";
interface Props {
entityType: EntityType,
handleClose: any,
mediaToken: string,
onSelect?: any,
type?: MediaFilterProp,
filterType?: MediaFilterProp | WeightFilterProp,
username: string,
}
export default function MediaBrowser({ mediaToken, handleClose = () => {}, onSelect, type, username }: Props) {
export default function MediaBrowser({ entityType, filterType: inputFilter, mediaToken, handleClose = () => {}, onSelect, username }: Props) {
console.log("😎",entityType);
const ratings = useRatings();
const [showMasonryGrid, setShowMasonryGrid] = useState(true);
const [mediaType, mediaTypeSet] = useState(type || "all");
const [list, listSet] = useState<MediaFile[]>([]);
const media = useListContent({
const [filterType, filterTypeSet] = useState(inputFilter || "all");
const [list, listSet] = useState<MediaFile | Weight[]>([]);
const entities = useListContent({
addQueries: {
page_size: 24,
...prepFilter(mediaType, "filter_media_type"),
...prepFilter(filterType, ["","filter_media_type","maybe_scoped_weight_type"][entityType]),
},
addSetters: { mediaTypeSet },
// debug: "profile media",
fetcher: GetMediaByUser,
addSetters: { filterTypeSet },
fetcher: [() => {},GetMediaByUser,GetWeightsByUser][entityType] || GetMediaByUser,
list,
listSet,
onInputChange: () => setShowMasonryGrid(false),
@@ -43,18 +46,18 @@ export default function MediaBrowser({ mediaToken, handleClose = () => {}, onSel
setShowMasonryGrid(true);
},
requestList: true,
urlParam: username,
urlParam: "echelon",
urlUpdate: false
});
const handlePageClick = (selectedItem: { selected: number }) => {
media.pageChange(selectedItem.selected);
entities.pageChange(selectedItem.selected);
};
const paginationProps = {
onPageChange: handlePageClick,
pageCount: media.pageCount,
currentPage: media.page,
pageCount: entities.pageCount,
currentPage: entities.page,
};
const sortOptions = [
@@ -71,7 +74,7 @@ export default function MediaBrowser({ mediaToken, handleClose = () => {}, onSel
return <>
<header {...{ className: "fy-media-browser-header" }}>
<div {...{ className: "fy-media-browser-tools" }}>
<h3>Media</h3>
<h3>{ ["","Media","Weights"][entityType] }</h3>
<Icon {...{ className: "icon-close-button", icon: faXmark, onClick: () => handleClose() }}/>
</div>
<div {...{ className: "fy-media-browser-tools" }}>
@@ -80,22 +83,22 @@ export default function MediaBrowser({ mediaToken, handleClose = () => {}, onSel
icon: faArrowDownWideShort,
options: sortOptions,
name: "sort",
onChange: media.onChange,
value: media.sort,
onChange: entities.onChange,
value: entities.sort,
}}
/>
{ (!type || type === "all") && <TempSelect {...{
{ (!inputFilter || inputFilter === "all") && <TempSelect {...{
icon: faFilter,
options: MediaFilterOptions(),
name: "mediaType",
onChange: media.onChange,
value: mediaType,
options: EntityFilterOptions(entityType),
name: "filterType",
onChange: entities.onChange,
value: filterType,
}}/> }
<Pagination {...paginationProps} />
</div>
</header>
<AudioPlayerProvider>
{ media.isLoading ? (
{ entities.isLoading ? (
<div {...{ className: "row gx-3 gy-3" }}>
{Array.from({ length: 12 }).map((_, index) => (
<SkeletonCard key={index} />
@@ -105,7 +108,7 @@ export default function MediaBrowser({ mediaToken, handleClose = () => {}, onSel
<>
{ showMasonryGrid && (
<div {...{ className: "fy-media-browser-list" }}>
<MediaList {...{ list: media.list, success: media.status === 3, onClick }}/>
<MediaList {...{ entityType, list: entities.list, success: entities.status === 3, onClick }}/>
</div>
) }
</>
@@ -33,6 +33,7 @@ export default function useListContent({
urlParam = "",
urlUpdate = true
}: Props) {
console.log("💎",fetcher);
const { pathname, search: locSearch } = useLocation();
const history = useHistory();
const urlQueries = new URLSearchParams(locSearch);
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import { Button, MocapInput } from "components/common";
import { EntityInput } from "components/entities";
import { Button } from "components/common";
// import { useInferenceJobs } from "hooks";
// import InferenceJobsList from "components/layout/InferenceJobsList";
// import { EnqueueEngineCompositing } from "@storyteller/components/src/api/engine_compositor/EnqueueEngineCompositing";
@@ -28,7 +29,7 @@ export default function DevMediaInput({ value }: Props) {
<h2>{ [526,187].map((num = 0) => String.fromCodePoint(128000 + num)) }</h2>
<Button {...{ label: "Enqueue", variant: "primary" }}/>
</header>
<MocapInput {...{ aspectRatio: "landscape", label: "Choose Media File", onChange, type: "all" }}/>
<EntityInput {...{ aspectRatio: "landscape", label: "Choose Media File", onChange, weightType: "sd_1.5" }}/>
<div>
{ mediaToken }
</div>