feat: add reset-to-default for widget parameters in right side panel (#8861)

## Summary

Add per-widget and reset-all-parameters functionality to the right side
panel, allowing users to quickly revert widget values to their defaults.

## Changes

- **What**: Per-widget "Reset to default" option in the WidgetActions
overflow menu, plus a "Reset all parameters" button in each
SectionWidgets header. Defaults are derived from the InputSpec (explicit
default, then type-specific fallbacks: 0 for INT/FLOAT, false for
BOOLEAN, empty string for STRING, first option for COMBO).
- **Dependencies**: Builds on #8594 (WidgetValueStore) for reactive UI
updates after reset.

## Review Focus

- `getWidgetDefaultValue` fallback logic in `src/utils/widgetUtil.ts` —
are the type-specific defaults appropriate?
- Deep equality check (`isEqual`) for disabling the reset button when
the value already matches the default.
- Event flow: WidgetActions emits `resetToDefault` → WidgetItem forwards
→ SectionWidgets handles via `writeWidgetValue` (sets value, triggers
callback, marks canvas dirty).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8861-feat-add-reset-to-default-for-widget-parameters-in-right-side-panel-3076d73d365081d1aa08d5b965a16cf4)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
This commit is contained in:
Christian Byrne
2026-02-15 00:55:04 -08:00
committed by GitHub
parent 066a1f1f11
commit d8d0dcbf71
7 changed files with 369 additions and 9 deletions

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { cn } from '@comfyorg/tailwind-utils'
import { isEqual } from 'es-toolkit'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -14,7 +15,10 @@ import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useDialogService } from '@/services/dialogService'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useFavoritedWidgetsStore } from '@/stores/workspace/favoritedWidgetsStore'
import { getWidgetDefaultValue } from '@/utils/widgetUtil'
import type { WidgetValue } from '@/utils/widgetUtil'
const {
widget,
@@ -28,10 +32,15 @@ const {
isShownOnParents?: boolean
}>()
const emit = defineEmits<{
resetToDefault: [value: WidgetValue]
}>()
const label = defineModel<string>('label', { required: true })
const canvasStore = useCanvasStore()
const favoritedWidgetsStore = useFavoritedWidgetsStore()
const nodeDefStore = useNodeDefStore()
const dialogService = useDialogService()
const { t } = useI18n()
@@ -43,6 +52,19 @@ const isFavorited = computed(() =>
favoritedWidgetsStore.isFavorited(favoriteNode.value, widget.name)
)
const inputSpec = computed(() =>
nodeDefStore.getInputSpecForWidget(node, widget.name)
)
const defaultValue = computed(() => getWidgetDefaultValue(inputSpec.value))
const hasDefault = computed(() => defaultValue.value !== undefined)
const isCurrentValueDefault = computed(() => {
if (!hasDefault.value) return true
return isEqual(widget.value, defaultValue.value)
})
async function handleRename() {
const newLabel = await dialogService.prompt({
title: t('g.rename'),
@@ -97,6 +119,11 @@ function handleToggleFavorite() {
favoritedWidgetsStore.toggleFavorite(favoriteNode.value, widget.name)
}
function handleResetToDefault() {
if (!hasDefault.value) return
emit('resetToDefault', defaultValue.value)
}
const buttonClasses = cn([
'border-none bg-transparent',
'w-full flex items-center gap-2 rounded px-3 py-2 text-sm',
@@ -162,6 +189,21 @@ const buttonClasses = cn([
<span>{{ t('rightSidePanel.addFavorite') }}</span>
</template>
</button>
<button
v-if="hasDefault"
:class="cn(buttonClasses, isCurrentValueDefault && 'opacity-50')"
:disabled="isCurrentValueDefault"
@click="
() => {
handleResetToDefault()
close()
}
"
>
<i class="icon-[lucide--rotate-ccw] size-4" />
<span>{{ t('rightSidePanel.resetToDefault') }}</span>
</button>
</template>
</MoreButton>
</template>