mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
Wip control widget state migration
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
* Provides event-driven reactivity with performance optimizations
|
* Provides event-driven reactivity with performance optimizations
|
||||||
*/
|
*/
|
||||||
import { reactiveComputed } from '@vueuse/core'
|
import { reactiveComputed } from '@vueuse/core'
|
||||||
import { reactive, shallowReactive } from 'vue'
|
import { reactive, ref, shallowReactive, watch } from 'vue'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||||
import type {
|
import type {
|
||||||
@@ -20,7 +21,7 @@ import type { NodeId } from '@/renderer/core/layout/types'
|
|||||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
import { isDOMWidget } from '@/scripts/domWidget'
|
import { isDOMWidget } from '@/scripts/domWidget'
|
||||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||||
import type { WidgetValue, SafeControlWidget } from '@/types/simplifiedWidget'
|
import type { WidgetValue, ControlOptions } from '@/types/simplifiedWidget'
|
||||||
import { normalizeControlOption } from '@/types/simplifiedWidget'
|
import { normalizeControlOption } from '@/types/simplifiedWidget'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -42,14 +43,14 @@ export interface SafeWidgetData {
|
|||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
value: WidgetValue
|
value: WidgetValue
|
||||||
|
borderStyle?: string
|
||||||
|
callback?: ((value: unknown) => void) | undefined
|
||||||
|
controlWidget?: () => Ref<ControlOptions>
|
||||||
|
isDOMWidget?: boolean
|
||||||
label?: string
|
label?: string
|
||||||
options?: IWidgetOptions<unknown>
|
options?: IWidgetOptions<unknown>
|
||||||
callback?: ((value: unknown) => void) | undefined
|
|
||||||
spec?: InputSpec
|
|
||||||
slotMetadata?: WidgetSlotMetadata
|
slotMetadata?: WidgetSlotMetadata
|
||||||
isDOMWidget?: boolean
|
spec?: InputSpec
|
||||||
controlWidget?: SafeControlWidget
|
|
||||||
borderStyle?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VueNodeData {
|
export interface VueNodeData {
|
||||||
@@ -86,15 +87,19 @@ export interface GraphNodeManager {
|
|||||||
cleanup(): void
|
cleanup(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
function getControlWidget(widget: IBaseWidget): SafeControlWidget | undefined {
|
function getControlWidget(widget: IBaseWidget): (() => Ref<ControlOptions>)|undefined {
|
||||||
const cagWidget = widget.linkedWidgets?.find(
|
const cagWidget = widget.linkedWidgets?.find(
|
||||||
(w) => w.name == 'control_after_generate'
|
(w) => w.name == 'control_after_generate'
|
||||||
)
|
)
|
||||||
if (!cagWidget) return
|
if (!cagWidget) return
|
||||||
return {
|
const cagRef = ref<ControlOptions>(
|
||||||
value: normalizeControlOption(cagWidget.value),
|
normalizeControlOption(cagWidget.value)
|
||||||
update: (value) => (cagWidget.value = normalizeControlOption(value))
|
)
|
||||||
}
|
watch(cagRef, (value) => {
|
||||||
|
cagWidget.value = normalizeControlOption(value)
|
||||||
|
cagWidget.callback?.(cagWidget.value)
|
||||||
|
})
|
||||||
|
return () => cagRef
|
||||||
}
|
}
|
||||||
|
|
||||||
export function safeWidgetMapper(
|
export function safeWidgetMapper(
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import Button from 'primevue/button'
|
|||||||
import Popover from 'primevue/popover'
|
import Popover from 'primevue/popover'
|
||||||
import ToggleSwitch from 'primevue/toggleswitch'
|
import ToggleSwitch from 'primevue/toggleswitch'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { useDialogService } from '@/services/dialogService'
|
import { useDialogService } from '@/services/dialogService'
|
||||||
|
import type { ControlOptions } from '@/types/simplifiedWidget'
|
||||||
import { NumberControlMode } from '../composables/useStepperControl'
|
|
||||||
|
|
||||||
type ControlOption = {
|
type ControlOption = {
|
||||||
description: string
|
description: string
|
||||||
mode: NumberControlMode
|
mode: ControlOptions
|
||||||
icon?: string
|
icon?: string
|
||||||
text?: string
|
text?: string
|
||||||
title: string
|
title: string
|
||||||
@@ -26,33 +26,21 @@ const toggle = (event: Event) => {
|
|||||||
}
|
}
|
||||||
defineExpose({ toggle })
|
defineExpose({ toggle })
|
||||||
|
|
||||||
const ENABLE_LINK_TO_GLOBAL = false
|
|
||||||
|
|
||||||
const controlOptions: ControlOption[] = [
|
const controlOptions: ControlOption[] = [
|
||||||
...(ENABLE_LINK_TO_GLOBAL
|
|
||||||
? ([
|
|
||||||
{
|
|
||||||
mode: NumberControlMode.LINK_TO_GLOBAL,
|
|
||||||
icon: 'pi pi-link',
|
|
||||||
title: 'linkToGlobal',
|
|
||||||
description: 'linkToGlobalDesc'
|
|
||||||
} satisfies ControlOption
|
|
||||||
] as ControlOption[])
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
mode: NumberControlMode.RANDOMIZE,
|
mode: 'randomize',
|
||||||
icon: 'icon-[lucide--shuffle]',
|
icon: 'icon-[lucide--shuffle]',
|
||||||
title: 'randomize',
|
title: 'randomize',
|
||||||
description: 'randomizeDesc'
|
description: 'randomizeDesc'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
mode: NumberControlMode.INCREMENT,
|
mode: 'increment',
|
||||||
text: '+1',
|
text: '+1',
|
||||||
title: 'increment',
|
title: 'increment',
|
||||||
description: 'incrementDesc'
|
description: 'incrementDesc'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
mode: NumberControlMode.DECREMENT,
|
mode: 'decrement',
|
||||||
text: '-1',
|
text: '-1',
|
||||||
title: 'decrement',
|
title: 'decrement',
|
||||||
description: 'decrementDesc'
|
description: 'decrementDesc'
|
||||||
@@ -64,20 +52,16 @@ const widgetControlMode = computed(() =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
controlMode: NumberControlMode
|
controlWidget: () => Ref<ControlOptions>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const handleToggle = (mode: ControlOptions) => {
|
||||||
'update:controlMode': [mode: NumberControlMode]
|
if (props.controlWidget().value === mode) return
|
||||||
}>()
|
props.controlWidget().value = mode
|
||||||
|
|
||||||
const handleToggle = (mode: NumberControlMode) => {
|
|
||||||
if (props.controlMode === mode) return
|
|
||||||
emit('update:controlMode', mode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = (mode: NumberControlMode) => {
|
const isActive = (mode: ControlOptions) => {
|
||||||
return props.controlMode === mode
|
return props.controlWidget().value === mode
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditSettings = () => {
|
const handleEditSettings = () => {
|
||||||
@@ -131,10 +115,6 @@ const handleEditSettings = () => {
|
|||||||
<div
|
<div
|
||||||
class="text-sm font-normal text-base-foreground leading-tight"
|
class="text-sm font-normal text-base-foreground leading-tight"
|
||||||
>
|
>
|
||||||
<span v-if="option.mode === NumberControlMode.LINK_TO_GLOBAL">
|
|
||||||
{{ $t('widgets.numberControl.linkToGlobal') }}
|
|
||||||
<em>{{ $t('widgets.numberControl.linkToGlobalSeed') }}</em>
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ $t(`widgets.numberControl.${option.title}`) }}
|
{{ $t(`widgets.numberControl.${option.title}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
import type {
|
||||||
|
SimplifiedControlWidget,
|
||||||
|
SimplifiedWidget
|
||||||
|
} from '@/types/simplifiedWidget'
|
||||||
|
|
||||||
import WidgetInputNumberInput from './WidgetInputNumberInput.vue'
|
import WidgetInputNumberInput from './WidgetInputNumberInput.vue'
|
||||||
import WidgetInputNumberSlider from './WidgetInputNumberSlider.vue'
|
import WidgetInputNumberSlider from './WidgetInputNumberSlider.vue'
|
||||||
import WidgetInputNumberWithControl from './WidgetInputNumberWithControl.vue'
|
import WidgetWithControl from './WidgetWithControl.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
widget: SimplifiedWidget<number>
|
widget: SimplifiedWidget<number>
|
||||||
@@ -19,14 +22,22 @@ const hasControlAfterGenerate = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<WidgetWithControl
|
||||||
|
v-if="hasControlAfterGenerate"
|
||||||
|
:widget="widget as SimplifiedControlWidget<number>"
|
||||||
|
:comp="
|
||||||
|
widget.type === 'slider'
|
||||||
|
? WidgetInputNumberSlider
|
||||||
|
: WidgetInputNumberInput
|
||||||
|
"
|
||||||
|
/>
|
||||||
<component
|
<component
|
||||||
:is="
|
:is="
|
||||||
hasControlAfterGenerate
|
widget.type === 'slider'
|
||||||
? WidgetInputNumberWithControl
|
? WidgetInputNumberSlider
|
||||||
: widget.type === 'slider'
|
: WidgetInputNumberInput
|
||||||
? WidgetInputNumberSlider
|
|
||||||
: WidgetInputNumberInput
|
|
||||||
"
|
"
|
||||||
|
v-else
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
:widget="widget"
|
:widget="widget"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Button from 'primevue/button'
|
|
||||||
import { defineAsyncComponent, ref } from 'vue'
|
|
||||||
|
|
||||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
|
||||||
|
|
||||||
import type { NumberControlMode } from '../composables/useStepperControl'
|
|
||||||
import { useStepperControl } from '../composables/useStepperControl'
|
|
||||||
import WidgetInputNumberInput from './WidgetInputNumberInput.vue'
|
|
||||||
|
|
||||||
const NumberControlPopover = defineAsyncComponent(
|
|
||||||
() => import('./NumberControlPopover.vue')
|
|
||||||
)
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
widget: SimplifiedWidget<number>
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const modelValue = defineModel<number>({ default: 0 })
|
|
||||||
const popover = ref()
|
|
||||||
|
|
||||||
const handleControlChange = (newValue: number) => {
|
|
||||||
modelValue.value = newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
const { controlMode, controlButtonIcon } = useStepperControl(
|
|
||||||
modelValue,
|
|
||||||
{
|
|
||||||
...props.widget.options,
|
|
||||||
onChange: handleControlChange
|
|
||||||
},
|
|
||||||
props.widget.controlWidget!.value
|
|
||||||
)
|
|
||||||
|
|
||||||
const setControlMode = (mode: NumberControlMode) => {
|
|
||||||
controlMode.value = mode
|
|
||||||
props.widget.controlWidget!.update(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
const togglePopover = (event: Event) => {
|
|
||||||
popover.value.toggle(event)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="relative grid grid-cols-subgrid">
|
|
||||||
<WidgetInputNumberInput
|
|
||||||
v-model="modelValue"
|
|
||||||
:widget
|
|
||||||
class="grid grid-cols-subgrid col-span-2"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
size="small"
|
|
||||||
class="h-4 w-7 self-center rounded-xl bg-blue-100/30 p-0"
|
|
||||||
@click="togglePopover"
|
|
||||||
>
|
|
||||||
<i :class="`${controlButtonIcon} text-blue-100 text-xs`" />
|
|
||||||
</Button>
|
|
||||||
</WidgetInputNumberInput>
|
|
||||||
<NumberControlPopover
|
|
||||||
ref="popover"
|
|
||||||
:control-mode
|
|
||||||
@update:control-mode="setControlMode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -146,9 +146,11 @@ const outputItems = computed<DropdownItem[]>(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const allItems = computed<DropdownItem[]>(() => {
|
const allItems = computed<DropdownItem[]>(() => {
|
||||||
|
if (props.isAssetMode && assetData) {
|
||||||
|
return assetData.dropdownItems.value
|
||||||
|
}
|
||||||
return [...inputItems.value, ...outputItems.value]
|
return [...inputItems.value, ...outputItems.value]
|
||||||
})
|
})
|
||||||
|
|
||||||
const dropdownItems = computed<DropdownItem[]>(() => {
|
const dropdownItems = computed<DropdownItem[]>(() => {
|
||||||
if (props.isAssetMode) {
|
if (props.isAssetMode) {
|
||||||
return allItems.value
|
return allItems.value
|
||||||
@@ -161,7 +163,7 @@ const dropdownItems = computed<DropdownItem[]>(() => {
|
|||||||
return outputItems.value
|
return outputItems.value
|
||||||
case 'all':
|
case 'all':
|
||||||
default:
|
default:
|
||||||
return [...inputItems.value, ...outputItems.value]
|
return allItems.value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
import { computed, defineAsyncComponent, ref } from 'vue'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ControlOptions,
|
||||||
|
SimplifiedControlWidget
|
||||||
|
} from '@/types/simplifiedWidget'
|
||||||
|
|
||||||
|
const NumberControlPopover = defineAsyncComponent(
|
||||||
|
() => import('./NumberControlPopover.vue')
|
||||||
|
)
|
||||||
|
|
||||||
|
function useControlButtonIcon(controlMode: Ref<ControlOptions>) {
|
||||||
|
return computed(() => {
|
||||||
|
switch (controlMode.value) {
|
||||||
|
case 'increment':
|
||||||
|
return 'pi pi-plus'
|
||||||
|
case 'decrement':
|
||||||
|
return 'pi pi-minus'
|
||||||
|
case 'fixed':
|
||||||
|
return 'icon-[lucide--pencil-off]'
|
||||||
|
default:
|
||||||
|
return 'icon-[lucide--shuffle]'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
widget: SimplifiedControlWidget<number>
|
||||||
|
comp: unknown
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const popover = ref()
|
||||||
|
|
||||||
|
const controlButtonIcon = useControlButtonIcon(props.widget.controlWidget())
|
||||||
|
|
||||||
|
const togglePopover = (event: Event) => {
|
||||||
|
popover.value.toggle(event)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component :is="comp" v-bind="$attrs" :widget>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
size="small"
|
||||||
|
class="h-4 w-7 self-center rounded-xl bg-blue-100/30 p-0"
|
||||||
|
@click="togglePopover"
|
||||||
|
>
|
||||||
|
<i :class="`${controlButtonIcon} text-blue-100 text-xs`" />
|
||||||
|
</Button>
|
||||||
|
</component>
|
||||||
|
<NumberControlPopover ref="popover" :control-widget="widget.controlWidget" />
|
||||||
|
</template>
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
* Simplified widget interface for Vue-based node rendering
|
* Simplified widget interface for Vue-based node rendering
|
||||||
* Removes all DOM manipulation and positioning concerns
|
* Removes all DOM manipulation and positioning concerns
|
||||||
*/
|
*/
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
|
|
||||||
/** Valid types for widget values */
|
/** Valid types for widget values */
|
||||||
@@ -32,11 +34,6 @@ export function normalizeControlOption(val: WidgetValue): ControlOptions {
|
|||||||
return 'randomize'
|
return 'randomize'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SafeControlWidget = {
|
|
||||||
value: ControlOptions
|
|
||||||
update: (value: WidgetValue) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SimplifiedWidget<
|
export interface SimplifiedWidget<
|
||||||
T extends WidgetValue = WidgetValue,
|
T extends WidgetValue = WidgetValue,
|
||||||
O = Record<string, any>
|
O = Record<string, any>
|
||||||
@@ -70,5 +67,8 @@ export interface SimplifiedWidget<
|
|||||||
/** Optional input specification backing this widget */
|
/** Optional input specification backing this widget */
|
||||||
spec?: InputSpecV2
|
spec?: InputSpecV2
|
||||||
|
|
||||||
controlWidget?: SafeControlWidget
|
controlWidget?: () => Ref<ControlOptions>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
export type SimplifiedControlWidget<T extends WidgetValue = WidgetValue> =
|
||||||
|
SimplifiedWidget<T> & Required<Pick<SimplifiedWidget<T>, 'controlWidget'>>
|
||||||
|
|||||||
Reference in New Issue
Block a user