From 02f9ffaccbf34630fa8bbf524c636148e3e88e94 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:28:40 -0800 Subject: [PATCH 01/29] Refactor the repo, and new docs styling --- .github/workflows/codeql.yml | 78 ++++ .github/workflows/publish-develop-docs.yml | 22 ++ .github/workflows/publish-docs.yaml | 21 -- .github/workflows/publish-py.yaml | 29 ++ .github/workflows/publish-release-docs.yml | 23 ++ .github/workflows/release.yaml | 30 -- .github/workflows/test-docs.yml | 36 ++ .github/workflows/test-src.yaml | 28 ++ .github/workflows/test.yaml | 39 -- .gitignore | 17 +- CHANGELOG.md | 66 ++++ LICENSE | 21 -- LICENSE.md | 9 + MANIFEST.in | 2 - docs/examples/python/__init__.py | 0 .../python/basic-routing-more-routes.py | 15 + docs/examples/python/basic-routing.py | 14 + docs/examples/python/nested-routes.py | 84 +++++ docs/examples/python/route-links.py | 23 ++ docs/examples/python/route-parameters.py | 83 +++++ docs/examples/python/use-params.py | 23 ++ docs/examples/python/use-query.py | 23 ++ docs/includes/pr.md | 3 + docs/mkdocs.yml | 176 ++++++--- .../add-interactivity-demo.html | 172 +++++++++ .../home-code-examples/add-interactivity.py | 30 ++ .../home-code-examples/code-block.html | 7 + .../create-user-interfaces-demo.html | 24 ++ .../create-user-interfaces.py | 22 ++ .../write-components-with-python-demo.html | 65 ++++ .../write-components-with-python.py | 15 + docs/overrides/home.html | 135 +++++++ docs/overrides/main.html | 20 ++ docs/src/about/changelog.md | 14 + docs/src/about/code.md | 55 +++ docs/src/about/docs.md | 45 +++ docs/src/about/license.md | 8 + docs/src/assets/css/admonition.css | 160 +++++++++ docs/src/assets/css/banner.css | 15 + docs/src/assets/css/button.css | 41 +++ docs/src/assets/css/code.css | 111 ++++++ docs/src/assets/css/footer.css | 33 ++ docs/src/assets/css/home.css | 335 ++++++++++++++++++ docs/src/assets/css/main.css | 85 +++++ docs/src/assets/css/navbar.css | 185 ++++++++++ docs/src/assets/css/sidebar.css | 104 ++++++ docs/src/assets/css/table-of-contents.css | 48 +++ docs/src/assets/js/main.js | 19 + docs/src/assets/logo.svg | 160 --------- docs/src/contributing.md | 70 ---- docs/src/dictionary.txt | 40 +++ docs/src/index.md | 22 +- .../add-reactpy-router-to-your-project.md | 11 + .../src/{tutorials => learn}/custom-router.md | 0 docs/src/learn/hooks.md | 27 ++ docs/src/learn/routers-routes-and-links.md | 67 ++++ docs/src/learn/simple-application.md | 88 +++++ docs/src/reference.md | 5 - docs/src/reference/core.md | 1 + docs/src/reference/router.md | 1 + docs/src/reference/types.md | 1 + docs/src/tutorials/simple-app.md | 277 --------------- docs/src/usage.md | 180 ---------- pyproject.toml | 25 +- requirements.txt | 3 +- requirements/build-docs.txt | 12 +- requirements/build-pkg.txt | 3 + requirements/check-style.txt | 5 +- requirements/test-env.txt | 1 + requirements/{nox-deps.txt => test-run.txt} | 0 setup.cfg | 19 - setup.py | 98 ++--- {js => src/js}/.eslintrc.json | 0 {js => src/js}/README.md | 0 {js => src/js}/package-lock.json | 0 {js => src/js}/package.json | 0 {js => src/js}/rollup.config.js | 0 {js => src/js}/src/index.js | 0 .../reactpy_router}/__init__.py | 0 .../reactpy_router}/core.py | 0 .../reactpy_router}/py.typed | 0 .../reactpy_router}/simple.py | 0 .../reactpy_router}/types.py | 0 83 files changed, 2758 insertions(+), 971 deletions(-) create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/publish-develop-docs.yml delete mode 100644 .github/workflows/publish-docs.yaml create mode 100644 .github/workflows/publish-py.yaml create mode 100644 .github/workflows/publish-release-docs.yml delete mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/test-docs.yml create mode 100644 .github/workflows/test-src.yaml delete mode 100644 .github/workflows/test.yaml create mode 100644 CHANGELOG.md delete mode 100644 LICENSE create mode 100644 LICENSE.md create mode 100644 docs/examples/python/__init__.py create mode 100644 docs/examples/python/basic-routing-more-routes.py create mode 100644 docs/examples/python/basic-routing.py create mode 100644 docs/examples/python/nested-routes.py create mode 100644 docs/examples/python/route-links.py create mode 100644 docs/examples/python/route-parameters.py create mode 100644 docs/examples/python/use-params.py create mode 100644 docs/examples/python/use-query.py create mode 100644 docs/includes/pr.md create mode 100644 docs/overrides/home-code-examples/add-interactivity-demo.html create mode 100644 docs/overrides/home-code-examples/add-interactivity.py create mode 100644 docs/overrides/home-code-examples/code-block.html create mode 100644 docs/overrides/home-code-examples/create-user-interfaces-demo.html create mode 100644 docs/overrides/home-code-examples/create-user-interfaces.py create mode 100644 docs/overrides/home-code-examples/write-components-with-python-demo.html create mode 100644 docs/overrides/home-code-examples/write-components-with-python.py create mode 100644 docs/overrides/home.html create mode 100644 docs/overrides/main.html create mode 100644 docs/src/about/changelog.md create mode 100644 docs/src/about/code.md create mode 100644 docs/src/about/docs.md create mode 100644 docs/src/about/license.md create mode 100644 docs/src/assets/css/admonition.css create mode 100644 docs/src/assets/css/banner.css create mode 100644 docs/src/assets/css/button.css create mode 100644 docs/src/assets/css/code.css create mode 100644 docs/src/assets/css/footer.css create mode 100644 docs/src/assets/css/home.css create mode 100644 docs/src/assets/css/main.css create mode 100644 docs/src/assets/css/navbar.css create mode 100644 docs/src/assets/css/sidebar.css create mode 100644 docs/src/assets/css/table-of-contents.css create mode 100644 docs/src/assets/js/main.js delete mode 100644 docs/src/assets/logo.svg delete mode 100644 docs/src/contributing.md create mode 100644 docs/src/dictionary.txt create mode 100644 docs/src/learn/add-reactpy-router-to-your-project.md rename docs/src/{tutorials => learn}/custom-router.md (100%) create mode 100644 docs/src/learn/hooks.md create mode 100644 docs/src/learn/routers-routes-and-links.md create mode 100644 docs/src/learn/simple-application.md delete mode 100644 docs/src/reference.md create mode 100644 docs/src/reference/core.md create mode 100644 docs/src/reference/router.md create mode 100644 docs/src/reference/types.md delete mode 100644 docs/src/tutorials/simple-app.md delete mode 100644 docs/src/usage.md create mode 100644 requirements/build-pkg.txt rename requirements/{nox-deps.txt => test-run.txt} (100%) rename {js => src/js}/.eslintrc.json (100%) rename {js => src/js}/README.md (100%) rename {js => src/js}/package-lock.json (100%) rename {js => src/js}/package.json (100%) rename {js => src/js}/rollup.config.js (100%) rename {js => src/js}/src/index.js (100%) rename {reactpy_router => src/reactpy_router}/__init__.py (100%) rename {reactpy_router => src/reactpy_router}/core.py (100%) rename {reactpy_router => src/reactpy_router}/py.typed (100%) rename {reactpy_router => src/reactpy_router}/simple.py (100%) rename {reactpy_router => src/reactpy_router}/types.py (100%) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..0f26793 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,78 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "main" ] + schedule: + # Runs at 22:21 on Monday. + - cron: '21 22 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/publish-develop-docs.yml b/.github/workflows/publish-develop-docs.yml new file mode 100644 index 0000000..6b1d4de --- /dev/null +++ b/.github/workflows/publish-develop-docs.yml @@ -0,0 +1,22 @@ +name: Publish Develop Docs +on: + push: + branches: + - main +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - run: pip install -r requirements/build-docs.txt + - name: Publish Develop Docs + run: | + git config user.name github-actions + git config user.email github-actions@github.com + cd docs + mike deploy --push develop diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml deleted file mode 100644 index 2d6aa80..0000000 --- a/.github/workflows/publish-docs.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: publish-docs - -on: - push: - branches: - - main - -jobs: - build: - name: Deploy docs - runs-on: ubuntu-latest - steps: - - name: Checkout main - uses: actions/checkout@v2 - - name: Deploy docs - # Use mhausenblas/mkdocs-deploy-gh-pages@nomaterial to exclude mkdocs-material theme - uses: mhausenblas/mkdocs-deploy-gh-pages@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CONFIG_FILE: docs/mkdocs.yml - REQUIREMENTS: requirements/build-docs.txt diff --git a/.github/workflows/publish-py.yaml b/.github/workflows/publish-py.yaml new file mode 100644 index 0000000..34ae5fa --- /dev/null +++ b/.github/workflows/publish-py.yaml @@ -0,0 +1,29 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Publish Python + +on: + release: + types: [published] + +jobs: + publish-package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements/build-pkg.txt + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py bdist_wheel + twine upload dist/* diff --git a/.github/workflows/publish-release-docs.yml b/.github/workflows/publish-release-docs.yml new file mode 100644 index 0000000..6fc3233 --- /dev/null +++ b/.github/workflows/publish-release-docs.yml @@ -0,0 +1,23 @@ +name: Publish Release Docs + +on: + release: + types: [published] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - run: pip install -r requirements/build-docs.txt + - name: Publish ${{ github.event.release.name }} Docs + run: | + git config user.name github-actions + git config user.email github-actions@github.com + cd docs + mike deploy --push --update-aliases ${{ github.event.release.name }} latest diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 00a3264..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# This workflows will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -name: release - -on: - release: - types: - - created - -jobs: - publish-package: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: "3.x" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py bdist_wheel - twine upload dist/* diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml new file mode 100644 index 0000000..22354c8 --- /dev/null +++ b/.github/workflows/test-docs.yml @@ -0,0 +1,36 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: "0 0 * * *" + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - 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 + 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/examples/python/ + black docs/examples/python/ --check + ruff check docs/examples/python/ diff --git a/.github/workflows/test-src.yaml b/.github/workflows/test-src.yaml new file mode 100644 index 0000000..c7ec831 --- /dev/null +++ b/.github/workflows/test-src.yaml @@ -0,0 +1,28 @@ +name: Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: "0 0 * * *" + +jobs: + source: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v3 + - name: Use Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install Python Dependencies + run: pip install -r requirements/test-run.txt + - name: Run Tests + run: nox -s test diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 7e0a9c8..0000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: test - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - coverage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Use Latest Python - uses: actions/setup-python@v2 - with: - python-version: "3.10" - - name: Install Python Dependencies - run: pip install -r requirements/nox-deps.txt - - name: Run Tests - run: nox -t test - - environments: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.9", "3.10", "3.11"] - steps: - - uses: actions/checkout@v2 - - name: Use Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install Python Dependencies - run: pip install -r requirements/nox-deps.txt - - name: Run Tests - run: nox -t test -- --no-cov diff --git a/.gitignore b/.gitignore index c9676bc..9155bda 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ docs/site # --- JAVASCRIPT BUNDLES --- -reactpy_router/bundle.js +src/reactpy_router/bundle.js # --- PYTHON IGNORE FILES ---- @@ -65,21 +65,6 @@ cover/ *.mo *.pot -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ # PyBuilder .pybuilder/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..02502ef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,66 @@ +# Changelog + +All notable changes to this project will be documented in this file. + + + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + + + + + +## [Unreleased] + +- Nothing (yet)! + +## [0.1.1] - 2023-12-13 + +### Fixed + +- Fixed relative navigation + +## [0.1.0] - 2023-06-16 + +### Added + +- Automatically handle client-side history changes + +## [0.0.1] - 2023-05-10 + +### Added + +- Add robust lint/testing + upgrade `reactpy` + more robust routing with Starlette +- Initial draft of router compiler + +### Changed + +- Rename `configure` to `create_router` +- Rename from `idom-router` to `reactpy-router` + +[Unreleased]: https://github.com/reactive-python/reactpy-router/compare/0.1.1...HEAD +[0.1.1]: https://github.com/reactive-python/reactpy-router/compare/0.1.0...0.1.1 +[0.1.0]: https://github.com/reactive-python/reactpy-router/compare/0.0.1...0.1.0 +[0.0.1]: https://github.com/reactive-python/reactpy-router/releases/tag/0.0.1 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1067742..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2022 Ryan S. Morshead - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f5423c3 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +## The MIT License (MIT) + +#### Copyright (c) Reactive Python and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in index 9a3edbc..7c1c601 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,2 @@ -include README.md include reactpy_router/bundle.js include reactpy_router/py.typed -include LICENSE diff --git a/docs/examples/python/__init__.py b/docs/examples/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/examples/python/basic-routing-more-routes.py b/docs/examples/python/basic-routing-more-routes.py new file mode 100644 index 0000000..8ddbebb --- /dev/null +++ b/docs/examples/python/basic-routing-more-routes.py @@ -0,0 +1,15 @@ +from reactpy import component, html, run + +from reactpy_router import route, simple + + +@component +def root(): + return simple.router( + route("/", html.h1("Home Page 🏠")), + route("/messages", html.h1("Messages đŸ’Ŧ")), + route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), + ) + + +run(root) diff --git a/docs/examples/python/basic-routing.py b/docs/examples/python/basic-routing.py new file mode 100644 index 0000000..57b7a37 --- /dev/null +++ b/docs/examples/python/basic-routing.py @@ -0,0 +1,14 @@ +from reactpy import component, html, run + +from reactpy_router import route, simple + + +@component +def root(): + return simple.router( + route("/", html.h1("Home Page 🏠")), + route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), + ) + + +run(root) diff --git a/docs/examples/python/nested-routes.py b/docs/examples/python/nested-routes.py new file mode 100644 index 0000000..c9431c8 --- /dev/null +++ b/docs/examples/python/nested-routes.py @@ -0,0 +1,84 @@ +from reactpy import component, html, run + +from reactpy_router import link, route, simple + +message_data = [ + {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, + {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, + {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, + {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"}, + {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"}, + {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."}, + {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"}, + {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"}, +] + + +@component +def root(): + return simple.router( + route("/", home()), + route( + "/messages", + all_messages(), + # we'll improve upon these manually created routes in the next section... + route("/with/Alice", messages_with("Alice")), + route("/with/Alice-Bob", messages_with("Alice", "Bob")), + ), + route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), + ) + + +@component +def home(): + return html.div( + html.h1("Home Page 🏠"), + link("Messages", to="/messages"), + ) + + +@component +def all_messages(): + last_messages = { + ", ".join(msg["with"]): msg + for msg in sorted(message_data, key=lambda m: m["id"]) + } + return html.div( + html.h1("All Messages đŸ’Ŧ"), + html.ul( + [ + html.li( + {"key": msg["id"]}, + html.p( + link( + f"Conversation with: {', '.join(msg['with'])}", + to=f"/messages/with/{'-'.join(msg['with'])}", + ), + ), + f"{'' if msg['from'] is None else '🔴'} {msg['message']}", + ) + for msg in last_messages.values() + ] + ), + ) + + +@component +def messages_with(*names): + names = set(names) + messages = [msg for msg in message_data if set(msg["with"]) == names] + return html.div( + html.h1(f"Messages with {', '.join(names)} đŸ’Ŧ"), + html.ul( + [ + html.li( + {"key": msg["id"]}, + f"{msg['from'] or 'You'}: {msg['message']}", + ) + for msg in messages + ] + ), + ) + + +run(root) diff --git a/docs/examples/python/route-links.py b/docs/examples/python/route-links.py new file mode 100644 index 0000000..f2be305 --- /dev/null +++ b/docs/examples/python/route-links.py @@ -0,0 +1,23 @@ +from reactpy import component, html, run + +from reactpy_router import link, route, simple + + +@component +def root(): + return simple.router( + route("/", home()), + route("/messages", html.h1("Messages đŸ’Ŧ")), + route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), + ) + + +@component +def home(): + return html.div( + html.h1("Home Page 🏠"), + link("Messages", to="/messages"), + ) + + +run(root) diff --git a/docs/examples/python/route-parameters.py b/docs/examples/python/route-parameters.py new file mode 100644 index 0000000..d990502 --- /dev/null +++ b/docs/examples/python/route-parameters.py @@ -0,0 +1,83 @@ +from reactpy import component, html, run + +from reactpy_router import link, route, simple +from reactpy_router.core import use_params + +message_data = [ + {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, + {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, + {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, + {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"}, + {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"}, + {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."}, + {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"}, + {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"}, +] + + +@component +def root(): + return simple.router( + route("/", home()), + route( + "/messages", + all_messages(), + route("/with/{names}", messages_with()), # note the path param + ), + route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), + ) + + +@component +def home(): + return html.div( + html.h1("Home Page 🏠"), + link("Messages", to="/messages"), + ) + + +@component +def all_messages(): + last_messages = { + ", ".join(msg["with"]): msg + for msg in sorted(message_data, key=lambda m: m["id"]) + } + return html.div( + html.h1("All Messages đŸ’Ŧ"), + html.ul( + [ + html.li( + {"key": msg["id"]}, + html.p( + link( + f"Conversation with: {', '.join(msg['with'])}", + to=f"/messages/with/{'-'.join(msg['with'])}", + ), + ), + f"{'' if msg['from'] is None else '🔴'} {msg['message']}", + ) + for msg in last_messages.values() + ] + ), + ) + + +@component +def messages_with(): + names = set(use_params()["names"].split("-")) # and here we use the path param + messages = [msg for msg in message_data if set(msg["with"]) == names] + return html.div( + html.h1(f"Messages with {', '.join(names)} đŸ’Ŧ"), + html.ul( + [ + html.li( + {"key": msg["id"]}, + f"{msg['from'] or 'You'}: {msg['message']}", + ) + for msg in messages + ] + ), + ) + + +run(root) diff --git a/docs/examples/python/use-params.py b/docs/examples/python/use-params.py new file mode 100644 index 0000000..7b1193a --- /dev/null +++ b/docs/examples/python/use-params.py @@ -0,0 +1,23 @@ +from reactpy import component, html + +from reactpy_router import link, route, simple, use_params + + +@component +def user(): + params = use_params() + return html.h1(f"User {params['id']} 👤") + + +@component +def root(): + return simple.router( + route( + "/", + html.div( + html.h1("Home Page 🏠"), + link("User 123", to="/user/123"), + ), + ), + route("/user/{id:int}", user()), + ) diff --git a/docs/examples/python/use-query.py b/docs/examples/python/use-query.py new file mode 100644 index 0000000..a8678cc --- /dev/null +++ b/docs/examples/python/use-query.py @@ -0,0 +1,23 @@ +from reactpy import component, html + +from reactpy_router import link, route, simple, use_query + + +@component +def search(): + query = use_query() + return html.h1(f"Search Results for {query['q'][0]} 🔍") + + +@component +def root(): + return simple.router( + route( + "/", + html.div( + html.h1("Home Page 🏠"), + link("Search", to="/search?q=reactpy"), + ), + ), + route("/about", html.h1("About Page 📖")), + ) diff --git a/docs/includes/pr.md b/docs/includes/pr.md new file mode 100644 index 0000000..9b4f0e4 --- /dev/null +++ b/docs/includes/pr.md @@ -0,0 +1,3 @@ +Now, you can create/modify the ReactPy-Router 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/mkdocs.yml b/docs/mkdocs.yml index 54a4f8c..76c5036 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,55 +1,141 @@ -site_name: ReactPy Router -docs_dir: src -repo_url: https://github.com/reactive-python/reactpy-router - +--- nav: - - Home: index.md - - Usage: usage.md - - Tutorials: - - Simple Application: tutorials/simple-app.md - - Custom Router: tutorials/custom-router.md - - Reference: reference.md - - Contributing: contributing.md - - Source Code: https://github.com/reactive-python/reactpy-router + - Get Started: + - Add ReactPy-Router to Your Project: learn/add-reactpy-router-to-your-project.md + - Your First Routed Application: learn/simple-application.md + - Advanced Topics: + - Routers, Routes, and Links: learn/routers-routes-and-links.md + - Hooks: learn/hooks.md + - Creating a Custom Router 🚧: learn/custom-router.md + - Reference: + - Core: reference/core.md + - Router: reference/router.md + - Types: reference/types.md + - About: + - Changelog: about/changelog.md + - Contributor Guide: + - Code: about/code.md + - Docs: about/docs.md + - Community: + - GitHub Discussions: https://github.com/reactive-python/reactpy-router/discussions + - Discord: https://discord.gg/uNb5P4hA9X + - Reddit: https://www.reddit.com/r/ReactPy/ + - License: about/license.md theme: - name: material - logo: assets/logo.svg - favicon: assets/logo.svg - palette: - # Palette toggle for light mode - - scheme: default - toggle: - icon: material/brightness-7 - name: Switch to dark mode - primary: black - accent: light-blue - - # Palette toggle for dark mode - - scheme: slate - toggle: - icon: material/brightness-4 - name: Switch to light mode - primary: black - accent: light-blue + name: material + custom_dir: overrides + palette: + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/white-balance-sunny + name: Switch to light mode + primary: red # We use red to indicate that something is unthemed + accent: red + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/weather-night + name: Switch to dark mode + primary: white + accent: red + features: + - navigation.instant + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - content.code.copy + - search.highlight + icon: + repo: fontawesome/brands/github + admonition: + note: fontawesome/solid/note-sticky + logo: https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg + favicon: https://raw.githubusercontent.com/reactive-python/reactpy/main/branding/svg/reactpy-logo-square.svg +markdown_extensions: + - toc: + permalink: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.tabbed: + alternate_style: true + - pymdownx.highlight: + linenums: true + - pymdownx.superfences + - pymdownx.details + - pymdownx.inlinehilite + - admonition + - attr_list + - md_in_html + - pymdownx.keys plugins: -- search -- mkdocstrings: - default_handler: python - handlers: - python: - paths: ["../"] - import: - - https://reactpy.dev/docs/objects.inv - - https://installer.readthedocs.io/en/stable/objects.inv + - search + - include-markdown + - git-authors + - minify: + minify_html: true + minify_js: true + minify_css: true + cache_safe: true + - git-revision-date-localized: + fallback_to_build_date: true + - spellcheck: + known_words: dictionary.txt + allow_unicode: no + ignore_code: yes + - mkdocstrings: + default_handler: python + handlers: + python: + paths: ["../"] + import: + - https://reactpy.dev/docs/objects.inv + - https://installer.readthedocs.io/en/stable/objects.inv -markdown_extensions: - - admonition - - pymdownx.details - - pymdownx.superfences +extra: + generator: false + version: + provider: mike + analytics: + provider: google + property: G-XRLQYZBG00 + +extra_javascript: + - assets/js/main.js + +extra_css: + - assets/css/main.css + - assets/css/button.css + - assets/css/admonition.css + - assets/css/banner.css + - assets/css/sidebar.css + - assets/css/navbar.css + - assets/css/table-of-contents.css + - assets/css/code.css + - assets/css/footer.css + - assets/css/home.css watch: - - "../reactpy_router" + - "../docs" + - "../reactpy_router" + - "../CHANGELOG.md" +site_name: ReactPy Router +site_author: Archmonger +site_description: It's React-Router, but in Python. +copyright: '© +
+ +Reactive Python and affiliates. +' +repo_url: https://github.com/reactive-python/reactpy-router +site_url: https://reactive-python.github.io/reactpy-router +repo_name: ReactPy Router (GitHub) +edit_uri: edit/main/docs/src/ +docs_dir: src diff --git a/docs/overrides/home-code-examples/add-interactivity-demo.html b/docs/overrides/home-code-examples/add-interactivity-demo.html new file mode 100644 index 0000000..48ac19a --- /dev/null +++ b/docs/overrides/home-code-examples/add-interactivity-demo.html @@ -0,0 +1,172 @@ +
+
+ +
+
+ + + + example.com/videos.html +
+
+ +
+
+

