export const Basic: Story = {
render: () => ({
- components: { MoreButton, IconTextButton, Download, ScrollText },
+ components: { MoreButton, IconTextButton },
template: `
@@ -29,7 +28,7 @@ export const Basic: Story = {
@click="() => { close() }"
>
-
+
@@ -39,7 +38,7 @@ export const Basic: Story = {
@click="() => { close() }"
>
-
+
diff --git a/src/components/button/TextButton.vue b/src/components/button/TextButton.vue
index a183ea788..14d32cc73 100644
--- a/src/components/button/TextButton.vue
+++ b/src/components/button/TextButton.vue
@@ -1,5 +1,11 @@
-
@@ -21,6 +27,10 @@ interface TextButtonProps extends BaseButtonProps {
onClick: () => void
}
+defineOptions({
+ inheritAttrs: false
+})
+
const {
size = 'md',
type = 'primary',
diff --git a/src/components/card/Card.stories.ts b/src/components/card/Card.stories.ts
index 0d8bf4385..923327a79 100644
--- a/src/components/card/Card.stories.ts
+++ b/src/components/card/Card.stories.ts
@@ -1,13 +1,4 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
-import {
- Download,
- Folder,
- Heart,
- Info,
- MoreVertical,
- Star,
- Upload
-} from 'lucide-vue-next'
import { ref } from 'vue'
import IconButton from '../button/IconButton.vue'
@@ -149,14 +140,7 @@ const createCardTemplate = (args: CardStoryArgs) => ({
CardTitle,
CardDescription,
IconButton,
- SquareChip,
- Info,
- Folder,
- Heart,
- Download,
- Star,
- Upload,
- MoreVertical
+ SquareChip
},
setup() {
const favorited = ref(false)
@@ -202,14 +186,14 @@ const createCardTemplate = (args: CardStoryArgs) => ({
class="!bg-white/90 !text-neutral-900"
@click="() => console.log('Info clicked')"
>
-
+
-
+
@@ -222,7 +206,7 @@ const createCardTemplate = (args: CardStoryArgs) => ({
-
+
@@ -409,11 +393,7 @@ export const GridOfCards: Story = {
CardTitle,
CardDescription,
IconButton,
- SquareChip,
- Info,
- Folder,
- Heart,
- Download
+ SquareChip
},
setup() {
const cards = ref([
@@ -500,7 +480,7 @@ export const GridOfCards: Story = {
class="!bg-white/90 !text-neutral-900"
@click="() => console.log('Info:', card.title)"
>
-
+
@@ -511,7 +491,7 @@ export const GridOfCards: Story = {
:label="tag"
>
-
+
diff --git a/src/components/common/EditableText.spec.ts b/src/components/common/EditableText.spec.ts
index 2e7b036b5..2d31123b9 100644
--- a/src/components/common/EditableText.spec.ts
+++ b/src/components/common/EditableText.spec.ts
@@ -68,4 +68,73 @@ describe('EditableText', () => {
// @ts-expect-error fixme ts strict error
expect(wrapper.emitted('edit')[0]).toEqual(['Test Text'])
})
+
+ it('cancels editing on escape key', async () => {
+ const wrapper = mountComponent({
+ modelValue: 'Original Text',
+ isEditing: true
+ })
+
+ // Change the input value
+ await wrapper.findComponent(InputText).setValue('Modified Text')
+
+ // Press escape
+ await wrapper.findComponent(InputText).trigger('keyup.escape')
+
+ // Should emit cancel event
+ expect(wrapper.emitted('cancel')).toBeTruthy()
+
+ // Should NOT emit edit event
+ expect(wrapper.emitted('edit')).toBeFalsy()
+
+ // Input value should be reset to original
+ expect(wrapper.findComponent(InputText).props()['modelValue']).toBe(
+ 'Original Text'
+ )
+ })
+
+ it('does not save changes when escape is pressed and blur occurs', async () => {
+ const wrapper = mountComponent({
+ modelValue: 'Original Text',
+ isEditing: true
+ })
+
+ // Change the input value
+ await wrapper.findComponent(InputText).setValue('Modified Text')
+
+ // Press escape (which triggers blur internally)
+ await wrapper.findComponent(InputText).trigger('keyup.escape')
+
+ // Manually trigger blur to simulate the blur that happens after escape
+ await wrapper.findComponent(InputText).trigger('blur')
+
+ // Should emit cancel but not edit
+ expect(wrapper.emitted('cancel')).toBeTruthy()
+ expect(wrapper.emitted('edit')).toBeFalsy()
+ })
+
+ it('saves changes on enter but not on escape', async () => {
+ // Test Enter key saves changes
+ const enterWrapper = mountComponent({
+ modelValue: 'Original Text',
+ isEditing: true
+ })
+ await enterWrapper.findComponent(InputText).setValue('Saved Text')
+ await enterWrapper.findComponent(InputText).trigger('keyup.enter')
+ // Trigger blur that happens after enter
+ await enterWrapper.findComponent(InputText).trigger('blur')
+ expect(enterWrapper.emitted('edit')).toBeTruthy()
+ // @ts-expect-error fixme ts strict error
+ expect(enterWrapper.emitted('edit')[0]).toEqual(['Saved Text'])
+
+ // Test Escape key cancels changes with a fresh wrapper
+ const escapeWrapper = mountComponent({
+ modelValue: 'Original Text',
+ isEditing: true
+ })
+ await escapeWrapper.findComponent(InputText).setValue('Cancelled Text')
+ await escapeWrapper.findComponent(InputText).trigger('keyup.escape')
+ expect(escapeWrapper.emitted('cancel')).toBeTruthy()
+ expect(escapeWrapper.emitted('edit')).toBeFalsy()
+ })
})
diff --git a/src/components/common/EditableText.vue b/src/components/common/EditableText.vue
index 16510d3fd..c6fa18a8d 100644
--- a/src/components/common/EditableText.vue
+++ b/src/components/common/EditableText.vue
@@ -14,10 +14,12 @@
fluid
:pt="{
root: {
- onBlur: finishEditing
+ onBlur: finishEditing,
+ ...inputAttrs
}
}"
@keyup.enter="blurInputElement"
+ @keyup.escape="cancelEditing"
@click.stop
/>
@@ -27,21 +29,41 @@
import InputText from 'primevue/inputtext'
import { nextTick, ref, watch } from 'vue'
-const { modelValue, isEditing = false } = defineProps<{
+const {
+ modelValue,
+ isEditing = false,
+ inputAttrs = {}
+} = defineProps<{
modelValue: string
isEditing?: boolean
+ inputAttrs?: Record
}>()
-const emit = defineEmits(['update:modelValue', 'edit'])
+const emit = defineEmits(['update:modelValue', 'edit', 'cancel'])
const inputValue = ref(modelValue)
const inputRef = ref | undefined>()
+const isCanceling = ref(false)
const blurInputElement = () => {
// @ts-expect-error - $el is an internal property of the InputText component
inputRef.value?.$el.blur()
}
const finishEditing = () => {
- emit('edit', inputValue.value)
+ // Don't save if we're canceling
+ if (!isCanceling.value) {
+ emit('edit', inputValue.value)
+ }
+ isCanceling.value = false
+}
+const cancelEditing = () => {
+ // Set canceling flag to prevent blur from saving
+ isCanceling.value = true
+ // Reset to original value
+ inputValue.value = modelValue
+ // Emit cancel event
+ emit('cancel')
+ // Blur the input to exit edit mode
+ blurInputElement()
}
watch(
() => isEditing,
diff --git a/src/components/common/FormImageUpload.vue b/src/components/common/FormImageUpload.vue
index 1515289cc..efb09c900 100644
--- a/src/components/common/FormImageUpload.vue
+++ b/src/components/common/FormImageUpload.vue
@@ -3,7 +3,7 @@
+
+
+
+
+
import Button from 'primevue/button'
+import Checkbox from 'primevue/checkbox'
import Message from 'primevue/message'
+import { ref } from 'vue'
+import { useI18n } from 'vue-i18n'
import type { ConfirmationDialogType } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'
+import { useSettingStore } from '@/stores/settingStore'
const props = defineProps<{
message: string
@@ -87,14 +106,20 @@ const props = defineProps<{
hint?: string
}>()
+const { t } = useI18n()
+
const onCancel = () => useDialogStore().closeDialog()
+const doNotAskAgain = ref(false)
+
const onDeny = () => {
props.onConfirm(false)
useDialogStore().closeDialog()
}
const onConfirm = () => {
+ if (props.type === 'overwriteBlueprint' && doNotAskAgain.value)
+ void useSettingStore().set('Comfy.Workflow.WarnBlueprintOverwrite', false)
props.onConfirm(true)
useDialogStore().closeDialog()
}
diff --git a/src/components/dialog/content/ErrorDialogContent.vue b/src/components/dialog/content/ErrorDialogContent.vue
index 4f35511cf..5b732ebbc 100644
--- a/src/components/dialog/content/ErrorDialogContent.vue
+++ b/src/components/dialog/content/ErrorDialogContent.vue
@@ -105,7 +105,7 @@ const showContactSupport = async () => {
onMounted(async () => {
if (!systemStatsStore.systemStats) {
- await systemStatsStore.fetchSystemStats()
+ await systemStatsStore.refetchSystemStats()
}
try {
diff --git a/src/components/dialog/content/LoadWorkflowWarning.vue b/src/components/dialog/content/LoadWorkflowWarning.vue
index 216e54dd0..54a006bee 100644
--- a/src/components/dialog/content/LoadWorkflowWarning.vue
+++ b/src/components/dialog/content/LoadWorkflowWarning.vue
@@ -2,8 +2,8 @@
{
@@ -111,47 +105,21 @@ const uniqueNodes = computed(() => {
})
})
-const managerStateStore = useManagerStateStore()
-
// Show manager buttons unless manager is disabled
const showManagerButtons = computed(() => {
- return managerStateStore.managerUIState !== ManagerUIState.DISABLED
+ return managerState.shouldShowManagerButtons.value
})
// Only show Install All button for NEW_UI (new manager with v4 support)
const showInstallAllButton = computed(() => {
- return managerStateStore.managerUIState === ManagerUIState.NEW_UI
+ return managerState.shouldShowInstallButton.value
})
const openManager = async () => {
- const state = managerStateStore.managerUIState
-
- switch (state) {
- case ManagerUIState.DISABLED:
- useDialogService().showSettingsDialog('extension')
- break
-
- case ManagerUIState.LEGACY_UI:
- try {
- await useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility')
- } catch {
- // If legacy command doesn't exist, show toast
- const { t } = useI18n()
- useToastStore().add({
- severity: 'error',
- summary: t('g.error'),
- detail: t('manager.legacyMenuNotAvailable'),
- life: 3000
- })
- }
- break
-
- case ManagerUIState.NEW_UI:
- useDialogService().showManagerDialog({
- initialTab: ManagerTab.Missing
- })
- break
- }
+ await managerState.openManager({
+ initialTab: ManagerTab.Missing,
+ showToastOnLegacyError: true
+ })
}
diff --git a/src/components/dialog/content/MissingCoreNodesMessage.vue b/src/components/dialog/content/MissingCoreNodesMessage.vue
index 036f088b3..cf81441f1 100644
--- a/src/components/dialog/content/MissingCoreNodesMessage.vue
+++ b/src/components/dialog/content/MissingCoreNodesMessage.vue
@@ -42,9 +42,8 @@
diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue
index b8b8baa4f..ee9bea3ba 100644
--- a/src/components/graph/GraphCanvas.vue
+++ b/src/components/graph/GraphCanvas.vue
@@ -31,6 +31,34 @@
class="w-full h-full touch-none"
/>
+
+
+
+
+
+
@@ -39,19 +67,28 @@
-
+
+
diff --git a/src/components/graph/SelectionToolbox.vue b/src/components/graph/SelectionToolbox.vue
index 38f0a2c0d..8ce9353aa 100644
--- a/src/components/graph/SelectionToolbox.vue
+++ b/src/components/graph/SelectionToolbox.vue
@@ -2,12 +2,12 @@
commandStore.execute('Comfy.PublishSubgraph')"
+ >
+
+
+
+
+
+
+
diff --git a/src/components/helpcenter/HelpCenterMenuContent.vue b/src/components/helpcenter/HelpCenterMenuContent.vue
index 852b1b593..c91589407 100644
--- a/src/components/helpcenter/HelpCenterMenuContent.vue
+++ b/src/components/helpcenter/HelpCenterMenuContent.vue
@@ -142,11 +142,12 @@ import { useI18n } from 'vue-i18n'
import PuzzleIcon from '@/components/icons/PuzzleIcon.vue'
import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment'
-import { useDialogService } from '@/services/dialogService'
+import { useManagerState } from '@/composables/useManagerState'
import { type ReleaseNote } from '@/services/releaseService'
import { useCommandStore } from '@/stores/commandStore'
import { useReleaseStore } from '@/stores/releaseStore'
import { useSettingStore } from '@/stores/settingStore'
+import { ManagerTab } from '@/types/comfyManagerTypes'
import { electronAPI, isElectron } from '@/utils/envUtil'
import { formatVersionAnchor } from '@/utils/formatUtil'
@@ -191,7 +192,6 @@ const { t, locale } = useI18n()
const releaseStore = useReleaseStore()
const commandStore = useCommandStore()
const settingStore = useSettingStore()
-const dialogService = useDialogService()
// Emits
const emit = defineEmits<{
@@ -313,8 +313,11 @@ const menuItems = computed