mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-12 00:20:15 +00:00
feat/fix: App mode QA feedback 2 (#9511)
## Summary Additional fixes and updates based on testing ## Changes - **What**: - add warning to welcome screen & when sharing an app that has had all outputs removed - fix target workflow when changing mode via tab right click menu - change build app text to be conditional "edit" vs "build" depending on if an app is already defined - update empty apps sidebar tab button text to make it clearer - remove templates button from app mode (we will reintroduce this once we have app templates) - add "exit to graph" after applying default mode of node graph - update cancel button to remove item from queue if it hasn't started yet - improve scoping of jobs/outputs to the current workflow [not perfect but should be much improved] - close sidebar tabs on entering app mode - change tooltip to be under the workflow menu rather than covering the button ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9511-feat-fix-App-mode-QA-feedback-2-31b6d73d365081d59bbbc13111100d46) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -7,7 +7,6 @@ import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
import { useWorkflowTemplateSelectorDialog } from '@/composables/useWorkflowTemplateSelectorDialog'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import {
|
||||
openShareDialog,
|
||||
@@ -44,10 +43,6 @@ function openAssets() {
|
||||
function showApps() {
|
||||
void commandStore.execute('Workspace.ToggleSidebarTab.apps')
|
||||
}
|
||||
|
||||
function openTemplates() {
|
||||
useWorkflowTemplateSelectorDialog().show('sidebar')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -116,19 +111,6 @@ function openTemplates() {
|
||||
>
|
||||
<i class="icon-[lucide--panels-top-left] size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-tooltip.right="{
|
||||
value: t('sideToolbar.templates'),
|
||||
...tooltipOptions
|
||||
}"
|
||||
variant="textonly"
|
||||
size="unset"
|
||||
:aria-label="t('sideToolbar.templates')"
|
||||
class="size-10"
|
||||
@click="openTemplates"
|
||||
>
|
||||
<i class="icon-[comfy--template] size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -217,7 +217,7 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
isArrangeMode ? t('nodeHelpPage.inputs') : t('linearMode.builder.title')
|
||||
}}
|
||||
</div>
|
||||
<div class="min-h-0 flex-1 overflow-y-auto">
|
||||
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
|
||||
<DraggableList
|
||||
v-if="isArrangeMode"
|
||||
v-slot="{ dragClass }"
|
||||
@@ -254,7 +254,6 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
:label="t('nodeHelpPage.inputs')"
|
||||
enable-empty-state
|
||||
:disabled="!appModeStore.selectedInputs.length"
|
||||
class="border-b border-border-subtle"
|
||||
:tooltip="`${t('linearMode.builder.inputsDesc')}\n${t('linearMode.builder.inputsExample')}`"
|
||||
:tooltip-delay="100"
|
||||
>
|
||||
@@ -266,14 +265,10 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
</template>
|
||||
<template #empty>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
class="p-4 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddInputs')"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddInputs')"
|
||||
/>
|
||||
<DraggableList
|
||||
v-slot="{ dragClass }"
|
||||
v-model="appModeStore.selectedInputs"
|
||||
@@ -303,6 +298,12 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
/>
|
||||
</DraggableList>
|
||||
</PropertiesAccordionItem>
|
||||
<div
|
||||
v-if="isSelectInputsMode && !appModeStore.selectedInputs.length"
|
||||
class="m-4 flex flex-1 items-center justify-center rounded-lg border-2 border-dashed border-primary-background bg-primary-background/20 text-center text-sm text-primary-background"
|
||||
>
|
||||
{{ t('linearMode.builder.inputPlaceholder') }}
|
||||
</div>
|
||||
<PropertiesAccordionItem
|
||||
v-if="isSelectOutputsMode"
|
||||
:label="t('nodeHelpPage.outputs')"
|
||||
@@ -319,14 +320,10 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
</template>
|
||||
<template #empty>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
class="p-4 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddOutputs')"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
class="w-full p-4 pt-2 text-muted-foreground"
|
||||
v-text="t('linearMode.builder.promptAddOutputs')"
|
||||
/>
|
||||
<DraggableList
|
||||
v-slot="{ dragClass }"
|
||||
v-model="appModeStore.selectedOutputs"
|
||||
@@ -349,6 +346,15 @@ const renderedInputs = computed<[string, MaybeRef<BoundStyle> | undefined][]>(
|
||||
/>
|
||||
</DraggableList>
|
||||
</PropertiesAccordionItem>
|
||||
<div
|
||||
v-if="isSelectOutputsMode && !appModeStore.selectedOutputs.length"
|
||||
class="m-4 flex flex-1 flex-col items-center justify-center gap-1 rounded-lg border-2 border-dashed border-warning-background bg-warning-background/20 text-center text-sm text-warning-background"
|
||||
>
|
||||
{{ t('linearMode.builder.outputPlaceholder') }}
|
||||
<span class="font-bold">
|
||||
{{ t('linearMode.builder.outputRequiredPlaceholder') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
<Button variant="muted-textonly" size="lg" @click="$emit('viewApp')">
|
||||
{{ $t('builderToolbar.viewApp') }}
|
||||
</Button>
|
||||
<Button variant="secondary" size="lg" @click="$emit('close')">
|
||||
{{ $t('g.close') }}
|
||||
<Button variant="secondary" size="lg" @click="$emit('exitToWorkflow')">
|
||||
{{ $t('builderToolbar.exitToWorkflow') }}
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
@@ -58,5 +58,6 @@ defineProps<{
|
||||
defineEmits<{
|
||||
viewApp: []
|
||||
close: []
|
||||
exitToWorkflow: []
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -58,6 +58,6 @@ useEventListener(window, 'keydown', (e: KeyboardEvent) => {
|
||||
})
|
||||
|
||||
function onExitBuilder() {
|
||||
void appModeStore.exitBuilder()
|
||||
appModeStore.exitBuilder()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -94,7 +94,7 @@ function onEnterAppMode(close: () => void) {
|
||||
}
|
||||
|
||||
function onExitBuilder(close: () => void) {
|
||||
void appModeStore.exitBuilder()
|
||||
appModeStore.exitBuilder()
|
||||
close()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -22,6 +22,10 @@ const mockApp = vi.hoisted(() => ({
|
||||
|
||||
const mockSetMode = vi.hoisted(() => vi.fn())
|
||||
|
||||
const mockAppModeStore = vi.hoisted(() => ({
|
||||
exitBuilder: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/services/dialogService', () => ({
|
||||
useDialogService: () => mockDialogService
|
||||
}))
|
||||
@@ -42,6 +46,10 @@ vi.mock('@/composables/useAppMode', () => ({
|
||||
useAppMode: () => ({ setMode: mockSetMode })
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/appModeStore', () => ({
|
||||
useAppModeStore: () => mockAppModeStore
|
||||
}))
|
||||
|
||||
vi.mock('./DefaultViewDialogContent.vue', () => ({
|
||||
default: { name: 'MockDefaultViewDialogContent' }
|
||||
}))
|
||||
@@ -208,6 +216,16 @@ describe('useAppSetDefaultView', () => {
|
||||
expect(mockSetMode).toHaveBeenCalledWith('app')
|
||||
})
|
||||
|
||||
it('onExitToWorkflow exits builder and closes dialog', () => {
|
||||
const confirmCall = applyAndGetConfirmDialog(true)
|
||||
confirmCall.props.onExitToWorkflow()
|
||||
|
||||
expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({
|
||||
key: 'builder-default-view-applied'
|
||||
})
|
||||
expect(mockAppModeStore.exitBuilder).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('onClose closes confirmation dialog', () => {
|
||||
const confirmCall = applyAndGetConfirmDialog(true)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
import BuilderDefaultModeAppliedDialogContent from './BuilderDefaultModeAppliedDialogContent.vue'
|
||||
import DefaultViewDialogContent from './DefaultViewDialogContent.vue'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
|
||||
const DIALOG_KEY = 'builder-default-view'
|
||||
const APPLIED_DIALOG_KEY = 'builder-default-view-applied'
|
||||
@@ -16,6 +17,7 @@ export function useAppSetDefaultView() {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const dialogService = useDialogService()
|
||||
const dialogStore = useDialogStore()
|
||||
const appModeStore = useAppModeStore()
|
||||
const { setMode } = useAppMode()
|
||||
|
||||
const settingView = computed(() => dialogStore.isDialogOpen(DIALOG_KEY))
|
||||
@@ -54,6 +56,10 @@ export function useAppSetDefaultView() {
|
||||
closeAppliedDialog()
|
||||
setMode('app')
|
||||
},
|
||||
onExitToWorkflow: () => {
|
||||
closeAppliedDialog()
|
||||
appModeStore.exitBuilder()
|
||||
},
|
||||
onClose: closeAppliedDialog
|
||||
}
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
:variant="buttonVariant ?? 'textonly'"
|
||||
@click="$emit('action')"
|
||||
>
|
||||
<i v-if="buttonIcon" :class="buttonIcon" />
|
||||
{{ buttonLabel }}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -37,6 +38,7 @@ const props = defineProps<{
|
||||
title?: string
|
||||
message: string
|
||||
textClass?: string
|
||||
buttonIcon?: string
|
||||
buttonLabel?: string
|
||||
buttonVariant?: ButtonVariants['variant']
|
||||
}>()
|
||||
|
||||
@@ -49,6 +49,15 @@ function toggleLinearMode() {
|
||||
metadata: { source }
|
||||
})
|
||||
}
|
||||
|
||||
const tooltipPt = {
|
||||
root: {
|
||||
style: { transform: 'translateX(calc(50% - 16px))' }
|
||||
},
|
||||
arrow: {
|
||||
class: '!left-[16px]'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -58,12 +67,13 @@ function toggleLinearMode() {
|
||||
class="pointer-events-auto inline-flex items-center rounded-lg bg-secondary-background"
|
||||
>
|
||||
<Button
|
||||
v-tooltip="{
|
||||
v-tooltip.bottom="{
|
||||
value: canvasStore.linearMode
|
||||
? t('breadcrumbsMenu.enterNodeGraph')
|
||||
: t('breadcrumbsMenu.enterAppMode'),
|
||||
showDelay: 300,
|
||||
hideDelay: 300
|
||||
hideDelay: 300,
|
||||
pt: tooltipPt
|
||||
}"
|
||||
:aria-label="
|
||||
canvasStore.linearMode
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
class="flex flex-col border-t border-border-default px-4 py-2 text-sm wrap-break-word text-muted-foreground"
|
||||
>
|
||||
<p v-if="promptTextReal">
|
||||
<p v-if="promptTextReal" :class="preserveNewlines && 'whitespace-pre-line'">
|
||||
{{ promptTextReal }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -11,8 +11,9 @@
|
||||
import { computed, toValue } from 'vue'
|
||||
import type { MaybeRefOrGetter } from 'vue'
|
||||
|
||||
const { promptText } = defineProps<{
|
||||
const { promptText, preserveNewlines = false } = defineProps<{
|
||||
promptText?: MaybeRefOrGetter<string>
|
||||
preserveNewlines?: boolean
|
||||
}>()
|
||||
|
||||
const promptTextReal = computed(() => toValue(promptText))
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
:disabled
|
||||
variant="textonly"
|
||||
:variant="confirmVariant ?? 'textonly'"
|
||||
:class="confirmClass"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
@@ -19,13 +19,21 @@ import type { MaybeRefOrGetter } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import type { ButtonVariants } from '@/components/ui/button/button.variants'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { cancelText, confirmText, confirmClass, optionsDisabled } = defineProps<{
|
||||
const {
|
||||
cancelText,
|
||||
confirmText,
|
||||
confirmClass,
|
||||
confirmVariant,
|
||||
optionsDisabled
|
||||
} = defineProps<{
|
||||
cancelText?: string
|
||||
confirmText?: string
|
||||
confirmClass?: string
|
||||
confirmVariant?: ButtonVariants['variant']
|
||||
optionsDisabled?: MaybeRefOrGetter<boolean>
|
||||
}>()
|
||||
|
||||
|
||||
@@ -22,9 +22,8 @@
|
||||
? $t('linearMode.appModeToolbar.appsEmptyMessage')
|
||||
: `${$t('linearMode.appModeToolbar.appsEmptyMessage')}\n${$t('linearMode.appModeToolbar.appsEmptyMessageAction')}`
|
||||
"
|
||||
:button-label="
|
||||
isAppMode ? undefined : $t('linearMode.appModeToolbar.enterAppMode')
|
||||
"
|
||||
button-icon="icon-[lucide--hammer]"
|
||||
:button-label="isAppMode ? undefined : $t('linearMode.buildAnApp')"
|
||||
@action="enterAppMode"
|
||||
/>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user