elismasilva commited on
Commit
98b69f4
·
verified ·
1 Parent(s): 7fdd7ac

Upload folder using huggingface_hub

Browse files
Files changed (9) hide show
  1. app.py +290 -269
  2. custom.css +9 -6
  3. custom.js +20 -0
  4. src/.vscode/launch.json +2 -1
  5. src/custom.css +80 -0
  6. src/custom.js +20 -0
  7. src/demo/app.py +46 -25
  8. src/demo/custom.css +9 -6
  9. src/demo/custom.js +20 -0
app.py CHANGED
@@ -1,270 +1,291 @@
1
- import os
2
- from pathlib import Path
3
- import gradio as gr
4
- from dataclasses import dataclass, field, asdict
5
- from typing import Literal
6
- from gradio_propertysheet import PropertySheet
7
-
8
-
9
- # --- 1. Dataclass Definitions ---
10
-
11
- # Dataclasses for the Original Sidebar Demo
12
- @dataclass
13
- class ModelSettings:
14
- """Settings for loading models, VAEs, etc."""
15
- model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
16
- custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
17
- vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
18
-
19
- @dataclass
20
- class SamplingSettings:
21
- """Settings for the image sampling process."""
22
- sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler"})
23
- steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1})
24
- cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5})
25
-
26
- @dataclass
27
- class RenderConfig:
28
- """Main configuration object for rendering, grouping all settings."""
29
- seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)"})
30
- model: ModelSettings = field(default_factory=ModelSettings)
31
- sampling: SamplingSettings = field(default_factory=SamplingSettings)
32
-
33
- @dataclass
34
- class Lighting:
35
- """Lighting settings for the environment."""
36
- sun_intensity: float = field(default=1.0, metadata={"component": "slider", "minimum": 0, "maximum": 5, "step": 0.1})
37
- color: str = field(default="#FFDDBB", metadata={"component": "colorpicker", "label": "Sun Color"})
38
-
39
- @dataclass
40
- class EnvironmentConfig:
41
- """Main configuration for the environment."""
42
- background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
43
- lighting: Lighting = field(default_factory=Lighting)
44
-
45
- # Dataclasses for the Flyout Demo
46
- @dataclass
47
- class EulerSettings:
48
- """Settings specific to the Euler sampler."""
49
- s_churn: float = field(default=0.0, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01})
50
-
51
- @dataclass
52
- class DPM_Settings:
53
- """Settings specific to DPM samplers."""
54
- karras_style: bool = field(default=True, metadata={"label": "Use Karras Sigma Schedule"})
55
-
56
- # --- 2. Data Mappings and Initial Instances ---
57
-
58
- # Data for Original Sidebar Demo
59
- initial_render_config = RenderConfig()
60
- initial_env_config = EnvironmentConfig()
61
-
62
- # Data for Flyout Demo
63
- sampler_settings_map_py = {"Euler": EulerSettings(), "DPM++ 2M Karras": DPM_Settings(), "UniPC": None}
64
- model_settings_map_py = {"SDXL 1.0": DPM_Settings(), "Stable Diffusion 1.5": EulerSettings(), "Pony": None}
65
-
66
- # --- 3. CSS and JavaScript Loading ---
67
-
68
- # Load external CSS file if it exists
69
- # script_path = Path(__file__).resolve()
70
- # script_dir = script_path.parent
71
- # css_path = script_dir / "custom.css"
72
- # flyout_css = ""
73
- # if css_path.exists():
74
- # with open(css_path, "r", encoding="utf8") as file:
75
- # flyout_css = file.read()
76
-
77
- # JavaScript for positioning the flyout panel
78
- head_script = f"""
79
- <script>
80
- function position_flyout(anchorId) {{
81
- setTimeout(() => {{
82
- const anchorRow = document.getElementById(anchorId).closest('.fake-input-container');
83
- const flyoutElem = document.getElementById('flyout_panel');
84
-
85
- if (anchorRow && flyoutElem && flyoutElem.style.display !== 'none') {{
86
- const anchorRect = anchorRow.getBoundingClientRect();
87
- const containerRect = anchorRow.closest('.flyout-context-area').getBoundingClientRect();
88
-
89
- const flyoutWidth = flyoutElem.offsetWidth;
90
- const flyoutHeight = flyoutElem.offsetHeight;
91
-
92
- const topPosition = (anchorRect.top - containerRect.top) + (anchorRect.height / 2) - (flyoutHeight / 2);
93
- const leftPosition = (anchorRect.left - containerRect.left) + (anchorRect.width / 2) - (flyoutWidth / 2);
94
-
95
- flyoutElem.style.top = `${{topPosition}}px`;
96
- flyoutElem.style.left = `${{leftPosition}}px`;
97
- }}
98
- }}, 50);
99
- }}
100
- </script>
101
- # """
102
-
103
- # --- 4. Gradio App Build ---
104
- with gr.Blocks(css_paths=["custom.css"], head=head_script, head_paths=["custom.html"], title="PropertySheet Demos") as demo:
105
- gr.Markdown("# PropertySheet Component Demos")
106
-
107
- with gr.Tabs():
108
- with gr.TabItem("Original Sidebar Demo"):
109
- gr.Markdown("An example of using the `PropertySheet` component as a traditional sidebar for settings.")
110
-
111
- render_state = gr.State(value=initial_render_config)
112
- env_state = gr.State(value=initial_env_config)
113
- sidebar_visible = gr.State(False)
114
-
115
- with gr.Row():
116
- with gr.Column(scale=3):
117
- generate = gr.Button("Show Settings", variant="primary")
118
- with gr.Row():
119
- output_render_json = gr.JSON(label="Live Render State")
120
- output_env_json = gr.JSON(label="Live Environment State")
121
-
122
- with gr.Column(scale=1):
123
- render_sheet = PropertySheet(
124
- value=initial_render_config,
125
- label="Render Settings",
126
- width=400,
127
- height=550,
128
- visible=False,
129
- root_label="Generator"
130
- )
131
- environment_sheet = PropertySheet(
132
- value=initial_env_config,
133
- label="Environment Settings",
134
- width=400,
135
- open=False,
136
- visible=False,
137
- root_label="General"
138
- )
139
-
140
- def change_visibility(is_visible, render_cfg, env_cfg):
141
- new_visibility = not is_visible
142
- button_text = "Hide Settings" if new_visibility else "Show Settings"
143
- return (
144
- new_visibility,
145
- gr.update(visible=new_visibility, value=render_cfg),
146
- gr.update(visible=new_visibility, value=env_cfg),
147
- gr.update(value=button_text)
148
- )
149
-
150
- def handle_render_change(updated_config: RenderConfig, current_state: RenderConfig):
151
- if updated_config is None:
152
- return current_state, asdict(current_state), current_state
153
- if updated_config.model.model_type != "Custom":
154
- updated_config.model.custom_model_path = "/path/to/default.safetensors"
155
- return updated_config, asdict(updated_config), updated_config
156
-
157
- def handle_env_change(updated_config: EnvironmentConfig, current_state: EnvironmentConfig):
158
- if updated_config is None:
159
- return current_state, asdict(current_state), current_state
160
- return updated_config, asdict(updated_config), current_state
161
-
162
- generate.click(
163
- fn=change_visibility,
164
- inputs=[sidebar_visible, render_state, env_state],
165
- outputs=[sidebar_visible, render_sheet, environment_sheet, generate]
166
- )
167
-
168
- render_sheet.change(
169
- fn=handle_render_change,
170
- inputs=[render_sheet, render_state],
171
- outputs=[render_sheet, output_render_json, render_state]
172
- )
173
-
174
- environment_sheet.change(
175
- fn=handle_env_change,
176
- inputs=[environment_sheet, env_state],
177
- outputs=[environment_sheet, output_env_json, env_state]
178
- )
179
-
180
- demo.load(
181
- fn=lambda r_cfg, e_cfg: (asdict(r_cfg), asdict(e_cfg)),
182
- inputs=[render_state, env_state],
183
- outputs=[output_render_json, output_env_json]
184
- )
185
-
186
- with gr.TabItem("Flyout Popup Demo"):
187
- gr.Markdown("An example of attaching a `PropertySheet` as a flyout panel to other components.")
188
-
189
- flyout_visible = gr.State(False)
190
- active_anchor_id = gr.State(None)
191
-
192
- with gr.Column(elem_classes=["flyout-context-area"]):
193
- with gr.Row(elem_classes=["fake-input-container", "no-border-dropdown"]):
194
- sampler_dd = gr.Dropdown(choices=list(sampler_settings_map_py.keys()), label="Sampler", value="Euler", elem_id="sampler_dd", scale=10)
195
- sampler_ear_btn = gr.Button("⚙️", elem_id="sampler_ear_btn", scale=1, elem_classes=["integrated-ear-btn"])
196
-
197
- with gr.Row(elem_classes=["fake-input-container", "no-border-dropdown"]):
198
- model_dd = gr.Dropdown(choices=list(model_settings_map_py.keys()), label="Model", value="SDXL 1.0", elem_id="model_dd", scale=10)
199
- model_ear_btn = gr.Button("⚙️", elem_id="model_ear_btn", scale=1, elem_classes=["integrated-ear-btn"])
200
-
201
- with gr.Column(visible=False, elem_id="flyout_panel", elem_classes=["flyout-sheet"]) as flyout_panel:
202
- with gr.Row(elem_classes=["close-btn-row"]):
203
- close_btn = gr.Button("×", elem_classes=["flyout-close-btn"])
204
- flyout_sheet = PropertySheet(visible=False, container=False, label="Settings", show_group_name_only_one=False, disable_accordion=True)
205
-
206
- def handle_flyout_toggle(is_vis, current_anchor, clicked_dropdown_id, settings_obj):
207
- if is_vis and current_anchor == clicked_dropdown_id:
208
- return False, None, gr.update(visible=False), gr.update(visible=False, value=None)
209
- else:
210
- return True, clicked_dropdown_id, gr.update(visible=True), gr.update(visible=True, value=settings_obj)
211
-
212
- def update_ear_visibility(selection, settings_map):
213
- has_settings = settings_map.get(selection) is not None
214
- return gr.update(visible=has_settings)
215
-
216
- def on_flyout_change(updated_settings, active_id, sampler_val, model_val):
217
- if updated_settings is None or active_id is None: return
218
- if active_id == sampler_dd.elem_id:
219
- sampler_settings_map_py[sampler_val] = updated_settings
220
- elif active_id == model_dd.elem_id:
221
- model_settings_map_py[model_val] = updated_settings
222
-
223
- def close_the_flyout():
224
- return False, None, gr.update(visible=False), gr.update(visible=False, value=None)
225
-
226
- sampler_dd.change(
227
- fn=lambda sel: update_ear_visibility(sel, sampler_settings_map_py),
228
- inputs=[sampler_dd],
229
- outputs=[sampler_ear_btn]
230
- ).then(fn=close_the_flyout, outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet])
231
-
232
- sampler_ear_btn.click(
233
- fn=lambda is_vis, anchor, sel: handle_flyout_toggle(is_vis, anchor, sampler_dd.elem_id, sampler_settings_map_py.get(sel)),
234
- inputs=[flyout_visible, active_anchor_id, sampler_dd],
235
- outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet]
236
- ).then(fn=None, inputs=None, outputs=None, js=f"() => position_flyout('{sampler_dd.elem_id}')")
237
-
238
- model_dd.change(
239
- fn=lambda sel: update_ear_visibility(sel, model_settings_map_py),
240
- inputs=[model_dd],
241
- outputs=[model_ear_btn]
242
- ).then(fn=close_the_flyout, outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet])
243
-
244
- model_ear_btn.click(
245
- fn=lambda is_vis, anchor, sel: handle_flyout_toggle(is_vis, anchor, model_dd.elem_id, model_settings_map_py.get(sel)),
246
- inputs=[flyout_visible, active_anchor_id, model_dd],
247
- outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet]
248
- ).then(fn=None, inputs=None, outputs=None, js=f"() => position_flyout('{model_dd.elem_id}')")
249
-
250
- flyout_sheet.change(
251
- fn=on_flyout_change,
252
- inputs=[flyout_sheet, active_anchor_id, sampler_dd, model_dd],
253
- outputs=None
254
- )
255
-
256
- close_btn.click(
257
- fn=close_the_flyout,
258
- inputs=None,
259
- outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet]
260
- )
261
-
262
- def initial_flyout_setup(sampler_val, model_val):
263
- return {
264
- sampler_ear_btn: update_ear_visibility(sampler_val, sampler_settings_map_py),
265
- model_ear_btn: update_ear_visibility(model_val, model_settings_map_py)
266
- }
267
- demo.load(fn=initial_flyout_setup, inputs=[sampler_dd, model_dd], outputs=[sampler_ear_btn, model_ear_btn])
268
-
269
- if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  demo.launch()
 