Searchable Videos

+

Type a search query below.

+ +
+ +

5 Videos

+ +
+
+ + + +
+
+

ReactPy: The Documentary

+

From web library to taco delivery service

+
+ +
+ +
+
+ + + +
+
+

Code using Worst Practices

+

Harriet Potter (2013)

+
+ +
+ +
+
+ + + +
+
+

Introducing ReactPy Foriegn

+

Tim Cooker (2015)

+
+ +
+ +
+
+ + + +
+
+

Introducing ReactPy Cooks

+

Soap Boat and Dinosaur Dan (2018)

+
+ +
+ +
+
+ + + +
+
+

Introducing Quantum Components

+

Isaac Asimov and Lauren-kun (2020)

+
+ +
+

+
+ + +
+
diff --git a/docs/overrides/home-code-examples/add-interactivity.py b/docs/overrides/home-code-examples/add-interactivity.py new file mode 100644 index 0000000..9097644 --- /dev/null +++ b/docs/overrides/home-code-examples/add-interactivity.py @@ -0,0 +1,30 @@ +from reactpy import component, html, use_state + + +def filter_videos(videos, search_text): + return None + + +def search_input(dictionary, value): + return None + + +def video_list(videos, empty_heading): + return None + + +@component +def searchable_video_list(videos): + search_text, set_search_text = use_state("") + found_videos = filter_videos(videos, search_text) + + return html._( + search_input( + {"on_change": lambda new_text: set_search_text(new_text)}, + value=search_text, + ), + video_list( + videos=found_videos, + empty_heading=f"No matches for “{search_text}”", + ), + ) diff --git a/docs/overrides/home-code-examples/code-block.html b/docs/overrides/home-code-examples/code-block.html new file mode 100644 index 0000000..c1f14e5 --- /dev/null +++ b/docs/overrides/home-code-examples/code-block.html @@ -0,0 +1,7 @@ +
+ +
+
+ +
+
diff --git a/docs/overrides/home-code-examples/create-user-interfaces-demo.html b/docs/overrides/home-code-examples/create-user-interfaces-demo.html new file mode 100644 index 0000000..9a684d3 --- /dev/null +++ b/docs/overrides/home-code-examples/create-user-interfaces-demo.html @@ -0,0 +1,24 @@ +
+
+
+
+ + + +
+
+

My video

+

Video description

+
+ +
+
+
diff --git a/docs/overrides/home-code-examples/create-user-interfaces.py b/docs/overrides/home-code-examples/create-user-interfaces.py new file mode 100644 index 0000000..37776ab --- /dev/null +++ b/docs/overrides/home-code-examples/create-user-interfaces.py @@ -0,0 +1,22 @@ +from reactpy import component, html + + +def thumbnail(video): + return None + + +def like_button(video): + return None + + +@component +def video(video): + return html.div( + thumbnail(video), + html.a( + {"href": video.url}, + html.h3(video.title), + html.p(video.description), + ), + like_button(video), + ) diff --git a/docs/overrides/home-code-examples/write-components-with-python-demo.html b/docs/overrides/home-code-examples/write-components-with-python-demo.html new file mode 100644 index 0000000..203287c --- /dev/null +++ b/docs/overrides/home-code-examples/write-components-with-python-demo.html @@ -0,0 +1,65 @@ +
+
+

3 Videos

+
+
+ + + +
+
+

First video

+

Video description

+
+ +
+
+
+ + + +
+
+

Second video

+

Video description

+
+ +
+
+
+ + + +
+
+

Third video

+

Video description

+
+ +
+
+
diff --git a/docs/overrides/home-code-examples/write-components-with-python.py b/docs/overrides/home-code-examples/write-components-with-python.py new file mode 100644 index 0000000..6af43ba --- /dev/null +++ b/docs/overrides/home-code-examples/write-components-with-python.py @@ -0,0 +1,15 @@ +from reactpy import component, html + + +@component +def video_list(videos, empty_heading): + count = len(videos) + heading = empty_heading + if count > 0: + noun = "Videos" if count > 1 else "Video" + heading = f"{count} {noun}" + + return html.section( + html.h2(heading), + [video(video) for video in videos], + ) diff --git a/docs/overrides/home.html b/docs/overrides/home.html new file mode 100644 index 0000000..67e3144 --- /dev/null +++ b/docs/overrides/home.html @@ -0,0 +1,135 @@ + +{% extends "main.html" %} + + +{% block content %}{% endblock %} + + +{% block tabs %} + +
+
+ +

{{ config.site_name }}

+

{{ config.site_description }}

+ +
+ +
+

Create user interfaces from components

+

+ ReactPy lets you build user interfaces out of individual pieces called components. Create your own ReactPy + components like thumbnail, like_button, and video. Then combine + them into entire screens, pages, and apps. +

+
+ {% with image="create-user-interfaces.png", class="pop-left" %} + {% include "home-code-examples/code-block.html" %} + {% endwith %} + {% include "home-code-examples/create-user-interfaces-demo.html" %} +
+

+ Whether you work on your own or with thousands of other developers, using React feels the same. It is + designed to let you seamlessly combine components written by independent people, teams, and + organizations. +

+
+ +
+

Write components with pure Python code

+

+ ReactPy components are Python functions. Want to show some content conditionally? Use an + if statement. Displaying a list? Try using + list comprehension. + Learning ReactPy is learning programming. +

+
+ {% with image="write-components-with-python.png", class="pop-left" %} + {% include "home-code-examples/code-block.html" %} + {% endwith %} + {% include "home-code-examples/write-components-with-python-demo.html" %} + +
+
+ +
+

Add interactivity wherever you need it

+

+ ReactPy components receive data and return what should appear on the screen. You can pass them new data in + response to an interaction, like when the user types into an input. ReactPy will then update the screen to + match the new data. +

+
+ {% with image="add-interactivity.png" %} + {% include "home-code-examples/code-block.html" %} + {% endwith %} + {% include "home-code-examples/add-interactivity-demo.html" %} +
+

+ You don't have to build your whole page in ReactPy. Add React to your existing HTML page, and render + interactive ReactPy components anywhere on it. +

+
+ +
+

Go full-stack with the Django framework

+

+ ReactPy is a library. It lets you put components together, but it doesn't prescribe how to do routing and + data fetching. To build an entire app with ReactPy, we recommend a backend framework like + Django. +

+ + Get Started + +
+
+{% endblock %} diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 0000000..c63ca9e --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} +{{ super() }} + +{% if git_page_authors %} +
+ + Authors: {{ git_page_authors | default('enable mkdocs-git-authors-plugin') }} + +
+{% endif %} +{% endblock %} + +{% block outdated %} +You're not viewing the latest release. + + Click here to go to latest. + +{% endblock %} diff --git a/docs/src/about/changelog.md b/docs/src/about/changelog.md new file mode 100644 index 0000000..1ecf88e --- /dev/null +++ b/docs/src/about/changelog.md @@ -0,0 +1,14 @@ +--- +hide: + - toc +--- + +

