Skip to content

Commit 02d7f91

Browse files
authored
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
1 parent eb1c66c commit 02d7f91

File tree

7 files changed

+217
-5
lines changed

7 files changed

+217
-5
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<template>
2+
<table class="comfy-modal-content comfy-table">
3+
<tbody>
4+
<tr v-for="setting in sortedSettings" :key="setting.id">
5+
<td>
6+
<span>
7+
{{ setting.name }}
8+
</span>
9+
<Chip
10+
v-if="setting.tooltip"
11+
icon="pi pi-info-circle"
12+
severity="secondary"
13+
v-tooltip="setting.tooltip"
14+
class="info-chip"
15+
/>
16+
</td>
17+
<td>
18+
<component
19+
:is="markRaw(getSettingComponent(setting))"
20+
:id="setting.id"
21+
:modelValue="settingStore.get(setting.id)"
22+
@update:modelValue="updateSetting(setting, $event)"
23+
v-bind="getSettingAttrs(setting)"
24+
/>
25+
</td>
26+
</tr>
27+
</tbody>
28+
</table>
29+
</template>
30+
31+
<script setup lang="ts">
32+
import { type Component, computed, markRaw } from 'vue'
33+
import InputText from 'primevue/inputtext'
34+
import InputNumber from 'primevue/inputnumber'
35+
import Select from 'primevue/select'
36+
import Chip from 'primevue/chip'
37+
import ToggleSwitch from 'primevue/toggleswitch'
38+
import { useSettingStore } from '@/stores/settingStore'
39+
import { SettingParams } from '@/types/settingTypes'
40+
import CustomSettingValue from '@/components/dialog/content/setting/CustomSettingValue.vue'
41+
import InputSlider from '@/components/dialog/content/setting/InputSlider.vue'
42+
43+
const settingStore = useSettingStore()
44+
const sortedSettings = computed<SettingParams[]>(() => {
45+
return Object.values(settingStore.settings)
46+
.filter((setting: SettingParams) => setting.type !== 'hidden')
47+
.sort((a, b) => a.name.localeCompare(b.name))
48+
})
49+
50+
function getSettingAttrs(setting: SettingParams) {
51+
const attrs = { ...(setting.attrs || {}) }
52+
const settingType = setting.type
53+
if (typeof settingType === 'function') {
54+
attrs['renderFunction'] = () =>
55+
settingType(
56+
setting.name,
57+
(v) => updateSetting(setting, v),
58+
settingStore.get(setting.id),
59+
setting.attrs
60+
)
61+
}
62+
switch (setting.type) {
63+
case 'combo':
64+
attrs['options'] = setting.options
65+
if (typeof setting.options[0] !== 'string') {
66+
attrs['optionLabel'] = 'text'
67+
attrs['optionValue'] = 'value'
68+
}
69+
break
70+
}
71+
72+
attrs['class'] += ' comfy-vue-setting-input'
73+
return attrs
74+
}
75+
76+
function getSettingComponent(setting: SettingParams): Component {
77+
if (typeof setting.type === 'function') {
78+
// return setting.type(
79+
// setting.name, (v) => updateSetting(setting, v), settingStore.get(setting.id), setting.attrs)
80+
return CustomSettingValue
81+
}
82+
switch (setting.type) {
83+
case 'boolean':
84+
return ToggleSwitch
85+
case 'number':
86+
return InputNumber
87+
case 'slider':
88+
return InputSlider
89+
case 'combo':
90+
return Select
91+
default:
92+
return InputText
93+
}
94+
}
95+
96+
const updateSetting = (setting: SettingParams, value: any) => {
97+
if (setting.onChange) setting.onChange(value, settingStore.get(setting.id))
98+
99+
settingStore.set(setting.id, value)
100+
}
101+
</script>
102+
103+
<style>
104+
.info-chip {
105+
background: transparent !important;
106+
}
107+
.comfy-vue-setting-input {
108+
width: 100%;
109+
}
110+
</style>
111+
112+
<style scoped>
113+
.comfy-table {
114+
width: 100%;
115+
}
116+
</style>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<template>
2+
<div ref="container"></div>
3+
</template>
4+
5+
<script setup lang="ts">
6+
import { ref, onMounted, watch } from 'vue'
7+
8+
const props = defineProps<{
9+
renderFunction: () => HTMLElement
10+
}>()
11+
12+
const container = ref<HTMLElement | null>(null)
13+
14+
function renderContent() {
15+
if (container.value) {
16+
container.value.innerHTML = ''
17+
const element = props.renderFunction()
18+
container.value.appendChild(element)
19+
}
20+
}
21+
22+
onMounted(renderContent)
23+
24+
watch(() => props.renderFunction, renderContent)
25+
</script>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<template>
2+
<div class="input-slider">
3+
<Slider
4+
:modelValue="modelValue"
5+
@update:modelValue="updateValue"
6+
class="slider-part"
7+
:class="sliderClass"
8+
v-bind="$attrs"
9+
/>
10+
<InputText
11+
:value="modelValue"
12+
@input="updateValue"
13+
class="input-part"
14+
:class="inputClass"
15+
/>
16+
</div>
17+
</template>
18+
19+
<script setup lang="ts">
20+
import InputText from 'primevue/inputtext'
21+
import Slider from 'primevue/slider'
22+
23+
const props = defineProps<{
24+
modelValue: number
25+
inputClass?: string
26+
sliderClass?: string
27+
}>()
28+
29+
const emit = defineEmits<{
30+
(e: 'update:modelValue', value: number): void
31+
}>()
32+
33+
const updateValue = (newValue: string | number) => {
34+
const numValue =
35+
typeof newValue === 'string' ? parseFloat(newValue) : newValue
36+
if (!isNaN(numValue)) {
37+
emit('update:modelValue', numValue)
38+
}
39+
}
40+
</script>
41+
42+
<style scoped>
43+
.input-slider {
44+
display: flex;
45+
align-items: center;
46+
gap: 1rem;
47+
/* Adjust this value to control space between slider and input */
48+
}
49+
50+
.slider-part {
51+
flex-grow: 1;
52+
}
53+
54+
.input-part {
55+
width: 5rem;
56+
/* Adjust this value to control input width */
57+
}
58+
</style>