1
+ import os
2
+ from pathlib import Path
3
+ import gradio as gr
4
+ from dataclasses import dataclass, field, asdict
5
+ from typing import Literal
6
+ from gradio_propertysheet import PropertySheet
7
+ from gradio_headinjector import HeadInjector
8
+
9
+ def inject_assets():
10
+ """
11
+ This function prepares the payload of CSS and JS code. It's called by the
12
+ demo.load() event listener when the Gradio app starts.
13
+ """
14
+ # Inline code
15
+ css_code = ""
16
+ js_code = ""
17
+
18
+ # Read from files
19
+ try:
20
+ with open("custom.css", "r", encoding="utf-8") as f:
21
+ css_code += f.read() + "\n"
22
+ with open("custom.js", "r", encoding="utf-8") as f:
23
+ js_code += f.read() + "\n"
24
+ except FileNotFoundError as e:
25
+ print(f"Warning: Could not read asset file: {e}")
26
+
27
+ return {"js": js_code, "css": css_code}
28
+
29
+ # --- 1. Dataclass Definitions ---
30
+
31
+ # Dataclasses for the Original Sidebar Demo
32
+ @dataclass
33
+ class ModelSettings:
34
+ """Settings for loading models, VAEs, etc."""
35
+ model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
36
+ custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
37
+ vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
38
+
39
+ @dataclass
40
+ class SamplingSettings:
41
+ """Settings for the image sampling process."""
42
+ sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler"})
43
+ steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1})
44
+ cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5})
45
+
46
+ @dataclass
47
+ class RenderConfig:
48
+ """Main configuration object for rendering, grouping all settings."""
49
+ seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)"})
50
+ model: ModelSettings = field(default_factory=ModelSettings)
51
+ sampling: SamplingSettings = field(default_factory=SamplingSettings)
52
+
53
+ @dataclass
54
+ class Lighting:
55
+ """Lighting settings for the environment."""
56
+ sun_intensity: float = field(default=1.0, metadata={"component": "slider", "minimum": 0, "maximum": 5, "step": 0.1})
57
+ color: str = field(default="#FFDDBB", metadata={"component": "colorpicker", "label": "Sun Color"})
58
+
59
+ @dataclass
60
+ class EnvironmentConfig:
61
+ """Main configuration for the environment."""
62
+ background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
63
+ lighting: Lighting = field(default_factory=Lighting)
64
+
65
+ # Dataclasses for the Flyout Demo
66
+ @dataclass
67
+ class EulerSettings:
68
+ """Settings specific to the Euler sampler."""
69
+ s_churn: float = field(default=0.0, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01})
70
+
71
+ @dataclass
72
+ class DPM_Settings:
73
+ """Settings specific to DPM samplers."""
74
+ karras_style: bool = field(default=True, metadata={"label": "Use Karras Sigma Schedule"})
75
+
76
+ # --- 2. Data Mappings and Initial Instances ---
77
+
78
+ # Data for Original Sidebar Demo
79
+ initial_render_config = RenderConfig()
80
+ initial_env_config = EnvironmentConfig()
81
+
82
+ # Data for Flyout Demo
83
+ sampler_settings_map_py = {"Euler": EulerSettings(), "DPM++ 2M Karras": DPM_Settings(), "UniPC": None}
84
+ model_settings_map_py = {"SDXL 1.0": DPM_Settings(), "Stable Diffusion 1.5": EulerSettings(), "Pony": None}
85
+
86
+ # --- 3. CSS and JavaScript Loading ---
87
+
88
+ # Load external CSS file if it exists
89
+ # script_path = Path(__file__).resolve()
90
+ # script_dir = script_path.parent
91
+ # css_path = script_dir / "custom.css"
92
+ # flyout_css = ""
93
+ # if css_path.exists():
94
+ # with open(css_path, "r", encoding="utf8") as file:
95
+ # flyout_css = file.read()
96
+
97
+ # JavaScript for positioning the flyout panel
98
+ # head_script = f"""
99
+ # <script>
100
+ # function position_flyout(anchorId) {{
101
+ # setTimeout(() => {{
102
+ # const anchorRow = document.getElementById(anchorId).closest('.fake-input-container');
103
+ # const flyoutElem = document.getElementById('flyout_panel');
104
+
105
+ # if (anchorRow && flyoutElem && flyoutElem.style.display !== 'none') {{
106
+ # const anchorRect = anchorRow.getBoundingClientRect();
107
+ # const containerRect = anchorRow.closest('.flyout-context-area').getBoundingClientRect();
108
+
109
+ # const flyoutWidth = flyoutElem.offsetWidth;
110
+ # const flyoutHeight = flyoutElem.offsetHeight;
111
+
112
+ # const topPosition = (anchorRect.top - containerRect.top) + (anchorRect.height / 2) - (flyoutHeight / 2);
113
+ # const leftPosition = (anchorRect.left - containerRect.left) + (anchorRect.width / 2) - (flyoutWidth / 2);
114
+
115
+ # flyoutElem.style.top = `${{topPosition}}px`;
116
+ # flyoutElem.style.left = `${{leftPosition}}px`;
117
+ # }}
118
+ # }}, 50);
119
+ # }}
120
+ # </script>
121
+ # # """
122
+
123
+ # --- 4. Gradio App Build ---
124
+ with gr.Blocks(title="PropertySheet Demos") as demo:
125
+ head_injector = HeadInjector()
126
+ gr.Markdown("# PropertySheet Component Demos")
127
+
128
+ with gr.Tabs():
129
+ with gr.TabItem("Original Sidebar Demo"):
130
+ gr.Markdown("An example of using the `PropertySheet` component as a traditional sidebar for settings.")
131
+
132
+ render_state = gr.State(value=initial_render_config)
133
+ env_state = gr.State(value=initial_env_config)
134
+ sidebar_visible = gr.State(False)
135
+
136
+ with gr.Row():
137
+ with gr.Column(scale=3):
138
+ generate = gr.Button("Show Settings", variant="primary")
139
+ with gr.Row():
140
+ output_render_json = gr.JSON(label="Live Render State")
141
+ output_env_json = gr.JSON(label="Live Environment State")
142
+
143
+ with gr.Column(scale=1):
144
+ render_sheet = PropertySheet(
145
+ value=initial_render_config,
146
+ label="Render Settings",
147
+ width=400,
148
+ height=550,
149
+ visible=False,
150
+ root_label="Generator"
151
+ )
152
+ environment_sheet = PropertySheet(
153
+ value=initial_env_config,
154
+ label="Environment Settings",
155
+ width=400,
156
+ open=False,
157
+ visible=False,
158
+ root_label="General"
159
+ )
160
+
161
+ def change_visibility(is_visible, render_cfg, env_cfg):
162
+ new_visibility = not is_visible
163
+ button_text = "Hide Settings" if new_visibility else "Show Settings"
164
+ return (
165
+ new_visibility,
166
+ gr.update(visible=new_visibility, value=render_cfg),
167
+ gr.update(visible=new_visibility, value=env_cfg),
168
+ gr.update(value=button_text)
169
+ )
170
+
171
+ def handle_render_change(updated_config: RenderConfig, current_state: RenderConfig):
172
+ if updated_config is None:
173
+ return current_state, asdict(current_state), current_state
174
+ if updated_config.model.model_type != "Custom":
175
+ updated_config.model.custom_model_path = "/path/to/default.safetensors"
176
+ return updated_config, asdict(updated_config), updated_config
177
+
178
+ def handle_env_change(updated_config: EnvironmentConfig, current_state: EnvironmentConfig):
179
+ if updated_config is None:
180
+ return current_state, asdict(current_state), current_state
181
+ return updated_config, asdict(updated_config), current_state
182
+
183
+ generate.click(
184
+ fn=change_visibility,
185
+ inputs=[sidebar_visible, render_state, env_state],
186
+ outputs=[sidebar_visible, render_sheet, environment_sheet, generate]
187
+ )
188
+
189
+ render_sheet.change(
190
+ fn=handle_render_change,
191
+ inputs=[render_sheet, render_state],
192
+ outputs=[render_sheet, output_render_json, render_state]
193
+ )
194
+
195
+ environment_sheet.change(
196
+ fn=handle_env_change,
197
+ inputs=[environment_sheet, env_state],
198
+ outputs=[environment_sheet, output_env_json, env_state]
199
+ )
200
+
201
+ demo.load(
202
+ fn=lambda r_cfg, e_cfg: (asdict(r_cfg), asdict(e_cfg)),
203
+ inputs=[render_state, env_state],
204
+ outputs=[output_render_json, output_env_json]
205
+ )
206
+
207
+ with gr.TabItem("Flyout Popup Demo"):
208
+ gr.Markdown("An example of attaching a `PropertySheet` as a flyout panel to other components.")
209
+
210
+ flyout_visible = gr.State(False)
211
+ active_anchor_id = gr.State(None)
212
+
213
+ with gr.Column(elem_classes=["flyout-context-area"]):
214
+ with gr.Row(elem_classes=["fake-input-container", "no-border-dropdown"]):
215
+ sampler_dd = gr.Dropdown(choices=list(sampler_settings_map_py.keys()), label="Sampler", value="Euler", elem_id="sampler_dd", scale=10)
216
+ sampler_ear_btn = gr.Button("⚙️", elem_id="sampler_ear_btn", scale=1, elem_classes=["integrated-ear-btn"])
217
+
218
+ with gr.Row(elem_classes=["fake-input-container", "no-border-dropdown"]):
219
+ model_dd = gr.Dropdown(choices=list(model_settings_map_py.keys()), label="Model", value="SDXL 1.0", elem_id="model_dd", scale=10)
220
+ model_ear_btn = gr.Button("⚙️", elem_id="model_ear_btn", scale=1, elem_classes=["integrated-ear-btn"])
221
+
222
+ with gr.Column(visible=False, elem_id="flyout_panel", elem_classes=["flyout-sheet"]) as flyout_panel:
223
+ with gr.Row(elem_classes=["close-btn-row"]):
224
+ close_btn = gr.Button("×", elem_classes=["flyout-close-btn"])
225
+ flyout_sheet = PropertySheet(visible=False, container=False, label="Settings", show_group_name_only_one=False, disable_accordion=True)
226
+
227
+ def handle_flyout_toggle(is_vis, current_anchor, clicked_dropdown_id, settings_obj):
228
+ if is_vis and current_anchor == clicked_dropdown_id:
229
+ return False, None, gr.update(visible=False), gr.update(visible=False, value=None)
230
+ else:
231
+ return True, clicked_dropdown_id, gr.update(visible=True), gr.update(visible=True, value=settings_obj)
232
+
233
+ def update_ear_visibility(selection, settings_map):
234
+ has_settings = settings_map.get(selection) is not None
235
+ return gr.update(visible=has_settings)
236
+
237
+ def on_flyout_change(updated_settings, active_id, sampler_val, model_val):
238
+ if updated_settings is None or active_id is None: return
239
+ if active_id == sampler_dd.elem_id:
240
+ sampler_settings_map_py[sampler_val] = updated_settings
241
+ elif active_id == model_dd.elem_id:
242
+ model_settings_map_py[model_val] = updated_settings
243
+
244
+ def close_the_flyout():
245
+ return False, None, gr.update(visible=False), gr.update(visible=False, value=None)
246
+
247
+ sampler_dd.change(
248
+ fn=lambda sel: update_ear_visibility(sel, sampler_settings_map_py),
249
+ inputs=[sampler_dd],
250
+ outputs=[sampler_ear_btn]
251
+ ).then(fn=close_the_flyout, outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet])
252
+
253
+ sampler_ear_btn.click(
254
+ fn=lambda is_vis, anchor, sel: handle_flyout_toggle(is_vis, anchor, sampler_dd.elem_id, sampler_settings_map_py.get(sel)),
255
+ inputs=[flyout_visible, active_anchor_id, sampler_dd],
256
+ outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet]
257
+ ).then(fn=None, inputs=None, outputs=None, js=f"() => position_flyout('{sampler_dd.elem_id}')")
258
+
259
+ model_dd.change(
260
+ fn=lambda sel: update_ear_visibility(sel, model_settings_map_py),
261
+ inputs=[model_dd],
262
+ outputs=[model_ear_btn]
263
+ ).then(fn=close_the_flyout, outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet])
264
+
265
+ model_ear_btn.click(
266
+ fn=lambda is_vis, anchor, sel: handle_flyout_toggle(is_vis, anchor, model_dd.elem_id, model_settings_map_py.get(sel)),
267
+ inputs=[flyout_visible, active_anchor_id, model_dd],
268
+ outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet]
269
+ ).then(fn=None, inputs=None, outputs=None, js=f"() => position_flyout('{model_dd.elem_id}')")
270
+
271
+ flyout_sheet.change(
272
+ fn=on_flyout_change,
273
+ inputs=[flyout_sheet, active_anchor_id, sampler_dd, model_dd],
274
+ outputs=None
275
+ )
276
+
277
+ close_btn.click(
278
+ fn=close_the_flyout,
279
+ inputs=None,
280
+ outputs=[flyout_visible, active_anchor_id, flyout_panel, flyout_sheet]
281
+ )
282
+
283
+ def initial_flyout_setup(sampler_val, model_val):
284
+ return {
285
+ sampler_ear_btn: update_ear_visibility(sampler_val, sampler_settings_map_py),
286
+ model_ear_btn: update_ear_visibility(model_val, model_settings_map_py)
287
+ }
288
+ demo.load(fn=inject_assets, inputs=None, outputs=[head_injector]).then(fn=initial_flyout_setup, inputs=[sampler_dd, model_dd], outputs=[sampler_ear_btn, model_ear_btn])
289
+
290
+ if __name__ == "__main__":
291
  demo.launch()
