mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
prep for weight card revisions
This commit is contained in:
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>
|
||||
|
||||
Reference in new issue
Block a user