mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
Component: Button Migration 3: IconTextButton (#7603)
## Summary Replace all the `IconTextButton`s with `Button` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7603-WIP-Component-Button-Migraion-3-IconTextButton-2cd6d73d365081b7b742fa2172dc2ba8) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -1,213 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import IconTextButton from './IconTextButton.vue'
|
||||
|
||||
const meta: Meta<typeof IconTextButton> = {
|
||||
title: 'Components/Button/IconTextButton',
|
||||
component: IconTextButton,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
label: {
|
||||
control: 'text'
|
||||
},
|
||||
size: {
|
||||
control: { type: 'select' },
|
||||
options: ['sm', 'md']
|
||||
},
|
||||
type: {
|
||||
control: { type: 'select' },
|
||||
options: ['primary', 'secondary', 'transparent']
|
||||
},
|
||||
border: {
|
||||
control: 'boolean',
|
||||
description: 'Toggle border attribute'
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: 'Toggle disable status'
|
||||
},
|
||||
iconPosition: {
|
||||
control: { type: 'select' },
|
||||
options: ['left', 'right']
|
||||
},
|
||||
onClick: { action: 'clicked' }
|
||||
}
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Primary: Story = {
|
||||
render: (args) => ({
|
||||
components: { IconTextButton },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<IconTextButton v-bind="args">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--package] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
label: 'Deploy',
|
||||
type: 'primary',
|
||||
size: 'md'
|
||||
}
|
||||
}
|
||||
|
||||
export const Secondary: Story = {
|
||||
render: (args) => ({
|
||||
components: { IconTextButton },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<IconTextButton v-bind="args">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--settings] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
label: 'Settings',
|
||||
type: 'secondary',
|
||||
size: 'md'
|
||||
}
|
||||
}
|
||||
|
||||
export const Transparent: Story = {
|
||||
render: (args) => ({
|
||||
components: { IconTextButton },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<IconTextButton v-bind="args">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--x] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
label: 'Cancel',
|
||||
type: 'transparent',
|
||||
size: 'md'
|
||||
}
|
||||
}
|
||||
|
||||
export const WithIconRight: Story = {
|
||||
render: (args) => ({
|
||||
components: { IconTextButton },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<IconTextButton v-bind="args">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--chevron-right] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
label: 'Next',
|
||||
type: 'primary',
|
||||
size: 'md',
|
||||
iconPosition: 'right'
|
||||
}
|
||||
}
|
||||
|
||||
export const Small: Story = {
|
||||
render: (args) => ({
|
||||
components: { IconTextButton },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<IconTextButton v-bind="args">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--save] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
`
|
||||
}),
|
||||
args: {
|
||||
label: 'Save',
|
||||
type: 'primary',
|
||||
size: 'sm'
|
||||
}
|
||||
}
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => ({
|
||||
components: {
|
||||
IconTextButton
|
||||
},
|
||||
template: `
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex gap-2 items-center">
|
||||
<IconTextButton label="Download" type="primary" size="sm" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--download] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton label="Download" type="primary" size="md" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<IconTextButton label="Settings" type="secondary" size="sm" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--settings] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton label="Settings" type="secondary" size="md" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--settings] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<IconTextButton label="Delete" type="transparent" size="sm" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--trash-2] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton label="Delete" type="transparent" size="md" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--trash-2] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<IconTextButton label="Next" type="primary" size="md" iconPosition="right" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--chevron-right] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton label="Previous" type="secondary" size="md" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--chevron-left] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton label="Save File" type="primary" size="md" @click="() => {}">
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--save] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}),
|
||||
parameters: {
|
||||
controls: { disable: true },
|
||||
actions: { disable: true }
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<Button
|
||||
v-bind="$attrs"
|
||||
unstyled
|
||||
:class="buttonStyle"
|
||||
:disabled="disabled"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot v-if="iconPosition !== 'right'" name="icon"></slot>
|
||||
<span>{{ label }}</span>
|
||||
<slot v-if="iconPosition === 'right'" name="icon"></slot>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { BaseButtonProps } from '@/types/buttonTypes'
|
||||
import {
|
||||
getBaseButtonClasses,
|
||||
getBorderButtonTypeClasses,
|
||||
getButtonSizeClasses,
|
||||
getButtonTypeClasses
|
||||
} from '@/types/buttonTypes'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
interface IconTextButtonProps extends BaseButtonProps {
|
||||
iconPosition?: 'left' | 'right'
|
||||
label: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
const {
|
||||
size = 'md',
|
||||
type = 'primary',
|
||||
border = false,
|
||||
disabled = false,
|
||||
class: className,
|
||||
iconPosition = 'left',
|
||||
label,
|
||||
onClick
|
||||
} = defineProps<IconTextButtonProps>()
|
||||
|
||||
const buttonStyle = computed(() => {
|
||||
const baseClasses = `${getBaseButtonClasses()} justify-start gap-2`
|
||||
const sizeClasses = getButtonSizeClasses(size)
|
||||
const typeClasses = border
|
||||
? getBorderButtonTypeClasses(type)
|
||||
: getButtonTypeClasses(type)
|
||||
|
||||
return cn(baseClasses, sizeClasses, typeClasses, className)
|
||||
})
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import IconTextButton from './IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import MoreButton from './MoreButton.vue'
|
||||
|
||||
const meta: Meta<typeof MoreButton> = {
|
||||
@@ -17,30 +17,26 @@ type Story = StoryObj<typeof MoreButton>
|
||||
|
||||
export const Basic: Story = {
|
||||
render: () => ({
|
||||
components: { MoreButton, IconTextButton },
|
||||
components: { MoreButton, Button },
|
||||
template: `
|
||||
<div style="height: 200px; display: flex; align-items: center; justify-content: center;">
|
||||
<MoreButton>
|
||||
<template #default="{ close }">
|
||||
<IconTextButton
|
||||
type="transparent"
|
||||
label="Settings"
|
||||
<Button
|
||||
variant="textonly"
|
||||
@click="() => { close() }"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
<span>Settings</span>
|
||||
</Button>
|
||||
|
||||
<IconTextButton
|
||||
type="transparent"
|
||||
label="Profile"
|
||||
<Button
|
||||
variant="textonly"
|
||||
@click="() => { close() }"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--scroll-text] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--scroll-text] size-4" />
|
||||
<span>Profile</span>
|
||||
</Button>
|
||||
</template>
|
||||
</MoreButton>
|
||||
</div>
|
||||
|
||||
@@ -22,16 +22,17 @@
|
||||
|
||||
<template #header-right-area>
|
||||
<div class="flex gap-2">
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-if="filteredCount !== totalCount"
|
||||
type="secondary"
|
||||
:label="$t('templateWorkflows.resetFilters', 'Clear Filters')"
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
@click="resetFilters"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--filter-x]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--filter-x]" />
|
||||
<span>{{
|
||||
$t('templateWorkflows.resetFilters', 'Clear Filters')
|
||||
}}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -382,7 +383,6 @@ import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import CardBottom from '@/components/card/CardBottom.vue'
|
||||
import CardContainer from '@/components/card/CardContainer.vue'
|
||||
import CardTop from '@/components/card/CardTop.vue'
|
||||
|
||||
@@ -4,20 +4,17 @@
|
||||
v-if="isCloud"
|
||||
class="flex w-full items-center justify-between gap-2 py-2 px-4"
|
||||
>
|
||||
<IconTextButton
|
||||
:label="$t('missingNodes.cloud.learnMore')"
|
||||
type="transparent"
|
||||
<Button
|
||||
variant="textonly"
|
||||
size="sm"
|
||||
icon-position="left"
|
||||
as="a"
|
||||
href="https://www.comfy.org/cloud"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--info]"></i>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--info]"></i>
|
||||
<span>{{ $t('missingNodes.cloud.learnMore') }}</span>
|
||||
</Button>
|
||||
<Button variant="secondary" size="md" @click="handleGotItClick">{{
|
||||
$t('missingNodes.cloud.gotIt')
|
||||
}}</Button>
|
||||
@@ -50,7 +47,6 @@
|
||||
import { computed, nextTick, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
|
||||
@@ -8,20 +8,15 @@
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-between px-3">
|
||||
<IconTextButton
|
||||
class="grow gap-1 p-2 text-center font-inter text-[12px] leading-none hover:opacity-90 justify-center"
|
||||
type="secondary"
|
||||
:label="t('sideToolbar.queueProgressOverlay.showAssets')"
|
||||
:aria-label="t('sideToolbar.queueProgressOverlay.showAssets')"
|
||||
<Button
|
||||
class="grow gap-1 justify-center"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@click="$emit('showAssets')"
|
||||
>
|
||||
<template #icon>
|
||||
<div
|
||||
class="pointer-events-none block size-4 shrink-0 leading-none icon-[comfy--image-ai-edit]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[comfy--image-ai-edit] size-4" />
|
||||
<span>{{ t('sideToolbar.queueProgressOverlay.showAssets') }}</span>
|
||||
</Button>
|
||||
<div class="ml-4 inline-flex items-center">
|
||||
<div
|
||||
class="inline-flex h-6 items-center text-[12px] leading-none text-text-primary opacity-90"
|
||||
@@ -78,7 +73,6 @@
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type {
|
||||
JobGroup,
|
||||
|
||||
@@ -46,19 +46,18 @@
|
||||
<div
|
||||
class="flex flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3 font-inter"
|
||||
>
|
||||
<IconTextButton
|
||||
class="w-full justify-start gap-2 bg-transparent p-2 font-inter text-[12px] leading-none text-text-primary hover:bg-transparent hover:opacity-90"
|
||||
type="transparent"
|
||||
:label="t('sideToolbar.queueProgressOverlay.clearHistory')"
|
||||
<Button
|
||||
class="w-full justify-start"
|
||||
variant="textonly"
|
||||
size="sm"
|
||||
:aria-label="t('sideToolbar.queueProgressOverlay.clearHistory')"
|
||||
@click="onClearHistoryFromMenu"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
class="icon-[lucide--file-x-2] block size-4 leading-none text-text-secondary"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--file-x-2] size-4 text-muted" />
|
||||
<span>{{
|
||||
t('sideToolbar.queueProgressOverlay.clearHistory')
|
||||
}}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
@@ -71,7 +70,6 @@ import type { PopoverMethods } from 'primevue/popover'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
@@ -20,24 +20,23 @@
|
||||
<div v-if="entry.kind === 'divider'" class="px-2 py-1">
|
||||
<div class="h-px bg-interface-stroke" />
|
||||
</div>
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-else
|
||||
class="w-full justify-start gap-2 bg-transparent p-2 font-inter text-[12px] leading-none text-text-primary hover:bg-interface-panel-hover-surface"
|
||||
type="transparent"
|
||||
:label="entry.label"
|
||||
class="w-full justify-start bg-transparent"
|
||||
variant="textonly"
|
||||
size="sm"
|
||||
:aria-label="entry.label"
|
||||
@click="onEntry(entry)"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
v-if="entry.icon"
|
||||
:class="[
|
||||
entry.icon,
|
||||
'block size-4 shrink-0 leading-none text-text-secondary'
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i
|
||||
v-if="entry.icon"
|
||||
:class="[
|
||||
entry.icon,
|
||||
'block size-4 shrink-0 leading-none text-text-secondary'
|
||||
]"
|
||||
/>
|
||||
<span>{{ entry.label }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
</Popover>
|
||||
@@ -47,7 +46,7 @@
|
||||
import Popover from 'primevue/popover'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { MenuEntry } from '@/composables/queue/useJobMenu'
|
||||
|
||||
defineProps<{ entries: MenuEntry[] }>()
|
||||
|
||||
@@ -58,31 +58,28 @@
|
||||
{{ t('queue.jobDetails.errorMessage') }}
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<IconTextButton
|
||||
class="h-6 justify-start gap-2 bg-transparent px-0 text-[0.75rem] leading-none text-text-secondary hover:opacity-90"
|
||||
type="transparent"
|
||||
:label="copyAriaLabel"
|
||||
:aria-label="copyAriaLabel"
|
||||
<Button
|
||||
class="justify-start px-0"
|
||||
variant="muted-textonly"
|
||||
size="sm"
|
||||
icon-position="right"
|
||||
@click.stop="copyErrorMessage"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--copy] block size-3.5 leading-none" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton
|
||||
class="h-6 justify-start gap-2 bg-transparent px-0 text-[0.75rem] leading-none text-text-secondary hover:opacity-90"
|
||||
type="transparent"
|
||||
:label="t('queue.jobDetails.report')"
|
||||
<span>{{ copyAriaLabel }}</span>
|
||||
<i class="icon-[lucide--copy] block size-3.5 leading-none" />
|
||||
</Button>
|
||||
<Button
|
||||
class="justify-start px-0"
|
||||
variant="muted-textonly"
|
||||
size="sm"
|
||||
icon-position="right"
|
||||
@click.stop="reportJobError"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
class="icon-[lucide--message-circle-warning] block size-3.5 leading-none"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{ t('queue.jobDetails.report') }}</span>
|
||||
<i
|
||||
class="icon-[lucide--message-circle-warning] block size-3.5 leading-none"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
class="col-span-2 mt-2 rounded bg-interface-panel-hover-surface px-4 py-2 text-[0.75rem] leading-normal text-text-secondary"
|
||||
@@ -98,7 +95,6 @@
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
|
||||
@@ -47,41 +47,34 @@
|
||||
<div
|
||||
class="flex min-w-[12rem] flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3"
|
||||
>
|
||||
<IconTextButton
|
||||
class="w-full justify-between gap-1 bg-transparent p-2 font-inter text-[12px] leading-none text-text-primary hover:bg-transparent hover:opacity-90"
|
||||
type="transparent"
|
||||
icon-position="right"
|
||||
:label="t('sideToolbar.queueProgressOverlay.filterAllWorkflows')"
|
||||
:aria-label="
|
||||
t('sideToolbar.queueProgressOverlay.filterAllWorkflows')
|
||||
"
|
||||
<Button
|
||||
class="w-full justify-between"
|
||||
variant="textonly"
|
||||
size="sm"
|
||||
@click="selectWorkflowFilter('all')"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
v-if="selectedWorkflowFilter === 'all'"
|
||||
class="icon-[lucide--check] block size-4 leading-none text-text-secondary"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{
|
||||
t('sideToolbar.queueProgressOverlay.filterAllWorkflows')
|
||||
}}</span>
|
||||
<i
|
||||
v-if="selectedWorkflowFilter === 'all'"
|
||||
class="icon-[lucide--check] size-4"
|
||||
/>
|
||||
</Button>
|
||||
<div class="mx-2 mt-1 h-px" />
|
||||
<IconTextButton
|
||||
class="w-full justify-between gap-1 bg-transparent p-2 font-inter text-[12px] leading-none text-text-primary hover:bg-transparent hover:opacity-90"
|
||||
type="transparent"
|
||||
icon-position="right"
|
||||
:label="t('sideToolbar.queueProgressOverlay.filterCurrentWorkflow')"
|
||||
:aria-label="
|
||||
t('sideToolbar.queueProgressOverlay.filterCurrentWorkflow')
|
||||
"
|
||||
<Button
|
||||
class="w-full justify-between"
|
||||
variant="textonly"
|
||||
@click="selectWorkflowFilter('current')"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
v-if="selectedWorkflowFilter === 'current'"
|
||||
class="icon-[lucide--check] block size-4 leading-none text-text-secondary"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{
|
||||
t('sideToolbar.queueProgressOverlay.filterCurrentWorkflow')
|
||||
}}</span>
|
||||
<i
|
||||
v-if="selectedWorkflowFilter === 'current'"
|
||||
class="icon-[lucide--check] block size-4 leading-none text-text-secondary"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Popover>
|
||||
<Button
|
||||
@@ -115,21 +108,18 @@
|
||||
class="flex min-w-[12rem] flex-col items-stretch rounded-lg border border-interface-stroke bg-interface-panel-surface px-2 py-3"
|
||||
>
|
||||
<template v-for="(mode, index) in jobSortModes" :key="mode">
|
||||
<IconTextButton
|
||||
class="w-full justify-between gap-1 bg-transparent p-2 font-inter text-[12px] leading-none text-text-primary hover:bg-transparent hover:opacity-90"
|
||||
type="transparent"
|
||||
icon-position="right"
|
||||
:label="sortLabel(mode)"
|
||||
:aria-label="sortLabel(mode)"
|
||||
<Button
|
||||
class="w-full justify-between"
|
||||
variant="textonly"
|
||||
size="sm"
|
||||
@click="selectSortMode(mode)"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
v-if="selectedSortMode === mode"
|
||||
class="icon-[lucide--check] block size-4 leading-none text-text-secondary"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{ sortLabel(mode) }}</span>
|
||||
<i
|
||||
v-if="selectedSortMode === mode"
|
||||
class="icon-[lucide--check] size-4 text-text-secondary"
|
||||
/>
|
||||
</Button>
|
||||
<div
|
||||
v-if="index < jobSortModes.length - 1"
|
||||
class="mx-2 mt-1 h-px"
|
||||
@@ -146,7 +136,6 @@ import Popover from 'primevue/popover'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { jobSortModes, jobTabs } from '@/composables/queue/useJobList'
|
||||
import type { JobSortMode, JobTab } from '@/composables/queue/useJobList'
|
||||
|
||||
@@ -37,15 +37,10 @@
|
||||
<template #header>
|
||||
<!-- Job Detail View Header -->
|
||||
<div v-if="isInFolderView" class="px-2 2xl:px-4">
|
||||
<IconTextButton
|
||||
:label="$t('sideToolbar.backToAssets')"
|
||||
type="secondary"
|
||||
@click="exitFolderView"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--arrow-left] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<Button variant="secondary" size="lg" @click="exitFolderView">
|
||||
<i class="icon-[lucide--arrow-left] size-4" />
|
||||
<span>{{ $t('sideToolbar.backToAssets') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
@@ -128,7 +123,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex shrink gap-2 pr-4 items-center-safe">
|
||||
<div class="flex shrink gap-2 pr-4 items-center-safe justify-end-safe">
|
||||
<template v-if="isCompact">
|
||||
<!-- Compact mode: Icon only -->
|
||||
<Button
|
||||
@@ -144,27 +139,18 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- Normal mode: Icon + Text -->
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-if="shouldShowDeleteButton"
|
||||
:label="$t('mediaAsset.selection.deleteSelected')"
|
||||
type="secondary"
|
||||
icon-position="right"
|
||||
variant="secondary"
|
||||
@click="handleDeleteSelected"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--trash-2] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton
|
||||
:label="$t('mediaAsset.selection.downloadSelected')"
|
||||
type="secondary"
|
||||
icon-position="right"
|
||||
@click="handleDownloadSelected"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{ $t('mediaAsset.selection.deleteSelected') }}</span>
|
||||
<i class="icon-[lucide--trash-2] size-4" />
|
||||
</Button>
|
||||
<Button variant="secondary" @click="handleDownloadSelected">
|
||||
<span>{{ $t('mediaAsset.selection.downloadSelected') }}</span>
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -184,7 +170,6 @@ import { useToast } from 'primevue/usetoast'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import VirtualGrid from '@/components/common/VirtualGrid.vue'
|
||||
import Load3dViewerContent from '@/components/load3d/Load3dViewerContent.vue'
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import type {
|
||||
Meta,
|
||||
StoryObj,
|
||||
ComponentPropsAndSlots
|
||||
} from '@storybook/vue3-vite'
|
||||
|
||||
import Button from './Button.vue'
|
||||
import { FOR_STORIES } from '@/components/ui/button/button.variants'
|
||||
|
||||
interface ButtonPropsAndStoryArgs extends ComponentPropsAndSlots<
|
||||
typeof Button
|
||||
> {
|
||||
icon?: 'left' | 'right'
|
||||
}
|
||||
|
||||
const { variants, sizes } = FOR_STORIES
|
||||
const meta: Meta<typeof Button> = {
|
||||
const meta: Meta<ButtonPropsAndStoryArgs> = {
|
||||
title: 'Components/Button/Button',
|
||||
component: Button,
|
||||
tags: ['autodocs'],
|
||||
@@ -22,13 +32,19 @@ const meta: Meta<typeof Button> = {
|
||||
as: { defaultValue: 'button' },
|
||||
asChild: { defaultValue: false },
|
||||
default: {
|
||||
control: { type: 'text' },
|
||||
defaultValue: 'Button'
|
||||
},
|
||||
icon: {
|
||||
control: { type: 'select' },
|
||||
options: [undefined, 'left', 'right']
|
||||
}
|
||||
},
|
||||
args: {
|
||||
variant: 'secondary',
|
||||
size: 'md',
|
||||
default: 'Button'
|
||||
default: 'Button',
|
||||
icon: undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +52,18 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const SingleButton: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
size: 'lg'
|
||||
}
|
||||
render: (args) => ({
|
||||
components: { Button },
|
||||
setup() {
|
||||
return { args }
|
||||
},
|
||||
template: `
|
||||
<Button v-bind="args">
|
||||
<i v-if="args.icon === 'left'" class="icon-[lucide--settings]" />
|
||||
{{args.default}}
|
||||
<i v-if="args.icon === 'right'" class="icon-[lucide--settings]" />
|
||||
</Button>`
|
||||
})
|
||||
}
|
||||
|
||||
function generateVariants() {
|
||||
|
||||
@@ -17,43 +17,34 @@
|
||||
|
||||
<template #header-right-area>
|
||||
<div class="flex gap-2">
|
||||
<IconTextButton
|
||||
type="primary"
|
||||
:label="$t('g.upload')"
|
||||
@click="() => {}"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--upload]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<Button variant="primary" @click="() => {}">
|
||||
<i class="icon-[lucide--upload]" />
|
||||
<span>{{ $t('g.upload') }}</span>
|
||||
</Button>
|
||||
<MoreButton>
|
||||
<template #default="{ close }">
|
||||
<IconTextButton
|
||||
type="secondary"
|
||||
:label="$t('g.settings')"
|
||||
<Button
|
||||
variant="secondary"
|
||||
@click="
|
||||
() => {
|
||||
close()
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--download]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton
|
||||
type="primary"
|
||||
:label="$t('g.profile')"
|
||||
<i class="icon-[lucide--download]" />
|
||||
<span>{{ $t('g.settings') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@click="
|
||||
() => {
|
||||
close()
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--scroll]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--scroll]" />
|
||||
<span>{{ $t('g.profile') }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
</MoreButton>
|
||||
</div>
|
||||
@@ -134,7 +125,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, provide, ref } from 'vue'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import MoreButton from '@/components/button/MoreButton.vue'
|
||||
import CardBottom from '@/components/card/CardBottom.vue'
|
||||
import CardContainer from '@/components/card/CardContainer.vue'
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
import { computed, provide, ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import MoreButton from '@/components/button/MoreButton.vue'
|
||||
import CardBottom from '@/components/card/CardBottom.vue'
|
||||
import CardContainer from '@/components/card/CardContainer.vue'
|
||||
@@ -75,7 +74,6 @@ const createStoryTemplate = (args: StoryArgs) => ({
|
||||
MultiSelect,
|
||||
SingleSelect,
|
||||
Button,
|
||||
IconTextButton,
|
||||
MoreButton,
|
||||
CardContainer,
|
||||
CardTop,
|
||||
@@ -202,33 +200,32 @@ const createStoryTemplate = (args: StoryArgs) => ({
|
||||
<!-- Header Right Area -->
|
||||
<template v-if="args.hasHeaderRightArea" #header-right-area>
|
||||
<div class="flex gap-2">
|
||||
<IconTextButton type="primary" label="Upload Model" @click="() => {}">
|
||||
<template #icon>
|
||||
<Button variant="primary" @click="() => {}">
|
||||
<i class="icon-[lucide--upload] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span> Upload Model </span>
|
||||
</Button>
|
||||
|
||||
<MoreButton>
|
||||
<template #default="{ close }">
|
||||
<IconTextButton
|
||||
type="secondary"
|
||||
<Button
|
||||
variant="secondary"
|
||||
label="Settings"
|
||||
@click="() => { close() }"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--download] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</Button>
|
||||
|
||||
<IconTextButton
|
||||
type="primary"
|
||||
<Button
|
||||
variant="primary"
|
||||
label="Profile"
|
||||
@click="() => { close() }"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--scroll] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
</Button>
|
||||
</template>
|
||||
</MoreButton>
|
||||
</div>
|
||||
@@ -327,33 +324,28 @@ const createStoryTemplate = (args: StoryArgs) => ({
|
||||
<!-- Header Right Area -->
|
||||
<template v-if="args.hasHeaderRightArea" #header-right-area>
|
||||
<div class="flex gap-2">
|
||||
<IconTextButton type="primary" label="Upload Model" @click="() => {}">
|
||||
<template #icon>
|
||||
<Button variant="primary" @click="() => {}">
|
||||
<i class="icon-[lucide--upload] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>Upload Model</span>
|
||||
</Button>
|
||||
|
||||
<MoreButton>
|
||||
<template #default="{ close }">
|
||||
<IconTextButton
|
||||
type="secondary"
|
||||
label="Settings"
|
||||
<Button
|
||||
variant="secondary"
|
||||
@click="() => { close() }"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--download] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>Settings</span>
|
||||
</Button>
|
||||
|
||||
<IconTextButton
|
||||
type="primary"
|
||||
label="Profile"
|
||||
<Button
|
||||
variant="primary"
|
||||
@click="() => { close() }"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--scroll] size-3" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>Profile</span>
|
||||
</Button>
|
||||
</template>
|
||||
</MoreButton>
|
||||
</div>
|
||||
|
||||
@@ -29,19 +29,18 @@
|
||||
:placeholder="$t('g.searchPlaceholder')"
|
||||
class="max-w-96"
|
||||
/>
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-if="isUploadButtonEnabled"
|
||||
type="accent"
|
||||
size="md"
|
||||
class="!h-10 [&>span]:hidden md:[&>span]:inline"
|
||||
variant="primary"
|
||||
:size="breakpoints.md ? 'md' : 'icon'"
|
||||
data-attr="upload-model-button"
|
||||
:label="$t('assetBrowser.uploadModel')"
|
||||
:on-click="showUploadDialog"
|
||||
@click="showUploadDialog"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--folder-input]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--folder-input]" />
|
||||
<span class="hidden md:inline">{{
|
||||
$t('assetBrowser.uploadModel')
|
||||
}}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -64,12 +63,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import {
|
||||
breakpointsTailwind,
|
||||
useAsyncState,
|
||||
useBreakpoints
|
||||
} from '@vueuse/core'
|
||||
import { computed, provide, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import SearchBox from '@/components/common/SearchBox.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'
|
||||
import LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'
|
||||
import AssetFilterBar from '@/platform/assets/components/AssetFilterBar.vue'
|
||||
@@ -99,6 +102,8 @@ const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
|
||||
provide(OnCloseKey, props.onClose ?? (() => {}))
|
||||
|
||||
const fetchAssets = async () => {
|
||||
|
||||
@@ -43,26 +43,24 @@
|
||||
>
|
||||
<MoreButton ref="dropdown-menu-button" size="sm">
|
||||
<template #default>
|
||||
<IconTextButton
|
||||
:label="$t('g.rename')"
|
||||
type="secondary"
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
class="justify-start"
|
||||
@click="startAssetRename"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--pencil]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton
|
||||
:label="$t('g.delete')"
|
||||
type="secondary"
|
||||
<i class="icon-[lucide--pencil]" />
|
||||
<span>{{ $t('g.rename') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
class="justify-start"
|
||||
@click="confirmDeletion"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--trash-2]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--trash-2]" />
|
||||
<span>{{ $t('g.delete') }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
</MoreButton>
|
||||
</IconGroup>
|
||||
@@ -121,10 +119,10 @@ import { computed, ref, toValue, useId, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconGroup from '@/components/button/IconGroup.vue'
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import MoreButton from '@/components/button/MoreButton.vue'
|
||||
import EditableText from '@/components/common/EditableText.vue'
|
||||
import { showConfirmDialog } from '@/components/dialog/confirm/confirmDialog'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue'
|
||||
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
||||
|
||||
@@ -70,19 +70,17 @@
|
||||
|
||||
<!-- Output count (top-right) -->
|
||||
<template v-if="showOutputCount" #top-right>
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-tooltip.top.pt:pointer-events-none="
|
||||
$t('mediaAsset.actions.seeMoreOutputs')
|
||||
"
|
||||
type="secondary"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
:label="String(outputCount)"
|
||||
@click.stop="handleOutputCountClick"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--layers] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--layers] size-4" />
|
||||
<span>{{ outputCount }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
</CardTop>
|
||||
</template>
|
||||
@@ -130,7 +128,6 @@ import { useElementHover, whenever } from '@vueuse/core'
|
||||
import { computed, defineAsyncComponent, provide, ref, toRef } from 'vue'
|
||||
|
||||
import IconGroup from '@/components/button/IconGroup.vue'
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import CardBottom from '@/components/card/CardBottom.vue'
|
||||
import CardContainer from '@/components/card/CardContainer.vue'
|
||||
import CardTop from '@/components/card/CardTop.vue'
|
||||
|
||||
@@ -13,18 +13,17 @@
|
||||
}"
|
||||
>
|
||||
<template #item="{ item, props }">
|
||||
<IconTextButton
|
||||
type="secondary"
|
||||
size="full-width"
|
||||
:label="
|
||||
typeof item.label === 'function' ? item.label() : (item.label ?? '')
|
||||
"
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
class="w-full justify-start"
|
||||
v-bind="props.action"
|
||||
>
|
||||
<template #icon>
|
||||
<i :class="item.icon" class="size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i :class="item.icon" class="size-4" />
|
||||
<span>{{
|
||||
typeof item.label === 'function' ? item.label() : (item.label ?? '')
|
||||
}}</span>
|
||||
</Button>
|
||||
</template>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
@@ -34,9 +33,9 @@ import { onClickOutside } from '@vueuse/core'
|
||||
import ContextMenu from 'primevue/contextmenu'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import { t } from '@/i18n'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { supportsWorkflowMetadata } from '@/platform/workflow/utils/workflowExtractionUtil'
|
||||
import { detectNodeTypeFromFilename } from '@/utils/loaderNodeUtil'
|
||||
@@ -60,6 +59,7 @@ const emit = defineEmits<{
|
||||
|
||||
const contextMenu = ref<InstanceType<typeof ContextMenu>>()
|
||||
const actions = useMediaAssetActions()
|
||||
const { t } = useI18n()
|
||||
|
||||
// Close context menu when clicking outside
|
||||
onClickOutside(
|
||||
|
||||
@@ -25,10 +25,9 @@
|
||||
>
|
||||
<template #default="{ close }">
|
||||
<MediaAssetSortMenu
|
||||
:sort-by="sortBy"
|
||||
v-model:sort-by="sortBy"
|
||||
:show-generation-time-sort
|
||||
:close="close"
|
||||
@update:sort-by="handleSortChange"
|
||||
/>
|
||||
</template>
|
||||
</AssetSortButton>
|
||||
@@ -43,30 +42,25 @@ import MediaAssetFilterButton from './MediaAssetFilterButton.vue'
|
||||
import MediaAssetFilterMenu from './MediaAssetFilterMenu.vue'
|
||||
import AssetSortButton from './MediaAssetSortButton.vue'
|
||||
import MediaAssetSortMenu from './MediaAssetSortMenu.vue'
|
||||
import type { SortBy } from './MediaAssetSortMenu.vue'
|
||||
|
||||
const { showGenerationTimeSort = false } = defineProps<{
|
||||
searchQuery: string
|
||||
sortBy: 'newest' | 'oldest' | 'longest' | 'fastest'
|
||||
showGenerationTimeSort?: boolean
|
||||
mediaTypeFilters: string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:searchQuery': [value: string]
|
||||
'update:sortBy': [value: 'newest' | 'oldest' | 'longest' | 'fastest']
|
||||
'update:mediaTypeFilters': [value: string[]]
|
||||
}>()
|
||||
|
||||
const sortBy = defineModel<SortBy>('sortBy', { required: true })
|
||||
|
||||
const handleSearchChange = (value: string | undefined) => {
|
||||
emit('update:searchQuery', value ?? '')
|
||||
}
|
||||
|
||||
const handleSortChange = (
|
||||
value: 'newest' | 'oldest' | 'longest' | 'fastest'
|
||||
) => {
|
||||
emit('update:sortBy', value)
|
||||
}
|
||||
|
||||
const handleMediaTypeFiltersChange = (value: string[]) => {
|
||||
emit('update:mediaTypeFilters', value)
|
||||
}
|
||||
|
||||
@@ -1,74 +1,59 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<IconTextButton
|
||||
type="transparent"
|
||||
icon-position="right"
|
||||
:label="$t('sideToolbar.mediaAssets.sortNewestFirst')"
|
||||
<Button
|
||||
variant="textonly"
|
||||
class="justify-start"
|
||||
@click="handleSortChange('newest')"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="sortBy === 'newest'" class="icon-[lucide--check] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{ $t('sideToolbar.mediaAssets.sortNewestFirst') }}</span>
|
||||
<i v-if="sortBy === 'newest'" class="icon-[lucide--check] size-4" />
|
||||
</Button>
|
||||
|
||||
<IconTextButton
|
||||
type="transparent"
|
||||
icon-position="right"
|
||||
:label="$t('sideToolbar.mediaAssets.sortOldestFirst')"
|
||||
<Button
|
||||
variant="textonly"
|
||||
class="justify-start"
|
||||
@click="handleSortChange('oldest')"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="sortBy === 'oldest'" class="icon-[lucide--check] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{ $t('sideToolbar.mediaAssets.sortOldestFirst') }}</span>
|
||||
<i v-if="sortBy === 'oldest'" class="icon-[lucide--check] size-4" />
|
||||
</Button>
|
||||
|
||||
<template v-if="showGenerationTimeSort">
|
||||
<IconTextButton
|
||||
type="transparent"
|
||||
icon-position="right"
|
||||
:label="$t('sideToolbar.mediaAssets.sortLongestFirst')"
|
||||
<Button
|
||||
variant="textonly"
|
||||
class="justify-start"
|
||||
@click="handleSortChange('longest')"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="sortBy === 'longest'" class="icon-[lucide--check] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{ $t('sideToolbar.mediaAssets.sortLongestFirst') }}</span>
|
||||
<i v-if="sortBy === 'longest'" class="icon-[lucide--check] size-4" />
|
||||
</Button>
|
||||
|
||||
<IconTextButton
|
||||
type="transparent"
|
||||
icon-position="right"
|
||||
:label="$t('sideToolbar.mediaAssets.sortFastestFirst')"
|
||||
<Button
|
||||
variant="textonly"
|
||||
class="justify-start"
|
||||
@click="handleSortChange('fastest')"
|
||||
>
|
||||
<template #icon>
|
||||
<i v-if="sortBy === 'fastest'" class="icon-[lucide--check] size-4" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<span>{{ $t('sideToolbar.mediaAssets.sortFastestFirst') }}</span>
|
||||
<i v-if="sortBy === 'fastest'" class="icon-[lucide--check] size-4" />
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const {
|
||||
sortBy,
|
||||
close,
|
||||
showGenerationTimeSort = false
|
||||
} = defineProps<{
|
||||
sortBy: 'newest' | 'oldest' | 'longest' | 'fastest'
|
||||
export type SortBy = 'newest' | 'oldest' | 'longest' | 'fastest'
|
||||
|
||||
const { close, showGenerationTimeSort = false } = defineProps<{
|
||||
close: () => void
|
||||
showGenerationTimeSort?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:sortBy': [value: 'newest' | 'oldest' | 'longest' | 'fastest']
|
||||
}>()
|
||||
const sortBy = defineModel<SortBy>('sortBy', { required: true })
|
||||
|
||||
const handleSortChange = (
|
||||
value: 'newest' | 'oldest' | 'longest' | 'fastest'
|
||||
) => {
|
||||
emit('update:sortBy', value)
|
||||
const handleSortChange = (value: SortBy) => {
|
||||
sortBy.value = value
|
||||
close()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<div class="flex justify-end gap-2 w-full">
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-if="currentStep === 1"
|
||||
:label="$t('assetBrowser.uploadModelHowDoIFindThis')"
|
||||
type="transparent"
|
||||
size="md"
|
||||
class="mr-auto underline text-muted-foreground"
|
||||
variant="muted-textonly"
|
||||
size="lg"
|
||||
class="mr-auto underline"
|
||||
data-attr="upload-model-step1-help-link"
|
||||
@click="showVideoHelp = true"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--circle-question-mark]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--circle-question-mark]" />
|
||||
<span>{{ $t('assetBrowser.uploadModelHowDoIFindThis') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-if="currentStep === 1"
|
||||
variant="muted-textonly"
|
||||
@@ -35,38 +33,31 @@
|
||||
</Button>
|
||||
<span v-else />
|
||||
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-if="currentStep === 1"
|
||||
:label="$t('g.continue')"
|
||||
type="secondary"
|
||||
size="md"
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
data-attr="upload-model-step1-continue-button"
|
||||
:disabled="!canFetchMetadata || isFetchingMetadata"
|
||||
@click="emit('fetchMetadata')"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
v-if="isFetchingMetadata"
|
||||
class="icon-[lucide--loader-circle] animate-spin"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<IconTextButton
|
||||
<i
|
||||
v-if="isFetchingMetadata"
|
||||
class="icon-[lucide--loader-circle] animate-spin"
|
||||
/>
|
||||
<span>{{ $t('g.continue') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="currentStep === 2"
|
||||
:label="$t('assetBrowser.upload')"
|
||||
type="secondary"
|
||||
size="md"
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
data-attr="upload-model-step2-confirm-button"
|
||||
:disabled="!canUploadModel || isUploading"
|
||||
@click="emit('upload')"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
v-if="isUploading"
|
||||
class="icon-[lucide--loader-circle] animate-spin"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i v-if="isUploading" class="icon-[lucide--loader-circle] animate-spin" />
|
||||
<span>{{ $t('assetBrowser.upload') }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="currentStep === 3 && uploadStatus === 'success'"
|
||||
variant="secondary"
|
||||
@@ -86,7 +77,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import VideoHelpDialog from '@/platform/assets/components/VideoHelpDialog.vue'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useModelUpload } from '@/platform/assets/composables/useModelUpload'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
@@ -40,16 +40,15 @@ const singleFilterOption = computed(() => filterOptions.length === 1)
|
||||
>
|
||||
{{ option.name }}
|
||||
</button>
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-if="isUploadButtonEnabled && singleFilterOption"
|
||||
:label="$t('g.import')"
|
||||
class="ml-auto text-base-foreground hover:bg-node-component-widget-input-surface"
|
||||
type="transparent"
|
||||
class="ml-auto"
|
||||
size="md"
|
||||
variant="textonly"
|
||||
@click="showUploadDialog"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--folder-input]" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i class="icon-[lucide--folder-input]" />
|
||||
<span>{{ $t('g.import') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
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 ButtonBorder = boolean
|
||||
|
||||
export interface BaseButtonProps {
|
||||
size?: ButtonSize
|
||||
type?: ButtonType
|
||||
border?: ButtonBorder
|
||||
disabled?: boolean
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
export const getButtonSizeClasses = (size: ButtonSize = 'md') => {
|
||||
const sizeClasses = {
|
||||
'fit-content': '',
|
||||
'full-width': 'w-full',
|
||||
sm: 'px-2 py-1.5 text-xs',
|
||||
md: 'px-4 py-2 text-sm'
|
||||
}
|
||||
return sizeClasses[size]
|
||||
}
|
||||
|
||||
export const getButtonTypeClasses = (type: ButtonType = 'primary') => {
|
||||
const baseByType = {
|
||||
primary: 'bg-base-foreground border-none text-base-background',
|
||||
secondary: cn(
|
||||
'bg-secondary-background border-none text-base-foreground hover:bg-secondary-background-hover'
|
||||
),
|
||||
transparent: cn(
|
||||
'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'
|
||||
} as const
|
||||
|
||||
return baseByType[type]
|
||||
}
|
||||
|
||||
export const getBorderButtonTypeClasses = (type: ButtonType = 'primary') => {
|
||||
const baseByType = {
|
||||
primary: 'bg-base-background text-base-foreground',
|
||||
secondary: 'bg-secondary-background text-base-foreground',
|
||||
transparent: cn(
|
||||
'bg-transparent text-base-foreground hover:bg-secondary-background-hover'
|
||||
),
|
||||
accent:
|
||||
'bg-primary-background hover:bg-primary-background-hover text-white font-bold'
|
||||
} 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'
|
||||
} as const
|
||||
|
||||
return `${baseByType[type]} ${borderByType[type]}`
|
||||
}
|
||||
|
||||
export const getBaseButtonClasses = () => {
|
||||
return [
|
||||
'flex items-center justify-center shrink-0',
|
||||
'outline-hidden rounded-lg cursor-pointer transition-all duration-200',
|
||||
'disabled:opacity-50 disabled:pointer-events-none'
|
||||
].join(' ')
|
||||
}
|
||||
@@ -1,34 +1,31 @@
|
||||
<template>
|
||||
<IconTextButton
|
||||
v-bind="$attrs"
|
||||
type="secondary"
|
||||
:label="computedLabel"
|
||||
:size="size"
|
||||
<Button
|
||||
variant="secondary"
|
||||
:size
|
||||
:disabled="isLoading || isInstalling"
|
||||
@click="installAllPacks"
|
||||
>
|
||||
<template #icon>
|
||||
<i
|
||||
v-if="hasConflict && !isInstalling && !isLoading"
|
||||
class="pi pi-exclamation-triangle text-yellow-500"
|
||||
/>
|
||||
<DotSpinner
|
||||
v-else-if="isLoading || isInstalling"
|
||||
duration="1s"
|
||||
:size="size === 'sm' ? 12 : 16"
|
||||
/>
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<i
|
||||
v-if="hasConflict && !isInstalling && !isLoading"
|
||||
class="pi pi-exclamation-triangle text-yellow-500"
|
||||
/>
|
||||
<DotSpinner
|
||||
v-else-if="isLoading || isInstalling"
|
||||
duration="1s"
|
||||
:size="size === 'sm' ? 12 : 16"
|
||||
/>
|
||||
<span>{{ computedLabel }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import DotSpinner from '@/components/common/DotSpinner.vue'
|
||||
import { t } from '@/i18n'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button/button.variants'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import type { ButtonSize } from '@/types/buttonTypes'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||
@@ -51,13 +48,14 @@ const {
|
||||
nodePacks: NodePack[]
|
||||
isLoading?: boolean
|
||||
label?: string
|
||||
size?: ButtonSize
|
||||
size?: ButtonVariants['size']
|
||||
hasConflict?: boolean
|
||||
conflictInfo?: ConflictDetail[]
|
||||
}>()
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
const { showNodeConflictDialog } = useDialogService()
|
||||
const { t } = useI18n()
|
||||
|
||||
// Check if any of the packs are currently being installed
|
||||
const isInstalling = computed(() => {
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<template>
|
||||
<IconTextButton
|
||||
v-bind="$attrs"
|
||||
type="transparent"
|
||||
:label="
|
||||
nodePacks.length > 1
|
||||
? $t('manager.uninstallSelected')
|
||||
: $t('manager.uninstall')
|
||||
"
|
||||
:border="true"
|
||||
:size="size"
|
||||
class="border-red-500"
|
||||
<Button
|
||||
variant="textonly"
|
||||
:size
|
||||
class="border border-red-500"
|
||||
@click="uninstallItems"
|
||||
/>
|
||||
>
|
||||
{{
|
||||
nodePacks.length > 1
|
||||
? t('manager.uninstallSelected')
|
||||
: t('manager.uninstall')
|
||||
}}
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import type { ButtonSize } from '@/types/buttonTypes'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button/button.variants'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||
import type { components as ManagerComponents } from '@/workbench/extensions/manager/types/generatedManagerTypes'
|
||||
@@ -25,10 +26,11 @@ type NodePack = components['schemas']['Node']
|
||||
|
||||
const { nodePacks, size } = defineProps<{
|
||||
nodePacks: NodePack[]
|
||||
size?: ButtonSize
|
||||
size?: ButtonVariants['size']
|
||||
}>()
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const createPayload = (
|
||||
uninstallItem: NodePack
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
<template>
|
||||
<IconTextButton
|
||||
<Button
|
||||
v-tooltip.top="
|
||||
hasDisabledUpdatePacks ? $t('manager.disabledNodesWontUpdate') : null
|
||||
"
|
||||
v-bind="$attrs"
|
||||
type="transparent"
|
||||
:label="$t('manager.updateAll')"
|
||||
:border="true"
|
||||
variant="textonly"
|
||||
class="border"
|
||||
size="sm"
|
||||
:disabled="isUpdating"
|
||||
@click="updateAllPacks"
|
||||
>
|
||||
<template v-if="isUpdating" #icon>
|
||||
<DotSpinner duration="1s" :size="12" />
|
||||
</template>
|
||||
</IconTextButton>
|
||||
<DotSpinner v-if="isUpdating" duration="1s" :size="12" />
|
||||
<span>{{ $t('manager.updateAll') }}</span>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import DotSpinner from '@/components/common/DotSpinner.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user