custom.css CHANGED
@@ -1,11 +1,14 @@
1
- .flyout-context-area {
2
- position: relative;
3
- overflow: visible;
4
  }
5
 
6
- .flyout-sheet {
7
- position: absolute;
8
- width: 350px;
 
 
 
9
  z-index: 1000;
10
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
11
  border: 1px solid #E5E7EB;
 
1
+ .flyout-context-area {
2
+ position: relative !important;
3
+ overflow: visible !important;
4
  }
5
 
6
+ .flyout-sheet {
7
+ position: absolute !important;
8
+ flex-grow: 0 !important;
9
+ min-width: unset !important;
10
+ width: 350px !important;
11
+ align-self: center !important;
12
  z-index: 1000;
13
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
14
  border: 1px solid #E5E7EB;
custom.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function position_flyout(anchorId) {{
2
+ setTimeout(() => {{
3
+ const anchorRow = document.getElementById(anchorId).closest('.fake-input-container');
4
+ const flyoutElem = document.getElementById('flyout_panel');
5
+
6
+ if (anchorRow && flyoutElem && flyoutElem.style.display !== 'none') {{
7
+ const anchorRect = anchorRow.getBoundingClientRect();
8
+ const containerRect = anchorRow.closest('.flyout-context-area').getBoundingClientRect();
9
+
10
+ const flyoutWidth = flyoutElem.offsetWidth;
11
+ const flyoutHeight = flyoutElem.offsetHeight;
12
+
13
+ const topPosition = (anchorRect.top - containerRect.top) + (anchorRect.height / 2) - (flyoutHeight / 2);
14
+ const leftPosition = (anchorRect.left - containerRect.left) + (anchorRect.width / 2) - (flyoutWidth / 2);
15
+
16
+ flyoutElem.style.top = `${{topPosition}}px`;
17
+ flyoutElem.style.left = `${{leftPosition}}px`;
18
+ }}
19
+ }}, 50);
20
+ }};
src/.vscode/launch.json CHANGED
@@ -10,7 +10,8 @@
10
  "type": "debugpy",
11
  "request": "launch",
12
  "program": "${file}",
13
- "console": "integratedTerminal"
 
14
  }
15
  ]
