import gradio as gr import modules.scripts as scripts from modules import script_callbacks, shared import os import shutil from pathlib import Path basedir = scripts.basedir() class MyTab(): def __init__(self, basedir): #This extensions directory self.extensiondir = basedir #Up two directories, webui root self.webui_dir = Path(self.extensiondir).parents[1] self.style_folder = os.path.join(basedir, "style_choices") self.backgrounds_folder = os.path.join(self.extensiondir, "backgrounds") self.logos_folder = os.path.join(self.extensiondir, "logos") self.favicon_folder = os.path.join(self.extensiondir, "favicons") self.effects_folder = os.path.join(self.extensiondir, "effects") self.javascript_folder = os.path.join(self.extensiondir, "javascript") self.static_folder = os.path.join(self.webui_dir, "static") self.favicon_workaround = gr.HTML(value='
', render=False) self.styles_list = self.get_files(self.style_folder) self.backgrounds_list = self.get_files(self.backgrounds_folder) self.logos_list = self.get_files(self.logos_folder) self.favicon_list = self.get_files(self.favicon_folder) self.effects_list = self.get_files(self.javascript_folder, file_filter=["quickcss.js", "utility.js", "background.js"], split=True) self.styles_dropdown = gr.Dropdown(label="Styles", render=False, interactive=True, choices=self.styles_list, type="value") self.background_dropdown = gr.Dropdown(label="Background", render=False, interactive=True, choices=self.backgrounds_list, type="value") self.logos_dropdown = gr.Dropdown(label="Logos", render=False, interactive=True, choices=self.logos_list, type="value") self.favicon_dropdown = gr.Dropdown(label="Favicon", render=False, interactive=True, choices=self.favicon_list, type="value") self.effects_dropdown = gr.Dropdown(label="Effects (on until refresh)", render=False, interactive=True, choices=self.effects_list, type="value") self.apply_style_bttn = gr.Button(value="Apply Style", render=False) self.apply_background_bttn = gr.Button(value="Apply (Reload UI)", render=False) #TODO: background off button to swap image in folder to blankbackground and disable style rule self.refresh_bkcgrnd_droplist_button = gr.Button(value="Refresh List", render=False) self.apply_logo_bttn = gr.Button(value="Apply Logo", render=False) self.apply_favicon_bttn = gr.Button(value="Apply Favicon (edit webui.py to see)", render=False) self.effects_button = gr.Button(value="Activate Selected Script", render=False) self.effects_off_button = gr.Button(value="Deactivate Selected Script", render=False) self.logo_image = gr.Image(render=False) self.favicon_image = gr.Image(render=False) self.import_style_file = gr.File(render=False, label="Import CSS file") self.import_background_file = gr.File(render=False, label="Import Background Images") self.import_logo_file = gr.File(render=False, label="Import Logo's (png)") self.import_favicon_file = gr.File(render=False, label="Import favicons (svg)") self.restart_bttn = gr.Button(value="Apply changes (Reload UI)", render=False, variant="primary") self.remove_style = gr.Button(value="Remove Stylesheet", render=False) # BEGIN CSS COLORPICK COMPONENTS self.save_as_filename = gr.Text(label="Save Name", visible=False, render=False) self.save_button = gr.Button(value="Save", visible=False, render=False, variant="primary") #Test for file being set self.file_exists = False self.style_path = os.path.join(self.extensiondir, "style.css") self.start_position_for_save = 0 self.insert_colorpicker_break_rule_for_save = 0 self.insert_break_rule_for_save = 0 if os.path.exists(self.style_path): self.file_exists = True #Conditional for creating inputs self.lines = [] line = "" self.dynamic_compatible = False with open(self.style_path, 'r', encoding='utf-8') as cssfile: try: for i, line in enumerate(cssfile): line = line.strip() if "/*BREAKFILEREADER*/" in line: self.insert_break_rule_for_save = i - self.start_position_for_save break elif "/*ENDCOLORPICKERS*/" in line: self.insert_colorpicker_break_rule_for_save = i - self.start_position_for_save continue if "quickcss_target" in line: self.dynamic_compatible = True self.start_position_for_save = i+1 continue if self.dynamic_compatible: if len(line) > 0: self.lines.append(line.split(":")) except UnicodeDecodeError as error: print(f"{error}\nCheck style.css in this extensions folder.") if self.dynamic_compatible: self.dynamically_generated_components = [gr.ColorPicker(label=x[0].replace("-", "").replace("_", " ").title(), render=False, elem_id="quikcss_colorpicker", value=x[1].replace(";", "").strip()) if i < self.insert_colorpicker_break_rule_for_save else gr.Slider(minimum=0, maximum=100, step=1, label=x[0].replace("-", "").replace("_", " ").title(), render=False, elem_id="quikcss_slider", value=x[1].replace(";", "").strip()) for i,x in enumerate(self.lines) ] else: self.dynamically_generated_components = [] # hidden_vals acts like an index, holds int values that are used in js self.hidden_vals = [gr.Text(value=str(x), render=False, visible=False) for x in range(len(self.dynamically_generated_components))] # length_of_colors similar to hidden vals, but provides length so js knows the limit when parsing rules self.length_of_colors = gr.Text(value=len(self.dynamically_generated_components), visible=False, render=False) # used as padding so we don't list index error or http 500 internal server, or http 422 forEach can't over undefined (promise pending) self.dummy_picker = gr.Text(visible=False, render=False, elem_id="hidden") # Acts like catcher, actual values store in list self.js_result_component = gr.Text(render=False, interactive=False) #dummy component for general purpose, currently used for _js effects; holds no relevant data, just for input/output element quota self._dummy = gr.Text(value="", visible=False, render=False, show_label=False, interactive=False) def ui(self, *args, **kwargs): with gr.Blocks(analytics_enabled=False) as ui: self.favicon_workaround.render() with gr.Accordion(label="Some instructions", open=False): gr.Markdown(value="""
This is a mix from old style to new style. It is not in it's finished state
To see effects, you must use dropdown, select as sheet, click apply, click restart. More options will be available on restart
I know it lives as a tab, but this was meant to be a demo at first, now it's growing to something more
To see favicon take affect, you will need to add `favicon_path="favicon.svg"` to webui.py
To do this, open file, search for `prevent_thread_lock` add comma, paste in text, save.
You may need to undo this for an update, if you have git issues and don't know how to deal with them
This won't break your system, if you find you can't update, try `git checkout webui.py` ~~`git fetch --all` `git reset --hard origin/master`~~
Once again, this `dynamic` demo has not removed/re-implemented all features present
""") if self.file_exists and self.dynamic_compatible: with gr.Row(equal_height=True): self.save_as_filename.render() self.save_button.render() #Necessary for values being accessible self.length_of_colors.render() self.dummy_picker.render() self.js_result_component.render() #Render hidden vals that serve as indices to map for h in self.hidden_vals: h.render() with gr.Row(): #Render adjusters for c in self.dynamically_generated_components: with gr.Column(elem_id="quickcss_colorpicker"): c.render() with gr.Row(): with gr.Column(): self.styles_dropdown.render() self.apply_style_bttn.render() with gr.Column(): self.background_dropdown.render() with gr.Row(): self.apply_background_bttn.render() self.refresh_bkcgrnd_droplist_button.render() with gr.Column(): self.logos_dropdown.render() self.apply_logo_bttn.render() with gr.Column(): self.favicon_dropdown.render() self.apply_favicon_bttn.render() with gr.Column(): self.effects_dropdown.render() with gr.Row(): self.effects_button.render() self.effects_off_button.render() with gr.Accordion(label="Import Files", open=False): with gr.Row(): with gr.Column(): self.import_style_file.render() with gr.Column(): self.import_background_file.render() with gr.Column(): self.import_logo_file.render() with gr.Column(): self.import_favicon_file.render() with gr.Row(): self.restart_bttn.render() self.remove_style.render() with gr.Row(): self.logo_image.render() self.favicon_image.render() self._dummy.render() # Handlers #Generate colorpickers and sliders handlers if self.file_exists: for comp,val in zip(self.dynamically_generated_components, self.hidden_vals): comp.change( fn = lambda *x: self.process_for_save(*x), _js = "quickcssFormatRule", inputs = [comp, val, self.length_of_colors], outputs = [self.js_result_component] + [self.save_as_filename, self.dummy_picker] ) self.save_as_filename.change( fn = lambda x: gr.update(visible=bool(x)), inputs = self.save_as_filename, outputs = self.save_button ) self.save_button.click( fn = self.save, inputs = [self.js_result_component, self.save_as_filename], outputs = [self.js_result_component, self.styles_dropdown] ) #Handler cont. #Common interface #NOTE: These dropdowns affect image placeholders self.logos_dropdown.change( fn = lambda x: self.get_image(x, folder = "logos"), inputs = self.logos_dropdown, outputs = self.logo_image ) self.favicon_dropdown.change( fn = lambda x: self.get_image(x, folder = "favicons"), inputs = self.favicon_dropdown, outputs = self.favicon_image ) #buttons self.apply_style_bttn.click( fn = self.apply_choice_wrapper(self.style_folder, self.extensiondir, "style.css"), inputs = self.styles_dropdown ) self.apply_background_bttn.click( fn = self.apply_choice_wrapper(self.backgrounds_folder, self.static_folder, "background.png"),#TODO: MAYBE: delete file extension _js = "injectBackground.refreshImage", inputs = self.background_dropdown, outputs=self._dummy ) self.refresh_bkcgrnd_droplist_button.click( fn = lambda: self.refresh_list(self.refresh_bkcgrnd_droplist_button, self.backgrounds_folder, self.background_dropdown), outputs=self.background_dropdown ) self.apply_logo_bttn.click( fn = self.apply_choice_wrapper(self.logos_folder, self.static_folder, "logo.png"),#TODO Update css files and change dir to static inputs = self.logos_dropdown, ) self.apply_favicon_bttn.click( fn = self.apply_choice_wrapper(self.favicon_folder, self.static_folder, "favicon.svg"),#TODO update css files and change dir to static inputs = self.favicon_dropdown ) self.effects_button.click( fn = None, _js = "launchEffect", inputs = self.effects_dropdown, outputs = self._dummy ) self.effects_off_button.click( fn = None, _js = "destroyEffect", inputs = self.effects_dropdown, outputs = self._dummy ) self.remove_style.click( fn = lambda: self.delete_style() ) self.restart_bttn.click(fn=self.local_request_restart, _js='restart_reload', inputs=[], outputs=[]) #File Importers self.import_style_file.change( fn = lambda tmp_file: self.import_file_from_path(tmp_file, target_folder="style_choices", comp = self.styles_dropdown, func = self.get_files, folder=self.style_folder, focus_list=self.styles_list), inputs=self.import_style_file, outputs=self.styles_dropdown ) self.import_background_file.change( fn = lambda tmp_file: self.import_file_from_path(tmp_file, target_folder="backgrounds", comp = self.background_dropdown, func = self.get_files, folder=self.backgrounds_folder, focus_list=self.backgrounds_list), inputs=self.import_background_file, outputs=self.background_dropdown ) self.import_logo_file.change( fn = lambda tmp_file: self.import_file_from_path(tmp_file, target_folder="logos", comp = self.logos_dropdown, func = self.get_files, folder=self.logos_folder, focus_list=self.logos_list), inputs=self.import_logo_file, outputs=self.logos_dropdown ) self.import_favicon_file.change( fn = lambda tmp_file: self.import_file_from_path(tmp_file, target_folder="favicons", comp = self.favicon_dropdown, func = self.get_files, folder=self.favicon_folder, focus_list=self.favicon_list), inputs=self.import_favicon_file, outputs=self.favicon_dropdown ) return [(ui, "Theme", "theme")] def import_file_from_path(self, tmp_file_obj, target_folder, comp, func, **kwargs): if tmp_file_obj: shutil.copy(tmp_file_obj.name, os.path.join(self.extensiondir, target_folder, tmp_file_obj.orig_name)) # Update appropriate list # Make backend the same as front-end so it matches when selected comp.choices = func(**kwargs) tmp_file_obj.flush() # return sends update to front-end return gr.update(choices=comp.choices) def get_files(self, folder, focus_list=[], file_filter=[], split=False): focus_list = [file_name if not split else os.path.splitext(file_name)[0] for file_name in os.listdir(folder) if os.path.isfile(os.path.join(folder, file_name)) and file_name not in file_filter] return focus_list def apply_choice_wrapper(self, src_base_path, dst_base_path, name): """Encapsulation so I don't need a different function for each type""" def apply_choice(selection): shutil.copy(os.path.join(src_base_path, selection), os.path.join(dst_base_path, name)) return apply_choice def get_image(self, name, folder): return os.path.join(self.extensiondir, folder, name) def refresh_list(self, component, folder, focus_list, file_filter=[]): component.choices = self.get_files(folder, focus_list, file_filter) return gr.update(choices=component.choices) def delete_style(self): try: os.remove(os.path.join(self.extensiondir, "style.css")) except FileNotFoundError: pass def local_request_restart(self): "Restart button" shared.state.interrupt() shared.state.need_restart = True def process_for_save(self, x, *y): return [x] + [gr.update(visible=True), None] def save(self, js_cmp_val, filename): rules = [f" {e};\n" for e in js_cmp_val[1:-1].split(";")][:-1] #issue, save button needs to stay hidden until some color change rules.insert(self.insert_colorpicker_break_rule_for_save, " /*ENDCOLORPICKERS*/\n") rules.insert(self.insert_break_rule_for_save, " /*BREAKFILEREADER*/\n") with open(self.style_path, 'r+') as file: lines = file.readlines() start_pos = self.start_position_for_save for i, rule in enumerate(rules): lines[start_pos + i] = rule file.seek(0) file.writelines(lines) self.styles_dropdown.choices.insert(0, f"{filename}.css") shutil.copy(self.style_path, os.path.join(self.style_folder, f"{filename}.css")) return ["Saved", gr.update(choices=self.styles_dropdown.choices, value=self.styles_dropdown.choices[0])] tab = MyTab(basedir) script_callbacks.on_ui_tabs(tab.ui)