syd 0.1.5__py3-none-any.whl → 0.1.7__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 +7 -11
- syd/flask_deployment/__init__.py +1 -0
- syd/flask_deployment/components.py +510 -0
- syd/flask_deployment/deployer.py +302 -0
- syd/flask_deployment/static/css/viewer.css +82 -0
- syd/flask_deployment/static/js/viewer.js +174 -0
- syd/flask_deployment/templates/base.html +29 -0
- syd/flask_deployment/templates/viewer.html +51 -0
- syd/flask_deployment/testing_principles.md +300 -0
- syd/notebook_deployment/__init__.py +1 -0
- syd/{notebook_deploy/deployer.py → notebook_deployment/_ipympl_deployer.py} +57 -36
- syd/notebook_deployment/deployer.py +330 -0
- syd/{notebook_deploy → notebook_deployment}/widgets.py +192 -112
- syd/parameters.py +390 -194
- syd/plotly_deployment/__init__.py +1 -0
- syd/plotly_deployment/components.py +531 -0
- syd/plotly_deployment/deployer.py +376 -0
- syd/{interactive_viewer.py → viewer.py} +309 -176
- syd-0.1.7.dist-info/METADATA +120 -0
- syd-0.1.7.dist-info/RECORD +22 -0
- syd/notebook_deploy/__init__.py +0 -1
- syd-0.1.5.dist-info/METADATA +0 -41
- syd-0.1.5.dist-info/RECORD +0 -10
- {syd-0.1.5.dist-info → syd-0.1.7.dist-info}/WHEEL +0 -0
- {syd-0.1.5.dist-info → syd-0.1.7.dist-info}/licenses/LICENSE +0 -0
syd/__init__.py
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
from typing import Callable
|
|
2
|
-
from .
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
from .viewer import Viewer
|
|
3
3
|
|
|
4
|
-
__version__ = "0.1.
|
|
4
|
+
__version__ = "0.1.7"
|
|
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 = Viewer()
|
|
9
|
+
if plot_func is not None:
|
|
10
|
+
viewer.set_plot(plot_func)
|
|
15
11
|
return viewer
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .deployer import FlaskDeployer
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, Generic, List, TypeVar, Union, Callable, Optional
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from html import escape
|
|
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
|
+
ButtonAction,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T", bound=Parameter[Any])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ComponentStyle:
|
|
26
|
+
"""Style configuration for components."""
|
|
27
|
+
|
|
28
|
+
width: str = "auto"
|
|
29
|
+
margin: str = "3px 0px"
|
|
30
|
+
description_width: str = "initial"
|
|
31
|
+
input_class: str = "form-control"
|
|
32
|
+
label_class: str = "form-label"
|
|
33
|
+
container_class: str = "mb-3"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BaseComponent(Generic[T], ABC):
|
|
37
|
+
"""
|
|
38
|
+
Abstract base class for all parameter components.
|
|
39
|
+
|
|
40
|
+
This class defines the common interface and shared functionality
|
|
41
|
+
for components that correspond to different parameter types.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
_callbacks: List[Callable]
|
|
45
|
+
_is_action: bool = False
|
|
46
|
+
_value: Any
|
|
47
|
+
_id: str
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
parameter: T,
|
|
52
|
+
continuous: bool = False,
|
|
53
|
+
style: Optional[ComponentStyle] = None,
|
|
54
|
+
):
|
|
55
|
+
self._id = f"param_{parameter.name}"
|
|
56
|
+
self._value = parameter.value
|
|
57
|
+
self._callbacks = []
|
|
58
|
+
self._continuous = continuous
|
|
59
|
+
self._style = style or ComponentStyle()
|
|
60
|
+
self._html = self._create_html(parameter)
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def _create_html(self, parameter: T) -> str:
|
|
64
|
+
"""Create and return the appropriate HTML markup."""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def html(self) -> str:
|
|
69
|
+
"""Get the HTML representation of the component."""
|
|
70
|
+
return self._html
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def value(self) -> Any:
|
|
74
|
+
"""Get the current value of the component."""
|
|
75
|
+
return self._value
|
|
76
|
+
|
|
77
|
+
@value.setter
|
|
78
|
+
def value(self, new_value: Any) -> None:
|
|
79
|
+
"""Set the value of the component."""
|
|
80
|
+
self._value = new_value
|
|
81
|
+
# In a real implementation, we'd use JavaScript to update the DOM
|
|
82
|
+
# This would be handled by the Flask app's frontend code
|
|
83
|
+
|
|
84
|
+
def matches_parameter(self, parameter: T) -> bool:
|
|
85
|
+
"""Check if the component matches the parameter."""
|
|
86
|
+
return self.value == parameter.value
|
|
87
|
+
|
|
88
|
+
def update_from_parameter(self, parameter: T) -> None:
|
|
89
|
+
"""Update the component from the parameter."""
|
|
90
|
+
try:
|
|
91
|
+
self.disable_callbacks()
|
|
92
|
+
self.extra_updates_from_parameter(parameter)
|
|
93
|
+
self.value = parameter.value
|
|
94
|
+
finally:
|
|
95
|
+
self.reenable_callbacks()
|
|
96
|
+
|
|
97
|
+
def extra_updates_from_parameter(self, parameter: T) -> None:
|
|
98
|
+
"""Extra updates from the parameter."""
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
def observe(self, callback: Callable) -> None:
|
|
102
|
+
"""Register a callback for value changes."""
|
|
103
|
+
self._callbacks.append(callback)
|
|
104
|
+
|
|
105
|
+
def unobserve(self, callback: Callable) -> None:
|
|
106
|
+
"""Unregister a callback."""
|
|
107
|
+
self._callbacks.remove(callback)
|
|
108
|
+
|
|
109
|
+
def reenable_callbacks(self) -> None:
|
|
110
|
+
"""Reenable all callbacks."""
|
|
111
|
+
pass # Handled by Flask routes and JavaScript
|
|
112
|
+
|
|
113
|
+
def disable_callbacks(self) -> None:
|
|
114
|
+
"""Disable all callbacks."""
|
|
115
|
+
pass # Handled by Flask routes and JavaScript
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TextComponent(BaseComponent[TextParameter]):
|
|
119
|
+
"""Component for text parameters."""
|
|
120
|
+
|
|
121
|
+
def _create_html(self, parameter: TextParameter) -> str:
|
|
122
|
+
return f"""
|
|
123
|
+
<div class="{self._style.container_class}">
|
|
124
|
+
<label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
125
|
+
<input type="text" class="{self._style.input_class}" id="{self._id}"
|
|
126
|
+
name="{parameter.name}" value="{escape(str(parameter.value))}"
|
|
127
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
128
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
129
|
+
</div>
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class BooleanComponent(BaseComponent[BooleanParameter]):
|
|
134
|
+
"""Component for boolean parameters."""
|
|
135
|
+
|
|
136
|
+
def _create_html(self, parameter: BooleanParameter) -> str:
|
|
137
|
+
checked = "checked" if parameter.value else ""
|
|
138
|
+
return f"""
|
|
139
|
+
<div class="{self._style.container_class}">
|
|
140
|
+
<div class="form-check">
|
|
141
|
+
<input type="checkbox" class="form-check-input" id="{self._id}"
|
|
142
|
+
name="{parameter.name}" {checked}
|
|
143
|
+
style="margin: {self._style.margin};"
|
|
144
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
145
|
+
<label class="form-check-label" for="{self._id}">
|
|
146
|
+
{escape(parameter.name)}
|
|
147
|
+
</label>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class SelectionComponent(BaseComponent[SelectionParameter]):
|
|
154
|
+
"""Component for single selection parameters."""
|
|
155
|
+
|
|
156
|
+
def _create_html(self, parameter: SelectionParameter) -> str:
|
|
157
|
+
options_html = ""
|
|
158
|
+
for option in parameter.options:
|
|
159
|
+
selected = "selected" if option == parameter.value else ""
|
|
160
|
+
options_html += f'<option value="{escape(str(option))}" {selected}>{escape(str(option))}</option>'
|
|
161
|
+
|
|
162
|
+
return f"""
|
|
163
|
+
<div class="{self._style.container_class}">
|
|
164
|
+
<label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
165
|
+
<select class="{self._style.input_class}" id="{self._id}"
|
|
166
|
+
name="{parameter.name}"
|
|
167
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
168
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
169
|
+
{options_html}
|
|
170
|
+
</select>
|
|
171
|
+
</div>
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def matches_parameter(self, parameter: SelectionParameter) -> bool:
|
|
175
|
+
"""Check if the component matches the parameter."""
|
|
176
|
+
return self.value == parameter.value and set(self._get_options()) == set(
|
|
177
|
+
parameter.options
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def _get_options(self) -> List[Any]:
|
|
181
|
+
"""Get current options from the HTML."""
|
|
182
|
+
# In a real implementation, this would parse the HTML
|
|
183
|
+
# For now, we'll just return an empty list
|
|
184
|
+
return []
|
|
185
|
+
|
|
186
|
+
def extra_updates_from_parameter(self, parameter: SelectionParameter) -> None:
|
|
187
|
+
"""Extra updates from the parameter."""
|
|
188
|
+
# In a real implementation, this would update the options in the DOM
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class MultipleSelectionComponent(BaseComponent[MultipleSelectionParameter]):
|
|
193
|
+
"""Component for multiple selection parameters."""
|
|
194
|
+
|
|
195
|
+
def _create_html(self, parameter: MultipleSelectionParameter) -> str:
|
|
196
|
+
options_html = ""
|
|
197
|
+
for option in parameter.options:
|
|
198
|
+
selected = "selected" if option in parameter.value else ""
|
|
199
|
+
options_html += f'<option value="{escape(str(option))}" {selected}>{escape(str(option))}</option>'
|
|
200
|
+
|
|
201
|
+
return f"""
|
|
202
|
+
<div class="{self._style.container_class}">
|
|
203
|
+
<label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
204
|
+
<select class="{self._style.input_class}" id="{self._id}"
|
|
205
|
+
name="{parameter.name}" multiple
|
|
206
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
207
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
208
|
+
{options_html}
|
|
209
|
+
</select>
|
|
210
|
+
</div>
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def matches_parameter(self, parameter: MultipleSelectionParameter) -> bool:
|
|
214
|
+
"""Check if the component matches the parameter."""
|
|
215
|
+
return set(self.value) == set(parameter.value) and set(
|
|
216
|
+
self._get_options()
|
|
217
|
+
) == set(parameter.options)
|
|
218
|
+
|
|
219
|
+
def _get_options(self) -> List[Any]:
|
|
220
|
+
"""Get current options from the HTML."""
|
|
221
|
+
# In a real implementation, this would parse the HTML
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
def extra_updates_from_parameter(
|
|
225
|
+
self, parameter: MultipleSelectionParameter
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Extra updates from the parameter."""
|
|
228
|
+
# In a real implementation, this would update the options in the DOM
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class IntegerComponent(BaseComponent[IntegerParameter]):
|
|
233
|
+
"""Component for integer parameters."""
|
|
234
|
+
|
|
235
|
+
def _create_html(self, parameter: IntegerParameter) -> str:
|
|
236
|
+
return f"""
|
|
237
|
+
<div class="{self._style.container_class}">
|
|
238
|
+
<label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
239
|
+
<input type="range" class="{self._style.input_class}" id="{self._id}"
|
|
240
|
+
name="{parameter.name}" value="{parameter.value}"
|
|
241
|
+
min="{parameter.min_value}" max="{parameter.max_value}"
|
|
242
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
243
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
244
|
+
<output for="{self._id}">{parameter.value}</output>
|
|
245
|
+
</div>
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
def matches_parameter(self, parameter: IntegerParameter) -> bool:
|
|
249
|
+
"""Check if the component matches the parameter."""
|
|
250
|
+
return (
|
|
251
|
+
self.value == parameter.value
|
|
252
|
+
and self._get_min() == parameter.min_value
|
|
253
|
+
and self._get_max() == parameter.max_value
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def _get_min(self) -> int:
|
|
257
|
+
"""Get minimum value from the HTML."""
|
|
258
|
+
return 0 # Placeholder
|
|
259
|
+
|
|
260
|
+
def _get_max(self) -> int:
|
|
261
|
+
"""Get maximum value from the HTML."""
|
|
262
|
+
return 100 # Placeholder
|
|
263
|
+
|
|
264
|
+
def extra_updates_from_parameter(self, parameter: IntegerParameter) -> None:
|
|
265
|
+
"""Extra updates from the parameter."""
|
|
266
|
+
# In a real implementation, this would update min/max in the DOM
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class FloatComponent(BaseComponent[FloatParameter]):
|
|
271
|
+
"""Component for float parameters."""
|
|
272
|
+
|
|
273
|
+
def _create_html(self, parameter: FloatParameter) -> str:
|
|
274
|
+
return f"""
|
|
275
|
+
<div class="{self._style.container_class}">
|
|
276
|
+
<label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
277
|
+
<input type="range" class="{self._style.input_class}" id="{self._id}"
|
|
278
|
+
name="{parameter.name}" value="{parameter.value}"
|
|
279
|
+
min="{parameter.min_value}" max="{parameter.max_value}" step="{parameter.step}"
|
|
280
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
281
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
282
|
+
<output for="{self._id}">{parameter.value}</output>
|
|
283
|
+
</div>
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
def matches_parameter(self, parameter: FloatParameter) -> bool:
|
|
287
|
+
"""Check if the component matches the parameter."""
|
|
288
|
+
return (
|
|
289
|
+
self.value == parameter.value
|
|
290
|
+
and self._get_min() == parameter.min_value
|
|
291
|
+
and self._get_max() == parameter.max_value
|
|
292
|
+
and self._get_step() == parameter.step
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def _get_min(self) -> float:
|
|
296
|
+
"""Get minimum value from the HTML."""
|
|
297
|
+
return 0.0 # Placeholder
|
|
298
|
+
|
|
299
|
+
def _get_max(self) -> float:
|
|
300
|
+
"""Get maximum value from the HTML."""
|
|
301
|
+
return 1.0 # Placeholder
|
|
302
|
+
|
|
303
|
+
def _get_step(self) -> float:
|
|
304
|
+
"""Get step value from the HTML."""
|
|
305
|
+
return 0.1 # Placeholder
|
|
306
|
+
|
|
307
|
+
def extra_updates_from_parameter(self, parameter: FloatParameter) -> None:
|
|
308
|
+
"""Extra updates from the parameter."""
|
|
309
|
+
# In a real implementation, this would update min/max/step in the DOM
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class IntegerRangeComponent(BaseComponent[IntegerRangeParameter]):
|
|
314
|
+
"""Component for integer range parameters."""
|
|
315
|
+
|
|
316
|
+
def _create_html(self, parameter: IntegerRangeParameter) -> str:
|
|
317
|
+
low, high = parameter.value
|
|
318
|
+
return f"""
|
|
319
|
+
<div class="{self._style.container_class}">
|
|
320
|
+
<label for="{self._id}_low" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
321
|
+
<div class="d-flex align-items-center">
|
|
322
|
+
<input type="range" class="{self._style.input_class}" id="{self._id}_low"
|
|
323
|
+
name="{parameter.name}_low" value="{low}"
|
|
324
|
+
min="{parameter.min_value}" max="{parameter.max_value}"
|
|
325
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
326
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
327
|
+
<output for="{self._id}_low">{low}</output>
|
|
328
|
+
</div>
|
|
329
|
+
<div class="d-flex align-items-center">
|
|
330
|
+
<input type="range" class="{self._style.input_class}" id="{self._id}_high"
|
|
331
|
+
name="{parameter.name}_high" value="{high}"
|
|
332
|
+
min="{parameter.min_value}" max="{parameter.max_value}"
|
|
333
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
334
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
335
|
+
<output for="{self._id}_high">{high}</output>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
def matches_parameter(self, parameter: IntegerRangeParameter) -> bool:
|
|
341
|
+
"""Check if the component matches the parameter."""
|
|
342
|
+
return (
|
|
343
|
+
self.value == parameter.value
|
|
344
|
+
and self._get_min() == parameter.min_value
|
|
345
|
+
and self._get_max() == parameter.max_value
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
def _get_min(self) -> int:
|
|
349
|
+
"""Get minimum value from the HTML."""
|
|
350
|
+
return 0 # Placeholder
|
|
351
|
+
|
|
352
|
+
def _get_max(self) -> int:
|
|
353
|
+
"""Get maximum value from the HTML."""
|
|
354
|
+
return 100 # Placeholder
|
|
355
|
+
|
|
356
|
+
def extra_updates_from_parameter(self, parameter: IntegerRangeParameter) -> None:
|
|
357
|
+
"""Extra updates from the parameter."""
|
|
358
|
+
# In a real implementation, this would update min/max in the DOM
|
|
359
|
+
pass
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class FloatRangeComponent(BaseComponent[FloatRangeParameter]):
|
|
363
|
+
"""Component for float range parameters."""
|
|
364
|
+
|
|
365
|
+
def _create_html(self, parameter: FloatRangeParameter) -> str:
|
|
366
|
+
low, high = parameter.value
|
|
367
|
+
return f"""
|
|
368
|
+
<div class="{self._style.container_class}">
|
|
369
|
+
<label for="{self._id}_low" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
370
|
+
<div class="d-flex align-items-center">
|
|
371
|
+
<input type="range" class="{self._style.input_class}" id="{self._id}_low"
|
|
372
|
+
name="{parameter.name}_low" value="{low}"
|
|
373
|
+
min="{parameter.min_value}" max="{parameter.max_value}" step="{parameter.step}"
|
|
374
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
375
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
376
|
+
<output for="{self._id}_low">{low}</output>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="d-flex align-items-center">
|
|
379
|
+
<input type="range" class="{self._style.input_class}" id="{self._id}_high"
|
|
380
|
+
name="{parameter.name}_high" value="{high}"
|
|
381
|
+
min="{parameter.min_value}" max="{parameter.max_value}" step="{parameter.step}"
|
|
382
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
383
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
384
|
+
<output for="{self._id}_high">{high}</output>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
def matches_parameter(self, parameter: FloatRangeParameter) -> bool:
|
|
390
|
+
"""Check if the component matches the parameter."""
|
|
391
|
+
return (
|
|
392
|
+
self.value == parameter.value
|
|
393
|
+
and self._get_min() == parameter.min_value
|
|
394
|
+
and self._get_max() == parameter.max_value
|
|
395
|
+
and self._get_step() == parameter.step
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def _get_min(self) -> float:
|
|
399
|
+
"""Get minimum value from the HTML."""
|
|
400
|
+
return 0.0 # Placeholder
|
|
401
|
+
|
|
402
|
+
def _get_max(self) -> float:
|
|
403
|
+
"""Get maximum value from the HTML."""
|
|
404
|
+
return 1.0 # Placeholder
|
|
405
|
+
|
|
406
|
+
def _get_step(self) -> float:
|
|
407
|
+
"""Get step value from the HTML."""
|
|
408
|
+
return 0.1 # Placeholder
|
|
409
|
+
|
|
410
|
+
def extra_updates_from_parameter(self, parameter: FloatRangeParameter) -> None:
|
|
411
|
+
"""Extra updates from the parameter."""
|
|
412
|
+
# In a real implementation, this would update min/max/step in the DOM
|
|
413
|
+
pass
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class UnboundedIntegerComponent(BaseComponent[UnboundedIntegerParameter]):
|
|
417
|
+
"""Component for unbounded integer parameters."""
|
|
418
|
+
|
|
419
|
+
def _create_html(self, parameter: UnboundedIntegerParameter) -> str:
|
|
420
|
+
return f"""
|
|
421
|
+
<div class="{self._style.container_class}">
|
|
422
|
+
<label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
423
|
+
<input type="number" class="{self._style.input_class}" id="{self._id}"
|
|
424
|
+
name="{parameter.name}" value="{parameter.value}"
|
|
425
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
426
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
427
|
+
</div>
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class UnboundedFloatComponent(BaseComponent[UnboundedFloatParameter]):
|
|
432
|
+
"""Component for unbounded float parameters."""
|
|
433
|
+
|
|
434
|
+
def _create_html(self, parameter: UnboundedFloatParameter) -> str:
|
|
435
|
+
step = parameter.step if parameter.step is not None else "any"
|
|
436
|
+
return f"""
|
|
437
|
+
<div class="{self._style.container_class}">
|
|
438
|
+
<label for="{self._id}" class="{self._style.label_class}">{escape(parameter.name)}</label>
|
|
439
|
+
<input type="number" class="{self._style.input_class}" id="{self._id}"
|
|
440
|
+
name="{parameter.name}" value="{parameter.value}" step="{step}"
|
|
441
|
+
style="width: {self._style.width}; margin: {self._style.margin};"
|
|
442
|
+
data-continuous="{str(self._continuous).lower()}">
|
|
443
|
+
</div>
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
def matches_parameter(self, parameter: UnboundedFloatParameter) -> bool:
|
|
447
|
+
"""Check if the component matches the parameter."""
|
|
448
|
+
return self.value == parameter.value and self._get_step() == parameter.step
|
|
449
|
+
|
|
450
|
+
def _get_step(self) -> Optional[float]:
|
|
451
|
+
"""Get step value from the HTML."""
|
|
452
|
+
return None # Placeholder
|
|
453
|
+
|
|
454
|
+
def extra_updates_from_parameter(self, parameter: UnboundedFloatParameter) -> None:
|
|
455
|
+
"""Extra updates from the parameter."""
|
|
456
|
+
# In a real implementation, this would update step in the DOM
|
|
457
|
+
pass
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class ButtonComponent(BaseComponent[ButtonAction]):
|
|
461
|
+
"""Component for button parameters."""
|
|
462
|
+
|
|
463
|
+
_is_action: bool = True
|
|
464
|
+
|
|
465
|
+
def _create_html(self, parameter: ButtonAction) -> str:
|
|
466
|
+
return f"""
|
|
467
|
+
<div class="{self._style.container_class}">
|
|
468
|
+
<button type="button" class="btn btn-primary" id="{self._id}"
|
|
469
|
+
name="{parameter.name}"
|
|
470
|
+
style="width: {self._style.width}; margin: {self._style.margin};">
|
|
471
|
+
{escape(parameter.label)}
|
|
472
|
+
</button>
|
|
473
|
+
</div>
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
def matches_parameter(self, parameter: ButtonAction) -> bool:
|
|
477
|
+
"""Check if the component matches the parameter."""
|
|
478
|
+
return True # Buttons don't have a value to match
|
|
479
|
+
|
|
480
|
+
def extra_updates_from_parameter(self, parameter: ButtonAction) -> None:
|
|
481
|
+
"""Extra updates from the parameter."""
|
|
482
|
+
# In a real implementation, this would update the button label in the DOM
|
|
483
|
+
pass
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def create_component(
|
|
487
|
+
parameter: Union[Parameter[Any], ButtonAction],
|
|
488
|
+
continuous: bool = False,
|
|
489
|
+
style: Optional[ComponentStyle] = None,
|
|
490
|
+
) -> BaseComponent[Union[Parameter[Any], ButtonAction]]:
|
|
491
|
+
"""Create the appropriate component for a parameter."""
|
|
492
|
+
component_map = {
|
|
493
|
+
TextParameter: TextComponent,
|
|
494
|
+
BooleanParameter: BooleanComponent,
|
|
495
|
+
SelectionParameter: SelectionComponent,
|
|
496
|
+
MultipleSelectionParameter: MultipleSelectionComponent,
|
|
497
|
+
IntegerParameter: IntegerComponent,
|
|
498
|
+
FloatParameter: FloatComponent,
|
|
499
|
+
IntegerRangeParameter: IntegerRangeComponent,
|
|
500
|
+
FloatRangeParameter: FloatRangeComponent,
|
|
501
|
+
UnboundedIntegerParameter: UnboundedIntegerComponent,
|
|
502
|
+
UnboundedFloatParameter: UnboundedFloatComponent,
|
|
503
|
+
ButtonAction: ButtonComponent,
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
for param_type, component_class in component_map.items():
|
|
507
|
+
if isinstance(parameter, param_type):
|
|
508
|
+
return component_class(parameter, continuous, style)
|
|
509
|
+
|
|
510
|
+
raise ValueError(f"No component available for parameter type: {type(parameter)}")
|