+ +{% include-markdown "../../../CHANGELOG.md" start="" end="" %} + +

+ +--- + +{% include-markdown "../../../CHANGELOG.md" start="" %} diff --git a/docs/src/about/code.md b/docs/src/about/code.md new file mode 100644 index 0000000..e8ec388 --- /dev/null +++ b/docs/src/about/code.md @@ -0,0 +1,55 @@ +## Overview + +

+ + You will need to set up a Python environment to develop ReactPy-Router. + +

+ +--- + +## Creating an environment + +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/) +- [Git](https://git-scm.com/downloads) + +Once done, you should clone this repository: + +```bash linenums="0" +git clone https://github.com/reactive-python/reactpy-django.git +cd reactpy-router +``` + +Then, by running the command below you can install the dependencies needed to run the ReactPy-Django development environment. + + + +```bash linenums="0" +pip install -e . -r requirements.txt --upgrade --verbose +``` + +## Running the full test suite + +!!! abstract "Note" + + This repository uses [Nox](https://nox.thea.codes/en/stable/) to run tests. For a full test of available scripts run `nox -l`. + +By running the command below you can run the full test suite: + +```bash linenums="0" +nox -s test +``` + +Or, if you want to run the tests in the foreground with a visible browser window, run: + + + +```bash linenums="0" +nox -s test -- --headed +``` + +## Creating a pull request + +{% include-markdown "../../includes/pr.md" %} diff --git a/docs/src/about/docs.md b/docs/src/about/docs.md new file mode 100644 index 0000000..4c1f566 --- /dev/null +++ b/docs/src/about/docs.md @@ -0,0 +1,45 @@ +## Overview + +

