mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 14:54:12 +00:00
Wip control widget state migration
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
* Provides event-driven reactivity with performance optimizations
|
||||
*/
|
||||
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 type {
|
||||
@@ -20,7 +21,7 @@ import type { NodeId } from '@/renderer/core/layout/types'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { isDOMWidget } from '@/scripts/domWidget'
|
||||
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 type {
|
||||
@@ -42,14 +43,14 @@ export interface SafeWidgetData {
|
||||
name: string
|
||||
type: string
|
||||
value: WidgetValue
|
||||
borderStyle?: string
|
||||
callback?: ((value: unknown) => void) | undefined
|
||||
controlWidget?: () => Ref<ControlOptions>
|
||||
isDOMWidget?: boolean
|
||||
label?: string
|
||||
options?: IWidgetOptions<unknown>
|
||||
callback?: ((value: unknown) => void) | undefined
|
||||
spec?: InputSpec
|
||||
slotMetadata?: WidgetSlotMetadata
|
||||
isDOMWidget?: boolean
|
||||
controlWidget?: SafeControlWidget
|
||||
borderStyle?: string
|
||||
spec?: InputSpec
|
||||
}
|
||||
|
||||
export interface VueNodeData {
|
||||
@@ -86,15 +87,19 @@ export interface GraphNodeManager {
|
||||
cleanup(): void
|
||||
}
|
||||
|
||||
function getControlWidget(widget: IBaseWidget): SafeControlWidget | undefined {
|
||||
function getControlWidget(widget: IBaseWidget): (() => Ref<ControlOptions>)|undefined {
|
||||
const cagWidget = widget.linkedWidgets?.find(
|
||||
(w) => w.name == 'control_after_generate'
|
||||
)
|
||||
if (!cagWidget) return
|
||||
return {
|
||||
value: normalizeControlOption(cagWidget.value),
|
||||
update: (value) => (cagWidget.value = normalizeControlOption(value))
|
||||
}
|
||||
const cagRef = ref<ControlOptions>(
|
||||
normalizeControlOption(cagWidget.value)
|
||||
)
|
||||
watch(cagRef, (value) => {
|
||||
cagWidget.value = normalizeControlOption(value)
|
||||
cagWidget.callback?.(cagWidget.value)
|
||||
})
|
||||
return () => cagRef
|
||||
}
|
||||
|
||||
export function safeWidgetMapper(
|
||||
|
||||
@@ -3,15 +3,15 @@ import Button from 'primevue/button'
|
||||
import Popover from 'primevue/popover'
|
||||
import ToggleSwitch from 'primevue/toggleswitch'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
|
||||
import { NumberControlMode } from '../composables/useStepperControl'
|
||||
import type { ControlOptions } from '@/types/simplifiedWidget'
|
||||
|
||||
type ControlOption = {
|
||||
description: string
|
||||
mode: NumberControlMode
|
||||
mode: ControlOptions
|
||||
icon?: string
|
||||
text?: string
|
||||
title: string
|
||||
@@ -26,33 +26,21 @@ const toggle = (event: Event) => {
|
||||
}
|
||||
defineExpose({ toggle })
|
||||
|
||||
const ENABLE_LINK_TO_GLOBAL = false
|
||||
|
||||
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]',
|
||||
title: 'randomize',
|
||||
description: 'randomizeDesc'
|
||||
},
|
||||
{
|
||||
mode: NumberControlMode.INCREMENT,
|
||||
mode: 'increment',
|
||||
text: '+1',
|
||||
title: 'increment',
|
||||
description: 'incrementDesc'
|
||||
},
|
||||
{
|
||||
mode: NumberControlMode.DECREMENT,
|
||||
mode: 'decrement',
|
||||
text: '-1',
|
||||
title: 'decrement',
|
||||
description: 'decrementDesc'
|
||||
@@ -64,20 +52,16 @@ const widgetControlMode = computed(() =>
|
||||
)
|
||||
|
||||
const props = defineProps<{
|
||||
controlMode: NumberControlMode
|
||||
controlWidget: () => Ref<ControlOptions>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:controlMode': [mode: NumberControlMode]
|
||||
}>()
|
||||
|
||||
const handleToggle = (mode: NumberControlMode) => {
|
||||
if (props.controlMode === mode) return
|
||||
emit('update:controlMode', mode)
|
||||
const handleToggle = (mode: ControlOptions) => {
|
||||
if (props.controlWidget().value === mode) return
|
||||
props.controlWidget().value = mode
|
||||
}
|
||||
|
||||
const isActive = (mode: NumberControlMode) => {
|
||||
return props.controlMode === mode
|
||||
const isActive = (mode: ControlOptions) => {
|
||||
return props.controlWidget().value === mode
|
||||
}
|
||||
|
||||
const handleEditSettings = () => {
|
||||
@@ -131,10 +115,6 @@ const handleEditSettings = () => {
|
||||
<div
|
||||
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>
|
||||
{{ $t(`widgets.numberControl.${option.title}`) }}
|
||||
</span>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import type {
|
||||
SimplifiedControlWidget,
|
||||
SimplifiedWidget
|
||||
} from '@/types/simplifiedWidget'
|
||||
|
||||
import WidgetInputNumberInput from './WidgetInputNumberInput.vue'
|
||||
import WidgetInputNumberSlider from './WidgetInputNumberSlider.vue'
|
||||
import WidgetInputNumberWithControl from './WidgetInputNumberWithControl.vue'
|
||||
import WidgetWithControl from './WidgetWithControl.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
widget: SimplifiedWidget<number>
|
||||
@@ -19,14 +22,22 @@ const hasControlAfterGenerate = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WidgetWithControl
|
||||
v-if="hasControlAfterGenerate"
|
||||
:widget="widget as SimplifiedControlWidget<number>"
|
||||
:comp="
|
||||
widget.type === 'slider'
|
||||
? WidgetInputNumberSlider
|
||||
: WidgetInputNumberInput
|
||||
"
|
||||
/>
|
||||
<component
|
||||
:is="
|
||||
hasControlAfterGenerate
|
||||
? WidgetInputNumberWithControl
|
||||
: widget.type === 'slider'
|
||||
? WidgetInputNumberSlider
|
||||
: WidgetInputNumberInput
|
||||
widget.type === 'slider'
|
||||
? WidgetInputNumberSlider
|
||||
: WidgetInputNumberInput
|
||||
"
|
||||
v-else
|
||||
v-model="modelValue"
|
||||
:widget="widget"
|
||||
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[]>(() => {
|
||||
if (props.isAssetMode && assetData) {
|
||||
return assetData.dropdownItems.value
|
||||
}
|
||||
return [...inputItems.value, ...outputItems.value]
|
||||
})
|
||||
|
||||
const dropdownItems = computed<DropdownItem[]>(() => {
|
||||
if (props.isAssetMode) {
|
||||
return allItems.value
|
||||
@@ -161,7 +163,7 @@ const dropdownItems = computed<DropdownItem[]>(() => {
|
||||
return outputItems.value
|
||||
case 'all':
|
||||
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
|
||||
* Removes all DOM manipulation and positioning concerns
|
||||
*/
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type { InputSpec as InputSpecV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
|
||||
/** Valid types for widget values */
|
||||
@@ -32,11 +34,6 @@ export function normalizeControlOption(val: WidgetValue): ControlOptions {
|
||||
return 'randomize'
|
||||
}
|
||||
|
||||
export type SafeControlWidget = {
|
||||
value: ControlOptions
|
||||
update: (value: WidgetValue) => void
|
||||
}
|
||||
|
||||
export interface SimplifiedWidget<
|
||||
T extends WidgetValue = WidgetValue,
|
||||
O = Record<string, any>
|
||||
@@ -70,5 +67,8 @@ export interface SimplifiedWidget<
|
||||
/** Optional input specification backing this widget */
|
||||
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