16
  }
 
10
  "type": "debugpy",
11
  "request": "launch",
12
  "program": "${file}",
13
+ "console": "integratedTerminal",
14
+ "justMyCode": false
15
  }
16
  ]
17
  }
src/custom.css ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .flyout-context-area {
2
+ position: relative !important;
3
+ overflow: visible !important;
4
+ }
5
+
6
+ .flyout-sheet {
7
+ position: absolute !important;
8
+ flex-grow: 0 !important;
9
+ min-width: unset !important;
10
+ width: 350px !important;
11
+ align-self: center !important;
12
+ z-index: 1000;
13
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
14
+ border: 1px solid #E5E7EB;
15
+ border-radius: var(--radius-lg);
16
+ background: var(--panel-background-fill, white);
17
+ padding: var(--spacing-lg) !important;
18
+ }
19
+
20
+ .fake-input-container {
21
+ border: var(--input-border-width) solid var(--border-color-primary);
22
+ border-radius: var(--input-radius);
23
+ background: var(--input-background-fill);
24
+ padding: 0 !important;
25
+ align-items: stretch !important;
26
+ box-shadow: var(--input-shadow);
27
+ transition: box-shadow 0.2s, border-color 0.2s;
28
+ gap: 0 !important;
29
+ }
30
+
31
+ .fake-input-container:focus-within {
32
+ box-shadow: var(--input-shadow-focus);
33
+ border-color: var(--input-border-color-focus);
34
+ }
35
+
36
+ .no-border-dropdown .form {
37
+ border: none !important;
38
+ box-shadow: none !important;
39
+ background: transparent !important;
40
+ }
41
+
42
+ .integrated-ear-btn {
43
+ align-self: center;
44
+ background: none !important;
45
+ border: none !important;
46
+ box-shadow: none !important;
47
+ color: var(--body-text-color-subdued) !important;
48
+ font-size: 1.1em;
49
+ min-width: 28px !important;
50
+ width: 28px !important;
51
+ height: 100% !important;
52
+ padding: 20px var(--spacing-sm) 0 0 !important;
53
+ transition: color 0.2s ease-in-out;
54
+ flex-grow: 0 !important;
55
+ }
56
+
57
+ .integrated-ear-btn:hover {
58
+ color: var(--body-text-color) !important;
59
+ }
60
+
61
+ .flyout-close-btn {
62
+ position: absolute;
63
+ top: 8px;
64
+ right: 8px;
65
+ min-width: 24px !important;
66
+ width: 24px !important;
67
+ height: 24px !important;
68
+ padding: 0 !important;
69
+ font-size: 1.2em !important;
70
+ line-height: 1;
71
+ background: transparent !important;
72
+ border: none !important;
73
+ box-shadow: none !important;
74
+ color: var(--body-text-color-subdued) !important;
75
+ z-index: 1000;
76
+ }
77
+
78
+ .flyout-close-btn button:hover {
79
+ color: var(--body-text-color) !important;
80
+ }
src/custom.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function position_flyout(anchorId) {{
2
+ setTimeout(() => {{
3
+ const anchorRow = document.getElementById(anchorId).closest('.fake-input-container');
4
+ const flyoutElem = document.getElementById('flyout_panel');
5
+
6
+ if (anchorRow && flyoutElem && flyoutElem.style.display !== 'none') {{
7
+ const anchorRect = anchorRow.getBoundingClientRect();
8
+ const containerRect = anchorRow.closest('.flyout-context-area').getBoundingClientRect();
9
+
10
+ const flyoutWidth = flyoutElem.offsetWidth;
11
+ const flyoutHeight = flyoutElem.offsetHeight;
12
+
13
+ const topPosition = (anchorRect.top - containerRect.top) + (anchorRect.height / 2) - (flyoutHeight / 2);
14
+ const leftPosition = (anchorRect.left - containerRect.left) + (anchorRect.width / 2) - (flyoutWidth / 2);
15
+
16
+ flyoutElem.style.top = `${{topPosition}}px`;
17
+ flyoutElem.style.left = `${{leftPosition}}px`;
18
+ }}
19
+ }}, 50);
20
+ }};
src/demo/app.py CHANGED
@@ -4,7 +4,27 @@ import gradio as gr
4
  from dataclasses import dataclass, field, asdict
