Add enterAppBuilder method for skipping arrange mode (#9310)

## Summary

When already in app mode and entering builder with outputs defined, skip
the select step and go straight to arrange

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9310-Add-enterAppBuilder-method-for-skipping-arrange-mode-3156d73d36508101903ff434a2a1ac08)
by [Unito](https://www.unito.io)
This commit is contained in:
pythongosssss
2026-03-02 19:10:48 +00:00
committed by GitHub
parent 0d7dc15916
commit 31a4dce5d4
4 changed files with 106 additions and 8 deletions

View File

@@ -9,11 +9,13 @@ import { useCommandStore } from '@/stores/commandStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { cn } from '@/utils/tailwindUtil'
import { useAppMode } from '@/composables/useAppMode'
import { useAppModeStore } from '@/stores/appModeStore'
const { t } = useI18n()
const commandStore = useCommandStore()
const workspaceStore = useWorkspaceStore()
const { enableAppBuilder, setMode } = useAppMode()
const { enableAppBuilder } = useAppMode()
const { enterBuilder } = useAppModeStore()
const tooltipOptions = { showDelay: 300, hideDelay: 300 }
const isAssetsActive = computed(
@@ -23,10 +25,6 @@ const isWorkflowsActive = computed(
() => workspaceStore.sidebarTab.activeSidebarTab?.id === 'workflows'
)
function enterBuilderMode() {
setMode('builder:select')
}
function openAssets() {
void commandStore.execute('Workspace.ToggleSidebarTab.assets')
}
@@ -75,7 +73,7 @@ function openTemplates() {
size="unset"
:aria-label="t('linearMode.appModeToolbar.appBuilder')"
class="size-10 rounded-lg"
@click="enterBuilderMode"
@click="enterBuilder"
>
<i class="icon-[lucide--hammer] size-4" />
</Button>

View File

@@ -7,7 +7,8 @@ import { storeToRefs } from 'pinia'
const { t } = useI18n()
const { setMode } = useAppMode()
const { hasOutputs } = storeToRefs(useAppModeStore())
const appModeStore = useAppModeStore()
const { hasOutputs } = storeToRefs(appModeStore)
</script>
<template>
@@ -44,7 +45,7 @@ const { hasOutputs } = storeToRefs(useAppModeStore())
<Button variant="textonly" size="lg" @click="setMode('graph')">
{{ t('linearMode.welcome.backToWorkflow') }}
</Button>
<Button variant="primary" size="lg" @click="setMode('builder:select')">
<Button variant="primary" size="lg" @click="appModeStore.enterBuilder()">
<i class="icon-[lucide--hammer]" />
{{ t('linearMode.welcome.buildApp') }}
<div

View File

@@ -0,0 +1,90 @@
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, ref } from 'vue'
import type { AppMode } from '@/composables/useAppMode'
import { useAppModeStore } from '@/stores/appModeStore'
const modeRef = ref<AppMode>('graph')
const mockSetMode = vi.fn()
vi.mock('@/composables/useAppMode', () => ({
useAppMode: () => ({
mode: computed(() => modeRef.value),
setMode: mockSetMode,
isBuilderMode: computed(
() =>
modeRef.value === 'builder:select' ||
modeRef.value === 'builder:arrange'
),
isAppMode: computed(
() => modeRef.value === 'app' || modeRef.value === 'builder:arrange'
),
isSelectMode: computed(() => modeRef.value === 'builder:select'),
isArrangeMode: computed(() => modeRef.value === 'builder:arrange'),
isGraphMode: computed(
() => modeRef.value === 'graph' || modeRef.value === 'builder:select'
)
})
}))
vi.mock('@/renderer/core/canvas/canvasStore', () => ({
useCanvasStore: () => ({
getCanvas: () => ({ read_only: false })
})
}))
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
useWorkflowStore: () => ({
activeWorkflow: null
})
}))
describe('appModeStore', () => {
beforeEach(() => {
setActivePinia(createPinia())
modeRef.value = 'graph'
mockSetMode.mockClear()
})
describe('enterBuilder', () => {
it('navigates to builder:arrange when in app mode with outputs', () => {
modeRef.value = 'app'
const store = useAppModeStore()
store.selectedOutputs.push('1')
store.enterBuilder()
expect(mockSetMode).toHaveBeenCalledWith('builder:arrange')
})
it('navigates to builder:select when in app mode without outputs', () => {
modeRef.value = 'app'
const store = useAppModeStore()
store.enterBuilder()
expect(mockSetMode).toHaveBeenCalledWith('builder:select')
})
it('navigates to builder:select when in graph mode with outputs', () => {
modeRef.value = 'graph'
const store = useAppModeStore()
store.selectedOutputs.push('1')
store.enterBuilder()
expect(mockSetMode).toHaveBeenCalledWith('builder:select')
})
it('navigates to builder:select when in graph mode without outputs', () => {
modeRef.value = 'graph'
const store = useAppModeStore()
store.enterBuilder()
expect(mockSetMode).toHaveBeenCalledWith('builder:select')
})
})
})

View File

@@ -66,6 +66,14 @@ export const useAppModeStore = defineStore('appMode', () => {
(inSelect) => (getCanvas().read_only = inSelect)
)
function enterBuilder() {
setMode(
mode.value === 'app' && hasOutputs.value
? 'builder:arrange'
: 'builder:select'
)
}
async function exitBuilder() {
const workflow = workflowStore.activeWorkflow
if (workflow) workflow.dirtyLinearData = null
@@ -74,6 +82,7 @@ export const useAppModeStore = defineStore('appMode', () => {
}
return {
enterBuilder,
exitBuilder,
hasOutputs,
flushSelections,