+ +You will need to set up a Python environment to create, test, and 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.9+](https://www.python.org/downloads/) +- [Git](https://git-scm.com/downloads) + +Once done, you should clone this repository: + +```bash linenums="0" +git clone https://github.com/reactive-python/reactpy-router.git +cd reactpy-router +``` + +Then, by running the command below you can: + +- Install an editable version of the documentation +- Self-host a test server for the documentation + +```bash linenums="0" +pip install -r requirements.txt --upgrade +``` + +Finally, to verify that everything is working properly, you can manually run the docs preview web server. + +```bash linenums="0" +cd docs +mkdocs serve +``` + +Navigate to [`http://127.0.0.1:8000`](http://127.0.0.1:8000) to view a preview of the documentation. + +## Creating a pull request + +{% include-markdown "../../includes/pr.md" %} diff --git a/docs/src/about/license.md b/docs/src/about/license.md new file mode 100644 index 0000000..15d975d --- /dev/null +++ b/docs/src/about/license.md @@ -0,0 +1,8 @@ +--- +hide: + - toc +--- + +--- + +{% include "../../../LICENSE.md" %} diff --git a/docs/src/assets/css/admonition.css b/docs/src/assets/css/admonition.css new file mode 100644 index 0000000..8b3f06e --- /dev/null +++ b/docs/src/assets/css/admonition.css @@ -0,0 +1,160 @@ +[data-md-color-scheme="slate"] { + --admonition-border-color: transparent; + --admonition-expanded-border-color: rgba(255, 255, 255, 0.1); + --note-bg-color: rgba(43, 110, 98, 0.2); + --terminal-bg-color: #0c0c0c; + --terminal-title-bg-color: #000; + --deep-dive-bg-color: rgba(43, 52, 145, 0.2); + --you-will-learn-bg-color: #353a45; + --pitfall-bg-color: rgba(182, 87, 0, 0.2); +} +[data-md-color-scheme="default"] { + --admonition-border-color: rgba(0, 0, 0, 0.08); + --admonition-expanded-border-color: var(--admonition-border-color); + --note-bg-color: rgb(244, 251, 249); + --terminal-bg-color: rgb(64, 71, 86); + --terminal-title-bg-color: rgb(35, 39, 47); + --deep-dive-bg-color: rgb(243, 244, 253); + --you-will-learn-bg-color: rgb(246, 247, 249); + --pitfall-bg-color: rgb(254, 245, 231); +} + +.md-typeset details, +.md-typeset .admonition { + border-color: var(--admonition-border-color) !important; + box-shadow: none; +} + +.md-typeset :is(.admonition, details) { + margin: 0.55em 0; +} + +.md-typeset .admonition { + font-size: 0.7rem; +} + +.md-typeset .admonition:focus-within, +.md-typeset details:focus-within { + box-shadow: none !important; +} + +.md-typeset details[open] { + border-color: var(--admonition-expanded-border-color) !important; +} + +/* +Admonition: "summary" +React Name: "You will learn" +*/ +.md-typeset .admonition.summary { + background: var(--you-will-learn-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; +} + +.md-typeset .summary .admonition-title { + font-size: 1rem; + background: transparent; + padding-left: 0.6rem; + padding-bottom: 0; +} + +.md-typeset .summary .admonition-title:before { + display: none; +} + +.md-typeset .admonition.summary { + border-color: #ffffff17 !important; +} + +/* +Admonition: "abstract" +React Name: "Note" +*/ +.md-typeset .admonition.abstract { + background: var(--note-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; +} + +.md-typeset .abstract .admonition-title { + font-size: 1rem; + background: transparent; + padding-bottom: 0; + color: rgb(68, 172, 153); +} + +.md-typeset .abstract .admonition-title:before { + font-size: 1.1rem; + background: rgb(68, 172, 153); +} + +/* +Admonition: "warning" +React Name: "Pitfall" +*/ +.md-typeset .admonition.warning { + background: var(--pitfall-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; +} + +.md-typeset .warning .admonition-title { + font-size: 1rem; + background: transparent; + padding-bottom: 0; + color: rgb(219, 125, 39); +} + +.md-typeset .warning .admonition-title:before { + font-size: 1.1rem; + background: rgb(219, 125, 39); +} + +/* +Admonition: "info" +React Name: "Deep Dive" +*/ +.md-typeset .admonition.info { + background: var(--deep-dive-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; +} + +.md-typeset .info .admonition-title { + font-size: 1rem; + background: transparent; + padding-bottom: 0; + color: rgb(136, 145, 236); +} + +.md-typeset .info .admonition-title:before { + font-size: 1.1rem; + background: rgb(136, 145, 236); +} + +/* +Admonition: "example" +React Name: "Terminal" +*/ +.md-typeset .admonition.example { + background: var(--terminal-bg-color); + border-radius: 0.4rem; + overflow: hidden; + border: none; +} + +.md-typeset .example .admonition-title { + background: var(--terminal-title-bg-color); + color: rgb(246, 247, 249); +} + +.md-typeset .example .admonition-title:before { + background: rgb(246, 247, 249); +} + +.md-typeset .admonition.example code { + background: transparent; + color: #fff; + box-shadow: none; +} diff --git a/docs/src/assets/css/banner.css b/docs/src/assets/css/banner.css new file mode 100644 index 0000000..3739a73 --- /dev/null +++ b/docs/src/assets/css/banner.css @@ -0,0 +1,15 @@ +body[data-md-color-scheme="slate"] { + --md-banner-bg-color: rgb(55, 81, 78); + --md-banner-font-color: #fff; +} + +body[data-md-color-scheme="default"] { + --md-banner-bg-color: #ff9; + --md-banner-font-color: #000; +} + +.md-banner--warning { + background-color: var(--md-banner-bg-color); + color: var(--md-banner-font-color); + text-align: center; +} diff --git a/docs/src/assets/css/button.css b/docs/src/assets/css/button.css new file mode 100644 index 0000000..8f71391 --- /dev/null +++ b/docs/src/assets/css/button.css @@ -0,0 +1,41 @@ +[data-md-color-scheme="slate"] { + --md-button-font-color: #fff; + --md-button-border-color: #404756; +} + +[data-md-color-scheme="default"] { + --md-button-font-color: #000; + --md-button-border-color: #8d8d8d; +} + +.md-typeset .md-button { + border-width: 1px; + border-color: var(--md-button-border-color); + border-radius: 9999px; + color: var(--md-button-font-color); + transition: color 125ms, background 125ms, border-color 125ms, + transform 125ms; +} + +.md-typeset .md-button:focus, +.md-typeset .md-button:hover { + border-color: var(--md-button-border-color); + color: var(--md-button-font-color); + background: rgba(78, 87, 105, 0.05); +} + +.md-typeset .md-button.md-button--primary { + color: #fff; + border-color: transparent; + background: var(--reactpy-color-dark); +} + +.md-typeset .md-button.md-button--primary:focus, +.md-typeset .md-button.md-button--primary:hover { + border-color: transparent; + background: var(--reactpy-color-darker); +} + +.md-typeset .md-button:focus { + transform: scale(0.98); +} diff --git a/docs/src/assets/css/code.css b/docs/src/assets/css/code.css new file mode 100644 index 0000000..c546549 --- /dev/null +++ b/docs/src/assets/css/code.css @@ -0,0 +1,111 @@ +:root { + --code-max-height: 17.25rem; + --md-code-backdrop: rgba(0, 0, 0, 0) 0px 0px 0px 0px, + rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.03) 0px 0.8px 2px 0px, + rgba(0, 0, 0, 0.047) 0px 2.7px 6.7px 0px, + rgba(0, 0, 0, 0.08) 0px 12px 30px 0px; +} +[data-md-color-scheme="slate"] { + --md-code-hl-color: #ffffcf1c; + --md-code-bg-color: #16181d; + --md-code-hl-comment-color: hsla(var(--md-hue), 75%, 90%, 0.43); + --code-tab-color: rgb(52, 58, 70); + --md-code-hl-name-color: #aadafc; + --md-code-hl-string-color: hsl(21 49% 63% / 1); + --md-code-hl-keyword-color: hsl(289.67deg 35% 60%); + --md-code-hl-constant-color: hsl(213.91deg 68% 61%); + --md-code-hl-number-color: #bfd9ab; + --func-and-decorator-color: #dcdcae; + --module-import-color: #60c4ac; +} +[data-md-color-scheme="default"] { + --md-code-hl-color: #ffffcf1c; + --md-code-bg-color: rgba(208, 211, 220, 0.4); + --md-code-fg-color: rgb(64, 71, 86); + --code-tab-color: #fff; + --func-and-decorator-color: var(--md-code-hl-function-color); + --module-import-color: #e153e5; +} +[data-md-color-scheme="default"] .md-typeset .highlight > pre > code, +[data-md-color-scheme="default"] .md-typeset .highlight > table.highlighttable { + --md-code-bg-color: #fff; +} + +/* All code blocks */ +.md-typeset pre > code { + max-height: var(--code-max-height); +} + +/* Code blocks with no line number */ +.md-typeset .highlight > pre > code { + border-radius: 16px; + max-height: var(--code-max-height); + box-shadow: var(--md-code-backdrop); +} + +/* Code blocks with line numbers */ +.md-typeset .highlighttable .linenos { + max-height: var(--code-max-height); + overflow: hidden; +} +.md-typeset .highlighttable { + box-shadow: var(--md-code-backdrop); + border-radius: 8px; + overflow: hidden; +} + +/* Tabbed code blocks */ +.md-typeset .tabbed-set { + box-shadow: var(--md-code-backdrop); + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--md-default-fg-color--lightest); +} +.md-typeset .tabbed-set .tabbed-block { + overflow: hidden; +} +.js .md-typeset .tabbed-set .tabbed-labels { + background: var(--code-tab-color); + margin: 0; + padding-left: 0.8rem; +} +.md-typeset .tabbed-set .tabbed-labels > label { + font-weight: 400; + font-size: 0.7rem; + padding-top: 0.55em; + padding-bottom: 0.35em; +} +.md-typeset .tabbed-set .highlighttable { + border-radius: 0; +} + +/* Code hightlighting colors */ + +/* Module imports */ +.highlight .nc, +.highlight .ne, +.highlight .nn, +.highlight .nv { + color: var(--module-import-color); +} + +/* Function def name and decorator */ +.highlight .nd, +.highlight .nf { + color: var(--func-and-decorator-color); +} + +/* None type */ +.highlight .kc { + color: var(--md-code-hl-constant-color); +} + +/* Keywords such as def and return */ +.highlight .k { + color: var(--md-code-hl-constant-color); +} + +/* HTML tags */ +.highlight .nt { + color: var(--md-code-hl-constant-color); +} diff --git a/docs/src/assets/css/footer.css b/docs/src/assets/css/footer.css new file mode 100644 index 0000000..b340828 --- /dev/null +++ b/docs/src/assets/css/footer.css @@ -0,0 +1,33 @@ +[data-md-color-scheme="slate"] { + --md-footer-bg-color: var(--md-default-bg-color); + --md-footer-bg-color--dark: var(--md-default-bg-color); + --md-footer-border-color: var(--md-header-border-color); +} + +[data-md-color-scheme="default"] { + --md-footer-fg-color: var(--md-typeset-color); + --md-footer-fg-color--light: var(--md-typeset-color); + --md-footer-bg-color: var(--md-default-bg-color); + --md-footer-bg-color--dark: var(--md-default-bg-color); + --md-footer-border-color: var(--md-header-border-color); +} + +.md-footer { + border-top: 1px solid var(--md-footer-border-color); +} + +.md-copyright { + width: 100%; +} + +.md-copyright__highlight { + width: 100%; +} + +.legal-footer-right { + float: right; +} + +.md-copyright__highlight div { + display: inline; +} diff --git a/docs/src/assets/css/home.css b/docs/src/assets/css/home.css new file mode 100644 index 0000000..c72e709 --- /dev/null +++ b/docs/src/assets/css/home.css @@ -0,0 +1,335 @@ +img.home-logo { + height: 120px; +} + +.home .row { + 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); +} + +.home .row.stripe { + 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; +} + +.home .row h1 { + 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; +} + +.home .row > p { + max-width: 35rem; + line-height: 1.5; + font-weight: 400; +} + +.home .row.first > p { + font-size: 32px; + font-weight: 500; +} + +/* Code blocks */ +.home .row .tabbed-set { + background: var(--home-tabbed-set-bg-color); + margin: 0; +} + +.home .row .tabbed-content { + 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; +} + +.home .row .tabbed-content { + -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; +} + +.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; +} + +.home .demo .vid-row { + display: flex; + flex-direction: row; + -moz-column-gap: 12px; + column-gap: 12px; +} + +.home .demo { + 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; +} + +.home .demo .vid-text { + 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; +} + +.home .demo h3 { + font-size: 16px; + line-height: 1.25; + margin: 0; +} + +.home .demo p { + 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; +} + +.home .demo .browser-navbar { + 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; +} + +.home .demo .browser-viewport .search-header > h1 { + 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; +} + +.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; +} + +.home .demo .search-bar svg { + height: 40px; + position: absolute; + transform: translateX(75%); +} + +.home .demo .search-bar { + 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; + } +} + +/* 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%; + } +} diff --git a/docs/src/assets/css/main.css b/docs/src/assets/css/main.css new file mode 100644 index 0000000..6eefdf2 --- /dev/null +++ b/docs/src/assets/css/main.css @@ -0,0 +1,85 @@ +/* Variable overrides */ +:root { + --reactpy-color: #58b962; + --reactpy-color-dark: #42914a; + --reactpy-color-darker: #34743b; + --reactpy-color-opacity-10: rgba(88, 185, 98, 0.1); +} + +[data-md-color-accent="red"] { + --md-primary-fg-color--light: var(--reactpy-color); + --md-primary-fg-color--dark: var(--reactpy-color-dark); +} + +[data-md-color-scheme="slate"] { + --md-default-bg-color: rgb(35, 39, 47); + --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-primary-fg-color: var(--md-default-bg-color); + --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1); + --md-default-fg-color--light: #fff; + --md-typeset-a-color: var(--reactpy-color); + --md-accent-fg-color: var(--reactpy-color-dark); +} + +[data-md-color-scheme="default"] { + --md-primary-fg-color: var(--md-default-bg-color); + --md-default-fg-color--light: #000; + --md-default-fg-color--lighter: #0000007e; + --md-default-fg-color--lightest: #00000029; + --md-typeset-color: rgb(35, 39, 47); + --md-typeset-a-color: var(--reactpy-color); + --md-accent-fg-color: var(--reactpy-color-dark); +} + +/* Font changes */ +.md-typeset { + font-weight: 300; +} + +.md-typeset h1 { + font-weight: 600; + margin: 0; + font-size: 2.5em; +} + +.md-typeset h2 { + font-weight: 500; +} + +.md-typeset h3 { + font-weight: 400; +} + +/* Intro section styling */ +p.intro { + font-size: 0.9rem; + font-weight: 500; +} + +/* Hide "Overview" jump selector */ +h2#overview { + visibility: hidden; + height: 0; + margin: 0; + padding: 0; +} + +/* Reduce size of the outdated banner */ +.md-banner__inner { + margin: 0.45rem auto; +} + +/* Desktop Styles */ +@media screen and (min-width: 60em) { + /* Remove max width on desktop */ + .md-grid { + max-width: none; + } +} + +/* Max size of page content */ +.md-content { + max-width: 56rem; +} diff --git a/docs/src/assets/css/navbar.css b/docs/src/assets/css/navbar.css new file mode 100644 index 0000000..33e8b14 --- /dev/null +++ b/docs/src/assets/css/navbar.css @@ -0,0 +1,185 @@ +[data-md-color-scheme="slate"] { + --md-header-border-color: rgb(255 255 255 / 5%); + --md-version-bg-color: #ffffff0d; +} + +[data-md-color-scheme="default"] { + --md-header-border-color: rgb(0 0 0 / 7%); + --md-version-bg-color: #ae58ee2e; +} + +.md-header { + border: 0 solid transparent; + border-bottom-width: 1px; +} + +.md-header--shadow { + box-shadow: none; + border-color: var(--md-header-border-color); + transition: border-color 0.35s cubic-bezier(0.1, 0.7, 0.1, 1); +} + +/* Version selector */ +.md-header__topic .md-ellipsis, +.md-header__title [data-md-component="header-topic"] { + display: none; +} + +[dir="ltr"] .md-version__current { + margin: 0; +} + +.md-version__list { + margin: 0; + left: 0; + right: 0; + top: 2.5rem; +} + +.md-version { + background: var(--md-version-bg-color); + border-radius: 999px; + padding: 0 0.8rem; + margin: 0.3rem 0; + height: 1.8rem; + display: flex; + font-size: 0.7rem; +} + +/* Mobile Styling */ +@media screen and (max-width: 60em) { + label.md-header__button.md-icon[for="__drawer"] { + order: 1; + } + .md-header__button.md-logo { + display: initial; + order: 2; + margin-right: auto; + } + .md-header__title { + order: 3; + } + .md-header__button[for="__search"] { + order: 4; + } + .md-header__option[data-md-component="palette"] { + order: 5; + } + .md-header__source { + display: initial; + order: 6; + } + .md-header__source .md-source__repository { + display: none; + } +} + +/* Desktop Styling */ +@media screen and (min-width: 60em) { + /* Nav container */ + nav.md-header__inner { + display: contents; + } + header.md-header { + display: flex; + align-items: center; + } + + /* Logo */ + .md-header__button.md-logo { + order: 1; + padding-right: 0.4rem; + padding-top: 0; + padding-bottom: 0; + } + .md-header__button.md-logo img { + height: 2rem; + } + + /* Version selector */ + [dir="ltr"] .md-header__title { + order: 2; + margin: 0; + margin-right: 0.8rem; + margin-left: 0.2rem; + flex-grow: 0; + } + .md-header__topic { + position: relative; + } + .md-header__title--active .md-header__topic { + transform: none; + opacity: 1; + pointer-events: auto; + z-index: 4; + } + + /* Search */ + .md-search { + order: 3; + width: 100%; + margin-right: 0.6rem; + } + .md-search__inner { + width: 100%; + float: unset !important; + } + .md-search__form { + border-radius: 9999px; + } + [data-md-toggle="search"]:checked ~ .md-header .md-header__option { + max-width: unset; + opacity: unset; + transition: unset; + } + + /* Tabs */ + .md-tabs { + order: 4; + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + z-index: -1; + overflow: visible; + border: none !important; + } + li.md-tabs__item.md-tabs__item--active { + background: var(--reactpy-color-opacity-10); + border-radius: 9999px; + color: var(--md-typeset-a-color); + } + .md-tabs__link { + margin: 0; + } + .md-tabs__item { + height: 1.8rem; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } + + /* Dark/Light Selector */ + .md-header__option[data-md-component="palette"] { + order: 5; + } + + /* GitHub info */ + .md-header__source { + order: 6; + margin-left: 0 !important; + } +} + +/* Ultrawide Desktop Styles */ +@media screen and (min-width: 1919px) { + .md-search { + order: 2; + width: 100%; + max-width: 34.4rem; + margin: 0 auto; + } +} diff --git a/docs/src/assets/css/sidebar.css b/docs/src/assets/css/sidebar.css new file mode 100644 index 0000000..b6507d9 --- /dev/null +++ b/docs/src/assets/css/sidebar.css @@ -0,0 +1,104 @@ +:root { + --sizebar-font-size: 0.62rem; +} + +.md-nav__link { + word-break: break-word; +} + +/* Desktop Styling */ +@media screen and (min-width: 76.1875em) { + /* Move the sidebar and TOC to the edge of the page */ + .md-main__inner.md-grid { + margin-left: 0; + margin-right: 0; + max-width: unset; + display: grid; + grid-template-columns: auto 1fr auto; + } + + .md-content { + justify-self: center; + width: 100%; + } + /* Made the sidebar buttons look React-like */ + .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { + text-transform: uppercase; + } + + .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: grey; + } + + .md-nav__item .md-nav__link--active:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + background: var(--reactpy-color-opacity-10); + } + + .md-nav__link { + padding: 0.5rem 0.5rem 0.5rem 1rem; + margin: 0; + border-radius: 0 10px 10px 0; + font-weight: 500; + overflow: hidden; + font-size: var(--sizebar-font-size); + } + + .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: 300; + } + + .md-nav__item--nested .md-nav__item .md-nav__item .md-nav__link { + font-weight: 400; + padding-left: 1.25rem; + } +} diff --git a/docs/src/assets/css/table-of-contents.css b/docs/src/assets/css/table-of-contents.css new file mode 100644 index 0000000..6c94f06 --- /dev/null +++ b/docs/src/assets/css/table-of-contents.css @@ -0,0 +1,48 @@ +/* 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; + font-size: var(--sizebar-font-size); + } + + [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: 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; + font-weight: 400; + } + + [data-md-component="toc"] + .md-nav__item + .md-nav__list + .md-nav__item + .md-nav__link { + padding-left: 1.25rem; + } + + [dir="ltr"] .md-sidebar__inner { + padding: 0; + } + + .md-nav__item { + padding: 0; + } +} diff --git a/docs/src/assets/js/main.js b/docs/src/assets/js/main.js new file mode 100644 index 0000000..50e2dda --- /dev/null +++ b/docs/src/assets/js/main.js @@ -0,0 +1,19 @@ +// Sync scrolling between the code node and the line number node +// Event needs to be a separate function, otherwise the event will be triggered multiple times +let code_with_lineno_scroll_event = function () { + let tr = this.parentNode.parentNode.parentNode.parentNode; + let lineno = tr.querySelector(".linenos"); + lineno.scrollTop = this.scrollTop; +}; + +const observer = new MutationObserver((mutations) => { + let lineno = document.querySelectorAll(".linenos~.code"); + lineno.forEach(function (element) { + let code = element.parentNode.querySelector("code"); + code.addEventListener("scroll", code_with_lineno_scroll_event); + }); +}); + +observer.observe(document.body, { + childList: true, +}); diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg deleted file mode 100644 index 312fb87..0000000 --- a/docs/src/assets/logo.svg +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/src/contributing.md b/docs/src/contributing.md deleted file mode 100644 index 520531f..0000000 --- a/docs/src/contributing.md +++ /dev/null @@ -1,70 +0,0 @@ -# Contributing - -!!! note - - The [Code of Conduct](https://github.com/reactive-python/reactpy/blob/main/CODE_OF_CONDUCT.md) - applies in all community spaces. If you are not familiar with our Code of Conduct policy, - take a minute to read it before making your first contribution. - -The ReactPy team welcomes contributions and contributors of all kinds - whether they -come as code changes, participation in the discussions, opening issues and pointing out -bugs, or simply sharing your work with your colleagues and friends. We’re excited to see -how you can help move this project and community forward! - -## Everyone Can Contribute! - -Trust us, there’s so many ways to support the project. We’re always looking for people who can: - -- Improve our documentation -- Teach and tell others about ReactPy -- Share ideas for new features -- Report bugs -- Participate in general discussions - -Still aren’t sure what you have to offer? Just [ask us](https://github.com/reactive-python/reactpy-router/discussions) and we’ll help you make your first contribution. - -## Development Environment - -For a developer installation from source be sure to install -[NPM](https://www.npmjs.com/) before running: - -```bash -git clone https://github.com/reactive-python/reactpy-router -cd reactpy-router -pip install -e . -r requirements.txt -``` - -This will install an ediable version of `reactpy-router` as well as tools you'll need -to work with this project. - -Of particular note is [`nox`](https://nox.thea.codes/en/stable/), which is used to -automate testing and other development tasks. - -## Running the Tests - -```bash -nox -t test -``` - -You can run the tests with a headed browser. - -```bash -nox -t test -- --headed -``` - -## Releasing This Package - -To release a new version of reactpy-router on PyPI: - -1. Install [`twine`](https://twine.readthedocs.io/en/latest/) with `pip install twine` -2. Update the `version = "x.y.z"` variable in `reactpy-router/__init__.py` -3. `git` add the changes to `__init__.py` and create a `git tag -a x.y.z -m 'comment'` -4. Build the Python package with `python setup.py sdist bdist_wheel` -5. Check the build artifacts `twine check --strict dist/*` -6. Upload the build artifacts to [PyPI](https://pypi.org/) `twine upload dist/*` - -To release a new version of `reactpy-router` on [NPM](https://www.npmjs.com/): - -1. Update `js/package.json` with new npm package version -2. Clean out prior builds `git clean -fdx` -3. Install and publish `npm install && npm publish` diff --git a/docs/src/dictionary.txt b/docs/src/dictionary.txt new file mode 100644 index 0000000..58f6eee --- /dev/null +++ b/docs/src/dictionary.txt @@ -0,0 +1,40 @@ +django +sanic +plotly +nox +WebSocket +WebSockets +changelog +async +pre +prefetch +prefetching +preloader +whitespace +refetch +refetched +refetching +html +jupyter +iframe +keyworded +stylesheet +stylesheets +unstyled +py +reactpy +asgi +postfixed +postprocessing +serializable +postprocessor +preprocessor +middleware +backends +backend +frontend +frontends +misconfiguration +misconfigurations +backhaul +sublicense diff --git a/docs/src/index.md b/docs/src/index.md index 351fd71..4abc843 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,18 +1,4 @@ -# ReactPy Router - -A URL router for [ReactPy](https://reactpy.dev). - -!!! note - - If you don't already know the basics of working with ReactPy, you should - [start there](https://reactpy.dev/docs/guides/getting-started/index.html). - -## Installation - -Use `pip` to install this package: - -```bash -pip install reactpy-router -``` - -[installer.records][] + diff --git a/docs/src/learn/add-reactpy-router-to-your-project.md b/docs/src/learn/add-reactpy-router-to-your-project.md new file mode 100644 index 0000000..d8e29c2 --- /dev/null +++ b/docs/src/learn/add-reactpy-router-to-your-project.md @@ -0,0 +1,11 @@ +## Install from PyPI + +Run the following command to install [`reactpy-router`](https://pypi.org/project/reactpy-router/) in your Python environment. + +```bash linenums="0" +pip install reactpy-router +``` + +## Done! + +You're now ready to start building your own ReactPy applications with routing. diff --git a/docs/src/tutorials/custom-router.md b/docs/src/learn/custom-router.md similarity index 100% rename from docs/src/tutorials/custom-router.md rename to docs/src/learn/custom-router.md diff --git a/docs/src/learn/hooks.md b/docs/src/learn/hooks.md new file mode 100644 index 0000000..819ee62 --- /dev/null +++ b/docs/src/learn/hooks.md @@ -0,0 +1,27 @@ +Several pre-fabricated hooks are provided to help integrate with routing features. You can learn more about them below. + +!!! abstract "Note" + + If you're not familiar what a hook is, you should [read the ReactPy docs](https://reactpy.dev/docs/guides/adding-interactivity/components-with-state/index.html#your-first-hook). + +--- + +## Use Query + +The [`use_query`][reactpy_router.use_query] hook can be used to access query parameters from the current location. It returns a dictionary of query parameters, where each value is a list of strings. + +=== "components.py" + + ```python + {% include "../../examples/python/use-query.py" %} + ``` + +## Use Params + +The [`use_params`][reactpy_router.use_params] hook can be used to access route parameters from the current location. It returns a dictionary of route parameters, where each value is mapped to a value that matches the type specified in the route path. + +=== "components.py" + + ```python + {% include "../../examples/python/use-params.py" %} + ``` diff --git a/docs/src/learn/routers-routes-and-links.md b/docs/src/learn/routers-routes-and-links.md new file mode 100644 index 0000000..471344f --- /dev/null +++ b/docs/src/learn/routers-routes-and-links.md @@ -0,0 +1,67 @@ +We include built-in components that automatically handle routing, which enable Single Page Application (SPA) behavior. + +--- + +## Routers and Routes + +The [`simple.router`][reactpy_router.simple.router] component is one possible implementation of a [Router][reactpy_router.types.Router]. Routers takes a series of [route][reactpy_router.route] objects as positional arguments and render whatever element matches the current location. + +!!! abstract "Note" + + The current location is determined based on the browser's current URL and can be found + by checking the [`use_location`][reactpy.backend.hooks.use_location] hook. + +Here's a basic example showing how to use `#!python simple.router` with two routes. + +=== "components.py" + + ```python + {% include "../../examples/python/basic-routing.py" %} + ``` + +Here we'll note some special syntax in the route path for the second route. The `#!python "*"` is a wildcard that will match any path. This is useful for creating a "404" page that will be shown when no other route matches. + +### Simple Router + +The syntax for declaring routes with the [simple.router][reactpy_router.simple.router] is very similar to the syntax used by [`starlette`](https://www.starlette.io/routing/) (a popular Python web framework). As such route parameters are declared using the following syntax: + +```python linenums="0" +/my/route/{param} +/my/route/{param:type} +``` + +In this case, `#!python param` is the name of the route parameter and the optionally declared `#!python type` specifies what kind of parameter it is. The available parameter types and what patterns they match are are: + +| Type | Pattern | +| --- | --- | +| `#!python str` (default) | `#!python [^/]+` | +| `#!python int` | `#!python \d+` | +| `#!python float` | `#!python \d+(\.\d+)?` | +| `#!python uuid` | `#!python [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}` | +| `#!python path` | `#!python .+` | + +So in practice these each might look like: + +```python linenums="0" +/my/route/{param} +/my/route/{param:int} +/my/route/{param:float} +/my/route/{param:uuid} +/my/route/{param:path} +``` + +Any route parameters collected from the current location then be accessed using the [`use_params`](#using-parameters) hook. + +!!! warning "Pitfall" + + While it is possible to use route parameters to capture values from query strings (such as `#!python /my/route/?foo={bar}`), this is not recommended. Instead, you should use the [`use_query`][reactpy_router.use_query] hook to access query string values. + +## Route Links + +Links between routes should be created using the [link][reactpy_router.link] component. This will allow ReactPy to handle the transition between routes and avoid a page reload. + +=== "components.py" + + ```python + {% include "../../examples/python/route-links.py" %} + ``` diff --git a/docs/src/learn/simple-application.md b/docs/src/learn/simple-application.md new file mode 100644 index 0000000..ad1a604 --- /dev/null +++ b/docs/src/learn/simple-application.md @@ -0,0 +1,88 @@ +

+ +Here you'll learn the various features of `reactpy-router` and how to use them. These examples will utilize the [`reactpy_router.simple.router`][reactpy_router.simple.router]. + +

+ +!!! abstract "Note" + + These docs assume you already know the basics of [ReacPy](https://reactpy.dev). + +--- + +Let's build a simple web application for viewing messages between several people. + +For the purposes of this tutorial we'll be working with the following data. + +```python linenums="0" +message_data = [ + {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, + {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, + {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, + {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"}, + {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"}, + {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."}, + {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"}, + {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"}, +] +``` + +In a more realistic application this data would be stored in a database, but for this tutorial we'll just keep it in memory. + +## Creating Basic Routes + +The first step is to create a basic router that will display the home page when the user navigates to the root of the application, and a "missing link" page for any other route. + +=== "components.py" + + ```python + {% include "../../examples/python/basic-routing.py" %} + ``` + +When navigating to [`http://127.0.0.1:8000``](http://127.0.0.1:8000) you should see `Home Page 🏠`. However, if you go to any other route you will instead see `Missing Link 🔗‍đŸ’Ĩ`. + +With this foundation you can start adding more routes. + +=== "components.py" + + ```python + {% include "../../examples/python/basic-routing-more-routes.py" %} + ``` + +With this change you can now also go to [`/messages`](http://127.0.0.1:8000/messages) to see `Messages đŸ’Ŧ`. + +## Using Route Links + +Instead of using the standard `#!python reactpy.html.a` element to create links to different parts of your application, use `#!python reactpy_router.link` instead. When users click links constructed using `#!python reactpy_router.link`, ReactPy will handle the transition and prevent a full page reload. + +=== "components.py" + + ```python + {% include "../../examples/python/route-links.py" %} + ``` + +Now, when you go to the home page, you can click `Messages` link to go to [`/messages`](http://127.0.0.1:8000/messages). + +## Adding Nested Routes + +Routes can be nested in order to construct more complicated application structures. + +=== "components.py" + + ```python + {% include "../../examples/python/nested-routes.py" %} + ``` + +## Adding Route Parameters + +In the example above we had to manually create a `#!python messages_with(...)` component for each conversation. This would be better accomplished by defining a single route that declares route parameters instead. + +Any parameters that have matched in the currently displayed route can then be consumed with the `#!python use_params` hook which returns a dictionary mapping the parameter names to their values. Note that parameters with a declared type will be converted to is in the parameters dictionary. So for example `#!python /my/route/{my_param:float}` would match `#!python /my/route/3.14` and have a parameter dictionary of `#!python {"my_param": 3.14}`. + +If we take this information and apply it to our growing example application we'd substitute the manually constructed `#!python /messages/with` routes with a single `#!python /messages/with/{names}` route. + +=== "components.py" + + ```python + {% include "../../examples/python/route-parameters.py" %} + ``` diff --git a/docs/src/reference.md b/docs/src/reference.md deleted file mode 100644 index aabc9b3..0000000 --- a/docs/src/reference.md +++ /dev/null @@ -1,5 +0,0 @@ -# Reference - -::: reactpy_router.core -::: reactpy_router.simple -::: reactpy_router.types diff --git a/docs/src/reference/core.md b/docs/src/reference/core.md new file mode 100644 index 0000000..ca9716d --- /dev/null +++ b/docs/src/reference/core.md @@ -0,0 +1 @@ +::: reactpy_router.core diff --git a/docs/src/reference/router.md b/docs/src/reference/router.md new file mode 100644 index 0000000..dfc0806 --- /dev/null +++ b/docs/src/reference/router.md @@ -0,0 +1 @@ +::: reactpy_router.simple diff --git a/docs/src/reference/types.md b/docs/src/reference/types.md new file mode 100644 index 0000000..204bee7 --- /dev/null +++ b/docs/src/reference/types.md @@ -0,0 +1 @@ +::: reactpy_router.types diff --git a/docs/src/tutorials/simple-app.md b/docs/src/tutorials/simple-app.md deleted file mode 100644 index 5f7fcbd..0000000 --- a/docs/src/tutorials/simple-app.md +++ /dev/null @@ -1,277 +0,0 @@ -# Simple Application - -Let's build a simple web application for viewing messages between several people. - -For the purposes of this tutorial we'll be working with the following data: - -```python -message_data = [ - {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, - {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, - {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, - {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"}, - {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"}, - {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."}, - {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"}, - {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"}, -] -``` - -In a more realistic application this data would be stored in a database, but for this -tutorial we'll just keep it in memory. - -## Basic Routing - -The first step is to create a basic router that will display the home page when the -user navigates to the root of the application, and a "missing link" page for any other -route: - -```python -from reactpy import component, html, run -from reactpy_router import route, simple - -@component -def root(): - return simple.router( - route("/", html.h1("Home Page 🏠")), - route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), - ) - -run(root) -``` - -When navigating to http://127.0.0.1:8000 you should see "Home Page 🏠". However, if you -go to any other route (e.g. http://127.0.0.1:8000/missing) you will instead see the -"Missing Link 🔗‍đŸ’Ĩ" page. - -With this foundation you can start adding more routes: - -```python -from reactpy import component, html, run -from reactpy_router import route, simple - -@component -def root(): - return simple.router( - route("/", html.h1("Home Page 🏠")), - route("/messages", html.h1("Messages đŸ’Ŧ")), - route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), - ) - -run(root) -``` - -With this change you can now also go to `/messages` to see "Messages đŸ’Ŧ" displayed. - -## Route Links - -Instead of using the standard `` element to create links to different parts of your -application, use `reactpy_router.link` instead. When users click links constructed using -`reactpy_router.link`, instead of letting the browser navigate to the associated route, -ReactPy will more quickly handle the transition by avoiding the cost of a full page -load. - -```python -from reactpy import component, html, run -from reactpy_router import link, route, simple - -@component -def root(): - return simple.router( - route("/", home()), - route("/messages", html.h1("Messages đŸ’Ŧ")), - route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), - ) - -@component -def home(): - return html.div( - html.h1("Home Page 🏠"), - link("Messages", to="/messages"), - ) - -run(root) -``` - -Now, when you go to the home page, you can click the link to go to `/messages`. - -## Nested Routes - -Routes can be nested in order to construct more complicated application structures: - -```python -from reactpy import component, html, run -from reactpy_router import route, simple, link - -message_data = [ - {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, - {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, - {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, - {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"}, - {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"}, - {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."}, - {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"}, - {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"}, -] - -@component -def root(): - return simple.router( - route("/", home()), - route( - "/messages", - all_messages(), - # we'll improve upon these manually created routes in the next section... - route("/with/Alice", messages_with("Alice")), - route("/with/Alice-Bob", messages_with("Alice", "Bob")), - ), - route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), - ) - -@component -def home(): - return html.div( - html.h1("Home Page 🏠"), - link("Messages", to="/messages"), - ) - -@component -def all_messages(): - last_messages = { - ", ".join(msg["with"]): msg - for msg in sorted(message_data, key=lambda m: m["id"]) - } - return html.div( - html.h1("All Messages đŸ’Ŧ"), - html.ul( - [ - html.li( - {"key": msg["id"]}, - html.p( - link( - f"Conversation with: {', '.join(msg['with'])}", - to=f"/messages/with/{'-'.join(msg['with'])}", - ), - ), - f"{'' if msg['from'] is None else '🔴'} {msg['message']}", - ) - for msg in last_messages.values() - ] - ), - ) - -@component -def messages_with(*names): - names = set(names) - messages = [msg for msg in message_data if set(msg["with"]) == names] - return html.div( - html.h1(f"Messages with {', '.join(names)} đŸ’Ŧ"), - html.ul( - [ - html.li( - {"key": msg["id"]}, - f"{msg['from'] or 'You'}: {msg['message']}", - ) - for msg in messages - ] - ), - ) - -run(root) -``` - -## Route Parameters - -In the example above we had to manually create a `messages_with(...)` component for each -conversation. This would be better accomplished by defining a single route that declares -["route parameters"](../usage.md#simple-router) instead. - -Any parameters that have matched in the currently displayed route can then be consumed -with the `use_params` hook which returns a dictionary mapping the parameter names to -their values. Note that parameters with a declared type will be converted to is in the -parameters dictionary. So for example `/my/route/{my_param:float}` would match -`/my/route/3.14` and have a parameter dictionary of `{"my_param": 3.14}`. - -If we take this information and apply it to our growing example application we'd -substitute the manually constructed `/messages/with` routes with a single -`/messages/with/{names}` route: - -```python -from reactpy import component, html, run -from reactpy_router import route, simple, link -from reactpy_router.core import use_params - -message_data = [ - {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, - {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, - {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, - {"id": 4, "with": ["Alice"], "from": "Alice", "message": "Good, thanks!"}, - {"id": 5, "with": ["Alice", "Bob"], "from": None, "message": "We meeting now?"}, - {"id": 6, "with": ["Alice", "Bob"], "from": "Alice", "message": "Not sure."}, - {"id": 7, "with": ["Alice", "Bob"], "from": "Bob", "message": "I'm here!"}, - {"id": 8, "with": ["Alice", "Bob"], "from": None, "message": "Great!"}, -] - -@component -def root(): - return simple.router( - route("/", home()), - route( - "/messages", - all_messages(), - route("/with/{names}", messages_with()), # note the path param - ), - route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), - ) - -@component -def home(): - return html.div( - html.h1("Home Page 🏠"), - link("Messages", to="/messages"), - ) - -@component -def all_messages(): - last_messages = { - ", ".join(msg["with"]): msg - for msg in sorted(message_data, key=lambda m: m["id"]) - } - return html.div( - html.h1("All Messages đŸ’Ŧ"), - html.ul( - [ - html.li( - {"key": msg["id"]}, - html.p( - link( - f"Conversation with: {', '.join(msg['with'])}", - to=f"/messages/with/{'-'.join(msg['with'])}", - ), - ), - f"{'' if msg['from'] is None else '🔴'} {msg['message']}", - ) - for msg in last_messages.values() - ] - ), - ) - -@component -def messages_with(): - names = set(use_params()["names"].split("-")) # and here we use the path param - messages = [msg for msg in message_data if set(msg["with"]) == names] - return html.div( - html.h1(f"Messages with {', '.join(names)} đŸ’Ŧ"), - html.ul( - [ - html.li( - {"key": msg["id"]}, - f"{msg['from'] or 'You'}: {msg['message']}", - ) - for msg in messages - ] - ), - ) - -run(root) -``` diff --git a/docs/src/usage.md b/docs/src/usage.md deleted file mode 100644 index e0f7bb2..0000000 --- a/docs/src/usage.md +++ /dev/null @@ -1,180 +0,0 @@ -# Usage - -!!! note - - The sections below assume you already know the basics of [ReacPy](https://reactpy.dev). - -Here you'll learn the various features of `reactpy-router` and how to use them. All examples -will utilize the [simple.router][reactpy_router.simple.router] (though you can [use your own](#custom-routers)). - -## Routers and Routes - -The [simple.router][reactpy_router.simple.router] component is one possible -implementation of a [Router][reactpy_router.types.Router]. Routers takes a series of -[Route][reactpy_router.types.Route] objects as positional arguments and render whatever -element matches the current location. For convenience, these `Route` objects are created -using the [route][reactpy_router.route] function. - -!!! note - - The current location is determined based on the browser's current URL and can be found - by checking the [use_location][reactpy.backend.hooks.use_location] hook. - -Here's a basic example showing how to use `simple.router` with two routes: - -```python -from reactpy import component, html, run -from reactpy_router import route, simple, use_location - -@component -def root(): - location = use_location() - return simple.router( - route("/", html.h1("Home Page 🏠")), - route("*", html.h1("Missing Link 🔗‍đŸ’Ĩ")), - ) -``` - -Here we'll note some special syntax in the route path for the second route. The `*` is a -wildcard that will match any path. This is useful for creating a "404" page that will be -shown when no other route matches. - -### Simple Router - -The syntax for declaring routes with the [simple.router][reactpy_router.simple.router] -is very similar to the syntax used by [Starlette](https://www.starlette.io/routing/) (a -popular Python web framework). As such route parameters are declared using the following -syntax: - -``` -/my/route/{param} -/my/route/{param:type} -``` - -In this case, `param` is the name of the route parameter and the optionally declared -`type` specifies what kind of parameter it is. The available parameter types and what -patterns they match are are: - -- str (default) - `[^/]+` -- int - `\d+` -- float - `\d+(\.\d+)?` -- uuid - `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}` -- path - `.+` - -!!! note - - The `path` type is special in that it will match any path, including `/` characters. - This is useful for creating routes that match a path prefix. - -So in practice these each might look like: - -``` -/my/route/{param} -/my/route/{param:int} -/my/route/{param:float} -/my/route/{param:uuid} -/my/route/{param:path} -``` - -Any route parameters collected from the current location then be accessed using the -[`use_params`](#using-parameters) hook. - -!!! note - - It's worth pointing out that, while you can use route parameters to capture values - from queryies (i.e. `?foo=bar`), this is not recommended. Instead, you should use - the [use_query][reactpy_router.use_query] hook to access query parameters. - -### Route Links - -Links between routes should be created using the [link][reactpy_router.link] component. -This will allow ReactPy to handle the transition between routes more quickly by avoiding -the cost of a full page load. - -```python -from reactpy import component, html, run, use_location -from reactpy_router import link, route, simple - -@component -def root(): - use_location() - return simple.router( - route( - "/", - html.div( - html.h1("Home Page 🏠"), - link(html.button("About"), to="/about"), - ), - ), - route("/about", html.h1("About Page 📖")), - ) -``` - -## Hooks - -`reactpy-router` provides a number of hooks for working with the routes: - -- [`use_query`](#using-queries) - for accessing query parameters -- [`use_params`](#using-parameters) - for accessing route parameters - -If you're not familiar with hooks, you should -[read the docs](https://reactpy.dev/docs/guides/adding-interactivity/components-with-state/index.html#your-first-hook). - -### Using Queries - -The [use_query][reactpy_router.use_query] hook can be used to access query parameters -from the current location. It returns a dictionary of query parameters, where each value -is a list of strings. - -```python -from reactpy import component, html, run -from reactpy_router import link, route, simple, use_query - -@component -def root(): - use_location() - return simple.router( - route( - "/", - html.div( - html.h1("Home Page 🏠"), - link("Search", to="/search?q=reactpy"), - ), - ), - route("/about", html.h1("About Page 📖")), - ) - -@component -def search(): - query = use_query() - return html.h1(f"Search Results for {query['q'][0]} 🔍") -``` - -### Using Parameters - -The [use_params][reactpy_router.use_params] hook can be used to access route parameters -from the current location. It returns a dictionary of route parameters, where each value -is mapped to a value that matches the type specified in the route path. - -```python -from reactpy import component, html, run -from reactpy_router import link, route, simple, use_params - -@component -def root(): - return simple.router( - route( - "/", - html.div( - html.h1("Home Page 🏠"), - link("User 123", to="/user/123"), - ), - ), - route("/user/{id:int}", user()), - ) - -@component -def user(): - params = use_params() - return html.h1(f"User {params['id']} 👤") -``` diff --git a/pyproject.toml b/pyproject.toml index d645c38..763f3a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,22 @@ [build-system] -requires = ["setuptools>=40.8.0", "wheel"] +requires = ["setuptools>=42", "wheel", "nodejs-bin==18.4.0a4"] build-backend = "setuptools.build_meta" - -[tool.pytest.ini_options] -testpaths = "tests" -asyncio_mode = "auto" - - -[tool.isort] -profile = "black" - - [tool.mypy] ignore_missing_imports = true warn_unused_configs = true warn_redundant_casts = true warn_unused_ignores = true +check_untyped_defs = true + +[tool.ruff.isort] +known-first-party = ["src", "tests"] + +[tool.ruff] +ignore = ["E501"] +extend-exclude = [".venv/*", ".eggs/*", ".nox/*", "build/*"] +line-length = 120 + +[tool.pytest.ini_options] +testpaths = "tests" +asyncio_mode = "auto" diff --git a/requirements.txt b/requirements.txt index 5893bf2..7c5eed9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ -r requirements/build-docs.txt +-r requirements/build-pkg.txt -r requirements/check-style.txt -r requirements/check-types.txt --r requirements/nox-deps.txt -r requirements/pkg-deps.txt -r requirements/test-env.txt +-r requirements/test-run.txt diff --git a/requirements/build-docs.txt b/requirements/build-docs.txt index 3f19165..0d2bca2 100644 --- a/requirements/build-docs.txt +++ b/requirements/build-docs.txt @@ -1,5 +1,11 @@ mkdocs -mkdocs-material -mkdocs-gen-files -mkdocs-literate-nav +mkdocs-git-revision-date-localized-plugin +mkdocs-material==9.4.0 +mkdocs-include-markdown-plugin +linkcheckmd +mkdocs-spellcheck[all] +mkdocs-git-authors-plugin +mkdocs-minify-plugin +mkdocs-section-index +mike mkdocstrings[python] diff --git a/requirements/build-pkg.txt b/requirements/build-pkg.txt new file mode 100644 index 0000000..88ec271 --- /dev/null +++ b/requirements/build-pkg.txt @@ -0,0 +1,3 @@ +twine +wheel +setuptools diff --git a/requirements/check-style.txt b/requirements/check-style.txt index 9a48a39..dc109e2 100644 --- a/requirements/check-style.txt +++ b/requirements/check-style.txt @@ -1,5 +1,2 @@ black -flake8 -flake8-print -reactpy-flake8 -isort +ruff diff --git a/requirements/test-env.txt b/requirements/test-env.txt index b6cdf49..4ddd635 100644 --- a/requirements/test-env.txt +++ b/requirements/test-env.txt @@ -3,3 +3,4 @@ pytest pytest-asyncio pytest-cov reactpy[testing,starlette] +nodejs-bin==18.4.0a4 diff --git a/requirements/nox-deps.txt b/requirements/test-run.txt similarity index 100% rename from requirements/nox-deps.txt rename to requirements/test-run.txt diff --git a/setup.cfg b/setup.cfg index 8d1ef40..3c6e79c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,21 +1,2 @@ [bdist_wheel] universal=1 - -[flake8] -ignore = E203, E266, E501, W503, F811, N802 -max-line-length = 88 -extend-exclude = - .nox - venv - .venv - tests/cases/* - -[coverage:report] -fail_under = 100 -show_missing = True -skip_covered = True -sort = Miss -exclude_lines = - pragma: no cover - \.\.\. - raise NotImplementedError diff --git a/setup.py b/setup.py index f3c5f79..46cf5fb 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,32 @@ from __future__ import print_function -import os -import shutil -import subprocess import sys +import traceback +from distutils import log +from pathlib import Path -from setuptools import find_packages, setup +from nodejs import npm +from setuptools import find_namespace_packages, setup from setuptools.command.develop import develop from setuptools.command.sdist import sdist -# the name of the project +# ----------------------------------------------------------------------------- +# Basic Constants +# ----------------------------------------------------------------------------- name = "reactpy_router" - -# basic paths used to gather files -here = os.path.abspath(os.path.dirname(__file__)) -package_dir = os.path.join(here, name) +root_dir = Path(__file__).parent +src_dir = root_dir / "src" +package_dir = src_dir / name # ----------------------------------------------------------------------------- # General Package Info # ----------------------------------------------------------------------------- - - package = { "name": name, "python_requires": ">=3.9", - "packages": find_packages(exclude=["tests*"]), - "description": "A URL router for ReactPy", + "packages": find_namespace_packages(str(src_dir)), + "description": "A URL router for ReactPy.", "author": "Ryan Morshead", "author_email": "ryan.morshead@gmail.com", "url": "https://github.com/reactive-python/reactpy-router", @@ -38,9 +38,9 @@ "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Science/Research", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Software Development :: User Interfaces", "Topic :: Software Development :: Widget Sets", "Typing :: Typed", @@ -49,38 +49,30 @@ # ----------------------------------------------------------------------------- -# Requirements +# Library Version # ----------------------------------------------------------------------------- - - -requirements = [] -with open(os.path.join(here, "requirements", "pkg-deps.txt"), "r") as f: - for line in map(str.strip, f): - if not line.startswith("#"): - requirements.append(line) -package["install_requires"] = requirements +for line in (package_dir / "__init__.py").read_text().split("\n"): + if line.startswith("__version__ = "): + package["version"] = eval(line.split("=", 1)[1]) + break +else: + print(f"No version found in {package_dir}/__init__.py") + sys.exit(1) # ----------------------------------------------------------------------------- -# Library Version +# Requirements # ----------------------------------------------------------------------------- - -with open(os.path.join(package_dir, "__init__.py")) as init_file: - for line in init_file: - if line.split("=", 1)[0].strip() == "__version__": - package["version"] = eval(line.split("=", 1)[1]) - break - else: - print("No version found in %s/__init__.py" % package_dir) # noqa: T201 - sys.exit(1) +requirements: list[str] = [] +with (root_dir / "requirements" / "pkg-deps.txt").open() as f: + requirements.extend(line for line in map(str.strip, f) if not line.startswith("#")) +package["install_requires"] = requirements # ----------------------------------------------------------------------------- # Library Description # ----------------------------------------------------------------------------- - - -with open(os.path.join(here, "README.md")) as f: +with (root_dir / "README.md").open() as f: long_description = f.read() package["long_description"] = long_description @@ -90,16 +82,26 @@ # ---------------------------------------------------------------------------- # Build Javascript # ---------------------------------------------------------------------------- - - -def build_javascript_first(cls): - class Command(cls): +def build_javascript_first(build_cls: type): + class Command(build_cls): def run(self): - npm = shutil.which("npm") # this is required on windows - if npm is None: - raise RuntimeError("NPM is not installed.") - for cmd_str in [f"{npm} install", f"{npm} run build"]: - subprocess.check_call(cmd_str.split(), cwd=os.path.join(here, "js")) + js_dir = str(src_dir / "js") + + log.info("Installing Javascript...") + result = npm.call(["install"], cwd=js_dir) + if result != 0: + log.error(traceback.format_exc()) + log.error("Failed to install Javascript") + raise RuntimeError("Failed to install Javascript") + + log.info("Building Javascript...") + result = npm.call(["run", "build"], cwd=js_dir) + if result != 0: + log.error(traceback.format_exc()) + log.error("Failed to build Javascript") + raise RuntimeError("Failed to build Javascript") + + log.info("Successfully built Javascript") super().run() return Command @@ -121,9 +123,7 @@ def run(self): # ----------------------------------------------------------------------------- -# Install It +# Installation # ----------------------------------------------------------------------------- - - if __name__ == "__main__": setup(**package) diff --git a/js/.eslintrc.json b/src/js/.eslintrc.json similarity index 100% rename from js/.eslintrc.json rename to src/js/.eslintrc.json diff --git a/js/README.md b/src/js/README.md similarity index 100% rename from js/README.md rename to src/js/README.md diff --git a/js/package-lock.json b/src/js/package-lock.json similarity index 100% rename from js/package-lock.json rename to src/js/package-lock.json diff --git a/js/package.json b/src/js/package.json similarity index 100% rename from js/package.json rename to src/js/package.json diff --git a/js/rollup.config.js b/src/js/rollup.config.js similarity index 100% rename from js/rollup.config.js rename to src/js/rollup.config.js diff --git a/js/src/index.js b/src/js/src/index.js similarity index 100% rename from js/src/index.js rename to src/js/src/index.js diff --git a/reactpy_router/__init__.py b/src/reactpy_router/__init__.py similarity index 100% rename from reactpy_router/__init__.py rename to src/reactpy_router/__init__.py diff --git a/reactpy_router/core.py b/src/reactpy_router/core.py similarity index 100% rename from reactpy_router/core.py rename to src/reactpy_router/core.py diff --git a/reactpy_router/py.typed b/src/reactpy_router/py.typed similarity index 100% rename from reactpy_router/py.typed rename to src/reactpy_router/py.typed diff --git a/reactpy_router/simple.py b/src/reactpy_router/simple.py similarity index 100% rename from reactpy_router/simple.py rename to src/reactpy_router/simple.py diff --git a/reactpy_router/types.py b/src/reactpy_router/types.py similarity index 100% rename from reactpy_router/types.py rename to src/reactpy_router/types.py From 9f2ed8592dbd64ad9db4f2d92cd15f984247b382 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:33:48 -0800 Subject: [PATCH 02/29] fix docs test --- .github/workflows/test-docs.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 22354c8..299ded3 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -26,11 +26,12 @@ jobs: linkcheckMarkdown docs/ -v -r linkcheckMarkdown README.md -v -r linkcheckMarkdown CHANGELOG.md -v -r + cd docs 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/examples/python/ - black docs/examples/python/ --check - ruff check docs/examples/python/ + mypy --show-error-codes examples/python/ + black examples/python/ --check + ruff check examples/python/ From 9b15d4b3f1c1ddddaf6bd73d18f45c5a27de6cec Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:34:01 -0800 Subject: [PATCH 03/29] use new github templates --- .github/CODEOWNERS | 1 + .github/FUNDING.yml | 12 ++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 24 ----------------------- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ .github/ISSUE_TEMPLATE/doc_enhancement.md | 14 ------------- .github/ISSUE_TEMPLATE/feature_request.md | 20 ------------------- .github/ISSUE_TEMPLATE/issue-form.yml | 16 +++++++++++++++ .github/pull_request_template.md | 14 +++++++++++++ 8 files changed, 48 insertions(+), 58 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/FUNDING.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/doc_enhancement.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/issue-form.yml create mode 100644 .github/pull_request_template.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..04e5a15 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @reactive-python/django diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..12f72a6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [archmonger] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index c6bdd8f..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: Bug Report -labels: bug -assignees: rmorshea - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..b0fd461 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Start a Discussion + url: https://github.com/reactive-python/reactpy-django/discussions + about: Report issues, request features, ask questions, and share ideas diff --git a/.github/ISSUE_TEMPLATE/doc_enhancement.md b/.github/ISSUE_TEMPLATE/doc_enhancement.md deleted file mode 100644 index 9a960b0..0000000 --- a/.github/ISSUE_TEMPLATE/doc_enhancement.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Doc enhancement -about: Documentation needs to be fixed or added -title: Doc Enhancement -labels: docs -assignees: rmorshea - ---- - -**Describe what documentation needs to be fixed or added** -Is something missing, worded poorly, or flat out wrong? Tells us about it here. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 1c5de5f..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: rmorshea - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/issue-form.yml b/.github/ISSUE_TEMPLATE/issue-form.yml new file mode 100644 index 0000000..b4a4b89 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue-form.yml @@ -0,0 +1,16 @@ +name: Plan a Task +description: Create a detailed plan of action (ONLY START AFTER DISCUSSION PLEASE 🙏). +labels: ["flag: triage"] +body: +- type: textarea + attributes: + label: Current Situation + description: Discuss how things currently are, why they require action, and any relevant prior discussion/context. + validations: + required: false +- type: textarea + attributes: + label: Proposed Actions + description: Describe what ought to be done, and why that will address the reasons for action mentioned above. + validations: + required: false diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..a555320 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,14 @@ +## Description + + + +## Checklist + +Please update this checklist as you complete each item: + +- [ ] Tests have been developed for bug fixes or new functionality. +- [ ] The changelog has been updated, if necessary. +- [ ] Documentation has been updated, if necessary. +- [ ] GitHub Issues closed by this PR have been linked. + +By submitting this pull request I agree that all contributions comply with this project's open source license(s). From f6a1993703f5fa348bddfef90d29e820e3cb67ad Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:36:52 -0800 Subject: [PATCH 04/29] fix watch dirs --- docs/mkdocs.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 76c5036..b639017 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -121,8 +121,10 @@ extra_css: watch: - "../docs" - - "../reactpy_router" - - "../CHANGELOG.md" + - ../README.md + - ../CHANGELOG.md + - ../LICENSE.md + - "../src" site_name: ReactPy Router site_author: Archmonger From c47a144dc647ee166b83227ef2f501dbf1675271 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 20 Jan 2024 23:57:12 -0800 Subject: [PATCH 05/29] noxfile refactoring --- .github/workflows/test-src.yaml | 2 +- docs/src/about/code.md | 4 +- noxfile.py | 68 +++++++++++---------------------- 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test-src.yaml b/.github/workflows/test-src.yaml index c7ec831..c7de8ac 100644 --- a/.github/workflows/test-src.yaml +++ b/.github/workflows/test-src.yaml @@ -25,4 +25,4 @@ jobs: - name: Install Python Dependencies run: pip install -r requirements/test-run.txt - name: Run Tests - run: nox -s test + run: nox -t test diff --git a/docs/src/about/code.md b/docs/src/about/code.md index e8ec388..439ad1c 100644 --- a/docs/src/about/code.md +++ b/docs/src/about/code.md @@ -39,7 +39,7 @@ pip install -e . -r requirements.txt --upgrade --verbose By running the command below you can run the full test suite: ```bash linenums="0" -nox -s test +nox -t test ``` Or, if you want to run the tests in the foreground with a visible browser window, run: @@ -47,7 +47,7 @@ Or, if you want to run the tests in the foreground with a visible browser window ```bash linenums="0" -nox -s test -- --headed +nox -t test -- --headed ``` ## Creating a pull request diff --git a/noxfile.py b/noxfile.py index 1fddabb..6c5d3d4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -2,46 +2,12 @@ from nox import Session, session -ROOT = Path(".") -REQUIREMENTS_DIR = ROOT / "requirements" - - -@session -def format(session: Session) -> None: - install_requirements(session, "check-style") - session.run("black", ".") - session.run("isort", ".") - - -@session -def docs(session: Session) -> None: - setup_docs(session) - session.run("mkdocs", "serve") - - -@session -def docs_build(session: Session) -> None: - setup_docs(session) - session.run("mkdocs", "build") - - -@session(tags=["test"]) -def test_style(session: Session) -> None: - install_requirements(session, "check-style") - session.run("black", "--check", ".") - session.run("isort", "--check", ".") - session.run("flake8", ".") - - -@session(tags=["test"]) -def test_types(session: Session) -> None: - install_requirements(session, "check-types") - session.run("mypy", "--strict", "reactpy_router") +ROOT_DIR = Path(__file__).parent @session(tags=["test"]) def test_suite(session: Session) -> None: - install_requirements(session, "test-env") + install_requirements_file(session, "test-env") session.run("playwright", "install", "chromium") posargs = session.posargs[:] @@ -58,17 +24,27 @@ def test_suite(session: Session) -> None: @session(tags=["test"]) -def test_javascript(session: Session) -> None: - session.chdir(ROOT / "js") - session.run("npm", "install", external=True) - session.run("npm", "run", "check") +def test_types(session: Session) -> None: + install_requirements_file(session, "check-types") + install_requirements_file(session, "pkg-deps") + session.run("mypy", "--show-error-codes", "src/reactpy_router", "tests/test_app") -def setup_docs(session: Session) -> None: - install_requirements(session, "build-docs") - session.install("-e", ".") - session.chdir("docs") +@session(tags=["test"]) +def test_style(session: Session) -> None: + install_requirements_file(session, "check-style") + session.run("black", ".", "--check") + session.run("ruff", "check", ".") + + +@session(tags=["test"]) +def test_javascript(session: Session) -> None: + session.chdir(ROOT_DIR / "src" / "js") + session.run("python", "-m", "nodejs.npm", "install", external=True) + session.run("python", "-m", "nodejs.npm", "run", "check") -def install_requirements(session: Session, name: str) -> None: - session.install("-r", str(REQUIREMENTS_DIR / f"{name}.txt")) +def install_requirements_file(session: Session, name: str) -> None: + file_path = ROOT_DIR / "requirements" / f"{name}.txt" + assert file_path.exists(), f"requirements file {file_path} does not exist" + session.install("-r", str(file_path)) From f0efd361a81f8c1cb5173324f497c5195ded93b8 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:07:12 -0800 Subject: [PATCH 06/29] fix broken code links --- docs/src/learn/hooks.md | 4 ++-- docs/src/learn/routers-routes-and-links.md | 8 ++++---- docs/src/learn/simple-application.md | 2 +- docs/src/reference/core.md | 2 +- docs/src/reference/router.md | 2 +- docs/src/reference/types.md | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/learn/hooks.md b/docs/src/learn/hooks.md index 819ee62..a92481b 100644 --- a/docs/src/learn/hooks.md +++ b/docs/src/learn/hooks.md @@ -8,7 +8,7 @@ Several pre-fabricated hooks are provided to help integrate with routing feature ## Use Query -The [`use_query`][reactpy_router.use_query] hook can be used to access query parameters from the current location. It returns a dictionary of query parameters, where each value is a list of strings. +The [`use_query`][src.reactpy_router.use_query] hook can be used to access query parameters from the current location. It returns a dictionary of query parameters, where each value is a list of strings. === "components.py" @@ -18,7 +18,7 @@ The [`use_query`][reactpy_router.use_query] hook can be used to access query par ## Use Params -The [`use_params`][reactpy_router.use_params] hook can be used to access route parameters from the current location. It returns a dictionary of route parameters, where each value is mapped to a value that matches the type specified in the route path. +The [`use_params`][src.reactpy_router.use_params] hook can be used to access route parameters from the current location. It returns a dictionary of route parameters, where each value is mapped to a value that matches the type specified in the route path. === "components.py" diff --git a/docs/src/learn/routers-routes-and-links.md b/docs/src/learn/routers-routes-and-links.md index 471344f..af62578 100644 --- a/docs/src/learn/routers-routes-and-links.md +++ b/docs/src/learn/routers-routes-and-links.md @@ -4,7 +4,7 @@ We include built-in components that automatically handle routing, which enable S ## Routers and Routes -The [`simple.router`][reactpy_router.simple.router] component is one possible implementation of a [Router][reactpy_router.types.Router]. Routers takes a series of [route][reactpy_router.route] objects as positional arguments and render whatever element matches the current location. +The [`simple.router`][src.reactpy_router.simple.router] component is one possible implementation of a [Router][src.reactpy_router.types.Router]. Routers takes a series of [route][src.reactpy_router.route] objects as positional arguments and render whatever element matches the current location. !!! abstract "Note" @@ -23,7 +23,7 @@ Here we'll note some special syntax in the route path for the second route. The ### Simple Router -The syntax for declaring routes with the [simple.router][reactpy_router.simple.router] is very similar to the syntax used by [`starlette`](https://www.starlette.io/routing/) (a popular Python web framework). As such route parameters are declared using the following syntax: +The syntax for declaring routes with the [simple.router][src.reactpy_router.simple.router] is very similar to the syntax used by [`starlette`](https://www.starlette.io/routing/) (a popular Python web framework). As such route parameters are declared using the following syntax: ```python linenums="0" /my/route/{param} @@ -54,11 +54,11 @@ Any route parameters collected from the current location then be accessed using !!! warning "Pitfall" - While it is possible to use route parameters to capture values from query strings (such as `#!python /my/route/?foo={bar}`), this is not recommended. Instead, you should use the [`use_query`][reactpy_router.use_query] hook to access query string values. + While it is possible to use route parameters to capture values from query strings (such as `#!python /my/route/?foo={bar}`), this is not recommended. Instead, you should use the [`use_query`][src.reactpy_router.use_query] hook to access query string values. ## Route Links -Links between routes should be created using the [link][reactpy_router.link] component. This will allow ReactPy to handle the transition between routes and avoid a page reload. +Links between routes should be created using the [link][src.reactpy_router.link] component. This will allow ReactPy to handle the transition between routes and avoid a page reload. === "components.py" diff --git a/docs/src/learn/simple-application.md b/docs/src/learn/simple-application.md index ad1a604..8f2a5b5 100644 --- a/docs/src/learn/simple-application.md +++ b/docs/src/learn/simple-application.md @@ -1,6 +1,6 @@

-Here you'll learn the various features of `reactpy-router` and how to use them. These examples will utilize the [`reactpy_router.simple.router`][reactpy_router.simple.router]. +Here you'll learn the various features of `reactpy-router` and how to use them. These examples will utilize the [`reactpy_router.simple.router`][src.reactpy_router.simple.router].

diff --git a/docs/src/reference/core.md b/docs/src/reference/core.md index ca9716d..26cf9e5 100644 --- a/docs/src/reference/core.md +++ b/docs/src/reference/core.md @@ -1 +1 @@ -::: reactpy_router.core +::: src.reactpy_router.core diff --git a/docs/src/reference/router.md b/docs/src/reference/router.md index dfc0806..2fcea59 100644 --- a/docs/src/reference/router.md +++ b/docs/src/reference/router.md @@ -1 +1 @@ -::: reactpy_router.simple +::: src.reactpy_router.simple diff --git a/docs/src/reference/types.md b/docs/src/reference/types.md index 204bee7..0482432 100644 --- a/docs/src/reference/types.md +++ b/docs/src/reference/types.md @@ -1 +1 @@ -::: reactpy_router.types +::: src.reactpy_router.types From 084e6ec551690dfdce15ea30cea375d387a0bf0c Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:10:43 -0800 Subject: [PATCH 07/29] Don't spellcheck auto-docs --- docs/mkdocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b639017..e76cf4e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -87,6 +87,10 @@ plugins: known_words: dictionary.txt allow_unicode: no ignore_code: yes + skip_files: + - "index.md" + - "reference\\core.md" + - "reference\\types.md" - mkdocstrings: default_handler: python handlers: From 6406da58752cc387b984f4c264bb4158f978a09f Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:11:20 -0800 Subject: [PATCH 08/29] Fix docs spelling checks --- CHANGELOG.md | 14 ++++++++------ docs/src/learn/hooks.md | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02502ef..c673a2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,25 +40,27 @@ Using the following categories, list your changes in this order: ### Fixed -- Fixed relative navigation +- Fixed relative navigation. ## [0.1.0] - 2023-06-16 ### Added -- Automatically handle client-side history changes +- Automatically handle client-side history changes. ## [0.0.1] - 2023-05-10 ### Added -- Add robust lint/testing + upgrade `reactpy` + more robust routing with Starlette -- Initial draft of router compiler +- Add robust lint/testing. +- Upgrade `reactpy`. +- More robust routing with `starlette`. +- Initial draft of router compiler. ### Changed -- Rename `configure` to `create_router` -- Rename from `idom-router` to `reactpy-router` +- Rename `configure` to `create_router`. +- Rename from `idom-router` to `reactpy-router`. [Unreleased]: https://github.com/reactive-python/reactpy-router/compare/0.1.1...HEAD [0.1.1]: https://github.com/reactive-python/reactpy-router/compare/0.1.0...0.1.1 diff --git a/docs/src/learn/hooks.md b/docs/src/learn/hooks.md index a92481b..3479ffc 100644 --- a/docs/src/learn/hooks.md +++ b/docs/src/learn/hooks.md @@ -16,7 +16,7 @@ The [`use_query`][src.reactpy_router.use_query] hook can be used to access query {% include "../../examples/python/use-query.py" %} ``` -## Use Params +## Use Parameters The [`use_params`][src.reactpy_router.use_params] hook can be used to access route parameters from the current location. It returns a dictionary of route parameters, where each value is mapped to a value that matches the type specified in the route path. From 6960fd2accf641efbc814a8eab85216453ef2a0c Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:28:26 -0800 Subject: [PATCH 09/29] remove references to django --- .github/CODEOWNERS | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- docs/overrides/home.html | 135 ------------------------------ docs/src/about/code.md | 8 +- docs/src/dictionary.txt | 1 - 5 files changed, 5 insertions(+), 143 deletions(-) delete mode 100644 docs/overrides/home.html diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 04e5a15..1ff35c8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @reactive-python/django +* @reactive-python/maintainers diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b0fd461..36e5aeb 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Start a Discussion - url: https://github.com/reactive-python/reactpy-django/discussions + url: https://github.com/reactive-python/reactpy-router/discussions about: Report issues, request features, ask questions, and share ideas diff --git a/docs/overrides/home.html b/docs/overrides/home.html deleted file mode 100644 index 67e3144..0000000 --- a/docs/overrides/home.html +++ /dev/null @@ -1,135 +0,0 @@ - -{% extends "main.html" %} - - -{% block content %}{% endblock %} - - -{% block tabs %} - -
-
- -

{{ config.site_name }}

-

{{ config.site_description }}

-
-
- -
-

Create user interfaces from components

-

- ReactPy lets you build user interfaces out of individual pieces called components. Create your own ReactPy - components like thumbnail, like_button, and video. Then combine - them into entire screens, pages, and apps. -

-
- {% with image="create-user-interfaces.png", class="pop-left" %} - {% include "home-code-examples/code-block.html" %} - {% endwith %} - {% include "home-code-examples/create-user-interfaces-demo.html" %} -
-

- Whether you work on your own or with thousands of other developers, using React feels the same. It is - designed to let you seamlessly combine components written by independent people, teams, and - organizations. -

-
- -
-

Write components with pure Python code

-

- ReactPy components are Python functions. Want to show some content conditionally? Use an - if statement. Displaying a list? Try using - list comprehension. - Learning ReactPy is learning programming. -

-
- {% with image="write-components-with-python.png", class="pop-left" %} - {% include "home-code-examples/code-block.html" %} - {% endwith %} - {% include "home-code-examples/write-components-with-python-demo.html" %} - -
-
- -
-

Add interactivity wherever you need it

-

- ReactPy components receive data and return what should appear on the screen. You can pass them new data in - response to an interaction, like when the user types into an input. ReactPy will then update the screen to - match the new data. -

-
- {% with image="add-interactivity.png" %} - {% include "home-code-examples/code-block.html" %} - {% endwith %} - {% include "home-code-examples/add-interactivity-demo.html" %} -
-

- You don't have to build your whole page in ReactPy. Add React to your existing HTML page, and render - interactive ReactPy components anywhere on it. -

-
- -
-

Go full-stack with the Django framework

-

- ReactPy is a library. It lets you put components together, but it doesn't prescribe how to do routing and - data fetching. To build an entire app with ReactPy, we recommend a backend framework like - Django. -

- - Get Started - -
-
-{% endblock %} diff --git a/docs/src/about/code.md b/docs/src/about/code.md index 439ad1c..5d73042 100644 --- a/docs/src/about/code.md +++ b/docs/src/about/code.md @@ -18,16 +18,14 @@ If you plan to make code changes to this repository, you will need to install th Once done, you should clone this repository: ```bash linenums="0" -git clone https://github.com/reactive-python/reactpy-django.git +git clone https://github.com/reactive-python/reactpy-router.git cd reactpy-router ``` -Then, by running the command below you can install the dependencies needed to run the ReactPy-Django development environment. - - +Then, by running the command below you can install the dependencies needed to run the ReactPy-Router development environment. ```bash linenums="0" -pip install -e . -r requirements.txt --upgrade --verbose +pip install -r requirements.txt --upgrade --verbose ``` ## Running the full test suite diff --git a/docs/src/dictionary.txt b/docs/src/dictionary.txt index 58f6eee..6eb9552 100644 --- a/docs/src/dictionary.txt +++ b/docs/src/dictionary.txt @@ -1,4 +1,3 @@ -django sanic plotly nox From eb9c0223659c2145b127f3936c6a8fe93450aaa6 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:28:53 -0800 Subject: [PATCH 10/29] remove dead files --- src/js/README.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/js/README.md diff --git a/src/js/README.md b/src/js/README.md deleted file mode 100644 index 1d6fc22..0000000 --- a/src/js/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# reactpy-router - -A URL router for ReactPy - -# Package Installation - -Requires [Node](https://nodejs.org/en/) to be installed: - -```bash -npm install --save reactpy-router -``` - -For a developer installation, `cd` into this directory and run: - -```bash -npm install -npm run build -``` - -This will install required dependencies and generate a Javascript bundle that is saved -to `reactpy-router/bundle.js`` and is distributed with the -associated Python package. From 21b67a6385e4a2a91696bcd45700768e44b773fd Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:29:04 -0800 Subject: [PATCH 11/29] fix nox tests --- noxfile.py | 3 ++- setup.py | 2 +- src/reactpy_router/core.py | 2 +- tests/test_core.py | 9 +++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/noxfile.py b/noxfile.py index 6c5d3d4..10417ea 100644 --- a/noxfile.py +++ b/noxfile.py @@ -27,7 +27,7 @@ def test_suite(session: Session) -> None: def test_types(session: Session) -> None: install_requirements_file(session, "check-types") install_requirements_file(session, "pkg-deps") - session.run("mypy", "--show-error-codes", "src/reactpy_router", "tests/test_app") + session.run("mypy", "--show-error-codes", "src/reactpy_router", "tests") @session(tags=["test"]) @@ -45,6 +45,7 @@ def test_javascript(session: Session) -> None: def install_requirements_file(session: Session, name: str) -> None: + session.install("--upgrade", "pip", "setuptools", "wheel") file_path = ROOT_DIR / "requirements" / f"{name}.txt" assert file_path.exists(), f"requirements file {file_path} does not exist" session.install("-r", str(file_path)) diff --git a/setup.py b/setup.py index 46cf5fb..1508e50 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ package = { "name": name, "python_requires": ">=3.9", - "packages": find_namespace_packages(str(src_dir)), + "packages": find_namespace_packages(package_dir), "description": "A URL router for ReactPy.", "author": "Ryan Morshead", "author_email": "ryan.morshead@gmail.com", diff --git a/src/reactpy_router/core.py b/src/reactpy_router/core.py index 9d8f7a2..490a78c 100644 --- a/src/reactpy_router/core.py +++ b/src/reactpy_router/core.py @@ -19,7 +19,7 @@ from reactpy.backend.hooks import ConnectionContext, use_connection from reactpy.backend.types import Connection, Location from reactpy.core.types import VdomChild, VdomDict -from reactpy.types import ComponentType, Context, Location +from reactpy.types import ComponentType, Context from reactpy.web.module import export, module_from_file from reactpy_router.types import Route, RouteCompiler, Router, RouteResolver diff --git a/tests/test_core.py b/tests/test_core.py index 5f05f5c..77577b3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,6 +1,7 @@ +from typing import Any + from reactpy import Ref, component, html, use_location from reactpy.testing import DisplayFixture - from reactpy_router import link, route, simple, use_params, use_query @@ -39,7 +40,7 @@ def sample(): root_element = await display.root_element() except AttributeError: root_element = await display.page.wait_for_selector( - f"#display-{display._next_view_id}", state="attached" + f"#display-{display._next_view_id}", state="attached" # type: ignore ) assert not await root_element.inner_html() @@ -99,7 +100,7 @@ def sample(): async def test_use_params(display: DisplayFixture): - expected_params = {} + expected_params: dict[str, Any] = {} @component def check_params(): @@ -135,7 +136,7 @@ def sample(): async def test_use_query(display: DisplayFixture): - expected_query = {} + expected_query: dict[str, Any] = {} @component def check_query(): From fd50c4acb52ccda40f31de316fd5be850c19370c Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:32:06 -0800 Subject: [PATCH 12/29] ignore matching for linux --- docs/mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e76cf4e..c9d2ee8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -90,7 +90,9 @@ plugins: skip_files: - "index.md" - "reference\\core.md" + - "reference/core.md" - "reference\\types.md" + - "reference/types.md" - mkdocstrings: default_handler: python handlers: From 9e143b462d531563ac7c540e74a8cb9e60c6867e Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:32:11 -0800 Subject: [PATCH 13/29] verbiage --- docs/src/learn/add-reactpy-router-to-your-project.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/learn/add-reactpy-router-to-your-project.md b/docs/src/learn/add-reactpy-router-to-your-project.md index d8e29c2..3630a87 100644 --- a/docs/src/learn/add-reactpy-router-to-your-project.md +++ b/docs/src/learn/add-reactpy-router-to-your-project.md @@ -8,4 +8,4 @@ pip install reactpy-router ## Done! -You're now ready to start building your own ReactPy applications with routing. +You're now ready to start building your own ReactPy applications with URL routing. From 2fd2a7170c20f8b7c79d7d4e0f3843a1f3d88790 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:46:13 -0800 Subject: [PATCH 14/29] potentially fix nox test_python --- MANIFEST.in | 4 ++-- noxfile.py | 12 ++---------- setup.py | 3 ++- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 7c1c601..bdca1f4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include reactpy_router/bundle.js -include reactpy_router/py.typed +include src/reactpy_router/bundle.js +include src/reactpy_router/py.typed diff --git a/noxfile.py b/noxfile.py index 10417ea..432854f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -6,20 +6,12 @@ @session(tags=["test"]) -def test_suite(session: Session) -> None: +def test_python(session: Session) -> None: install_requirements_file(session, "test-env") + session.install(".[all]") session.run("playwright", "install", "chromium") posargs = session.posargs[:] - - if "--no-cov" in session.posargs: - posargs.remove("--no-cov") - session.log("Coverage won't be checked") - session.install(".") - else: - posargs += ["--cov=reactpy_router", "--cov-report=term"] - session.install("-e", ".") - session.run("pytest", "tests", *posargs) diff --git a/setup.py b/setup.py index 1508e50..c643fa1 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,8 @@ package = { "name": name, "python_requires": ">=3.9", - "packages": find_namespace_packages(package_dir), + "packages": find_namespace_packages(src_dir), + "package_dir": {"": "src"}, "description": "A URL router for ReactPy.", "author": "Ryan Morshead", "author_email": "ryan.morshead@gmail.com", From f16617ed60667be034c338ec64cb294c10b3e13b Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 00:49:32 -0800 Subject: [PATCH 15/29] not sure if this fixes docs tests --- .github/workflows/test-docs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 299ded3..d5f5052 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -32,6 +32,6 @@ jobs: run: | pip install -r requirements/check-types.txt pip install -r requirements/check-style.txt - mypy --show-error-codes examples/python/ - black examples/python/ --check - ruff check examples/python/ + mypy --show-error-codes docs/examples/python/ + black docs/examples/python/ --check + ruff check docs/examples/python/ From 861baee23fe9fda7d5b12b3f4a6e5ec519511e92 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:13:40 -0800 Subject: [PATCH 16/29] fix nox deps --- noxfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/noxfile.py b/noxfile.py index 432854f..71588a0 100644 --- a/noxfile.py +++ b/noxfile.py @@ -31,6 +31,7 @@ def test_style(session: Session) -> None: @session(tags=["test"]) def test_javascript(session: Session) -> None: + install_requirements_file(session, "test-env") session.chdir(ROOT_DIR / "src" / "js") session.run("python", "-m", "nodejs.npm", "install", external=True) session.run("python", "-m", "nodejs.npm", "run", "check") From 25bdef1401bbc172ec77235e4657713df8aa2cd5 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:20:22 -0800 Subject: [PATCH 17/29] Attempt fixes for docs examples type check --- docs/examples/python/nested-routes.py | 10 ++++++++-- docs/examples/python/route-parameters.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/examples/python/nested-routes.py b/docs/examples/python/nested-routes.py index c9431c8..9e98915 100644 --- a/docs/examples/python/nested-routes.py +++ b/docs/examples/python/nested-routes.py @@ -1,8 +1,9 @@ -from reactpy import component, html, run +from typing import TypedDict +from reactpy import component, html, run from reactpy_router import link, route, simple -message_data = [ +message_data: list["MessageDataType"] = [ {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, @@ -82,3 +83,8 @@ def messages_with(*names): run(root) + +MessageDataType = TypedDict( + "MessageDataType", + {"id": int, "with": list[str], "from": str | None, "message": str}, +) diff --git a/docs/examples/python/route-parameters.py b/docs/examples/python/route-parameters.py index d990502..4fd30e2 100644 --- a/docs/examples/python/route-parameters.py +++ b/docs/examples/python/route-parameters.py @@ -1,9 +1,10 @@ -from reactpy import component, html, run +from typing import TypedDict +from reactpy import component, html, run from reactpy_router import link, route, simple from reactpy_router.core import use_params -message_data = [ +message_data: list["MessageDataType"] = [ {"id": 1, "with": ["Alice"], "from": None, "message": "Hello!"}, {"id": 2, "with": ["Alice"], "from": "Alice", "message": "How's it going?"}, {"id": 3, "with": ["Alice"], "from": None, "message": "Good, you?"}, @@ -81,3 +82,8 @@ def messages_with(): run(root) + +MessageDataType = TypedDict( + "MessageDataType", + {"id": int, "with": list[str], "from": str | None, "message": str}, +) From ac72e5fe58ee480e59a56af9725507cecd757206 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:24:27 -0800 Subject: [PATCH 18/29] minor docs type fix --- docs/examples/python/nested-routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/examples/python/nested-routes.py b/docs/examples/python/nested-routes.py index 9e98915..f03a692 100644 --- a/docs/examples/python/nested-routes.py +++ b/docs/examples/python/nested-routes.py @@ -66,7 +66,6 @@ def all_messages(): @component def messages_with(*names): - names = set(names) messages = [msg for msg in message_data if set(msg["with"]) == names] return html.div( html.h1(f"Messages with {', '.join(names)} đŸ’Ŧ"), From 81d7f750ae750c1ee2cce01af1a0b788629d4605 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:32:46 -0800 Subject: [PATCH 19/29] add COC --- CODE_OF_CONDUCT.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..809177a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,47 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ryan.morshead@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq From 8272e385d9f698a2e2e3120dfb9fbd2a1886c801 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:34:42 -0800 Subject: [PATCH 20/29] update readme --- README.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 63aeaab..45bcd59 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,23 @@ -# reactpy-router +# ReactPy Router -A URL router for ReactPy +

+ + + + + + + + + + + + + + + +

-Read the docs: https://reactive-python.github.io/reactpy-router +[ReactPy-Router](https://github.com/reactive-python/reactpy-router) is used to add used to add URL routing support to an existing **ReactPy project**. + +More information about this package can be found on [the documentation](https://reactive-python.github.io/reactpy-router). From 79216a36eff7d10b9692875bdce54bc1903b88ff Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:37:01 -0800 Subject: [PATCH 21/29] New readme --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 45bcd59..3cb0c5d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # ReactPy Router

- - + + - - + + - + - - + + From 85fb4346485ba4002ca7a37993124eb9a7f33c71 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:38:55 -0800 Subject: [PATCH 22/29] Use installation page as index.md --- docs/mkdocs.yml | 2 +- docs/src/index.md | 15 +++++++++++---- .../learn/add-reactpy-router-to-your-project.md | 11 ----------- 3 files changed, 12 insertions(+), 16 deletions(-) delete mode 100644 docs/src/learn/add-reactpy-router-to-your-project.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c9d2ee8..d93b302 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,7 +1,7 @@ --- nav: - Get Started: - - Add ReactPy-Router to Your Project: learn/add-reactpy-router-to-your-project.md + - Add ReactPy-Router to Your Project: index.md - Your First Routed Application: learn/simple-application.md - Advanced Topics: - Routers, Routes, and Links: learn/routers-routes-and-links.md diff --git a/docs/src/index.md b/docs/src/index.md index 4abc843..3630a87 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,4 +1,11 @@ - +## Install from PyPI + +Run the following command to install [`reactpy-router`](https://pypi.org/project/reactpy-router/) in your Python environment. + +```bash linenums="0" +pip install reactpy-router +``` + +## Done! + +You're now ready to start building your own ReactPy applications with URL routing. diff --git a/docs/src/learn/add-reactpy-router-to-your-project.md b/docs/src/learn/add-reactpy-router-to-your-project.md deleted file mode 100644 index 3630a87..0000000 --- a/docs/src/learn/add-reactpy-router-to-your-project.md +++ /dev/null @@ -1,11 +0,0 @@ -## Install from PyPI - -Run the following command to install [`reactpy-router`](https://pypi.org/project/reactpy-router/) in your Python environment. - -```bash linenums="0" -pip install reactpy-router -``` - -## Done! - -You're now ready to start building your own ReactPy applications with URL routing. From 4d4f66d122b2c630b62d121d7c3174c1293e5674 Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Thu, 15 Feb 2024 20:34:50 -0700 Subject: [PATCH 23/29] set black requirement --- requirements/check-style.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/check-style.txt b/requirements/check-style.txt index dc109e2..8c8a38e 100644 --- a/requirements/check-style.txt +++ b/requirements/check-style.txt @@ -1,2 +1,2 @@ -black +black=23 ruff From 8aef254ee0f2795c3431bf177df389139ca331c8 Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Thu, 15 Feb 2024 20:36:56 -0700 Subject: [PATCH 24/29] fix black to v23 --- requirements/check-style.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/check-style.txt b/requirements/check-style.txt index 8c8a38e..240cba5 100644 --- a/requirements/check-style.txt +++ b/requirements/check-style.txt @@ -1,2 +1,2 @@ -black=23 +black==23 ruff From e45508698648680fe3035808213f0d480a37c01c Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Thu, 15 Feb 2024 20:39:10 -0700 Subject: [PATCH 25/29] black >=23,<24 --- requirements/check-style.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/check-style.txt b/requirements/check-style.txt index 240cba5..e4f6562 100644 --- a/requirements/check-style.txt +++ b/requirements/check-style.txt @@ -1,2 +1,2 @@ -black==23 +black >=23,<24 ruff From e746d415c9b1f273a05df0c88044f6a00bfde3d2 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Fri, 16 Feb 2024 20:07:24 -0800 Subject: [PATCH 26/29] re-add coverage --- noxfile.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/noxfile.py b/noxfile.py index 71588a0..7becf6a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,6 +12,15 @@ def test_python(session: Session) -> None: session.run("playwright", "install", "chromium") posargs = session.posargs[:] + + if "--no-cov" in session.posargs: + posargs.remove("--no-cov") + session.log("Coverage won't be checked") + session.install(".") + else: + posargs += ["--cov=reactpy_router", "--cov-report=term"] + session.install("-e", ".") + session.run("pytest", "tests", *posargs) From ce7ae15e846b9279be61fff6df61853d2eeab900 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Fri, 16 Feb 2024 20:11:18 -0800 Subject: [PATCH 27/29] Add coverage to setup.cfg --- setup.cfg | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/setup.cfg b/setup.cfg index 3c6e79c..e7be17a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,12 @@ [bdist_wheel] universal=1 + +[coverage:report] +fail_under = 100 +show_missing = True +skip_covered = True +sort = Miss +exclude_lines = + pragma: no cover + \.\.\. + raise NotImplementedError From 15a21661f1c927696439d2b443d6518eae6ede64 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Fri, 16 Feb 2024 20:34:18 -0800 Subject: [PATCH 28/29] Add coverage to test-src --- .github/workflows/test-src.yaml | 12 ++++++++++++ noxfile.py | 9 ++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-src.yaml b/.github/workflows/test-src.yaml index c7de8ac..538dbb0 100644 --- a/.github/workflows/test-src.yaml +++ b/.github/workflows/test-src.yaml @@ -26,3 +26,15 @@ jobs: run: pip install -r requirements/test-run.txt - name: Run Tests run: nox -t test + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Latest Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install Python Dependencies + run: pip install -r requirements/nox-deps.txt + - name: Run Tests + run: nox -t test -- --coverage diff --git a/noxfile.py b/noxfile.py index 7becf6a..1eebea2 100644 --- a/noxfile.py +++ b/noxfile.py @@ -13,13 +13,12 @@ def test_python(session: Session) -> None: posargs = session.posargs[:] - if "--no-cov" in session.posargs: - posargs.remove("--no-cov") - session.log("Coverage won't be checked") - session.install(".") - else: + if "--coverage" in posargs: posargs += ["--cov=reactpy_router", "--cov-report=term"] + posargs.remove("--coverage") session.install("-e", ".") + else: + session.log("Coverage won't be checked unless `-- --coverage` is defined.") session.run("pytest", "tests", *posargs) From 3a68bc436065aef217c646bf7d2e75a688ee1a5f Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Fri, 16 Feb 2024 20:39:01 -0800 Subject: [PATCH 29/29] change coverage requirements file --- .github/workflows/test-src.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-src.yaml b/.github/workflows/test-src.yaml index 538dbb0..b5ae7d0 100644 --- a/.github/workflows/test-src.yaml +++ b/.github/workflows/test-src.yaml @@ -35,6 +35,6 @@ jobs: with: python-version: "3.10" - name: Install Python Dependencies - run: pip install -r requirements/nox-deps.txt + run: pip install -r requirements/test-run.txt - name: Run Tests run: nox -t test -- --coverage