5
  from typing import Literal
6
  from gradio_propertysheet import PropertySheet
7
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
  # --- 1. Dataclass Definitions ---
10
 
@@ -75,33 +95,34 @@ model_settings_map_py = {"SDXL 1.0": DPM_Settings(), "Stable Diffusion 1.5": Eul
75
  # flyout_css = file.read()
76
 
77
  # JavaScript for positioning the flyout panel
78
- head_script = f"""
79
- <script>
80
- function position_flyout(anchorId) {{
81
- setTimeout(() => {{
82
- const anchorRow = document.getElementById(anchorId).closest('.fake-input-container');
83
- const flyoutElem = document.getElementById('flyout_panel');
84
 
85
- if (anchorRow && flyoutElem && flyoutElem.style.display !== 'none') {{
86
- const anchorRect = anchorRow.getBoundingClientRect();
87
- const containerRect = anchorRow.closest('.flyout-context-area').getBoundingClientRect();
88
 
89
- const flyoutWidth = flyoutElem.offsetWidth;
90
- const flyoutHeight = flyoutElem.offsetHeight;
91
 
92
- const topPosition = (anchorRect.top - containerRect.top) + (anchorRect.height / 2) - (flyoutHeight / 2);
93
- const leftPosition = (anchorRect.left - containerRect.left) + (anchorRect.width / 2) - (flyoutWidth / 2);
94
 
95
- flyoutElem.style.top = `${{topPosition}}px`;
96
- flyoutElem.style.left = `${{leftPosition}}px`;
97
- }}
98
- }}, 50);
99
- }}
100
- </script>
101
- # """
102
 
103
  # --- 4. Gradio App Build ---
104
- with gr.Blocks(css_paths=["demo/custom.css"], head=head_script, head_paths=["demo/custom.html"], title="PropertySheet Demos") as demo:
 
105
  gr.Markdown("# PropertySheet Component Demos")
106
 
107
  with gr.Tabs():
@@ -263,8 +284,8 @@ with gr.Blocks(css_paths=["demo/custom.css"], head=head_script, head_paths=["dem
263
  return {
264
  sampler_ear_btn: update_ear_visibility(sampler_val, sampler_settings_map_py),
265
  model_ear_btn: update_ear_visibility(model_val, model_settings_map_py)
266
- }
267
- demo.load(fn=initial_flyout_setup, inputs=[sampler_dd, model_dd], outputs=[sampler_ear_btn, model_ear_btn])
268
-
269
  if __name__ == "__main__":
270
  demo.launch()
 
4
  from dataclasses import dataclass, field, asdict
5
  from typing import Literal
6
  from gradio_propertysheet import PropertySheet
7
+ from gradio_headinjector import HeadInjector
8
+
9
+ def inject_assets():
10
+ """
11
+ This function prepares the payload of CSS and JS code. It's called by the
12
+ demo.load() event listener when the Gradio app starts.
13
+ """
14
+ # Inline code
15
+ css_code = ""
16
+ js_code = ""
17
+
18
+ # Read from files
19
+ try:
20
+ with open("custom.css", "r", encoding="utf-8") as f:
21
+ css_code += f.read() + "\n"
22
+ with open("custom.js", "r", encoding="utf-8") as f:
23
+ js_code += f.read() + "\n"
24
+ except FileNotFoundError as e:
25
+ print(f"Warning: Could not read asset file: {e}")
26
+
27
+ return {"js": js_code, "css": css_code}
28
 
29
  # --- 1. Dataclass Definitions ---
30
 
 
95
  # flyout_css = file.read()
96
 
97
  # JavaScript for positioning the flyout panel
98
+ # head_script = f"""
99
+ # <script>
100
+ # function position_flyout(anchorId) {{
101
+ # setTimeout(() => {{
102
+ # const anchorRow = document.getElementById(anchorId).closest('.fake-input-container');
103
+ # const flyoutElem = document.getElementById('flyout_panel');
104
 
105
+ # if (anchorRow && flyoutElem && flyoutElem.style.display !== 'none') {{
106
+ # const anchorRect = anchorRow.getBoundingClientRect();
107
+ # const containerRect = anchorRow.closest('.flyout-context-area').getBoundingClientRect();
108
 
109
+ # const flyoutWidth = flyoutElem.offsetWidth;
110
+ # const flyoutHeight = flyoutElem.offsetHeight;
111
 
112
+ # const topPosition = (anchorRect.top - containerRect.top) + (anchorRect.height / 2) - (flyoutHeight / 2);
113
+ # const leftPosition = (anchorRect.left - containerRect.left) + (anchorRect.width / 2) - (flyoutWidth / 2);
114
 
115
+ # flyoutElem.style.top = `${{topPosition}}px`;
116
+ # flyoutElem.style.left = `${{leftPosition}}px`;
117
+ # }}
118
+ # }}, 50);
119
+ # }}
120
+ # </script>
121
+ # # """
122
 
123
  # --- 4. Gradio App Build ---
124
+ with gr.Blocks(title="PropertySheet Demos") as demo:
125
+ head_injector = HeadInjector()
126
  gr.Markdown("# PropertySheet Component Demos")
127
 
128
  with gr.Tabs():
 
284
  return {
285
  sampler_ear_btn: update_ear_visibility(sampler_val, sampler_settings_map_py),
286
  model_ear_btn: update_ear_visibility(model_val, model_settings_map_py)
287
+ }
288
+ demo.load(fn=inject_assets, inputs=None, outputs=[head_injector]).then(fn=initial_flyout_setup, inputs=[sampler_dd, model_dd], outputs=[sampler_ear_btn, model_ear_btn])
289
+
290
  if __name__ == "__main__":
291
  demo.launch()
src/demo/custom.css CHANGED
@@ -1,11 +1,14 @@
1
- .flyout-context-area {
2
- position: relative;
3
- overflow: visible;
4
  }
5
 
6
- .flyout-sheet {
7
- position: absolute;
8
- width: 350px;
 
 
 
9
  z-index: 1000;
10
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
11
  border: 1px solid #E5E7EB;
 
1
+ .flyout-context-area {
2
+ position: relative !important;
3
+ overflow: visible !important;
4
  }
5
 
6
+ .flyout-sheet {
7
+ position: absolute !important;
8
+ flex-grow: 0 !important;
9
+ min-width: unset !important;
10
+ width: 350px !important;
11
+ align-self: center !important;
12
  z-index: 1000;
13
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
14
  border: 1px solid #E5E7EB;
src/demo/custom.js ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function position_flyout(anchorId) {{
2
+ setTimeout(() => {{
3
+ const anchorRow = document.getElementById(anchorId).closest('.fake-input-container');
4
+ const flyoutElem = document.getElementById('flyout_panel');
5
+
6
+ if (anchorRow && flyoutElem && flyoutElem.style.display !== 'none') {{
7
+ const anchorRect = anchorRow.getBoundingClientRect();
8
+ const containerRect = anchorRow.closest('.flyout-context-area').getBoundingClientRect();
9
+
10
+ const flyoutWidth = flyoutElem.offsetWidth;
11
+ const flyoutHeight = flyoutElem.offsetHeight;
12
+
13
+ const topPosition = (anchorRect.top - containerRect.top) + (anchorRect.height / 2) - (flyoutHeight / 2);
14
+ const leftPosition = (anchorRect.left - containerRect.left) + (anchorRect.width / 2) - (flyoutWidth / 2);
15
+
16
+ flyoutElem.style.top = `${{topPosition}}px`;
17
+ flyoutElem.style.left = `${{leftPosition}}px`;
18
+ }}
19
+ }}, 50);
20
+ }};