Files
ComfyUI_frontend/vue-widget-implementation-plan.md
bymyself eca48a8787 [docs] Add Vue node system architecture and implementation plans
- Implementation plan for Vue-based node rendering system
- Migration strategy from canvas to Vue components
- Widget system integration documentation
2025-07-05 03:08:18 -07:00

5.5 KiB

Vue Widget Implementation Plan

Overview

This document outlines the implementation plan for creating simplified Vue-based widget components that will work with the new Vue node rendering system.

Directory Structure

src/components/graph/vueWidgets/     # New directory alongside existing widgets
├── WidgetButton.vue
├── WidgetInputText.vue
├── WidgetSelect.vue
├── WidgetColorPicker.vue
├── WidgetMultiSelect.vue
├── WidgetSelectButton.vue
├── WidgetSlider.vue
├── WidgetTextarea.vue
├── WidgetToggleSwitch.vue
├── WidgetChart.vue
├── WidgetImage.vue
├── WidgetImageCompare.vue
├── WidgetGalleria.vue
├── WidgetFileUpload.vue
└── WidgetTreeSelect.vue

Prop Filtering Utility

Create a utility file for prop filtering:

// src/utils/widgetPropFilter.ts

// Props to exclude based on the widget interface specifications
export const STANDARD_EXCLUDED_PROPS = ['style', 'class', 'dt', 'pt', 'ptOptions', 'unstyled'] as const

export const INPUT_EXCLUDED_PROPS = [
  ...STANDARD_EXCLUDED_PROPS,
  'inputClass',
  'inputStyle'
] as const

export const PANEL_EXCLUDED_PROPS = [
  ...STANDARD_EXCLUDED_PROPS,
  'panelClass',
  'panelStyle',
  'overlayClass'
] as const

export const IMAGE_EXCLUDED_PROPS = [
  ...STANDARD_EXCLUDED_PROPS,
  'imageClass',
  'imageStyle'
] as const

export const GALLERIA_EXCLUDED_PROPS = [
  ...STANDARD_EXCLUDED_PROPS,
  'thumbnailsPosition',
  'verticalThumbnailViewPortHeight',
  'indicatorsPosition',
  'maskClass',
  'containerStyle',
  'containerClass',
  'galleriaClass'
] as const

// Utility function to filter props
export function filterWidgetProps<T extends Record<string, any>>(
  props: T | undefined,
  excludeList: readonly string[]
): Partial<T> {
  if (!props) return {}
  
  const filtered: Record<string, any> = {}
  for (const [key, value] of Object.entries(props)) {
    if (!excludeList.includes(key)) {
      filtered[key] = value
    }
  }
  return filtered as Partial<T>
}

Component Template Pattern

Each widget follows this structure without style tags:

<template>
  <div class="flex flex-col gap-1">
    <label v-if="widget.name" class="text-sm opacity-80">{{ widget.name }}</label>
    <ComponentName
      v-model="value"
      v-bind="filteredProps"
      :disabled="readonly"
    />
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import ComponentName from 'primevue/componentname'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import { filterWidgetProps, STANDARD_EXCLUDED_PROPS } from '@/utils/widgetPropFilter'

const value = defineModel<T>({ required: true })

const props = defineProps<{
  widget: SimplifiedWidget<T>
  readonly?: boolean
}>()

const filteredProps = computed(() => 
  filterWidgetProps(props.widget.options, STANDARD_EXCLUDED_PROPS)
)
</script>

Complete Widget List (All 15 from the spec)

Input Components:

  1. WidgetInputText - Single line text input
  2. WidgetTextarea - Multiline text input
  3. WidgetSlider - Numeric range slider
  4. WidgetToggleSwitch - Boolean on/off switch

Selection Components:

  1. WidgetSelect - Dropdown selection
  2. WidgetMultiSelect - Multiple item selection
  3. WidgetSelectButton - Button group selection
  4. WidgetTreeSelect - Hierarchical selection

Visual Components:

  1. WidgetColorPicker - Color picker
  2. WidgetImage - Single image display
  3. WidgetImageCompare - Before/after image comparison
  4. WidgetGalleria - Image gallery/carousel
  5. WidgetChart - Chart display (using Chart.js)

Action Components:

  1. WidgetButton - Button with actions
  2. WidgetFileUpload - File upload interface

Implementation Details

Each widget will:

  • Use defineModel for two-way binding
  • Import the appropriate PrimeVue component
  • Use the prop filtering utility with the correct exclusion list
  • Apply minimal Tailwind classes for basic layout
  • Handle the readonly prop to disable interaction when needed

Widget Type Mapping

When integrating with the node system, we'll need to map widget types to components:

// src/components/graph/vueWidgets/widgetRegistry.ts
export enum WidgetType {
  BUTTON = 'BUTTON',
  STRING = 'STRING',
  INT = 'INT',
  FLOAT = 'FLOAT',
  NUMBER = 'NUMBER',
  BOOLEAN = 'BOOLEAN',
  COMBO = 'COMBO',
  COLOR = 'COLOR',
  MULTISELECT = 'MULTISELECT',
  SELECTBUTTON = 'SELECTBUTTON',
  SLIDER = 'SLIDER',
  TEXTAREA = 'TEXTAREA',
  TOGGLESWITCH = 'TOGGLESWITCH',
  CHART = 'CHART',
  IMAGE = 'IMAGE',
  IMAGECOMPARE = 'IMAGECOMPARE',
  GALLERIA = 'GALLERIA',
  FILEUPLOAD = 'FILEUPLOAD',
  TREESELECT = 'TREESELECT'
}

export const widgetTypeToComponent: Record<string, Component> = {
  [WidgetType.BUTTON]: WidgetButton,
  [WidgetType.STRING]: WidgetInputText,
  [WidgetType.INT]: WidgetSlider,
  [WidgetType.FLOAT]: WidgetSlider,
  [WidgetType.NUMBER]: WidgetSlider, // For compatibility
  [WidgetType.BOOLEAN]: WidgetToggleSwitch,
  [WidgetType.COMBO]: WidgetSelect,
  // ... other mappings
}

SimplifiedWidget Interface

Based on the simplification plan:

interface SimplifiedWidget<T = any> {
  name: string
  type: string
  value: T
  options?: Record<string, any>  // Contains filtered PrimeVue props
  callback?: (value: T) => void
}

Key Differences from Current System

  • No DOM manipulation or positioning logic
  • No visibility/zoom management
  • No canvas interaction
  • Purely focused on value display and user input
  • Relies on parent Vue components for layout and positioning