From c4b53c14f58946c1f785bc916d2d1c551d0a4346 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Mon, 8 Dec 2025 11:49:47 -0800 Subject: [PATCH] Make job list button visual seperation change --- src/components/queue/job/QueueJobItem.vue | 356 ++++++++++++++-------- src/types/buttonTypes.ts | 20 +- 2 files changed, 238 insertions(+), 138 deletions(-) diff --git a/src/components/queue/job/QueueJobItem.vue b/src/components/queue/job/QueueJobItem.vue index bfe0eeda1c..fc9c8d6d0e 100644 --- a/src/components/queue/job/QueueJobItem.vue +++ b/src/components/queue/job/QueueJobItem.vue @@ -2,8 +2,8 @@
@@ -42,158 +42,119 @@ />
-
+ +
-
-
- -
-
+ v-if=" + props.state === 'running' && + (props.progressTotalPercent !== undefined || + props.progressCurrentPercent !== undefined) + " + class="absolute inset-0" + >
- - +
+ +
+
+
+
+ + +
-
-
-
- {{ props.title }} +
+
+ {{ props.title }} +
+
+ +
+ {{ props.rightText }}
- -
- +
-
+ +
+ +
+ +
@@ -256,6 +217,9 @@ const { t } = useI18n() const cancelTooltipConfig = computed(() => buildTooltipConfig(t('g.cancel'))) const deleteTooltipConfig = computed(() => buildTooltipConfig(t('g.delete'))) const moreTooltipConfig = computed(() => buildTooltipConfig(t('g.more'))) +const viewTooltipConfig = computed(() => + buildTooltipConfig(t('menuLabels.View')) +) const rowRef = ref(null) const showDetails = computed(() => props.activeDetailsId === props.jobId) @@ -324,6 +288,32 @@ const isAnyPopoverVisible = computed( () => showDetails.value || (isPreviewVisible.value && canShowPreview.value) ) +type ActionVariant = 'neutral' | 'destructive' +type ActionMode = 'hover' | 'always' + +type BaseActionConfig = { + key: string + variant: ActionVariant + mode: ActionMode + ariaLabel: string + tooltip?: ReturnType + isVisible: () => boolean + onClick?: (event?: MouseEvent) => void +} + +type IconActionConfig = BaseActionConfig & { + type: 'icon' + iconClass: string + buttonType: 'secondary' | 'destructive' +} + +type TextActionConfig = BaseActionConfig & { + type: 'text' + label: string +} + +type ActionConfig = IconActionConfig | TextActionConfig + watch( isAnyPopoverVisible, (visible) => { @@ -338,6 +328,109 @@ watch( const isHovered = ref(false) +const computedShowClear = computed(() => { + if (props.showClear !== undefined) return props.showClear + return props.state !== 'completed' +}) + +const baseActions = computed(() => { + const showMenu = props.showMenu !== undefined ? props.showMenu : true + + return [ + { + key: 'menu', + type: 'icon', + variant: 'neutral', + buttonType: 'secondary', + mode: 'hover', + iconClass: 'icon-[lucide--more-horizontal]', + ariaLabel: t('g.more'), + tooltip: moreTooltipConfig.value, + isVisible: () => showMenu, + onClick: (event?: MouseEvent) => { + if (event) emit('menu', event) + } + }, + { + key: 'delete', + type: 'icon', + variant: 'destructive', + buttonType: 'destructive', + mode: 'hover', + iconClass: 'icon-[lucide--trash-2]', + ariaLabel: t('g.delete'), + tooltip: deleteTooltipConfig.value, + isVisible: () => props.state === 'failed' && computedShowClear.value, + onClick: () => emit('delete') + }, + { + key: 'cancel-hover', + type: 'icon', + variant: 'destructive', + buttonType: 'destructive', + mode: 'hover', + iconClass: 'icon-[lucide--x]', + ariaLabel: t('g.cancel'), + tooltip: cancelTooltipConfig.value, + isVisible: () => + props.state !== 'completed' && + props.state !== 'running' && + props.state !== 'failed' && + computedShowClear.value, + onClick: () => emit('cancel') + }, + { + key: 'view', + type: 'icon', + variant: 'neutral', + buttonType: 'secondary', + mode: 'hover', + iconClass: 'icon-[lucide--zoom-in]', + ariaLabel: t('menuLabels.View'), + tooltip: viewTooltipConfig.value, + isVisible: () => props.state === 'completed', + onClick: () => emit('view') + }, + { + key: 'cancel-running', + type: 'icon', + variant: 'destructive', + buttonType: 'destructive', + mode: 'always', + iconClass: 'icon-[lucide--x]', + ariaLabel: t('g.cancel'), + tooltip: cancelTooltipConfig.value, + isVisible: () => props.state === 'running' && computedShowClear.value, + onClick: () => emit('cancel') + } + ] +}) + +const hoverActions = computed(() => + baseActions.value.filter( + (action) => action.mode === 'hover' && action.isVisible() + ) +) + +const alwaysActions = computed(() => + baseActions.value.filter( + (action) => action.mode === 'always' && action.isVisible() + ) +) + +const handleMouseEnter = () => { + isHovered.value = true + onRowEnter() +} + +const handleMouseLeave = () => { + isHovered.value = false + onRowLeave() +} + +const getActionButtonClass = () => + 'h-8 min-w-8 gap-1 rounded-lg text-text-primary transition duration-150 ease-in-out hover:opacity-95' + const iconClass = computed(() => { if (props.iconName) return props.iconName return iconForJobState(props.state) @@ -350,11 +443,6 @@ const shouldSpin = computed( !props.iconImageUrl ) -const computedShowClear = computed(() => { - if (props.showClear !== undefined) return props.showClear - return props.state !== 'completed' -}) - const onContextMenu = (event: MouseEvent) => { const shouldShowMenu = props.showMenu !== undefined ? props.showMenu : true if (shouldShowMenu) emit('menu', event) diff --git a/src/types/buttonTypes.ts b/src/types/buttonTypes.ts index 38d09b1f55..5647166951 100644 --- a/src/types/buttonTypes.ts +++ b/src/types/buttonTypes.ts @@ -2,7 +2,12 @@ import { cn } from '@comfyorg/tailwind-utils' import type { HTMLAttributes } from 'vue' export type ButtonSize = 'full-width' | 'fit-content' | 'sm' | 'md' -type ButtonType = 'primary' | 'secondary' | 'transparent' | 'accent' +type ButtonType = + | 'primary' + | 'secondary' + | 'transparent' + | 'accent' + | 'destructive' type ButtonBorder = boolean export interface BaseButtonProps { @@ -33,7 +38,10 @@ export const getButtonTypeClasses = (type: ButtonType = 'primary') => { 'bg-transparent border-none text-muted-foreground hover:bg-secondary-background-hover' ), accent: - 'bg-primary-background hover:bg-primary-background-hover border-none text-white font-bold' + 'bg-primary-background hover:bg-primary-background-hover border-none text-white font-bold', + destructive: cn( + 'bg-destructive-background hover:bg-destructive-background-hover border-none text-base-foreground' + ) } as const return baseByType[type] @@ -47,14 +55,18 @@ export const getBorderButtonTypeClasses = (type: ButtonType = 'primary') => { 'bg-transparent text-base-foreground hover:bg-secondary-background-hover' ), accent: - 'bg-primary-background hover:bg-primary-background-hover text-white font-bold' + 'bg-primary-background hover:bg-primary-background-hover text-white font-bold', + destructive: cn( + 'bg-destructive-background hover:bg-destructive-background-hover text-base-foreground' + ) } as const const borderByType = { primary: 'border border-solid border-base-background', secondary: 'border border-solid border-base-foreground', transparent: 'border border-solid border-base-foreground', - accent: 'border border-solid border-primary-background' + accent: 'border border-solid border-primary-background', + destructive: 'border border-solid border-destructive-background' } as const return `${baseByType[type]} ${borderByType[type]}`