From 9d5873c83fc410934f5a19cdd5159bb10949bee7 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Sun, 19 May 2024 12:51:39 -0500 Subject: [PATCH 1/3] fix headlessui deprecations --- ui/src/components/ThemeSelector.tsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ui/src/components/ThemeSelector.tsx b/ui/src/components/ThemeSelector.tsx index e50620d..67de71a 100644 --- a/ui/src/components/ThemeSelector.tsx +++ b/ui/src/components/ThemeSelector.tsx @@ -1,6 +1,12 @@ import { useEffect, useState } from "react"; import { useTheme } from "next-themes"; -import { Listbox } from "@headlessui/react"; +import { + Label, + Listbox, + ListboxButton, + ListboxOption, + ListboxOptions, +} from "@headlessui/react"; import clsx from "clsx"; const themes = [ @@ -61,8 +67,8 @@ export function ThemeSelector( return ( - Theme - Theme + @@ -78,10 +84,10 @@ export function ThemeSelector( theme === "system" ? "fill-slate-400" : "fill-sky-400" )} /> - - + + {themes.map((theme) => ( - @@ -111,9 +117,9 @@ export function ThemeSelector(
{theme.name}
)} -
+ ))} -
+
); } From 402aa721422c73a76a868c7ace7b90cbafa38852 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Sun, 19 May 2024 13:00:32 -0500 Subject: [PATCH 2/3] correct grid layout in dropdown job state list --- ui/src/components/JobList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/components/JobList.tsx b/ui/src/components/JobList.tsx index 4a3133e..e826374 100644 --- a/ui/src/components/JobList.tsx +++ b/ui/src/components/JobList.tsx @@ -223,7 +223,7 @@ const JobList = (props: JobListProps) => { aria-label="Account options" > - + {stateFormatted} { params={{}} > {item.name} - + {item.count.toString()} From 84ee9f5c795b60628f834c8eedff91e6f316e3e6 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Mon, 20 May 2024 09:40:30 -0500 Subject: [PATCH 3/3] customizable refresh interval --- ui/src/components/JobList.tsx | 2 +- ui/src/components/RefreshPauser.tsx | 127 ++++++++++++++++++++--- ui/src/components/ThemeSelector.tsx | 14 +-- ui/src/components/TopNav.tsx | 2 +- ui/src/contexts/RefreshSettings.hook.tsx | 2 - ui/src/contexts/RefreshSettings.tsx | 10 +- ui/src/routes/jobs/$jobId.tsx | 4 +- ui/src/routes/jobs/index.tsx | 4 +- ui/src/routes/queues/$name.tsx | 4 +- ui/src/routes/queues/index.tsx | 4 +- ui/src/routes/workflows/$workflowId.tsx | 4 +- ui/tailwind.config.ts | 18 ++++ 12 files changed, 144 insertions(+), 51 deletions(-) diff --git a/ui/src/components/JobList.tsx b/ui/src/components/JobList.tsx index e826374..928799e 100644 --- a/ui/src/components/JobList.tsx +++ b/ui/src/components/JobList.tsx @@ -223,7 +223,7 @@ const JobList = (props: JobListProps) => { aria-label="Account options" > - + {stateFormatted} + > +) { + const { intervalMs, setIntervalMs } = useRefreshSetting(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return
; + } -export function RefreshPauser(_props: React.ComponentPropsWithoutRef<"div">) { - const { disabled, setDisabled } = useRefreshSetting(); + const disabled = intervalMs === 0; + const selectedInterval = + refreshIntervals.find((i) => i.value === intervalMs) ?? + ({ name: "Custom", value: intervalMs } as RefreshIntervalSetting); return ( - + + setDisabled(!disabled)} + // title={disabled ? "Resume live updates" : "Pause live updates"} + > + + {disabled ? "Resume live updates" : "Pause live updates"} + + {disabled ? ( + + +
+ Live Updates +
+ {refreshIntervals.map((intervalSetting) => ( + + clsx( + "flex cursor-pointer select-none rounded-[0.625rem] px-2 py-1", + { + "text-blue-600 dark:text-blue-400": selected, + "text-slate-900 dark:text-white": focus && !selected, + "text-slate-700 dark:text-slate-300": !focus && !selected, + "bg-slate-100 dark:bg-slate-700": focus, + } + ) + } + > + {({ selected }) => ( +
+ {intervalSetting.name} +
+ )} +
+ ))} +
+ ); } diff --git a/ui/src/components/ThemeSelector.tsx b/ui/src/components/ThemeSelector.tsx index 67de71a..a7fff2f 100644 --- a/ui/src/components/ThemeSelector.tsx +++ b/ui/src/components/ThemeSelector.tsx @@ -90,14 +90,14 @@ export function ThemeSelector( + className={({ focus, selected }) => clsx( "flex cursor-pointer select-none items-center rounded-[0.625rem] p-1", { - "text-brand-primary dark:text-blue-400": selected, - "text-slate-900 dark:text-white": active && !selected, - "text-slate-700 dark:text-slate-300": !active && !selected, - "bg-slate-100 dark:bg-slate-900/40": active, + "text-blue-600 dark:text-blue-400": selected, + "text-slate-900 dark:text-white": focus && !selected, + "text-slate-700 dark:text-slate-300": !focus && !selected, + "bg-slate-100 dark:bg-slate-700": focus, } ) } @@ -109,8 +109,8 @@ export function ThemeSelector( className={clsx( "size-4", selected - ? "fill-sky-400 dark:fill-sky-400" - : "fill-slate-400" + ? "fill-blue-600 dark:fill-blue-400" + : "fill-slate-600 dark:fill-slate-400" )} />
diff --git a/ui/src/components/TopNav.tsx b/ui/src/components/TopNav.tsx index b2b1f78..1a9ecba 100644 --- a/ui/src/components/TopNav.tsx +++ b/ui/src/components/TopNav.tsx @@ -37,7 +37,7 @@ const TopNav = ({ children }: TopNavProps) => { className="hidden lg:block lg:h-6 lg:w-px lg:bg-slate-200 dark:lg:bg-slate-700" aria-hidden="true" /> - + diff --git a/ui/src/contexts/RefreshSettings.hook.tsx b/ui/src/contexts/RefreshSettings.hook.tsx index f30b4e7..77a7959 100644 --- a/ui/src/contexts/RefreshSettings.hook.tsx +++ b/ui/src/contexts/RefreshSettings.hook.tsx @@ -5,9 +5,7 @@ import { } from "./RefreshSettings"; const defaultContext: UseRefreshSettingProps = { - disabled: false, intervalMs: 2000, - setDisabled: () => {}, setIntervalMs: () => {}, }; diff --git a/ui/src/contexts/RefreshSettings.tsx b/ui/src/contexts/RefreshSettings.tsx index a7b09f6..facd33e 100644 --- a/ui/src/contexts/RefreshSettings.tsx +++ b/ui/src/contexts/RefreshSettings.tsx @@ -1,12 +1,9 @@ import { Fragment, createContext, useContext, useMemo, useState } from "react"; export interface UseRefreshSettingProps { - disabled: boolean; /** List of all available theme names */ intervalMs: number; - /** Update the disabled setting */ - setDisabled: React.Dispatch>; - /** Update the interval setting */ + /** Update the interval setting. Set to 0 to disable refresh. */ setIntervalMs: React.Dispatch>; } @@ -31,17 +28,14 @@ export const RefreshSettingProvider: React.FC = ( const RefreshSetting: React.FC = ({ children, }) => { - const [disabled, setDisabled] = useState(false); const [intervalMs, setIntervalMs] = useState(2000); const providerValue = useMemo( () => ({ - disabled, intervalMs, - setDisabled, setIntervalMs, }), - [disabled, intervalMs, setDisabled, setIntervalMs] + [intervalMs, setIntervalMs] ); return ( diff --git a/ui/src/routes/jobs/$jobId.tsx b/ui/src/routes/jobs/$jobId.tsx index 08e87ae..bbd14d4 100644 --- a/ui/src/routes/jobs/$jobId.tsx +++ b/ui/src/routes/jobs/$jobId.tsx @@ -39,9 +39,7 @@ function JobComponent() { const { jobId } = Route.useParams(); const { queryOptions } = Route.useRouteContext(); const refreshSettings = useRefreshSetting(); - queryOptions.refetchInterval = !refreshSettings.disabled - ? refreshSettings.intervalMs - : 0; + queryOptions.refetchInterval = refreshSettings.intervalMs; const queryClient = useQueryClient(); const jobQuery = useQuery(queryOptions); diff --git a/ui/src/routes/jobs/index.tsx b/ui/src/routes/jobs/index.tsx index 9f3dfa6..0ed2cb8 100644 --- a/ui/src/routes/jobs/index.tsx +++ b/ui/src/routes/jobs/index.tsx @@ -50,9 +50,7 @@ function JobsIndexComponent() { const navigate = Route.useNavigate(); const { limit } = Route.useLoaderDeps(); const refreshSettings = useRefreshSetting(); - const refetchInterval = !refreshSettings.disabled - ? refreshSettings.intervalMs - : 0; + const refetchInterval = refreshSettings.intervalMs; const jobsQuery = useSuspenseQuery( jobsQueryOptions(Route.useLoaderDeps(), { refetchInterval }) diff --git a/ui/src/routes/queues/$name.tsx b/ui/src/routes/queues/$name.tsx index 3a8e5c2..89489d6 100644 --- a/ui/src/routes/queues/$name.tsx +++ b/ui/src/routes/queues/$name.tsx @@ -40,9 +40,7 @@ function QueueComponent() { const { name } = Route.useParams(); const { queryOptions } = Route.useRouteContext(); const refreshSettings = useRefreshSetting(); - queryOptions.refetchInterval = !refreshSettings.disabled - ? refreshSettings.intervalMs - : 0; + queryOptions.refetchInterval = refreshSettings.intervalMs; const queueQuery = useQuery(queryOptions); const { data: queue } = queueQuery; diff --git a/ui/src/routes/queues/index.tsx b/ui/src/routes/queues/index.tsx index 54b3678..881e2a2 100644 --- a/ui/src/routes/queues/index.tsx +++ b/ui/src/routes/queues/index.tsx @@ -31,9 +31,7 @@ export const Route = createFileRoute("/queues/")({ function QueuesIndexComponent() { const { queryOptions } = Route.useRouteContext(); const refreshSettings = useRefreshSetting(); - queryOptions.refetchInterval = !refreshSettings.disabled - ? refreshSettings.intervalMs - : 0; + queryOptions.refetchInterval = refreshSettings.intervalMs; const queryClient = useQueryClient(); diff --git a/ui/src/routes/workflows/$workflowId.tsx b/ui/src/routes/workflows/$workflowId.tsx index a3f7f1e..29df283 100644 --- a/ui/src/routes/workflows/$workflowId.tsx +++ b/ui/src/routes/workflows/$workflowId.tsx @@ -38,9 +38,7 @@ export const Route = createFileRoute("/workflows/$workflowId")({ function WorkflowComponent() { const { queryOptions } = Route.useRouteContext(); const refreshSettings = useRefreshSetting(); - queryOptions.refetchInterval = !refreshSettings.disabled - ? refreshSettings.intervalMs - : 0; + queryOptions.refetchInterval = refreshSettings.intervalMs; const workflowQuery = useQuery(queryOptions); diff --git a/ui/tailwind.config.ts b/ui/tailwind.config.ts index f83ca5b..7377b52 100644 --- a/ui/tailwind.config.ts +++ b/ui/tailwind.config.ts @@ -8,12 +8,30 @@ export default { darkMode: "class", theme: { extend: { + animation: { + "spin-slow": "spin 3s linear infinite", + "spin-50-50": "pausingSpin 6s infinite", + }, colors: { "brand-primary": "rgb(37, 99, 235)", }, fontFamily: { sans: ["Inter var", ...defaultTheme.fontFamily.sans], }, + keyframes: { + pausingSpin: { + "0%": { + transform: "rotate(0)", + animationTimingFunction: "ease-in-out", + }, + "42%,50%": { transform: "rotate(180deg)" }, + "50%": { + transform: "rotate(180deg)", + animationTimingFunction: "ease-in-out", + }, + "92%,100%": { transform: "rotate(360deg)" }, + }, + }, }, }, plugins: [formsPlugin, typographyPlugin, headlessuiPlugin],