diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..170d7ddb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +end_of_line = lf + +[*.py] +indent_size = 4 +max_line_length = 120 + +[*.yml] +indent_size = 4 + +[*.md] +indent_size = 4 + +[*.html] +indent_size = 4 +max_line_length = off + +[*.js] +max_line_length = off + +[*.css] +indent_size = 4 +max_line_length = off + +# Tests can violate line width restrictions in the interest of clarity. +[**/test_*.py] +max_line_length = off diff --git a/.github/workflows/publish-develop-docs.yml b/.github/workflows/publish-develop-docs.yml index b79d3cd2..53c5aa16 100644 --- a/.github/workflows/publish-develop-docs.yml +++ b/.github/workflows/publish-develop-docs.yml @@ -11,6 +11,9 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - uses: actions/setup-python@v5 with: python-version: 3.x @@ -21,3 +24,5 @@ jobs: git config user.email github-actions@github.com cd docs mike deploy --push develop + concurrency: + group: publish-docs diff --git a/.github/workflows/publish-py.yml b/.github/workflows/publish-py.yml index 72a04dae..6a86db98 100644 --- a/.github/workflows/publish-py.yml +++ b/.github/workflows/publish-py.yml @@ -12,6 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/publish-release-docs.yml b/.github/workflows/publish-release-docs.yml index a98e9869..93df3e2a 100644 --- a/.github/workflows/publish-release-docs.yml +++ b/.github/workflows/publish-release-docs.yml @@ -11,6 +11,9 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - uses: actions/setup-python@v5 with: python-version: 3.x @@ -21,3 +24,5 @@ jobs: git config user.email github-actions@github.com cd docs mike deploy --push --update-aliases ${{ github.event.release.name }} latest + concurrency: + group: publish-docs diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 907e1a2c..66a5c942 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -17,15 +17,21 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - uses: actions/setup-python@v5 with: python-version: 3.x + # - name: Check docs links + # uses: umbrelladocs/action-linkspector@v1 + # with: + # github_token: ${{ secrets.github_token }} + # reporter: github-pr-review + # fail_on_error: false - name: Check docs build run: | pip install -r requirements/build-docs.txt - linkcheckMarkdown docs/ -v -r - linkcheckMarkdown README.md -v -r - linkcheckMarkdown CHANGELOG.md -v -r cd docs mkdocs build --strict - name: Check docs examples diff --git a/.github/workflows/test-src.yml b/.github/workflows/test-src.yml index 328bd1c3..5eb2e67a 100644 --- a/.github/workflows/test-src.yml +++ b/.github/workflows/test-src.yml @@ -18,6 +18,9 @@ jobs: python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - name: Use Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: diff --git a/.gitignore b/.gitignore index ffabb7fc..e3cbc599 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,7 @@ venv.bak/ # mkdocs documentation /site +docs/site # mypy .mypy_cache/ diff --git a/.linkspector.yml b/.linkspector.yml new file mode 100644 index 00000000..6c0747e7 --- /dev/null +++ b/.linkspector.yml @@ -0,0 +1,7 @@ +dirs: + - ./docs +files: + - README.md + - CHANGELOG.md +useGitIgnore: true +modifiedFilesOnly: false diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..32ad81f3 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "proseWrap": "never", + "trailingComma": "all" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 620c1f75..5342e003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,9 @@ Using the following categories, list your changes in this order: ### Security - for vulnerability fixes. - --> + +Don't forget to remove deprecated code on each major release! +--> @@ -36,7 +38,23 @@ Using the following categories, list your changes in this order: - Nothing (yet)! -## [4.0.0] +## [5.0.0] - 2024-10-22 + +### Changed + +- Now using ReactPy-Router v1 for URL routing, which comes with a slightly different API than before. +- Removed dependency on `aiofile`. + +### Removed + +- Removed the following **deprecated** features: + - The `compatibility` argument on `reactpy_django.components.view_to_component` + - `reactpy_django.components.view_to_component` **usage as a decorator** + - `reactpy_django.decorators.auth_required` + - `reactpy_django.REACTPY_WEBSOCKET_PATH` + - `settings.py:REACTPY_WEBSOCKET_URL` + +## [4.0.0] - 2024-06-22 ### Added @@ -110,8 +128,8 @@ Using the following categories, list your changes in this order: - New Django `User` related features! - `reactpy_django.hooks.use_user` can be used to access the current user. - `reactpy_django.hooks.use_user_data` provides a simplified interface for storing user key-value data. - - `reactpy_django.decorators.user_passes_test` is inspired by the [equivalent Django decorator](http://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.decorators.user_passes_test), but ours works with ReactPy components. - - `settings.py:REACTPY_AUTO_RELOGIN` will cause component WebSocket connections to automatically [re-login](https://channels.readthedocs.io/en/latest/topics/authentication.html#how-to-log-a-user-in-out) users that are already authenticated. This is useful to continuously update `last_login` timestamps and refresh the [Django login session](https://docs.djangoproject.com/en/dev/topics/http/sessions/). + - `reactpy_django.decorators.user_passes_test` is inspired by the [equivalent Django decorator](http://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.decorators.user_passes_test), but ours works with ReactPy components. + - `settings.py:REACTPY_AUTO_RELOGIN` will cause component WebSocket connections to automatically [re-login](https://channels.readthedocs.io/en/latest/topics/authentication.html#how-to-log-a-user-in-out) users that are already authenticated. This is useful to continuously update `last_login` timestamps and refresh the [Django login session](https://docs.djangoproject.com/en/stable/topics/http/sessions/). ### Changed @@ -501,7 +519,8 @@ Using the following categories, list your changes in this order: - Support for IDOM within the Django -[Unreleased]: https://github.com/reactive-python/reactpy-django/compare/4.0.0...HEAD +[Unreleased]: https://github.com/reactive-python/reactpy-django/compare/5.0.0...HEAD +[5.0.0]: https://github.com/reactive-python/reactpy-django/compare/4.0.0...5.0.0 [4.0.0]: https://github.com/reactive-python/reactpy-django/compare/3.8.1...4.0.0 [3.8.1]: https://github.com/reactive-python/reactpy-django/compare/3.8.0...3.8.1 [3.8.0]: https://github.com/reactive-python/reactpy-django/compare/3.7.0...3.8.0 diff --git a/README.md b/README.md index 817e684b..d3d2a1a9 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ def hello_world(recipient: str): -## [`my_app/templates/my_template.html`](https://docs.djangoproject.com/en/dev/topics/templates/) +## [`my_app/templates/my_template.html`](https://docs.djangoproject.com/en/stable/topics/templates/) diff --git a/docs/examples/html/pyscript-js-module.html b/docs/examples/html/pyscript-local-import.html similarity index 100% rename from docs/examples/html/pyscript-js-module.html rename to docs/examples/html/pyscript-local-import.html diff --git a/docs/examples/html/pyscript-setup-local-interpreter.html b/docs/examples/html/pyscript-setup-local-interpreter.html new file mode 100644 index 00000000..8371fa94 --- /dev/null +++ b/docs/examples/html/pyscript-setup-local-interpreter.html @@ -0,0 +1 @@ +{% pyscript_setup config='{"interpreter":"/static/pyodide/pyodide.mjs"}' %} diff --git a/docs/examples/python/pyscript-js-execution.py b/docs/examples/python/pyodide-js-module.py similarity index 100% rename from docs/examples/python/pyscript-js-execution.py rename to docs/examples/python/pyodide-js-module.py diff --git a/docs/examples/python/pyscript-js-module.py b/docs/examples/python/pyscript-local-import.py similarity index 100% rename from docs/examples/python/pyscript-js-module.py rename to docs/examples/python/pyscript-local-import.py diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e4159640..c1b5922f 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -89,8 +89,6 @@ plugins: - spellcheck: known_words: dictionary.txt allow_unicode: no - ignore_code: yes - # - section-index extra: generator: false diff --git a/docs/overrides/home-code-examples/add-interactivity.py b/docs/overrides/home-code-examples/add-interactivity.py index 90976446..f29ba3c8 100644 --- a/docs/overrides/home-code-examples/add-interactivity.py +++ b/docs/overrides/home-code-examples/add-interactivity.py @@ -1,16 +1,14 @@ +# pylint: disable=assignment-from-no-return, unnecessary-lambda from reactpy import component, html, use_state -def filter_videos(videos, search_text): - return None +def filter_videos(*_, **__): ... -def search_input(dictionary, value): - return None +def search_input(*_, **__): ... -def video_list(videos, empty_heading): - return None +def video_list(*_, **__): ... @component @@ -20,7 +18,7 @@ def searchable_video_list(videos): return html._( search_input( - {"on_change": lambda new_text: set_search_text(new_text)}, + {"onChange": lambda new_text: set_search_text(new_text)}, value=search_text, ), video_list( diff --git a/docs/overrides/home-code-examples/create-user-interfaces.py b/docs/overrides/home-code-examples/create-user-interfaces.py index 37776ab1..873b9d88 100644 --- a/docs/overrides/home-code-examples/create-user-interfaces.py +++ b/docs/overrides/home-code-examples/create-user-interfaces.py @@ -1,22 +1,20 @@ from reactpy import component, html -def thumbnail(video): - return None +def thumbnail(*_, **__): ... -def like_button(video): - return None +def like_button(*_, **__): ... @component -def video(video): +def video(data): return html.div( - thumbnail(video), + thumbnail(data), html.a( - {"href": video.url}, - html.h3(video.title), - html.p(video.description), + {"href": data.url}, + html.h3(data.title), + html.p(data.description), ), - like_button(video), + like_button(data), ) diff --git a/docs/overrides/home-code-examples/write-components-with-python.py b/docs/overrides/home-code-examples/write-components-with-python.py index 6af43baa..47e28b68 100644 --- a/docs/overrides/home-code-examples/write-components-with-python.py +++ b/docs/overrides/home-code-examples/write-components-with-python.py @@ -1,6 +1,9 @@ from reactpy import component, html +def video(*_, **__): ... + + @component def video_list(videos, empty_heading): count = len(videos) @@ -11,5 +14,5 @@ def video_list(videos, empty_heading): return html.section( html.h2(heading), - [video(video) for video in videos], + [video(x, key=x.id) for x in videos], ) diff --git a/docs/src/about/code.md b/docs/src/about/code.md index 205c2c96..81e49c51 100644 --- a/docs/src/about/code.md +++ b/docs/src/about/code.md @@ -19,6 +19,7 @@ If you plan to make code changes to this repository, you will need to install the following dependencies first: - [Python 3.9+](https://www.python.org/downloads/) +- [Bun](https://bun.sh/) - [Git](https://git-scm.com/downloads) Once done, you should clone this repository: diff --git a/docs/src/assets/css/home.css b/docs/src/assets/css/home.css index c72e7093..70f05cf2 100644 --- a/docs/src/assets/css/home.css +++ b/docs/src/assets/css/home.css @@ -1,335 +1,337 @@ img.home-logo { - height: 120px; + height: 120px; } .home .row { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - padding: 6rem 0.8rem; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + padding: 6rem 0.8rem; } .home .row:not(.first, .stripe) { - background: var(--row-bg-color); + background: var(--row-bg-color); } .home .row.stripe { - background: var(--row-stripe-bg-color); - border: 0 solid var(--stripe-border-color); - border-top-width: 1px; - border-bottom-width: 1px; + background: var(--row-stripe-bg-color); + border: 0 solid var(--stripe-border-color); + border-top-width: 1px; + border-bottom-width: 1px; } .home .row.first { - text-align: center; + text-align: center; } .home .row h1 { - max-width: 28rem; - line-height: 1.15; - font-weight: 500; - margin-bottom: 0.55rem; - margin-top: -1rem; + max-width: 28rem; + line-height: 1.15; + font-weight: 500; + margin-bottom: 0.55rem; + margin-top: -1rem; } .home .row.first h1 { - margin-top: 0.55rem; - margin-bottom: -0.75rem; + margin-top: 0.55rem; + margin-bottom: -0.75rem; } .home .row > p { - max-width: 35rem; - line-height: 1.5; - font-weight: 400; + max-width: 35rem; + line-height: 1.5; + font-weight: 400; } .home .row.first > p { - font-size: 32px; - font-weight: 500; + font-size: 32px; + font-weight: 500; } /* Code blocks */ .home .row .tabbed-set { - background: var(--home-tabbed-set-bg-color); - margin: 0; + background: var(--home-tabbed-set-bg-color); + margin: 0; } .home .row .tabbed-content { - padding: 20px 18px; - overflow-x: auto; + padding: 20px 18px; + overflow-x: auto; } .home .row .tabbed-content img { - user-select: none; - -moz-user-select: none; - -webkit-user-drag: none; - -webkit-user-select: none; - -ms-user-select: none; - max-width: 580px; + user-select: none; + -moz-user-select: none; + -webkit-user-drag: none; + -webkit-user-select: none; + -ms-user-select: none; + max-width: 580px; } .home .row .tabbed-content { - -webkit-filter: var(--code-block-filter); - filter: var(--code-block-filter); + -webkit-filter: var(--code-block-filter); + filter: var(--code-block-filter); } /* Code examples */ .home .example-container { - background: radial-gradient( - circle at 0% 100%, - rgb(41 84 147 / 11%) 0%, - rgb(22 89 189 / 4%) 70%, - rgb(48 99 175 / 0%) 80% - ), - radial-gradient( - circle at 100% 100%, - rgb(24 87 45 / 55%) 0%, - rgb(29 61 12 / 4%) 70%, - rgb(94 116 93 / 0%) 80% - ), - radial-gradient( - circle at 100% 0%, - rgba(54, 66, 84, 0.55) 0%, - rgb(102 111 125 / 4%) 70%, - rgba(54, 66, 84, 0) 80% - ), - radial-gradient( - circle at 0% 0%, - rgba(91, 114, 135, 0.55) 0%, - rgb(45 111 171 / 4%) 70%, - rgb(5 82 153 / 0%) 80% - ), - rgb(0, 0, 0) center center/cover no-repeat fixed; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - align-items: center; - border-radius: 16px; - margin: 30px 0; - max-width: 100%; - grid-column-gap: 20px; - padding-left: 20px; - padding-right: 20px; + background: radial-gradient( + circle at 0% 100%, + rgb(41 84 147 / 11%) 0%, + rgb(22 89 189 / 4%) 70%, + rgb(48 99 175 / 0%) 80% + ), + radial-gradient( + circle at 100% 100%, + rgb(24 87 45 / 55%) 0%, + rgb(29 61 12 / 4%) 70%, + rgb(94 116 93 / 0%) 80% + ), + radial-gradient( + circle at 100% 0%, + rgba(54, 66, 84, 0.55) 0%, + rgb(102 111 125 / 4%) 70%, + rgba(54, 66, 84, 0) 80% + ), + radial-gradient( + circle at 0% 0%, + rgba(91, 114, 135, 0.55) 0%, + rgb(45 111 171 / 4%) 70%, + rgb(5 82 153 / 0%) 80% + ), + rgb(0, 0, 0) center center/cover no-repeat fixed; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: center; + border-radius: 16px; + margin: 30px 0; + max-width: 100%; + grid-column-gap: 20px; + padding-left: 20px; + padding-right: 20px; } .home .demo .white-bg { - background: #fff; - border-radius: 16px; - display: flex; - flex-direction: column; - max-width: 590px; - min-width: -webkit-min-content; - min-width: -moz-min-content; - min-width: min-content; - row-gap: 1rem; - padding: 1rem; + background: #fff; + border-radius: 16px; + display: flex; + flex-direction: column; + max-width: 590px; + min-width: -webkit-min-content; + min-width: -moz-min-content; + min-width: min-content; + row-gap: 1rem; + padding: 1rem; + border: 1px rgb(0 0 0 / 20%) solid; + overflow: hidden; } .home .demo .vid-row { - display: flex; - flex-direction: row; - -moz-column-gap: 12px; - column-gap: 12px; + display: flex; + flex-direction: row; + -moz-column-gap: 12px; + column-gap: 12px; } .home .demo { - color: #000; + color: #000; } .home .demo .vid-thumbnail { - background: radial-gradient( - circle at 0% 100%, - rgb(41 84 147 / 55%) 0%, - rgb(22 89 189 / 4%) 70%, - rgb(48 99 175 / 0%) 80% - ), - radial-gradient( - circle at 100% 100%, - rgb(24 63 87 / 55%) 0%, - rgb(29 61 12 / 4%) 70%, - rgb(94 116 93 / 0%) 80% - ), - radial-gradient( - circle at 100% 0%, - rgba(54, 66, 84, 0.55) 0%, - rgb(102 111 125 / 4%) 70%, - rgba(54, 66, 84, 0) 80% - ), - radial-gradient( - circle at 0% 0%, - rgba(91, 114, 135, 0.55) 0%, - rgb(45 111 171 / 4%) 70%, - rgb(5 82 153 / 0%) 80% - ), - rgb(0, 0, 0) center center/cover no-repeat fixed; - width: 9rem; - aspect-ratio: 16 / 9; - border-radius: 8px; - display: flex; - justify-content: center; - align-items: center; + background: radial-gradient( + circle at 0% 100%, + rgb(41 84 147 / 55%) 0%, + rgb(22 89 189 / 4%) 70%, + rgb(48 99 175 / 0%) 80% + ), + radial-gradient( + circle at 100% 100%, + rgb(24 63 87 / 55%) 0%, + rgb(29 61 12 / 4%) 70%, + rgb(94 116 93 / 0%) 80% + ), + radial-gradient( + circle at 100% 0%, + rgba(54, 66, 84, 0.55) 0%, + rgb(102 111 125 / 4%) 70%, + rgba(54, 66, 84, 0) 80% + ), + radial-gradient( + circle at 0% 0%, + rgba(91, 114, 135, 0.55) 0%, + rgb(45 111 171 / 4%) 70%, + rgb(5 82 153 / 0%) 80% + ), + rgb(0, 0, 0) center center/cover no-repeat fixed; + width: 9rem; + aspect-ratio: 16 / 9; + border-radius: 8px; + display: flex; + justify-content: center; + align-items: center; } .home .demo .vid-text { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + width: 100%; } .home .demo h2 { - font-size: 18px; - line-height: 1.375; - margin: 0; - text-align: left; - font-weight: 700; + font-size: 18px; + line-height: 1.375; + margin: 0; + text-align: left; + font-weight: 700; } .home .demo h3 { - font-size: 16px; - line-height: 1.25; - margin: 0; + font-size: 16px; + line-height: 1.25; + margin: 0; } .home .demo p { - font-size: 14px; - line-height: 1.375; - margin: 0; + font-size: 14px; + line-height: 1.375; + margin: 0; } .home .demo .browser-nav-url { - background: rgba(153, 161, 179, 0.2); - border-radius: 9999px; - font-size: 14px; - color: grey; - display: flex; - align-items: center; - justify-content: center; - -moz-column-gap: 5px; - column-gap: 5px; + background: rgba(153, 161, 179, 0.2); + border-radius: 9999px; + font-size: 14px; + color: grey; + display: flex; + align-items: center; + justify-content: center; + -moz-column-gap: 5px; + column-gap: 5px; } .home .demo .browser-navbar { - margin: -1rem; - margin-bottom: 0; - padding: 0.75rem 1rem; - border-bottom: 1px solid darkgrey; + margin: -1rem; + margin-bottom: 0; + padding: 0.75rem 1rem; + border-bottom: 1px solid darkgrey; } .home .demo .browser-viewport { - background: #fff; - border-radius: 16px; - display: flex; - flex-direction: column; - row-gap: 1rem; - height: 400px; - overflow-y: scroll; - margin: -1rem; - padding: 1rem; + background: #fff; + border-radius: 16px; + display: flex; + flex-direction: column; + row-gap: 1rem; + height: 400px; + overflow-y: scroll; + margin: -1rem; + padding: 1rem; } .home .demo .browser-viewport .search-header > h1 { - color: #000; - text-align: left; - font-size: 24px; - margin: 0; + color: #000; + text-align: left; + font-size: 24px; + margin: 0; } .home .demo .browser-viewport .search-header > p { - text-align: left; - font-size: 16px; - margin: 10px 0; + text-align: left; + font-size: 16px; + margin: 10px 0; } .home .demo .search-bar input { - width: 100%; - background: rgba(153, 161, 179, 0.2); - border-radius: 9999px; - padding-left: 40px; - padding-right: 40px; - height: 40px; - color: #000; + width: 100%; + background: rgba(153, 161, 179, 0.2); + border-radius: 9999px; + padding-left: 40px; + padding-right: 40px; + height: 40px; + color: #000; } .home .demo .search-bar svg { - height: 40px; - position: absolute; - transform: translateX(75%); + height: 40px; + position: absolute; + transform: translateX(75%); } .home .demo .search-bar { - position: relative; + position: relative; } /* Desktop Styling */ @media screen and (min-width: 60em) { - .home .row { - text-align: center; - } - .home .row > p { - font-size: 21px; - } - .home .row > h1 { - font-size: 52px; - } - .home .row .pop-left { - margin-left: -20px; - margin-right: 0; - margin-top: -20px; - margin-bottom: -20px; - } - .home .row .pop-right { - margin-left: 0px; - margin-right: 0px; - margin-top: -20px; - margin-bottom: -20px; - } + .home .row { + text-align: center; + } + .home .row > p { + font-size: 21px; + } + .home .row > h1 { + font-size: 52px; + } + .home .row .pop-left { + margin-left: -20px; + margin-right: 0; + margin-top: -20px; + margin-bottom: -20px; + } + .home .row .pop-right { + margin-left: 0px; + margin-right: 0px; + margin-top: -20px; + margin-bottom: -20px; + } } /* Mobile Styling */ @media screen and (max-width: 60em) { - .home .row { - padding: 4rem 0.8rem; - } - .home .row > h1, - .home .row > p { - padding-left: 1rem; - padding-right: 1rem; - } - .home .row.first { - padding-top: 2rem; - } - .home-btns { - width: 100%; - display: grid; - grid-gap: 0.5rem; - gap: 0.5rem; - } - .home .example-container { - display: flex; - flex-direction: column; - row-gap: 20px; - width: 100%; - justify-content: center; - border-radius: 0; - padding: 1rem 0; - } - .home .row { - padding-left: 0; - padding-right: 0; - } - .home .tabbed-set { - width: 100%; - border-radius: 0; - } - .home .demo { - width: 100%; - display: flex; - justify-content: center; - } - .home .demo > .white-bg { - width: 80%; - max-width: 80%; - } + .home .row { + padding: 4rem 0.8rem; + } + .home .row > h1, + .home .row > p { + padding-left: 1rem; + padding-right: 1rem; + } + .home .row.first { + padding-top: 2rem; + } + .home-btns { + width: 100%; + display: grid; + grid-gap: 0.5rem; + gap: 0.5rem; + } + .home .example-container { + display: flex; + flex-direction: column; + row-gap: 20px; + width: 100%; + justify-content: center; + border-radius: 0; + padding: 1rem 0; + } + .home .row { + padding-left: 0; + padding-right: 0; + } + .home .tabbed-set { + width: 100%; + border-radius: 0; + } + .home .demo { + width: 100%; + display: flex; + justify-content: center; + } + .home .demo > .white-bg { + width: 80%; + max-width: 80%; + } } diff --git a/docs/src/assets/img/add-interactivity.png b/docs/src/assets/img/add-interactivity.png index e5e24d29..009b52ac 100644 Binary files a/docs/src/assets/img/add-interactivity.png and b/docs/src/assets/img/add-interactivity.png differ diff --git a/docs/src/assets/img/create-user-interfaces.png b/docs/src/assets/img/create-user-interfaces.png index 13abd064..06f6ea0c 100644 Binary files a/docs/src/assets/img/create-user-interfaces.png and b/docs/src/assets/img/create-user-interfaces.png differ diff --git a/docs/src/assets/img/write-components-with-python.png b/docs/src/assets/img/write-components-with-python.png index ba34cdf9..380d2c3a 100644 Binary files a/docs/src/assets/img/write-components-with-python.png and b/docs/src/assets/img/write-components-with-python.png differ diff --git a/docs/src/dictionary.txt b/docs/src/dictionary.txt index 66265e78..14aa7a61 100644 --- a/docs/src/dictionary.txt +++ b/docs/src/dictionary.txt @@ -1,43 +1,45 @@ +asgi +async +backend +backends +backhaul +broadcasted +changelog django -sanic -plotly +frontend +frontends +hello_world +html +iframe +jupyter +keyworded +middleware +misconfiguration +misconfigurations +my_template nox -WebSocket -WebSockets -changelog -async +plotly +postfixed +postprocessing +postprocessor pre prefetch prefetching preloader -whitespace +preprocessor +py +pyodide +pyscript +reactpy refetch refetched refetching -html -jupyter -iframe -keyworded +sanic +serializable stylesheet stylesheets -unstyled -py -reactpy -asgi -postfixed -postprocessing -serializable -postprocessor -preprocessor -middleware -backends -backend -frontend -frontends -misconfiguration -misconfigurations -backhaul sublicense -broadcasted -hello_world -my_template +unstyled +WebSocket +WebSockets +whitespace diff --git a/docs/src/learn/add-reactpy-to-a-django-project.md b/docs/src/learn/add-reactpy-to-a-django-project.md index dd258737..0bf919e2 100644 --- a/docs/src/learn/add-reactpy-to-a-django-project.md +++ b/docs/src/learn/add-reactpy-to-a-django-project.md @@ -8,7 +8,7 @@ If you want to add some interactivity to your existing **Django project**, you d !!! abstract "Note" - 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**. + These docs assumes you have already created [a **Django project**](https://docs.djangoproject.com/en/stable/intro/tutorial01/), which involves creating and installing at least one **Django app**. If do not have a **Django project**, check out this [9 minute YouTube tutorial](https://www.youtube.com/watch?v=ZsJRXS_vrw0) created by _IDG TECHtalk_. @@ -24,7 +24,7 @@ pip install reactpy-django ## Step 2: Configure `settings.py` -Add `#!python "reactpy_django"` to [`INSTALLED_APPS`](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS) in your [`settings.py`](https://docs.djangoproject.com/en/dev/topics/settings/) file. +Add `#!python "reactpy_django"` to [`INSTALLED_APPS`](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-INSTALLED_APPS) in your [`settings.py`](https://docs.djangoproject.com/en/stable/topics/settings/) file. === "settings.py" @@ -36,7 +36,7 @@ Add `#!python "reactpy_django"` to [`INSTALLED_APPS`](https://docs.djangoproject ReactPy-Django requires Django ASGI and [Django Channels](https://github.com/django/channels) WebSockets. - If you have not enabled ASGI on your **Django project** yet, here is a summary of the [`django`](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/) and [`channels`](https://channels.readthedocs.io/en/stable/installation.html) installation docs: + If you have not enabled ASGI on your **Django project** yet, here is a summary of the [`django`](https://docs.djangoproject.com/en/stable/howto/deployment/asgi/) and [`channels`](https://channels.readthedocs.io/en/stable/installation.html) installation docs: 1. Install `channels[daphne]` 2. Add `#!python "daphne"` to `#!python INSTALLED_APPS`. @@ -59,7 +59,7 @@ Add `#!python "reactpy_django"` to [`INSTALLED_APPS`](https://docs.djangoproject ## Step 3: Configure `urls.py` -Add ReactPy HTTP paths to your `#!python urlpatterns` in your [`urls.py`](https://docs.djangoproject.com/en/dev/topics/http/urls/) file. +Add ReactPy HTTP paths to your `#!python urlpatterns` in your [`urls.py`](https://docs.djangoproject.com/en/stable/topics/http/urls/) file. === "urls.py" @@ -69,7 +69,7 @@ Add ReactPy HTTP paths to your `#!python urlpatterns` in your [`urls.py`](https: ## Step 4: Configure `asgi.py` -Register ReactPy's WebSocket using `#!python REACTPY_WEBSOCKET_ROUTE` in your [`asgi.py`](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/) file. +Register ReactPy's WebSocket using `#!python REACTPY_WEBSOCKET_ROUTE` in your [`asgi.py`](https://docs.djangoproject.com/en/stable/howto/deployment/asgi/) file. === "asgi.py" @@ -97,7 +97,7 @@ Register ReactPy's WebSocket using `#!python REACTPY_WEBSOCKET_ROUTE` in your [` ## Step 5: Run database migrations -Run Django's [`migrate` command](https://docs.djangoproject.com/en/dev/topics/migrations/) to initialize ReactPy-Django's database table. +Run Django's [`migrate` command](https://docs.djangoproject.com/en/stable/topics/migrations/) to initialize ReactPy-Django's database table. ```bash linenums="0" python manage.py migrate @@ -105,7 +105,7 @@ python manage.py migrate ## Step 6: Check your configuration -Run Django's [`check` command](https://docs.djangoproject.com/en/dev/ref/django-admin/#check) to verify if ReactPy was set up correctly. +Run Django's [`check` command](https://docs.djangoproject.com/en/stable/ref/django-admin/#check) to verify if ReactPy was set up correctly. ```bash linenums="0" python manage.py check @@ -113,7 +113,7 @@ python manage.py check ## Step 7: Create your first component -The [next step](./your-first-component.md) will show you how to create your first ReactPy component. +The [next page](./your-first-component.md) will show you how to create your first ReactPy component. Prefer a quick summary? Read the **At a Glance** section below. diff --git a/docs/src/learn/your-first-component.md b/docs/src/learn/your-first-component.md index 08df6a57..85af4109 100644 --- a/docs/src/learn/your-first-component.md +++ b/docs/src/learn/your-first-component.md @@ -18,7 +18,7 @@ You will now need to pick at least one **Django app** to start using ReactPy-Dja For the following examples, we will assume the following: -1. You have a **Django app** named `my_app`, which was created by Django's [`startapp` command](https://docs.djangoproject.com/en/dev/intro/tutorial01/#creating-the-polls-app). +1. You have a **Django app** named `my_app`, which was created by Django's [`startapp` command](https://docs.djangoproject.com/en/stable/intro/tutorial01/#creating-the-polls-app). 2. You have placed `my_app` directly into your **Django project** folder (`./example_project/my_app`). This is common for small projects. ??? question "How do I organize my Django project for ReactPy?" @@ -31,7 +31,7 @@ You will need a file to start creating ReactPy components. We recommend creating a `components.py` file within your chosen **Django app** to start out. For this example, the file path will look like this: `./example_project/my_app/components.py`. -Within this file, you can define your component functions using ReactPy's `#!python @component` decorator. +Within this file, you will define your component function(s) using the `#!python @component` decorator. === "components.py" @@ -43,7 +43,7 @@ Within this file, you can define your component functions using ReactPy's `#!pyt We recommend creating a `components.py` for small **Django apps**. If your app has a lot of components, you should consider breaking them apart into individual modules such as `components/navbar.py`. - Ultimately, components are referenced by Python dotted path in `my_template.html` ([_see next step_](#embedding-in-a-template)). This path must be valid to Python's `#!python importlib`. + Ultimately, components are referenced by Python dotted path in `my_template.html` ([_see next step_](#embedding-in-a-template)). This dotted path must be valid to Python's `#!python importlib`. ??? question "What does the decorator actually do?" @@ -66,17 +66,23 @@ Additionally, you can pass in `#!python args` and `#!python kwargs` into your co {% include-markdown "../../../README.md" start="" end="" %} +???+ tip "Components are automatically registered!" + + ReactPy-Django will automatically register any component that is referenced in a Django HTML template. This means you [typically](../reference/utils.md#register-component) do not need to manually register components in your **Django app**. + + Please note that this HTML template must be properly stored within a registered Django app. ReactPy-Django will output a console log message containing all detected components when the server starts up. + {% include-markdown "../reference/template-tag.md" start="" end="" %} {% include-markdown "../reference/template-tag.md" start="" end="" %} ??? question "Where is my templates folder?" - If you do not have a `./templates/` folder in your **Django app**, you can simply create one! Keep in mind, templates within this folder will not be detected by Django unless you [add the corresponding **Django app** to `settings.py:INSTALLED_APPS`](https://docs.djangoproject.com/en/dev/ref/applications/#configuring-applications). + If you do not have a `./templates/` folder in your **Django app**, you can simply create one! Keep in mind, templates within this folder will not be detected by Django unless you [add the corresponding **Django app** to `settings.py:INSTALLED_APPS`](https://docs.djangoproject.com/en/stable/ref/applications/#configuring-applications). ## Setting up a Django view -Within your **Django app**'s `views.py` file, you will need to [create a view function](https://docs.djangoproject.com/en/dev/intro/tutorial01/#write-your-first-view) to render the HTML template `my_template.html` ([_from the previous step_](#embedding-in-a-template)). +Within your **Django app**'s `views.py` file, you will need to [create a view function](https://docs.djangoproject.com/en/stable/intro/tutorial01/#write-your-first-view) to render the HTML template `my_template.html` ([_from the previous step_](#embedding-in-a-template)). === "views.py" @@ -84,7 +90,7 @@ Within your **Django app**'s `views.py` file, you will need to [create a view fu {% include "../../examples/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) and define what URL it should be accessible at. +We will add this new view into your [`urls.py`](https://docs.djangoproject.com/en/stable/intro/tutorial01/#write-your-first-view) and define what URL it should be accessible at. === "urls.py" @@ -98,7 +104,7 @@ We will add this new view into your [`urls.py`](https://docs.djangoproject.com/e Once you reach that point, we recommend creating an individual `urls.py` within each of your **Django apps**. - Then, within your **Django project's** `urls.py` you will use Django's [`include` function](https://docs.djangoproject.com/en/dev/ref/urls/#include) to link it all together. + Then, within your **Django project's** `urls.py` you will use Django's [`include` function](https://docs.djangoproject.com/en/stable/ref/urls/#include) to link it all together. ## Viewing your component @@ -114,7 +120,7 @@ If you copy-pasted our example component, you will now see your component displa ??? warning "Do not use `manage.py runserver` for production" - This command is only intended for development purposes. For production deployments make sure to read [Django's documentation](https://docs.djangoproject.com/en/dev/howto/deployment/). + This command is only intended for development purposes. For production deployments make sure to read [Django's documentation](https://docs.djangoproject.com/en/stable/howto/deployment/). ## Learn more diff --git a/docs/src/reference/components.md b/docs/src/reference/components.md index aaeabba7..943b76c0 100644 --- a/docs/src/reference/components.md +++ b/docs/src/reference/components.md @@ -14,6 +14,8 @@ This allows you to embedded any number of client-side PyScript components within {% include-markdown "../reference/template-tag.md" start="" end="" %} +{% include-markdown "../reference/template-tag.md" start="" end="" %} + === "components.py" ```python @@ -122,7 +124,7 @@ Automatically convert a Django view into a component. At this time, this works best with static views with no interactivity. -Compatible with sync or async [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/). +Compatible with sync or async [Function Based Views](https://docs.djangoproject.com/en/stable/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/stable/topics/class-based-views/). === "components.py" @@ -252,7 +254,7 @@ Automatically convert a Django view into an [`iframe` element](https://www.techt The contents of this `#!python iframe` is handled entirely by traditional Django view rendering. While this solution is compatible with more views than `#!python view_to_component`, it comes with different limitations. -Compatible with sync or async [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/). +Compatible with sync or async [Function Based Views](https://docs.djangoproject.com/en/stable/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/stable/topics/class-based-views/). === "components.py" @@ -381,7 +383,7 @@ Compatible with sync or async [Function Based Views](https://docs.djangoproject. ## Django CSS -Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/). +Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/stable/howto/static-files/). === "components.py" @@ -434,11 +436,11 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering. ## Django JS -Allows you to defer loading JavaScript until a component begins rendering. This JavaScript must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/). +Allows you to defer loading JavaScript until a component begins rendering. This JavaScript must be stored within [Django's static files](https://docs.djangoproject.com/en/stable/howto/static-files/). - - You can use the `#!python prerender` argument in your [template tag](../reference/template-tag.md#component) to manually override this default. --- diff --git a/docs/src/reference/template-tag.md b/docs/src/reference/template-tag.md index 434c81d0..091b2ac8 100644 --- a/docs/src/reference/template-tag.md +++ b/docs/src/reference/template-tag.md @@ -10,7 +10,7 @@ Django template tags can be used within your HTML templates to provide ReactPy f ## Component -This template tag can be used to insert any number of ReactPy components onto your page. +This template tag can be used to insert any number of **server-side** ReactPy components onto your page. Each component loaded via this template tag will receive a dedicated WebSocket connection to the server. @@ -78,7 +78,7 @@ Each component loaded via this template tag will receive a dedicated WebSocket c
{% component "example_project.my_app_2.components.goodbye_world" class="bold small-font" %}
- {% component "example_project.my_app_3.components.simple_button" %} + {% component "example_project.my_app_3.components.my_button" %}