hyperflask 0.1.0__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.

Potentially problematic release.


This version of hyperflask might be problematic. Click here for more details.

Files changed (66) hide show
  1. hyperflask/__init__.py +15 -0
  2. hyperflask/__main__.py +3 -0
  3. hyperflask/api.py +57 -0
  4. hyperflask/app.py +176 -0
  5. hyperflask/cli.py +140 -0
  6. hyperflask/components/__init__.py +54 -0
  7. hyperflask/components/alpine.py +16 -0
  8. hyperflask/components/core.py +60 -0
  9. hyperflask/components/webcomponent.py +23 -0
  10. hyperflask/factory.py +173 -0
  11. hyperflask/forms/__init__.py +51 -0
  12. hyperflask/forms/fields.py +17 -0
  13. hyperflask/forms/widgets.py +19 -0
  14. hyperflask/layout.html +28 -0
  15. hyperflask/page_helpers.py +36 -0
  16. hyperflask/reactivity.py +46 -0
  17. hyperflask/resources.py +149 -0
  18. hyperflask/static/alpine.js +5 -0
  19. hyperflask/static/app.js +5 -0
  20. hyperflask/static/htmx.js +5 -0
  21. hyperflask/static/reactive.js +5 -0
  22. hyperflask/static/stimulus.js +4 -0
  23. hyperflask/static/translations.js +55 -0
  24. hyperflask/ui/components/README.md +3 -0
  25. hyperflask/ui/components/actions/Button.html +6 -0
  26. hyperflask/ui/components/actions/Dropdown.html +16 -0
  27. hyperflask/ui/components/actions/DropdownItem.html +8 -0
  28. hyperflask/ui/components/actions/LinkButton.html +6 -0
  29. hyperflask/ui/components/actions/Modal.html +28 -0
  30. hyperflask/ui/components/actions/ModalAction.html +12 -0
  31. hyperflask/ui/components/actions/ModalTitle.html +6 -0
  32. hyperflask/ui/components/data-display/Accordion.html +8 -0
  33. hyperflask/ui/components/data-display/AccordionPanel.html +18 -0
  34. hyperflask/ui/components/data-input/Checkbox.html +4 -0
  35. hyperflask/ui/components/data-input/FileInput.html +4 -0
  36. hyperflask/ui/components/data-input/FormControl.html +15 -0
  37. hyperflask/ui/components/data-input/Input.html +4 -0
  38. hyperflask/ui/components/data-input/LabeledCheckbox.html +11 -0
  39. hyperflask/ui/components/data-input/LabeledTextInput.html +11 -0
  40. hyperflask/ui/components/data-input/Radiobox.html +4 -0
  41. hyperflask/ui/components/data-input/Range.html +4 -0
  42. hyperflask/ui/components/data-input/Rating.html +20 -0
  43. hyperflask/ui/components/data-input/Select.html +11 -0
  44. hyperflask/ui/components/data-input/Textarea.html +6 -0
  45. hyperflask/ui/components/data-input/Toggle.html +4 -0
  46. hyperflask/ui/components/feedback/Loading.html +4 -0
  47. hyperflask/ui/components/feedback/Toast.html +8 -0
  48. hyperflask/ui/components/layout/Footer.html +8 -0
  49. hyperflask/ui/components/layout/FooterLink.html +6 -0
  50. hyperflask/ui/components/layout/FooterNav.html +11 -0
  51. hyperflask/ui/components/layout/Indicator.html +11 -0
  52. hyperflask/ui/components/navigation/Link.html +6 -0
  53. hyperflask/ui/components/navigation/Navbar.html +21 -0
  54. hyperflask/ui/forms.html +16 -0
  55. hyperflask/ui/icons.html +5 -0
  56. hyperflask/ui/modal.html +7 -0
  57. hyperflask/utils/__init__.py +5 -0
  58. hyperflask/utils/htmx.py +24 -0
  59. hyperflask/utils/page_actions.py +25 -0
  60. hyperflask/utils/request.py +25 -0
  61. hyperflask/utils/tokens.py +28 -0
  62. hyperflask-0.1.0.dist-info/LICENSE +19 -0
  63. hyperflask-0.1.0.dist-info/METADATA +157 -0
  64. hyperflask-0.1.0.dist-info/RECORD +66 -0
  65. hyperflask-0.1.0.dist-info/WHEEL +4 -0
  66. hyperflask-0.1.0.dist-info/entry_points.txt +3 -0
