From 9fd30aa4088f153aee4b64f970efec9cc39bd7dd Mon Sep 17 00:00:00 2001 From: Mark <16909269+Archmonger@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:09:02 -0800 Subject: [PATCH] v3.0.0a1: Bump IDOM to 1.0.0 pre-release (#125) - Modify docs to use the upcoming IDOM-Core docs styling - Move docs python examples to individual files so we can run tests on them - CI for type checking + linting docs examples - Minor wording and section naming changes to feel more React-like - Bump IDOM to 1.0.0 pre-release - Use the new `idom.html` API - Update package.json to be compatible with `idom>=1.0.0` - Make the main `requirements.txt` be fully inclusive of all dev/user/docs dependencies to simplify development workflow - Update `setup.py` to automatically install the latest NPM, and be easier to debug when things fail --- .github/workflows/test-docs.yml | 16 +- .gitignore | 1 + CHANGELOG.md | 49 +- README.md | 2 +- docs/includes/examples.md | 10 - docs/includes/pr.md | 3 + docs/overrides/main.html | 13 + docs/python/__init__.py | 0 docs/python/auth-required-attribute.py | 9 + .../auth-required-component-fallback.py | 14 + .../auth-required-custom-attribute-model.py | 7 + docs/python/auth-required-custom-attribute.py | 9 + docs/python/auth-required-vdom-fallback.py | 9 + docs/python/auth-required.py | 9 + docs/python/configure-asgi.py | 27 + docs/python/configure-channels.py | 5 + docs/python/configure-installed-apps.py | 4 + docs/python/configure-urls.py | 7 + docs/python/django-css-external-link.py | 11 + docs/python/django-css-local-link.py | 10 + docs/python/django-css.py | 11 + docs/python/django-js-local-script.py | 10 + docs/python/django-js-remote-script.py | 9 + docs/python/django-js.py | 11 + docs/python/django-query-postprocessor.py | 24 + docs/python/example/__init__.py | 0 docs/python/example/models.py | 5 + docs/python/example/urls.py | 7 + docs/python/example/views.py | 5 + docs/python/settings.py | 15 + docs/python/template-tag-args-kwargs.py | 6 + docs/python/template-tag-bad-view.py | 6 + docs/python/use-connection.py | 9 + docs/python/use-location.py | 9 + docs/python/use-mutation-args-kwargs.py | 16 + docs/python/use-mutation-query-refetch.py | 45 ++ docs/python/use-mutation-reset.py | 33 + docs/python/use-mutation.py | 29 + docs/python/use-origin.py | 9 + docs/python/use-query-args.py | 18 + docs/python/use-query-postprocessor-change.py | 32 + .../python/use-query-postprocessor-disable.py | 22 + docs/python/use-query-postprocessor-kwargs.py | 32 + docs/python/use-query.py | 22 + docs/python/use-scope.py | 9 + docs/python/vtc-args-kwargs.py | 22 + docs/python/vtc-cbv-compatibility.py | 11 + docs/python/vtc-cbv.py | 20 + docs/python/vtc-compatibility.py | 16 + docs/python/vtc-fbv-compat.py | 9 + docs/python/vtc-func.py | 14 + docs/python/vtc-request.py | 22 + docs/python/vtc-strict-parsing.py | 16 + docs/python/vtc-transforms.py | 22 + docs/python/vtc.py | 16 + docs/src/changelog/index.md | 5 +- docs/src/contribute/code.md | 18 +- docs/src/contribute/docs.md | 16 +- docs/src/contribute/running-tests.md | 25 +- docs/src/features/components.md | 209 +---- docs/src/features/decorators.md | 58 +- docs/src/features/hooks.md | 330 ++------ docs/src/features/settings.md | 30 +- .../{templatetag.md => template-tag.md} | 83 +- docs/src/features/utils.md | 33 +- .../choose-django-app.md | 8 +- .../create-component.md | 6 +- .../installation.md | 53 +- .../learn-more.md | 8 +- .../render-view.md | 18 +- .../use-template-tag.md} | 12 +- docs/src/stylesheets/extra.css | 240 +++++- mkdocs.yml | 46 +- requirements.txt | 7 +- requirements/build-docs.txt | 2 + requirements/pkg-deps.txt | 2 +- setup.py | 46 +- src/django_idom/__init__.py | 2 +- src/django_idom/components.py | 5 +- src/django_idom/http/views.py | 6 +- src/django_idom/websocket/consumer.py | 10 +- src/js/package-lock.json | 725 +++++++++++++----- src/js/package.json | 17 +- src/js/rollup.config.js | 29 - src/js/rollup.config.mjs | 21 + tests/test_app/components.py | 116 ++- 86 files changed, 1896 insertions(+), 1067 deletions(-) delete mode 100644 docs/includes/examples.md create mode 100644 docs/includes/pr.md create mode 100644 docs/overrides/main.html create mode 100644 docs/python/__init__.py create mode 100644 docs/python/auth-required-attribute.py create mode 100644 docs/python/auth-required-component-fallback.py create mode 100644 docs/python/auth-required-custom-attribute-model.py create mode 100644 docs/python/auth-required-custom-attribute.py create mode 100644 docs/python/auth-required-vdom-fallback.py create mode 100644 docs/python/auth-required.py create mode 100644 docs/python/configure-asgi.py create mode 100644 docs/python/configure-channels.py create mode 100644 docs/python/configure-installed-apps.py create mode 100644 docs/python/configure-urls.py create mode 100644 docs/python/django-css-external-link.py create mode 100644 docs/python/django-css-local-link.py create mode 100644 docs/python/django-css.py create mode 100644 docs/python/django-js-local-script.py create mode 100644 docs/python/django-js-remote-script.py create mode 100644 docs/python/django-js.py create mode 100644 docs/python/django-query-postprocessor.py create mode 100644 docs/python/example/__init__.py create mode 100644 docs/python/example/models.py create mode 100644 docs/python/example/urls.py create mode 100644 docs/python/example/views.py create mode 100644 docs/python/settings.py create mode 100644 docs/python/template-tag-args-kwargs.py create mode 100644 docs/python/template-tag-bad-view.py create mode 100644 docs/python/use-connection.py create mode 100644 docs/python/use-location.py create mode 100644 docs/python/use-mutation-args-kwargs.py create mode 100644 docs/python/use-mutation-query-refetch.py create mode 100644 docs/python/use-mutation-reset.py create mode 100644 docs/python/use-mutation.py create mode 100644 docs/python/use-origin.py create mode 100644 docs/python/use-query-args.py create mode 100644 docs/python/use-query-postprocessor-change.py create mode 100644 docs/python/use-query-postprocessor-disable.py create mode 100644 docs/python/use-query-postprocessor-kwargs.py create mode 100644 docs/python/use-query.py create mode 100644 docs/python/use-scope.py create mode 100644 docs/python/vtc-args-kwargs.py create mode 100644 docs/python/vtc-cbv-compatibility.py create mode 100644 docs/python/vtc-cbv.py create mode 100644 docs/python/vtc-compatibility.py create mode 100644 docs/python/vtc-fbv-compat.py create mode 100644 docs/python/vtc-func.py create mode 100644 docs/python/vtc-request.py create mode 100644 docs/python/vtc-strict-parsing.py create mode 100644 docs/python/vtc-transforms.py create mode 100644 docs/python/vtc.py rename docs/src/features/{templatetag.md => template-tag.md} (68%) rename docs/src/{getting-started => get-started}/choose-django-app.md (84%) rename docs/src/{getting-started => get-started}/create-component.md (94%) rename docs/src/{getting-started => get-started}/installation.md (60%) rename docs/src/{getting-started => get-started}/learn-more.md (54%) rename docs/src/{getting-started => get-started}/render-view.md (84%) rename docs/src/{getting-started/reference-component.md => get-started/use-template-tag.md} (63%) delete mode 100644 src/js/rollup.config.js create mode 100644 src/js/rollup.config.mjs diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 5b5bcf6b..4db0842e 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -20,6 +20,16 @@ jobs: - uses: actions/setup-python@v4 with: python-version: 3.x - - run: pip install -r requirements/build-docs.txt - - run: linkcheckMarkdown docs/ -v -r - - run: mkdocs build --strict + - name: Check docs build + run: | + pip install -r requirements/build-docs.txt + linkcheckMarkdown docs/ -v -r + mkdocs build --strict + - name: Check docs examples + run: | + pip install -r requirements/check-types.txt + pip install -r requirements/check-style.txt + mypy --show-error-codes docs/python/ + black docs/python/ --check + isort docs/python/ --check-only + flake8 docs/python/ diff --git a/.gitignore b/.gitignore index 2477a484..90028709 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ settings.json *$py.class # Distribution / packaging +build/ .Python build/ develop-eggs/ dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac34c86..aca2dceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,18 +10,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +- Nothing (yet) + +## [3.0.0a1] - 2023-02-02 + +???+ note + + This is Django-IDOM's biggest update yet! + + To upgrade from previous version you will need to... + + 1. Install `django-idom >= 3.0.0` + 2. Run `idom update-html-usages ` to update your `idom.html.*` calls to the new syntax + 3. Run `python manage.py migrate` to create the new Django-IDOM database entries + ### Added - The `idom` client will automatically configure itself to debug mode depending on `settings.py:DEBUG`. @@ -29,11 +55,13 @@ Using the following categories, list your changes in this order: ### Changed +- It is now mandatory to run `manage.py migrate` after installing IDOM. +- Bumped the minimum IDOM version to 1.0.0 + - Due to IDOM 1.0.0, `idom.html.*`, HTML properties are now `snake_case` `**kwargs` rather than a `dict` of values. + - You can auto-convert to the new style using `idom update-html-usages `. - The `component` template tag now supports both positional and keyword arguments. - The `component` template tag now supports non-serializable arguments. - `IDOM_WS_MAX_RECONNECT_TIMEOUT` setting has been renamed to `IDOM_RECONNECT_MAX`. -- It is now mandatory to run `manage.py migrate` after installing IDOM. -- Bumped the minimum IDOM version to 0.43.0 ### Removed @@ -51,7 +79,7 @@ Using the following categories, list your changes in this order: - Fixed a potential method of component template tag argument spoofing. - Exception information will no longer be displayed on the page, based on the value of `settings.py:DEBUG`. -## [2.2.1] - 2022-01-09 +## [2.2.1] - 2023-01-09 ### Fixed @@ -218,7 +246,8 @@ Using the following categories, list your changes in this order: - Support for IDOM within the Django -[unreleased]: https://github.com/idom-team/django-idom/compare/2.2.1...HEAD +[unreleased]: https://github.com/idom-team/django-idom/compare/3.0.0a1...HEAD +[3.0.0a1]: https://github.com/idom-team/django-idom/compare/2.2.1...3.0.0a1 [2.2.1]: https://github.com/idom-team/django-idom/compare/2.2.0...2.2.1 [2.2.0]: https://github.com/idom-team/django-idom/compare/2.1.0...2.2.0 [2.1.0]: https://github.com/idom-team/django-idom/compare/2.0.1...2.1.0 diff --git a/README.md b/README.md index 11e19477..2e706b41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Django IDOM · [![Tests](https://github.com/idom-team/django-idom/workflows/Test/badge.svg?event=push)](https://github.com/idom-team/django-idom/actions?query=workflow%3ATest) [![PyPI Version](https://img.shields.io/pypi/v/django-idom.svg?label=PyPI)](https://pypi.python.org/pypi/django-idom) [![License](https://img.shields.io/badge/License-MIT-purple.svg)](https://github.com/idom-team/django-idom/blob/main/LICENSE) [![Docs](https://img.shields.io/website?down_message=offline&label=Docs&logo=read%20the%20docs&logoColor=white&up_message=online&url=https%3A%2F%2Fidom-team.github.io%2Fdjango-idom%2F)](https://idom-team.github.io/django-idom/) +# Django-IDOM · [![Tests](https://github.com/idom-team/django-idom/workflows/Test/badge.svg?event=push)](https://github.com/idom-team/django-idom/actions?query=workflow%3ATest) [![PyPI Version](https://img.shields.io/pypi/v/django-idom.svg?label=PyPI)](https://pypi.python.org/pypi/django-idom) [![License](https://img.shields.io/badge/License-MIT-purple.svg)](https://github.com/idom-team/django-idom/blob/main/LICENSE) [![Docs](https://img.shields.io/website?down_message=offline&label=Docs&logo=read%20the%20docs&logoColor=white&up_message=online&url=https%3A%2F%2Fidom-team.github.io%2Fdjango-idom%2F)](https://idom-team.github.io/django-idom/) diff --git a/docs/includes/examples.md b/docs/includes/examples.md deleted file mode 100644 index 6db558c0..00000000 --- a/docs/includes/examples.md +++ /dev/null @@ -1,10 +0,0 @@ - - -```python -from django.db import models - -class TodoItem(models.Model): - text = models.CharField(max_length=255) -``` - - diff --git a/docs/includes/pr.md b/docs/includes/pr.md new file mode 100644 index 00000000..c701e8e2 --- /dev/null +++ b/docs/includes/pr.md @@ -0,0 +1,3 @@ +Now, you can create/modify the Django-IDOM source code, and Pull Request (PR) your changes to our GitHub repository. + +To learn how to create GitHub PRs, [click here](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 00000000..e70aa10c --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block content %} +{{ super() }} + +{% if git_page_authors %} +
+ + Authors: {{ git_page_authors | default('enable mkdocs-git-authors-plugin') }} + +
+{% endif %} +{% endblock %} diff --git a/docs/python/__init__.py b/docs/python/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/python/auth-required-attribute.py b/docs/python/auth-required-attribute.py new file mode 100644 index 00000000..f88cd064 --- /dev/null +++ b/docs/python/auth-required-attribute.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.decorators import auth_required + + +@component +@auth_required(auth_attribute="is_staff") +def my_component(): + return html.div("I am logged in!") diff --git a/docs/python/auth-required-component-fallback.py b/docs/python/auth-required-component-fallback.py new file mode 100644 index 00000000..4ad205ad --- /dev/null +++ b/docs/python/auth-required-component-fallback.py @@ -0,0 +1,14 @@ +from idom import component, html + +from django_idom.decorators import auth_required + + +@component +def my_component_fallback(): + return html.div("I am NOT logged in!") + + +@component +@auth_required(fallback=my_component_fallback) +def my_component(): + return html.div("I am logged in!") diff --git a/docs/python/auth-required-custom-attribute-model.py b/docs/python/auth-required-custom-attribute-model.py new file mode 100644 index 00000000..cecb6fa4 --- /dev/null +++ b/docs/python/auth-required-custom-attribute-model.py @@ -0,0 +1,7 @@ +from django.contrib.auth.models import AbstractBaseUser + + +class CustomUserModel(AbstractBaseUser): + @property + def is_really_cool(self): + return True diff --git a/docs/python/auth-required-custom-attribute.py b/docs/python/auth-required-custom-attribute.py new file mode 100644 index 00000000..336af9c5 --- /dev/null +++ b/docs/python/auth-required-custom-attribute.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.decorators import auth_required + + +@component +@auth_required(auth_attribute="is_really_cool") +def my_component(): + return html.div("I am logged in!") diff --git a/docs/python/auth-required-vdom-fallback.py b/docs/python/auth-required-vdom-fallback.py new file mode 100644 index 00000000..80ceb5d7 --- /dev/null +++ b/docs/python/auth-required-vdom-fallback.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.decorators import auth_required + + +@component +@auth_required(fallback=html.div("I am NOT logged in!")) +def my_component(): + return html.div("I am logged in!") diff --git a/docs/python/auth-required.py b/docs/python/auth-required.py new file mode 100644 index 00000000..fe815f3a --- /dev/null +++ b/docs/python/auth-required.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.decorators import auth_required + + +@component +@auth_required +def my_component(): + return html.div("I am logged in!") diff --git a/docs/python/configure-asgi.py b/docs/python/configure-asgi.py new file mode 100644 index 00000000..46260bb7 --- /dev/null +++ b/docs/python/configure-asgi.py @@ -0,0 +1,27 @@ +import os + +from django.core.asgi import get_asgi_application + + +# Ensure DJANGO_SETTINGS_MODULE is set properly based on your project name! +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings") + +# Fetch ASGI application before importing dependencies that require ORM models. +django_asgi_app = get_asgi_application() + + +from channels.auth import AuthMiddlewareStack # noqa: E402 +from channels.routing import ProtocolTypeRouter, URLRouter # noqa: E402 +from channels.sessions import SessionMiddlewareStack # noqa: E402 + +from django_idom import IDOM_WEBSOCKET_PATH # noqa: E402 + + +application = ProtocolTypeRouter( + { + "http": django_asgi_app, + "websocket": SessionMiddlewareStack( + AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH])) + ), + } +) diff --git a/docs/python/configure-channels.py b/docs/python/configure-channels.py new file mode 100644 index 00000000..337a922f --- /dev/null +++ b/docs/python/configure-channels.py @@ -0,0 +1,5 @@ +INSTALLED_APPS = [ + "daphne", + ..., +] +ASGI_APPLICATION = "example_project.asgi.application" diff --git a/docs/python/configure-installed-apps.py b/docs/python/configure-installed-apps.py new file mode 100644 index 00000000..f1d7860a --- /dev/null +++ b/docs/python/configure-installed-apps.py @@ -0,0 +1,4 @@ +INSTALLED_APPS = [ + "django_idom", + ..., +] diff --git a/docs/python/configure-urls.py b/docs/python/configure-urls.py new file mode 100644 index 00000000..db3a6780 --- /dev/null +++ b/docs/python/configure-urls.py @@ -0,0 +1,7 @@ +from django.urls import include, path + + +urlpatterns = [ + path("idom/", include("django_idom.http.urls")), + ..., +] diff --git a/docs/python/django-css-external-link.py b/docs/python/django-css-external-link.py new file mode 100644 index 00000000..22018a31 --- /dev/null +++ b/docs/python/django-css-external-link.py @@ -0,0 +1,11 @@ +from idom import component, html + + +@component +def my_component(): + return html.div( + html.link( + {"rel": "stylesheet", "href": "https://example.com/external-styles.css"} + ), + html.button("My Button!"), + ) diff --git a/docs/python/django-css-local-link.py b/docs/python/django-css-local-link.py new file mode 100644 index 00000000..b6fc1e53 --- /dev/null +++ b/docs/python/django-css-local-link.py @@ -0,0 +1,10 @@ +from django.templatetags.static import static +from idom import component, html + + +@component +def my_component(): + return html.div( + html.link({"rel": "stylesheet", "href": static("css/buttons.css")}), + html.button("My Button!"), + ) diff --git a/docs/python/django-css.py b/docs/python/django-css.py new file mode 100644 index 00000000..1f5a31da --- /dev/null +++ b/docs/python/django-css.py @@ -0,0 +1,11 @@ +from idom import component, html + +from django_idom.components import django_css + + +@component +def my_component(): + return html.div( + django_css("css/buttons.css"), + html.button("My Button!"), + ) diff --git a/docs/python/django-js-local-script.py b/docs/python/django-js-local-script.py new file mode 100644 index 00000000..58021edb --- /dev/null +++ b/docs/python/django-js-local-script.py @@ -0,0 +1,10 @@ +from django.templatetags.static import static +from idom import component, html + + +@component +def my_component(): + return html.div( + html.script({"src": static("js/scripts.js")}), + html.button("My Button!"), + ) diff --git a/docs/python/django-js-remote-script.py b/docs/python/django-js-remote-script.py new file mode 100644 index 00000000..c1e734e0 --- /dev/null +++ b/docs/python/django-js-remote-script.py @@ -0,0 +1,9 @@ +from idom import component, html + + +@component +def my_component(): + return html.div( + html.script({"src": "https://example.com/external-scripts.js"}), + html.button("My Button!"), + ) diff --git a/docs/python/django-js.py b/docs/python/django-js.py new file mode 100644 index 00000000..fd7197ba --- /dev/null +++ b/docs/python/django-js.py @@ -0,0 +1,11 @@ +from idom import component, html + +from django_idom.components import django_js + + +@component +def my_component(): + return html.div( + html.button("My Button!"), + django_js("js/scripts.js"), + ) diff --git a/docs/python/django-query-postprocessor.py b/docs/python/django-query-postprocessor.py new file mode 100644 index 00000000..36994a28 --- /dev/null +++ b/docs/python/django-query-postprocessor.py @@ -0,0 +1,24 @@ +from example.models import TodoItem +from idom import component + +from django_idom.hooks import use_query +from django_idom.types import QueryOptions +from django_idom.utils import django_query_postprocessor + + +def get_items(): + return TodoItem.objects.all() + + +@component +def todo_list(): + # These `QueryOptions` are functionally equivalent to Django-IDOM's default values + item_query = use_query( + QueryOptions( + postprocessor=django_query_postprocessor, + postprocessor_kwargs={"many_to_many": True, "many_to_one": True}, + ), + get_items, + ) + + return item_query.data diff --git a/docs/python/example/__init__.py b/docs/python/example/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/python/example/models.py b/docs/python/example/models.py new file mode 100644 index 00000000..ec98be92 --- /dev/null +++ b/docs/python/example/models.py @@ -0,0 +1,5 @@ +from django.db.models import CharField, Model + + +class TodoItem(Model): + text: CharField = CharField(max_length=255) diff --git a/docs/python/example/urls.py b/docs/python/example/urls.py new file mode 100644 index 00000000..e3d7175d --- /dev/null +++ b/docs/python/example/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from example import views + + +urlpatterns = [ + path("example/", views.index), +] diff --git a/docs/python/example/views.py b/docs/python/example/views.py new file mode 100644 index 00000000..a8ed7fdb --- /dev/null +++ b/docs/python/example/views.py @@ -0,0 +1,5 @@ +from django.shortcuts import render + + +def index(request): + return render(request, "my-template.html") diff --git a/docs/python/settings.py b/docs/python/settings.py new file mode 100644 index 00000000..f7ee0f15 --- /dev/null +++ b/docs/python/settings.py @@ -0,0 +1,15 @@ +# If "idom" cache is not configured, then "default" will be used +# IDOM works best with a multiprocessing-safe and thread-safe cache backend. +CACHES = { + "idom": {"BACKEND": ...}, +} + +# Maximum seconds between reconnection attempts before giving up. +# Use `0` to prevent component reconnection. +IDOM_RECONNECT_MAX = 259200 + +# The URL for IDOM to serve the component rendering websocket +IDOM_WEBSOCKET_URL = "idom/" + +# Dotted path to the default postprocessor function, or `None` +IDOM_DEFAULT_QUERY_POSTPROCESSOR = "example_project.utils.my_postprocessor" diff --git a/docs/python/template-tag-args-kwargs.py b/docs/python/template-tag-args-kwargs.py new file mode 100644 index 00000000..0eb96bd6 --- /dev/null +++ b/docs/python/template-tag-args-kwargs.py @@ -0,0 +1,6 @@ +from idom import component + + +@component +def frog_greeter(number, name, species=""): + return f"Hello #{number}, {name} the {species}!" diff --git a/docs/python/template-tag-bad-view.py b/docs/python/template-tag-bad-view.py new file mode 100644 index 00000000..a798abb0 --- /dev/null +++ b/docs/python/template-tag-bad-view.py @@ -0,0 +1,6 @@ +from django.shortcuts import render + + +def example_view(request): + context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"} + return render(request, "my-template.html", context_vars) diff --git a/docs/python/use-connection.py b/docs/python/use-connection.py new file mode 100644 index 00000000..cbee5c04 --- /dev/null +++ b/docs/python/use-connection.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.hooks import use_connection + + +@component +def my_component(): + my_connection = use_connection() + return html.div(str(my_connection)) diff --git a/docs/python/use-location.py b/docs/python/use-location.py new file mode 100644 index 00000000..e4c38caf --- /dev/null +++ b/docs/python/use-location.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.hooks import use_location + + +@component +def my_component(): + my_location = use_location() + return html.div(str(my_location)) diff --git a/docs/python/use-mutation-args-kwargs.py b/docs/python/use-mutation-args-kwargs.py new file mode 100644 index 00000000..9df5861f --- /dev/null +++ b/docs/python/use-mutation-args-kwargs.py @@ -0,0 +1,16 @@ +from idom import component + +from django_idom.hooks import use_mutation + + +def example_mutation(value: int, other_value: bool = False): + ... + + +@component +def my_component(): + mutation = use_mutation(example_mutation) + + mutation.execute(123, other_value=True) + + ... diff --git a/docs/python/use-mutation-query-refetch.py b/docs/python/use-mutation-query-refetch.py new file mode 100644 index 00000000..065bb433 --- /dev/null +++ b/docs/python/use-mutation-query-refetch.py @@ -0,0 +1,45 @@ +from example.models import TodoItem +from idom import component, html + +from django_idom.hooks import use_mutation, use_query + + +def get_items(): + return TodoItem.objects.all() + + +def add_item(text: str): + TodoItem(text=text).save() + + +@component +def todo_list(): + item_query = use_query(get_items) + item_mutation = use_mutation(add_item, refetch=get_items) + + def submit_event(event): + if event["key"] == "Enter": + item_mutation.execute(text=event["target"]["value"]) + + # Handle all possible query states + if item_query.loading: + rendered_items = html.h2("Loading...") + elif item_query.error or not item_query.data: + rendered_items = html.h2("Error when loading!") + else: + rendered_items = html.ul(html.li(item, key=item) for item in item_query.data) + + # Handle all possible mutation states + if item_mutation.loading: + mutation_status = html.h2("Adding...") + elif item_mutation.error: + mutation_status = html.h2("Error when adding!") + else: + mutation_status = html.h2("Mutation done.") + + return html.div( + html.label("Add an item:"), + html.input({"type": "text", "onKeyDown": submit_event}), + mutation_status, + rendered_items, + ) diff --git a/docs/python/use-mutation-reset.py b/docs/python/use-mutation-reset.py new file mode 100644 index 00000000..cab70156 --- /dev/null +++ b/docs/python/use-mutation-reset.py @@ -0,0 +1,33 @@ +from example.models import TodoItem +from idom import component, html + +from django_idom.hooks import use_mutation + + +def add_item(text: str): + TodoItem(text=text).save() + + +@component +def todo_list(): + item_mutation = use_mutation(add_item) + + def reset_event(event): + item_mutation.reset() + + def submit_event(event): + if event["key"] == "Enter": + item_mutation.execute(text=event["target"]["value"]) + + if item_mutation.loading: + mutation_status = html.h2("Adding...") + elif item_mutation.error: + mutation_status = html.button({"onClick": reset_event}, "Error: Try again!") + else: + mutation_status = html.h2("Mutation done.") + + return html.div( + html.label("Add an item:"), + html.input({"type": "text", "onKeyDown": submit_event}), + mutation_status, + ) diff --git a/docs/python/use-mutation.py b/docs/python/use-mutation.py new file mode 100644 index 00000000..ce762e5f --- /dev/null +++ b/docs/python/use-mutation.py @@ -0,0 +1,29 @@ +from example.models import TodoItem +from idom import component, html + +from django_idom.hooks import use_mutation + + +def add_item(text: str): + TodoItem(text=text).save() + + +@component +def todo_list(): + def submit_event(event): + if event["key"] == "Enter": + item_mutation.execute(text=event["target"]["value"]) + + item_mutation = use_mutation(add_item) + if item_mutation.loading: + mutation_status = html.h2("Adding...") + elif item_mutation.error: + mutation_status = html.h2("Error when adding!") + else: + mutation_status = html.h2("Mutation done.") + + return html.div( + html.label("Add an item:"), + html.input({"type": "text", "onKeyDown": submit_event}), + mutation_status, + ) diff --git a/docs/python/use-origin.py b/docs/python/use-origin.py new file mode 100644 index 00000000..6f274766 --- /dev/null +++ b/docs/python/use-origin.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.hooks import use_origin + + +@component +def my_component(): + my_origin = use_origin() + return html.div(my_origin or "No origin") diff --git a/docs/python/use-query-args.py b/docs/python/use-query-args.py new file mode 100644 index 00000000..670f310b --- /dev/null +++ b/docs/python/use-query-args.py @@ -0,0 +1,18 @@ +from idom import component + +from django_idom.hooks import use_query + + +def example_query(value: int, other_value: bool = False): + ... + + +@component +def my_component(): + query = use_query( + example_query, + 123, + other_value=True, + ) + + return str(query.data) diff --git a/docs/python/use-query-postprocessor-change.py b/docs/python/use-query-postprocessor-change.py new file mode 100644 index 00000000..ba8fc0fa --- /dev/null +++ b/docs/python/use-query-postprocessor-change.py @@ -0,0 +1,32 @@ +from idom import component + +from django_idom.hooks import use_query +from django_idom.types import QueryOptions + + +def my_postprocessor(data, example_kwarg=True): + if example_kwarg: + return data + + return dict(data) + + +def execute_io_intensive_operation(): + """This is an example query function that does something IO intensive.""" + pass + + +@component +def todo_list(): + query = use_query( + QueryOptions( + postprocessor=my_postprocessor, + postprocessor_kwargs={"example_kwarg": False}, + ), + execute_io_intensive_operation, + ) + + if query.loading or query.error: + return None + + return str(query.data) diff --git a/docs/python/use-query-postprocessor-disable.py b/docs/python/use-query-postprocessor-disable.py new file mode 100644 index 00000000..552c5bc9 --- /dev/null +++ b/docs/python/use-query-postprocessor-disable.py @@ -0,0 +1,22 @@ +from idom import component + +from django_idom.hooks import use_query +from django_idom.types import QueryOptions + + +def execute_io_intensive_operation(): + """This is an example query function that does something IO intensive.""" + pass + + +@component +def todo_list(): + query = use_query( + QueryOptions(postprocessor=None), + execute_io_intensive_operation, + ) + + if query.loading or query.error: + return None + + return str(query.data) diff --git a/docs/python/use-query-postprocessor-kwargs.py b/docs/python/use-query-postprocessor-kwargs.py new file mode 100644 index 00000000..05f72e38 --- /dev/null +++ b/docs/python/use-query-postprocessor-kwargs.py @@ -0,0 +1,32 @@ +from example.models import TodoItem +from idom import component + +from django_idom.hooks import use_query +from django_idom.types import QueryOptions + + +def get_model_with_relationships(): + """This is an example query function that gets `MyModel` which has a ManyToMany field, and + additionally other models that have formed a ForeignKey association to `MyModel`. + + ManyToMany Field: `many_to_many_field` + ForeignKey Field: `foreign_key_field_set` + """ + return TodoItem.objects.get(id=1) + + +@component +def todo_list(): + query = use_query( + QueryOptions( + postprocessor_kwargs={"many_to_many": False, "many_to_one": False} + ), + get_model_with_relationships, + ) + + if query.loading or query.error or not query.data: + return None + + # By disabling `many_to_many` and `many_to_one`, accessing these fields will now + # generate a `SynchronousOnlyOperation` exception + return f"{query.data.many_to_many_field} {query.data.foriegn_key_field_set}" diff --git a/docs/python/use-query.py b/docs/python/use-query.py new file mode 100644 index 00000000..df45998c --- /dev/null +++ b/docs/python/use-query.py @@ -0,0 +1,22 @@ +from example.models import TodoItem +from idom import component, html + +from django_idom.hooks import use_query + + +def get_items(): + return TodoItem.objects.all() + + +@component +def todo_list(): + item_query = use_query(get_items) + + if item_query.loading: + rendered_items = html.h2("Loading...") + elif item_query.error or not item_query.data: + rendered_items = html.h2("Error when loading!") + else: + rendered_items = html.ul(html.li(item, key=item) for item in item_query.data) + + return html.div("Rendered items: ", rendered_items) diff --git a/docs/python/use-scope.py b/docs/python/use-scope.py new file mode 100644 index 00000000..f5e2fbf3 --- /dev/null +++ b/docs/python/use-scope.py @@ -0,0 +1,9 @@ +from idom import component, html + +from django_idom.hooks import use_scope + + +@component +def my_component(): + my_scope = use_scope() + return html.div(str(my_scope)) diff --git a/docs/python/vtc-args-kwargs.py b/docs/python/vtc-args-kwargs.py new file mode 100644 index 00000000..74e1c89f --- /dev/null +++ b/docs/python/vtc-args-kwargs.py @@ -0,0 +1,22 @@ +from django.http import HttpResponse +from idom import component, html + +from django_idom.components import view_to_component + + +@view_to_component +def hello_world_view(request, arg1, arg2, key1=None, key2=None): + return HttpResponse(f"Hello World! {arg1} {arg2} {key1} {key2}") + + +@component +def my_component(): + return html.div( + hello_world_view( + None, # Your request object (optional) + "value_1", + "value_2", + key1="abc", + key2="123", + ), + ) diff --git a/docs/python/vtc-cbv-compatibility.py b/docs/python/vtc-cbv-compatibility.py new file mode 100644 index 00000000..5b6730c1 --- /dev/null +++ b/docs/python/vtc-cbv-compatibility.py @@ -0,0 +1,11 @@ +from django.contrib.auth.decorators import user_passes_test +from django.utils.decorators import method_decorator +from django.views.generic import TemplateView + +from django_idom.components import view_to_component + + +@view_to_component(compatibility=True) +@method_decorator(user_passes_test(lambda u: u.is_superuser), name="dispatch") # type: ignore[union-attr] +class ExampleView(TemplateView): + ... diff --git a/docs/python/vtc-cbv.py b/docs/python/vtc-cbv.py new file mode 100644 index 00000000..c8da882f --- /dev/null +++ b/docs/python/vtc-cbv.py @@ -0,0 +1,20 @@ +from django.http import HttpResponse +from django.views import View +from idom import component, html + +from django_idom.components import view_to_component + + +class HelloWorldView(View): + def get(self, request): + return HttpResponse("Hello World!") + + +vtc = view_to_component(HelloWorldView) + + +@component +def my_component(): + return html.div( + vtc(), + ) diff --git a/docs/python/vtc-compatibility.py b/docs/python/vtc-compatibility.py new file mode 100644 index 00000000..d7e384a3 --- /dev/null +++ b/docs/python/vtc-compatibility.py @@ -0,0 +1,16 @@ +from django.http import HttpResponse +from idom import component, html + +from django_idom.components import view_to_component + + +@view_to_component(compatibility=True) +def hello_world_view(request): + return HttpResponse("Hello World!") + + +@component +def my_component(): + return html.div( + hello_world_view(), + ) diff --git a/docs/python/vtc-fbv-compat.py b/docs/python/vtc-fbv-compat.py new file mode 100644 index 00000000..ac527ce0 --- /dev/null +++ b/docs/python/vtc-fbv-compat.py @@ -0,0 +1,9 @@ +from django.contrib.auth.decorators import user_passes_test + +from django_idom.components import view_to_component + + +@view_to_component(compatibility=True) +@user_passes_test(lambda u: u.is_superuser) # type: ignore[union-attr] +def example_view(request): + ... diff --git a/docs/python/vtc-func.py b/docs/python/vtc-func.py new file mode 100644 index 00000000..342e0f39 --- /dev/null +++ b/docs/python/vtc-func.py @@ -0,0 +1,14 @@ +from example.views import example_view +from idom import component, html + +from django_idom.components import view_to_component + + +example_vtc = view_to_component(example_view) + + +@component +def my_component(): + return html.div( + example_vtc(), + ) diff --git a/docs/python/vtc-request.py b/docs/python/vtc-request.py new file mode 100644 index 00000000..bfc32ac2 --- /dev/null +++ b/docs/python/vtc-request.py @@ -0,0 +1,22 @@ +from django.http import HttpRequest, HttpResponse +from idom import component, html + +from django_idom.components import view_to_component + + +example_request = HttpRequest() +example_request.method = "PUT" + + +@view_to_component +def hello_world_view(request): + return HttpResponse(f"Hello World! {request.method}") + + +@component +def my_component(): + return html.div( + hello_world_view( + example_request, + ), + ) diff --git a/docs/python/vtc-strict-parsing.py b/docs/python/vtc-strict-parsing.py new file mode 100644 index 00000000..859a3a43 --- /dev/null +++ b/docs/python/vtc-strict-parsing.py @@ -0,0 +1,16 @@ +from django.http import HttpResponse +from idom import component, html + +from django_idom.components import view_to_component + + +@view_to_component(strict_parsing=False) +def hello_world_view(request): + return HttpResponse(" Hello World ") + + +@component +def my_component(): + return html.div( + hello_world_view(), + ) diff --git a/docs/python/vtc-transforms.py b/docs/python/vtc-transforms.py new file mode 100644 index 00000000..1b1fc9c0 --- /dev/null +++ b/docs/python/vtc-transforms.py @@ -0,0 +1,22 @@ +from django.http import HttpResponse +from idom import component, html + +from django_idom.components import view_to_component + + +def example_transform(vdom): + attributes = vdom.get("attributes") + if attributes and attributes.get("id") == "hello-world": + vdom["children"][0] = "Good Bye World!" + + +@view_to_component(transforms=[example_transform]) +def hello_world_view(request): + return HttpResponse("
Hello World!
") + + +@component +def my_component(): + return html.div( + hello_world_view(), + ) diff --git a/docs/python/vtc.py b/docs/python/vtc.py new file mode 100644 index 00000000..a7a73bbf --- /dev/null +++ b/docs/python/vtc.py @@ -0,0 +1,16 @@ +from django.http import HttpResponse +from idom import component, html + +from django_idom.components import view_to_component + + +@view_to_component +def hello_world_view(request): + return HttpResponse("Hello World!") + + +@component +def my_component(): + return html.div( + hello_world_view(), + ) diff --git a/docs/src/changelog/index.md b/docs/src/changelog/index.md index a4a0f241..20120d5e 100644 --- a/docs/src/changelog/index.md +++ b/docs/src/changelog/index.md @@ -1,11 +1,12 @@ --- hide: - - navigation - toc --- -!!! note "Attribution" +!!! summary "Attribution" {% include-markdown "../../../CHANGELOG.md" start="" end="" %} +--- + {% include-markdown "../../../CHANGELOG.md" start="" %} diff --git a/docs/src/contribute/code.md b/docs/src/contribute/code.md index 3575671b..569155c0 100644 --- a/docs/src/contribute/code.md +++ b/docs/src/contribute/code.md @@ -1,7 +1,17 @@ -???+ tip "Looking to contribute features that are not Django specific?" +## Overview + +!!! summary + + You will need to set up a Python environment to develop Django-IDOM. + +??? tip "Looking to contribute features that are not Django specific?" Everything within the `django-idom` repository must be specific to Django integration. Check out the [IDOM Core documentation](https://idom-docs.herokuapp.com/docs/about/contributor-guide.html) to contribute general features such as: components, hooks, events, and more. +--- + +## Modifying Code + If you plan to make code changes to this repository, you will need to install the following dependencies first: - [Python 3.8+](https://www.python.org/downloads/) @@ -24,7 +34,7 @@ Then, by running the command below you can: pip install -e . -r requirements.txt ``` -Finally, to verify that everything is working properly, you can manually run the development webserver. +Finally, to verify that everything is working properly, you can manually run the test webserver. ```bash linenums="0" cd tests @@ -32,3 +42,7 @@ python manage.py runserver ``` Navigate to `http://127.0.0.1:8000` to see if the tests are rendering correctly. + +## GitHub Pull Request + +{% include-markdown "../../includes/pr.md" %} diff --git a/docs/src/contribute/docs.md b/docs/src/contribute/docs.md index 666cf6e1..b16d2ec7 100644 --- a/docs/src/contribute/docs.md +++ b/docs/src/contribute/docs.md @@ -1,3 +1,13 @@ +## Overview + +!!! summary + + You will need to set up a Python environment to preview docs changes. + +--- + +## Modifying Docs + If you plan to make changes to this documentation, you will need to install the following dependencies first: - [Python 3.8+](https://www.python.org/downloads/) @@ -16,7 +26,7 @@ Then, by running the command below you can: - Self-host a test server for the documentation ```bash linenums="0" -pip install -r ./requirements/build-docs.txt --upgrade +pip install -e . -r requirements.txt --upgrade ``` Finally, to verify that everything is working properly, you can manually run the docs preview webserver. @@ -26,3 +36,7 @@ mkdocs serve ``` Navigate to `http://127.0.0.1:8000` to view a preview of the documentation. + +## GitHub Pull Request + +{% include-markdown "../../includes/pr.md" %} diff --git a/docs/src/contribute/running-tests.md b/docs/src/contribute/running-tests.md index 5d4c48cf..c0f20065 100644 --- a/docs/src/contribute/running-tests.md +++ b/docs/src/contribute/running-tests.md @@ -1,3 +1,13 @@ +## Overview + +!!! summary + + You will need to set up a Python environment to run out test suite. + +--- + +## Running Tests + This repository uses [Nox](https://nox.thea.codes/en/stable/) to run tests. For a full test of available scripts run `nox -l`. If you plan to run tests, you will need to install the following dependencies first: @@ -10,10 +20,12 @@ Once done, you should clone this repository: ```bash linenums="0" git clone https://github.com/idom-team/django-idom.git cd django-idom -pip install -r ./requirements/test-run.txt --upgrade +pip install -e . -r requirements.txt --upgrade ``` -Then, by running the command below you can run the full test suite: +## Full Test Suite + +By running the command below you can run the full test suite: ```bash linenums="0" nox -s test @@ -24,3 +36,12 @@ Or, if you want to run the tests in the foreground: ```bash linenums="0" nox -s test -- --headed ``` + +## Only Django Tests + +Alternatively, if you want to only run Django related tests, you can use the following command: + +```bash linenums="0" +cd tests +python mange.py test +``` diff --git a/docs/src/features/components.md b/docs/src/features/components.md index 849635ab..37a50cb8 100644 --- a/docs/src/features/components.md +++ b/docs/src/features/components.md @@ -1,7 +1,11 @@ -???+ summary +## Overview + +!!! summary Prefabricated components can be used within your `components.py` to help simplify development. +--- + ## View To Component Convert any Django view into a IDOM component by using this decorator. Compatible with [Function Based Views](https://docs.djangoproject.com/en/dev/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/dev/topics/class-based-views/). Views can be sync or async. @@ -9,19 +13,7 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse - from django_idom.components import view_to_component - - @view_to_component - def hello_world_view(request): - return HttpResponse("Hello World!") - - @component - def my_component(): - return html.div( - hello_world_view(), - ) + {% include "../../python/vtc.py" %} ``` ??? example "See Interface" @@ -52,23 +44,13 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "Function Based View" ```python - ... - - @view_to_component(compatibility=True) - @user_passes_test(lambda u: u.is_superuser) - def example_view(request): - ... + {% include "../../python/vtc-fbv-compat.py" %} ``` === "Class Based View" ```python - ... - - @view_to_component(compatibility=True) - @method_decorator(user_passes_test(lambda u: u.is_superuser), name="dispatch") - class ExampleView(TemplateView): - ... + {% include "../../python/vtc-cbv-compatibility.py" %} ``` ??? info "Existing limitations" @@ -88,21 +70,7 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse - from django.views import View - from django_idom.components import view_to_component - - @view_to_component - class HelloWorldView(View): - def get(self, request): - return HttpResponse("Hello World!") - - @component - def my_component(): - return html.div( - HelloWorldView(), - ) + {% include "../../python/vtc-cbv.py" %} ``` ??? question "How do I transform views from external libraries?" @@ -112,18 +80,7 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse - from django_idom.components import view_to_component - from some_library.views import example_view - - example_vtc = view_to_component(example_view) - - @component - def my_component(): - return html.div( - example_vtc(), - ) + {% include "../../python/vtc-func.py" %} ``` ??? question "How do I provide `request`, `args`, and `kwargs` to a view?" @@ -135,24 +92,7 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse, HttpRequest - from django_idom.components import view_to_component - - example_request = HttpRequest() - example_request.method = "PUT" - - @view_to_component - def hello_world_view(request): - return HttpResponse(f"Hello World! {request.method}") - - @component - def my_component(): - return html.div( - hello_world_view( - example_request, - ), - ) + {% include "../../python/vtc-request.py" %} ``` --- @@ -164,28 +104,10 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse - from django_idom.components import view_to_component - - @view_to_component - def hello_world_view(request, arg1, arg2, key1=None, key2=None): - return HttpResponse(f"Hello World! {arg1} {arg2} {key1} {key2}") - - @component - def my_component(): - return html.div( - hello_world_view( - None, # Your request object (optional) - "value_1", - "value_2", - key1="abc", - key2="123", - ), - ) + {% include "../../python/vtc-args-kwargs.py" %} ``` -??? question "How do I use `strict_parseing`, `compatibility`, and `transforms`?" +??? question "How do I use `strict_parsing`, `compatibility`, and `transforms`?" **`strict_parsing`** @@ -198,19 +120,7 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse - from django_idom.components import view_to_component - - @view_to_component(strict_parsing=False) - def hello_world_view(request): - return HttpResponse(" Hello World ") - - @component - def my_component(): - return html.div( - hello_world_view(), - ) + {% include "../../python/vtc-strict-parsing.py" %} ``` _Note: Best-fit parsing is designed to be similar to how web browsers would handle non-standard or broken HTML._ @@ -228,19 +138,7 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse - from django_idom.components import view_to_component - - @view_to_component(compatibility=True) - def hello_world_view(request): - return HttpResponse("Hello World!") - - @component - def my_component(): - return html.div( - hello_world_view(), - ) + {% include "../../python/vtc-compatibility.py" %} ``` _Note: By default the `compatibility` iframe is unstyled, and thus won't look pretty until you add some CSS._ @@ -258,24 +156,7 @@ Convert any Django view into a IDOM component by using this decorator. Compatibl === "components.py" ```python - from idom import component, html - from django.http import HttpResponse - from django_idom.components import view_to_component - - def example_transform(vdom): - attributes = vdom.get("attributes") - if attributes and attributes.get("id") == "hello-world": - vdom["children"][0] = "Good Bye World!" - - @view_to_component(transforms=[example_transform]) - def hello_world_view(request): - return HttpResponse("
Hello World!
") - - @component - def my_component(): - return html.div( - hello_world_view(), - ) + {% include "../../python/vtc-transforms.py" %} ``` ## Django CSS @@ -285,15 +166,7 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering. === "components.py" ```python - from idom import component, html - from django_idom.components import django_css - - @component - def my_component(): - return html.div( - django_css("css/buttons.css"), - html.button("My Button!"), - ) + {% include "../../python/django-css.py" %} ``` ??? example "See Interface" @@ -322,15 +195,7 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering. Here's an example on what you should avoid doing for Django static files: ```python - from idom import component, html - from django.templatetags.static import static - - @component - def my_component(): - return html.div( - html.link({"rel": "stylesheet", "href": static("css/buttons.css")}), - html.button("My Button!"), - ) + {% include "../../python/django-css-local-link.py" %} ``` ??? question "How do I load external CSS?" @@ -340,14 +205,7 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering. For external CSS, substitute `django_css` with `html.link`. ```python - from idom import component, html - - @component - def my_component(): - return html.div( - html.link({"rel": "stylesheet", "href": "https://example.com/external-styles.css"}), - html.button("My Button!"), - ) + {% include "../../python/django-css-external-link.py" %} ``` ??? question "Why not load my CSS in `#!html `?" @@ -363,15 +221,7 @@ Allows you to defer loading JavaScript until a component begins rendering. This === "components.py" ```python - from idom import component, html - from django_idom.components import django_js - - @component - def my_component(): - return html.div( - html.button("My Button!"), - django_js("js/scripts.js"), - ) + {% include "../../python/django-js.py" %} ``` ??? example "See Interface" @@ -400,15 +250,7 @@ Allows you to defer loading JavaScript until a component begins rendering. This Here's an example on what you should avoid doing for Django static files: ```python - from idom import component, html - from django.templatetags.static import static - - @component - def my_component(): - return html.div( - html.script({"src": static("js/scripts.js")}), - html.button("My Button!"), - ) + {% include "../../python/django-js-local-script.py" %} ``` ??? question "How do I load external JS?" @@ -418,14 +260,7 @@ Allows you to defer loading JavaScript until a component begins rendering. This For external JavaScript, substitute `django_js` with `html.script`. ```python - from idom import component, html - - @component - def my_component(): - return html.div( - html.script({"src": "https://example.com/external-scripts.js"}), - html.button("My Button!"), - ) + {% include "../../python/django-js-remote-script.py" %} ``` ??? question "Why not load my JS in `#!html `?" diff --git a/docs/src/features/decorators.md b/docs/src/features/decorators.md index 19ff8726..c7c0a958 100644 --- a/docs/src/features/decorators.md +++ b/docs/src/features/decorators.md @@ -1,7 +1,11 @@ -???+ summary +## Overview + +!!! summary Decorator utilities can be used within your `components.py` to help simplify development. +--- + ## Auth Required You can limit access to a component to users with a specific `auth_attribute` by using this decorator (with or without parentheses). @@ -13,13 +17,7 @@ This decorator is commonly used to selectively render a component only if a user === "components.py" ```python - from django_idom.decorators import auth_required - from idom import component, html - - @component - @auth_required - def my_component(): - return html.div("I am logged in!") + {% include "../../python/auth-required.py" %} ``` ??? example "See Interface" @@ -46,17 +44,7 @@ This decorator is commonly used to selectively render a component only if a user === "components.py" ```python - from django_idom.decorators import auth_required - from idom import component, html - - @component - def my_component_fallback(): - return html.div("I am NOT logged in!") - - @component - @auth_required(fallback=my_component_fallback) - def my_component(): - return html.div("I am logged in!") + {% include "../../python/auth-required-component-fallback.py" %} ``` ??? question "How do I render a simple `idom.html` snippet if authentication fails?" @@ -66,13 +54,7 @@ This decorator is commonly used to selectively render a component only if a user === "components.py" ```python - from django_idom.decorators import auth_required - from idom import component, html - - @component - @auth_required(fallback=html.div("I am NOT logged in!")) - def my_component(): - return html.div("I am logged in!") + {% include "../../python/auth-required-vdom-fallback.py" %} ``` ??? question "How can I check if a user `is_staff`?" @@ -82,14 +64,7 @@ This decorator is commonly used to selectively render a component only if a user === "components.py" ```python - from django_idom.decorators import auth_required - from idom import component, html - - - @component - @auth_required(auth_attribute="is_staff") - def my_component(): - return html.div("I am logged in!") + {% include "../../python/auth-required-attribute.py" %} ``` ??? question "How can I check for a custom attribute?" @@ -101,12 +76,7 @@ This decorator is commonly used to selectively render a component only if a user === "models.py" ```python - from django.contrib.auth.models import AbstractBaseUser - - class CustomUserModel(AbstractBaseUser): - @property - def is_really_cool(self): - return True + {% include "../../python/auth-required-custom-attribute-model.py" %} ``` ... then you would do the following within your decorator: @@ -114,11 +84,5 @@ This decorator is commonly used to selectively render a component only if a user === "components.py" ```python - from django_idom.decorators import auth_required - from idom import component, html - - @component - @auth_required(auth_attribute="is_really_cool") - def my_component(): - return html.div("I am logged in!") + {% include "../../python/auth-required-custom-attribute.py" %} ``` diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md index 5e1f9731..393a84e8 100644 --- a/docs/src/features/hooks.md +++ b/docs/src/features/hooks.md @@ -1,13 +1,17 @@ -???+ summary +## Overview + +!!! summary Prefabricated hooks can be used within your `components.py` to help simplify development. -??? tip "Looking for standard ReactJS hooks?" +??? tip "Looking for standard React hooks?" - Standard ReactJS hooks are contained within [`idom-team/idom`](https://github.com/idom-team/idom). Since `idom` is installed by default alongside `django-idom`, you can import them at any time. + Standard hooks are contained within [`idom-team/idom`](https://github.com/idom-team/idom). Since `idom` is installed alongside `django-idom`, you can import them at any time. Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html#basic-hooks) to see what hooks are available! +--- + ## Use Query The `use_query` hook is used fetch Django ORM queries. @@ -17,34 +21,13 @@ The function you provide into this hook must return either a `Model` or `QuerySe === "components.py" ```python - from example_project.my_app.models import TodoItem - from idom import component, html - from django_idom.hooks import use_query - - def get_items(): - return TodoItem.objects.all() - - @component - def todo_list(): - item_query = use_query(get_items) - - if item_query.loading: - rendered_items = html.h2("Loading...") - elif item_query.error: - rendered_items = html.h2("Error when loading!") - else: - rendered_items = html.ul(html.li(item, key=item) for item in item_query.data) - - return rendered_items + {% include "../../python/use-query.py" %} ``` === "models.py" ```python - from django.db import models - - class TodoItem(models.Model): - text = models.CharField(max_length=255) + {% include "../../python/example/models.py" %} ``` ??? example "See Interface" @@ -71,21 +54,7 @@ The function you provide into this hook must return either a `Model` or `QuerySe === "components.py" ```python - from idom import component - from django_idom.hooks import use_query - - def example_query(value: int, other_value: bool = False): - ... - - @component - def my_component(): - query = use_query( - example_query, - 123, - other_value=True, - ) - - ... + {% include "../../python/use-query-args.py" %} ``` ??? question "Why does the example `get_items` function return `TodoItem.objects.all()`?" @@ -108,25 +77,7 @@ The function you provide into this hook must return either a `Model` or `QuerySe === "components.py" ```python - from idom import component - from django_idom.types import QueryOptions - from django_idom.hooks import use_query - - def execute_io_intensive_operation(): - """This is an example query function that does something IO intensive.""" - pass - - @component - def todo_list(): - query = use_query( - QueryOptions(postprocessor=None), - execute_io_intensive_operation, - ) - - if query.loading or query.error: - return None - - return str(query.data) + {% include "../../python/use-query-postprocessor-disable.py" %} ``` If you wish to create a custom `postprocessor`, you will need to create a callable. @@ -138,34 +89,7 @@ The function you provide into this hook must return either a `Model` or `QuerySe === "components.py" ```python - from idom import component - from django_idom.types import QueryOptions - from django_idom.hooks import use_query - - def my_postprocessor(data, example_kwarg=True): - if example_kwarg: - return data - - return dict(data) - - def execute_io_intensive_operation(): - """This is an example query function that does something IO intensive.""" - pass - - @component - def todo_list(): - query = use_query( - QueryOptions( - postprocessor=my_postprocessor, - postprocessor_kwargs={"example_kwarg": False}, - ), - execute_io_intensive_operation, - ) - - if query.loading or query.error: - return None - - return str(query.data) + {% include "../../python/use-query-postprocessor-change.py" %} ``` ??? question "How can I prevent this hook from recursively fetching `ManyToMany` fields or `ForeignKey` relationships?" @@ -179,36 +103,10 @@ The function you provide into this hook must return either a `Model` or `QuerySe === "components.py" ```python - from example_project.my_app.models import MyModel - from idom import component - from django_idom.types import QueryOptions - from django_idom.hooks import use_query - - def get_model_with_relationships(): - """This is an example query function that gets `MyModel` which has a ManyToMany field, and - additionally other models that have formed a ForeignKey association to `MyModel`. - - ManyToMany Field: `many_to_many_field` - ForeignKey Field: `foreign_key_field_set` - """ - return MyModel.objects.get(id=1) - - @component - def todo_list(): - query = use_query( - QueryOptions(postprocessor_kwargs={"many_to_many": False, "many_to_one": False}), - get_model_with_relationships, - ) - - if query.loading or query.error: - return None - - # By disabling `many_to_many` and `many_to_one`, accessing these fields will now - # generate a `SynchronousOnlyOperation` exception - return f"{query.data.many_to_many_field} {query.data.foriegn_key_field_set}" + {% include "../../python/use-query-postprocessor-kwargs.py" %} ``` - _Note: In Django's ORM design, the field name to access foreign keys is [always be postfixed with `_set`](https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_one/)._ + _Note: In Django's ORM design, the field name to access foreign keys is [postfixed with `_set`](https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_one/) by default._ ??? question "Can I make ORM calls without hooks?" @@ -223,37 +121,14 @@ The function you provide into this hook will have no return value. === "components.py" ```python - from example_project.my_app.models import TodoItem - from idom import component, html - from django_idom.hooks import use_mutation - - def add_item(text: str): - TodoItem(text=text).save() - - @component - def todo_list(): - def submit_event(event): - if event["key"] == "Enter": - item_mutation.execute(text=event["target"]["value"]) - - item_mutation = use_mutation(add_item) - if item_mutation.loading: - mutation_status = html.h2("Adding...") - elif item_mutation.error: - mutation_status = html.h2("Error when adding!") - else: - mutation_status = "" - - return html.div( - html.label("Add an item:"), - html.input({"type": "text", "onKeyDown": submit_event}), - mutation_status, - ) + {% include "../../python/use-mutation.py" %} ``` === "models.py" - {% include-markdown "../../includes/examples.md" start="" end="" %} + ```python + {% include "../../python/example/models.py" %} + ``` ??? example "See Interface" @@ -277,19 +152,7 @@ The function you provide into this hook will have no return value. === "components.py" ```python - from idom import component - from django_idom.hooks import use_mutation - - def example_mutation(value: int, other_value: bool = False): - ... - - @component - def my_component(): - mutation = use_mutation(example_mutation) - - mutation.execute(123, other_value=True) - - ... + {% include "../../python/use-mutation-args-kwargs.py" %} ``` ??? question "Can `use_mutation` trigger a refetch of `use_query`?" @@ -303,52 +166,14 @@ The function you provide into this hook will have no return value. === "components.py" ```python - from example_project.my_app.models import TodoItem - from idom import component, html - from django_idom.hooks import use_mutation - - def get_items(): - return TodoItem.objects.all() - - def add_item(text: str): - TodoItem(text=text).save() - - @component - def todo_list(): - item_query = use_query(get_items) - item_mutation = use_mutation(add_item, refetch=get_items) - - def submit_event(event): - if event["key"] == "Enter": - item_mutation.execute(text=event["target"]["value"]) - - # Handle all possible query states - if item_query.loading: - rendered_items = html.h2("Loading...") - elif item_query.error: - rendered_items = html.h2("Error when loading!") - else: - rendered_items = html.ul(html.li(item, key=item) for item in item_query.data) - - # Handle all possible mutation states - if item_mutation.loading: - mutation_status = html.h2("Adding...") - elif item_mutation.error: - mutation_status = html.h2("Error when adding!") - else: - mutation_status = "" - - return html.div( - html.label("Add an item:"), - html.input({"type": "text", "onKeyDown": submit_event}), - mutation_status, - rendered_items, - ) + {% include "../../python/use-mutation-query-refetch.py" %} ``` === "models.py" - {% include-markdown "../../includes/examples.md" start="" end="" %} + ```python + {% include "../../python/example/models.py" %} + ``` ??? question "Can I make a failed `use_mutation` try again?" @@ -359,62 +184,27 @@ The function you provide into this hook will have no return value. === "components.py" ```python - from example_project.my_app.models import TodoItem - from idom import component, html - from django_idom.hooks import use_mutation - - def add_item(text: str): - TodoItem(text=text).save() - - @component - def todo_list(): - item_mutation = use_mutation(add_item) - - def reset_event(event): - item_mutation.reset() - - def submit_event(event): - if event["key"] == "Enter": - item_mutation.execute(text=event["target"]["value"]) - - if item_mutation.loading: - mutation_status = html.h2("Adding...") - elif item_mutation.error: - mutation_status = html.button({"onClick": reset_event}, "Error: Try again!") - else: - mutation_status = "" - - return html.div( - html.label("Add an item:"), - html.input({"type": "text", "onKeyDown": submit_event}), - mutation_status, - ) + {% include "../../python/use-mutation-reset.py" %} ``` === "models.py" - {% include-markdown "../../includes/examples.md" start="" end="" %} + ```python + {% include "../../python/example/models.py" %} + ``` ??? question "Can I make ORM calls without hooks?" {% include-markdown "../../includes/orm.md" start="" end="" %} -## Use Origin - -This is a shortcut that returns the Websocket's `origin`. +## Use Connection -You can expect this hook to provide strings such as `http://example.com`. +You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) at any time by using `use_connection`. === "components.py" ```python - from idom import component, html - from django_idom.hooks import use_origin - - @component - def my_component(): - my_origin = use_origin() - return html.div(my_origin) + {% include "../../python/use-connection.py" %} ``` ??? example "See Interface" @@ -427,22 +217,16 @@ You can expect this hook to provide strings such as `http://example.com`. | Type | Description | | --- | --- | - | `str | None` | A string containing the browser's current origin, obtained from websocket headers (if available). | + | `Connection` | The component's websocket. | -## Use Connection +## Use Scope -You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) at any time by using `use_connection`. +This is a shortcut that returns the Websocket's [`scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope). === "components.py" ```python - from idom import component, html - from django_idom.hooks import use_connection - - @component - def my_component(): - my_connection = use_connection() - return html.div(my_connection) + {% include "../../python/use-scope.py" %} ``` ??? example "See Interface" @@ -455,22 +239,18 @@ You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en | Type | Description | | --- | --- | - | `Connection` | The component's websocket. | + | `MutableMapping[str, Any]` | The websocket's `scope`. | -## Use Scope +## Use Location -This is a shortcut that returns the Websocket's [`scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope). +This is a shortcut that returns the Websocket's `path`. + +You can expect this hook to provide strings such as `/idom/my_path`. === "components.py" ```python - from idom import component, html - from django_idom.hooks import use_scope - - @component - def my_component(): - my_scope = use_scope() - return html.div(my_scope) + {% include "../../python/use-location.py" %} ``` ??? example "See Interface" @@ -483,24 +263,24 @@ This is a shortcut that returns the Websocket's [`scope`](https://channels.readt | Type | Description | | --- | --- | - | `MutableMapping[str, Any]` | The websocket's `scope`. | + | `Location` | A object containing the current URL's `pathname` and `search` query. | -## Use Location +??? info "This hook's behavior will be changed in a future update" -This is a shortcut that returns the Websocket's `path`. + This hook will be updated to return the browser's currently active path. This change will come in alongside IDOM URL routing support. -You can expect this hook to provide strings such as `/idom/my_path`. + Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information. + +## Use Origin + +This is a shortcut that returns the Websocket's `origin`. + +You can expect this hook to provide strings such as `http://example.com`. === "components.py" ```python - from idom import component, html - from django_idom.hooks import use_location - - @component - def my_component(): - my_location = use_location() - return html.div(my_location) + {% include "../../python/use-origin.py" %} ``` ??? example "See Interface" @@ -513,10 +293,4 @@ You can expect this hook to provide strings such as `/idom/my_path`. | Type | Description | | --- | --- | - | `Location` | A object containing the current URL's `pathname` and `search` query. | - -??? info "This hook's behavior will be changed in a future update" - - This hook will be updated to return the browser's currently active path. This change will come in alongside IDOM URL routing support. - - Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information. + | `str | None` | A string containing the browser's current origin, obtained from websocket headers (if available). | diff --git a/docs/src/features/settings.md b/docs/src/features/settings.md index 826da81d..711c267d 100644 --- a/docs/src/features/settings.md +++ b/docs/src/features/settings.md @@ -1,33 +1,21 @@ -???+ summary +## Overview - Django-IDOM uses your **Django project's** `settings.py` file to modify the behavior of IDOM. +!!! summary -## Primary Configuration - -=== "settings.py" + Your **Django project's** `settings.py` can modify the behavior of IDOM. - +--- - ```python - # If "idom" cache is not configured, then "default" will be used - # IDOM expects a multiprocessing-safe and thread-safe cache backend. - CACHES = { - "idom": {"BACKEND": ...}, - } +## Primary Configuration - # Maximum seconds between reconnection attempts before giving up. - # Use `0` to prevent component reconnection. - IDOM_RECONNECT_MAX = 259200 +These are Django-IDOM's default settings values. You can modify these values in your **Django project's** `settings.py` to change the behavior of IDOM. - # The URL for IDOM to serve the component rendering websocket - IDOM_WEBSOCKET_URL = "idom/" +=== "settings.py" - # Dotted path to the default postprocessor function, or `None` - IDOM_DEFAULT_QUERY_POSTPROCESSOR = "example_project.utils.my_postprocessor" + ```python + {% include "../../python/settings.py" %} ``` - - ??? question "Do I need to modify my settings?" The default configuration of IDOM is adequate for the majority of use cases. diff --git a/docs/src/features/templatetag.md b/docs/src/features/template-tag.md similarity index 68% rename from docs/src/features/templatetag.md rename to docs/src/features/template-tag.md index 7dc34c2e..122b2272 100644 --- a/docs/src/features/templatetag.md +++ b/docs/src/features/template-tag.md @@ -1,7 +1,11 @@ -???+ summary +## Overview + +!!! summary Template tags can be used within your Django templates such as `my-template.html` to import IDOM features. +--- + ## Component The `component` template tag can be used to insert any number of IDOM components onto your page. @@ -10,43 +14,57 @@ The `component` template tag can be used to insert any number of IDOM components {% include-markdown "../../../README.md" start="" end="" %} +??? example "See Interface" + + **Parameters** + + | Name | Type | Description | Default | + | --- | --- | --- | --- | + | `dotted_path` | `str` | The dotted path to the component to render. | N/A | + | `*args` | `Any` | The positional arguments to provide to the component. | N/A | + | `**kwargs` | `Any` | The keyword arguments to provide to the component. | N/A | + + **Returns** + + | Type | Description | + | --- | --- | + | `Component` | An IDOM component. | + ??? warning "Do not use context variables for the IDOM component name" Our preprocessor relies on the template tag containing a string. - **Do not** use Django template/context variables for the component path. Failure to follow this warning will result in unexpected behavior. + **Do not** use Django template/context variables for the component path. Failure to follow this warning can result in unexpected behavior. For example, **do not** do the following: === "my-template.html" ```jinja - - {% component dont_do_this recipient="World" %} - {% component "example_project.my_app.components.hello_world" recipient="World" %} + + + {% component dont_do_this recipient="World" %} ``` === "views.py" ```python - def example_view(): - context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"} - return render(request, "my-template.html", context_vars) + {% include "../../python/template-tag-bad-view.py" %} ``` - + ??? info "Reserved keyword arguments: `class` and `key`" For this template tag, there are two reserved keyword arguments: `class` and `key` - `class` allows you to apply a HTML class to the top-level component div. This is useful for styling purposes. - - `key` allows you to force the component to use a [specific key value](https://idom-docs.herokuapp.com/docs/guides/creating-interfaces/rendering-data/index.html#organizing-items-with-keys). Using `key` within a template tag is effectively useless. + - `key` allows you to force the component's root node to use a [specific key value](https://idom-docs.herokuapp.com/docs/guides/creating-interfaces/rendering-data/index.html#organizing-items-with-keys). Using `key` within a template tag is effectively useless. === "my-template.html" @@ -56,24 +74,7 @@ The `component` template tag can be used to insert any number of IDOM components ... ``` - - -??? example "See Interface" - - **Parameters** - - | Name | Type | Description | Default | - | --- | --- | --- | --- | - | `dotted_path` | `str` | The dotted path to the component to render. | N/A | - | `*args` | `Any` | The positional arguments to provide to the component. | N/A | - | `**kwargs` | `Any` | The keyword arguments to provide to the component. | N/A | - - **Returns** - - | Type | Description | - | --- | --- | - | `Component` | An IDOM component. | - + ??? question "Can I use multiple components on one page?" @@ -87,9 +88,9 @@ The `component` template tag can be used to insert any number of IDOM components - {% component "example_project.my_app.components.hello_world" recipient="World" %} - {% component "example_project.my_app_2.components.class_component" class="bold small-font" %} -
{% component "example_project.my_app_3.components.simple_component" %}
+

{% component "example_project.my_app.components.my_title" %}

+

{% component "example_project.my_app_2.components.goodbye_world" class="bold small-font" %}

+ {% component "example_project.my_app_3.components.simple_button" %} ``` @@ -99,3 +100,23 @@ The `component` template tag can be used to insert any number of IDOM components Additionally, in scenarios where you are trying to create a Single Page Application (SPA) within Django, you will only have one component within your `#!html ` tag. + + + +??? question "Can I use positional arguments instead of keyword arguments?" + + You can use any combination of `*args`/`**kwargs` in your template tag. + + === "my-template.html" + + ```jinja + {% component "example_project.my_app.components.frog_greeter" 123 "Mr. Froggles" species="Grey Treefrog" %} + ``` + + === "components.py" + + ```python + {% include "../../python/template-tag-args-kwargs.py" %} + ``` + + diff --git a/docs/src/features/utils.md b/docs/src/features/utils.md index 9a0aca8b..3cf63c29 100644 --- a/docs/src/features/utils.md +++ b/docs/src/features/utils.md @@ -1,7 +1,11 @@ -???+ summary +## Overview + +!!! summary Utility functions that you can use when needed. +--- + ## Django Query Postprocessor This is the default postprocessor for the `use_query` hook. @@ -11,36 +15,13 @@ This postprocessor is designed to prevent Django's `SynchronousOnlyException` by === "components.py" ```python - from example_project.my_app.models import TodoItem - from idom import component - from django_idom.hooks import use_query - from django_idom.types import QueryOptions - from django_idom.utils import django_query_postprocessor - - def get_items(): - return TodoItem.objects.all() - - @component - def todo_list(): - # These `QueryOptions` are functionally equivalent to Django-IDOM's default values - item_query = use_query( - QueryOptions( - postprocessor=django_query_postprocessor, - postprocessor_kwargs={"many_to_many": True, "many_to_one": True}, - ), - get_items, - ) - - ... + {% include "../../python/django-query-postprocessor.py" %} ``` === "models.py" ```python - from django.db import models - - class TodoItem(models.Model): - text = models.CharField(max_length=255) + {% include "../../python/example/models.py" %} ``` ??? example "See Interface" diff --git a/docs/src/getting-started/choose-django-app.md b/docs/src/get-started/choose-django-app.md similarity index 84% rename from docs/src/getting-started/choose-django-app.md rename to docs/src/get-started/choose-django-app.md index b414cc2a..fffb8c76 100644 --- a/docs/src/getting-started/choose-django-app.md +++ b/docs/src/get-started/choose-django-app.md @@ -1,10 +1,14 @@ -???+ summary +## Overview + +!!! summary Set up a **Django Project** with at least one app. --- -If you have reached this point, you should have already [installed Django-IDOM](../getting-started/installation.md) through the previous steps. +## Choose a Django App + +If you have reached this point, you should have already [installed Django-IDOM](../get-started/installation.md) through the previous steps. You will now need to pick at least one **Django app** to start using Django-IDOM on. diff --git a/docs/src/getting-started/create-component.md b/docs/src/get-started/create-component.md similarity index 94% rename from docs/src/getting-started/create-component.md rename to docs/src/get-started/create-component.md index e024a3e9..8a74350d 100644 --- a/docs/src/getting-started/create-component.md +++ b/docs/src/get-started/create-component.md @@ -1,9 +1,13 @@ -???+ summary +## Overview + +!!! summary Create a component function using our decorator. --- +## Create a Component + {% include-markdown "../../../README.md" start="" end="" %} === "components.py" diff --git a/docs/src/getting-started/installation.md b/docs/src/get-started/installation.md similarity index 60% rename from docs/src/getting-started/installation.md rename to docs/src/get-started/installation.md index f311abef..f822c8b2 100644 --- a/docs/src/getting-started/installation.md +++ b/docs/src/get-started/installation.md @@ -1,7 +1,11 @@ -???+ summary +## Overview + +!!! summary Django-IDOM can be installed from PyPI to an existing **Django project** with minimal configuration. +--- + ## Step 0: Create a Django Project These docs assumes you have already created [a **Django project**](https://docs.djangoproject.com/en/dev/intro/tutorial01/), which involves creating and installing at least one **Django app**. If not, check out this [9 minute YouTube tutorial](https://www.youtube.com/watch?v=ZsJRXS_vrw0) created by _IDG TECHtalk_. @@ -19,15 +23,12 @@ In your settings you will need to add `django_idom` to [`INSTALLED_APPS`](https: === "settings.py" ```python - INSTALLED_APPS = [ - "django_idom", - ... - ] + {% include "../../python/configure-installed-apps.py" %} ``` -??? warning "Enable Django ASGI (Required)" +??? warning "Enable Django Channels ASGI (Required)" - Django-IDOM requires ASGI in order to use Websockets. + Django-IDOM requires ASGI Websockets from [Django Channels](https://github.com/django/channels). If you have not enabled ASGI on your **Django project** yet, you will need to install `channels[daphne]`, add `daphne` to `INSTALLED_APPS`, then set your `ASGI_APPLICATION` variable. @@ -36,18 +37,16 @@ In your settings you will need to add `django_idom` to [`INSTALLED_APPS`](https: === "settings.py" ```python - INSTALLED_APPS = [ - "daphne", - ... - ] - ASGI_APPLICATION = "example_project.asgi.application" + {% include "../../python/configure-channels.py" %} ``` ??? note "Configure IDOM settings (Optional)" Below are a handful of values you can change within `settings.py` to modify the behavior of IDOM. - {% include-markdown "../features/settings.md" start="" end="" preserve-includer-indent=false %} + ```python + {% include "../../python/settings.py" %} + ``` ## Step 3: Configure [`urls.py`](https://docs.djangoproject.com/en/dev/topics/http/urls/) @@ -56,12 +55,7 @@ Add IDOM HTTP paths to your `urlpatterns`. === "urls.py" ```python - from django.urls import include, path - - urlpatterns = [ - path("idom/", include("django_idom.http.urls")), - ... - ] + {% include "../../python/configure-urls.py" %} ``` ## Step 4: Configure [`asgi.py`](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/) @@ -71,26 +65,7 @@ Register IDOM's Websocket using `IDOM_WEBSOCKET_PATH`. === "asgi.py" ```python - import os - from django.core.asgi import get_asgi_application - - # Ensure DJANGO_SETTINGS_MODULE is set properly based on your project name! - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings") - django_asgi_app = get_asgi_application() - - from channels.auth import AuthMiddlewareStack - from channels.routing import ProtocolTypeRouter, URLRouter - from channels.sessions import SessionMiddlewareStack - from django_idom import IDOM_WEBSOCKET_PATH - - application = ProtocolTypeRouter( - { - "http": django_asgi_app, - "websocket": SessionMiddlewareStack( - AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH])) - ), - } - ) + {% include "../../python/configure-asgi.py" %} ``` ??? question "Where is my `asgi.py`?" diff --git a/docs/src/getting-started/learn-more.md b/docs/src/get-started/learn-more.md similarity index 54% rename from docs/src/getting-started/learn-more.md rename to docs/src/get-started/learn-more.md index b41b6484..e50af0ab 100644 --- a/docs/src/getting-started/learn-more.md +++ b/docs/src/get-started/learn-more.md @@ -4,8 +4,8 @@ If you followed the previous steps, you have now created a "Hello World" compone The docs you are reading only covers our Django integration. To learn more about features, such as interactive events and hooks, check out the [IDOM Core Documentation](https://idom-docs.herokuapp.com/docs/guides/creating-interfaces/index.html)! -Additionally, the vast majority of tutorials/guides you find for React can be applied to IDOM. +Additionally, the vast majority of tutorials/guides you find for ReactJS can be applied to IDOM. -| Learn More | -| --- | -| [Django-IDOM Advanced Usage](../features/components.md){ .md-button } [IDOM Core Documentation](https://idom-docs.herokuapp.com/docs/guides/creating-interfaces/index.html){ .md-button } [Ask Questions on GitHub Discussions](https://github.com/idom-team/idom/discussions){ .md-button .md-button--primary } | +=== "Learn More" + + [Django-IDOM Advanced Usage](../features/components.md){ .md-button .md-button--primary} [IDOM Core Documentation](https://idom-docs.herokuapp.com/docs/guides/creating-interfaces/index.html){ .md-button .md-button--primary } [Ask Questions](https://github.com/idom-team/idom/discussions){ .md-button .md-button--primary } diff --git a/docs/src/getting-started/render-view.md b/docs/src/get-started/render-view.md similarity index 84% rename from docs/src/getting-started/render-view.md rename to docs/src/get-started/render-view.md index 6098680b..30e0ead6 100644 --- a/docs/src/getting-started/render-view.md +++ b/docs/src/get-started/render-view.md @@ -1,9 +1,13 @@ -???+ summary +## Overview + +!!! summary Select your template containing an IDOM component, and render it using a Django view. --- +## Render Your View + We will assume you have [created a Django View](https://docs.djangoproject.com/en/dev/intro/tutorial01/#write-your-first-view) before, but here's a simple example below. Within your **Django app**'s `views.py` file, you will need to create a function to render the HTML template containing your IDOM components. @@ -13,10 +17,7 @@ In this example, we will create a view that renders `my-template.html` (_from th === "views.py" ```python - from django.shortcuts import render - - def index(request): - return render(request, "my-template.html") + {% include "../../python/example/views.py" %} ``` We will add this new view into your [`urls.py`](https://docs.djangoproject.com/en/dev/intro/tutorial01/#write-your-first-view). @@ -24,12 +25,7 @@ We will add this new view into your [`urls.py`](https://docs.djangoproject.com/e === "urls.py" ```python - from django.urls import path - from example_project.my_app import views - - urlpatterns = [ - path("example/", views.index), - ] + {% include "../../python/example/urls.py" %} ``` Now, navigate to `http://127.0.0.1:8000/example/`. If you copy-pasted the component from the previous example, you will now see your component display "Hello World". diff --git a/docs/src/getting-started/reference-component.md b/docs/src/get-started/use-template-tag.md similarity index 63% rename from docs/src/getting-started/reference-component.md rename to docs/src/get-started/use-template-tag.md index 1576f3ee..6fd9017c 100644 --- a/docs/src/getting-started/reference-component.md +++ b/docs/src/get-started/use-template-tag.md @@ -1,20 +1,24 @@ -???+ summary +## Overview + +!!! summary Decide where the component will be displayed by using our template tag. --- +## Use the Template Tag + {% include-markdown "../../../README.md" start="" end="" %} === "my-template.html" {% include-markdown "../../../README.md" start="" end="" %} -{% include-markdown "../features/templatetag.md" start="" end="" %} +{% include-markdown "../features/template-tag.md" start="" end="" %} -{% include-markdown "../features/templatetag.md" start="" end="" %} +{% include-markdown "../features/template-tag.md" start="" end="" %} -{% include-markdown "../features/templatetag.md" start="" end="" %} +{% include-markdown "../features/template-tag.md" start="" end="" %} ??? question "Where is my templates folder?" diff --git a/docs/src/stylesheets/extra.css b/docs/src/stylesheets/extra.css index 259625f9..72e8bd26 100644 --- a/docs/src/stylesheets/extra.css +++ b/docs/src/stylesheets/extra.css @@ -1,12 +1,4 @@ -.md-footer__inner { - display: none; -} - -.md-tabs, -.md-header { - background-color: var(--md-footer-bg-color--dark); -} - +/* Reduce the insane amounts of white space the default theme has */ .md-typeset :is(.admonition, details) { margin: 0.55em 0; } @@ -16,14 +8,230 @@ padding-bottom: 0.35em; } -body[data-md-color-scheme="slate"] - .md-typeset - :is(.admonition, details):is(.question, .help, .faq) { - border-color: #366517; +/* Font size for admonitions */ +.md-typeset .admonition.summary, +.md-typeset details.summary { + font-size: 0.7rem; } -body[data-md-color-scheme="slate"] +/* Colors for admonitions */ +[data-md-color-scheme="slate"] .md-typeset - :-webkit-any(.admonition, details):-webkit-any(.question, .help, .faq) { - border-color: #366517; + details:not(.warning, .failure, .danger, .bug) + > .admonition-title, +[data-md-color-scheme="slate"] + .md-typeset + details:not(.warning, .failure, .danger, .bug) + > summary { + background: var(--md-primary-fg-color) !important; +} + +[data-md-color-scheme="slate"] .md-typeset .admonition, +[data-md-color-scheme="slate"] .md-typeset details { + border-color: transparent !important; +} + +[data-md-color-scheme="slate"] .md-typeset .admonition.summary, +[data-md-color-scheme="slate"] .md-typeset details.summary { + background: #353a45; +} + +[data-md-color-scheme="slate"] .md-typeset details > .admonition-title:after, +[data-md-color-scheme="slate"] .md-typeset details > summary:after { + color: var(--md-admonition-fg-color) !important; +} + +/* Colors for summary admonition */ +[data-md-color-scheme="slate"] .md-typeset .admonition.summary, +[data-md-color-scheme="slate"] .md-typeset details.summary { + background: #353a45; +} + +[data-md-color-scheme="slate"] .md-typeset details.summary > .admonition-title, +[data-md-color-scheme="slate"] .md-typeset details.summary > summary { + background: #353a45 !important; +} + +[data-md-color-scheme="slate"] .md-typeset .summary .admonition-title, +[data-md-color-scheme="slate"] .md-typeset .summary summary { + background: transparent; +} + +/* Move the sidebars to the edges of the page */ +.md-main__inner.md-grid { + margin-left: 0; + margin-right: 0; + max-width: unset; + display: flex; + justify-content: center; +} + +.md-sidebar--primary { + margin-right: auto; +} + +.md-sidebar.md-sidebar--secondary { + margin-left: auto; +} + +.md-content { + max-width: 56rem; +} + +/* Maintain content positioning even if sidebars are disabled */ +@media screen and (min-width: 76.1875em) { + .md-sidebar { + display: block; + } + + .md-sidebar[hidden] { + visibility: hidden; + } +} + +/* Sidebar styling */ +@media screen and (min-width: 76.1875em) { + .md-nav__title[for="__toc"] { + text-transform: uppercase; + margin: 0.5rem; + } + + .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { + color: rgb(133 142 159); + margin: 0.5rem; + } + + .md-nav__item .md-nav__link { + position: relative; + } + + .md-nav__link:is(:focus, :hover):not(.md-nav__link--active) { + color: unset; + } + + .md-nav__item + .md-nav__link:is(:focus, :hover):not(.md-nav__link--active):before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.2; + z-index: -1; + background-color: grey; + } + + .md-nav__item .md-nav__link--active:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.15; + z-index: -1; + background-color: var(--md-typeset-a-color); + } + + .md-nav__link { + padding: 0.5rem 0.5rem 0.5rem 1rem; + margin: 0; + border-radius: 0 10px 10px 0; + font-weight: 500; + } + + .md-sidebar__scrollwrap { + margin: 0; + } + + [dir="ltr"] + .md-nav--lifted + .md-nav[data-md-level="1"] + > .md-nav__list + > .md-nav__item { + padding: 0; + } + + .md-nav__item--nested .md-nav__item .md-nav__item { + padding: 0; + } + + .md-nav__item--nested .md-nav__item .md-nav__item .md-nav__link { + font-weight: 400; + padding-left: 1.5rem; + } +} + +/* Table of Contents styling */ +@media screen and (min-width: 60em) { + [data-md-component="sidebar"] .md-nav__title[for="__toc"] { + text-transform: uppercase; + margin: 0.5rem; + margin-left: 0; + } + + [data-md-component="toc"] .md-nav__item .md-nav__link--active { + position: relative; + } + + [data-md-component="toc"] .md-nav__item .md-nav__link--active:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.15; + z-index: -1; + background-color: var(--md-typeset-a-color); + } + + [data-md-component="toc"] .md-nav__link { + padding: 0.5rem 0.5rem; + margin: 0; + border-radius: 10px 0 0 10px; + } + [dir="ltr"] .md-sidebar__inner { + padding: 0; + } + + .md-nav__item { + padding: 0; + } +} + +/* Page background color */ +[data-md-color-scheme="slate"] { + --md-hue: 225; + --md-default-bg-color: hsla(var(--md-hue), 15%, 16%, 1); + --md-default-bg-color--light: hsla(var(--md-hue), 15%, 16%, 0.54); + --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 16%, 0.26); + --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 16%, 0.07); + --md-code-bg-color: #16181d; + --md-primary-fg-color: #2b3540; + --md-default-fg-color--light: #fff; + --md-typeset-a-color: #00b0f0; + --md-code-hl-comment-color: hsla(var(--md-hue), 75%, 90%, 0.43); +} + +/* Font changes */ +.md-typeset h1 { + font-weight: 500; +} + +.md-typeset h1:not([id]) { + display: none; +} + +.md-typeset h2 { + font-weight: 400; +} + +/* Hide invisible jump selectors */ +h2#overview { + visibility: hidden; + height: 0; + margin: 0; + padding: 0; } diff --git a/mkdocs.yml b/mkdocs.yml index 2a60cb5c..78fb08af 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,36 +1,39 @@ --- nav: - Home: index.md - - Getting Started: - - 1. Install Django-IDOM: getting-started/installation.md - - 2. Choose a Django App: getting-started/choose-django-app.md - - 3. Create a Component: getting-started/create-component.md - - 4. Use the Template Tag: getting-started/reference-component.md - - 5. Render Your View: getting-started/render-view.md - - 6. Learn More: getting-started/learn-more.md - - Usage: + - Get Started: + - Install Django-IDOM: get-started/installation.md + - Choose a Django App: get-started/choose-django-app.md + - Create a Component: get-started/create-component.md + - Use the Template Tag: get-started/use-template-tag.md + - Render Your View: get-started/render-view.md + - Learn More: get-started/learn-more.md + - Reference: - Components: features/components.md - Hooks: features/hooks.md - Decorators: features/decorators.md - Utilities: features/utils.md - - Template Tag: features/templatetag.md + - Template Tag: features/template-tag.md - Settings: features/settings.md - - Contribute: - - Code: contribute/code.md - - Docs: contribute/docs.md - - Running Tests: contribute/running-tests.md - - Changelog: changelog/index.md + - About: + - Contribute: + - Code: contribute/code.md + - Docs: contribute/docs.md + - Running Tests: contribute/running-tests.md + - Community: https://github.com/idom-team/idom/discussions + - Changelog: changelog/index.md theme: name: material + custom_dir: docs/overrides palette: - media: "(prefers-color-scheme: dark)" scheme: slate toggle: icon: material/white-balance-sunny name: Switch to light mode - primary: deep-orange - accent: orange + primary: light blue + accent: light blue - media: "(prefers-color-scheme: light)" scheme: default toggle: @@ -39,10 +42,9 @@ theme: primary: black features: - navigation.instant - - navigation.tracking - navigation.tabs - - toc.integrate - navigation.top + - content.code.copy icon: repo: fontawesome/brands/github @@ -64,7 +66,13 @@ markdown_extensions: plugins: - search + - git-authors - include-markdown + - minify: + minify_html: true + minify_js: true + minify_css: true + cache_safe: true - git-revision-date-localized: fallback_to_build_date: true - spellcheck: @@ -84,7 +92,7 @@ watch: - README.md - CHANGELOG.md -site_name: Django IDOM Docs +site_name: Django-IDOM Docs site_author: Archmonger site_description: React for Django developers. copyright: Copyright © 2022 IDOM Team diff --git a/requirements.txt b/requirements.txt index 7328d474..63e3d68e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ --r requirements/pkg-deps.txt +-r requirements/build-docs.txt +-r requirements/build-pkg.txt -r requirements/check-style.txt +-r requirements/check-types.txt +-r requirements/dev-env.txt +-r requirements/pkg-deps.txt -r requirements/test-env.txt -r requirements/test-run.txt --r requirements/dev-env.txt diff --git a/requirements/build-docs.txt b/requirements/build-docs.txt index 304cc367..9ae8fcf1 100644 --- a/requirements/build-docs.txt +++ b/requirements/build-docs.txt @@ -4,3 +4,5 @@ mkdocs-material mkdocs-include-markdown-plugin linkcheckmd mkdocs-spellcheck[all] +mkdocs-git-authors-plugin +mkdocs-minify-plugin diff --git a/requirements/pkg-deps.txt b/requirements/pkg-deps.txt index 441e7a04..ef43fa41 100644 --- a/requirements/pkg-deps.txt +++ b/requirements/pkg-deps.txt @@ -1,5 +1,5 @@ channels >=4.0.0 -idom >=0.43.0, <0.44.0 +idom >=1.0.0a2, <1.1.0 aiofile >=3.0 dill >=0.3.5 typing_extensions diff --git a/setup.py b/setup.py index 4d796537..9fd8e902 100644 --- a/setup.py +++ b/setup.py @@ -84,7 +84,7 @@ def list2cmdline(cmd_list): package["version"] = eval(line.split("=", 1)[1]) break else: - print("No version found in %s/__init__.py" % package_dir) + print(f"No version found in {package_dir}/__init__.py") sys.exit(1) @@ -95,9 +95,7 @@ def list2cmdline(cmd_list): requirements = [] with (root_dir / "requirements" / "pkg-deps.txt").open() as f: - for line in map(str.strip, f): - if not line.startswith("#"): - requirements.append(line) + requirements.extend(line for line in map(str.strip, f) if not line.startswith("#")) package["install_requires"] = requirements @@ -121,22 +119,42 @@ def list2cmdline(cmd_list): def build_javascript_first(cls): class Command(cls): def run(self): + js_dir = str(src_dir / "js") + npm = shutil.which("npm") # this is required on windows + if npm is None: + raise RuntimeError("NPM is not installed.") + + log.info("Updating NPM...") + try: + args_list = [npm, "install", "-g", "npm@latest"] + log.info(f"> {list2cmdline(args_list)}") + subprocess.run(args_list, cwd=js_dir, check=True) + except Exception: + log.error("Failed to update NPM") + log.error(traceback.format_exc()) + raise + log.info("Installing Javascript...") try: - js_dir = str(src_dir / "js") - npm = shutil.which("npm") # this is required on windows - if npm is None: - raise RuntimeError("NPM is not installed.") - for args in (f"{npm} install", f"{npm} run build"): - args_list = args.split() - log.info(f"> {list2cmdline(args_list)}") - subprocess.run(args_list, cwd=js_dir, check=True) + args_list = [npm, "install"] + log.info(f"> {list2cmdline(args_list)}") + subprocess.run(args_list, cwd=js_dir, check=True) except Exception: log.error("Failed to install Javascript") log.error(traceback.format_exc()) raise - else: - log.info("Successfully installed Javascript") + + log.info("Building Javascript...") + try: + args_list = [npm, "run", "build"] + log.info(f"> {list2cmdline(args_list)}") + subprocess.run(args_list, cwd=js_dir, check=True) + except Exception: + log.error("Failed to build Javascript") + log.error(traceback.format_exc()) + raise + + log.info("Successfully built Javascript") super().run() return Command diff --git a/src/django_idom/__init__.py b/src/django_idom/__init__.py index ca378289..ed57b138 100644 --- a/src/django_idom/__init__.py +++ b/src/django_idom/__init__.py @@ -2,7 +2,7 @@ from django_idom.websocket.paths import IDOM_WEBSOCKET_PATH -__version__ = "2.2.1" +__version__ = "3.0.0a1" __all__ = [ "IDOM_WEBSOCKET_PATH", "hooks", diff --git a/src/django_idom/components.py b/src/django_idom/components.py index fd9d5292..4448371f 100644 --- a/src/django_idom/components.py +++ b/src/django_idom/components.py @@ -76,10 +76,7 @@ async def async_render(): view, _args, _kwargs ) return html.iframe( - { - "src": reverse("idom:view_to_component", args=[dotted_path]), - "loading": "lazy", - } + src=reverse("idom:view_to_component", args=[dotted_path]), loading="lazy" ) # Return the view if it's been rendered via the `async_render` hook diff --git a/src/django_idom/http/views.py b/src/django_idom/http/views.py index d8858f4c..5fffde7e 100644 --- a/src/django_idom/http/views.py +++ b/src/django_idom/http/views.py @@ -3,7 +3,7 @@ from aiofile import async_open from django.core.exceptions import SuspiciousOperation from django.http import HttpRequest, HttpResponse, HttpResponseNotFound -from idom.config import IDOM_WED_MODULES_DIR +from idom.config import IDOM_WEB_MODULES_DIR from django_idom.utils import create_cache_key, render_view @@ -13,13 +13,13 @@ async def web_modules_file(request: HttpRequest, file: str) -> HttpResponse: returned from cache if available.""" from django_idom.config import IDOM_CACHE - web_modules_dir = IDOM_WED_MODULES_DIR.current + web_modules_dir = IDOM_WEB_MODULES_DIR.current path = os.path.abspath(web_modules_dir.joinpath(*file.split("/"))) # Prevent attempts to walk outside of the web modules dir if str(web_modules_dir) != os.path.commonpath((path, web_modules_dir)): raise SuspiciousOperation( - "Attempt to access a directory outside of IDOM_WED_MODULES_DIR." + "Attempt to access a directory outside of IDOM_WEB_MODULES_DIR." ) # Fetch the file from cache, if available diff --git a/src/django_idom/websocket/consumer.py b/src/django_idom/websocket/consumer.py index fa90ca48..99f860ba 100644 --- a/src/django_idom/websocket/consumer.py +++ b/src/django_idom/websocket/consumer.py @@ -13,8 +13,8 @@ from django.utils import timezone from idom.backend.hooks import ConnectionContext from idom.backend.types import Connection, Location -from idom.core.layout import Layout, LayoutEvent -from idom.core.serve import serve_json_patch +from idom.core.layout import Layout +from idom.core.serve import serve_layout from django_idom.types import ComponentParamData, ComponentWebsocket from django_idom.utils import db_cleanup, func_has_params @@ -50,8 +50,8 @@ async def disconnect(self, code: int) -> None: self._idom_dispatcher_future.cancel() await super().disconnect(code) - async def receive_json(self, content: Any, **kwargs: Any) -> None: - await self._idom_recv_queue.put(LayoutEvent(**content)) + async def receive_json(self, content: Any, **_) -> None: + await self._idom_recv_queue.put(content) async def _run_dispatch_loop(self): from django_idom import models @@ -121,7 +121,7 @@ async def _run_dispatch_loop(self): # Begin serving the IDOM component try: - await serve_json_patch( + await serve_layout( Layout(ConnectionContext(component_instance, value=connection)), self.send_json, self._idom_recv_queue.get, diff --git a/src/js/package-lock.json b/src/js/package-lock.json index fc51d27a..83e0ec39 100644 --- a/src/js/package-lock.json +++ b/src/js/package-lock.json @@ -5,43 +5,146 @@ "packages": { "": { "dependencies": { - "idom-client-react": "^0.43.0", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "idom-client-react": "^1.0.0-a2" }, "devDependencies": { - "prettier": "^2.2.1", - "rollup": "^2.56.3", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-node-resolve": "^5.2.0", - "rollup-plugin-replace": "^2.2.0" + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-replace": "^5.0.2", + "prettier": "^2.8.3", + "rollup": "^3.12.0" } }, - "node_modules/@types/estree": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", - "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, - "node_modules/@types/node": { - "version": "15.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", - "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", + "node_modules/@rollup/plugin-commonjs": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", + "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", + "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.0", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", + "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@types/estree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", "dev": true }, "node_modules/@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "@types/node": "*" + "balanced-match": "^1.0.0" } }, "node_modules/builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, "engines": { "node": ">=6" @@ -50,16 +153,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, - "node_modules/fast-json-patch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -81,6 +205,25 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -99,22 +242,53 @@ "integrity": "sha512-L0s3Sid5r6YwrEvkig14SK3Emmc+kIjlfLhEGn2Vy3bk21JyDEes4MoDsbJk6luaPp8bugErnxPz86ZuAw6e5Q==" }, "node_modules/idom-client-react": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/idom-client-react/-/idom-client-react-0.43.0.tgz", - "integrity": "sha512-SjVR7wmqsNO5ymKKOsLsQUA+cN12+KuG56xLkCDVyEnlDxUytIOzpzQ3qBsAeMbRJkT/BFURim7UeKnAgWgoLw==", + "version": "1.0.0-a2", + "resolved": "https://registry.npmjs.org/idom-client-react/-/idom-client-react-1.0.0-a2.tgz", + "integrity": "sha512-mfpyPXfM8R4lvgd45DJg+tn/tc5gKNxM32sQPaUr5oWFmt81f1nhWHLmM6RlNv/hB1n51023QCcU4Fj0NCmleg==", "dependencies": { - "fast-json-patch": "^3.1.1", - "htm": "^3.0.3" + "htm": "^3.0.3", + "json-pointer": "^0.6.2" }, "peerDependencies": { "react": ">=16", "react-dom": ">=16" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-builtin-module": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz", + "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -126,7 +300,7 @@ "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", "dev": true }, "node_modules/is-reference": { @@ -141,12 +315,22 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "peer": true + }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dependencies": { + "foreach": "^2.0.4" + } }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -155,44 +339,85 @@ } }, "node_modules/magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { - "sourcemap-codec": "^1.4.4" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/prettier": { + "node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", - "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prettier": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "dev": true, "bin": { "prettier": "bin-prettier.js" }, "engines": { "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" } }, "node_modules/react": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -205,6 +430,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -215,141 +441,184 @@ } }, "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dev": true, "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/rollup": { - "version": "2.56.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz", - "integrity": "sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.0.tgz", + "integrity": "sha512-4MZ8kA2HNYahIjz63rzrMMRvDqQDeS9LoriJvMuV0V6zIGysP36e9t4yObUfwdT9h/szXoHQideICftcdZklWg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.18.0", + "npm": ">=8.0.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, - "node_modules/rollup-plugin-commonjs": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", - "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-commonjs.", - "dev": true, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "peer": true, "dependencies": { - "estree-walker": "^0.6.1", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0", - "rollup-pluginutils": "^2.8.1" - }, - "peerDependencies": { - "rollup": ">=1.12.0" + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" } }, - "node_modules/rollup-plugin-node-resolve": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", - "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-node-resolve.", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "dependencies": { - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.11.1", - "rollup-pluginutils": "^2.8.1" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "rollup": ">=1.11.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/rollup-plugin-replace": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz", - "integrity": "sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==", - "deprecated": "This module has moved and is now available at @rollup/plugin-replace. Please update your dependencies. This version is no longer maintained.", + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@rollup/plugin-commonjs": { + "version": "24.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz", + "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==", "dev": true, - "dependencies": { - "magic-string": "^0.25.2", - "rollup-pluginutils": "^2.6.0" + "requires": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + } + }, + "@rollup/plugin-node-resolve": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz", + "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.0", + "is-module": "^1.0.0", + "resolve": "^1.22.1" } }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "@rollup/plugin-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz", + "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==", "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" + "requires": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" } }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "@rollup/pluginutils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", + "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" } }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - } - }, - "dependencies": { "@types/estree": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz", - "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", "dev": true }, - "@types/node": { - "version": "15.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", - "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", + "@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "@types/node": "*" + "balanced-match": "^1.0.0" } }, "builtin-modules": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", - "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, - "fast-json-patch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" + "foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -364,6 +633,19 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -379,18 +661,43 @@ "integrity": "sha512-L0s3Sid5r6YwrEvkig14SK3Emmc+kIjlfLhEGn2Vy3bk21JyDEes4MoDsbJk6luaPp8bugErnxPz86ZuAw6e5Q==" }, "idom-client-react": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/idom-client-react/-/idom-client-react-0.43.0.tgz", - "integrity": "sha512-SjVR7wmqsNO5ymKKOsLsQUA+cN12+KuG56xLkCDVyEnlDxUytIOzpzQ3qBsAeMbRJkT/BFURim7UeKnAgWgoLw==", + "version": "1.0.0-a2", + "resolved": "https://registry.npmjs.org/idom-client-react/-/idom-client-react-1.0.0-a2.tgz", + "integrity": "sha512-mfpyPXfM8R4lvgd45DJg+tn/tc5gKNxM32sQPaUr5oWFmt81f1nhWHLmM6RlNv/hB1n51023QCcU4Fj0NCmleg==", + "requires": { + "htm": "^3.0.3", + "json-pointer": "^0.6.2" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, "requires": { - "fast-json-patch": "^3.1.1", - "htm": "^3.0.3" + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-builtin-module": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz", + "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==", + "dev": true, + "requires": { + "builtin-modules": "^3.3.0" } }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dev": true, "requires": { "has": "^1.0.3" @@ -399,7 +706,7 @@ "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", "dev": true }, "is-reference": { @@ -414,29 +721,58 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "peer": true + }, + "json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "requires": { + "foreach": "^2.0.4" + } }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } }, "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", "dev": true, "requires": { - "sourcemap-codec": "^1.4.4" + "@jridgewell/sourcemap-codec": "^1.4.13" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" } }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "peer": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } }, "path-parse": { "version": "1.0.7", @@ -444,16 +780,23 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "prettier": { + "picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", - "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "prettier": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "dev": true }, "react": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -463,6 +806,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -470,82 +814,45 @@ } }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "rollup": { - "version": "2.56.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz", - "integrity": "sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.0.tgz", + "integrity": "sha512-4MZ8kA2HNYahIjz63rzrMMRvDqQDeS9LoriJvMuV0V6zIGysP36e9t4yObUfwdT9h/szXoHQideICftcdZklWg==", "dev": true, "requires": { "fsevents": "~2.3.2" } }, - "rollup-plugin-commonjs": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", - "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-node-resolve": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", - "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", - "dev": true, - "requires": { - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.11.1", - "rollup-pluginutils": "^2.8.1" - } - }, - "rollup-plugin-replace": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz", - "integrity": "sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==", - "dev": true, - "requires": { - "magic-string": "^0.25.2", - "rollup-pluginutils": "^2.6.0" - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - } - }, "scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true } } diff --git a/src/js/package.json b/src/js/package.json index 10b43551..b43be2dc 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -1,6 +1,7 @@ { - "description": "test app for idom_django websocket server", + "description": "django-idom client", "main": "src/index.js", + "type": "module", "files": [ "src/**/*.js" ], @@ -9,15 +10,13 @@ "format": "prettier --ignore-path .gitignore --write ." }, "devDependencies": { - "prettier": "^2.2.1", - "rollup": "^2.56.3", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-node-resolve": "^5.2.0", - "rollup-plugin-replace": "^2.2.0" + "prettier": "^2.8.3", + "rollup": "^3.12.0", + "@rollup/plugin-commonjs": "^24.0.1", + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-replace": "^5.0.2" }, "dependencies": { - "idom-client-react": "^0.43.0", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "idom-client-react": "^1.0.0-a2" } } diff --git a/src/js/rollup.config.js b/src/js/rollup.config.js deleted file mode 100644 index 2443f388..00000000 --- a/src/js/rollup.config.js +++ /dev/null @@ -1,29 +0,0 @@ -import resolve from "rollup-plugin-node-resolve"; -import commonjs from "rollup-plugin-commonjs"; -import replace from "rollup-plugin-replace"; - -export default { - input: "src/index.js", - output: { - file: "../django_idom/static/django_idom/client.js", - format: "esm", - }, - plugins: [ - resolve(), - commonjs(), - replace({ - "process.env.NODE_ENV": JSON.stringify("production"), - }), - ], - onwarn: function (warning) { - // Skip certain warnings - - // should intercept ... but doesn't in some rollup versions - if (warning.code === "THIS_IS_UNDEFINED") { - return; - } - - // console.warn everything else - console.warn(warning.message); - }, -}; diff --git a/src/js/rollup.config.mjs b/src/js/rollup.config.mjs new file mode 100644 index 00000000..57c36438 --- /dev/null +++ b/src/js/rollup.config.mjs @@ -0,0 +1,21 @@ +import resolve from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; +import replace from "@rollup/plugin-replace"; + +export default { + input: "src/index.js", + output: { + file: "../django_idom/static/django_idom/client.js", + format: "esm", + }, + plugins: [ + resolve(), + commonjs(), + replace({ + "process.env.NODE_ENV": JSON.stringify("production"), + }), + ], + onwarn: function (warning) { + console.warn(warning.message); + }, +}; diff --git a/tests/test_app/components.py b/tests/test_app/components.py index 38ba30a5..042b9aa7 100644 --- a/tests/test_app/components.py +++ b/tests/test_app/components.py @@ -15,7 +15,7 @@ @component def hello_world(): - return html._(html.div({"id": "hello-world"}, "Hello World!"), html.hr()) + return html._(html.div("Hello World!", id="hello-world"), html.hr()) @component @@ -25,13 +25,11 @@ def button(): html.div( "button:", html.button( - {"id": "counter-inc", "onClick": lambda event: set_count(count + 1)}, "Click me!", + id="counter-inc", + on_click=lambda event: set_count(count + 1), ), - html.p( - {"id": "counter-num", "data-count": count}, - f"Current count is: {count}", - ), + html.p(f"Current count is: {count}", id="counter-num", data_count=count), ), html.hr(), ) @@ -42,8 +40,9 @@ def parameterized_component(x, y): total = x + y return html._( html.div( - {"id": "parametrized-component", "data-value": total}, f"parameterized_component: {total}", + id="parametrized-component", + data_value=total, ), html.hr(), ) @@ -54,14 +53,7 @@ def object_in_templatetag(my_object: TestObject): success = bool(my_object and my_object.value) co_name = inspect.currentframe().f_code.co_name # type: ignore return html._( - html.div( - { - "id": co_name, - "data-success": success, - }, - f"{co_name}: ", - str(my_object), - ), + html.div(f"{co_name}: ", str(my_object), id=co_name, data_success=success), html.hr(), ) @@ -79,7 +71,7 @@ def object_in_templatetag(my_object: TestObject): def simple_button(): return html._( "simple_button:", - SimpleButton({"id": "simple-button"}), + SimpleButton(id="simple-button"), html.hr(), ) @@ -95,9 +87,7 @@ def use_connection(): and getattr(ws.carrier, "dotted_path", None) ) return html.div( - {"id": "use-connection", "data-success": success}, - f"use_connection: {ws}", - html.hr(), + f"use_connection: {ws}", html.hr(), id="use-connection", data_success=success ) @@ -106,9 +96,7 @@ def use_scope(): scope = django_idom.hooks.use_scope() success = len(scope) >= 10 and scope["type"] == "websocket" return html.div( - {"id": "use-scope", "data-success": success}, - f"use_scope: {scope}", - html.hr(), + f"use_scope: {scope}", html.hr(), id="use-scope", data_success=success ) @@ -117,9 +105,7 @@ def use_location(): location = django_idom.hooks.use_location() success = bool(location) return html.div( - {"id": "use-location", "data-success": success}, - f"use_location: {location}", - html.hr(), + f"use_location: {location}", html.hr(), id="use-location", data_success=success ) @@ -128,20 +114,18 @@ def use_origin(): origin = django_idom.hooks.use_origin() success = bool(origin) return html.div( - {"id": "use-origin", "data-success": success}, - f"use_origin: {origin}", - html.hr(), + f"use_origin: {origin}", html.hr(), id="use-origin", data_success=success ) @component def django_css(): return html.div( - {"id": "django-css"}, django_idom.components.django_css("django-css-test.css", key="test"), - html.div({"style": {"display": "inline"}}, "django_css: "), + html.div("django_css: ", style={"display": "inline"}), html.button("This text should be blue."), html.hr(), + id="django-css", ) @@ -150,9 +134,10 @@ def django_js(): success = False return html._( html.div( - {"id": "django-js", "data-success": success}, f"django_js: {success}", django_idom.components.django_js("django-js-test.js", key="test"), + id="django-js", + data_success=success, ), html.hr(), ) @@ -161,34 +146,22 @@ def django_js(): @component @django_idom.decorators.auth_required( fallback=html.div( - {"id": "unauthorized-user-fallback"}, - "unauthorized_user: Success", - html.hr(), + "unauthorized_user: Success", html.hr(), id="unauthorized-user-fallback" ) ) def unauthorized_user(): - return html.div( - {"id": "unauthorized-user"}, - "unauthorized_user: Fail", - html.hr(), - ) + return html.div("unauthorized_user: Fail", html.hr(), id="unauthorized-user") @component @django_idom.decorators.auth_required( auth_attribute="is_anonymous", fallback=html.div( - {"id": "authorized-user-fallback"}, - "authorized_user: Fail", - html.hr(), + "authorized_user: Fail", html.hr(), id="authorized-user-fallback" ), ) def authorized_user(): - return html.div( - {"id": "authorized-user"}, - "authorized_user: Success", - html.hr(), - ) + return html.div("authorized_user: Success", html.hr(), id="authorized-user") def create_relational_parent() -> RelationalParent: @@ -231,15 +204,13 @@ def relational_query(): fk = foriegn_child.data.parent return html.div( - { - "id": "relational-query", - "data-success": bool(mtm) and bool(oto) and bool(mto) and bool(fk), - }, html.div(f"Relational Parent Many To Many: {mtm}"), html.div(f"Relational Parent One To One: {oto}"), html.div(f"Relational Parent Many to One: {mto}"), html.div(f"Relational Child Foreign Key: {fk}"), html.hr(), + id="relational-query", + data_success=bool(mtm) and bool(oto) and bool(mto) and bool(fk), ) @@ -302,13 +273,11 @@ def on_change(event): return html.div( html.label("Add an item:"), html.input( - { - "type": "text", - "id": "todo-input", - "value": input_value, - "onKeyPress": on_submit, - "onChange": on_change, - } + type="text", + id="todo-input", + value=input_value, + on_key_press=on_submit, + on_change=on_change, ), mutation_status, rendered_items, @@ -320,17 +289,15 @@ def _render_todo_items(items, toggle_item): return html.ul( [ html.li( - {"id": f"todo-item-{item.text}"}, item.text, html.input( - { - "id": f"todo-item-{item.text}-checkbox", - "type": "checkbox", - "checked": item.done, - "onChange": lambda event, i=item: toggle_item.execute(i), - } + id=f"todo-item-{item.text}-checkbox", + type="checkbox", + checked=item.done, + on_change=lambda event, i=item: toggle_item.execute(i), ), key=item.text, + id=f"todo-item-{item.text}", ) for item in items ] @@ -368,45 +335,45 @@ def _render_todo_items(items, toggle_item): @component def view_to_component_sync_func_compatibility(): return html.div( - {"id": inspect.currentframe().f_code.co_name}, # type: ignore _view_to_component_sync_func_compatibility(key="test"), html.hr(), + id=inspect.currentframe().f_code.co_name, # type: ignore ) @component def view_to_component_async_func_compatibility(): return html.div( - {"id": inspect.currentframe().f_code.co_name}, # type: ignore _view_to_component_async_func_compatibility(), html.hr(), + id=inspect.currentframe().f_code.co_name, # type: ignore ) @component def view_to_component_sync_class_compatibility(): return html.div( - {"id": inspect.currentframe().f_code.co_name}, # type: ignore _view_to_component_sync_class_compatibility(), html.hr(), + id=inspect.currentframe().f_code.co_name, # type: ignore ) @component def view_to_component_async_class_compatibility(): return html.div( - {"id": inspect.currentframe().f_code.co_name}, # type: ignore _view_to_component_async_class_compatibility(), html.hr(), + id=inspect.currentframe().f_code.co_name, # type: ignore ) @component def view_to_component_template_view_class_compatibility(): return html.div( - {"id": inspect.currentframe().f_code.co_name}, # type: ignore _view_to_component_template_view_class_compatibility(), html.hr(), + id=inspect.currentframe().f_code.co_name, # type: ignore ) @@ -421,8 +388,9 @@ def on_click(_): return html._( html.button( - {"id": f"{inspect.currentframe().f_code.co_name}_btn", "onClick": on_click}, # type: ignore "Click me", + id=f"{inspect.currentframe().f_code.co_name}_btn", # type: ignore + on_click=on_click, ), _view_to_component_request(request=request), ) @@ -437,8 +405,9 @@ def on_click(_): return html._( html.button( - {"id": f"{inspect.currentframe().f_code.co_name}_btn", "onClick": on_click}, # type: ignore "Click me", + id=f"{inspect.currentframe().f_code.co_name}_btn", # type: ignore + on_click=on_click, ), _view_to_component_args(None, success), ) @@ -453,8 +422,9 @@ def on_click(_): return html._( html.button( - {"id": f"{inspect.currentframe().f_code.co_name}_btn", "onClick": on_click}, # type: ignore "Click me", + id=f"{inspect.currentframe().f_code.co_name}_btn", # type: ignore + on_click=on_click, ), _view_to_component_kwargs(success=success), )