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:
pythongosssss
2026-02-23 18:16:22 +00:00
committed by GitHub
parent 0f4bceafdd
commit b29dbec916
5 changed files with 125 additions and 20 deletions

View File

@@ -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": {

View 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>

View File

@@ -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

View File

@@ -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="

View File

@@ -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>