mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
feat: Add cancel button to active job card in grid view (#8264)
## Summary Add a cancel (x) button overlay to active job cards in grid view, matching the existing list view behavior. ## Changes - **What**: Added hover-triggered cancel button to `ActiveJobCard.vue` using the existing `useJobActions` composable - Button appears on hover for jobs in cancellable states (pending, initialization, running) <img width="1710" height="1107" alt="스크린샷 2026-01-23 오후 5 11 04" src="https://github.com/user-attachments/assets/31a6b6d1-46e7-49c4-a253-92260cb28514" /> <img width="1710" height="1107" alt="스크린샷 2026-01-23 오후 5 10 59" src="https://github.com/user-attachments/assets/b559a81e-8e62-4858-b8e7-92de9caa2919" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8264-feat-Add-cancel-button-to-active-job-card-in-grid-view-2f16d73d3650817d83f0dd3b955759cb) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { computed } from 'vue'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
@@ -6,6 +7,18 @@ import ActiveJobCard from './ActiveMediaAssetCard.vue'
|
|||||||
|
|
||||||
import type { JobListItem } from '@/composables/queue/useJobList'
|
import type { JobListItem } from '@/composables/queue/useJobList'
|
||||||
|
|
||||||
|
vi.mock('@/composables/queue/useJobActions', () => ({
|
||||||
|
useJobActions: () => ({
|
||||||
|
cancelAction: {
|
||||||
|
icon: 'icon-[lucide--x]',
|
||||||
|
label: 'Cancel',
|
||||||
|
variant: 'destructive'
|
||||||
|
},
|
||||||
|
canCancelJob: computed(() => false),
|
||||||
|
runCancelJob: vi.fn()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
vi.mock('@/composables/useProgressBarBackground', () => ({
|
vi.mock('@/composables/useProgressBarBackground', () => ({
|
||||||
useProgressBarBackground: () => ({
|
useProgressBarBackground: () => ({
|
||||||
progressBarPrimaryClass: 'bg-blue-500',
|
progressBarPrimaryClass: 'bg-blue-500',
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
|
tabindex="0"
|
||||||
:aria-label="t('sideToolbar.activeJobStatus', { status: statusText })"
|
:aria-label="t('sideToolbar.activeJobStatus', { status: statusText })"
|
||||||
class="flex flex-col gap-2 p-2 rounded-lg"
|
class="flex flex-col gap-2 p-2 rounded-lg"
|
||||||
|
@mouseenter="hovered = true"
|
||||||
|
@mouseleave="hovered = false"
|
||||||
|
@focusin="hovered = true"
|
||||||
|
@focusout="hovered = false"
|
||||||
>
|
>
|
||||||
<!-- Thumbnail -->
|
<!-- Thumbnail -->
|
||||||
<div class="relative aspect-square overflow-hidden rounded-lg">
|
<div class="relative aspect-square overflow-hidden rounded-lg">
|
||||||
@@ -34,6 +39,17 @@
|
|||||||
class="icon-[lucide--loader-circle] size-8 animate-spin text-muted-foreground"
|
class="icon-[lucide--loader-circle] size-8 animate-spin text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Cancel button overlay -->
|
||||||
|
<Button
|
||||||
|
v-if="hovered && canCancelJob"
|
||||||
|
variant="destructive"
|
||||||
|
size="icon"
|
||||||
|
:aria-label="cancelAction.label"
|
||||||
|
class="absolute top-2 right-2"
|
||||||
|
@click.stop="runCancelJob()"
|
||||||
|
>
|
||||||
|
<i :class="cancelAction.icon" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer: Progress bar or status text -->
|
<!-- Footer: Progress bar or status text -->
|
||||||
@@ -61,15 +77,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import Button from '@/components/ui/button/Button.vue'
|
||||||
|
import { useJobActions } from '@/composables/queue/useJobActions'
|
||||||
import type { JobListItem } from '@/composables/queue/useJobList'
|
import type { JobListItem } from '@/composables/queue/useJobList'
|
||||||
import { useProgressBarBackground } from '@/composables/useProgressBarBackground'
|
import { useProgressBarBackground } from '@/composables/useProgressBarBackground'
|
||||||
|
|
||||||
const { job } = defineProps<{ job: JobListItem }>()
|
const { job } = defineProps<{ job: JobListItem }>()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const hovered = ref(false)
|
||||||
|
|
||||||
|
const { cancelAction, canCancelJob, runCancelJob } = useJobActions(() => job)
|
||||||
|
|
||||||
const { progressBarPrimaryClass, hasProgressPercent, progressPercentStyle } =
|
const { progressBarPrimaryClass, hasProgressPercent, progressPercentStyle } =
|
||||||
useProgressBarBackground()
|
useProgressBarBackground()
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ function createMockAsset(overrides: Partial<AssetItem> = {}): AssetItem {
|
|||||||
|
|
||||||
describe('useMediaAssetActions', () => {
|
describe('useMediaAssetActions', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
vi.resetModules()
|
||||||
setActivePinia(createPinia())
|
setActivePinia(createPinia())
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
capturedFilenames.values = []
|
capturedFilenames.values = []
|
||||||
|
|||||||
Reference in New Issue
Block a user