dars-framework 1.2.3__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.
- dars/__init__.py +0 -0
- dars/all.py +69 -0
- dars/cli/__init__.py +0 -0
- dars/cli/doctor/__init__.py +1 -0
- dars/cli/doctor/detect.py +154 -0
- dars/cli/doctor/doctor.py +176 -0
- dars/cli/doctor/installers.py +100 -0
- dars/cli/doctor/persist.py +62 -0
- dars/cli/doctor/preflight.py +33 -0
- dars/cli/doctor/ui.py +54 -0
- dars/cli/hot_reload.py +33 -0
- dars/cli/main.py +1107 -0
- dars/cli/preview.py +448 -0
- dars/cli/translations.py +531 -0
- dars/components/__init__.py +0 -0
- dars/components/advanced/__init__.py +8 -0
- dars/components/advanced/accordion.py +26 -0
- dars/components/advanced/card.py +33 -0
- dars/components/advanced/modal.py +45 -0
- dars/components/advanced/navbar.py +44 -0
- dars/components/advanced/table.py +25 -0
- dars/components/advanced/tabs.py +31 -0
- dars/components/basic/__init__.py +34 -0
- dars/components/basic/button.py +55 -0
- dars/components/basic/checkbox.py +35 -0
- dars/components/basic/container.py +29 -0
- dars/components/basic/datepicker.py +139 -0
- dars/components/basic/image.py +36 -0
- dars/components/basic/input.py +57 -0
- dars/components/basic/link.py +31 -0
- dars/components/basic/markdown.py +86 -0
- dars/components/basic/page.py +20 -0
- dars/components/basic/progressbar.py +18 -0
- dars/components/basic/radiobutton.py +35 -0
- dars/components/basic/select.py +82 -0
- dars/components/basic/slider.py +63 -0
- dars/components/basic/spinner.py +12 -0
- dars/components/basic/text.py +23 -0
- dars/components/basic/textarea.py +46 -0
- dars/components/basic/tooltip.py +19 -0
- dars/components/layout/__init__.py +0 -0
- dars/components/layout/anchor.py +13 -0
- dars/components/layout/flex.py +26 -0
- dars/components/layout/grid.py +45 -0
- dars/config.py +134 -0
- dars/core/__init__.py +0 -0
- dars/core/app.py +957 -0
- dars/core/component.py +284 -0
- dars/core/events.py +102 -0
- dars/core/js_bridge.py +99 -0
- dars/core/properties.py +127 -0
- dars/core/state.py +309 -0
- dars/dars_tests/apps_test/health_check.py +56 -0
- dars/dars_tests/run_tests.py +275 -0
- dars/dars_tests/tests/test_advanced_components.py +69 -0
- dars/dars_tests/tests/test_basic_components.py +88 -0
- dars/dars_tests/tests/test_core_and_cli.py +17 -0
- dars/dars_tests/tests/test_layout_components.py +58 -0
- dars/dars_tests/tests/test_version_check.py +21 -0
- dars/docs/__init__.py +0 -0
- dars/docs/app.md +290 -0
- dars/docs/cli.md +80 -0
- dars/docs/components.md +1679 -0
- dars/docs/custom_components.md +30 -0
- dars/docs/events.md +45 -0
- dars/docs/exporters.md +162 -0
- dars/docs/getting_started.md +79 -0
- dars/docs/index.md +18 -0
- dars/docs/scripts.md +593 -0
- dars/docs/state_management.md +57 -0
- dars/exporters/__init__.py +0 -0
- dars/exporters/base.py +96 -0
- dars/exporters/web/OLD/html_css_js_OLD4.py +1538 -0
- dars/exporters/web/OLD/html_css_js_old.py +1406 -0
- dars/exporters/web/OLD/html_css_js_old2.py +1406 -0
- dars/exporters/web/__init__.py +0 -0
- dars/exporters/web/html_css_js.py +2675 -0
- dars/exporters/web/vdom.py +251 -0
- dars/js_lib.py +206 -0
- dars/scripts/__init__.py +0 -0
- dars/scripts/dscript.py +26 -0
- dars/scripts/script.py +39 -0
- dars/security.py +195 -0
- dars/templates/__init__.py +0 -0
- dars/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- dars/templates/examples/README.md +4 -0
- dars/templates/examples/__pycache__/dynamic_event_demo.cpython-311.pyc +0 -0
- dars/templates/examples/advanced/Modal_Demo/advanced_modal_demo.py +275 -0
- dars/templates/examples/advanced/SimpleDashboard/dashboard.py +437 -0
- dars/templates/examples/advanced/SimpleModermWeb/modern_web_app.py +452 -0
- dars/templates/examples/advanced/VariousComponents/all_components_demo.py +87 -0
- dars/templates/examples/advanced/__init__.py +0 -0
- dars/templates/examples/advanced/dState/state_mods_demo.py +68 -0
- dars/templates/examples/basic/Forms/form_components.py +516 -0
- dars/templates/examples/basic/Forms/simple_form.py +379 -0
- dars/templates/examples/basic/HelloWorld/hello_world.py +56 -0
- dars/templates/examples/basic/Layouts/flex_layout_responsive.py +13 -0
- dars/templates/examples/basic/Layouts/grid_layout_responsive.py +12 -0
- dars/templates/examples/basic/Layouts/layout_multipage_demo.py +23 -0
- dars/templates/examples/basic/Multipage/multipage_example.py +67 -0
- dars/templates/examples/basic/PWA/icon-192x192.png +0 -0
- dars/templates/examples/basic/PWA/icon-512x512.png +0 -0
- dars/templates/examples/basic/PWA/pwa_custom_icons.py +33 -0
- dars/templates/examples/basic/__init__.py +0 -0
- dars/templates/examples/demo/__pycache__/complete_app.cpython-311.pyc +0 -0
- dars/templates/examples/demo/complete_app.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/README.md +159 -0
- dars/templates/examples/markdown/MarkdownTemplate/markdown_template.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/other_docs.md +1 -0
- dars/templates/examples/markdown/__init__.py +0 -0
- dars/templates/html/__init__.py +0 -0
- dars/version.py +2 -0
- dars_framework-1.2.3.dist-info/METADATA +15 -0
- dars_framework-1.2.3.dist-info/RECORD +118 -0
- dars_framework-1.2.3.dist-info/WHEEL +5 -0
- dars_framework-1.2.3.dist-info/entry_points.txt +2 -0
- dars_framework-1.2.3.dist-info/licenses/LICENSE +21 -0
- dars_framework-1.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
5
|
+
|
|
6
|
+
class RadioButton(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
label: str = "",
|
|
10
|
+
value: str = "",
|
|
11
|
+
name: str = "radio_group",
|
|
12
|
+
checked: bool = False,
|
|
13
|
+
id: Optional[str] = None,
|
|
14
|
+
class_name: Optional[str] = None,
|
|
15
|
+
style: Optional[Dict[str, Any]] = None,
|
|
16
|
+
disabled: bool = False,
|
|
17
|
+
required: bool = False,
|
|
18
|
+
on_change: Optional[Callable] = None,
|
|
19
|
+
**props
|
|
20
|
+
):
|
|
21
|
+
super().__init__(id=id, class_name=class_name, style=style, **props)
|
|
22
|
+
self.label = label
|
|
23
|
+
self.value = value or label # Si no se proporciona value, usar label
|
|
24
|
+
self.name = name # Requerido para agrupar radio buttons
|
|
25
|
+
self.checked = checked
|
|
26
|
+
self.disabled = disabled
|
|
27
|
+
self.required = required
|
|
28
|
+
|
|
29
|
+
# Registrar evento de cambio si se proporciona
|
|
30
|
+
if on_change:
|
|
31
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
32
|
+
|
|
33
|
+
def render(self, exporter: Any) -> str:
|
|
34
|
+
# El método render será implementado por cada exportador
|
|
35
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable, List
|
|
5
|
+
|
|
6
|
+
class SelectOption:
|
|
7
|
+
"""Class to represent a select option"""
|
|
8
|
+
def __init__(self, value: str, label: str, disabled: bool = False):
|
|
9
|
+
self.value = value
|
|
10
|
+
self.label = label
|
|
11
|
+
self.disabled = disabled
|
|
12
|
+
|
|
13
|
+
class Select(Component):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
options: List[Union[SelectOption, Dict[str, Any], str]] = None,
|
|
17
|
+
value: Optional[str] = None,
|
|
18
|
+
placeholder: str = "Seleccionar...",
|
|
19
|
+
multiple: bool = False,
|
|
20
|
+
size: Optional[int] = None,
|
|
21
|
+
id: Optional[str] = None,
|
|
22
|
+
class_name: Optional[str] = None,
|
|
23
|
+
style: Optional[Dict[str, Any]] = None,
|
|
24
|
+
disabled: bool = False,
|
|
25
|
+
required: bool = False,
|
|
26
|
+
on_change: Optional[Callable] = None,
|
|
27
|
+
**props
|
|
28
|
+
):
|
|
29
|
+
super().__init__(id=id, class_name=class_name, style=style, **props)
|
|
30
|
+
self.options = self._process_options(options or [])
|
|
31
|
+
self.value = value
|
|
32
|
+
self.placeholder = placeholder
|
|
33
|
+
self.multiple = multiple
|
|
34
|
+
self.size = size # Número de opciones visibles (para select múltiple)
|
|
35
|
+
self.disabled = disabled
|
|
36
|
+
self.required = required
|
|
37
|
+
|
|
38
|
+
# Registrar evento de cambio si se proporciona
|
|
39
|
+
if on_change:
|
|
40
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
41
|
+
|
|
42
|
+
def _process_options(self, options: List[Union[SelectOption, Dict[str, Any], str]]) -> List[SelectOption]:
|
|
43
|
+
"""Procesa las opciones y las convierte a objetos SelectOption"""
|
|
44
|
+
processed_options = []
|
|
45
|
+
|
|
46
|
+
for option in options:
|
|
47
|
+
if isinstance(option, SelectOption):
|
|
48
|
+
processed_options.append(option)
|
|
49
|
+
elif isinstance(option, dict):
|
|
50
|
+
processed_options.append(SelectOption(
|
|
51
|
+
value=option.get('value', ''),
|
|
52
|
+
label=option.get('label', option.get('value', '')),
|
|
53
|
+
disabled=option.get('disabled', False)
|
|
54
|
+
))
|
|
55
|
+
elif isinstance(option, str):
|
|
56
|
+
processed_options.append(SelectOption(value=option, label=option))
|
|
57
|
+
|
|
58
|
+
return processed_options
|
|
59
|
+
|
|
60
|
+
def add_option(self, value: str, label: str = None, disabled: bool = False):
|
|
61
|
+
"""Añade una nueva opción al select"""
|
|
62
|
+
self.options.append(SelectOption(
|
|
63
|
+
value=value,
|
|
64
|
+
label=label or value,
|
|
65
|
+
disabled=disabled
|
|
66
|
+
))
|
|
67
|
+
|
|
68
|
+
def remove_option(self, value: str):
|
|
69
|
+
"""Elimina una opción por su valor"""
|
|
70
|
+
self.options = [opt for opt in self.options if opt.value != value]
|
|
71
|
+
|
|
72
|
+
def get_selected_option(self) -> Optional[SelectOption]:
|
|
73
|
+
"""Obtiene la opción seleccionada actualmente"""
|
|
74
|
+
if self.value:
|
|
75
|
+
for option in self.options:
|
|
76
|
+
if option.value == self.value:
|
|
77
|
+
return option
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def render(self, exporter: Any) -> str:
|
|
81
|
+
# El método render será implementado por cada exportador
|
|
82
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
5
|
+
|
|
6
|
+
class Slider(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
min_value: Union[int, float] = 0,
|
|
10
|
+
max_value: Union[int, float] = 100,
|
|
11
|
+
value: Union[int, float] = 50,
|
|
12
|
+
step: Union[int, float] = 1,
|
|
13
|
+
label: str = "",
|
|
14
|
+
show_value: bool = True,
|
|
15
|
+
orientation: str = "horizontal", # "horizontal" o "vertical"
|
|
16
|
+
id: Optional[str] = None,
|
|
17
|
+
class_name: Optional[str] = None,
|
|
18
|
+
style: Optional[Dict[str, Any]] = None,
|
|
19
|
+
disabled: bool = False,
|
|
20
|
+
on_change: Optional[Callable] = None,
|
|
21
|
+
on_input: Optional[Callable] = None
|
|
22
|
+
):
|
|
23
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
24
|
+
self.min_value = min_value
|
|
25
|
+
self.max_value = max_value
|
|
26
|
+
self.value = max(min_value, min(max_value, value)) # Asegurar que esté en rango
|
|
27
|
+
self.step = step
|
|
28
|
+
self.label = label
|
|
29
|
+
self.show_value = show_value
|
|
30
|
+
self.orientation = orientation
|
|
31
|
+
self.disabled = disabled
|
|
32
|
+
|
|
33
|
+
# Validar orientación
|
|
34
|
+
if orientation not in ["horizontal", "vertical"]:
|
|
35
|
+
raise ValueError("orientation debe ser 'horizontal' o 'vertical'")
|
|
36
|
+
|
|
37
|
+
# Registrar eventos si se proporcionan
|
|
38
|
+
if on_change:
|
|
39
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
40
|
+
if on_input:
|
|
41
|
+
self.set_event(EventTypes.INPUT, on_input)
|
|
42
|
+
|
|
43
|
+
def set_value(self, value: Union[int, float]):
|
|
44
|
+
"""Establece el valor del slider asegurando que esté en rango"""
|
|
45
|
+
self.value = max(self.min_value, min(self.max_value, value))
|
|
46
|
+
|
|
47
|
+
def get_percentage(self) -> float:
|
|
48
|
+
"""Obtiene el porcentaje actual del slider (0-100)"""
|
|
49
|
+
if self.max_value == self.min_value:
|
|
50
|
+
return 0
|
|
51
|
+
return ((self.value - self.min_value) / (self.max_value - self.min_value)) * 100
|
|
52
|
+
|
|
53
|
+
def is_at_min(self) -> bool:
|
|
54
|
+
"""Verifica si el slider está en su valor mínimo"""
|
|
55
|
+
return self.value == self.min_value
|
|
56
|
+
|
|
57
|
+
def is_at_max(self) -> bool:
|
|
58
|
+
"""Verifica si el slider está en su valor máximo"""
|
|
59
|
+
return self.value == self.max_value
|
|
60
|
+
|
|
61
|
+
def render(self, exporter: Any) -> str:
|
|
62
|
+
# El método render será implementado por cada exportador
|
|
63
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
|
|
3
|
+
class Spinner(Component):
|
|
4
|
+
"""
|
|
5
|
+
Circular loading indicator (spinner).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, **props):
|
|
9
|
+
super().__init__(**props)
|
|
10
|
+
|
|
11
|
+
def render(self) -> str:
|
|
12
|
+
return '<div class="dars-spinner"></div>'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from typing import Optional, Union, Dict, Any
|
|
4
|
+
|
|
5
|
+
class Text(Component):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
text: str = "",
|
|
9
|
+
id: Optional[str] = None,
|
|
10
|
+
class_name: Optional[str] = None,
|
|
11
|
+
style: Optional[Dict[str, Any]] = None,
|
|
12
|
+
**props
|
|
13
|
+
):
|
|
14
|
+
super().__init__(id=id, class_name=class_name, style=style, **props)
|
|
15
|
+
self.text = text
|
|
16
|
+
|
|
17
|
+
def render(self, exporter: Any) -> str:
|
|
18
|
+
# El método render será implementado por cada exportador
|
|
19
|
+
# para generar el código específico de la plataforma.
|
|
20
|
+
# Por ahora, solo definimos la interfaz.
|
|
21
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
22
|
+
|
|
23
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
class Textarea(Component):
|
|
5
|
+
"""Component for multiline text areas."""
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
value: str = "",
|
|
9
|
+
placeholder: str = "",
|
|
10
|
+
rows: int = 4,
|
|
11
|
+
cols: int = 50,
|
|
12
|
+
disabled: bool = False,
|
|
13
|
+
readonly: bool = False,
|
|
14
|
+
required: bool = False,
|
|
15
|
+
max_length: Optional[int] = None,
|
|
16
|
+
class_name: Optional[str] = None,
|
|
17
|
+
style: Optional[Dict[str, Any]] = None,
|
|
18
|
+
**kwargs
|
|
19
|
+
):
|
|
20
|
+
super().__init__(class_name=class_name, style=style, **kwargs)
|
|
21
|
+
self.value = value
|
|
22
|
+
self.placeholder = placeholder
|
|
23
|
+
self.rows = rows
|
|
24
|
+
self.cols = cols
|
|
25
|
+
self.disabled = disabled
|
|
26
|
+
self.readonly = readonly
|
|
27
|
+
self.required = required
|
|
28
|
+
self.max_length = max_length
|
|
29
|
+
|
|
30
|
+
def render(self) -> str:
|
|
31
|
+
attrs = [
|
|
32
|
+
f'rows="{self.rows}"',
|
|
33
|
+
f'cols="{self.cols}"',
|
|
34
|
+
]
|
|
35
|
+
if self.placeholder: attrs.append(f'placeholder="{self.placeholder}"')
|
|
36
|
+
if self.disabled: attrs.append('disabled')
|
|
37
|
+
if self.readonly: attrs.append('readonly')
|
|
38
|
+
if self.required: attrs.append('required')
|
|
39
|
+
if self.max_length: attrs.append(f'maxlength="{self.max_length}"')
|
|
40
|
+
if self.class_name: attrs.append(f'class="{self.class_name}"')
|
|
41
|
+
if self.style: attrs.append(f'style="{self.render_styles(self.style)}"')
|
|
42
|
+
|
|
43
|
+
return f'<textarea {" ".join(attrs)}>{self.value}</textarea>'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class Tooltip(Component):
|
|
5
|
+
"""
|
|
6
|
+
Tooltip: information box on hover.
|
|
7
|
+
text: text to display
|
|
8
|
+
child: wrapped component or HTML
|
|
9
|
+
position: top, right, bottom, left (optional)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, text: str, child: Component, position: Optional[str] = "top", **props):
|
|
13
|
+
super().__init__(**props)
|
|
14
|
+
self.text = text
|
|
15
|
+
self.child = child
|
|
16
|
+
self.position = position
|
|
17
|
+
|
|
18
|
+
def render(self) -> str:
|
|
19
|
+
return f'<div class="dars-tooltip dars-tooltip-{self.position}">{self.child.render() if hasattr(self.child, "render") else self.child}<span class="dars-tooltip-text">{self.text}</span></div>'
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
class AnchorPoint:
|
|
4
|
+
"""
|
|
5
|
+
Represents an anchor or alignment point for a child in a layout (top, left, right, bottom, center, etc).
|
|
6
|
+
"""
|
|
7
|
+
def __init__(self, x: Optional[str] = None, y: Optional[str] = None, name: Optional[str] = None):
|
|
8
|
+
self.x = x # e.g. 'left', 'center', 'right', percent or px
|
|
9
|
+
self.y = y # e.g. 'top', 'center', 'bottom', percent or px
|
|
10
|
+
self.name = name # Optional semantic name for anchor
|
|
11
|
+
|
|
12
|
+
def __repr__(self):
|
|
13
|
+
return f"AnchorPoint(x={self.x}, y={self.y}, name={self.name})"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from dars.components.layout.grid import LayoutBase
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
class FlexLayout(LayoutBase):
|
|
5
|
+
"""
|
|
6
|
+
Responsive flexbox layout component. Allows direction, wrap, justify, align, and anchor points for children.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self,
|
|
9
|
+
children: Optional[List[object]] = None,
|
|
10
|
+
direction: str = "row",
|
|
11
|
+
wrap: str = "wrap",
|
|
12
|
+
justify: str = "flex-start",
|
|
13
|
+
align: str = "stretch",
|
|
14
|
+
gap: str = "16px",
|
|
15
|
+
anchors: Optional[dict] = None,
|
|
16
|
+
**kwargs):
|
|
17
|
+
super().__init__(children=children, anchors=anchors, **kwargs)
|
|
18
|
+
self.direction = direction
|
|
19
|
+
self.wrap = wrap
|
|
20
|
+
self.justify = justify
|
|
21
|
+
self.align = align
|
|
22
|
+
self.gap = gap
|
|
23
|
+
|
|
24
|
+
def add_child(self, child, anchor: Optional[str] = None):
|
|
25
|
+
self.children.append(child)
|
|
26
|
+
# Could store anchor info per child if needed
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import List, Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
class LayoutBase(Component):
|
|
5
|
+
"""
|
|
6
|
+
Base class for all layout components. Allows adding children and anchor/positioning info.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, children: Optional[List[Component]] = None, anchors: Optional[Dict[str, Any]] = None, **kwargs):
|
|
9
|
+
super().__init__(**kwargs)
|
|
10
|
+
self.children = children or []
|
|
11
|
+
self.anchors = anchors or {}
|
|
12
|
+
|
|
13
|
+
def add_child(self, child: Component):
|
|
14
|
+
self.children.append(child)
|
|
15
|
+
|
|
16
|
+
def render(self, exporter=None):
|
|
17
|
+
# Layouts se renderizan solo por el exporter, pero se requiere para evitar TypeError
|
|
18
|
+
return ""
|
|
19
|
+
|
|
20
|
+
class GridLayout(LayoutBase):
|
|
21
|
+
"""
|
|
22
|
+
Responsive grid layout component. Supports rows/columns and anchor points for children.
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, rows: int = 1, cols: int = 1, children: Optional[List[Component]] = None, anchors: Optional[Dict[str, Any]] = None, gap: str = "16px", **kwargs):
|
|
25
|
+
super().__init__(children=children, anchors=anchors, **kwargs)
|
|
26
|
+
self.rows = rows
|
|
27
|
+
self.cols = cols
|
|
28
|
+
self.gap = gap
|
|
29
|
+
|
|
30
|
+
def add_child(self, child: Component, row: int = 0, col: int = 0, row_span: int = 1, col_span: int = 1, anchor: Optional[str] = None):
|
|
31
|
+
# Store child with layout info
|
|
32
|
+
if not hasattr(self, '_child_layout'):
|
|
33
|
+
self._child_layout = []
|
|
34
|
+
self._child_layout.append({
|
|
35
|
+
'child': child,
|
|
36
|
+
'row': row,
|
|
37
|
+
'col': col,
|
|
38
|
+
'row_span': row_span,
|
|
39
|
+
'col_span': col_span,
|
|
40
|
+
'anchor': anchor
|
|
41
|
+
})
|
|
42
|
+
self.children.append(child)
|
|
43
|
+
|
|
44
|
+
def get_child_layout(self):
|
|
45
|
+
return getattr(self, '_child_layout', [])
|
dars/config.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from typing import Tuple, Dict, Any
|
|
4
|
+
|
|
5
|
+
DEFAULT_CONFIG = {
|
|
6
|
+
"entry": "main.py",
|
|
7
|
+
"format": "html",
|
|
8
|
+
"outdir": "dist",
|
|
9
|
+
"publicDir": None, # autodetect if None: prefers ./public then ./assets
|
|
10
|
+
"include": [],
|
|
11
|
+
"exclude": ["**/__pycache__", ".git", ".venv", "node_modules"],
|
|
12
|
+
"bundle": True,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
CONFIG_FILENAME = "dars.config.json"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_config(project_root: str) -> Tuple[Dict[str, Any], bool]:
|
|
19
|
+
"""Load dars.config.json from project_root. Returns (config, found)."""
|
|
20
|
+
config_path = os.path.join(project_root, CONFIG_FILENAME)
|
|
21
|
+
if not os.path.isfile(config_path):
|
|
22
|
+
# Autodetect public dir if exists for convenience even without config
|
|
23
|
+
cfg = DEFAULT_CONFIG.copy()
|
|
24
|
+
if os.path.isdir(os.path.join(project_root, "public")):
|
|
25
|
+
cfg["publicDir"] = "public"
|
|
26
|
+
elif os.path.isdir(os.path.join(project_root, "assets")):
|
|
27
|
+
cfg["publicDir"] = "assets"
|
|
28
|
+
return cfg, False
|
|
29
|
+
try:
|
|
30
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
31
|
+
data = json.load(f)
|
|
32
|
+
cfg = DEFAULT_CONFIG.copy()
|
|
33
|
+
cfg.update({k: v for k, v in data.items() if v is not None})
|
|
34
|
+
return cfg, True
|
|
35
|
+
except Exception:
|
|
36
|
+
# On parse error, fallback to defaults but mark as not found to avoid enforcing
|
|
37
|
+
cfg = DEFAULT_CONFIG.copy()
|
|
38
|
+
return cfg, False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def resolve_paths(cfg: Dict[str, Any], project_root: str) -> Dict[str, Any]:
|
|
42
|
+
"""Return a copy of cfg with absolute resolved paths for entry, publicDir, outdir."""
|
|
43
|
+
out = dict(cfg)
|
|
44
|
+
if out.get("entry"):
|
|
45
|
+
out["entry_abs"] = os.path.join(project_root, out["entry"])
|
|
46
|
+
else:
|
|
47
|
+
out["entry_abs"] = None
|
|
48
|
+
if out.get("publicDir"):
|
|
49
|
+
out["public_abs"] = os.path.join(project_root, out["publicDir"])
|
|
50
|
+
else:
|
|
51
|
+
# If None, keep None; exporter may still auto-detect
|
|
52
|
+
out["public_abs"] = None
|
|
53
|
+
if out.get("outdir"):
|
|
54
|
+
out["outdir_abs"] = os.path.join(project_root, out["outdir"])
|
|
55
|
+
else:
|
|
56
|
+
out["outdir_abs"] = os.path.join(project_root, "dist")
|
|
57
|
+
return out
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def copy_public_dir(public_dir: str, dest_dir: str, include=None, exclude=None):
|
|
61
|
+
"""Copy entire public_dir into dest_dir applying basic include/exclude filters.
|
|
62
|
+
Exclude supports substrings or glob-like simple matches; keep it simple to avoid heavy deps.
|
|
63
|
+
"""
|
|
64
|
+
import shutil
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
if not public_dir or not os.path.isdir(public_dir):
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
include = include or []
|
|
71
|
+
exclude = exclude or []
|
|
72
|
+
|
|
73
|
+
def is_excluded(path: Path) -> bool:
|
|
74
|
+
# Simple checks: substring or name matches
|
|
75
|
+
p_str = str(path)
|
|
76
|
+
for pat in exclude:
|
|
77
|
+
if pat in p_str:
|
|
78
|
+
return True
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def is_included(path: Path) -> bool:
|
|
82
|
+
if not include:
|
|
83
|
+
return True
|
|
84
|
+
p_str = str(path)
|
|
85
|
+
for pat in include:
|
|
86
|
+
if pat in p_str:
|
|
87
|
+
return True
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
for root, dirs, files in os.walk(public_dir):
|
|
91
|
+
# filter excluded dirs in-place
|
|
92
|
+
dirs[:] = [d for d in dirs if not is_excluded(Path(root) / d)]
|
|
93
|
+
for f in files:
|
|
94
|
+
src_path = Path(root) / f
|
|
95
|
+
if is_excluded(src_path) or not is_included(src_path):
|
|
96
|
+
continue
|
|
97
|
+
rel = src_path.relative_to(public_dir)
|
|
98
|
+
dst_path = Path(dest_dir) / rel
|
|
99
|
+
os.makedirs(dst_path.parent, exist_ok=True)
|
|
100
|
+
try:
|
|
101
|
+
shutil.copy2(str(src_path), str(dst_path))
|
|
102
|
+
except Exception:
|
|
103
|
+
# Best-effort; ignore copy errors
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def write_default_config(project_root: str, overwrite: bool = False) -> str:
|
|
108
|
+
"""Create a dars.config.json with defaults if it doesn't exist (or overwrite=True).
|
|
109
|
+
Returns the path to the config file.
|
|
110
|
+
"""
|
|
111
|
+
path = os.path.join(project_root, CONFIG_FILENAME)
|
|
112
|
+
if os.path.exists(path) and not overwrite:
|
|
113
|
+
return path
|
|
114
|
+
cfg = DEFAULT_CONFIG.copy()
|
|
115
|
+
# Prefer autodetected public dir as default
|
|
116
|
+
if os.path.isdir(os.path.join(project_root, "public")):
|
|
117
|
+
cfg["publicDir"] = "public"
|
|
118
|
+
elif os.path.isdir(os.path.join(project_root, "assets")):
|
|
119
|
+
cfg["publicDir"] = "assets"
|
|
120
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
121
|
+
json.dump(cfg, f, indent=2)
|
|
122
|
+
return path
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def update_config(project_root: str, updates: Dict[str, Any]) -> str:
|
|
126
|
+
"""Update or create config merging with defaults.
|
|
127
|
+
Returns the path to the config file.
|
|
128
|
+
"""
|
|
129
|
+
cfg, _ = load_config(project_root)
|
|
130
|
+
cfg.update({k: v for k, v in updates.items() if v is not None})
|
|
131
|
+
path = os.path.join(project_root, CONFIG_FILENAME)
|
|
132
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
133
|
+
json.dump(cfg, f, indent=2)
|
|
134
|
+
return path
|
dars/core/__init__.py
ADDED
|
File without changes
|