hyperflask/factory.py ADDED
@@ -0,0 +1,173 @@
1
+ from .app import Hyperflask, db
2
+ from .resources import Resource
3
+ from .api import Api
4
+ from flask_configurator import Config
5
+ import importlib
6
+ from importlib.abc import MetaPathFinder, SourceLoader
7
+ from importlib.util import spec_from_file_location
8
+ from importlib.machinery import SourceFileLoader
9
+ from importlib.metadata import entry_points
10
+ import logging
11
+ import os
12
+ import sys
13
+ import runpy
14
+
15
+
16
+ app = None
17
+ api = None
18
+
19
+
20
+ def load_config(root_path=None):
21
+ debug = os.environ.get("FLASK_DEBUG") == "1"
22
+ default_env = "development" if debug else "production"
23
+ config = Config(root_path, defaults={
24
+ "SQLORM_URI": "sqlite://app.db"
25
+ })
26
+ config.load("config.yml", default_env)
27
+ if config["ENV"] == "development" and not config.get("SECRET_KEY"):
28
+ logging.getLogger().warn("Missing SECRET_KEY has been automatically set to 'changeme' while in development mode")
29
+ config["SECRET_KEY"] = "changeme"
30
+ return config
31
+
32
+
33
+ def create_app(root_path=None):
34
+ global app
35
+ global api
36
+ config = load_config(root_path)
37
+ static_folder = config.get("STATIC_FOLDER", "public")
38
+ migrations_folder = config.get("MIGRATIONS_FOLDER", "migrations")
39
+
40
+ if root_path is None:
41
+ root_path = os.getcwd()
42
+
43
+ if os.path.exists(os.path.join(root_path, "app")):
44
+ static_folder = os.path.abspath(os.path.join(root_path, static_folder))
45
+ migrations_folder = os.path.abspath(os.path.join(root_path, migrations_folder))
46
+ import_name = "app"
47
+ root_path = os.path.join(root_path, "app")
48
+ sys.meta_path.insert(0, VirtualPackageFinder(import_name, root_path))
49
+ else:
50
+ import_name = "__main__"
51
+
52
+ app = Hyperflask(import_name, root_path=root_path, config=config, static_folder=static_folder,
53
+ static_url_path=config.get("STATIC_URL_PATH", "/static"),
54
+ migrations_folder=migrations_folder)
55
+
56
+ api = Api(app)
57
+
58
+ if app.config.get("LOAD_HYPERFLASK_EXTENSIONS", True):
59
+ for entry in entry_points(group="hyperflask.extensions"):
60
+ entry.load()(app)
61
+
62
+ extensions = {}
63
+ if "FLASK_EXTENSIONS" in app.config:
64
+ extensions.update(dict([ext.items()[0] if isinstance(ext, dict) else (ext, {}) for ext in app.config["FLASK_EXTENSIONS"]]))
65
+
66
+ for ext, config in extensions.items():
67
+ module, class_name = ext.rsplit(":", 1)
68
+ m = importlib.import_module(module)
69
+ getattr(m, class_name)(app, **config)
70
+
71
+ try_import(app, "models")
72
+ try_import(app, "routes")
73
+ try_import(app, "api")
74
+ try_import(app, "tasks")
75
+ try_import(app, "cron")
76
+
77
+ resources = try_import(app, "resources")
78
+ if resources:
79
+ for name in dir(resources):
80
+ obj = getattr(resources, name)
81
+ if issubclass(obj, Resource) and obj.url_prefix:
82
+ obj.register(app, api)
83
+
84
+ db.init_db()
85
+ return app
86
+
87
+
88
+ def try_import(app, name, import_submodules=True):
89
+ if app.import_name != "__main__":
90
+ name = f"{app.import_name}.{name}"
91
+ try:
92
+ mod = importlib.import_module(name)
93
+
94
+ if import_submodules:
95
+ is_virtual_package = os.path.isdir(os.path.join(app.root_path, name)) and not os.path.exists(os.path.join(app.root_path, name, "__init__.py"))
96
+ if is_virtual_package:
97
+ mod = {}
98
+ for f in os.listdir(os.path.join(app.root_path, name)):
99
+ if f.endswith(".py"):
100
+ m = importlib.import_module(f"{name}.{f.rsplit('.', 1)[0]}")
101
+ mod.update({k: getattr(m, k) for k in dir(m) if not k.startswith("_")})
102
+ return mod
103
+
104
+ return {k: getattr(mod, k) for k in dir(mod) if not k.startswith("_")}
105
+ except ImportError:
106
+ return
107
+
108
+
109
+ def get_factory_package(name):
110
+ tasks_dir = os.path.join(app.root_path, name)
111
+ if not os.path.exists(os.path.join(app.root_path, f"{name}.py")) and os.path.isdir(tasks_dir) and not os.path.exists(os.path.join(tasks_dir, "__init__.py")):
112
+ return tasks_dir
113
+ return False
114
+
115
+
116
+ def create_task_from_file(app, filename, **kwargs):
117
+ kwargs["actor_name"] = os.path.basename(filename).rsplit(".", 1)[0]
118
+ @app.actor(**kwargs)
119
+ def actor():
120
+ runpy.run_path(filename)
121
+
122
+
123
+ def extract_cron_schedule_from_file(filename):
124
+ with open(filename) as f:
125
+ line = f.readline()
126
+ if not line.startswith("#"):
127
+ raise Exception("Cronjobs must start with a comment line specifying the schedule")
128
+ return line[1:].strip()
129
+
130
+
131
+ class VirtualPackageFinder(MetaPathFinder):
132
+ def __init__(self, package_name, path=None):
133
+ if not path:
134
+ path = os.path.join(os.getcwd(), package_name.replace(".", "/"))
135
+ self.package_name = package_name
136
+ self.path = path
137
+
138
+ def find_spec(self, fullname, path, target=None):
139
+ if not fullname.startswith(self.package_name):
140
+ return
141
+
142
+ if fullname == self.package_name:
143
+ relname = ""
144
+ else:
145
+ relname = fullname[len(self.package_name)+1:].replace(".", "/")
146
+
147
+ if os.path.isdir(os.path.join(self.path, relname)):
148
+ filename = os.path.join(self.path, relname, "__init__.py")
149
+ loader_class = SourceFileLoader if os.path.exists(filename) else EmptyFileLoader
150
+ return spec_from_file_location(fullname, filename, loader=loader_class(fullname, filename),
151
+ submodule_search_locations=[os.path.join(self.path, relname)])
152
+
153
+ filename = os.path.join(self.path, f"{relname}.py")
154
+ if os.path.exists(filename):
155
+ return spec_from_file_location(fullname, filename, loader=SourceFileLoader(fullname, filename),
156
+ submodule_search_locations=None)
157
+
158
+
159
+
160
+ class EmptyFileLoader(SourceLoader):
161
+ def __init__(self, fullname, filename):
162
+ self.fullname = fullname
163
+ self.filename = filename
164
+
165
+ def get_filename(self, fullname):
166
+ return self.filename
167
+
168
+ def get_data(self, filename):
169
+ return ""
170
+
171
+
172
+ if __name__ == "__main__":
173
+ create_app()
@@ -0,0 +1,51 @@
1
+ from wtforms import validators, ValidationError
2
+ from wtforms.fields import *
3
+ from wtforms.csrf.core import CSRFTokenField
4
+ from flask_wtf import FlaskForm
5
+ from flask import abort
6
+ from jinja_wtforms.form import TEMPLATE_FORM_FIELDS
7
+ from .fields import *
8
+
9
+
10
+ TEMPLATE_FORM_FIELDS.update({
11
+ "checkbox": BooleanField,
12
+ "decimal": DecimalField,
13
+ "date": DateField,
14
+ "datetime": DateTimeField,
15
+ "float": FloatField,
16
+ "int": IntegerField,
17
+ "radio": RadioField,
18
+ "select": SelectField,
19
+ "selectmulti": SelectMultipleField,
20
+ "text": StringField,
21
+ "textarea": TextAreaField,
22
+ "password": PasswordField,
23
+ "hidden": HiddenField,
24
+ "datetimelocal": DateTimeLocalField,
25
+ "decimalrange": DecimalRangeField,
26
+ "email": EmailField,
27
+ "intrange": IntegerRangeField,
28
+ "search": SearchField,
29
+ "tel": TelField,
30
+ "url": URLField,
31
+ "file": FileField,
32
+ "files": MultipleFileField
33
+ })
34
+
35
+
36
+ class Form(FlaskForm):
37
+ def validate_or_400(self):
38
+ if not self.validate():
39
+ abort(400)
40
+
41
+ def populate_obj(self, obj):
42
+ for name, field in self._fields.items():
43
+ if not isinstance(field, CSRFTokenField):
44
+ field.populate_obj(obj, name)
45
+
46
+ @property
47
+ def data(self):
48
+ return {name: f.data for name, f in self._fields.items() if not isinstance(f, CSRFTokenField)}
49
+
50
+ def data_without(self, *fields):
51
+ return {name: f.data for name, f in self._fields.items() if name not in fields and not isinstance(f, CSRFTokenField)}
@@ -0,0 +1,17 @@
1
+ from . import widgets
2
+ from wtforms import fields
3
+
4
+
5
+ __all__ = ('StringField', 'PasswordField', 'EmailField')
6
+
7
+
8
+ class StringField(fields.StringField):
9
+ widget = widgets.TextInput()
10
+
11
+
12
+ class PasswordField(fields.PasswordField):
13
+ widget = widgets.PasswordInput()
14
+
15
+
16
+ class EmailField(fields.StringField):
17
+ widget = widgets.EmailInput()
@@ -0,0 +1,19 @@
1
+ from wtforms import widgets
2
+
3
+
4
+ def create_input_class(basecls):
5
+ class Input(basecls):
6
+ def __call__(self, field, **kwargs):
7
+ extend_class(kwargs, "input input-bordered w-full")
8
+ return super().__call__(field, **kwargs)
9
+ return Input
10
+
11
+
12
+ TextInput = create_input_class(widgets.TextInput)
13
+ PasswordInput = create_input_class(widgets.PasswordInput)
14
+ EmailInput = create_input_class(widgets.EmailInput)
15
+
16
+
17
+ def extend_class(kwargs, classes):
18
+ c = kwargs.pop('class', '') or kwargs.pop('class_', '')
19
+ kwargs['class'] = '%s %s' % (classes, c)
hyperflask/layout.html ADDED
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="{{config.get("SITE_LANG", "en")}}" {% block html_tag_attrs %}{% endblock %}>
3
+ <head>
4
+ {% block head -%}
5
+ {% block head_metas %}
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ {% endblock %}
9
+ {% if config["PWA"] -%}
10
+ <link rel="manifest" href="{{url_for("pwa_manifest")}}" />
11
+ {%- endif %}
12
+ {% block page_title %}<title>{% if page_title is defined %}{{ page_title }}{% if config.get("SITE_TITLE") %} | {% endif %}{% endif %}{% if config.get("SITE_TITLE") %}{{config["SITE_TITLE"]}}{% endif %}</title>{% endblock %}
13
+ {% asset_tags %}
14
+ {% block head_assets %}{% endblock %}
15
+ {%- endblock %}
16
+ </head>
17
+ {% block body_tag -%}
18
+ <body
19
+ {% if config["HTMX_BOOST_SITE"] %}hx-boost="true"{% endif %}
20
+ {% if config["PWA"] %}hx-ext="pwa" pwa-service-worker="{{url_for('pwa_service_worker')}}"{% endif %}
21
+ {% block body_tag_attrs %}{% endblock %}
22
+ >
23
+ {%- endblock %}
24
+ {% block body %}{% block content %}{% endblock %}{% endblock %}
25
+ {% block footer %}{% endblock %}
26
+ {% block body_end %}{% endblock %}
27
+ </body>
28
+ </html>
@@ -0,0 +1,36 @@
1
+ from flask_file_routes import DEFAULT_HELPERS
2
+ from flask import g, current_app
3
+ from .utils.page_actions import page_action
4
+
5
+
6
+ def csrf_protect(page):
7
+ current_app.csrf.protect()
8
+
9
+
10
+ def forms(page):
11
+ return current_app.forms[page.template]
12
+
13
+
14
+ def form(page):
15
+ def form_helper(template=None, **kwargs):
16
+ page.form = current_app.forms[template or page.template](**kwargs)
17
+ return page.form
18
+ return form_helper
19
+
20
+
21
+ def stream(page):
22
+ g.__page_stream__ = True
23
+ return
24
+
25
+
26
+ def action(page):
27
+ def decorator(func=None, name=None):
28
+ return page_action(func, name)
29
+ return decorator
30
+
31
+
32
+ DEFAULT_HELPERS.update(csrf_protect=csrf_protect,
33
+ forms=forms,
34
+ form=form,
35
+ stream=stream,
36
+ action=action)
@@ -0,0 +1,46 @@
1
+ from jinja2.ext import Extension
2
+ from jinja_super_macros import html_tag
3
+ from flask import current_app
4
+ from .resources import ResourceList, Resource
5
+
6
+
7
+ def reactive(obj, attr=None):
8
+ if attr and hasattr(getattr(obj, attr), "__reactive__"):
9
+ obj = getattr(obj, attr)
10
+ attr = None
11
+ elif not hasattr(obj, "__reactive__"):
12
+ return getattr(obj, attr) if attr else obj
13
+
14
+ current_app.assets.include("@hyperflask/reactive")
15
+
16
+ mercure_topic, many = obj.__reactive__()
17
+
18
+ if many:
19
+ if attr:
20
+ raise Exception("Cannot access attribute of a ResourceList")
21
+ return html_tag("div", {"x-reactive-list": obj.resource.name})
22
+
23
+ return html_tag("span", {"x-reactive-prop": obj.resource.name, "data-attr": attr})
24
+
25
+
26
+ class ReactiveExtension(Extension):
27
+ def __init__(self, environment):
28
+ super().__init__(environment)
29
+ environment.globals["reactive"] = reactive
30
+
31
+ def preprocess(self, source, name, filename=None):
32
+ pos = source.find("{!")
33
+ while pos >= 0:
34
+ end = source.find("!}", pos)
35
+ if end < 0:
36
+ raise Exception("Missing closing bracket for reactive")
37
+ value = source[pos + 2:end]
38
+ if "." in value:
39
+ obj, attr = value.rsplit(".", 1)
40
+ args = [obj, attr]
41
+ else:
42
+ args = [value]
43
+ replacement = "{{ reactive(" + ", ".join(args) + ") }}"
44
+ source = source[:pos] + replacement + source[end + 2:]
45
+ pos = source.find("{!", pos + len(replacement) + 2)
46
+ return source
@@ -0,0 +1,149 @@
1
+ from hyperflask import db
2
+ from sqlorm import Model
3
+ from flask_apispec import marshal_with, use_args
4
+ from flask_apispec.views import MethodResource
5
+ from werkzeug.exceptions import HTTPException
6
+ import typing as t
7
+
8
+
9
+ class ResourceMeta(type):
10
+ def __new__(cls, name, bases, attrs):
11
+ cls = super().__new__(cls, name, bases, attrs)
12
+ if not bases:
13
+ return cls
14
+
15
+ if not hasattr(cls, "schema"):
16
+ cls.schema = create_model_schema(cls.model)
17
+
18
+ return cls
19
+
20
+
21
+ def create_model_schema(model):
22
+ pass
23
+
24
+
25
+ class ResourceError(HTTPException):
26
+ def __init__(self, description, code=400):
27
+ super().__init__(description)
28
+ self.code = code
29
+
30
+
31
+ class ResourceNotFound(ResourceError):
32
+ def __init__(self):
33
+ super().__init__("URL not found", 404)
34
+
35
+
36
+ class Resource(metaclass=ResourceMeta):
37
+ model: t.ClassVar[Model]
38
+ url_prefix = None
39
+ object_url_prefix = "/<id>"
40
+ expose_create = True
41
+ expose_edit = True
42
+ expose_methods = t.ClassVar[t.Sequence[str]]
43
+
44
+ @classmethod
45
+ def list(cls):
46
+ return ResourceList(cls, cls.model.find_all())
47
+
48
+ @classmethod
49
+ def create(cls, **kwargs):
50
+ with db:
51
+ return cls(cls.model.create(**kwargs))
52
+
53
+ @classmethod
54
+ def get(cls, id):
55
+ obj = cls.model.get(id)
56
+ if not obj:
57
+ raise ResourceNotFound()
58
+ return cls(obj)
59
+
60
+ def __init__(self, obj):
61
+ self.obj = obj
62
+
63
+ def update(self, data):
64
+ with db:
65
+ self.obj.update(**data)
66
+
67
+ def delete(self):
68
+ with db:
69
+ self.obj.delete()
70
+
71
+ def __getattr__(self, name):
72
+ return getattr(self.obj, name)
73
+
74
+ def __reactive__(self):
75
+ return
76
+
77
+ @classmethod
78
+ def register(cls, app, url_prefix="", endpoint_prefix=""):
79
+ from .api import create_view_func
80
+
81
+ @marshal_with(cls.schema(many=True))
82
+ def many_get():
83
+ return cls.list()
84
+
85
+ @use_args(cls.schema)
86
+ @marshal_with(cls.schema)
87
+ def many_post(data):
88
+ return cls.create(**data)
89
+
90
+ @marshal_with(cls.schema)
91
+ def single_get(id):
92
+ return cls.get(id)
93
+
94
+ @use_args(cls.schema)
95
+ @marshal_with(cls.schema)
96
+ def single_put(data, id):
97
+ obj = cls.get(id)
98
+ obj.update(data)
99
+ return obj
100
+
101
+ def single_delete(id):
102
+ obj = cls.get(id)
103
+ obj.delete()
104
+ return {}, 204
105
+
106
+ many_methods = {"get": many_get}
107
+ if cls.expose_create:
108
+ many_methods["post"] = many_post
109
+ many_view = type(f"{cls.__name__}ManyView", (MethodResource,), many_methods)
110
+ many_endpoint = endpoint_prefix + cls.__name__.lower() + "_many"
111
+
112
+ single_methods = {"get": single_get}
113
+ if cls.expose_edit:
114
+ single_methods.update({"put": single_put, "delete": single_delete})
115
+ single_view = type(f"{cls.__name__}View", (MethodResource,), single_methods)
116
+ single_endpoint = endpoint_prefix + cls.__name__.lower()
117
+
118
+ app.add_url_rule(url_prefix + cls.url_prefix, view_func=many_view.as_view(many_endpoint))
119
+ app.add_url_rule(url_prefix + cls.url_prefix + cls.object_url_prefix, view_func=single_view.as_view(single_endpoint))
120
+
121
+ endpoints = [many_endpoint, single_endpoint]
122
+
123
+ for attr in dir(cls):
124
+ if hasattr(getattr(cls, attr), "__expose__"):
125
+ rule, endpoint, kwargs = getattr(cls, attr).__expose__
126
+ view_func = create_view_func(getattr(cls, attr))
127
+ app.add_url_rule(url_prefix + cls.url_prefix + rule, endpoint_prefix + endpoint, view_func=view_func, **kwargs)
128
+ endpoints.append(endpoint_prefix + endpoint)
129
+
130
+ return endpoints
131
+
132
+
133
+ def expose(rule=None, endpoint=None, **kwargs):
134
+ def decorator(func):
135
+ func.__expose__ = (rule or func.__name__, endpoint or func.__name__, kwargs)
136
+ return func
137
+ return decorator
138
+
139
+
140
+ class ResourceList:
141
+ def __init__(self, resource_class, objects):
142
+ self.resource_class = resource_class
143
+ self.objects = objects
144
+
145
+ def __iter__(self):
146
+ return [self.resource_class(obj) for obj in self.objects]
147
+
148
+ def __reactive__(self):
149
+ return
@@ -0,0 +1,5 @@
1
+ import Alpine from 'alpinejs'
2
+
3
+ window.Alpine = Alpine
4
+
5
+ export default Alpine
@@ -0,0 +1,5 @@
1
+ import "./htmx"
2
+
3
+ if (window.Alpine) {
4
+ window.Alpine.start()
5
+ }
@@ -0,0 +1,5 @@
1
+ import htmx from "htmx.org";
2
+
3
+ window.htmx = htmx;
4
+
5
+ export default htmx;
@@ -0,0 +1,5 @@
1
+ import Alpine from "./alpine"
2
+
3
+ Alpine.directive("reactive", (el, { value, modifiers, expression }, { Alpine, effect, cleanup }) => {
4
+
5
+ })
@@ -0,0 +1,4 @@
1
+ import { Application, Controller } from "@hotwired/stimulus"
2
+
3
+ const Stimulus = Application.start();
4
+ export { Stimulus, Controller };
@@ -0,0 +1,55 @@
1
+
2
+
3
+ export function _(text, ...args) {
4
+ return formatTranslationString(getCatalogValue(text)[1] || text, ...args)
5
+ }
6
+
7
+ export function _p(context, text, ...args) {
8
+ return formatTranslationString(getCatalogValue(text, context)[1] || text, ...args)
9
+ }
10
+
11
+ export function _n(singular, plural, count, args, context) {
12
+ args = args || {};
13
+ args['count'] = count;
14
+ const value = getCatalogValue(singular, context);
15
+ if (value) {
16
+ if (value[1]) {
17
+ singular = value[1];
18
+ }
19
+ if (value[2]) {
20
+ plural = value[2];
21
+ }
22
+ }
23
+ if (count > 1) {
24
+ return formatTranslationString(plural, args);
25
+ }
26
+ return formatTranslationString(singular, args);
27
+ }
28
+
29
+ export function _np(context, singular, plural, count, args) {
30
+ return _n(singular, plural, count, args, context);
31
+ }
32
+
33
+ function formatTranslationString(text, ...data) {
34
+ if (data.length) {
35
+ if (data.length === 1 && $.isPlainObject(data[0])) {
36
+ data = data[0];
37
+ }
38
+ for (let [key, value] of Object.entries(data)) {
39
+ text = text.replace(new RegExp('\\{' + key + '\\}', 'gi'), value);
40
+ }
41
+ }
42
+ return text;
43
+ }
44
+
45
+ function getCatalogValue(text, context) {
46
+ const messageId = context ? context + '::' + text : text;
47
+ if (typeof(catalog[messageId]) !== 'undefined' && catalog[messageId][1]) {
48
+ return catalog[messageId];
49
+ }
50
+ return [null, null];
51
+ }
52
+
53
+ export function getCurrentLocale() {
54
+
55
+ }
@@ -0,0 +1,3 @@
1
+ [DaisyUI](https://daisyui.com) components generated from [uilibspec-daisyui](https:/github.com/hyperflask/uilib-spec-daisyui) using [uilibspec](https:/github.com/hyperflask/uilib-spec)
2
+
3
+ This folder is auto-generated!
@@ -0,0 +1,6 @@
1
+ {% macro Button(style=None, active=None, outline=None, size=None, wide=None, glass=None) -%}
2
+ <button class="btn {{kwargs.pop('class', '')}} {% if style %} btn-{{style}}{% endif %} {% if active %} btn-active{% endif %} {% if outline %} btn-outline{% endif %} {% if size %} btn-{{size}}{% endif %} {% if wide %} btn-wide{% endif %} {% if glass %} glass{% endif %}" {{ html_attrs(kwargs) }}>
3
+ {{ caller() }}
4
+
5
+ </button>
6
+ {%- endmacro %}
@@ -0,0 +1,16 @@
1
+ {% macro Dropdown() -%}
2
+ <div class="dropdown">
3
+
4
+ <div tabindex="0" role="button" class="btn m-1">
5
+ Click
6
+ </div>
7
+
8
+ <ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
9
+
10
+ {{ caller() }}
11
+
12
+
13
+ </ul>
14
+
15
+ </div>
16
+ {%- endmacro %}
@@ -0,0 +1,8 @@
1
+ {% macro DropdownItem() -%}
2
+ <li >
3
+ <a >
4
+ {{ caller() }}
5
+
6
+ </a>
7
+ </li>
8
+ {%- endmacro %}
@@ -0,0 +1,6 @@
1
+ {% macro LinkButton(style=None, active=None, outline=None, size=None, wide=None, glass=None) -%}
2
+ <a class="btn {{kwargs.pop('class', '')}} {% if style %} btn-{{style}}{% endif %} {% if active %} btn-active{% endif %} {% if outline %} btn-outline{% endif %} {% if size %} btn-{{size}}{% endif %} {% if wide %} btn-wide{% endif %} {% if glass %} glass{% endif %}" {{ html_attrs(kwargs) }}>
3
+ {{ caller() }}
4
+
5
+ </a>
6
+ {%- endmacro %}