Migrate settings dialog to Vue (#335)

* Basic setting dialog

* Add custom setting value render

* handle combo options

* Add input slider

* 100% width for select dropdown
This commit is contained in:
Chenlei Hu
2024-08-07 14:01:43 -04:00
committed by GitHub
parent eb1c66c90a
commit 02d7f91e9e
7 changed files with 217 additions and 5 deletions

View File

@@ -0,0 +1,116 @@
<template>
<table class="comfy-modal-content comfy-table">
<tbody>
<tr v-for="setting in sortedSettings" :key="setting.id">
<td>
<span>
{{ setting.name }}
</span>
<Chip
v-if="setting.tooltip"
icon="pi pi-info-circle"
severity="secondary"
v-tooltip="setting.tooltip"
class="info-chip"
/>
</td>
<td>
<component
:is="markRaw(getSettingComponent(setting))"
:id="setting.id"
:modelValue="settingStore.get(setting.id)"
@update:modelValue="updateSetting(setting, $event)"
v-bind="getSettingAttrs(setting)"
/>
</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
import { type Component, computed, markRaw } from 'vue'
import InputText from 'primevue/inputtext'
import InputNumber from 'primevue/inputnumber'
import Select from 'primevue/select'
import Chip from 'primevue/chip'
import ToggleSwitch from 'primevue/toggleswitch'
import { useSettingStore } from '@/stores/settingStore'
import { SettingParams } from '@/types/settingTypes'
import CustomSettingValue from '@/components/dialog/content/setting/CustomSettingValue.vue'
import InputSlider from '@/components/dialog/content/setting/InputSlider.vue'
const settingStore = useSettingStore()
const sortedSettings = computed<SettingParams[]>(() => {
return Object.values(settingStore.settings)
.filter((setting: SettingParams) => setting.type !== 'hidden')
.sort((a, b) => a.name.localeCompare(b.name))
})
function getSettingAttrs(setting: SettingParams) {
const attrs = { ...(setting.attrs || {}) }
const settingType = setting.type
if (typeof settingType === 'function') {
attrs['renderFunction'] = () =>
settingType(
setting.name,
(v) => updateSetting(setting, v),
settingStore.get(setting.id),
setting.attrs
)
}
switch (setting.type) {
case 'combo':
attrs['options'] = setting.options
if (typeof setting.options[0] !== 'string') {
attrs['optionLabel'] = 'text'
attrs['optionValue'] = 'value'
}
break
}
attrs['class'] += ' comfy-vue-setting-input'
return attrs
}
function getSettingComponent(setting: SettingParams): Component {
if (typeof setting.type === 'function') {
// return setting.type(
// setting.name, (v) => updateSetting(setting, v), settingStore.get(setting.id), setting.attrs)
return CustomSettingValue
}
switch (setting.type) {
case 'boolean':
return ToggleSwitch
case 'number':
return InputNumber
case 'slider':
return InputSlider
case 'combo':
return Select
default:
return InputText
}
}
const updateSetting = (setting: SettingParams, value: any) => {
if (setting.onChange) setting.onChange(value, settingStore.get(setting.id))
settingStore.set(setting.id, value)
}
</script>
<style>
.info-chip {
background: transparent !important;
}
.comfy-vue-setting-input {
width: 100%;
}
</style>
<style scoped>
.comfy-table {
width: 100%;
}
</style>

View File

@@ -0,0 +1,25 @@
<template>
<div ref="container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
const props = defineProps<{
renderFunction: () => HTMLElement
}>()
const container = ref<HTMLElement | null>(null)
function renderContent() {
if (container.value) {
container.value.innerHTML = ''
const element = props.renderFunction()
container.value.appendChild(element)
}
}
onMounted(renderContent)
watch(() => props.renderFunction, renderContent)
</script>

View File

@@ -0,0 +1,58 @@
<template>
<div class="input-slider">
<Slider
:modelValue="modelValue"
@update:modelValue="updateValue"
class="slider-part"
:class="sliderClass"
v-bind="$attrs"
/>
<InputText
:value="modelValue"
@input="updateValue"
class="input-part"
:class="inputClass"
/>
</div>
</template>
<script setup lang="ts">
import InputText from 'primevue/inputtext'
import Slider from 'primevue/slider'
const props = defineProps<{
modelValue: number
inputClass?: string
sliderClass?: string
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: number): void
}>()
const updateValue = (newValue: string | number) => {
const numValue =
typeof newValue === 'string' ? parseFloat(newValue) : newValue
if (!isNaN(numValue)) {
emit('update:modelValue', numValue)
}
}
</script>
<style scoped>
.input-slider {
display: flex;
align-items: center;
gap: 1rem;
/* Adjust this value to control space between slider and input */
}
.slider-part {
flex-grow: 1;
}
.input-part {
width: 5rem;
/* Adjust this value to control input width */
}
</style>

View File

@@ -7,10 +7,16 @@
</template>
<script setup lang="ts">
import { app } from '@/scripts/app'
import SideBarIcon from './SideBarIcon.vue'
import { useDialogStore } from '@/stores/dialogStore'
import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue'
const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']
const dialogStore = useDialogStore()
const showSetting = () => {
app.ui.settings.show()
dialogStore.showDialog({
title: `Settings (v${frontendVersion})`,
component: SettingDialogContent
})
}
</script>

View File

@@ -185,6 +185,9 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
}
this.settingsParamLookup[id] = params
if (this.app.vueAppReady) {
useSettingStore().settings[id] = params
}
this.settingsLookup[id] = {
id,
onChange,

View File

@@ -2,7 +2,7 @@
// Currently we need to bridge between legacy app code and Vue app with a Pinia store.
import { defineStore } from 'pinia'
import { Component } from 'vue'
import { type Component, markRaw } from 'vue'
interface DialogState {
isVisible: boolean
@@ -26,7 +26,7 @@ export const useDialogStore = defineStore('dialog', {
props?: Record<string, any>
}) {
this.title = options.title
this.component = options.component
this.component = markRaw(options.component)
this.props = options.props || {}
this.isVisible = true
},

View File

@@ -9,15 +9,18 @@
import { app } from '@/scripts/app'
import { ComfySettingsDialog } from '@/scripts/ui/settings'
import { SettingParams } from '@/types/settingTypes'
import { defineStore } from 'pinia'
interface State {
settingValues: Record<string, any>
settings: Record<string, SettingParams>
}
export const useSettingStore = defineStore('setting', {
state: (): State => ({
settingValues: {}
settingValues: {},
settings: {}
}),
actions: {
addSettings(settings: ComfySettingsDialog) {
@@ -25,6 +28,7 @@ export const useSettingStore = defineStore('setting', {
const value = settings.getSettingValue(id)
this.settingValues[id] = value
}
this.settings = settings.settingsParamLookup
},
set(key: string, value: any) {