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,44 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any, List
|
|
3
|
+
|
|
4
|
+
class Navbar(Component):
|
|
5
|
+
"""Component to create navigation bars."""
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
*children,
|
|
9
|
+
brand: Optional[str] = None,
|
|
10
|
+
class_name: Optional[str] = None,
|
|
11
|
+
style: Optional[Dict[str, Any]] = None,
|
|
12
|
+
**kwargs
|
|
13
|
+
):
|
|
14
|
+
# Compatibilidad retro: si 'children' está en kwargs, lo usamos; si no, usamos los posicionales
|
|
15
|
+
children_kwarg = kwargs.pop('children', None)
|
|
16
|
+
from dars.core.component import Component
|
|
17
|
+
if children_kwarg is not None:
|
|
18
|
+
children_final = children_kwarg
|
|
19
|
+
elif len(children) == 1 and isinstance(children[0], list):
|
|
20
|
+
children_final = children[0]
|
|
21
|
+
else:
|
|
22
|
+
children_final = list(children)
|
|
23
|
+
# Filtro: solo instancias válidas de Component
|
|
24
|
+
children_final = [c for c in children_final if isinstance(c, Component)]
|
|
25
|
+
super().__init__(class_name=class_name, style=style, **kwargs)
|
|
26
|
+
self.brand = brand
|
|
27
|
+
for child in children_final:
|
|
28
|
+
self.add_child(child)
|
|
29
|
+
|
|
30
|
+
def render(self) -> str:
|
|
31
|
+
brand_html = f'<div class="dars-navbar-brand">{self.brand}</div>' if self.brand else ''
|
|
32
|
+
children_html = ''.join([child.render() for child in self.children])
|
|
33
|
+
|
|
34
|
+
attrs = []
|
|
35
|
+
if self.class_name: attrs.append(f'class="dars-navbar {self.class_name}"')
|
|
36
|
+
else: attrs.append('class="dars-navbar"')
|
|
37
|
+
|
|
38
|
+
navbar_style = 'display: flex; justify-content: space-between; align-items: center; padding: 1rem; background-color: #f8f9fa; border-bottom: 1px solid #dee2e6'
|
|
39
|
+
if self.style:
|
|
40
|
+
navbar_style += f'; {self.render_styles(self.style)}'
|
|
41
|
+
attrs.append(f'style="{navbar_style}"')
|
|
42
|
+
|
|
43
|
+
return f'<nav {" ".join(attrs)}>{brand_html}<div class="dars-navbar-nav">{children_html}</div></nav>'
|
|
44
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import List, Dict, Any, Optional
|
|
3
|
+
|
|
4
|
+
class Table(Component):
|
|
5
|
+
"""
|
|
6
|
+
Component to display tabular data with columns, data, pagination, sorting, and filtering.
|
|
7
|
+
columns: List of dictionaries with keys 'title', 'field', 'sortable', 'width', etc.
|
|
8
|
+
data: List of dictionaries (each one is a row)
|
|
9
|
+
page_size: Number of rows per page (optional)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, columns: List[Dict[str, Any]], data: List[Dict[str, Any]], page_size: Optional[int]=None, **props):
|
|
13
|
+
super().__init__(**props)
|
|
14
|
+
self.columns = columns
|
|
15
|
+
self.data = data
|
|
16
|
+
self.page_size = page_size
|
|
17
|
+
|
|
18
|
+
def render(self) -> str:
|
|
19
|
+
# Renderiza la tabla en HTML (solo vista simple, sin JS avanzado todavía)
|
|
20
|
+
thead = '<thead><tr>' + ''.join(f'<th>{col["title"]}</th>' for col in self.columns) + '</tr></thead>'
|
|
21
|
+
rows = self.data[:self.page_size] if self.page_size else self.data
|
|
22
|
+
tbody = '<tbody>' + ''.join(
|
|
23
|
+
'<tr>' + ''.join(f'<td>{row.get(col["field"], "")}</td>' for col in self.columns) + '</tr>'
|
|
24
|
+
for row in rows) + '</tbody>'
|
|
25
|
+
return f'<table class="dars-table">{thead}{tbody}</table>'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
class Tabs(Component):
|
|
5
|
+
"""
|
|
6
|
+
Tab navigation component.
|
|
7
|
+
tabs: List of tab titles
|
|
8
|
+
panels: List of components or strings (content of each tab)
|
|
9
|
+
selected: Index of the active tab (optional)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, tabs: List[str], panels: List[Component], selected: Optional[int]=0, minimum_logic: bool = True, **props):
|
|
13
|
+
super().__init__(**props)
|
|
14
|
+
self.tabs = tabs
|
|
15
|
+
self.panels = panels
|
|
16
|
+
self.selected = selected or 0
|
|
17
|
+
self.minimum_logic = minimum_logic
|
|
18
|
+
for panel in panels:
|
|
19
|
+
if hasattr(panel, 'render'):
|
|
20
|
+
self.add_child(panel)
|
|
21
|
+
|
|
22
|
+
def render(self) -> str:
|
|
23
|
+
tab_headers = ''.join(
|
|
24
|
+
f'<button class="dars-tab{ " dars-tab-active" if i == self.selected else "" }" data-tab="{i}">{title}</button>'
|
|
25
|
+
for i, title in enumerate(self.tabs)
|
|
26
|
+
)
|
|
27
|
+
panels_html = ''.join(
|
|
28
|
+
f'<div class="dars-tab-panel{ " dars-tab-panel-active" if i == self.selected else "" }">{panel.render() if hasattr(panel, "render") else panel}</div>'
|
|
29
|
+
for i, panel in enumerate(self.panels)
|
|
30
|
+
)
|
|
31
|
+
return f'<div class="dars-tabs"><div class="dars-tabs-header">{tab_headers}</div><div class="dars-tabs-panels">{panels_html}</div></div>'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .text import Text
|
|
2
|
+
from .button import Button
|
|
3
|
+
from .input import Input
|
|
4
|
+
from .container import Container
|
|
5
|
+
from .page import Page
|
|
6
|
+
from .image import Image
|
|
7
|
+
from .link import Link
|
|
8
|
+
from .textarea import Textarea
|
|
9
|
+
from .checkbox import Checkbox
|
|
10
|
+
from .radiobutton import RadioButton
|
|
11
|
+
from .select import Select, SelectOption
|
|
12
|
+
from .slider import Slider
|
|
13
|
+
from .datepicker import DatePicker
|
|
14
|
+
|
|
15
|
+
from .progressbar import ProgressBar
|
|
16
|
+
from .spinner import Spinner
|
|
17
|
+
from .tooltip import Tooltip
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'Text',
|
|
21
|
+
'Button',
|
|
22
|
+
'Input',
|
|
23
|
+
'Container',
|
|
24
|
+
'Page',
|
|
25
|
+
'Image',
|
|
26
|
+
'Link',
|
|
27
|
+
'Textarea',
|
|
28
|
+
'Checkbox',
|
|
29
|
+
'RadioButton',
|
|
30
|
+
'Select',
|
|
31
|
+
'SelectOption',
|
|
32
|
+
'Slider',
|
|
33
|
+
'DatePicker'
|
|
34
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from dars.core.events import EventTypes
|
|
4
|
+
from dars.scripts.script import Script
|
|
5
|
+
from typing import Optional, Union, Dict, Any, Callable
|
|
6
|
+
|
|
7
|
+
class Button(Component):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
text: str = "Button",
|
|
11
|
+
id: Optional[str] = None,
|
|
12
|
+
class_name: Optional[str] = None,
|
|
13
|
+
style: Optional[Dict[str, Any]] = None,
|
|
14
|
+
disabled: bool = False,
|
|
15
|
+
button_type: str = "button", # "button", "submit", "reset"
|
|
16
|
+
on_click: Optional[Callable] = None,
|
|
17
|
+
on_double_click: Optional[Callable] = None,
|
|
18
|
+
on_mouse_enter: Optional[Callable] = None,
|
|
19
|
+
on_mouse_leave: Optional[Callable] = None,
|
|
20
|
+
on_mouse_down: Optional[Callable] = None,
|
|
21
|
+
on_mouse_up: Optional[Callable] = None,
|
|
22
|
+
on_key_down: Optional[Callable] = None,
|
|
23
|
+
on_key_up: Optional[Callable] = None
|
|
24
|
+
):
|
|
25
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
26
|
+
self.text = text
|
|
27
|
+
self.disabled = disabled
|
|
28
|
+
self.button_type = button_type
|
|
29
|
+
|
|
30
|
+
# Soporte para presets JS editables con dScript u otros Script
|
|
31
|
+
if on_click:
|
|
32
|
+
# Convertir a Script si es necesario
|
|
33
|
+
if not isinstance(on_click, Script) and callable(on_click):
|
|
34
|
+
from dars.scripts.dscript import dScript
|
|
35
|
+
on_click = dScript(on_click.__code__)
|
|
36
|
+
self.set_event(EventTypes.CLICK, on_click)
|
|
37
|
+
if on_double_click:
|
|
38
|
+
self.set_event(EventTypes.DOUBLE_CLICK, on_double_click)
|
|
39
|
+
if on_mouse_enter:
|
|
40
|
+
self.set_event(EventTypes.MOUSE_ENTER, on_mouse_enter)
|
|
41
|
+
if on_mouse_leave:
|
|
42
|
+
self.set_event(EventTypes.MOUSE_LEAVE, on_mouse_leave)
|
|
43
|
+
if on_mouse_down:
|
|
44
|
+
self.set_event(EventTypes.MOUSE_DOWN, on_mouse_down)
|
|
45
|
+
if on_mouse_up:
|
|
46
|
+
self.set_event(EventTypes.MOUSE_UP, on_mouse_up)
|
|
47
|
+
if on_key_down:
|
|
48
|
+
self.set_event(EventTypes.KEY_DOWN, on_key_down)
|
|
49
|
+
if on_key_up:
|
|
50
|
+
self.set_event(EventTypes.KEY_UP, on_key_up)
|
|
51
|
+
|
|
52
|
+
def render(self, exporter: Any) -> str:
|
|
53
|
+
# El método render será implementado por cada exportador
|
|
54
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
55
|
+
|
|
@@ -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 Checkbox(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
label: str = "",
|
|
10
|
+
checked: bool = False,
|
|
11
|
+
value: str = "",
|
|
12
|
+
name: Optional[str] = None,
|
|
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.checked = checked
|
|
24
|
+
self.value = value or label # Si no se proporciona value, usar label
|
|
25
|
+
self.name = name
|
|
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,29 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from dars.core.properties import StyleProps
|
|
3
|
+
from typing import Optional, Union, Dict, Any, List
|
|
4
|
+
|
|
5
|
+
class Container(Component):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
*children: Component,
|
|
9
|
+
id: Optional[str] = None,
|
|
10
|
+
class_name: Optional[str] = None,
|
|
11
|
+
style: Optional[Dict[str, Any]] = None,
|
|
12
|
+
additional_children: Optional[List[Component]] = None,
|
|
13
|
+
**props
|
|
14
|
+
):
|
|
15
|
+
super().__init__(id=id, class_name=class_name, style=style, **props)
|
|
16
|
+
|
|
17
|
+
# Agregar hijos pasados como argumentos posicionales
|
|
18
|
+
for child in children:
|
|
19
|
+
self.add_child(child)
|
|
20
|
+
|
|
21
|
+
# Agregar hijos adicionales si se proporcionan
|
|
22
|
+
if additional_children:
|
|
23
|
+
for child in additional_children:
|
|
24
|
+
self.add_child(child)
|
|
25
|
+
|
|
26
|
+
def render(self, exporter: Any) -> str:
|
|
27
|
+
# El método render será implementado por cada exportador
|
|
28
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
29
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
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
|
+
from datetime import datetime, date
|
|
6
|
+
|
|
7
|
+
class DatePicker(Component):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
value: Optional[Union[str, date, datetime]] = None,
|
|
11
|
+
min_date: Optional[Union[str, date, datetime]] = None,
|
|
12
|
+
max_date: Optional[Union[str, date, datetime]] = None,
|
|
13
|
+
placeholder: str = "Seleccionar fecha",
|
|
14
|
+
format: str = "YYYY-MM-DD", # Formato de fecha
|
|
15
|
+
locale: str = "es", # Idioma para el picker
|
|
16
|
+
show_time: bool = False, # Si incluir selector de tiempo
|
|
17
|
+
inline: bool = False, # Si mostrar inline o como popup
|
|
18
|
+
disabled_dates: Optional[list] = None, # Fechas deshabilitadas
|
|
19
|
+
id: Optional[str] = None,
|
|
20
|
+
class_name: Optional[str] = None,
|
|
21
|
+
style: Optional[Dict[str, Any]] = None,
|
|
22
|
+
disabled: bool = False,
|
|
23
|
+
required: bool = False,
|
|
24
|
+
readonly: bool = False,
|
|
25
|
+
on_change: Optional[Callable] = None,
|
|
26
|
+
on_open: Optional[Callable] = None,
|
|
27
|
+
on_close: Optional[Callable] = None
|
|
28
|
+
):
|
|
29
|
+
super().__init__(id=id, class_name=class_name, style=style)
|
|
30
|
+
|
|
31
|
+
# Asignar atributos básicos primero
|
|
32
|
+
self.placeholder = placeholder
|
|
33
|
+
self.format = format
|
|
34
|
+
self.locale = locale
|
|
35
|
+
self.show_time = show_time
|
|
36
|
+
self.inline = inline
|
|
37
|
+
self.disabled_dates = disabled_dates or []
|
|
38
|
+
self.disabled = disabled
|
|
39
|
+
self.required = required
|
|
40
|
+
self.readonly = readonly
|
|
41
|
+
|
|
42
|
+
# Validar formato
|
|
43
|
+
valid_formats = ["YYYY-MM-DD", "DD/MM/YYYY", "MM/DD/YYYY", "DD-MM-YYYY"]
|
|
44
|
+
if format not in valid_formats:
|
|
45
|
+
raise ValueError(f"format debe ser uno de: {valid_formats}")
|
|
46
|
+
|
|
47
|
+
# Validar locale
|
|
48
|
+
valid_locales = ["es", "en", "fr", "de", "it", "pt"]
|
|
49
|
+
if locale not in valid_locales:
|
|
50
|
+
raise ValueError(f"locale debe ser uno de: {valid_locales}")
|
|
51
|
+
|
|
52
|
+
# Ahora procesar las fechas (después de asignar self.format)
|
|
53
|
+
self.value = self._process_date(value)
|
|
54
|
+
self.min_date = self._process_date(min_date)
|
|
55
|
+
self.max_date = self._process_date(max_date)
|
|
56
|
+
|
|
57
|
+
# Registrar eventos si se proporcionan
|
|
58
|
+
if on_change:
|
|
59
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
60
|
+
if on_open:
|
|
61
|
+
self.set_event("open", on_open)
|
|
62
|
+
if on_close:
|
|
63
|
+
self.set_event("close", on_close)
|
|
64
|
+
|
|
65
|
+
def _process_date(self, date_value: Optional[Union[str, date, datetime]]) -> Optional[str]:
|
|
66
|
+
"""Procesa y normaliza el valor de fecha"""
|
|
67
|
+
if date_value is None:
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
if isinstance(date_value, str):
|
|
71
|
+
# Validar formato de string de fecha
|
|
72
|
+
try:
|
|
73
|
+
# Intentar parsear la fecha para validarla
|
|
74
|
+
if self.format == "YYYY-MM-DD":
|
|
75
|
+
datetime.strptime(date_value, "%Y-%m-%d")
|
|
76
|
+
elif self.format == "DD/MM/YYYY":
|
|
77
|
+
datetime.strptime(date_value, "%d/%m/%Y")
|
|
78
|
+
elif self.format == "MM/DD/YYYY":
|
|
79
|
+
datetime.strptime(date_value, "%m/%d/%Y")
|
|
80
|
+
elif self.format == "DD-MM-YYYY":
|
|
81
|
+
datetime.strptime(date_value, "%d-%m-%Y")
|
|
82
|
+
return date_value
|
|
83
|
+
except ValueError:
|
|
84
|
+
raise ValueError(f"Formato de fecha inválido: {date_value}")
|
|
85
|
+
|
|
86
|
+
elif isinstance(date_value, (date, datetime)):
|
|
87
|
+
# Convertir objeto date/datetime a string según el formato
|
|
88
|
+
if self.format == "YYYY-MM-DD":
|
|
89
|
+
return date_value.strftime("%Y-%m-%d")
|
|
90
|
+
elif self.format == "DD/MM/YYYY":
|
|
91
|
+
return date_value.strftime("%d/%m/%Y")
|
|
92
|
+
elif self.format == "MM/DD/YYYY":
|
|
93
|
+
return date_value.strftime("%m/%d/%Y")
|
|
94
|
+
elif self.format == "DD-MM-YYYY":
|
|
95
|
+
return date_value.strftime("%d-%m-%Y")
|
|
96
|
+
|
|
97
|
+
return str(date_value)
|
|
98
|
+
|
|
99
|
+
def set_value(self, value: Union[str, date, datetime]):
|
|
100
|
+
"""Establece el valor de la fecha"""
|
|
101
|
+
self.value = self._process_date(value)
|
|
102
|
+
|
|
103
|
+
def get_date_object(self) -> Optional[datetime]:
|
|
104
|
+
"""Obtiene el valor como objeto datetime"""
|
|
105
|
+
if not self.value:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
if self.format == "YYYY-MM-DD":
|
|
110
|
+
return datetime.strptime(self.value, "%Y-%m-%d")
|
|
111
|
+
elif self.format == "DD/MM/YYYY":
|
|
112
|
+
return datetime.strptime(self.value, "%d/%m/%Y")
|
|
113
|
+
elif self.format == "MM/DD/YYYY":
|
|
114
|
+
return datetime.strptime(self.value, "%m/%d/%Y")
|
|
115
|
+
elif self.format == "DD-MM-YYYY":
|
|
116
|
+
return datetime.strptime(self.value, "%d-%m-%Y")
|
|
117
|
+
except ValueError:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def is_date_disabled(self, date_to_check: Union[str, date, datetime]) -> bool:
|
|
121
|
+
"""Verifica si una fecha está deshabilitada"""
|
|
122
|
+
check_date = self._process_date(date_to_check)
|
|
123
|
+
return check_date in self.disabled_dates
|
|
124
|
+
|
|
125
|
+
def add_disabled_date(self, date_to_disable: Union[str, date, datetime]):
|
|
126
|
+
"""Añade una fecha a la lista de fechas deshabilitadas"""
|
|
127
|
+
disabled_date = self._process_date(date_to_disable)
|
|
128
|
+
if disabled_date and disabled_date not in self.disabled_dates:
|
|
129
|
+
self.disabled_dates.append(disabled_date)
|
|
130
|
+
|
|
131
|
+
def remove_disabled_date(self, date_to_enable: Union[str, date, datetime]):
|
|
132
|
+
"""Elimina una fecha de la lista de fechas deshabilitadas"""
|
|
133
|
+
enabled_date = self._process_date(date_to_enable)
|
|
134
|
+
if enabled_date in self.disabled_dates:
|
|
135
|
+
self.disabled_dates.remove(enabled_date)
|
|
136
|
+
|
|
137
|
+
def render(self, exporter: Any) -> str:
|
|
138
|
+
# El método render será implementado por cada exportador
|
|
139
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
class Image(Component):
|
|
5
|
+
"""Component to display images."""
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
src: str,
|
|
9
|
+
alt: str = "",
|
|
10
|
+
width: Optional[str] = None,
|
|
11
|
+
height: Optional[str] = None,
|
|
12
|
+
class_name: Optional[str] = None,
|
|
13
|
+
style: Optional[Dict[str, Any]] = None,
|
|
14
|
+
**kwargs
|
|
15
|
+
):
|
|
16
|
+
super().__init__(class_name=class_name, style=style, **kwargs)
|
|
17
|
+
self.src = src
|
|
18
|
+
self.alt = alt
|
|
19
|
+
self.width = width
|
|
20
|
+
self.height = height
|
|
21
|
+
|
|
22
|
+
def render(self) -> str:
|
|
23
|
+
attrs = [
|
|
24
|
+
f'src="{self.src}"',
|
|
25
|
+
f'alt="{self.alt}"',
|
|
26
|
+
]
|
|
27
|
+
if self.width: attrs.append(f'width="{self.width}"')
|
|
28
|
+
if self.height: attrs.append(f'height="{self.height}"')
|
|
29
|
+
if self.class_name: attrs.append(f'class="{self.class_name}"')
|
|
30
|
+
if self.style: attrs.append(f'style="{self.render_styles(self.style)}"')
|
|
31
|
+
|
|
32
|
+
return f'<img {" ".join(attrs)} />'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
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 Input(Component):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
value: str = "",
|
|
10
|
+
placeholder: str = "",
|
|
11
|
+
input_type: str = "text", # "text", "password", "email", "number", etc.
|
|
12
|
+
id: Optional[str] = None,
|
|
13
|
+
class_name: Optional[str] = None,
|
|
14
|
+
style: Optional[Dict[str, Any]] = None,
|
|
15
|
+
disabled: bool = False,
|
|
16
|
+
readonly: bool = False,
|
|
17
|
+
required: bool = False,
|
|
18
|
+
max_length: Optional[int] = None,
|
|
19
|
+
min_length: Optional[int] = None,
|
|
20
|
+
pattern: Optional[str] = None,
|
|
21
|
+
on_change: Optional[Callable] = None,
|
|
22
|
+
on_input: Optional[Callable] = None,
|
|
23
|
+
on_focus: Optional[Callable] = None,
|
|
24
|
+
on_blur: Optional[Callable] = None,
|
|
25
|
+
on_key_down: Optional[Callable] = None,
|
|
26
|
+
on_key_up: Optional[Callable] = None,
|
|
27
|
+
**props
|
|
28
|
+
):
|
|
29
|
+
super().__init__(id=id, class_name=class_name, style=style, **props)
|
|
30
|
+
self.value = value
|
|
31
|
+
self.placeholder = placeholder
|
|
32
|
+
self.input_type = input_type
|
|
33
|
+
self.disabled = disabled
|
|
34
|
+
self.readonly = readonly
|
|
35
|
+
self.required = required
|
|
36
|
+
self.max_length = max_length
|
|
37
|
+
self.min_length = min_length
|
|
38
|
+
self.pattern = pattern
|
|
39
|
+
|
|
40
|
+
# Soporte para presets JS editables con dScript u otros Script
|
|
41
|
+
if on_change:
|
|
42
|
+
self.set_event(EventTypes.CHANGE, on_change)
|
|
43
|
+
if on_input:
|
|
44
|
+
self.set_event(EventTypes.INPUT, on_input)
|
|
45
|
+
if on_focus:
|
|
46
|
+
self.set_event(EventTypes.FOCUS, on_focus)
|
|
47
|
+
if on_blur:
|
|
48
|
+
self.set_event(EventTypes.BLUR, on_blur)
|
|
49
|
+
if on_key_down:
|
|
50
|
+
self.set_event(EventTypes.KEY_DOWN, on_key_down)
|
|
51
|
+
if on_key_up:
|
|
52
|
+
self.set_event(EventTypes.KEY_UP, on_key_up)
|
|
53
|
+
|
|
54
|
+
def render(self, exporter: Any) -> str:
|
|
55
|
+
# El método render será implementado por cada exportador
|
|
56
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
57
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
class Link(Component):
|
|
5
|
+
"""Component to create links."""
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
text: str,
|
|
9
|
+
href: str,
|
|
10
|
+
target: str = "_self",
|
|
11
|
+
class_name: Optional[str] = None,
|
|
12
|
+
style: Optional[Dict[str, Any]] = None,
|
|
13
|
+
**kwargs
|
|
14
|
+
):
|
|
15
|
+
super().__init__(class_name=class_name, style=style, **kwargs)
|
|
16
|
+
self.text = text
|
|
17
|
+
self.href = href
|
|
18
|
+
self.target = target
|
|
19
|
+
|
|
20
|
+
def render(self) -> str:
|
|
21
|
+
attrs = [
|
|
22
|
+
f'href="{self.href}"',
|
|
23
|
+
f'target="{self.target}"',
|
|
24
|
+
]
|
|
25
|
+
if self.class_name: attrs.append(f'class="{self.class_name}"')
|
|
26
|
+
if self.style: attrs.append(f'style="{self.render_styles(self.style)}"')
|
|
27
|
+
|
|
28
|
+
return f'<a {" ".join(attrs)}>{self.text}</a>'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any, Callable, Union
|
|
2
|
+
import os
|
|
3
|
+
from dars.core.component import Component
|
|
4
|
+
|
|
5
|
+
class Markdown(Component):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
content: Optional[str] = None,
|
|
9
|
+
file_path: Optional[str] = None,
|
|
10
|
+
id: Optional[str] = None,
|
|
11
|
+
class_name: Optional[str] = None,
|
|
12
|
+
style: Optional[Dict[str, Any]] = None,
|
|
13
|
+
dark_theme: bool = False,
|
|
14
|
+
**kwargs
|
|
15
|
+
):
|
|
16
|
+
"""
|
|
17
|
+
Markdown component that converts markdown content to HTML.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
content: String with markdown content
|
|
21
|
+
file_path: Path to a .md file to load content from
|
|
22
|
+
id: Component ID
|
|
23
|
+
class_name: CSS class name
|
|
24
|
+
style: CSS styles
|
|
25
|
+
dark_theme: Enable dark theme styling
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(id=id, class_name=class_name, style=style, **kwargs)
|
|
28
|
+
|
|
29
|
+
if content and file_path:
|
|
30
|
+
raise ValueError("Only content or file_path can be specified, not both")
|
|
31
|
+
|
|
32
|
+
if not content and not file_path:
|
|
33
|
+
raise ValueError("Either content or file_path must be specified")
|
|
34
|
+
|
|
35
|
+
self.content = content
|
|
36
|
+
self.file_path = file_path
|
|
37
|
+
self.dark_theme = dark_theme
|
|
38
|
+
self.rendered_html = ""
|
|
39
|
+
|
|
40
|
+
# Load and process markdown content
|
|
41
|
+
self._load_and_process_content()
|
|
42
|
+
|
|
43
|
+
def _load_and_process_content(self):
|
|
44
|
+
"""Load and process markdown content."""
|
|
45
|
+
if self.file_path:
|
|
46
|
+
if not os.path.exists(self.file_path):
|
|
47
|
+
raise FileNotFoundError(f"File {self.file_path} does not exist")
|
|
48
|
+
|
|
49
|
+
if not self.file_path.endswith('.md'):
|
|
50
|
+
raise ValueError("File must have .md extension")
|
|
51
|
+
|
|
52
|
+
with open(self.file_path, 'r', encoding='utf-8') as f:
|
|
53
|
+
self.content = f.read()
|
|
54
|
+
|
|
55
|
+
def update_content(self, new_content: Optional[str] = None, new_file_path: Optional[str] = None):
|
|
56
|
+
"""
|
|
57
|
+
Update the markdown content of the component.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
new_content: New markdown content as string
|
|
61
|
+
new_file_path: New markdown file path
|
|
62
|
+
"""
|
|
63
|
+
if new_content and new_file_path:
|
|
64
|
+
raise ValueError("Only new_content or new_file_path can be specified, not both")
|
|
65
|
+
|
|
66
|
+
if new_content:
|
|
67
|
+
self.content = new_content
|
|
68
|
+
self.file_path = None
|
|
69
|
+
elif new_file_path:
|
|
70
|
+
self.file_path = new_file_path
|
|
71
|
+
self.content = None
|
|
72
|
+
|
|
73
|
+
self._load_and_process_content()
|
|
74
|
+
|
|
75
|
+
def set_dark_theme(self, enabled: bool = True):
|
|
76
|
+
"""Enable or disable dark theme"""
|
|
77
|
+
self.dark_theme = enabled
|
|
78
|
+
# Add dark theme class dynamically
|
|
79
|
+
if enabled:
|
|
80
|
+
self.class_name = f"{self.class_name or ''} dars-markdown-dark"
|
|
81
|
+
else:
|
|
82
|
+
self.class_name = self.class_name.replace("dars-markdown-dark", "") if self.class_name else ""
|
|
83
|
+
|
|
84
|
+
def render(self, exporter: Any) -> str:
|
|
85
|
+
# El método render será implementado por cada exportador
|
|
86
|
+
raise NotImplementedError("El método render debe ser implementado por el exportador")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional, Dict, Any, List
|
|
3
|
+
|
|
4
|
+
class Page(Component):
|
|
5
|
+
"""Root component for pages in Dars multipage apps. Allows passing children as positional arguments and scripts per page."""
|
|
6
|
+
def __init__(self, *children: Component, id: Optional[str] = None, class_name: Optional[str] = None, style: Optional[Dict[str, Any]] = None, **props):
|
|
7
|
+
super().__init__(id=id, class_name=class_name, style=style, **props)
|
|
8
|
+
self.scripts = []
|
|
9
|
+
for child in children:
|
|
10
|
+
self.add_child(child)
|
|
11
|
+
|
|
12
|
+
def add_script(self, script):
|
|
13
|
+
self.scripts.append(script)
|
|
14
|
+
|
|
15
|
+
def get_scripts(self):
|
|
16
|
+
return self.scripts
|
|
17
|
+
|
|
18
|
+
def render(self, exporter: Any) -> str:
|
|
19
|
+
# El método render será implementado por el exporter
|
|
20
|
+
raise NotImplementedError("El método render debe ser implementado por el exporter")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dars.core.component import Component
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class ProgressBar(Component):
|
|
5
|
+
"""
|
|
6
|
+
Visual progress bar.
|
|
7
|
+
value: current value (0-100)
|
|
8
|
+
max_value: maximum value (default 100)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, value: int, max_value: int = 100, **props):
|
|
12
|
+
super().__init__(**props)
|
|
13
|
+
self.value = value
|
|
14
|
+
self.max_value = max_value
|
|
15
|
+
|
|
16
|
+
def render(self) -> str:
|
|
17
|
+
percent = min(max(self.value / self.max_value * 100, 0), 100)
|
|
18
|
+
return f'<div class="dars-progressbar"><div class="dars-progressbar-bar" style="width: {percent}%;"></div></div>'
|