mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
Merge branch 'feature/mvp' of https://github.com/storytold/storyteller-frontend into feature/mvp
This commit is contained in:
commit
e9bbac2ec0
30 files changed
+510
-187
No files matched your search
@@ -11,6 +11,7 @@ import { faChevronDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { animated, useSpring } from "@react-spring/web";
|
||||
|
||||
interface AccordionProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -96,7 +97,7 @@ function AccordionItem({
|
||||
);
|
||||
}
|
||||
|
||||
function Accordion({ children }: AccordionProps) {
|
||||
function Accordion({ children, className }: AccordionProps) {
|
||||
// Extract titles of items that should be initially open
|
||||
const defaultOpenTitles = React.Children.toArray(children)
|
||||
.filter((child: any) => child.props.defaultOpen)
|
||||
@@ -115,7 +116,9 @@ function Accordion({ children }: AccordionProps) {
|
||||
|
||||
return (
|
||||
<AccordionContext.Provider value={{ openItems, toggleItem }}>
|
||||
<div className="d-flex flex-column gap-3">{children}</div>
|
||||
<div className={`d-flex flex-column gap-3 ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
</AccordionContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import Button from "components/common/Button";
|
||||
import useWeightTypeInfo from "hooks/useWeightTypeInfo/useWeightTypeInfo";
|
||||
import WeightCoverImage from "components/common/WeightCoverImage";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import useToken from "hooks/useToken";
|
||||
import getCardUrl from "../getCardUrl";
|
||||
// import getCardUrl from "../getCardUrl";
|
||||
|
||||
interface AudioCardProps {
|
||||
bookmarks?: any;
|
||||
@@ -24,8 +24,8 @@ interface AudioCardProps {
|
||||
source?: string;
|
||||
type: "media" | "weights";
|
||||
inSelectModal?: boolean;
|
||||
onClick?: (e:any) => any;
|
||||
onResultSelect?: () => void;
|
||||
onResultSelect?: (data: { token: string; title: string }) => void;
|
||||
// onClick?: (e:any) => any;
|
||||
}
|
||||
|
||||
export default function AudioCard({
|
||||
@@ -37,11 +37,10 @@ export default function AudioCard({
|
||||
source = "",
|
||||
type,
|
||||
inSelectModal = false,
|
||||
onClick: inClick,
|
||||
// onClick: inClick,
|
||||
onResultSelect,
|
||||
}: AudioCardProps) {
|
||||
const { setToken, setWeightTitle } = useToken();
|
||||
const linkUrl = getCardUrl(data,source,type);
|
||||
const linkUrl = getCardUrl(data, source, type);
|
||||
const history = useHistory();
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
@@ -49,10 +48,11 @@ export default function AudioCard({
|
||||
};
|
||||
|
||||
const handleSelectModalResultSelect = () => {
|
||||
if (inSelectModal) {
|
||||
setToken(data.weight_token);
|
||||
setWeightTitle && setWeightTitle(data.title);
|
||||
onResultSelect && onResultSelect();
|
||||
if (inSelectModal && onResultSelect) {
|
||||
onResultSelect({
|
||||
token: data.weight_token,
|
||||
title: data.title,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,10 +85,6 @@ export default function AudioCard({
|
||||
}
|
||||
}
|
||||
|
||||
// const onClick = () => {
|
||||
// if (inClick) inClick(data);
|
||||
// };
|
||||
|
||||
const card = (
|
||||
<Card
|
||||
padding={true}
|
||||
@@ -169,14 +165,16 @@ export default function AudioCard({
|
||||
<div className="d-flex flex-grow-1">
|
||||
<Badge label={weightBadgeLabel} color={weightBadgeColor} />
|
||||
</div>
|
||||
{inClick ? (
|
||||
{inSelectModal ? (
|
||||
<Button
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
variant="link"
|
||||
label="Select"
|
||||
className="fs-7"
|
||||
onClick={handleSelectModalResultSelect}
|
||||
onClick={() => {
|
||||
history.push(linkUrl);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
@@ -185,9 +183,7 @@ export default function AudioCard({
|
||||
variant="link"
|
||||
label="Use"
|
||||
className="fs-7"
|
||||
onClick={() => {
|
||||
history.push(linkUrl);
|
||||
}}
|
||||
onClick={handleSelectModalResultSelect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -263,12 +259,12 @@ export default function AudioCard({
|
||||
|
||||
return (
|
||||
<>
|
||||
{inClick ? (
|
||||
{inSelectModal ? (
|
||||
<>{card}</>
|
||||
) : (
|
||||
<Link
|
||||
{...{
|
||||
to: linkUrl
|
||||
to: linkUrl,
|
||||
}}
|
||||
>
|
||||
{card}
|
||||
|
||||
@@ -10,7 +10,7 @@ import Button from "components/common/Button";
|
||||
import { faArrowRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import useWeightTypeInfo from "hooks/useWeightTypeInfo/useWeightTypeInfo";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import useToken from "hooks/useToken";
|
||||
// import useToken from "hooks/useToken";
|
||||
import getCardUrl from "../getCardUrl";
|
||||
|
||||
interface ImageCardProps {
|
||||
@@ -21,7 +21,7 @@ interface ImageCardProps {
|
||||
source?: string;
|
||||
type: "media" | "weights";
|
||||
inSelectModal?: boolean;
|
||||
onResultSelect?: () => void;
|
||||
onResultSelect?: (data:{token: string, title:string}) => void;
|
||||
}
|
||||
|
||||
export default function ImageCard({
|
||||
@@ -35,18 +35,22 @@ export default function ImageCard({
|
||||
onResultSelect,
|
||||
}: ImageCardProps) {
|
||||
const history = useHistory();
|
||||
const { setToken, setWeightTitle } = useToken();
|
||||
// const { setToken, setWeightTitle } = useToken();
|
||||
const linkUrl = getCardUrl(data,source,type);
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
console.log("handleInnderClick")
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const handleSelectModalResultSelect = () => {
|
||||
console.log("handleSelectModalResultSelect")
|
||||
if (inSelectModal) {
|
||||
setToken(data.weight_token);
|
||||
setWeightTitle && setWeightTitle(data.title);
|
||||
onResultSelect && onResultSelect();
|
||||
|
||||
onResultSelect && onResultSelect({
|
||||
token: data.weight_token,
|
||||
title: data.title
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import Button from "components/common/Button";
|
||||
import CreatorName from "../CreatorName";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { Link } from "react-router-dom";
|
||||
import useToken from "hooks/useToken";
|
||||
import getCardUrl from "../getCardUrl";
|
||||
|
||||
interface VideoCardProps {
|
||||
@@ -19,7 +18,7 @@ interface VideoCardProps {
|
||||
source?: string;
|
||||
type: "media" | "weights";
|
||||
inSelectModal?: boolean;
|
||||
onResultSelect?: () => void;
|
||||
onResultSelect?: (data:{token: string, title:string}) => void;
|
||||
}
|
||||
|
||||
export default function VideoCard({
|
||||
@@ -31,7 +30,6 @@ export default function VideoCard({
|
||||
inSelectModal = false,
|
||||
onResultSelect,
|
||||
}: VideoCardProps) {
|
||||
const { setToken, setWeightTitle } = useToken();
|
||||
const linkUrl = getCardUrl(data,source,type);
|
||||
|
||||
const handleInnerClick = (event: any) => {
|
||||
@@ -39,10 +37,8 @@ export default function VideoCard({
|
||||
};
|
||||
|
||||
const handleSelectModalResultSelect = () => {
|
||||
if (inSelectModal) {
|
||||
setToken(data.weight_token || data.token);
|
||||
setWeightTitle && setWeightTitle(data.title);
|
||||
onResultSelect && onResultSelect();
|
||||
if (inSelectModal && onResultSelect) {
|
||||
onResultSelect(data);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,55 +133,6 @@ export default function VideoCard({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* {type === "weights" && (
|
||||
<>
|
||||
<img
|
||||
src={data.public_bucket_path}
|
||||
alt={data.weight_name}
|
||||
className="card-video"
|
||||
/>
|
||||
<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="Video" color="purple" overlay={true} />
|
||||
</div>
|
||||
<Button
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
variant="link"
|
||||
label="Use"
|
||||
className="fs-7"
|
||||
onClick={() => {
|
||||
history.push(linkUrl);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FontAwesomeIcon icon={faPlayCircle} className="card-video-play" />
|
||||
<div className="card-img-overlay-text">
|
||||
<div>
|
||||
<h6 className="fw-semibold text-white mb-1">
|
||||
{data.weight_name}
|
||||
</h6>
|
||||
<p className="fs-7 opacity-75">{timeAgo}</p>
|
||||
<div>
|
||||
<LikeButton
|
||||
{...{
|
||||
overlay: true,
|
||||
...ratings.makeProps({
|
||||
entityToken: data.weight_token,
|
||||
entityType: "model_weight",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)} */}
|
||||
</Card>
|
||||
);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function WeightsCards({ props, type }: Props) {
|
||||
case WeightCategory.TTS:
|
||||
case WeightCategory.VC:
|
||||
case WeightCategory.ZS:
|
||||
return <AudioCard {...{ ...props, showCover: true }} />;
|
||||
return <AudioCard { ...props} showCover />;
|
||||
case WeightCategory.SD:
|
||||
return <ImageCard {...props} />;
|
||||
default:
|
||||
|
||||
@@ -8,12 +8,16 @@ interface Props {
|
||||
label?: string;
|
||||
max?: number;
|
||||
min?: number;
|
||||
name?: string;
|
||||
onChange?: any;
|
||||
required?: boolean;
|
||||
step?: number;
|
||||
thumbTip?: string;
|
||||
// name, value and setter for onChange has to be in this particular format
|
||||
// name = state variable's name as a stringh
|
||||
// value = the state variable's reference
|
||||
// onChange = the statevariable's setter function's reference
|
||||
name?: string;
|
||||
value?: number;
|
||||
onChange?: any;
|
||||
}
|
||||
|
||||
const renderTrack = ({ props: { style, ...props }, children }: any) => (
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.fy-number-slider {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
.fy-number-slider-range {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.fy-number-slider-value {
|
||||
text-align: center;
|
||||
width: fit-content;
|
||||
padding: 6px;
|
||||
border-radius: 8px;
|
||||
background-color: $secondary;
|
||||
border-width: 0px;
|
||||
border-color: none;
|
||||
color: rgba(#fff, 0.85);
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fy-number-slider-track {
|
||||
height: 6px;
|
||||
margin: 0 0 0 0.6875rem; // margin half width of thumb, so that thumb does not overlap margins at minimum position
|
||||
background-color: #4f4f66;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.fy-number-slider-thumb {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: #e66462;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 0px 10px rgba(#000, 0.15);
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from "react";
|
||||
import { Range } from "react-range";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { Label } from "components/common";
|
||||
import "./NumberSliderV2.scss";
|
||||
|
||||
interface Props {
|
||||
label?: string;
|
||||
thumbTip?: string;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
initialValue?: number;
|
||||
onChange?: (x:number) => void;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
function roundToStep(x:number, step:number){
|
||||
return Math.round(x/step) * step;
|
||||
}
|
||||
|
||||
const renderTrack = ({ props: { style, ...props }, children }: any) => (
|
||||
<div {...{ ...props, className: "fy-number-slider-track", style }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
const thumb =
|
||||
(thumbTip = "") =>
|
||||
({ props: { style, ...props } }: any) => {
|
||||
return (
|
||||
<Tippy
|
||||
{...{
|
||||
arrow: false,
|
||||
content: thumbTip,
|
||||
placement: "bottom",
|
||||
theme: "range-slider",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
{...{ ...props, className: "fy-number-slider-thumb", style }}
|
||||
></div>
|
||||
</Tippy>
|
||||
);
|
||||
};
|
||||
|
||||
export default function NumberSlider({
|
||||
label,
|
||||
thumbTip,
|
||||
min,
|
||||
max,
|
||||
step: stepProps,
|
||||
onChange: onChangeCallback,
|
||||
required,
|
||||
initialValue: initialValueProps,
|
||||
}: Props) {
|
||||
const step = stepProps && stepProps <= max-min ? stepProps
|
||||
: max-min >= 1 ? 1 : (max-min) / 10;
|
||||
const initialValue = initialValueProps && initialValueProps <= max && initialValueProps >= min ? initialValueProps
|
||||
: max-min === step ? min : roundToStep((max+min)/2, step);
|
||||
|
||||
function handleInputOnChange(e: React.ChangeEvent<HTMLInputElement>){
|
||||
if(onChangeCallback)onChangeCallback(Number.parseInt(e.target.value))
|
||||
}
|
||||
function handleRangeOnChange(rangeValue: number[]){
|
||||
if(onChangeCallback)onChangeCallback(rangeValue[0])
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label {...{ label, required }} />
|
||||
<div className="fy-number-slider">
|
||||
<input
|
||||
className="fy-number-slider-value"
|
||||
type="number"
|
||||
{...{ min, max, step, value:initialValue}}
|
||||
onChange={handleInputOnChange}
|
||||
/>
|
||||
<div className="fy-number-slider-range">
|
||||
<Range
|
||||
{...{min, max, step,
|
||||
onChange: handleRangeOnChange,
|
||||
renderThumb: thumb(thumbTip),
|
||||
renderTrack,
|
||||
thumbTip,
|
||||
values: [initialValue],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './NumberSliderV2';
|
||||
@@ -17,7 +17,7 @@ interface SearcherProps {
|
||||
type?: "page" | "modal";
|
||||
dataType?: "media" | "weights";
|
||||
weightType?: string;
|
||||
onResultSelect?: () => void;
|
||||
onResultSelect?: (data:{token:string, title:string}) => void;
|
||||
searcherKey: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ListMediaFiles } from "@storyteller/components/src/api/media_files/List
|
||||
interface SelectMediaListProps {
|
||||
mediaType: string;
|
||||
listKey: string;
|
||||
onResultSelect?: () => void;
|
||||
onResultSelect?: (data:{token:string, title:string}) => void;
|
||||
}
|
||||
|
||||
export default function SelectMediaList({
|
||||
|
||||
@@ -4,7 +4,6 @@ import Modal from "../Modal";
|
||||
import NonRouteTabs from "../Tabs/NonRouteTabs";
|
||||
import Input from "../Input";
|
||||
import Button from "../Button";
|
||||
import useToken from "hooks/useToken";
|
||||
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import SelectMediaList from "./SelectMediaList";
|
||||
import SelectWeightsList from "./SelectWeightsList";
|
||||
@@ -21,7 +20,10 @@ interface SelectModalProps {
|
||||
label?: string;
|
||||
tabs: TabConfig[];
|
||||
modalTitle?: string;
|
||||
onSelect?: (token: string) => void;
|
||||
onSelect?: (data:{
|
||||
token: string,
|
||||
title: string,
|
||||
}) => void;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
@@ -34,8 +36,8 @@ const SelectModal = memo(
|
||||
required,
|
||||
}: SelectModalProps) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const { token, setToken, weightTitle, setWeightTitle } = useToken();
|
||||
const [selectedValue, setSelectedValue] = useState("");
|
||||
const initialValue = {token:"", title:""};
|
||||
const [selectedValue, setSelectedValue] = useState(initialValue);
|
||||
const [activeTab, setActiveTab] = useState(tabs[0].tabKey);
|
||||
const [mediaType, setMediaType] = useState(
|
||||
tabs[0].mediaTypeFilter || "all"
|
||||
@@ -51,19 +53,6 @@ const SelectModal = memo(
|
||||
setWeightType(currentTab?.weightTypeFilter || "all");
|
||||
}, [activeTab, tabs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (token && token !== selectedValue && onSelect) {
|
||||
setSelectedValue(token);
|
||||
onSelect(token);
|
||||
} else if (weightTitle && weightTitle !== selectedValue && onSelect) {
|
||||
setSelectedValue(weightTitle);
|
||||
onSelect(weightTitle);
|
||||
}
|
||||
|
||||
console.log("token", token, "weightTitle", weightTitle);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [token, weightTitle]);
|
||||
|
||||
const openModal = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
@@ -73,16 +62,21 @@ const SelectModal = memo(
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
setWeightTitle && setWeightTitle("");
|
||||
setToken("");
|
||||
setSelectedValue(initialValue);
|
||||
};
|
||||
|
||||
const handleOnSelect = (data:{token:string, title:string}) => {
|
||||
setSelectedValue({token: data.token, title: data.title || ""});
|
||||
if (onSelect) onSelect(data);
|
||||
closeModal();
|
||||
}
|
||||
|
||||
const searchTabs = tabs.map(tab => ({
|
||||
label: tab.label,
|
||||
content: tab.searcher ? (
|
||||
<Searcher
|
||||
type="modal"
|
||||
onResultSelect={closeModal}
|
||||
onResultSelect={handleOnSelect}
|
||||
searcherKey={tab.tabKey}
|
||||
weightType={tab.weightTypeFilter}
|
||||
/>
|
||||
@@ -92,14 +86,14 @@ const SelectModal = memo(
|
||||
<SelectMediaList
|
||||
mediaType={mediaType}
|
||||
listKey={tab.tabKey}
|
||||
onResultSelect={closeModal}
|
||||
onResultSelect={handleOnSelect}
|
||||
/>
|
||||
)}
|
||||
{tab.type === "weights" && (
|
||||
<SelectWeightsList
|
||||
weightType={weightType}
|
||||
listKey={tab.tabKey}
|
||||
onResultSelect={closeModal}
|
||||
onResultSelect={handleOnSelect}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -122,10 +116,12 @@ const SelectModal = memo(
|
||||
disabled={true}
|
||||
className="w-100"
|
||||
placeholder="None selected"
|
||||
value={weightTitle ? weightTitle : token || ""}
|
||||
value={selectedValue.title !==""
|
||||
? selectedValue.title
|
||||
: selectedValue.token || ""}
|
||||
/>
|
||||
<Button label={token ? "Change" : "Select"} onClick={openModal} />
|
||||
{token && (
|
||||
<Button label={selectedValue.token !== "" ? "Change" : "Select"} onClick={openModal} />
|
||||
{selectedValue.token && (
|
||||
<Button
|
||||
square={true}
|
||||
variant="danger"
|
||||
@@ -153,4 +149,4 @@ const SelectModal = memo(
|
||||
}
|
||||
);
|
||||
|
||||
export default SelectModal;
|
||||
export default SelectModal;
|
||||
@@ -10,7 +10,7 @@ import { Weight } from "@storyteller/components/src/api/weights/GetWeight";
|
||||
interface SelectWeightsListProps {
|
||||
weightType: string;
|
||||
listKey: string;
|
||||
onResultSelect?: () => void;
|
||||
onResultSelect?: (data:{token:string, title:string}) => void;
|
||||
}
|
||||
|
||||
export default function SelectWeightsList({
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./SelectModal";
|
||||
@@ -0,0 +1,27 @@
|
||||
.fy-textarea {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&:only-child,
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.d-flex:not(.flex-column) > .fy-textarea {
|
||||
margin-bottom: 0;
|
||||
margin-right: 0.5rem;
|
||||
|
||||
&:only-child,
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.gap-1 > .fy-textarea,
|
||||
.gap-2 > .fy-textarea,
|
||||
.gap-3 > .fy-textarea,
|
||||
.gap-4 > .fy-textarea,
|
||||
.gap-5 > .fy-textarea {
|
||||
margin-bottom: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import "./TextAreaV2.scss";
|
||||
|
||||
interface TextAreaProps {
|
||||
label?: string;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
initialValue?: string;
|
||||
value?: string;
|
||||
onChange?: (newText:string) => void
|
||||
}
|
||||
|
||||
export default function TextArea({
|
||||
label,
|
||||
required,
|
||||
value,
|
||||
placeholder,
|
||||
onChange: onChangeCallback,
|
||||
}: TextAreaProps) {
|
||||
const handleOnChange = (e:React.ChangeEvent<HTMLTextAreaElement>)=>{
|
||||
if(onChangeCallback) onChangeCallback (e.target.value);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{label &&
|
||||
<label className={`sub-title ${required ? "required" : ""}`}>
|
||||
{label}
|
||||
</label>
|
||||
}
|
||||
<div className="form-group">
|
||||
<textarea
|
||||
className="form-control"
|
||||
onChange={handleOnChange}
|
||||
{...{value, placeholder}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./TextAreaV2";
|
||||
@@ -1,3 +1,4 @@
|
||||
import Accordion from "./Accordion";
|
||||
import AudioBlobPreview from "./AudioBlobPreview";
|
||||
import AudioInput from "./AudioInput";
|
||||
import BasicVideo from "./BasicVideo";
|
||||
@@ -16,10 +17,12 @@ import Label from "./Label";
|
||||
import MocapInput from "./MocapInput";
|
||||
import Modal from "./Modal";
|
||||
import NumberSlider from "./NumberSlider";
|
||||
import NumberSliderV2 from "./NumberSliderV2";
|
||||
import Panel from "./Panel";
|
||||
import Radio from "./Radio";
|
||||
import SegmentButtons from "./SegmentButtons";
|
||||
import Select from "./Select";
|
||||
import SelectModal from "./SelectModal";
|
||||
import Skeleton from "./Skeleton";
|
||||
import Slider from "./Slider";
|
||||
import Spinner from "./Spinner";
|
||||
@@ -29,10 +32,13 @@ import TempAudioPlayer from "./TempAudioPlayer";
|
||||
import TempInput from "./TempInput";
|
||||
import TempSelect from "./TempSelect";
|
||||
import TempTextArea from "./TempTextArea";
|
||||
import TextArea from "./TextArea";
|
||||
import TextAreaV2 from "./TextAreaV2";
|
||||
import TintSpinner from "./TintSpinner";
|
||||
import VideoInput from "./VideoInput";
|
||||
|
||||
export {
|
||||
Accordion,
|
||||
AudioBlobPreview,
|
||||
AudioInput,
|
||||
BasicVideo,
|
||||
@@ -51,10 +57,12 @@ export {
|
||||
MocapInput,
|
||||
Modal,
|
||||
NumberSlider,
|
||||
NumberSliderV2,
|
||||
Panel,
|
||||
Radio,
|
||||
SegmentButtons,
|
||||
Select,
|
||||
SelectModal,
|
||||
Skeleton,
|
||||
Slider,
|
||||
Spinner,
|
||||
@@ -64,6 +72,8 @@ export {
|
||||
TempInput,
|
||||
TempSelect,
|
||||
TempTextArea,
|
||||
TextArea,
|
||||
TextAreaV2,
|
||||
TintSpinner,
|
||||
VideoInput
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import useTimeAgo from "hooks/useTimeAgo";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
// import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import { MakeRatingsProps } from "hooks/useRatings";
|
||||
import { faPersonWalking } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
faCameraMovie,
|
||||
faPersonRays,
|
||||
faMessageImage,
|
||||
faPhotoFilm,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React, { useEffect, useState } from "react";
|
||||
@@ -371,6 +372,20 @@ export default function SideNav({
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NavLink
|
||||
to="/storyteller-filter"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faPhotoFilm}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
{t("storytellerFilter")}
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NavLink
|
||||
to="/studio"
|
||||
|
||||
@@ -821,7 +821,7 @@ class PageContainer extends React.Component<
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/storyteller-filter/:mediaToken?">
|
||||
<Route path="/storyteller-filter">
|
||||
<StorytellerFilterPage
|
||||
{...{
|
||||
enqueueInferenceJob: this.props.enqueueInferenceJob,
|
||||
|
||||
@@ -3,13 +3,11 @@ import React, {useReducer } from 'react';
|
||||
import { useLocalize } from "hooks";
|
||||
import { FrontendInferenceJobType, InferenceJob } from '@storyteller/components/src/jobs/InferenceJob';
|
||||
|
||||
import { Container, ErrorMessage } from "components/common";
|
||||
import { Container } from "components/common";
|
||||
import PageHeader from "components/layout/PageHeader";
|
||||
|
||||
import PageVideoProvision from './components/pageVideoProvision';
|
||||
import PageFilterControls from './components/pageFilterControls';
|
||||
import { states, reducer } from "./storytellerFilterReducer";
|
||||
|
||||
import SubRoutes from "./storytellerFilterRoutes";
|
||||
|
||||
export default function StorytellerFilter(props:{
|
||||
enqueueInferenceJob: (
|
||||
@@ -20,37 +18,20 @@ export default function StorytellerFilter(props:{
|
||||
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
|
||||
}){
|
||||
const debug=true;
|
||||
const { t } = useLocalize("StorytellerFilter");
|
||||
const { NO_FILE, FILE_STAGED, FILE_UPLOADING, FILE_UPLOADED, FILE_SELECTED, FILE_LOADING, FILE_LOADED } = states;
|
||||
const {t} = useLocalize("StorytellerFilter");
|
||||
const { NO_FILE } = states;
|
||||
const [pageState, dispatchPageState] = useReducer(reducer, {
|
||||
status: NO_FILE,
|
||||
});
|
||||
|
||||
const pagePicker = () => {
|
||||
switch (pageState.status){
|
||||
case NO_FILE:
|
||||
case FILE_STAGED:
|
||||
case FILE_UPLOADING:
|
||||
case FILE_SELECTED:
|
||||
return <PageVideoProvision {...{debug, t, pageState, dispatchPageState}}/>
|
||||
case FILE_UPLOADED:
|
||||
case FILE_LOADING:
|
||||
case FILE_LOADED:
|
||||
return <PageFilterControls {...{debug, t, pageState, dispatchPageState}}/>
|
||||
default:
|
||||
return <ErrorMessage/>
|
||||
}
|
||||
}
|
||||
const currPage = pagePicker();
|
||||
|
||||
return(
|
||||
<Container type="panel" className="mb-5">
|
||||
<PageHeader
|
||||
title={t("headings.title")}
|
||||
subText={t("headings.subtitle")}
|
||||
// imageUrl="/images/header/video-mocap.png"
|
||||
/>
|
||||
{currPage}
|
||||
|
||||
<SubRoutes {...{debug, t, pageState, dispatchPageState}}/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
+143
-15
@@ -1,8 +1,22 @@
|
||||
import React from "react";
|
||||
import React, {useState, useEffect} from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { useMedia } from "hooks";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { states, Action, State } from "../storytellerFilterReducer";
|
||||
import { BasicVideo, ErrorMessage, Panel, Spinner } from "components/common";
|
||||
import {
|
||||
Accordion,
|
||||
BasicVideo,
|
||||
ErrorMessage,
|
||||
NumberSliderV2,
|
||||
Panel,
|
||||
SelectModal,
|
||||
Spinner,
|
||||
TextAreaV2
|
||||
} from "components/common";
|
||||
|
||||
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function PageFilterControls({
|
||||
debug=false, t, pageState, dispatchPageState
|
||||
@@ -12,11 +26,12 @@ export default function PageFilterControls({
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const { mediaToken } = useParams<any>();
|
||||
|
||||
useMedia({
|
||||
mediaToken: pageState.mediaFileToken,
|
||||
mediaToken: pageState.mediaFileToken || mediaToken,
|
||||
onSuccess: (res: any) => {
|
||||
// ratings.gather({ res, key: "token" });
|
||||
console.log(res)
|
||||
dispatchPageState({
|
||||
type: 'loadFileSuccess',
|
||||
payload: {mediaFile: res}
|
||||
@@ -24,22 +39,135 @@ export default function PageFilterControls({
|
||||
},
|
||||
});
|
||||
|
||||
const [filterState, setFilterState] = useState({
|
||||
sdModelToken: "",
|
||||
loraModelToken: "",
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
firstPass: 15,
|
||||
upScalePass: 15
|
||||
});
|
||||
useEffect(()=>{
|
||||
if(debug) console.log(filterState)
|
||||
},[filterState, debug])
|
||||
|
||||
const handleOnChange = (key: string, newValue:any,) => {
|
||||
setFilterState((curr)=>({...curr, [key]: newValue}));
|
||||
}
|
||||
|
||||
if (pageState.mediaFile){
|
||||
const mediaLink = new BucketConfig().getGcsUrl(pageState.mediaFile.public_bucket_path);
|
||||
if (mediaLink)
|
||||
return(
|
||||
<Panel>
|
||||
<div className="row g-3 p-3">
|
||||
{/* <h1>{t("message.fileUploaded")}</h1> */}
|
||||
{debug && <p>{`File Token: ${pageState.mediaFileToken}`}</p> }
|
||||
<div className="col-6">
|
||||
<BasicVideo src={mediaLink} />
|
||||
<>
|
||||
<Panel>
|
||||
<div className="row g-3 p-3">
|
||||
{debug && <p>{`File Token: ${pageState.mediaFileToken}`}</p> }
|
||||
<div className="col-5">
|
||||
<BasicVideo src={mediaLink} />
|
||||
</div>
|
||||
<div className="col-1 d-flex align-items-center justify-items-center">
|
||||
<FontAwesomeIcon icon={faChevronRight} className="fa-6x"/>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<BasicVideo src={mediaLink} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<BasicVideo src={mediaLink} />
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Panel>
|
||||
<Accordion className="mt-4">
|
||||
<Accordion.Item title={t("heading1")}>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModal
|
||||
modalTitle="Select a Stable Diffusion Weight"
|
||||
label="Select a Stable Diffusion Weight"
|
||||
onSelect={({token})=>{
|
||||
handleOnChange("sdModelToken", token);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Weights",
|
||||
tabKey: "allWeights",
|
||||
weightTypeFilter: "sdxl",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedWeights",
|
||||
weightTypeFilter: "sdxl",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SelectModal
|
||||
modalTitle="Select a LoRA Weight"
|
||||
label="Additional LoRA Weight"
|
||||
onSelect={({token})=>{
|
||||
console.log(`calling from select modal result select ${token}`);
|
||||
handleOnChange("loraModelToken", token);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All LoRA Weights",
|
||||
tabKey: "allLoraWeights",
|
||||
weightTypeFilter: "rvc_v2",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedLoraWeights",
|
||||
weightTypeFilter: "rvc_v2",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<TextAreaV2
|
||||
{...{
|
||||
label: "Prompt",
|
||||
placeholder: "Enter a prompt",
|
||||
onChange: (val:string)=>handleOnChange("posPrompt", val),
|
||||
value: filterState.posPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
<TextAreaV2
|
||||
{...{
|
||||
label: "Negative Prompt",
|
||||
placeholder: "Enter Negative Prompt",
|
||||
onChange: (val:string)=>handleOnChange("negPrompt", val),
|
||||
value: filterState.negPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title={t("headings2")}>
|
||||
<div className="row g-3 p-3">
|
||||
<NumberSliderV2 {...{
|
||||
min: 1,
|
||||
max: 30,
|
||||
step: 0.5,
|
||||
initialValue: filterState.firstPass,
|
||||
label: " Denoise First Pass",
|
||||
thumbTip: "Denoise First Pass",
|
||||
onChange: (val)=>{handleOnChange("firstPass",val)}
|
||||
}}/>
|
||||
<NumberSliderV2 {...{
|
||||
min: 1,
|
||||
max: 30,
|
||||
initialValue: filterState.upScalePass,
|
||||
label: "Denoise Upscale Pass",
|
||||
thumbTip: "Denoise Upscale Pass",
|
||||
onChange: (val)=>{handleOnChange("upScalePass", val)}
|
||||
}}/>
|
||||
<br />
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</>
|
||||
);
|
||||
}else if (pageState.status <= states.FILE_LOADING){
|
||||
return (
|
||||
|
||||
+2
-8
@@ -1,9 +1,8 @@
|
||||
import React from "react";
|
||||
import { Redirect, useLocation } from "react-router-dom";
|
||||
|
||||
import { BasicVideo, Panel, Tabs } from "components/common";
|
||||
|
||||
import { Action, State } from "../storytellerFilterReducer";
|
||||
import { State, Action } from "../storytellerFilterReducer";
|
||||
import TabContentUpload from "./tabContentUpload";
|
||||
import TabContentLibrary from "./tabContentLibrary";
|
||||
|
||||
@@ -15,6 +14,7 @@ export default function PageVideoProvision({
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: t("tabTitle.upload"),
|
||||
@@ -34,12 +34,6 @@ export default function PageVideoProvision({
|
||||
},
|
||||
];
|
||||
|
||||
//pick default tab using pathname
|
||||
const { pathname } = useLocation();
|
||||
if (pathname === "/storyteller-filter" || pathname === "/storyteller-filter/") {
|
||||
return <Redirect to={"/storyteller-filter/upload"} />;
|
||||
}
|
||||
|
||||
return(
|
||||
<Panel>
|
||||
<div className="row g-0">
|
||||
|
||||
+12
-9
@@ -1,7 +1,9 @@
|
||||
import SelectModal from "components/common/SelectModal/SelectModal";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { states, Action, State } from "../storytellerFilterReducer";
|
||||
import { Button } from "components/common";
|
||||
import SelectModal from "components/common/SelectModal/SelectModal";
|
||||
|
||||
export default function TabContentLibrary({
|
||||
t, pageState, dispatchPageState
|
||||
}: {
|
||||
@@ -9,14 +11,13 @@ export default function TabContentLibrary({
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}){
|
||||
|
||||
const handleProceed = ()=>{
|
||||
dispatchPageState({type: "loadFile"})
|
||||
}
|
||||
const handleOnSelect = (token:string)=>{
|
||||
const handleOnSelect = (data:{token:string, title:string})=>{
|
||||
dispatchPageState({
|
||||
type: "selectedFile",
|
||||
payload: {mediaFileToken: token}
|
||||
payload: {mediaFileToken: data.token}
|
||||
});
|
||||
};
|
||||
return (
|
||||
@@ -35,11 +36,13 @@ export default function TabContentLibrary({
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{pageState.status === states.FILE_SELECTED &&
|
||||
<Button
|
||||
label={t("button.proceed")}
|
||||
onClick={handleProceed}
|
||||
/>
|
||||
{pageState.status === states.FILE_SELECTED &&
|
||||
<NavLink to={`load/${pageState.mediaFileToken}`}>
|
||||
<Button
|
||||
label={t("button.proceed")}
|
||||
onClick={handleProceed}
|
||||
/>
|
||||
</NavLink>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
+6
-1
@@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { UploadMedia } from "@storyteller/components/src/api/media_files/UploadMedia";
|
||||
@@ -15,7 +16,7 @@ export default function TabContentUpload({
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const videoProps = useFile({});
|
||||
const {NO_FILE, FILE_STAGED, FILE_UPLOADING} = states;
|
||||
const {NO_FILE, FILE_STAGED, FILE_UPLOADING, FILE_UPLOADED} = states;
|
||||
|
||||
const makeVideoUploadRequest = () => ({
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
@@ -85,6 +86,10 @@ export default function TabContentUpload({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}else if (pageState.status === FILE_UPLOADED){
|
||||
return(
|
||||
<Redirect to={`load/${pageState.mediaFileToken}`}/>
|
||||
)
|
||||
}
|
||||
return <ErrorMessage />;
|
||||
}
|
||||
+1
-1
@@ -28,7 +28,7 @@ export type Action =
|
||||
|
||||
|
||||
export function reducer (state: State, action: Action): State {
|
||||
console.log(action);
|
||||
|
||||
switch(action.type){
|
||||
case 'stagedFile':
|
||||
return {...state, status: states.FILE_STAGED}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
import {Switch, Route, useRouteMatch, Redirect} from "react-router-dom";
|
||||
import PageFilterControls from "./components/pageFilterControls";
|
||||
import PageVideoProvision from "./components/pageVideoProvision";
|
||||
import {State,Action} from "./storytellerFilterReducer";
|
||||
|
||||
export default function SubRoutes ({
|
||||
debug, t, pageState, dispatchPageState
|
||||
}:{
|
||||
debug?:boolean,
|
||||
t:Function,
|
||||
pageState:State,
|
||||
dispatchPageState: (action: Action) => void
|
||||
}){
|
||||
const { path } = useRouteMatch();
|
||||
|
||||
return(
|
||||
<Switch>
|
||||
<Route exact path={`${path}/load/:mediaToken`} >
|
||||
<PageFilterControls {...{debug, t, pageState, dispatchPageState}}/>
|
||||
</Route>
|
||||
<Route exact path={`${path}/upload`}>
|
||||
<PageVideoProvision {...{debug, t, pageState, dispatchPageState}}/>
|
||||
</Route>
|
||||
<Route exact path={`${path}/select-media`}>
|
||||
<PageVideoProvision {...{debug, t, pageState, dispatchPageState}}/>
|
||||
</Route>
|
||||
<Route path={`${path}`}>
|
||||
<Redirect to={`${path}/upload`} />
|
||||
</Route>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ export default function TabContentLibrary({
|
||||
payload: { mediaFileToken: token },
|
||||
});
|
||||
};
|
||||
const handleOnSelect = (token: string) => {
|
||||
setToken(token);
|
||||
const handleOnSelect = (data:{token:string,title:string}) => {
|
||||
setToken(data.token);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import React, { useRef, useState, memo } from "react";
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
NumberSlider,
|
||||
Panel,
|
||||
SegmentButtons,
|
||||
SelectModal,
|
||||
TempSelect,
|
||||
TempTextArea,
|
||||
} from "components/common";
|
||||
import { onChanger } from "resources";
|
||||
import Accordion from "components/common/Accordion";
|
||||
import {
|
||||
faRectangleLandscape,
|
||||
faRectanglePortrait,
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
EnqueueImageGenIsError,
|
||||
} from "@storyteller/components/src/api/image_generation/EnqueueImageGen";
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import SelectModal from "components/common/SelectModal/SelectModal";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import PremiumLock from "components/PremiumLock";
|
||||
|
||||
@@ -46,12 +46,11 @@ function SdInferencePanel({
|
||||
const [loraToken, setLoraToken] = useState<string | null>(null);
|
||||
const [weightToken, setWeightToken] = useState(sd_model_token);
|
||||
|
||||
const handleOnWeightSelect = (token: string) => {
|
||||
setWeightToken(token);
|
||||
const handleOnWeightSelect = (data:{token:string,title:string}) => {
|
||||
setWeightToken(data.token);
|
||||
};
|
||||
|
||||
const handleOnSelect = (token: string) => {
|
||||
setLoraToken(token);
|
||||
const handleOnSelect = (data:{token:string,title:string}) => {
|
||||
setLoraToken(data.token);
|
||||
};
|
||||
|
||||
const [isEnqueuing, setIsEnqueuing] = useState(false);
|
||||
|
||||
Reference in new issue
Block a user