mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-03 20:20:03 +00:00
feat: right side panel (#6952)
<img width="1183" height="809" alt="CleanShot 2025-11-26 at 16 01 15" src="https://github.com/user-attachments/assets/c14dc5c3-a672-4dcd-917d-14f16310188e" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6952-feat-right-side-panel-2b76d73d36508112b121c283a479f42a) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
70
src/components/rightSidePanel/parameters/SectionWidgets.vue
Normal file
70
src/components/rightSidePanel/parameters/SectionWidgets.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { provide } from 'vue'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import WidgetLegacy from '@/renderer/extensions/vueNodes/widgets/components/WidgetLegacy.vue'
|
||||
import { getComponent } from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
|
||||
|
||||
import RightPanelSection from '../layout/RightPanelSection.vue'
|
||||
|
||||
defineProps<{
|
||||
label?: string
|
||||
widgets: { widget: IBaseWidget; node: LGraphNode }[]
|
||||
}>()
|
||||
|
||||
provide('hideLayoutField', true)
|
||||
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
function getWidgetComponent(widget: IBaseWidget) {
|
||||
const component = getComponent(widget.type, widget.name)
|
||||
return component || WidgetLegacy
|
||||
}
|
||||
|
||||
function onWidgetValueChange(
|
||||
widget: IBaseWidget,
|
||||
value: string | number | boolean | object
|
||||
) {
|
||||
widget.value = value
|
||||
widget.callback?.(value)
|
||||
canvasStore.canvas?.setDirty(true, true)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RightPanelSection>
|
||||
<template #label>
|
||||
<slot name="label">
|
||||
{{ label ?? $t('rightSidePanel.inputs') }}
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<div class="space-y-4 rounded-lg bg-interface-surface px-4">
|
||||
<div
|
||||
v-for="({ widget, node }, index) in widgets"
|
||||
:key="`widget-${index}-${widget.name}`"
|
||||
class="widget-item gap-1.5 col-span-full grid grid-cols-subgrid"
|
||||
>
|
||||
<div class="min-h-8">
|
||||
<p v-if="widget.name" class="text-sm leading-8 p-0 m-0 line-clamp-1">
|
||||
{{ widget.label || widget.name }}
|
||||
</p>
|
||||
</div>
|
||||
<component
|
||||
:is="getWidgetComponent(widget)"
|
||||
:widget="widget"
|
||||
:model-value="widget.value"
|
||||
:node-id="String(node.id)"
|
||||
:node-type="node.type"
|
||||
class="col-span-1"
|
||||
@update:model-value="
|
||||
(value: string | number | boolean | object) =>
|
||||
onWidgetValueChange(widget, value)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RightPanelSection>
|
||||
</template>
|
||||
89
src/components/rightSidePanel/parameters/TabParameters.vue
Normal file
89
src/components/rightSidePanel/parameters/TabParameters.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, shallowRef } from 'vue'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import SidePanelSearch from '../layout/SidePanelSearch.vue'
|
||||
import SectionWidgets from './SectionWidgets.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
nodes: LGraphNode[]
|
||||
}>()
|
||||
|
||||
const widgetsSectionDataList = computed(() => {
|
||||
const list: {
|
||||
widgets: { node: LGraphNode; widget: IBaseWidget }[]
|
||||
node: LGraphNode
|
||||
}[] = []
|
||||
for (const node of props.nodes) {
|
||||
const shownWidgets: IBaseWidget[] = []
|
||||
for (const widget of node.widgets ?? []) {
|
||||
if (widget.options?.canvasOnly || widget.options?.hidden) continue
|
||||
shownWidgets.push(widget)
|
||||
}
|
||||
list.push({
|
||||
widgets: shownWidgets?.map((widget) => ({ node, widget })) ?? [],
|
||||
node
|
||||
})
|
||||
}
|
||||
return list
|
||||
})
|
||||
|
||||
const searchedWidgetsSectionDataList = shallowRef<
|
||||
{
|
||||
widgets: { node: LGraphNode; widget: IBaseWidget }[]
|
||||
node: LGraphNode
|
||||
}[]
|
||||
>([])
|
||||
|
||||
/**
|
||||
* Searches widgets in all selected nodes and returns search results.
|
||||
* Filters by name, localized label, type, and user-input value.
|
||||
* Performs basic tokenization of the query string.
|
||||
*/
|
||||
async function searcher(query: string) {
|
||||
if (query.trim() === '') {
|
||||
searchedWidgetsSectionDataList.value = widgetsSectionDataList.value
|
||||
return
|
||||
}
|
||||
const words = query.trim().toLowerCase().split(' ')
|
||||
searchedWidgetsSectionDataList.value = widgetsSectionDataList.value
|
||||
.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
widgets: item.widgets.filter(({ widget }) => {
|
||||
const label = widget.label?.toLowerCase()
|
||||
const name = widget.name.toLowerCase()
|
||||
const type = widget.type.toLowerCase()
|
||||
const value = widget.value?.toString().toLowerCase()
|
||||
return words.every(
|
||||
(word) =>
|
||||
name.includes(word) ||
|
||||
label?.includes(word) ||
|
||||
type?.includes(word) ||
|
||||
value?.includes(word)
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
.filter((item) => item.widgets.length > 0)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-4 flex gap-2 border-b border-interface-stroke">
|
||||
<SidePanelSearch :searcher :update-key="widgetsSectionDataList" />
|
||||
</div>
|
||||
<SectionWidgets
|
||||
v-for="section in searchedWidgetsSectionDataList"
|
||||
:key="section.node.id"
|
||||
:label="widgetsSectionDataList.length > 1 ? section.node.title : undefined"
|
||||
:widgets="section.widgets"
|
||||
:default-collapse="
|
||||
widgetsSectionDataList.length > 1 &&
|
||||
widgetsSectionDataList === searchedWidgetsSectionDataList
|
||||
"
|
||||
class="border-b border-interface-stroke"
|
||||
/>
|
||||
</template>
|
||||
Reference in New Issue
Block a user