feat: show vue node settings toggle and show banner to switch back (#6382)
## Summary - When switching from litegraph to vue nodes, show a toast that opens settings to switch back - Show a toggle in settings under Vue nodes for now called Modern Node Design (Vue Nodes) - Plan to update all the nomenclature as we get closer to GA ## Screenshots (if applicable) <img width="1480" height="911" alt="image" src="https://github.com/user-attachments/assets/fa3a34b9-2631-4175-917a-aa319f9fe415" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6382-feat-show-vue-node-settings-toggle-and-show-banner-to-switch-back-29b6d73d365081d29252c83349f06c0a) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 107 KiB |
48
src/components/toast/VueNodesMigrationToast.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<Toast
|
||||||
|
group="vue-nodes-migration"
|
||||||
|
position="bottom-center"
|
||||||
|
class="w-auto"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<template #message>
|
||||||
|
<div class="flex flex-auto items-center justify-between gap-4">
|
||||||
|
<span class="whitespace-nowrap">{{
|
||||||
|
t('vueNodesMigration.message')
|
||||||
|
}}</span>
|
||||||
|
<Button
|
||||||
|
class="whitespace-nowrap"
|
||||||
|
size="small"
|
||||||
|
:label="t('vueNodesMigration.button')"
|
||||||
|
text
|
||||||
|
@click="handleOpenSettings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Toast>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useToast } from 'primevue'
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
import Toast from 'primevue/toast'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useVueNodesMigrationDismissed } from '@/composables/useVueNodesMigrationDismissed'
|
||||||
|
import { useDialogService } from '@/services/dialogService'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const toast = useToast()
|
||||||
|
const dialogService = useDialogService()
|
||||||
|
const isDismissed = useVueNodesMigrationDismissed()
|
||||||
|
|
||||||
|
const handleOpenSettings = () => {
|
||||||
|
dialogService.showSettingsDialog()
|
||||||
|
toast.removeGroup('vue-nodes-migration')
|
||||||
|
isDismissed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
isDismissed.value = true
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -4,6 +4,7 @@ import { shallowRef, watch } from 'vue'
|
|||||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||||
import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||||
|
import { useVueNodesMigrationDismissed } from '@/composables/useVueNodesMigrationDismissed'
|
||||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||||
@@ -11,6 +12,7 @@ import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
|||||||
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
|
import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync'
|
||||||
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
|
import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale'
|
||||||
import { app as comfyApp } from '@/scripts/app'
|
import { app as comfyApp } from '@/scripts/app'
|
||||||
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
|
|
||||||
function useVueNodeLifecycleIndividual() {
|
function useVueNodeLifecycleIndividual() {
|
||||||
const canvasStore = useCanvasStore()
|
const canvasStore = useCanvasStore()
|
||||||
@@ -21,6 +23,8 @@ function useVueNodeLifecycleIndividual() {
|
|||||||
|
|
||||||
const { startSync } = useLayoutSync()
|
const { startSync } = useLayoutSync()
|
||||||
|
|
||||||
|
const isVueNodeToastDismissed = useVueNodesMigrationDismissed()
|
||||||
|
|
||||||
const initializeNodeManager = () => {
|
const initializeNodeManager = () => {
|
||||||
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
|
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
|
||||||
const activeGraph = comfyApp.canvas?.graph
|
const activeGraph = comfyApp.canvas?.graph
|
||||||
@@ -75,11 +79,20 @@ function useVueNodeLifecycleIndividual() {
|
|||||||
// Watch for Vue nodes enabled state changes
|
// Watch for Vue nodes enabled state changes
|
||||||
watch(
|
watch(
|
||||||
() => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph),
|
() => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph),
|
||||||
(enabled) => {
|
(enabled, wasEnabled) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
initializeNodeManager()
|
initializeNodeManager()
|
||||||
ensureCorrectLayoutScale()
|
ensureCorrectLayoutScale()
|
||||||
|
|
||||||
|
if (!wasEnabled && !isVueNodeToastDismissed.value) {
|
||||||
|
useToastStore().add({
|
||||||
|
group: 'vue-nodes-migration',
|
||||||
|
severity: 'info',
|
||||||
|
life: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
comfyApp.canvas?.setDirty(true, true)
|
||||||
disposeNodeManagerAndSyncs()
|
disposeNodeManagerAndSyncs()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
8
src/composables/useVueNodesMigrationDismissed.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { createSharedComposable, useLocalStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
// Browser storage events don't fire in the same tab, so separate
|
||||||
|
// useLocalStorage() calls create isolated reactive refs. Use shared
|
||||||
|
// composable to ensure all components use the same ref instance.
|
||||||
|
export const useVueNodesMigrationDismissed = createSharedComposable(() =>
|
||||||
|
useLocalStorage('comfy.vueNodesMigration.dismissed', false)
|
||||||
|
)
|
||||||
@@ -1820,8 +1820,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vueNodesMigration": {
|
||||||
|
"message": "Prefer the classic node design?",
|
||||||
|
"button": "Open Settings"
|
||||||
|
},
|
||||||
"vueNodesBanner": {
|
"vueNodesBanner": {
|
||||||
"message": "Nodes just got a new look and feel",
|
"message": "Nodes just got a new look and feel",
|
||||||
"tryItOut": "Try it out"
|
"tryItOut": "Try it out"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1057,17 +1057,17 @@ export const CORE_SETTINGS: SettingParams[] = [
|
|||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
id: 'Comfy.VueNodes.Enabled',
|
id: 'Comfy.VueNodes.Enabled',
|
||||||
name: 'Enable Vue node rendering (hidden)',
|
name: 'Modern Node Design (Vue Nodes)',
|
||||||
type: 'hidden',
|
type: 'boolean',
|
||||||
tooltip:
|
tooltip:
|
||||||
'Render nodes as Vue components instead of canvas. Hidden; toggle via Experimental keybinding.',
|
'Modern: DOM-based rendering with enhanced interactivity, native browser features, and updated visual design. Classic: Traditional canvas rendering.',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
experimental: true,
|
experimental: true,
|
||||||
versionAdded: '1.27.1'
|
versionAdded: '1.27.1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'Comfy.VueNodes.AutoScaleLayout',
|
id: 'Comfy.VueNodes.AutoScaleLayout',
|
||||||
name: 'Auto-scale layout for Vue nodes',
|
name: 'Auto-scale layout (Vue nodes)',
|
||||||
tooltip:
|
tooltip:
|
||||||
'Automatically scale node positions when switching to Vue rendering to prevent overlap',
|
'Automatically scale node positions when switching to Vue rendering to prevent overlap',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
<GlobalToast />
|
<GlobalToast />
|
||||||
<RerouteMigrationToast />
|
<RerouteMigrationToast />
|
||||||
|
<VueNodesMigrationToast />
|
||||||
<UnloadWindowConfirmDialog v-if="!isElectron()" />
|
<UnloadWindowConfirmDialog v-if="!isElectron()" />
|
||||||
<MenuHamburger />
|
<MenuHamburger />
|
||||||
</template>
|
</template>
|
||||||
@@ -40,6 +41,7 @@ import UnloadWindowConfirmDialog from '@/components/dialog/UnloadWindowConfirmDi
|
|||||||
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
|
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
|
||||||
import GlobalToast from '@/components/toast/GlobalToast.vue'
|
import GlobalToast from '@/components/toast/GlobalToast.vue'
|
||||||
import RerouteMigrationToast from '@/components/toast/RerouteMigrationToast.vue'
|
import RerouteMigrationToast from '@/components/toast/RerouteMigrationToast.vue'
|
||||||
|
import VueNodesMigrationToast from '@/components/toast/VueNodesMigrationToast.vue'
|
||||||
import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'
|
import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle'
|
||||||
import { useCoreCommands } from '@/composables/useCoreCommands'
|
import { useCoreCommands } from '@/composables/useCoreCommands'
|
||||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||||
|
|||||||