mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b6890fa12 |
@@ -28,7 +28,7 @@ export const Button = ({
|
||||
function getVariantClassNames(variant: string) {
|
||||
switch (variant) {
|
||||
case "secondary": {
|
||||
return "bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
|
||||
return "bg-brand-secondary hover:bg-brand-secondary-700 text-white focus-visible:outline-brand-secondary";
|
||||
}
|
||||
case "action": {
|
||||
return "bg-action hover:bg-action-900 text-white focus-visible:outline-action";
|
||||
|
||||
@@ -25,6 +25,8 @@ interface PopoverMenuProps {
|
||||
panelClassName?: string;
|
||||
onAction?: (action: string) => void;
|
||||
panelTitle?: string;
|
||||
position?: "top" | "bottom";
|
||||
align?: "start" | "center" | "end";
|
||||
}
|
||||
|
||||
export const PopoverMenu = ({
|
||||
@@ -38,8 +40,11 @@ export const PopoverMenu = ({
|
||||
triggerLabel,
|
||||
children,
|
||||
buttonClassName,
|
||||
panelClassName,
|
||||
onAction,
|
||||
panelTitle,
|
||||
position = "top",
|
||||
align = "start",
|
||||
}: PopoverMenuProps) => {
|
||||
const selectedItem = items.find((item) => item.selected);
|
||||
|
||||
@@ -61,6 +66,17 @@ export const PopoverMenu = ({
|
||||
buttonClassName,
|
||||
);
|
||||
|
||||
const positionClasses = {
|
||||
top: "bottom-full",
|
||||
bottom: "top-full",
|
||||
};
|
||||
|
||||
const alignClasses = {
|
||||
start: "left-0",
|
||||
center: "left-1/2 -translate-x-1/2",
|
||||
end: "right-0",
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
{({ close }) => (
|
||||
@@ -71,19 +87,40 @@ export const PopoverMenu = ({
|
||||
{mode === "default" && triggerLabel ? triggerLabel : null}
|
||||
</Popover.Button>
|
||||
|
||||
<div className="absolute bottom-full left-0 transform-gpu">
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute transform-gpu",
|
||||
positionClasses[position],
|
||||
alignClasses[align],
|
||||
)}
|
||||
>
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="translate-y-2 opacity-0"
|
||||
enterFrom={
|
||||
position === "bottom"
|
||||
? "translate-y-2 opacity-0"
|
||||
: "-translate-y-2 opacity-0"
|
||||
}
|
||||
enterTo="translate-y-0 opacity-100"
|
||||
leave="transition duration-100 ease-in"
|
||||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo="translate-y-2 opacity-0"
|
||||
leaveTo={
|
||||
position === "bottom"
|
||||
? "translate-y-2 opacity-0"
|
||||
: "-translate-y-2 opacity-0"
|
||||
}
|
||||
>
|
||||
<Popover.Panel className="origin-bottom">
|
||||
<Popover.Panel
|
||||
className={twMerge(
|
||||
position === "bottom" ? "origin-top" : "origin-bottom",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-10 mb-2 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
|
||||
"z-10 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
|
||||
position === "top" && "mb-2",
|
||||
position === "bottom" && "mt-2",
|
||||
panelClassName,
|
||||
)}
|
||||
>
|
||||
{panelTitle && (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useLocation, useParams } from "@remix-run/react";
|
||||
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ButtonLink } from "~/components";
|
||||
import { faChevronLeft, faImages } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, ButtonLink } from "~/components";
|
||||
import { AuthButtons } from "./AuthButtons";
|
||||
import { SceneTitleInput } from "./SceneTitleInput";
|
||||
import { MyMoviesButton } from "./MyMoviesButton";
|
||||
import { getCurrentLocationWithoutParams } from "~/utilities";
|
||||
import { Activity } from "~/pages/PageEnigma/comps/GenerateModals/Activity";
|
||||
|
||||
function isEditorPath(path: string) {
|
||||
if (path === "/") return true;
|
||||
@@ -47,8 +47,13 @@ export const TopBar = ({ pageName }: Props) => {
|
||||
<SceneTitleInput pageName={pageName} />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2.5">
|
||||
<MyMoviesButton />
|
||||
<div className="flex justify-end gap-5">
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" icon={faImages} disabled>
|
||||
My Library
|
||||
</Button>
|
||||
<Activity />
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<AuthButtons />
|
||||
</div>
|
||||
|
||||
@@ -13,4 +13,5 @@ export enum JobType {
|
||||
TextToSpeech = "text_to_speech",
|
||||
VoiceConversion = "voice_conversion",
|
||||
VideoStyleTransfer = "workflow",
|
||||
ImageGeneration = "image_generation",
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { faBell, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { PopoverMenu } from "~/components/reusable/Popover/Popover";
|
||||
import { CompletedCard } from "./CompletedCard";
|
||||
import { InProgressCard } from "./InProgressCard";
|
||||
import { H3, P, Tooltip } from "~/components";
|
||||
import { activeImageGenerationJobs, userMovies } from "~/signals";
|
||||
|
||||
export function Activity() {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<Tooltip content="Activity" position="bottom">
|
||||
<PopoverMenu
|
||||
mode="default"
|
||||
buttonClassName="h-[38px] w-[38px] !p-0"
|
||||
panelClassName="w-[360px] p-2"
|
||||
position="bottom"
|
||||
align="end"
|
||||
triggerIcon={
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faBell} />
|
||||
{activeImageGenerationJobs.value &&
|
||||
activeImageGenerationJobs.value.length > 0 && (
|
||||
<div className="bg-blue-500 absolute -right-1 -top-1 h-3 w-3 animate-pulse rounded-full" />
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="max-h-[480px] overflow-y-auto">
|
||||
{/* In progress */}
|
||||
{activeImageGenerationJobs.value &&
|
||||
activeImageGenerationJobs.value.length > 0 && (
|
||||
<div>
|
||||
{activeImageGenerationJobs.value.map((movieJob) => (
|
||||
<InProgressCard key={movieJob.job_token} movie={movieJob} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!userMovies.value && !activeImageGenerationJobs.value ? (
|
||||
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
spin
|
||||
size="2x"
|
||||
className="text-gray-400"
|
||||
/>
|
||||
<H3 className="text-gray-300">Retrieving Activities</H3>
|
||||
</div>
|
||||
) : userMovies.value &&
|
||||
userMovies.value.length === 0 &&
|
||||
!activeImageGenerationJobs.value ? (
|
||||
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
|
||||
<H3 className="text-gray-300">No activities yet</H3>
|
||||
<P className="text-gray-400">
|
||||
Try generating something new from the featured scenes
|
||||
</P>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{/* Completed */}
|
||||
<div className="flex flex-col">
|
||||
{userMovies.value?.map((movie) => (
|
||||
<CompletedCard key={movie.token} movie={movie} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverMenu>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export function getStyleName(typeInput: string) {
|
||||
|
||||
interface Props {
|
||||
movie: MediaInfo;
|
||||
setMovieId: (id: string) => void;
|
||||
setMovieId?: (id: string) => void;
|
||||
}
|
||||
|
||||
export function CompletedCard({ movie, setMovieId }: Props) {
|
||||
@@ -37,20 +37,23 @@ export function CompletedCard({ movie, setMovieId }: Props) {
|
||||
);
|
||||
const styleName =
|
||||
movie.maybe_style_name && getStyleName(movie.maybe_style_name);
|
||||
|
||||
const handleClick = () => {
|
||||
setMovieId?.(movie.token);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex w-full items-center justify-between px-5 py-3 text-start transition-all duration-150 hover:cursor-pointer hover:bg-brand-secondary/40"
|
||||
onClick={() => {
|
||||
setMovieId(movie.token);
|
||||
}}
|
||||
className="flex w-full items-center justify-between rounded-lg p-2 text-start transition-all duration-150 hover:cursor-pointer hover:bg-white/10"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex h-32 w-32 justify-center overflow-hidden rounded-lg bg-black/60">
|
||||
<div className="flex aspect-square h-14 w-14 justify-center overflow-hidden rounded-lg border border-[#A9A9A9]/50 bg-black/60">
|
||||
<img
|
||||
src={
|
||||
loadError ? "/resources/images/movie-placeholder.png" : imageUrl
|
||||
}
|
||||
className="h-full object-contain"
|
||||
className="h-full w-full object-cover"
|
||||
alt={movie.maybe_title ?? "unknown"}
|
||||
crossOrigin="anonymous"
|
||||
onError={() => setLoadError(true)}
|
||||
@@ -60,16 +63,16 @@ export function CompletedCard({ movie, setMovieId }: Props) {
|
||||
<div className="flex flex-col justify-center gap-1">
|
||||
<div className="font-medium">{movie.maybe_title || "Untitled"}</div>
|
||||
<div>
|
||||
{styleName && (
|
||||
{/* {styleName && (
|
||||
<div className="text-sm text-white/60">{styleName}</div>
|
||||
)}
|
||||
)} */}
|
||||
<div className="text-sm text-white/60">
|
||||
{dayjs(movie.updated_at).format("MMM D, YYYY HH:mm:ss")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pr-5">
|
||||
{/* <div className="pr-5">
|
||||
<button
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
@@ -83,7 +86,7 @@ export function CompletedCard({ movie, setMovieId }: Props) {
|
||||
<FontAwesomeIcon icon={faArrowDownToLine} className="mr-2" />
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,19 +58,19 @@ export function InProgressCard({ movie }: Props) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between px-5 py-3 text-start transition-all duration-150">
|
||||
<div className="flex w-full items-center justify-between rounded-lg p-2 text-start transition-all duration-150 hover:bg-white/10">
|
||||
<div className="flex gap-4">
|
||||
<div className="flex h-32 w-32 items-center justify-center rounded-lg bg-white/10">
|
||||
<div className="flex aspect-square h-14 w-14 items-center justify-center overflow-hidden rounded-lg border border-[#A9A9A9]/50 bg-black/60">
|
||||
{(movie.status.status === JobState.STARTED ||
|
||||
movie.status.status === JobState.PENDING) && (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} spin size={"2x"} />
|
||||
<FontAwesomeIcon icon={faSpinnerThird} spin size={"lg"} />
|
||||
)}
|
||||
{(movie.status.status === JobState.COMPLETE_FAILURE ||
|
||||
movie.status.status === JobState.ATTEMPT_FAILED) && (
|
||||
<FontAwesomeIcon icon={faCircleExclamation} size={"2x"} />
|
||||
<FontAwesomeIcon icon={faCircleExclamation} size={"lg"} />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-center gap-2">
|
||||
<div className="flex flex-col justify-center gap-1">
|
||||
<div className="font-medium">
|
||||
{movie.request.maybe_model_title || "Untitled"}
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ export function InProgressCard({ movie }: Props) {
|
||||
<Tooltip content="Cancel" position="top">
|
||||
<button
|
||||
onClick={() => deleteJob(movie)}
|
||||
className="text-xl text-white/50 transition-all duration-150 hover:text-white/90"
|
||||
className="text-[15px] font-medium text-white/50 transition-all duration-150 hover:text-white/100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faClose} />
|
||||
</button>
|
||||
|
||||
@@ -27,7 +27,7 @@ export const SidePanel = () => {
|
||||
<>
|
||||
<div
|
||||
className={[
|
||||
"fixed z-30 flex border-l border-[#363636] bg-ui-panel transition-all duration-100",
|
||||
"fixed flex border-l border-[#363636] bg-ui-panel transition-all duration-100",
|
||||
].join(" ")}
|
||||
style={{
|
||||
top: 64,
|
||||
|
||||
@@ -30,7 +30,7 @@ export const SidePanelMenu = ({
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"fixed z-30 bg-assets-background",
|
||||
"fixed bg-assets-background",
|
||||
"right-0 top-[64px] w-[84px] overflow-auto border-l px-2 py-2",
|
||||
sidePanelVisible.value ? "border-transparent" : "border-[#363636]",
|
||||
)}
|
||||
@@ -49,9 +49,9 @@ export const SidePanelMenu = ({
|
||||
? "border-[#363636] bg-brand-secondary-900/60 opacity-100 hover:bg-brand-secondary-900/60"
|
||||
: "opacity-60",
|
||||
tab.title === TabTitles.STYLIZE &&
|
||||
"bg-brand-primary font-medium opacity-90 hover:border-white/25 hover:bg-brand-primary hover:opacity-100",
|
||||
"bg-brand-primary font-medium opacity-90 hover:border-white/25 hover:bg-brand-primary hover:opacity-100",
|
||||
tab.title === selectedTab.title &&
|
||||
tab.title === TabTitles.STYLIZE
|
||||
tab.title === TabTitles.STYLIZE
|
||||
? "border-white/50 opacity-100 hover:border-white/50"
|
||||
: "",
|
||||
])}
|
||||
|
||||
@@ -33,6 +33,15 @@ export const activeWorkflowJobs = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
export const activeImageGenerationJobs = computed(() => {
|
||||
if (!activeJobs.value) {
|
||||
return undefined;
|
||||
}
|
||||
return activeJobs.value.filter((job) => {
|
||||
return job.request.inference_category === JobType.ImageGeneration;
|
||||
});
|
||||
});
|
||||
|
||||
export const activeAudioJobs = computed(() => {
|
||||
if (!activeJobs.value) {
|
||||
return undefined;
|
||||
|
||||
Reference in New Issue
Block a user