mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Adds e2e Save As tests for #10679. Refactors tests to remove getByX and other locators in tests to instead be in fixtures. ## Changes - **What**: - extract app mode fixtures - add test ids where required - add new tests ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10680-test-refactor-App-mode-Refactor-Save-As-tests-3316d73d3650815b9d49ccfbb6d54416) by [Unito](https://www.unito.io)
201 lines
5.9 KiB
Vue
201 lines
5.9 KiB
Vue
<template>
|
|
<div
|
|
class="fixed bottom-4 left-1/2 z-1000 flex -translate-x-1/2 flex-col items-center"
|
|
>
|
|
<!-- "Opens as" attachment tab -->
|
|
<BuilderOpensAsPopover
|
|
v-if="isSaved"
|
|
:is-app-mode="isAppMode"
|
|
@select="onSetDefaultView"
|
|
/>
|
|
|
|
<!-- Main toolbar -->
|
|
<nav
|
|
data-testid="builder-footer-nav"
|
|
class="flex items-center gap-2 rounded-2xl border border-border-default bg-base-background p-2 shadow-interface"
|
|
>
|
|
<Button variant="textonly" size="lg" @click="onExitBuilder">
|
|
{{ t('builderMenu.exitAppBuilder') }}
|
|
</Button>
|
|
<Button variant="secondary" size="lg" @click="onViewApp">
|
|
{{ t('builderToolbar.viewApp') }}
|
|
</Button>
|
|
<Button
|
|
variant="textonly"
|
|
size="lg"
|
|
:disabled="isFirstStep"
|
|
@click="goBack"
|
|
>
|
|
<i class="icon-[lucide--chevron-left]" aria-hidden="true" />
|
|
{{ t('g.back') }}
|
|
</Button>
|
|
<Button size="lg" :disabled="isLastStep" @click="goNext">
|
|
{{ t('g.next') }}
|
|
<i class="icon-[lucide--chevron-right]" aria-hidden="true" />
|
|
</Button>
|
|
<ConnectOutputPopover
|
|
v-if="!hasOutputs"
|
|
:is-select-active="isSelectStep"
|
|
@switch="navigateToStep('builder:outputs')"
|
|
>
|
|
<Button
|
|
size="lg"
|
|
:class="cn('w-24', disabledSaveClasses)"
|
|
data-testid="builder-save-as-button"
|
|
>
|
|
{{ isSaved ? t('g.save') : t('builderToolbar.saveAs') }}
|
|
</Button>
|
|
</ConnectOutputPopover>
|
|
<ButtonGroup
|
|
v-else-if="isSaved"
|
|
class="w-24 rounded-lg bg-secondary-background has-[[data-save-chevron]:hover]:bg-secondary-background-hover"
|
|
>
|
|
<Button
|
|
size="lg"
|
|
:disabled="!isModified"
|
|
class="flex-1"
|
|
:class="isModified ? activeSaveClasses : disabledSaveClasses"
|
|
data-testid="builder-save-button"
|
|
@click="save()"
|
|
>
|
|
{{ t('g.save') }}
|
|
</Button>
|
|
<DropdownMenuRoot>
|
|
<DropdownMenuTrigger as-child>
|
|
<Button
|
|
size="lg"
|
|
:aria-label="t('builderToolbar.saveAs')"
|
|
data-save-chevron
|
|
data-testid="builder-save-as-chevron"
|
|
class="w-6 rounded-l-none border-l border-border-default px-0"
|
|
>
|
|
<i
|
|
class="icon-[lucide--chevron-down] size-4"
|
|
aria-hidden="true"
|
|
/>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuPortal>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
:side-offset="4"
|
|
class="z-1001 min-w-36 rounded-lg border border-border-subtle bg-base-background p-1 shadow-interface"
|
|
>
|
|
<DropdownMenuItem as-child @select="saveAs()">
|
|
<Button
|
|
variant="secondary"
|
|
size="lg"
|
|
class="w-full justify-start font-normal"
|
|
>
|
|
{{ t('builderToolbar.saveAs') }}
|
|
</Button>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenuPortal>
|
|
</DropdownMenuRoot>
|
|
</ButtonGroup>
|
|
<Button
|
|
v-else
|
|
size="lg"
|
|
:class="activeSaveClasses"
|
|
data-testid="builder-save-as-button"
|
|
@click="saveAs()"
|
|
>
|
|
{{ t('builderToolbar.saveAs') }}
|
|
</Button>
|
|
</nav>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { useEventListener } from '@vueuse/core'
|
|
import { storeToRefs } from 'pinia'
|
|
import {
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuPortal,
|
|
DropdownMenuRoot,
|
|
DropdownMenuTrigger
|
|
} from 'reka-ui'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import ButtonGroup from '@/components/ui/button-group/ButtonGroup.vue'
|
|
import { useAppMode } from '@/composables/useAppMode'
|
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
|
import { useAppModeStore } from '@/stores/appModeStore'
|
|
import { useDialogStore } from '@/stores/dialogStore'
|
|
import { cn } from '@/utils/tailwindUtil'
|
|
|
|
import BuilderOpensAsPopover from './BuilderOpensAsPopover.vue'
|
|
import { setWorkflowDefaultView } from './builderViewOptions'
|
|
import ConnectOutputPopover from './ConnectOutputPopover.vue'
|
|
import { useBuilderSave } from './useBuilderSave'
|
|
import { useBuilderSteps } from './useBuilderSteps'
|
|
|
|
const { t } = useI18n()
|
|
const appModeStore = useAppModeStore()
|
|
const dialogStore = useDialogStore()
|
|
const workflowStore = useWorkflowStore()
|
|
const { isBuilderMode, setMode } = useAppMode()
|
|
const { hasOutputs } = storeToRefs(appModeStore)
|
|
const {
|
|
isFirstStep,
|
|
isLastStep,
|
|
isSelectStep,
|
|
navigateToStep,
|
|
goBack,
|
|
goNext
|
|
} = useBuilderSteps({
|
|
hasOutputs
|
|
})
|
|
const { save, saveAs } = useBuilderSave()
|
|
|
|
const isSaved = computed(
|
|
() => workflowStore.activeWorkflow?.isTemporary === false
|
|
)
|
|
|
|
const activeSaveClasses =
|
|
'bg-interface-builder-mode-button-background text-interface-builder-mode-button-foreground hover:bg-interface-builder-mode-button-background/80'
|
|
const disabledSaveClasses =
|
|
'bg-secondary-background text-muted-foreground/50 disabled:opacity-100'
|
|
|
|
const isModified = computed(
|
|
() => workflowStore.activeWorkflow?.isModified === true
|
|
)
|
|
|
|
const isAppMode = computed(
|
|
() => workflowStore.activeWorkflow?.initialMode !== 'graph'
|
|
)
|
|
|
|
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
|
|
if (
|
|
e.key === 'Escape' &&
|
|
!e.ctrlKey &&
|
|
!e.altKey &&
|
|
!e.metaKey &&
|
|
dialogStore.dialogStack.length === 0 &&
|
|
isBuilderMode.value
|
|
) {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
onExitBuilder()
|
|
}
|
|
})
|
|
|
|
function onExitBuilder() {
|
|
appModeStore.exitBuilder()
|
|
}
|
|
|
|
function onViewApp() {
|
|
setMode('app')
|
|
}
|
|
|
|
function onSetDefaultView(openAsApp: boolean) {
|
|
const workflow = workflowStore.activeWorkflow
|
|
if (!workflow) return
|
|
setWorkflowDefaultView(workflow, openAsApp)
|
|
}
|
|
</script>
|