syd 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- syd/__init__.py +6 -10
- syd/flask_deployment/__init__.py +0 -0
- syd/flask_deployment/components.py +497 -0
- syd/flask_deployment/deployer.py +338 -0
- syd/flask_deployment/static/css/styles.css +39 -0
- syd/flask_deployment/static/js/components.js +51 -0
- syd/flask_deployment/static/js/viewer.js +0 -0
- syd/flask_deployment/templates/base.html +26 -0
- syd/flask_deployment/templates/viewer.html +97 -0
- syd/interactive_viewer.py +200 -31
- syd/{notebook_deploy → notebook_deployment}/deployer.py +39 -22
- syd/{notebook_deploy → notebook_deployment}/widgets.py +69 -62
- syd/parameters.py +356 -73
- {syd-0.1.5.dist-info → syd-0.1.6.dist-info}/METADATA +68 -3
- syd-0.1.6.dist-info/RECORD +18 -0
- syd-0.1.5.dist-info/RECORD +0 -10
- /syd/{notebook_deploy → notebook_deployment}/__init__.py +0 -0
- {syd-0.1.5.dist-info → syd-0.1.6.dist-info}/WHEEL +0 -0
- {syd-0.1.5.dist-info → syd-0.1.6.dist-info}/licenses/LICENSE +0 -0
syd/__init__.py
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
from typing import Callable
|
|
1
|
+
from typing import Callable, Optional
|
|
2
2
|
from .interactive_viewer import InteractiveViewer
|
|
3
3
|
|
|
4
|
-
__version__ = "0.1.
|
|
4
|
+
__version__ = "0.1.6"
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def make_viewer(plot_func: Callable):
|
|
13
|
-
viewer = DefaultViewer()
|
|
14
|
-
viewer.plot = plot_func
|
|
7
|
+
def make_viewer(plot_func: Optional[Callable] = None):
|
|
8
|
+
viewer = InteractiveViewer()
|
|
9
|
+
if plot_func is not None:
|
|
10
|
+
viewer.set_plot(plot_func)
|
|
15
11
|
return viewer
|
|
File without changes
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, Generic, List, TypeVar, Union, Optional
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from markupsafe import Markup
|
|
5
|
+
|
|
6
|
+
from ..parameters import (
|
|
7
|
+
Parameter,
|
|
8
|
+
TextParameter,
|
|
9
|
+
SelectionParameter,
|
|
10
|
+
MultipleSelectionParameter,
|
|
11
|
+
BooleanParameter,
|
|
12
|
+
IntegerParameter,
|
|
13
|
+
FloatParameter,
|
|
14
|
+
IntegerRangeParameter,
|
|
15
|
+
FloatRangeParameter,
|
|
16
|
+
UnboundedIntegerParameter,
|
|
17
|
+
UnboundedFloatParameter,
|
|
18
|
+
ButtonParameter,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T", bound=Parameter[Any])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseWebComponent(Generic[T], ABC):
|
|
25
|
+
"""
|
|
26
|
+
Abstract base class for all web components.
|
|
27
|
+
|
|
28
|
+
This class defines the interface for HTML/JS components that correspond
|
|
29
|
+
to different parameter types in the web deployment.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, parameter: T, component_id: str):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the web component.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
parameter: The parameter this component represents
|
|
38
|
+
component_id: Unique ID for this component in the DOM
|
|
39
|
+
"""
|
|
40
|
+
self.parameter = parameter
|
|
41
|
+
self.component_id = component_id
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def render_html(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Render the HTML for this component.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: HTML markup for the component
|
|
50
|
+
"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def get_js_init(self) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Get JavaScript code needed to initialize this component.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
str: JavaScript code that sets up event listeners etc.
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def get_js_update(self, value: Any) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Get JavaScript code needed to update this component's value.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
value: New value to set
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: JavaScript code that updates the component's value
|
|
72
|
+
"""
|
|
73
|
+
# Default implementation for simple components
|
|
74
|
+
return f"document.getElementById('{self.component_id}').value = {self._value_to_js(value)};"
|
|
75
|
+
|
|
76
|
+
def _value_to_js(self, value: Any) -> str:
|
|
77
|
+
"""Convert a Python value to its JavaScript representation."""
|
|
78
|
+
if isinstance(value, bool):
|
|
79
|
+
return str(value).lower()
|
|
80
|
+
elif isinstance(value, (int, float)):
|
|
81
|
+
return str(value)
|
|
82
|
+
elif isinstance(value, str):
|
|
83
|
+
return f"'{value}'"
|
|
84
|
+
elif isinstance(value, (list, tuple)):
|
|
85
|
+
return f"[{', '.join(self._value_to_js(v) for v in value)}]"
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError(f"Cannot convert {type(value)} to JavaScript")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TextComponent(BaseWebComponent[TextParameter]):
|
|
91
|
+
"""Component for text input parameters."""
|
|
92
|
+
|
|
93
|
+
def render_html(self) -> str:
|
|
94
|
+
return f"""
|
|
95
|
+
<div class="form-group">
|
|
96
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
97
|
+
<input type="text"
|
|
98
|
+
class="form-control"
|
|
99
|
+
id="{self.component_id}"
|
|
100
|
+
value="{self.parameter.value}">
|
|
101
|
+
</div>
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def get_js_init(self) -> str:
|
|
105
|
+
return f"""
|
|
106
|
+
document.getElementById('{self.component_id}').addEventListener('change', (e) => {{
|
|
107
|
+
updateParameter('{self.parameter.name}', e.target.value);
|
|
108
|
+
}});
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class BooleanComponent(BaseWebComponent[BooleanParameter]):
|
|
113
|
+
"""Component for boolean parameters."""
|
|
114
|
+
|
|
115
|
+
def render_html(self) -> str:
|
|
116
|
+
checked = "checked" if self.parameter.value else ""
|
|
117
|
+
return f"""
|
|
118
|
+
<div class="form-check">
|
|
119
|
+
<input type="checkbox"
|
|
120
|
+
class="form-check-input"
|
|
121
|
+
id="{self.component_id}"
|
|
122
|
+
{checked}>
|
|
123
|
+
<label class="form-check-label"
|
|
124
|
+
for="{self.component_id}">
|
|
125
|
+
{self.parameter.name}
|
|
126
|
+
</label>
|
|
127
|
+
</div>
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def get_js_init(self) -> str:
|
|
131
|
+
return f"""
|
|
132
|
+
document.getElementById('{self.component_id}').addEventListener('change', (e) => {{
|
|
133
|
+
updateParameter('{self.parameter.name}', e.target.checked);
|
|
134
|
+
}});
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def get_js_update(self, value: bool) -> str:
|
|
138
|
+
return f"document.getElementById('{self.component_id}').checked = {str(value).lower()};"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class SelectionComponent(BaseWebComponent[SelectionParameter]):
|
|
142
|
+
"""Component for single selection parameters."""
|
|
143
|
+
|
|
144
|
+
def render_html(self) -> str:
|
|
145
|
+
options = []
|
|
146
|
+
for opt in self.parameter.options:
|
|
147
|
+
selected = "selected" if opt == self.parameter.value else ""
|
|
148
|
+
options.append(f'<option value="{opt}" {selected}>{opt}</option>')
|
|
149
|
+
|
|
150
|
+
return f"""
|
|
151
|
+
<div class="form-group">
|
|
152
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
153
|
+
<select class="form-control" id="{self.component_id}">
|
|
154
|
+
{"".join(options)}
|
|
155
|
+
</select>
|
|
156
|
+
</div>
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def get_js_init(self) -> str:
|
|
160
|
+
return f"""
|
|
161
|
+
document.getElementById('{self.component_id}').addEventListener('change', (e) => {{
|
|
162
|
+
updateParameter('{self.parameter.name}', e.target.value);
|
|
163
|
+
}});
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class MultipleSelectionComponent(BaseWebComponent[MultipleSelectionParameter]):
|
|
168
|
+
"""Component for multiple selection parameters."""
|
|
169
|
+
|
|
170
|
+
def render_html(self) -> str:
|
|
171
|
+
options = []
|
|
172
|
+
for opt in self.parameter.options:
|
|
173
|
+
selected = "selected" if opt in self.parameter.value else ""
|
|
174
|
+
options.append(f'<option value="{opt}" {selected}>{opt}</option>')
|
|
175
|
+
|
|
176
|
+
return f"""
|
|
177
|
+
<div class="form-group">
|
|
178
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
179
|
+
<select multiple class="form-control" id="{self.component_id}">
|
|
180
|
+
{"".join(options)}
|
|
181
|
+
</select>
|
|
182
|
+
</div>
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def get_js_init(self) -> str:
|
|
186
|
+
return f"""
|
|
187
|
+
document.getElementById('{self.component_id}').addEventListener('change', (e) => {{
|
|
188
|
+
const selected = Array.from(e.target.selectedOptions).map(opt => opt.value);
|
|
189
|
+
updateParameter('{self.parameter.name}', selected);
|
|
190
|
+
}});
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def get_js_update(self, values: List[str]) -> str:
|
|
194
|
+
# More complex update for multi-select
|
|
195
|
+
return f"""
|
|
196
|
+
const sel = document.getElementById('{self.component_id}');
|
|
197
|
+
Array.from(sel.options).forEach(opt => {{
|
|
198
|
+
opt.selected = {self._value_to_js(values)}.includes(opt.value);
|
|
199
|
+
}});
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class SliderMixin:
|
|
204
|
+
"""Shared functionality for slider components."""
|
|
205
|
+
|
|
206
|
+
def _get_slider_js_init(self, component_id: str, param_name: str) -> str:
|
|
207
|
+
return f"""
|
|
208
|
+
noUiSlider.create(document.getElementById('{component_id}'), {{
|
|
209
|
+
start: {self._value_to_js(self.parameter.value)},
|
|
210
|
+
connect: true,
|
|
211
|
+
range: {{
|
|
212
|
+
'min': {self.parameter.min_value},
|
|
213
|
+
'max': {self.parameter.max_value}
|
|
214
|
+
}}
|
|
215
|
+
}}).on('change', (values) => {{
|
|
216
|
+
updateParameter('{param_name}', parseFloat(values[0]));
|
|
217
|
+
const value = parseFloat(values[0]);
|
|
218
|
+
// Update the display text
|
|
219
|
+
document.getElementById('{component_id}_display').textContent = value.toFixed(2);
|
|
220
|
+
debouncedUpdateParameter('{param_name}', value);
|
|
221
|
+
}});
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class IntegerComponent(SliderMixin, BaseWebComponent[IntegerParameter]):
|
|
226
|
+
"""Component for integer parameters."""
|
|
227
|
+
|
|
228
|
+
def render_html(self) -> str:
|
|
229
|
+
return f"""
|
|
230
|
+
<div class="form-group">
|
|
231
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
232
|
+
<div id="{self.component_id}" class="slider"></div>
|
|
233
|
+
<span id="{self.component_id}_display" class="value-display">{self.parameter.value}</span>
|
|
234
|
+
</div>
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
def get_js_init(self) -> str:
|
|
238
|
+
return self._get_slider_js_init(self.component_id, self.parameter.name)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class FloatComponent(SliderMixin, BaseWebComponent[FloatParameter]):
|
|
242
|
+
"""Component for float parameters."""
|
|
243
|
+
|
|
244
|
+
def render_html(self) -> str:
|
|
245
|
+
return f"""
|
|
246
|
+
<div class="form-group">
|
|
247
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
248
|
+
<div id="{self.component_id}" class="slider"></div>
|
|
249
|
+
<span id="{self.component_id}_display" class="value-display">{self.parameter.value:.2f}</span>
|
|
250
|
+
</div>
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
def get_js_init(self) -> str:
|
|
254
|
+
return self._get_slider_js_init(self.component_id, self.parameter.name)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class RangeSliderMixin:
|
|
258
|
+
"""Shared functionality for range slider components."""
|
|
259
|
+
|
|
260
|
+
def _get_range_slider_js_init(self, component_id: str, param_name: str) -> str:
|
|
261
|
+
return f"""
|
|
262
|
+
noUiSlider.create(document.getElementById('{component_id}'), {{
|
|
263
|
+
start: {self._value_to_js(self.parameter.value)},
|
|
264
|
+
connect: true,
|
|
265
|
+
range: {{
|
|
266
|
+
'min': {self.parameter.min_value},
|
|
267
|
+
'max': {self.parameter.max_value}
|
|
268
|
+
}}
|
|
269
|
+
}}).on('change', (values) => {{
|
|
270
|
+
updateParameter('{param_name}', [
|
|
271
|
+
parseFloat(values[0]),
|
|
272
|
+
parseFloat(values[1])
|
|
273
|
+
]);
|
|
274
|
+
}});
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class IntegerRangeComponent(RangeSliderMixin, BaseWebComponent[IntegerRangeParameter]):
|
|
279
|
+
"""Component for integer range parameters."""
|
|
280
|
+
|
|
281
|
+
def render_html(self) -> str:
|
|
282
|
+
low, high = self.parameter.value
|
|
283
|
+
return f"""
|
|
284
|
+
<div class="form-group">
|
|
285
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
286
|
+
<div id="{self.component_id}" class="range-slider"></div>
|
|
287
|
+
<span class="value-display">{low} - {high}</span>
|
|
288
|
+
</div>
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
def get_js_init(self) -> str:
|
|
292
|
+
return self._get_range_slider_js_init(self.component_id, self.parameter.name)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class FloatRangeComponent(RangeSliderMixin, BaseWebComponent[FloatRangeParameter]):
|
|
296
|
+
"""Component for float range parameters."""
|
|
297
|
+
|
|
298
|
+
def render_html(self) -> str:
|
|
299
|
+
low, high = self.parameter.value
|
|
300
|
+
return f"""
|
|
301
|
+
<div class="form-group">
|
|
302
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
303
|
+
<div id="{self.component_id}" class="range-slider"></div>
|
|
304
|
+
<span class="value-display">{low:.2f} - {high:.2f}</span>
|
|
305
|
+
</div>
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
def get_js_init(self) -> str:
|
|
309
|
+
return self._get_range_slider_js_init(self.component_id, self.parameter.name)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class UnboundedIntegerComponent(BaseWebComponent[UnboundedIntegerParameter]):
|
|
313
|
+
"""Component for unbounded integer parameters."""
|
|
314
|
+
|
|
315
|
+
def render_html(self) -> str:
|
|
316
|
+
min_attr = (
|
|
317
|
+
f'min="{self.parameter.min_value}"'
|
|
318
|
+
if self.parameter.min_value is not None
|
|
319
|
+
else ""
|
|
320
|
+
)
|
|
321
|
+
max_attr = (
|
|
322
|
+
f'max="{self.parameter.max_value}"'
|
|
323
|
+
if self.parameter.max_value is not None
|
|
324
|
+
else ""
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
return f"""
|
|
328
|
+
<div class="form-group">
|
|
329
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
330
|
+
<input type="number"
|
|
331
|
+
class="form-control"
|
|
332
|
+
id="{self.component_id}"
|
|
333
|
+
value="{self.parameter.value}"
|
|
334
|
+
{min_attr}
|
|
335
|
+
{max_attr}
|
|
336
|
+
step="1">
|
|
337
|
+
</div>
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
def get_js_init(self) -> str:
|
|
341
|
+
return f"""
|
|
342
|
+
document.getElementById('{self.component_id}').addEventListener('change', (e) => {{
|
|
343
|
+
updateParameter('{self.parameter.name}', parseInt(e.target.value));
|
|
344
|
+
}});
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class UnboundedFloatComponent(BaseWebComponent[UnboundedFloatParameter]):
|
|
349
|
+
"""Component for unbounded float parameters."""
|
|
350
|
+
|
|
351
|
+
def render_html(self) -> str:
|
|
352
|
+
min_attr = (
|
|
353
|
+
f'min="{self.parameter.min_value}"'
|
|
354
|
+
if self.parameter.min_value is not None
|
|
355
|
+
else ""
|
|
356
|
+
)
|
|
357
|
+
max_attr = (
|
|
358
|
+
f'max="{self.parameter.max_value}"'
|
|
359
|
+
if self.parameter.max_value is not None
|
|
360
|
+
else ""
|
|
361
|
+
)
|
|
362
|
+
step_attr = (
|
|
363
|
+
f'step="{self.parameter.step}"' if self.parameter.step is not None else ""
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
return f"""
|
|
367
|
+
<div class="form-group">
|
|
368
|
+
<label for="{self.component_id}">{self.parameter.name}</label>
|
|
369
|
+
<input type="number"
|
|
370
|
+
class="form-control"
|
|
371
|
+
id="{self.component_id}"
|
|
372
|
+
value="{self.parameter.value}"
|
|
373
|
+
{min_attr}
|
|
374
|
+
{max_attr}
|
|
375
|
+
{step_attr}>
|
|
376
|
+
</div>
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
def get_js_init(self) -> str:
|
|
380
|
+
return f"""
|
|
381
|
+
document.getElementById('{self.component_id}').addEventListener('change', (e) => {{
|
|
382
|
+
updateParameter('{self.parameter.name}', parseFloat(e.target.value));
|
|
383
|
+
}});
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class ButtonComponent(BaseWebComponent[ButtonParameter]):
|
|
388
|
+
"""Component for button parameters."""
|
|
389
|
+
|
|
390
|
+
def render_html(self) -> str:
|
|
391
|
+
return f"""
|
|
392
|
+
<div class="form-group">
|
|
393
|
+
<button type="button"
|
|
394
|
+
class="btn btn-primary"
|
|
395
|
+
id="{self.component_id}">
|
|
396
|
+
{self.parameter.label}
|
|
397
|
+
</button>
|
|
398
|
+
</div>
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
def get_js_init(self) -> str:
|
|
402
|
+
return f"""
|
|
403
|
+
document.getElementById('{self.component_id}').addEventListener('click', () => {{
|
|
404
|
+
buttonClick('{self.parameter.name}');
|
|
405
|
+
}});
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def create_web_component(
|
|
410
|
+
parameter: Parameter[Any], component_id: str
|
|
411
|
+
) -> BaseWebComponent[Parameter[Any]]:
|
|
412
|
+
"""Create and return the appropriate web component for the given parameter."""
|
|
413
|
+
|
|
414
|
+
component_map = {
|
|
415
|
+
TextParameter: TextComponent,
|
|
416
|
+
BooleanParameter: BooleanComponent,
|
|
417
|
+
SelectionParameter: SelectionComponent,
|
|
418
|
+
MultipleSelectionParameter: MultipleSelectionComponent,
|
|
419
|
+
IntegerParameter: IntegerComponent,
|
|
420
|
+
FloatParameter: FloatComponent,
|
|
421
|
+
IntegerRangeParameter: IntegerRangeComponent,
|
|
422
|
+
FloatRangeParameter: FloatRangeComponent,
|
|
423
|
+
UnboundedIntegerParameter: UnboundedIntegerComponent,
|
|
424
|
+
UnboundedFloatParameter: UnboundedFloatComponent,
|
|
425
|
+
ButtonParameter: ButtonComponent,
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
component_class = component_map.get(type(parameter))
|
|
429
|
+
if component_class is None:
|
|
430
|
+
raise ValueError(
|
|
431
|
+
f"No component implementation for parameter type: {type(parameter)}"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
return component_class(parameter, component_id)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class WebComponentCollection:
|
|
438
|
+
"""
|
|
439
|
+
Manages a collection of web components for a viewer's parameters.
|
|
440
|
+
|
|
441
|
+
This class helps organize all the components needed for a viewer,
|
|
442
|
+
handling their creation, initialization, and updates.
|
|
443
|
+
"""
|
|
444
|
+
|
|
445
|
+
def __init__(self):
|
|
446
|
+
self.components: Dict[str, BaseWebComponent] = {}
|
|
447
|
+
|
|
448
|
+
def add_component(self, name: str, parameter: Parameter[Any]) -> None:
|
|
449
|
+
"""Add a new component for the given parameter."""
|
|
450
|
+
component_id = f"param_{name}"
|
|
451
|
+
self.components[name] = create_web_component(parameter, component_id)
|
|
452
|
+
|
|
453
|
+
def get_all_html(self) -> str:
|
|
454
|
+
"""Get the combined HTML for all components."""
|
|
455
|
+
return "\n".join(comp.render_html() for comp in self.components.values())
|
|
456
|
+
|
|
457
|
+
def get_init_js(self) -> str:
|
|
458
|
+
"""Get the combined initialization JavaScript for all components."""
|
|
459
|
+
return "\n".join(comp.get_js_init() for comp in self.components.values())
|
|
460
|
+
|
|
461
|
+
def get_update_js(self, name: str, value: Any) -> str:
|
|
462
|
+
"""Get the JavaScript to update a specific component's value."""
|
|
463
|
+
if name not in self.components:
|
|
464
|
+
raise ValueError(f"No component found for parameter: {name}")
|
|
465
|
+
return self.components[name].get_js_update(value)
|
|
466
|
+
|
|
467
|
+
def get_required_css(self) -> List[str]:
|
|
468
|
+
"""Get list of CSS files required by the components."""
|
|
469
|
+
return [
|
|
470
|
+
"https://cdn.jsdelivr.net/npm/nouislider@14.6.3/distribute/nouislider.min.css",
|
|
471
|
+
"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css",
|
|
472
|
+
]
|
|
473
|
+
|
|
474
|
+
def get_required_js(self) -> List[str]:
|
|
475
|
+
"""Get list of JavaScript files required by the components."""
|
|
476
|
+
return [
|
|
477
|
+
"https://cdn.jsdelivr.net/npm/nouislider@14.6.3/distribute/nouislider.min.js",
|
|
478
|
+
"https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js",
|
|
479
|
+
]
|
|
480
|
+
|
|
481
|
+
def get_custom_styles(self) -> str:
|
|
482
|
+
"""Get custom CSS styles needed for the components."""
|
|
483
|
+
return """
|
|
484
|
+
.slider, .range-slider {
|
|
485
|
+
margin: 10px 0;
|
|
486
|
+
}
|
|
487
|
+
.value-display {
|
|
488
|
+
display: block;
|
|
489
|
+
text-align: center;
|
|
490
|
+
margin-top: 5px;
|
|
491
|
+
font-size: 0.9em;
|
|
492
|
+
color: #666;
|
|
493
|
+
}
|
|
494
|
+
.form-group {
|
|
495
|
+
margin-bottom: 1rem;
|
|
496
|
+
}
|
|
497
|
+
"""
|