mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 16:54:03 +00:00
App mode - landing/arrange screens - 4 (#9026)
## Summary Updates messaging on app mode welcome pages and adds arrange view ## Screenshots (if applicable) Build app view <img width="628" height="470" alt="image" src="https://github.com/user-attachments/assets/7f648769-1380-4093-8de9-414d05303b87" /> Run view <img width="593" height="471" alt="image" src="https://github.com/user-attachments/assets/cc81cfd0-fa89-4402-82f4-6bbdd0e7e2f9" /> Arrange view <img width="1071" height="829" alt="image" src="https://github.com/user-attachments/assets/ae8666ef-dff1-4fde-929e-89479c891261" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9026-App-mode-landing-arrange-screens-4-30d6d73d365081f4b209f34834a2ce5e) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -2941,15 +2941,27 @@
|
||||
"reuseParameters": "Reuse Parameters",
|
||||
"downloadAll": "Download All",
|
||||
"welcome": {
|
||||
"title": "Welcome to App Mode",
|
||||
"intro": "A simplified view that hides the node graph so you can focus on creating.",
|
||||
"layout": "On the left, you'll see your generated images, videos, and outputs. On the right, just the controls you need. Everything complex stays out of sight.",
|
||||
"sharing": "Sharing is easy: create your workflow, open App Mode, right-click the tab, and export. When others open your file, it launches straight into this clean view. You can share powerful workflows as simple tools without anyone needing to understand node graphs.",
|
||||
"widget": "If you want to control which settings appear, convert your top-level nodes into a subgraph, then use widget promotion in the toolbox above it to choose what's exposed."
|
||||
"title": "App Mode",
|
||||
"message": "A simplified view that hides the node graph so you can focus on creating.",
|
||||
"controls": "Your outputs appear at the bottom, your controls are on the right. Everything else stays out of the way.",
|
||||
"sharing": "Share your workflow as a simple tool anyone can use. Export it from the tab menu and when others open it, they'll see App Mode. No node graph knowledge needed.",
|
||||
"getStarted": "Click {runButton} to get started.",
|
||||
"backToWorkflow": "Back to workflow",
|
||||
"buildApp": "Build app"
|
||||
},
|
||||
"appModeToolbar": {
|
||||
"appBuilder": "App builder",
|
||||
"apps": "Apps"
|
||||
},
|
||||
"arrange": {
|
||||
"noOutputs": "No outputs added yet",
|
||||
"switchToSelect": "Switch to the 'Select' step and click on output nodes to add them here.",
|
||||
"connectAtLeastOne": "Connect {atLeastOne} output node so users can see results after running.",
|
||||
"atLeastOne": "at least one",
|
||||
"outputExamples": "Examples: 'Save Image' or 'Save Video'",
|
||||
"switchToSelectButton": "Switch to Select",
|
||||
"outputs": "Outputs",
|
||||
"resultsLabel": "Results generated from the selected output node(s) will be shown here after running this app"
|
||||
}
|
||||
},
|
||||
"missingNodes": {
|
||||
|
||||
59
src/renderer/extensions/linearMode/LinearArrange.vue
Normal file
59
src/renderer/extensions/linearMode/LinearArrange.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appModeStore = useAppModeStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="appModeStore.hasOutputs"
|
||||
role="article"
|
||||
data-testid="arrange-preview"
|
||||
class="flex flex-col items-center justify-center h-full w-3/4 gap-6 p-8 mx-auto"
|
||||
>
|
||||
<div
|
||||
class="border-warning-background border-2 border-dashed rounded-2xl w-full h-4/5 flex items-center justify-center flex-col p-12"
|
||||
>
|
||||
<p class="text-base-foreground font-bold mb-0">
|
||||
{{ t('linearMode.arrange.outputs') }}
|
||||
</p>
|
||||
<p>{{ t('linearMode.arrange.resultsLabel') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
role="article"
|
||||
data-testid="arrange-no-outputs"
|
||||
class="flex flex-col items-center justify-center h-full gap-6 p-8 w-lg mx-auto text-center"
|
||||
>
|
||||
<p class="m-0 text-base-foreground">
|
||||
{{ t('linearMode.arrange.noOutputs') }}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col gap-1 text-muted-foreground w-lg text-[14px]">
|
||||
<p class="mt-0 p-0">{{ t('linearMode.arrange.switchToSelect') }}</p>
|
||||
|
||||
<i18n-t keypath="linearMode.arrange.connectAtLeastOne" tag="div">
|
||||
<template #atLeastOne>
|
||||
<span class="italic font-bold">
|
||||
{{ t('linearMode.arrange.atLeastOne') }}
|
||||
</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<p class="mt-0 p-0">{{ t('linearMode.arrange.outputExamples') }}</p>
|
||||
</div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
@click="appModeStore.setMode('builder:select')"
|
||||
>
|
||||
{{ t('linearMode.arrange.switchToSelectButton') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -147,7 +147,7 @@ defineExpose({ runButtonClick })
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="!appModeStore.isBuilderMode"
|
||||
v-if="!appModeStore.isBuilderMode && appModeStore.hasOutputs"
|
||||
class="flex flex-col min-w-80 md:h-full"
|
||||
>
|
||||
<section
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
|
||||
import { extractWorkflowFromAsset } from '@/platform/workflow/utils/workflowExtractionUtil'
|
||||
import ImagePreview from '@/renderer/extensions/linearMode/ImagePreview.vue'
|
||||
import LinearWelcome from '@/renderer/extensions/linearMode/LinearWelcome.vue'
|
||||
import LinearArrange from '@/renderer/extensions/linearMode/LinearArrange.vue'
|
||||
import OutputHistory from '@/renderer/extensions/linearMode/OutputHistory.vue'
|
||||
// Lazy-loaded to avoid pulling THREE.js into the main bundle
|
||||
const Preview3d = () => import('@/renderer/extensions/linearMode/Preview3d.vue')
|
||||
@@ -28,11 +29,11 @@ import type { ResultItemImpl } from '@/stores/queueStore'
|
||||
import { formatDuration } from '@/utils/dateTimeUtil'
|
||||
import { collectAllNodes } from '@/utils/graphTraversalUtil'
|
||||
import { executeWidgetsCallback } from '@/utils/litegraphUtil'
|
||||
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
const { t, d } = useI18n()
|
||||
const mediaActions = useMediaAssetActions()
|
||||
const nodeOutputStore = useNodeOutputStore()
|
||||
|
||||
const appModeStore = useAppModeStore()
|
||||
const { runButtonClick } = defineProps<{
|
||||
runButtonClick?: (e: Event) => void
|
||||
mobile?: boolean
|
||||
@@ -190,6 +191,7 @@ async function rerun(e: Event) {
|
||||
v-else-if="getMediaType(selectedOutput) === '3d'"
|
||||
:model-url="selectedOutput!.url"
|
||||
/>
|
||||
<LinearArrange v-else-if="appModeStore.mode === 'builder:arrange'" />
|
||||
<LinearWelcome v-else />
|
||||
<OutputHistory
|
||||
@update-selection="
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppModeStore } from '@/stores/appModeStore'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appModeStore = useAppModeStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -11,21 +14,50 @@ const { t } = useI18n()
|
||||
class="flex flex-col items-center justify-center h-full gap-6 p-8 max-w-lg mx-auto text-center"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h2 class="text-xl font-semibold text-base-foreground">
|
||||
{{ t('linearMode.welcome.title')
|
||||
}}<sup class="ml-1 text-xs font-normal text-muted-foreground">{{
|
||||
t('g.experimental')
|
||||
}}</sup>
|
||||
<h2 class="text-3xl font-semibold text-muted-foreground">
|
||||
{{ t('linearMode.welcome.title') }}
|
||||
</h2>
|
||||
<p class="text-base text-muted-foreground">
|
||||
{{ t('linearMode.welcome.intro') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 text-xs text-muted-foreground/70 max-w-md">
|
||||
<p>{{ t('linearMode.welcome.layout') }}</p>
|
||||
<p>{{ t('linearMode.welcome.sharing') }}</p>
|
||||
<p>{{ t('linearMode.welcome.widget') }}</p>
|
||||
<div class="flex flex-col gap-3 text-muted-foreground max-w-md text-[14px]">
|
||||
<p class="mt-0">{{ t('linearMode.welcome.message') }}</p>
|
||||
<p class="mt-0">{{ t('linearMode.welcome.controls') }}</p>
|
||||
<p class="mt-0">{{ t('linearMode.welcome.sharing') }}</p>
|
||||
</div>
|
||||
<div v-if="appModeStore.hasOutputs" class="flex flex-row gap-2 text-[14px]">
|
||||
<p class="mt-0 text-base-foreground">
|
||||
<i18n-t keypath="linearMode.welcome.getStarted" tag="span">
|
||||
<template #runButton>
|
||||
<span
|
||||
class="inline-flex items-center px-3.5 py-0.5 mx-0.5 transform -translate-y-0.5 rounded bg-primary-background text-base-foreground text-xxs font-medium cursor-default"
|
||||
>
|
||||
{{ t('menu.run') }}
|
||||
</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="flex flex-row gap-2">
|
||||
<Button
|
||||
variant="textonly"
|
||||
size="lg"
|
||||
@click="appModeStore.setMode('graph')"
|
||||
>
|
||||
{{ t('linearMode.welcome.backToWorkflow') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="lg"
|
||||
@click="appModeStore.setMode('builder:select')"
|
||||
>
|
||||
<i class="icon-[lucide--hammer]" />
|
||||
{{ t('linearMode.welcome.buildApp') }}
|
||||
<div
|
||||
class="bg-base-foreground text-base-background text-xxs rounded-full absolute -top-2 -right-2 px-1"
|
||||
>
|
||||
{{ t('g.experimental') }}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user