Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweak job list states, selectable refresh interval #7

Merged
merged 3 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/src/components/JobList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ const JobList = (props: JobListProps) => {
params={{}}
>
<span className="">{item.name}</span>
<span className="col-span-2 ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-white px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200 dark:bg-gray-900 dark:text-white dark:ring-gray-700">
<span className="col-span-4 ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-white px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200 dark:bg-gray-900 dark:text-white dark:ring-gray-700">
{item.count.toString()}
</span>
</DropdownItem>
Expand Down
127 changes: 110 additions & 17 deletions ui/src/components/RefreshPauser.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,117 @@
import { PauseIcon } from "@heroicons/react/24/outline";
import { useEffect, useState } from "react";
import {
Label,
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions,
} from "@headlessui/react";
import clsx from "clsx";

import { useRefreshSetting } from "@contexts/RefreshSettings.hook";
import { ArrowPathIcon, PauseIcon } from "@heroicons/react/24/outline";
import { ArrowPathIcon } from "@heroicons/react/20/solid";

type RefreshIntervalSetting = {
name: string;
value: number;
};

const refreshIntervals: RefreshIntervalSetting[] = [
{ name: "Pause", value: 0 },
{ name: "2s", value: 2000 },
{ name: "5s", value: 5000 },
{ name: "10s", value: 10000 },
{ name: "30s", value: 30000 },
{ name: "60s", value: 60000 },
];

export function RefreshPauser(
props: React.ComponentPropsWithoutRef<
typeof Listbox<"div", RefreshIntervalSetting>
>
) {
const { intervalMs, setIntervalMs } = useRefreshSetting();
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return <div className="size-6" />;
}

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 (
<button
type="button"
className="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500"
onClick={() => setDisabled(!disabled)}
title={disabled ? "Resume live updates" : "Pause live updates"}
<Listbox
as="div"
value={selectedInterval}
by="value"
onChange={(newInterval) => setIntervalMs(newInterval.value)}
{...props}
>
<span className="sr-only">
{disabled ? "Resume live updates" : "Pause live updates"}
</span>
{disabled ? (
<ArrowPathIcon className="size-6" aria-hidden="true" />
) : (
<PauseIcon className="size-6" aria-hidden="true" />
)}
</button>
<Label className="sr-only">
{" "}
{disabled ? "Resume live updates" : "Pause live updates"}{" "}
</Label>
<ListboxButton
className="relative z-10 flex size-7 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-slate-100"
aria-label="Theme"
// type="button"
// className="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500"
// onClick={() => setDisabled(!disabled)}
// title={disabled ? "Resume live updates" : "Pause live updates"}
>
<span className="sr-only">
{disabled ? "Resume live updates" : "Pause live updates"}
</span>
{disabled ? (
<PauseIcon className="size-6 text-slate-400" aria-hidden="true" />
) : (
<ArrowPathIcon
className="size-6 text-slate-400 motion-safe:animate-spin-50-50"
aria-hidden="true"
/>
)}
</ListboxButton>
<ListboxOptions className="absolute right-0 top-full mt-3 w-32 space-y-1 rounded-xl bg-white p-3 text-sm font-medium shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
<div className="px-2 text-xs font-semibold leading-6 text-slate-500">
Live Updates
</div>
{refreshIntervals.map((intervalSetting) => (
<ListboxOption
key={intervalSetting.value}
value={intervalSetting}
className={({ focus, selected }) =>
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 }) => (
<div
className={clsx({
"text-blue-600 dark:text-blue-400": selected,
"text-slate-600 dark:text-slate-300": !selected,
})}
>
{intervalSetting.name}
</div>
)}
</ListboxOption>
))}
</ListboxOptions>
</Listbox>
);
}
36 changes: 21 additions & 15 deletions ui/src/components/ThemeSelector.tsx
Original file line number Diff line number Diff line change
@@ -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 = [
Expand Down Expand Up @@ -61,8 +67,8 @@ export function ThemeSelector(

return (
<Listbox as="div" value={theme} onChange={setTheme} {...props}>
<Listbox.Label className="sr-only">Theme</Listbox.Label>
<Listbox.Button
<Label className="sr-only">Theme</Label>
<ListboxButton
className="relative z-10 flex size-7 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-slate-100"
aria-label="Theme"
>
Expand All @@ -78,20 +84,20 @@ export function ThemeSelector(
theme === "system" ? "fill-slate-400" : "fill-sky-400"
)}
/>
</Listbox.Button>
<Listbox.Options className="absolute right-0 top-full mt-3 w-36 space-y-1 rounded-xl bg-white p-3 text-sm font-medium shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
</ListboxButton>
<ListboxOptions className="absolute right-0 top-full mt-3 w-36 space-y-1 rounded-xl bg-white p-3 text-sm font-medium shadow-md shadow-black/5 ring-1 ring-black/5 dark:bg-slate-800 dark:ring-white/5">
{themes.map((theme) => (
<Listbox.Option
<ListboxOption
key={theme.value}
value={theme.value}
className={({ active, selected }) =>
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,
}
)
}
Expand All @@ -103,17 +109,17 @@ 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"
)}
/>
</div>
<div className="ml-3">{theme.name}</div>
</>
)}
</Listbox.Option>
</ListboxOption>
))}
</Listbox.Options>
</ListboxOptions>
</Listbox>
);
}
2 changes: 1 addition & 1 deletion ui/src/components/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
<RefreshPauser />
<RefreshPauser className="relative z-10" />
<ThemeSelector className="relative z-10" />
</div>
</div>
Expand Down
2 changes: 0 additions & 2 deletions ui/src/contexts/RefreshSettings.hook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import {
} from "./RefreshSettings";

const defaultContext: UseRefreshSettingProps = {
disabled: false,
intervalMs: 2000,
setDisabled: () => {},
setIntervalMs: () => {},
};

Expand Down
10 changes: 2 additions & 8 deletions ui/src/contexts/RefreshSettings.tsx
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<boolean>>;
/** Update the interval setting */
/** Update the interval setting. Set to 0 to disable refresh. */
setIntervalMs: React.Dispatch<React.SetStateAction<number>>;
}

Expand All @@ -31,17 +28,14 @@ export const RefreshSettingProvider: React.FC<RefreshSettingProviderProps> = (
const RefreshSetting: React.FC<RefreshSettingProviderProps> = ({
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 (
Expand Down
4 changes: 1 addition & 3 deletions ui/src/routes/jobs/$jobId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 1 addition & 3 deletions ui/src/routes/jobs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
4 changes: 1 addition & 3 deletions ui/src/routes/queues/$name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions ui/src/routes/queues/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
4 changes: 1 addition & 3 deletions ui/src/routes/workflows/$workflowId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
18 changes: 18 additions & 0 deletions ui/tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
Loading