added previews/downloads to InferenceJobsList

This commit is contained in:
Victor committed 2024-09-04 20:58:04 -07:00
1 parent d4b61743e7
commit 303d8d2b66
11 files changed
+215 -42

No files matched your search

@@ -2,12 +2,16 @@ import { FetchStatus } from "./_common/SharedFetchTypes";
import { MediaFileType, GetMediaByUser } from "./media_files";
import { Weight, ListWeights, ListFeaturedWeights } from "./weights";
import { GetBookmarksByUser } from "./bookmarks/GetBookmarksByUser";
import { MediaFileClass } from "./enums/MediaFileClass";
import { BucketConfig } from "./BucketConfig";
export type { MediaFileType, Weight };
export {
BucketConfig,
FetchStatus,
GetBookmarksByUser,
GetMediaByUser,
ListWeights,
ListFeaturedWeights,
MediaFileClass,
};
@@ -10,6 +10,7 @@ export default function Button({
buttonRef,
label,
icon,
onClick,
small,
variant: variantProps = "primary",
to,
@@ -74,6 +75,7 @@ export default function Button({
square ? (small ? "button-square-small" : "button-square") : ""
} button-${variant} ${full ? "w-100" : ""}`,
disabled: disabled || isLoading,
onClick,
};
delete rest.className;
@@ -95,18 +97,14 @@ export default function Button({
);
const WrappedButton = to ? (
<Link to={to} {...commonProps}>
{ButtonContent}
</Link>
<Link {...{ ...commonProps, to }}>{ButtonContent}</Link>
) : href ? (
<a
href={href}
target={target}
rel="noopener noreferrer"
download={
download ? (typeof download === "string" ? download : true) : undefined
}
{...commonProps}
{...{ ...commonProps, href, target }}
>
{ButtonContent}
</a>
@@ -5,6 +5,7 @@ export default interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement> {
buttonRef?: any;
label?: string | null;
onClick?: (e: any) => any;
icon?: IconDefinition;
small?: boolean;
variant?: string;
@@ -55,10 +55,11 @@ $primary-transparent: rgba($primary, 0.75);
.fy-inference-jobs-list-grid {
display: grid;
grid-template-columns: 2.25rem fit-content(100px) minmax(0, 1fr) minmax(
grid-template-columns: min-content fit-content(100px) minmax(0, 1fr) minmax(
min-content,
1fr
);
// grid-template-rows: 4rem;
gap: 1rem 0;
.work-indicator {
@@ -75,7 +76,7 @@ $primary-transparent: rgba($primary, 0.75);
.fy-inference-job-hover-on,
.fy-inference-job-hover-off {
padding: 0.5rem;
padding: 0.75rem;
// &:first-of-type { }
}
@@ -83,6 +84,12 @@ $primary-transparent: rgba($primary, 0.75);
.fy-inference-job-success {
cursor: pointer;
button {
height: 2.25rem;
width: 2.25rem;
margin: auto 0 auto 1rem;
}
&.fy-inference-job-hover-on {
background-color: $panel-inner;
}
@@ -90,8 +97,8 @@ $primary-transparent: rgba($primary, 0.75);
.fy-inference-job-indicator {
border-radius: 0.5rem 0 0 0.5rem;
margin-left: -0.5rem;
padding-right: 0;
// margin-left: -0.5rem;
// padding-right: 0;
.fy-inference-job-indicator-container {
// background-color: red;
@@ -162,7 +169,7 @@ $primary-transparent: rgba($primary, 0.75);
}
.fy-inference-job-action-frame {
margin-right: -0.5rem;
// margin-right: -0.5rem;
position: relative;
padding: 0;
// width: fit-content;
@@ -175,12 +182,8 @@ $primary-transparent: rgba($primary, 0.75);
cursor: pointer;
margin: auto;
white-space: nowrap;
// padding-left: .75rem;
// display: flex;
// align-items: center;
border-radius: 0.5rem;
// line-height: 1em;
margin: auto 0 auto auto;
margin: auto 0 auto 1rem;
}
.job-status-complete-success {
@@ -222,6 +225,33 @@ $primary-transparent: rgba($primary, 0.75);
// height: 2.25rem;
}
}
.fy-inference-job-preview {
// background-color: red;
// grid-column-start: 1;
// grid-column-end: five;
.fy-inference-job-preview-thumb {
// background-color: blue;
// margin: 3rem 0;
height: 3.25rem;
width: fit-content;
overflow: hidden;
aspect-ratio: 4/3;
position: relative;
.fy-inference-job-preview-static,
.fy-inference-job-preview-gif {
position: absolute;
border-radius: 0.5rem;
width: auto;
height: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
}
.fy-job-queue-ticker {
@@ -5,17 +5,21 @@ import {
FrontendInferenceJobType,
InferenceJob,
} from "@storyteller/components/src/jobs/InferenceJob";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
import {
CancelJob,
CancelJobResponse,
} from "@storyteller/components/src/api/jobs/CancelJob";
import { useHover, useSlides } from "hooks";
import { MediaURLs, useHover, useMedia, useSlides } from "hooks";
import {
JobState,
jobStateCanChange,
} from "@storyteller/components/src/jobs/JobStates";
import { Button } from "components/common";
import { useHistory } from "react-router-dom";
import { STYLES_BY_KEY } from "common/StyleOptions";
import JobResultPreview from "./JobResultPreview";
import { faArrowDownToLine } from "@fortawesome/pro-solid-svg-icons";
interface JobListItem extends InferenceJob {
failures: (fail: string) => string;
@@ -28,18 +32,56 @@ interface JobListItem extends InferenceJob {
const BaseAction = ({
canStop = false,
hover = false,
mediaFile,
maybeResultToken = "",
success = false,
toggleSlide = () => {},
urls,
}: {
canStop: boolean;
hover: boolean;
mediaFile: MediaFile;
maybeResultToken: string;
success: boolean;
toggleSlide: () => void;
urls: MediaURLs;
}) =>
canStop || success ? (
<svg
{...{
className: `fy-inference-job-action${success ? "-success" : ""}`,
...(success ? {} : { onClick: toggleSlide }),
}}
>
<ArrowX {...{ checked: success }} />
</svg>
<>
<JobResultPreview
{...{
hover,
mediaFile,
mediaToken: maybeResultToken,
show: success,
urls,
}}
/>
{success && (
<Button
{...{
href: urls.file,
icon: faArrowDownToLine,
onClick: (e: any) => {
e.stopPropagation();
},
square: true,
small: true,
target: "_blank",
variant: "secondary",
}}
/>
)}
<svg
{...{
className: `fy-inference-job-action${success ? "-success" : ""}`,
...(success ? {} : { onClick: toggleSlide }),
}}
>
<ArrowX {...{ checked: success }} />
</svg>
</>
) : (
<></>
);
@@ -164,12 +206,22 @@ export default function JobItem({
}
};
const { mediaFile, urls } = useMedia({ mediaToken: maybeResultToken || "" });
const slides = useSlides({
index,
slides: [
{
component: BaseAction,
props: { success, toggleSlide, canStop: canStop() },
props: {
canStop: canStop(),
hover,
maybeResultToken,
mediaFile,
success,
toggleSlide,
urls,
},
},
{ component: StopConfirm, props: { stopClick, toggleSlide } },
],
@@ -229,11 +281,6 @@ export default function JobItem({
<div {...{ ...outerProps(`fy-inference-job-action-frame`), ...hoverSet }}>
{slides}
</div>
{
// <div {...{ className: "fy-inference-job-previews" }}>
// { }
// </div>
}
</>
);
}
@@ -0,0 +1,66 @@
import React from "react";
import { MediaURLs } from "hooks";
// import { MediaFileClass } from "../enums/MediaFileClass";
import { MediaFileClass } from "@storyteller/components/src/api";
import { a, TransitionFn, useTransition } from "@react-spring/web";
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
interface JobResultPreviewProps {
hover: boolean;
mediaFile: MediaFile;
show?: boolean;
urls: MediaURLs;
}
export default function JobResultPreview({
hover,
mediaFile,
show,
urls,
}: JobResultPreviewProps) {
const transitions: TransitionFn<boolean, { opacity: number }> = useTransition(
hover,
{
config: { mass: 1, tension: 80, friction: 10 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
}
);
const previewSwitch = () => {
switch (mediaFile?.media_class) {
case MediaFileClass.Image:
return <div>Image</div>;
case MediaFileClass.Video:
return (
<div {...{ className: "fy-inference-job-preview-thumb" }}>
<a.img
{...{
className: "fy-inference-job-preview-static",
src: urls.thumb(),
}}
/>
{transitions((style, isHovering) =>
isHovering ? (
<a.img
{...{
className: "fy-inference-job-preview-gif",
src: urls.gif(),
style,
}}
/>
) : null
)}
</div>
);
default:
return null;
}
};
return show ? (
<div {...{ className: "fy-inference-job-preview" }}>{previewSwitch()}</div>
) : null;
}
@@ -12,7 +12,7 @@ import useChanger from "./useChanger";
import useCoverImgUpload from "./useCoverImgUpload";
import useDebounce from "./useDebounce";
import useFile from "./useFile";
import useHover from "./useHover";
import useHover, { HoverValues } from "./useHover";
import useId from "./useId";
import useIdempotency from "./useIdempotency";
import useInferenceJobs from "./useInferenceJobs";
@@ -22,7 +22,7 @@ import useJobStatus from "./useJobStatus";
import useLazyLists from "./useLazyLists";
import useListContent from "./useListContent";
import useLocalize from "./useLocalize";
import useMedia from "./useMedia";
import useMedia, { MediaURLs } from "./useMedia";
import useMediaUploader from "./useMediaUploader";
import useModal from "./useModal";
import useModalState, { ModalConfig, ModalWidth } from "./useModalState";
@@ -83,9 +83,11 @@ export type {
AnimationEvents,
BatchInputProps,
CameraState,
HoverValues,
MakeBookmarksProps,
MakeBatchPropsParams,
MakeRatingsProps,
MediaURLs,
ModalConfig,
ModalWidth,
};
@@ -1,14 +1,24 @@
import { useState } from "react";
type hoverEventFn = (e?: any) => void;
interface UseHoverParams {
onMouseEnter?: (e?: any) => void;
onMouseLeave?: (e?: any) => void;
onMouseEnter?: hoverEventFn;
onMouseLeave?: hoverEventFn;
}
export type HoverValues = [
boolean,
{
onMouseEnter: hoverEventFn;
onMouseLeave: hoverEventFn;
},
];
export default function useHover({
onMouseEnter = () => {},
onMouseLeave = () => {},
}: UseHoverParams) {
}: UseHoverParams): HoverValues {
const [hover, hoverSet] = useState(false);
const onHover = (x: boolean) => (e: any) => {
hoverSet(x);
@@ -1 +0,0 @@
export { default } from './useHover';
@@ -11,6 +11,14 @@ import { DeleteMedia } from "@storyteller/components/src/api/media_files/DeleteM
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
type ThumbFn = (width?: number, quality?: number) => string;
export interface MediaURLs {
file: string;
gif: ThumbFn;
thumb: ThumbFn;
}
export default function useMedia({
debug = "",
mediaToken = "",
@@ -42,11 +50,19 @@ export default function useMedia({
status === FetchStatus.in_progress ||
writeStatus === FetchStatus.in_progress;
// const bucketConfig = new BucketConfig();
const bucketConfig = new BucketConfig();
const bucketUrl = media?.public_bucket_path
? new BucketConfig().getGcsUrl(media.public_bucket_path)
: "";
const publicBucketPath = media?.public_bucket_path || "";
const bucketUrl = bucketConfig.getGcsUrl(publicBucketPath);
const urls = {
file: bucketConfig.getGcsUrl(publicBucketPath),
gif: (width = 360, quality = 20) =>
bucketConfig.getCdnUrl(publicBucketPath + "-thumb.gif", width, quality),
thumb: (width = 600, quality = 100) =>
bucketConfig.getCdnUrl(publicBucketPath + "-thumb.jpg", width, quality),
};
useEffect(() => {
// this condidition handles all media file fetches
@@ -96,6 +112,7 @@ export default function useMedia({
reload,
status,
statusSet,
urls,
writeStatus,
};
}
@@ -1 +0,0 @@
export { default } from './useMedia';