mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
fix(autocomplete): fix value persistence in DOM text widgets
Remove multiple sources of truth and async sync chains that caused values to be lost during load/switch workflow or reload page. Changes: - Remove internalValue state variable from main.ts - Update getValue/setValue to read/write DOM directly via widget.inputEl - Remove textValue reactive ref and v-model from Vue component - Remove serializeValue, onSetValue, and watch callbacks - Register textarea reference on mount, clean up on unmount - Simplify AutocompleteTextWidgetInterface Follows ComfyUI built-in addMultilineWidget pattern: - Single source of truth (DOM element value only) - Direct sync (no intermediate variables or async chains) Also adds documentation: - docs/dom-widgets/value-persistence-best-practices.md - docs/dom-widgets/README.md - Update docs/dom_widget_dev_guide.md with reference
This commit is contained in:
28
docs/dom-widgets/README.md
Normal file
28
docs/dom-widgets/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# DOM Widgets Documentation
|
||||||
|
|
||||||
|
Documentation for custom DOM widget development in ComfyUI LoRA Manager.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- **[Value Persistence Best Practices](value-persistence-best-practices.md)** - Essential guide for implementing text input DOM widgets that persist values correctly
|
||||||
|
|
||||||
|
## Key Lessons
|
||||||
|
|
||||||
|
### Common Anti-Patterns
|
||||||
|
|
||||||
|
❌ **Don't**: Create internal state variables
|
||||||
|
❌ **Don't**: Use v-model for text inputs
|
||||||
|
❌ **Don't**: Add serializeValue, onSetValue callbacks
|
||||||
|
❌ **Don't**: Watch props.widget.value
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
✅ **Do**: Use DOM element as single source of truth
|
||||||
|
✅ **Do**: Store DOM reference on widget.inputEl
|
||||||
|
✅ **Do**: Direct getValue/setValue to DOM
|
||||||
|
✅ **Do**: Clean up reference on unmount
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [DOM Widget Development Guide](../dom_widget_dev_guide.md) - Comprehensive guide for building DOM widgets
|
||||||
|
- [ComfyUI Built-in Example](../../../../code/ComfyUI_frontend/src/renderer/extensions/vueNodes/widgets/composables/useStringWidget.ts) - Reference implementation
|
||||||
144
docs/dom-widgets/value-persistence-best-practices.md
Normal file
144
docs/dom-widgets/value-persistence-best-practices.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# DOM Widget Value Persistence - Best Practices
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
DOM widgets with text inputs failed to persist values after:
|
||||||
|
- Loading workflows
|
||||||
|
- Switching workflows
|
||||||
|
- Reloading pages
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
**Multiple sources of truth** causing sync issues:
|
||||||
|
- Internal state variable (`internalValue` in main.ts)
|
||||||
|
- Vue reactive ref (`textValue` in component)
|
||||||
|
- DOM element value (actual textarea)
|
||||||
|
- ComfyUI widget value (`props.widget.value`)
|
||||||
|
|
||||||
|
**Broken sync chains:**
|
||||||
|
```
|
||||||
|
getValue() → internalValue (not actual DOM value)
|
||||||
|
setValue(v) → internalValue → onSetValue() → textValue.value (async chain)
|
||||||
|
serializeValue() → textValue.value (different from getValue)
|
||||||
|
watch() → another sync layer
|
||||||
|
```
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Follow ComfyUI built-in `addMultilineWidget` pattern:
|
||||||
|
|
||||||
|
### ✅ Do
|
||||||
|
|
||||||
|
1. **Single source of truth**: Use the DOM element directly
|
||||||
|
```typescript
|
||||||
|
// main.ts
|
||||||
|
const widget = node.addDOMWidget(name, type, container, {
|
||||||
|
getValue() {
|
||||||
|
return widget.inputEl?.value ?? ''
|
||||||
|
},
|
||||||
|
setValue(v: string) {
|
||||||
|
if (widget.inputEl) {
|
||||||
|
widget.inputEl.value = v ?? ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Register DOM reference** when component mounts
|
||||||
|
```typescript
|
||||||
|
// Vue component
|
||||||
|
onMounted(() => {
|
||||||
|
if (textareaRef.value) {
|
||||||
|
props.widget.inputEl = textareaRef.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Clean up reference** on unmount
|
||||||
|
```typescript
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (props.widget.inputEl === textareaRef.value) {
|
||||||
|
props.widget.inputEl = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Simplify interface** - only expose what's needed
|
||||||
|
```typescript
|
||||||
|
export interface MyWidgetInterface {
|
||||||
|
inputEl?: HTMLTextAreaElement
|
||||||
|
callback?: (v: string) => void
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ Don't
|
||||||
|
|
||||||
|
1. **Don't create internal state variables**
|
||||||
|
```typescript
|
||||||
|
// Wrong
|
||||||
|
let internalValue = ''
|
||||||
|
getValue() { return internalValue }
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Don't use v-model** for text inputs in DOM widgets
|
||||||
|
```html
|
||||||
|
<!-- Wrong -->
|
||||||
|
<textarea v-model="textValue" />
|
||||||
|
|
||||||
|
<!-- Right -->
|
||||||
|
<textarea ref="textareaRef" @input="onInput" />
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Don't add serializeValue** - getValue/setValue handle it
|
||||||
|
```typescript
|
||||||
|
// Wrong
|
||||||
|
props.widget.serializeValue = async () => textValue.value
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Don't add onSetValue** callback
|
||||||
|
```typescript
|
||||||
|
// Wrong
|
||||||
|
setValue(v: string) {
|
||||||
|
internalValue = v
|
||||||
|
widget.onSetValue?.(v) // Unnecessary layer
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Don't watch props.widget.value** - creates race conditions
|
||||||
|
```typescript
|
||||||
|
// Wrong
|
||||||
|
watch(() => props.widget.value, (newValue) => {
|
||||||
|
textValue.value = newValue
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Don't restore from props.widget.value** in onMounted
|
||||||
|
```typescript
|
||||||
|
// Wrong
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.widget.value) {
|
||||||
|
textValue.value = props.widget.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Principles
|
||||||
|
|
||||||
|
1. **One source of truth**: DOM element value only
|
||||||
|
2. **Direct sync**: getValue/setValue read/write DOM directly
|
||||||
|
3. **No async chains**: Eliminate intermediate variables
|
||||||
|
4. **Match built-in patterns**: Study ComfyUI's `addMultilineWidget` implementation
|
||||||
|
5. **Minimal interface**: Only expose `inputEl` and `callback`
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
- [ ] Load workflow - value restores correctly
|
||||||
|
- [ ] Switch workflow - value persists
|
||||||
|
- [ ] Reload page - value persists
|
||||||
|
- [ ] Type in widget - callback fires
|
||||||
|
- [ ] No console errors
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- ComfyUI built-in: `/home/miao/code/ComfyUI_frontend/src/renderer/extensions/vueNodes/widgets/composables/useStringWidget.ts`
|
||||||
|
- Example fix: `vue-widgets/src/components/AutocompleteTextWidget.vue` (after fix)
|
||||||
@@ -240,6 +240,8 @@ inputEl.addEventListener("change", () => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **⚠️ Important**: For Vue-based DOM widgets with text inputs, follow the [Value Persistence Best Practices](dom-widgets/value-persistence-best-practices.md) to avoid sync issues. Key takeaway: use DOM element as single source of truth, avoid internal state variables and v-model.
|
||||||
|
|
||||||
### 5.3 The Restoration Mechanism (`configure`)
|
### 5.3 The Restoration Mechanism (`configure`)
|
||||||
|
|
||||||
* **`configure(data)`**: When a Workflow is loaded, `LGraphNode` calls its `configure(data)` method.
|
* **`configure(data)`**: When a Workflow is loaded, `LGraphNode` calls its `configure(data)` method.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<div class="autocomplete-text-widget">
|
<div class="autocomplete-text-widget">
|
||||||
<textarea
|
<textarea
|
||||||
ref="textareaRef"
|
ref="textareaRef"
|
||||||
v-model="textValue"
|
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:spellcheck="spellcheck ?? false"
|
:spellcheck="spellcheck ?? false"
|
||||||
:class="['text-input', { 'vue-dom-mode': isVueDomMode }]"
|
:class="['text-input', { 'vue-dom-mode': isVueDomMode }]"
|
||||||
@@ -15,16 +14,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useAutocomplete } from '@/composables/useAutocomplete'
|
import { useAutocomplete } from '@/composables/useAutocomplete'
|
||||||
|
|
||||||
// Access LiteGraph global for initial mode detection
|
// Access LiteGraph global for initial mode detection
|
||||||
declare const LiteGraph: { vueNodesMode?: boolean } | undefined
|
declare const LiteGraph: { vueNodesMode?: boolean } | undefined
|
||||||
|
|
||||||
export interface AutocompleteTextWidgetInterface {
|
export interface AutocompleteTextWidgetInterface {
|
||||||
serializeValue?: () => Promise<string>
|
inputEl?: HTMLTextAreaElement
|
||||||
value?: string
|
|
||||||
onSetValue?: (v: string) => void
|
|
||||||
callback?: (v: string) => void
|
callback?: (v: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,20 +43,10 @@ const onModeChange = (event: Event) => {
|
|||||||
isVueDomMode.value = customEvent.detail.isVueDomMode
|
isVueDomMode.value = customEvent.detail.isVueDomMode
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// Listen for custom event dispatched by main.ts
|
|
||||||
document.addEventListener('lora-manager:vue-mode-change', onModeChange)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener('lora-manager:vue-mode-change', onModeChange)
|
|
||||||
})
|
|
||||||
|
|
||||||
const textareaRef = ref<HTMLTextAreaElement | null>(null)
|
const textareaRef = ref<HTMLTextAreaElement | null>(null)
|
||||||
const textValue = ref('')
|
|
||||||
|
|
||||||
// Initialize autocomplete with direct ref access
|
// Initialize autocomplete with direct ref access
|
||||||
const { isInitialized } = useAutocomplete(
|
useAutocomplete(
|
||||||
textareaRef,
|
textareaRef,
|
||||||
props.modelType ?? 'loras',
|
props.modelType ?? 'loras',
|
||||||
{ showPreview: props.showPreview ?? true }
|
{ showPreview: props.showPreview ?? true }
|
||||||
@@ -67,37 +54,35 @@ const { isInitialized } = useAutocomplete(
|
|||||||
|
|
||||||
const onInput = () => {
|
const onInput = () => {
|
||||||
// Call widget callback when text changes
|
// Call widget callback when text changes
|
||||||
if (typeof props.widget.callback === 'function') {
|
if (textareaRef.value && typeof props.widget.callback === 'function') {
|
||||||
props.widget.callback(textValue.value)
|
props.widget.callback(textareaRef.value.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Setup serialization
|
// Register textarea reference with widget
|
||||||
props.widget.serializeValue = async () => textValue.value
|
if (textareaRef.value) {
|
||||||
|
props.widget.inputEl = textareaRef.value
|
||||||
// Handle external value updates (e.g., loading workflow, paste)
|
|
||||||
props.widget.onSetValue = (v: string) => {
|
|
||||||
if (v !== textValue.value) {
|
|
||||||
textValue.value = v ?? ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore from saved value if exists
|
// Setup callback for input changes
|
||||||
if (props.widget.value !== undefined && props.widget.value !== null) {
|
if (textareaRef.value && typeof props.widget.callback === 'function') {
|
||||||
textValue.value = props.widget.value
|
props.widget.callback(textareaRef.value.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for custom event dispatched by main.ts
|
||||||
|
document.addEventListener('lora-manager:vue-mode-change', onModeChange)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watch for external value changes and sync
|
onUnmounted(() => {
|
||||||
watch(
|
// Clean up textarea reference
|
||||||
() => props.widget.value,
|
if (props.widget.inputEl === textareaRef.value) {
|
||||||
(newValue) => {
|
props.widget.inputEl = undefined
|
||||||
if (newValue !== undefined && newValue !== textValue.value) {
|
|
||||||
textValue.value = newValue ?? ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
// Remove event listener
|
||||||
|
document.removeEventListener('lora-manager:vue-mode-change', onModeChange)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -423,20 +423,17 @@ function createAutocompleteTextWidgetFactory(
|
|||||||
|
|
||||||
forwardMiddleMouseToCanvas(container)
|
forwardMiddleMouseToCanvas(container)
|
||||||
|
|
||||||
let internalValue = ''
|
|
||||||
|
|
||||||
const widget = node.addDOMWidget(
|
const widget = node.addDOMWidget(
|
||||||
widgetName,
|
widgetName,
|
||||||
`AUTOCOMPLETE_TEXT_${modelType.toUpperCase()}`,
|
`AUTOCOMPLETE_TEXT_${modelType.toUpperCase()}`,
|
||||||
container,
|
container,
|
||||||
{
|
{
|
||||||
getValue() {
|
getValue() {
|
||||||
return internalValue
|
return widget.inputEl?.value ?? ''
|
||||||
},
|
},
|
||||||
setValue(v: string) {
|
setValue(v: string) {
|
||||||
internalValue = v ?? ''
|
if (widget.inputEl) {
|
||||||
if (typeof widget.onSetValue === 'function') {
|
widget.inputEl.value = v ?? ''
|
||||||
widget.onSetValue(v)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
serialize: true,
|
serialize: true,
|
||||||
|
|||||||
@@ -1725,7 +1725,7 @@ to {
|
|||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-text-widget[data-v-d5278afc] {
|
.autocomplete-text-widget[data-v-82697d49] {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1734,7 +1734,7 @@ to {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Canvas mode styles (default) - matches built-in comfy-multiline-input */
|
/* Canvas mode styles (default) - matches built-in comfy-multiline-input */
|
||||||
.text-input[data-v-d5278afc] {
|
.text-input[data-v-82697d49] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--comfy-input-bg, #222);
|
background-color: var(--comfy-input-bg, #222);
|
||||||
@@ -1751,7 +1751,7 @@ to {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Vue DOM mode styles - matches built-in p-textarea in Vue DOM mode */
|
/* Vue DOM mode styles - matches built-in p-textarea in Vue DOM mode */
|
||||||
.text-input.vue-dom-mode[data-v-d5278afc] {
|
.text-input.vue-dom-mode[data-v-82697d49] {
|
||||||
background-color: var(--color-charcoal-400, #313235);
|
background-color: var(--color-charcoal-400, #313235);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@@ -1760,7 +1760,7 @@ to {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
.text-input[data-v-d5278afc]:focus {
|
.text-input[data-v-82697d49]:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}`));
|
}`));
|
||||||
document.head.appendChild(elementStyle);
|
document.head.appendChild(elementStyle);
|
||||||
@@ -1770,7 +1770,7 @@ to {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
var _a;
|
var _a;
|
||||||
import { app as app$1 } from "../../../scripts/app.js";
|
import { app } from "../../../scripts/app.js";
|
||||||
/**
|
/**
|
||||||
* @vue/shared v3.5.26
|
* @vue/shared v3.5.26
|
||||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||||
@@ -3317,7 +3317,7 @@ function onWatcherCleanup(cleanupFn, failSilently = false, owner = activeWatcher
|
|||||||
cleanups.push(cleanupFn);
|
cleanups.push(cleanupFn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function watch$1(source, cb, options = EMPTY_OBJ) {
|
function watch$2(source, cb, options = EMPTY_OBJ) {
|
||||||
const { immediate, deep, once, scheduler, augmentJob, call } = options;
|
const { immediate, deep, once, scheduler, augmentJob, call } = options;
|
||||||
const reactiveGetter = (source2) => {
|
const reactiveGetter = (source2) => {
|
||||||
if (deep) return source2;
|
if (deep) return source2;
|
||||||
@@ -3914,7 +3914,7 @@ const useSSRContext = () => {
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function watch(source, cb, options) {
|
function watch$1(source, cb, options) {
|
||||||
return doWatch(source, cb, options);
|
return doWatch(source, cb, options);
|
||||||
}
|
}
|
||||||
function doWatch(source, cb, options = EMPTY_OBJ) {
|
function doWatch(source, cb, options = EMPTY_OBJ) {
|
||||||
@@ -3964,7 +3964,7 @@ function doWatch(source, cb, options = EMPTY_OBJ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const watchHandle = watch$1(source, cb, baseWatchOptions);
|
const watchHandle = watch$2(source, cb, baseWatchOptions);
|
||||||
if (isInSSRComponentSetup) {
|
if (isInSSRComponentSetup) {
|
||||||
if (ssrCleanup) {
|
if (ssrCleanup) {
|
||||||
ssrCleanup.push(watchHandle);
|
ssrCleanup.push(watchHandle);
|
||||||
@@ -5319,12 +5319,12 @@ function createWatcher(raw, ctx, publicThis, key) {
|
|||||||
const handler = ctx[raw];
|
const handler = ctx[raw];
|
||||||
if (isFunction(handler)) {
|
if (isFunction(handler)) {
|
||||||
{
|
{
|
||||||
watch(getter, handler);
|
watch$1(getter, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isFunction(raw)) {
|
} else if (isFunction(raw)) {
|
||||||
{
|
{
|
||||||
watch(getter, raw.bind(publicThis));
|
watch$1(getter, raw.bind(publicThis));
|
||||||
}
|
}
|
||||||
} else if (isObject(raw)) {
|
} else if (isObject(raw)) {
|
||||||
if (isArray(raw)) {
|
if (isArray(raw)) {
|
||||||
@@ -5332,7 +5332,7 @@ function createWatcher(raw, ctx, publicThis, key) {
|
|||||||
} else {
|
} else {
|
||||||
const handler = isFunction(raw.handler) ? raw.handler.bind(publicThis) : ctx[raw.handler];
|
const handler = isFunction(raw.handler) ? raw.handler.bind(publicThis) : ctx[raw.handler];
|
||||||
if (isFunction(handler)) {
|
if (isFunction(handler)) {
|
||||||
watch(getter, handler, raw);
|
watch$1(getter, handler, raw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else ;
|
} else ;
|
||||||
@@ -9649,7 +9649,7 @@ function useStyle(css3) {
|
|||||||
onStyleMounted === null || onStyleMounted === void 0 || onStyleMounted(_name);
|
onStyleMounted === null || onStyleMounted === void 0 || onStyleMounted(_name);
|
||||||
}
|
}
|
||||||
if (isLoaded.value) return;
|
if (isLoaded.value) return;
|
||||||
stop = watch(cssRef, function(value) {
|
stop = watch$1(cssRef, function(value) {
|
||||||
styleRef.value.textContent = value;
|
styleRef.value.textContent = value;
|
||||||
onStyleUpdated === null || onStyleUpdated === void 0 || onStyleUpdated(_name);
|
onStyleUpdated === null || onStyleUpdated === void 0 || onStyleUpdated(_name);
|
||||||
}, {
|
}, {
|
||||||
@@ -10104,7 +10104,7 @@ function setupConfig(app2, PrimeVue2) {
|
|||||||
isThemeChanged.value = true;
|
isThemeChanged.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var stopConfigWatcher = watch(PrimeVue2.config, function(newValue, oldValue) {
|
var stopConfigWatcher = watch$1(PrimeVue2.config, function(newValue, oldValue) {
|
||||||
PrimeVueService.emit("config:change", {
|
PrimeVueService.emit("config:change", {
|
||||||
newValue,
|
newValue,
|
||||||
oldValue
|
oldValue
|
||||||
@@ -10113,7 +10113,7 @@ function setupConfig(app2, PrimeVue2) {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
deep: true
|
deep: true
|
||||||
});
|
});
|
||||||
var stopRippleWatcher = watch(function() {
|
var stopRippleWatcher = watch$1(function() {
|
||||||
return PrimeVue2.config.ripple;
|
return PrimeVue2.config.ripple;
|
||||||
}, function(newValue, oldValue) {
|
}, function(newValue, oldValue) {
|
||||||
PrimeVueService.emit("config:ripple:change", {
|
PrimeVueService.emit("config:ripple:change", {
|
||||||
@@ -10124,7 +10124,7 @@ function setupConfig(app2, PrimeVue2) {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
deep: true
|
deep: true
|
||||||
});
|
});
|
||||||
var stopThemeWatcher = watch(function() {
|
var stopThemeWatcher = watch$1(function() {
|
||||||
return PrimeVue2.config.theme;
|
return PrimeVue2.config.theme;
|
||||||
}, function(newValue, oldValue) {
|
}, function(newValue, oldValue) {
|
||||||
if (!isThemeChanged.value) {
|
if (!isThemeChanged.value) {
|
||||||
@@ -10142,7 +10142,7 @@ function setupConfig(app2, PrimeVue2) {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
deep: false
|
deep: false
|
||||||
});
|
});
|
||||||
var stopUnstyledWatcher = watch(function() {
|
var stopUnstyledWatcher = watch$1(function() {
|
||||||
return PrimeVue2.config.unstyled;
|
return PrimeVue2.config.unstyled;
|
||||||
}, function(newValue, oldValue) {
|
}, function(newValue, oldValue) {
|
||||||
if (!newValue && PrimeVue2.config.theme) {
|
if (!newValue && PrimeVue2.config.theme) {
|
||||||
@@ -10730,7 +10730,7 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener("keydown", handleKeydown);
|
document.removeEventListener("keydown", handleKeydown);
|
||||||
});
|
});
|
||||||
watch(() => props.visible, (isVisible) => {
|
watch$1(() => props.visible, (isVisible) => {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = "hidden";
|
||||||
} else {
|
} else {
|
||||||
@@ -10830,7 +10830,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
|
|||||||
searchQuery.value = "";
|
searchQuery.value = "";
|
||||||
(_a2 = searchInputRef.value) == null ? void 0 : _a2.focus();
|
(_a2 = searchInputRef.value) == null ? void 0 : _a2.focus();
|
||||||
};
|
};
|
||||||
watch(() => props.visible, (isVisible) => {
|
watch$1(() => props.visible, (isVisible) => {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
var _a2;
|
var _a2;
|
||||||
@@ -10957,7 +10957,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
|
|||||||
searchQuery.value = "";
|
searchQuery.value = "";
|
||||||
(_a2 = searchInputRef.value) == null ? void 0 : _a2.focus();
|
(_a2 = searchInputRef.value) == null ? void 0 : _a2.focus();
|
||||||
};
|
};
|
||||||
watch(() => props.visible, (isVisible) => {
|
watch$1(() => props.visible, (isVisible) => {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
var _a2;
|
var _a2;
|
||||||
@@ -11192,7 +11192,7 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
const newSelected = props.selected.includes(key) ? props.selected.filter((k2) => k2 !== key) : [...props.selected, key];
|
const newSelected = props.selected.includes(key) ? props.selected.filter((k2) => k2 !== key) : [...props.selected, key];
|
||||||
emit2("update:selected", newSelected);
|
emit2("update:selected", newSelected);
|
||||||
};
|
};
|
||||||
watch(() => props.visible, (isVisible) => {
|
watch$1(() => props.visible, (isVisible) => {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
searchQuery.value = "";
|
searchQuery.value = "";
|
||||||
expandedKeys.value = /* @__PURE__ */ new Set();
|
expandedKeys.value = /* @__PURE__ */ new Set();
|
||||||
@@ -11452,7 +11452,7 @@ function useLoraPoolState(widget) {
|
|||||||
refreshPreview();
|
refreshPreview();
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
watch([
|
watch$1([
|
||||||
selectedBaseModels,
|
selectedBaseModels,
|
||||||
includeTags,
|
includeTags,
|
||||||
excludeTags,
|
excludeTags,
|
||||||
@@ -12473,7 +12473,7 @@ function useLoraRandomizerState(widget) {
|
|||||||
};
|
};
|
||||||
const isClipStrengthDisabled = computed(() => !useCustomClipRange.value);
|
const isClipStrengthDisabled = computed(() => !useCustomClipRange.value);
|
||||||
const isRecommendedStrengthEnabled = computed(() => useRecommendedStrength.value);
|
const isRecommendedStrengthEnabled = computed(() => useRecommendedStrength.value);
|
||||||
watch([
|
watch$1([
|
||||||
countMode,
|
countMode,
|
||||||
countFixed,
|
countFixed,
|
||||||
countMin,
|
countMin,
|
||||||
@@ -12598,7 +12598,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|||||||
state.rollMode.value = "fixed";
|
state.rollMode.value = "fixed";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
watch(() => {
|
watch$1(() => {
|
||||||
var _a2, _b;
|
var _a2, _b;
|
||||||
return (_b = (_a2 = props.node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "loras")) == null ? void 0 : _b.value;
|
return (_b = (_a2 = props.node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "loras")) == null ? void 0 : _b.value;
|
||||||
}, (newVal) => {
|
}, (newVal) => {
|
||||||
@@ -13000,12 +13000,12 @@ function useLoraCyclerState(widget) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const isClipStrengthDisabled = computed(() => !useCustomClipRange.value);
|
const isClipStrengthDisabled = computed(() => !useCustomClipRange.value);
|
||||||
watch(modelStrength, (newValue) => {
|
watch$1(modelStrength, (newValue) => {
|
||||||
if (!useCustomClipRange.value) {
|
if (!useCustomClipRange.value) {
|
||||||
clipStrength.value = newValue;
|
clipStrength.value = newValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
watch([
|
watch$1([
|
||||||
currentIndex,
|
currentIndex,
|
||||||
totalCount,
|
totalCount,
|
||||||
poolConfigHash,
|
poolConfigHash,
|
||||||
@@ -13396,67 +13396,53 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|||||||
const customEvent = event;
|
const customEvent = event;
|
||||||
isVueDomMode.value = customEvent.detail.isVueDomMode;
|
isVueDomMode.value = customEvent.detail.isVueDomMode;
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener("lora-manager:vue-mode-change", onModeChange);
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener("lora-manager:vue-mode-change", onModeChange);
|
|
||||||
});
|
|
||||||
const textareaRef = ref(null);
|
const textareaRef = ref(null);
|
||||||
const textValue = ref("");
|
|
||||||
useAutocomplete(
|
useAutocomplete(
|
||||||
textareaRef,
|
textareaRef,
|
||||||
props.modelType ?? "loras",
|
props.modelType ?? "loras",
|
||||||
{ showPreview: props.showPreview ?? true }
|
{ showPreview: props.showPreview ?? true }
|
||||||
);
|
);
|
||||||
const onInput = () => {
|
const onInput = () => {
|
||||||
if (typeof props.widget.callback === "function") {
|
if (textareaRef.value && typeof props.widget.callback === "function") {
|
||||||
props.widget.callback(textValue.value);
|
props.widget.callback(textareaRef.value.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
props.widget.serializeValue = async () => textValue.value;
|
if (textareaRef.value) {
|
||||||
props.widget.onSetValue = (v2) => {
|
props.widget.inputEl = textareaRef.value;
|
||||||
if (v2 !== textValue.value) {
|
|
||||||
textValue.value = v2 ?? "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (props.widget.value !== void 0 && props.widget.value !== null) {
|
|
||||||
textValue.value = props.widget.value;
|
|
||||||
}
|
}
|
||||||
|
if (textareaRef.value && typeof props.widget.callback === "function") {
|
||||||
|
props.widget.callback(textareaRef.value.value);
|
||||||
|
}
|
||||||
|
document.addEventListener("lora-manager:vue-mode-change", onModeChange);
|
||||||
});
|
});
|
||||||
watch(
|
onUnmounted(() => {
|
||||||
() => props.widget.value,
|
if (props.widget.inputEl === textareaRef.value) {
|
||||||
(newValue) => {
|
props.widget.inputEl = void 0;
|
||||||
if (newValue !== void 0 && newValue !== textValue.value) {
|
|
||||||
textValue.value = newValue ?? "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
document.removeEventListener("lora-manager:vue-mode-change", onModeChange);
|
||||||
|
});
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1, [
|
return openBlock(), createElementBlock("div", _hoisted_1, [
|
||||||
withDirectives(createBaseVNode("textarea", {
|
createBaseVNode("textarea", {
|
||||||
ref_key: "textareaRef",
|
ref_key: "textareaRef",
|
||||||
ref: textareaRef,
|
ref: textareaRef,
|
||||||
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => textValue.value = $event),
|
|
||||||
placeholder: __props.placeholder,
|
placeholder: __props.placeholder,
|
||||||
spellcheck: __props.spellcheck ?? false,
|
spellcheck: __props.spellcheck ?? false,
|
||||||
class: normalizeClass(["text-input", { "vue-dom-mode": isVueDomMode.value }]),
|
class: normalizeClass(["text-input", { "vue-dom-mode": isVueDomMode.value }]),
|
||||||
onInput,
|
onInput,
|
||||||
onPointerdown: _cache[1] || (_cache[1] = withModifiers(() => {
|
onPointerdown: _cache[0] || (_cache[0] = withModifiers(() => {
|
||||||
}, ["stop"])),
|
}, ["stop"])),
|
||||||
onPointermove: _cache[2] || (_cache[2] = withModifiers(() => {
|
onPointermove: _cache[1] || (_cache[1] = withModifiers(() => {
|
||||||
}, ["stop"])),
|
}, ["stop"])),
|
||||||
onPointerup: _cache[3] || (_cache[3] = withModifiers(() => {
|
onPointerup: _cache[2] || (_cache[2] = withModifiers(() => {
|
||||||
}, ["stop"]))
|
}, ["stop"]))
|
||||||
}, null, 42, _hoisted_2), [
|
}, null, 42, _hoisted_2)
|
||||||
[vModelText, textValue.value]
|
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d5278afc"]]);
|
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-82697d49"]]);
|
||||||
const LORA_PROVIDER_NODE_TYPES$1 = [
|
const LORA_PROVIDER_NODE_TYPES$1 = [
|
||||||
"Lora Stacker (LoraManager)",
|
"Lora Stacker (LoraManager)",
|
||||||
"Lora Randomizer (LoraManager)",
|
"Lora Randomizer (LoraManager)",
|
||||||
@@ -13519,7 +13505,7 @@ function createModeChangeCallback(node, updateDownstreamLoaders2, nodeSpecificCa
|
|||||||
updateDownstreamLoaders2(node);
|
updateDownstreamLoaders2(node);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const app = {};
|
const app$1 = {};
|
||||||
const ROOT_GRAPH_ID = "root";
|
const ROOT_GRAPH_ID = "root";
|
||||||
const LORA_PROVIDER_NODE_TYPES = [
|
const LORA_PROVIDER_NODE_TYPES = [
|
||||||
"Lora Stacker (LoraManager)",
|
"Lora Stacker (LoraManager)",
|
||||||
@@ -13539,7 +13525,7 @@ function getNodeGraphId(node) {
|
|||||||
if (!node) {
|
if (!node) {
|
||||||
return ROOT_GRAPH_ID;
|
return ROOT_GRAPH_ID;
|
||||||
}
|
}
|
||||||
return getGraphId(node.graph || app.graph);
|
return getGraphId(node.graph || app$1.graph);
|
||||||
}
|
}
|
||||||
function getNodeReference(node) {
|
function getNodeReference(node) {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -13740,7 +13726,7 @@ function forwardMiddleMouseToCanvas(container) {
|
|||||||
if (!container) return;
|
if (!container) return;
|
||||||
container.addEventListener("pointerdown", (event) => {
|
container.addEventListener("pointerdown", (event) => {
|
||||||
if (event.button === 1) {
|
if (event.button === 1) {
|
||||||
const canvas = app$1.canvas;
|
const canvas = app.canvas;
|
||||||
if (canvas && typeof canvas.processMouseDown === "function") {
|
if (canvas && typeof canvas.processMouseDown === "function") {
|
||||||
canvas.processMouseDown(event);
|
canvas.processMouseDown(event);
|
||||||
}
|
}
|
||||||
@@ -13748,7 +13734,7 @@ function forwardMiddleMouseToCanvas(container) {
|
|||||||
});
|
});
|
||||||
container.addEventListener("pointermove", (event) => {
|
container.addEventListener("pointermove", (event) => {
|
||||||
if ((event.buttons & 4) === 4) {
|
if ((event.buttons & 4) === 4) {
|
||||||
const canvas = app$1.canvas;
|
const canvas = app.canvas;
|
||||||
if (canvas && typeof canvas.processMouseMove === "function") {
|
if (canvas && typeof canvas.processMouseMove === "function") {
|
||||||
canvas.processMouseMove(event);
|
canvas.processMouseMove(event);
|
||||||
}
|
}
|
||||||
@@ -13756,7 +13742,7 @@ function forwardMiddleMouseToCanvas(container) {
|
|||||||
});
|
});
|
||||||
container.addEventListener("pointerup", (event) => {
|
container.addEventListener("pointerup", (event) => {
|
||||||
if (event.button === 1) {
|
if (event.button === 1) {
|
||||||
const canvas = app$1.canvas;
|
const canvas = app.canvas;
|
||||||
if (canvas && typeof canvas.processMouseUp === "function") {
|
if (canvas && typeof canvas.processMouseUp === "function") {
|
||||||
canvas.processMouseUp(event);
|
canvas.processMouseUp(event);
|
||||||
}
|
}
|
||||||
@@ -14015,11 +14001,11 @@ function createJsonDisplayWidget(node) {
|
|||||||
const widgetInputOptions = /* @__PURE__ */ new Map();
|
const widgetInputOptions = /* @__PURE__ */ new Map();
|
||||||
const initVueDomModeListener = () => {
|
const initVueDomModeListener = () => {
|
||||||
var _a2, _b;
|
var _a2, _b;
|
||||||
if ((_b = (_a2 = app$1.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.addEventListener) {
|
if ((_b = (_a2 = app.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.addEventListener) {
|
||||||
app$1.ui.settings.addEventListener("Comfy.VueNodes.Enabled.change", () => {
|
app.ui.settings.addEventListener("Comfy.VueNodes.Enabled.change", () => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
var _a3, _b2, _c;
|
var _a3, _b2, _c;
|
||||||
const isVueDomMode = ((_c = (_b2 = (_a3 = app$1.ui) == null ? void 0 : _a3.settings) == null ? void 0 : _b2.getSettingValue) == null ? void 0 : _c.call(_b2, "Comfy.VueNodes.Enabled")) ?? false;
|
const isVueDomMode = ((_c = (_b2 = (_a3 = app.ui) == null ? void 0 : _a3.settings) == null ? void 0 : _b2.getSettingValue) == null ? void 0 : _c.call(_b2, "Comfy.VueNodes.Enabled")) ?? false;
|
||||||
document.dispatchEvent(new CustomEvent("lora-manager:vue-mode-change", {
|
document.dispatchEvent(new CustomEvent("lora-manager:vue-mode-change", {
|
||||||
detail: { isVueDomMode }
|
detail: { isVueDomMode }
|
||||||
}));
|
}));
|
||||||
@@ -14027,12 +14013,12 @@ const initVueDomModeListener = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if ((_a = app$1.ui) == null ? void 0 : _a.settings) {
|
if ((_a = app.ui) == null ? void 0 : _a.settings) {
|
||||||
initVueDomModeListener();
|
initVueDomModeListener();
|
||||||
} else {
|
} else {
|
||||||
const checkAppReady = setInterval(() => {
|
const checkAppReady = setInterval(() => {
|
||||||
var _a2;
|
var _a2;
|
||||||
if ((_a2 = app$1.ui) == null ? void 0 : _a2.settings) {
|
if ((_a2 = app.ui) == null ? void 0 : _a2.settings) {
|
||||||
initVueDomModeListener();
|
initVueDomModeListener();
|
||||||
clearInterval(checkAppReady);
|
clearInterval(checkAppReady);
|
||||||
}
|
}
|
||||||
@@ -14048,19 +14034,18 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
|
|||||||
container.style.flexDirection = "column";
|
container.style.flexDirection = "column";
|
||||||
container.style.overflow = "hidden";
|
container.style.overflow = "hidden";
|
||||||
forwardMiddleMouseToCanvas(container);
|
forwardMiddleMouseToCanvas(container);
|
||||||
let internalValue = "";
|
|
||||||
const widget = node.addDOMWidget(
|
const widget = node.addDOMWidget(
|
||||||
widgetName,
|
widgetName,
|
||||||
`AUTOCOMPLETE_TEXT_${modelType.toUpperCase()}`,
|
`AUTOCOMPLETE_TEXT_${modelType.toUpperCase()}`,
|
||||||
container,
|
container,
|
||||||
{
|
{
|
||||||
getValue() {
|
getValue() {
|
||||||
return internalValue;
|
var _a3;
|
||||||
|
return ((_a3 = widget.inputEl) == null ? void 0 : _a3.value) ?? "";
|
||||||
},
|
},
|
||||||
setValue(v2) {
|
setValue(v2) {
|
||||||
internalValue = v2 ?? "";
|
if (widget.inputEl) {
|
||||||
if (typeof widget.onSetValue === "function") {
|
widget.inputEl.value = v2 ?? "";
|
||||||
widget.onSetValue(v2);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
serialize: true,
|
serialize: true,
|
||||||
@@ -14074,7 +14059,7 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const spellcheck = ((_c = (_b = (_a2 = app$1.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.getSettingValue) == null ? void 0 : _c.call(_b, "Comfy.TextareaWidget.Spellcheck")) ?? false;
|
const spellcheck = ((_c = (_b = (_a2 = app.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.getSettingValue) == null ? void 0 : _c.call(_b, "Comfy.TextareaWidget.Spellcheck")) ?? false;
|
||||||
const vueApp = createApp(AutocompleteTextWidget, {
|
const vueApp = createApp(AutocompleteTextWidget, {
|
||||||
widget,
|
widget,
|
||||||
node,
|
node,
|
||||||
@@ -14099,7 +14084,7 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
|
|||||||
};
|
};
|
||||||
return { widget };
|
return { widget };
|
||||||
}
|
}
|
||||||
app$1.registerExtension({
|
app.registerExtension({
|
||||||
name: "LoraManager.VueWidgets",
|
name: "LoraManager.VueWidgets",
|
||||||
getCustomWidgets() {
|
getCustomWidgets() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user