src/components/sidebar/SideBarSettingsToggleIcon.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
</template>
88

99
<script setup lang="ts">
10-
import { app } from '@/scripts/app'
1110
import SideBarIcon from './SideBarIcon.vue'
11+
import { useDialogStore } from '@/stores/dialogStore'
12+
import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue'
13+
const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']
1214
15+
const dialogStore = useDialogStore()
1316
const showSetting = () => {
14-
app.ui.settings.show()
17+
dialogStore.showDialog({
18+
title: `Settings (v${frontendVersion})`,
19+
component: SettingDialogContent
20+
})
1521
}
1622
</script>

src/scripts/ui/settings.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ export class ComfySettingsDialog extends ComfyDialog<HTMLDialogElement> {
185185
}
186186

187187
this.settingsParamLookup[id] = params
188+
if (this.app.vueAppReady) {
189+
useSettingStore().settings[id] = params
190+
}
188191
this.settingsLookup[id] = {
189192
id,
190193
onChange,

src/stores/dialogStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Currently we need to bridge between legacy app code and Vue app with a Pinia store.
33

44
import { defineStore } from 'pinia'
5-
import { Component } from 'vue'
5+
import { type Component, markRaw } from 'vue'
66

77
interface DialogState {
88
isVisible: boolean
@@ -26,7 +26,7 @@ export const useDialogStore = defineStore('dialog', {
2626
props?: Record<string, any>
2727
}) {
2828
this.title = options.title
29-
this.component = options.component
29+
this.component = markRaw(options.component)
3030
this.props = options.props || {}
3131
this.isVisible = true
3232
},

src/stores/settingStore.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,26 @@
99

1010
import { app } from '@/scripts/app'
1111
import { ComfySettingsDialog } from '@/scripts/ui/settings'
12+
import { SettingParams } from '@/types/settingTypes'
1213
import { defineStore } from 'pinia'
1314

1415
interface State {
1516
settingValues: Record<string, any>
17+
settings: Record<string, SettingParams>
1618
}
1719

1820
export const useSettingStore = defineStore('setting', {
1921
state: (): State => ({
20-
settingValues: {}
22+
settingValues: {},
23+
settings: {}
2124
}),
2225
actions: {
2326
addSettings(settings: ComfySettingsDialog) {
2427
for (const id in settings.settingsLookup) {
2528
const value = settings.getSettingValue(id)
2629
this.settingValues[id] = value
2730
}
31+
this.settings = settings.settingsParamLookup
2832
},
2933

3034
set(key: string, value: any) {

0 commit comments

Comments
 (0)