import clsx from 'clsx'; import { forwardRef, ForwardRefExoticComponent, Fragment, HTMLAttributes, ReactNode, useEffect, useState } from 'react'; import { TextItems } from '@sd/client'; import { Tooltip, tw } from '@sd/ui'; import classes from './Job.module.scss'; interface JobContainerProps extends HTMLAttributes { name: string; icon?: string | ForwardRefExoticComponent; textItems?: TextItems; isChild?: boolean; children?: ReactNode; eta?: number; status?: string; } const CIRCLE_ICON_CLASS = 'relative flex-shrink-0 top-1 z-20 mr-3 h-7 w-7 rounded-full bg-app-button p-[5.5px]'; const IMG_ICON_CLASS = 'relative left-[-2px] top-1 z-10 mr-2 h-8 w-8'; const MetaContainer = tw.div`flex w-full overflow-hidden flex-col`; const TextLine = tw.div`mt-[2px] gap-1 text-ink-faint truncate mr-8 pl-1.5`; const TextItem = tw.span`truncate`; const formatETA = (eta: number): string => { const seconds = Math.floor((eta / 1000) % 60); const minutes = Math.floor((eta / (1000 * 60)) % 60); const hours = Math.floor((eta / (1000 * 60 * 60)) % 24); const days = Math.floor(eta / (1000 * 60 * 60 * 24)); let formattedETA = ''; if (days > 0) formattedETA += `${days} day${days > 1 ? 's' : ''} `; if (hours > 0) formattedETA += `${hours} hour${hours > 1 ? 's' : ''} `; if (minutes > 0) formattedETA += `${minutes} minute${minutes > 1 ? 's' : ''} `; if (seconds > 0 || formattedETA === '') formattedETA += `${seconds} second${seconds != 1 ? 's' : ''} `; return formattedETA.trim() + ' remaining'; }; // Job container consolidates the common layout of a job item, used for regular jobs (Job.tsx) and grouped jobs (JobGroup.tsx). const JobContainer = forwardRef((props, ref) => { const { name, icon: Icon, textItems, isChild, children, className, eta, status, ...restProps } = props; const [currentETA, setCurrentETA] = useState(eta); useEffect(() => { if (currentETA !== undefined && currentETA > 0) { const interval = setInterval(() => { setCurrentETA((prevETA) => { if (prevETA === undefined || prevETA <= 1000) return 0; return prevETA - 1000; }); }, 1000); return () => clearInterval(interval); } }, [currentETA]); useEffect(() => { setCurrentETA(eta); }, [eta]); return (
  • {typeof Icon === 'string' ? ( ) : ( Icon && ( ) )}

    {name}

    {textItems?.map((item, index) => { const filteredItems = item.filter((i) => i?.text); const popoverText = filteredItems.map((i) => i?.text).join(' • '); return ( {filteredItems.map((textItem, index) => { const Icon = textItem?.icon; return ( {Icon && ( )} {textItem?.text} {index < filteredItems.length - 1 && ( )} ); })} {status == 'Running' && (
    {currentETA !== undefined ? formatETA(currentETA) : 'Unable to calculate estimated completion time'}
    )}
    ); })}
    {children}
  • ); }); export default JobContainer;