mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 09:14:35 +02:00
Merge pull request #638 from Gegell/settings-improvements
Settings Tab Improvements
This commit is contained in:
+62
-8
@@ -215,19 +215,31 @@ div#extras_scale_to_tab div.form{
|
||||
}
|
||||
|
||||
#settings{
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: var(--layout-gap);
|
||||
}
|
||||
|
||||
#settings > div{
|
||||
#settings div {
|
||||
border: none;
|
||||
margin-left: 10em;
|
||||
}
|
||||
|
||||
#settings > div.tab-content {
|
||||
flex: 100000 0 75%;
|
||||
}
|
||||
|
||||
#settings > div.tab-content > div{
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#settings > div.tab-nav{
|
||||
float: left;
|
||||
display: block;
|
||||
margin-left: 0;
|
||||
width: 14em;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, .3em minmax(10em, 1fr));
|
||||
flex: 1 0 auto;
|
||||
width: 11em;
|
||||
align-self: flex-start;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
#settings > div.tab-nav button{
|
||||
@@ -235,10 +247,52 @@ div#extras_scale_to_tab div.form{
|
||||
border: none;
|
||||
text-align: left;
|
||||
white-space: initial;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#settings > div.tab-nav > #settings_show_all_pages {
|
||||
padding: var(--size-2) var(--size-4);
|
||||
}
|
||||
|
||||
#settings .block.gradio-checkbox {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#settings .dirtyable {
|
||||
display: grid;
|
||||
grid-template-columns: .3em auto;
|
||||
gap: .5em;
|
||||
}
|
||||
|
||||
#settings .dirtyable.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#settings .modification-indicator {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1em !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#settings .modification-indicator:disabled {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#settings .modification-indicator.unsaved {
|
||||
background: var(--color-accent-soft);
|
||||
}
|
||||
|
||||
#settings .modification-indicator.changed {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
#settings .modification-indicator.changed.unsaved {
|
||||
background-image: linear-gradient(var(--color-accent) 25%, var(--color-accent-soft) 75%);
|
||||
}
|
||||
|
||||
#settings_result{
|
||||
height: 1.4em;
|
||||
margin: 0 1.2em;
|
||||
}
|
||||
|
||||
|
||||
+116
-19
@@ -199,6 +199,32 @@ function register_drag_drop() {
|
||||
});
|
||||
}
|
||||
|
||||
opts = {}
|
||||
opts_metadata = {}
|
||||
function updateOpts(json_string){
|
||||
let settings_data = JSON.parse(json_string)
|
||||
opts = settings_data.values
|
||||
opts_metadata = settings_data.metadata
|
||||
opts_tabs = {}
|
||||
Object.entries(opts_metadata).forEach(([opt, meta]) => {
|
||||
opts_tabs[meta.tab_name] ||= {}
|
||||
let unsaved = (opts_tabs[meta.tab_name].unsaved_keys ||= new Set())
|
||||
if (!meta.is_stored) unsaved.add(opt)
|
||||
})
|
||||
}
|
||||
|
||||
function showAllSettings() {
|
||||
// Try to ensure that the show all settings tab is opened by clicking on its tab button
|
||||
let tab_dirty_indicator = gradioApp().getElementById('modification_indicator_show_all_pages');
|
||||
if (tab_dirty_indicator && tab_dirty_indicator.nextSibling) {
|
||||
tab_dirty_indicator.nextSibling.click();
|
||||
}
|
||||
gradioApp().querySelectorAll('#settings > .tab-content > .tabitem').forEach((elem) => {
|
||||
if (elem.id === 'settings_tab_licenses' || elem.id === 'settings_show_all_pages') return;
|
||||
elem.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
onAfterUiUpdate(() => {
|
||||
sort_ui_elements();
|
||||
if (Object.keys(opts).length !== 0) return;
|
||||
@@ -207,7 +233,7 @@ onAfterUiUpdate(() => {
|
||||
json_elem.parentElement.style.display = 'none';
|
||||
const textarea = json_elem.querySelector('textarea');
|
||||
const jsdata = textarea.value;
|
||||
opts = JSON.parse(jsdata);
|
||||
updateOpts(jsdata);
|
||||
executeCallbacks(optionsChangedCallbacks);
|
||||
register_drag_drop();
|
||||
|
||||
@@ -216,7 +242,7 @@ onAfterUiUpdate(() => {
|
||||
const valueProp = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
|
||||
const oldValue = valueProp.get.call(textarea);
|
||||
valueProp.set.call(textarea, newValue);
|
||||
if (oldValue !== newValue) opts = JSON.parse(textarea.value);
|
||||
if (oldValue !== newValue) updateOpts(textarea.value);
|
||||
executeCallbacks(optionsChangedCallbacks);
|
||||
},
|
||||
get() {
|
||||
@@ -240,28 +266,19 @@ onAfterUiUpdate(() => {
|
||||
registerTextarea('txt2img_neg_prompt', 'txt2img_negative_token_counter', 'txt2img_negative_token_button');
|
||||
registerTextarea('img2img_prompt', 'img2img_token_counter', 'img2img_token_button');
|
||||
registerTextarea('img2img_neg_prompt', 'img2img_negative_token_counter', 'img2img_negative_token_button');
|
||||
const show_all_pages = gradioApp().getElementById('settings_show_all_pages');
|
||||
const settings_tabs = gradioApp().querySelector('#settings div');
|
||||
if (show_all_pages && settings_tabs) {
|
||||
settings_tabs.appendChild(show_all_pages);
|
||||
show_all_pages.onclick = () => {
|
||||
gradioApp().querySelectorAll('#settings > div').forEach((elem) => {
|
||||
elem.style.display = 'block';
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const settings_search = gradioApp().querySelectorAll('#settings_search > label > textarea')[0];
|
||||
settings_search.oninput = (e) => {
|
||||
setTimeout(() => {
|
||||
gradioApp().querySelectorAll('#settings > div').forEach((elem) => {
|
||||
if (elem.id === 'settings_tab_licenses') return;
|
||||
elem.style.display = 'block';
|
||||
});
|
||||
showAllSettings();
|
||||
gradioApp().querySelectorAll('#tab_settings .tabitem').forEach((section) => {
|
||||
section.querySelectorAll('.block').forEach((setting) => {
|
||||
section.querySelectorAll('.dirtyable').forEach((setting) => {
|
||||
const visible = setting.innerText.toLowerCase().includes(e.target.value.toLowerCase()) || setting.id.toLowerCase().includes(e.target.value.toLowerCase());
|
||||
if (setting.parentElement.classList.contains('form')) setting.parentElement.style.display = visible ? 'flex' : 'none';
|
||||
else setting.style.display = visible ? 'block' : 'none';
|
||||
if (!visible) {
|
||||
setting.style.display = 'none'
|
||||
} else {
|
||||
setting.style.removeProperty('display')
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 50);
|
||||
@@ -280,6 +297,86 @@ onOptionsChanged(() => {
|
||||
}
|
||||
});
|
||||
|
||||
onOptionsChanged(function(){
|
||||
let setting_elems = gradioApp().querySelectorAll('#settings [id^="setting_"]')
|
||||
setting_elems.forEach(function(elem){
|
||||
setting_name = elem.id.replace("setting_", "")
|
||||
markIfModified(setting_name, opts[setting_name])
|
||||
})
|
||||
})
|
||||
|
||||
onUiLoaded(function(){
|
||||
let tab_nav_element = gradioApp().querySelector('#settings > .tab-nav')
|
||||
let tab_nav_buttons = gradioApp().querySelectorAll('#settings > .tab-nav > button')
|
||||
let tab_elements = gradioApp().querySelectorAll('#settings > div:not(.tab-nav)')
|
||||
|
||||
// HACK Add mutation observer to keep gradio from closing setting tabs when showing all pages
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
const show_all_pages_dummy = gradioApp().getElementById('settings_show_all_pages')
|
||||
if (show_all_pages_dummy.style.display == "none")
|
||||
return;
|
||||
function mutation_on_style(mut) {
|
||||
return mut.type === 'attributes' && mut.attributeName === 'style'
|
||||
}
|
||||
if (mutations.some(mutation_on_style)) {
|
||||
showAllSettings();
|
||||
}
|
||||
})
|
||||
|
||||
// Add a wrapper for the tab content (everything but the tab nav)
|
||||
const tab_content_wrapper = document.createElement('div')
|
||||
tab_content_wrapper.className = "tab-content"
|
||||
tab_nav_element.parentElement.insertBefore(tab_content_wrapper, tab_nav_element.nextSibling)
|
||||
|
||||
tab_elements.forEach(function(elem, index){
|
||||
// Move the modification indicator to the toplevel tab button
|
||||
let tab_name = elem.id.replace("settings_", "")
|
||||
let indicator = gradioApp().getElementById("modification_indicator_"+tab_name)
|
||||
tab_nav_element.insertBefore(indicator, tab_nav_buttons[index])
|
||||
|
||||
// Add the tab content to the wrapper
|
||||
tab_content_wrapper.appendChild(elem)
|
||||
|
||||
// Add the mutation observer to the tab element
|
||||
observer.observe(elem, { attributes: true, attributeFilter: ['style'] })
|
||||
})
|
||||
})
|
||||
|
||||
function markIfModified(setting_name, value) {
|
||||
let elem = gradioApp().getElementById("modification_indicator_"+setting_name)
|
||||
if(elem == null) return;
|
||||
// Use JSON.stringify to compare nested objects (e.g. arrays for checkbox-groups)
|
||||
let previous_value_json = JSON.stringify(opts[setting_name])
|
||||
let changed_value = JSON.stringify(value) != previous_value_json
|
||||
if (changed_value) {
|
||||
elem.title = `Click to revert to previous value: ${previous_value_json}`
|
||||
}
|
||||
|
||||
is_unsaved = !opts_metadata[setting_name].is_stored
|
||||
if (is_unsaved) {
|
||||
elem.title = 'Default value (not saved to config)';
|
||||
}
|
||||
elem.disabled = !(is_unsaved || changed_value)
|
||||
elem.classList.toggle('changed', changed_value)
|
||||
elem.classList.toggle('unsaved', is_unsaved)
|
||||
|
||||
let tab_name = opts_metadata[setting_name].tab_name
|
||||
let changed_items = (opts_tabs[tab_name].changed ||= new Set())
|
||||
changed_value ? changed_items.add(setting_name) : changed_items.delete(setting_name)
|
||||
let unsaved = opts_tabs[tab_name].unsaved_keys
|
||||
|
||||
// Set the indicator on the tab nav element
|
||||
let tab_nav_indicator = gradioApp().getElementById('modification_indicator_'+tab_name)
|
||||
tab_nav_indicator.disabled = (changed_items.size == 0) && (unsaved.size == 0)
|
||||
tab_nav_indicator.title = '';
|
||||
tab_nav_indicator.classList.toggle('changed', changed_items.size > 0)
|
||||
tab_nav_indicator.classList.toggle('unsaved', unsaved.size > 0)
|
||||
if (changed_items.size > 0)
|
||||
tab_nav_indicator.title += `Click to reset ${changed_items.size} unapplied change${changed_items.size > 1 ? 's': ''} in this tab.\n`
|
||||
if (unsaved.size > 0)
|
||||
tab_nav_indicator.title += `${unsaved.size} new default value${unsaved.size > 1 ? 's':''} (not yet saved).`;
|
||||
}
|
||||
|
||||
let txt2img_textarea;
|
||||
let img2img_textarea;
|
||||
const wait_time = 800;
|
||||
|
||||
+8
-2
@@ -426,7 +426,7 @@ options_templates.update(options_section(('ui', "User interface"), {
|
||||
"ui_extra_networks_tab_reorder": OptionInfo("Checkpoints, Lora, LyCORIS, Textual Inversion, Hypernetworks", "Extra networks tab order"),
|
||||
}))
|
||||
|
||||
options_templates.update(options_section(('ui', "Live previews"), {
|
||||
options_templates.update(options_section(('live-preview', "Live previews"), {
|
||||
"show_progressbar": OptionInfo(True, "Show progressbar"),
|
||||
"live_previews_enable": OptionInfo(True, "Show live previews of the created image"),
|
||||
"show_progress_grid": OptionInfo(True, "Show previews of all images generated in a batch as a grid"),
|
||||
@@ -644,7 +644,13 @@ class Options:
|
||||
|
||||
def dumpjson(self):
|
||||
d = {k: self.data.get(k, self.data_labels.get(k).default) for k in self.data_labels.keys()}
|
||||
return json.dumps(d)
|
||||
metadata = {
|
||||
k: {
|
||||
"is_stored": k in self.data,
|
||||
"tab_name": v.section[0]
|
||||
} for k, v in self.data_labels.items()
|
||||
}
|
||||
return json.dumps({"values": d, "metadata": metadata})
|
||||
|
||||
def add_option(self, key, info):
|
||||
self.data_labels[key] = info
|
||||
|
||||
+41
-2
@@ -1274,6 +1274,18 @@ def create_ui():
|
||||
else:
|
||||
raise ValueError(f'bad options item type: {t} for key {key}')
|
||||
elem_id = f"setting_{key}"
|
||||
|
||||
if not is_quicksettings:
|
||||
# FIXME: the visibility is only copied once initially, so if the user changes it, it won't be updated
|
||||
# This can probably be fixed by using a proper wrapper element
|
||||
dirtyable_setting = gr.Group(elem_classes="dirtyable", visible=(args or {}).get("visible", True))
|
||||
dirtyable_setting.__enter__()
|
||||
dirty_indicator = gr.Button(
|
||||
"",
|
||||
elem_classes="modification-indicator",
|
||||
elem_id="modification_indicator_" + key
|
||||
)
|
||||
|
||||
if info.refresh is not None:
|
||||
if is_quicksettings:
|
||||
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
|
||||
@@ -1284,8 +1296,28 @@ def create_ui():
|
||||
create_refresh_button(res, info.refresh, info.component_args, f"refresh_{key}")
|
||||
else:
|
||||
res = comp(label=info.label, value=fun(), elem_id=elem_id, **(args or {}))
|
||||
|
||||
if not is_quicksettings:
|
||||
res.change(fn=None, inputs=res, _js=f'(val) => markIfModified("{key}", val)')
|
||||
dirty_indicator.click(fn=lambda: getattr(opts, key), outputs=res, show_progress=False)
|
||||
dirtyable_setting.__exit__()
|
||||
|
||||
return res
|
||||
|
||||
def create_dirty_indicator(key, keys_to_reset, **kwargs):
|
||||
def get_opt_values():
|
||||
return [getattr(opts, _key) for _key in keys_to_reset]
|
||||
|
||||
elements_to_reset = [component_dict[_key] for _key in keys_to_reset]
|
||||
indicator = gr.Button(
|
||||
"",
|
||||
elem_classes="modification-indicator",
|
||||
elem_id="modification_indicator_" + key,
|
||||
**kwargs
|
||||
)
|
||||
indicator.click(fn=get_opt_values, outputs=elements_to_reset, show_progress=False)
|
||||
return indicator
|
||||
|
||||
components = []
|
||||
component_dict = {}
|
||||
modules.shared.settings_components = component_dict
|
||||
@@ -1337,6 +1369,7 @@ def create_ui():
|
||||
quicksettings_names = {x: i for i, x in enumerate(quicksettings_names) if x != 'quicksettings'}
|
||||
quicksettings_list = []
|
||||
previous_section = None
|
||||
tab_item_keys = []
|
||||
current_tab = None
|
||||
current_row = None
|
||||
with gr.Tabs(elem_id="settings"):
|
||||
@@ -1345,9 +1378,10 @@ def create_ui():
|
||||
if previous_section != item.section and not section_must_be_skipped:
|
||||
elem_id, text = item.section
|
||||
if current_tab is not None:
|
||||
create_dirty_indicator(previous_section[0], tab_item_keys)
|
||||
tab_item_keys = []
|
||||
current_row.__exit__()
|
||||
current_tab.__exit__()
|
||||
gr.Group()
|
||||
current_tab = gr.TabItem(elem_id=f"settings_{elem_id}", label=text)
|
||||
current_tab.__enter__()
|
||||
current_row = gr.Column(variant='compact')
|
||||
@@ -1361,15 +1395,20 @@ def create_ui():
|
||||
else:
|
||||
component = create_setting_component(k)
|
||||
component_dict[k] = component
|
||||
tab_item_keys.append(k)
|
||||
components.append(component)
|
||||
if current_tab is not None:
|
||||
create_dirty_indicator(previous_section[0], tab_item_keys)
|
||||
tab_item_keys = []
|
||||
current_row.__exit__()
|
||||
current_tab.__exit__()
|
||||
|
||||
request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications", visible=False)
|
||||
_show_all_pages = gr.Button(value="Show all pages", variant='primary', elem_id="settings_show_all_pages")
|
||||
with gr.TabItem("Licenses", id="licenses", elem_id="settings_tab_licenses"):
|
||||
gr.HTML(modules.shared.html("licenses.html"), elem_id="licenses")
|
||||
create_dirty_indicator("tab_licenses", [], interactive=False)
|
||||
with gr.TabItem("Show all pages", variant='primary', elem_id="settings_show_all_pages"):
|
||||
create_dirty_indicator("show_all_pages", [], interactive=False)
|
||||
|
||||
def unload_sd_weights():
|
||||
modules.sd_models.unload_model_weights()
|
||||
|
||||
Reference in New Issue
Block a user