diff --git a/merge/.git-blame-ignore-revs b/merge/.git-blame-ignore-revs new file mode 100644 index 000000000..8bb0f1de0 --- /dev/null +++ b/merge/.git-blame-ignore-revs @@ -0,0 +1,8 @@ +# Format all python with ruff +47f713ddce20e3c751e3cbd05a8671dda8f9a054 +# Format all HTML with djLint +0cd2a6561291dabccf435f19aca3f96323097e8b +# Format login form with djLint +07313f9fbb9384489c6aa57885f3fecba358f288 +# Reformat middleware file using ruff +bc456a556e0df11b0f25420c5c6933c068adef10 diff --git a/merge/.gitattributes b/merge/.gitattributes new file mode 100644 index 000000000..2accc33da --- /dev/null +++ b/merge/.gitattributes @@ -0,0 +1 @@ +*.bat eol=crlf diff --git a/merge/.github/workflows/format.yml b/merge/.github/workflows/format.yml new file mode 100644 index 000000000..f88184ad9 --- /dev/null +++ b/merge/.github/workflows/format.yml @@ -0,0 +1,35 @@ +name: Verify formatting + +on: + push: + branches: main + pull_request: + +jobs: + ruff: + runs-on: ubuntu-latest + name: Verify Python formatting + steps: + - uses: actions/checkout@v4 + + - uses: astral-sh/ruff-action@v1 + with: + args: "format --check" + changed-files: 'true' + + djlint: + runs-on: ubuntu-latest + name: Check HTML formatting + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + + - name: Install dependencies + run: | + pip install -U pip + pip install djlint + + - name: Format HTML + run: djlint . --check diff --git a/merge/.github/workflows/lint.yml b/merge/.github/workflows/lint.yml new file mode 100644 index 000000000..c36d946e8 --- /dev/null +++ b/merge/.github/workflows/lint.yml @@ -0,0 +1,33 @@ +name: Lint codebase + +on: + push: + branches: main + pull_request: + +jobs: + ruff: + runs-on: ubuntu-latest + name: Lint Python + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/ruff-action@v1 + with: + changed-files: 'true' + + djlint: + runs-on: ubuntu-latest + name: Lint HTML + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + + - name: Install dependencies + run: | + pip install -U pip + pip install djlint + + - name: Lint + run: djlint --lint . diff --git a/merge/.gitignore b/merge/.gitignore new file mode 100644 index 000000000..532f6fa4e --- /dev/null +++ b/merge/.gitignore @@ -0,0 +1,23 @@ +.* + +*.egg +*.egg-info +*.py[cio] +*.sw? + +__pycache__/ +build/ +dist/ + +# Tailwind CSS +src/argus_htmx/static/styles.css +# Autogenerated via management command +src/argus_htmx/tailwindtheme/tailwind.config.js +# OS specific +src/argus_htmx/tailwindtheme/tailwindcss + +!.git-blame-ignore-revs +!.gitattributes +!.gitignore +!.pre-commit-config.yaml +!.github diff --git a/merge/.pre-commit-config.yaml b/merge/.pre-commit-config.yaml new file mode 100644 index 000000000..232a38d96 --- /dev/null +++ b/merge/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: mixed-line-ending + - id: end-of-file-fixer + - id: debug-statements +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.2 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format +- repo: https://github.com/djlint/djLint + rev: v1.35.4 + hooks: + - id: djlint-reformat-django + - id: djlint-django diff --git a/merge/Makefile b/merge/Makefile new file mode 100644 index 000000000..e48391485 --- /dev/null +++ b/merge/Makefile @@ -0,0 +1,28 @@ +.PHONY: clean testclean distclean coverageclean nuke tailwind + +TAILWINDDIR=src/argus_htmx/tailwindtheme +STATICDIR=src/argus_htmx/static + +clean: + -find . -name __pycache__ -print0 | xargs -0 rm -rf + -find . -name "*.pyc" -print0 | xargs -0 rm -rf + -find . -name "*.egg-info" -print0 | xargs -0 rm -rf + -find . -name ".ruff_cache" -print0 | xargs -0 rm -rf + +distclean: + -rm -rf ./dist + -rm -rf ./build + +coverageclean: + -rm .coverage + -rm .coverage.* + -rm coverage.xml + -rm -rf htmlcov + +testclean: coverageclean clean + -rm -rf .tox + +nuke: clean distclean testclean + +tailwind: + tailwindcss -c $(TAILWINDDIR)/tailwind.config.js -i $(TAILWINDDIR)/styles.css -o $(STATICDIR)/styles.css diff --git a/merge/README.rst b/merge/README.rst new file mode 100644 index 000000000..ecb7e6647 --- /dev/null +++ b/merge/README.rst @@ -0,0 +1,320 @@ +=================== +argus-HTMx-frontend +=================== + +Experimental frontend for `argus-server`_ as a django app. + +Will possibly cease to exist as a separate app if the experiment is deemed +successful. + +See `argus-server`_ for more about argus. + +Imports `django-htmx`_. See the `documentation for django-htmx`_ +for details. + +How to play +=========== + +Install +------- + +To make sure you do not accidentally work on an old argus-server, do the following: + +1. Use/make a venv, for instance: create a new one with ``python -m venv argus-htmx`` +2. Check out argus-server code +3. Install argus-server dynamically into the venv: ``pip install -e .`` +4. Check out this repo +5. Install this app dynamically into the venv: ``pip install -e .`` + +It is now safe to remove argus-server from the venv if you feel like it. + +Configure +--------- + +Do this in your workdir, which could be the checked out `argus-server`_ repo. + +This assumes that you have a local settings file (we recommend calling it +"localsettings.py" since that is hidden by .gitignore) as a sibling of +``src/``. + +At the top of this local settings file, copy the contents of +``argus.htmx.settings``. This will base the settings-file on +``argus.site.settings.backend`` and automatically use +``argus.site.utils.update_settings`` with +``argus_htmx.app_config.APP_SETTINGS`` to set/overwrite some settings and +mutate others. Note the usage of ``globals()``; due to this, inheriting from +``argus.htmx.settings`` will probably not work as expected. + +While developing you will probably prefer to swap out +``argus.site.settings.backend`` with ``argus.site.settings.dev``, as the former +is almost production-ready while the latter is tuned for development and +depends on the optional dependencies you can install via ``pip install +argus-server[dev]``. + +The ``argus.site.utils.update_settings`` function will add or change the settings + +* INSTALLED_APPS +* LOGIN_REDIRECT_URL +* LOGIN_URL +* LOGOUT_REDIRECT_URL +* LOGOUT_URL +* MIDDLEWARE +* PUBLIC_URLS +* ROOT_URLCONF +* TEMPLATES + +See ``argus_htmx.appconfig._app_settings`` for what is being set. The +management command ``printsettings`` (which depends on the app +``django-extensions``, a ``dev``-dependency) will print out the complete +settings used. + +Customizing +----------- + +If you add more pages and endpoints you will have to write your own root +urls.py and set ROOT_URLCONF appropriately. + +If you have some other apps you want installed and configured, you could either +add the necessary settings to your ``localsettings.py`` or use the extra-apps +machinery. The later is especially useful during the development phase when you +haven't settled on which apps to use yet. + +With extra-apps machinery +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You make a JSON-file which is read into your settings via one of two +environment variables. + +In order to add apps and settings that *extend* ``argus-server`` and this +``app`` you use the environment variable ``ARGUS_EXTRA_APPS``:: + + export ARGUS_EXTRA_APPS=`cat extra.json` + +If you want to *override* existing apps the environment variable to use is +``ARGUS_OVERRIDING_APPS``:: + + export ARGUS_OVERRIDING_APPS=`cat overriding.json` + +Have a look at the contents of ``argus_htmx.appconfig._app_settings`` for an +example of what you can set this way. + +You can merge your urlpatterns with the apps' urlpatterns via the +``argus.site.utils.get_urlpatterns`` function, see ``argus.htmx.urls`` for an +example. + +Optional authentication backend settings +---------------------------------------- + +If using ``django.contrib.auth.backends.RemoteUserBackend`` (which depends on +the middleware ``django.contrib.auth.middleware.RemoteUserMiddleware``) there's +an optional setting ``ARGUS_REMOTE_USER_METHOD_NAME`` to choose what to show on +the button. + +If using ``social_core.backends.open_id_connect.OpenIdConnectAuth`` there's an +optional setting ``ARGUS_OIDC_METHOD_NAME`` to choose what to show on the +button. + +Both can be set via environment variables. + +Update +====== + +On every new version, reinstall the dependencies since there might be new ones. + +Themes and styling +================== + +To try out daisyUI themes use the context processor +``argus_htmx.context_processor.theme_via_session`` instead of +``argus_htmx.context_processor.theme_via_GET``. + +Default included themes are: `light`, `dark` and `argus`. + +This project supports Tailwind CSS utility classes and daisyUI components for styling. +Below is an overview of the stack, installation and build instructions, and configuration details for themes and styles. + +Overview +-------- +* Tailwind CSS: A utility-first CSS framework for rapidly building custom user interfaces. +* daisyUI: A component library for Tailwind CSS that provides a set of ready-to-use components as well as color themes. + +Installation and build instructions +----------------------------------- +Recommended but open for tweaks and adaptations steps: + +1. Get Tailwind standalone CLI bundled with daisyUI from + https://github.com/dobicinaitis/tailwind-cli-extra + + Most linux:: + + $ curl -sL https://github.com/dobicinaitis/tailwind-cli-extra/releases/latest/download/tailwindcss-extra-linux-x64 -o /tmp/tailwindcss + $ chmod +x /tmp/tailwindcss + + For other OSes see + https://github.com/dobicinaitis/tailwind-cli-extra/releases/latest/ and + update the bit after ``download/`` accordingly. + + Optionally you can compile tailwind+daisyUI standalone cli bundle yourself as described here: + https://github.com/tailwindlabs/tailwindcss/discussions/12294#discussioncomment-8268378. +2. (Linux/OsX) Move the tailwindcss file to your $PATH, for instance to ``~/bin/`` or ``.local/bin``. +3. Go to the repo directory (parent of ``src/``) +4. Build main stylesheet file using ``tailwindcss`` executable from step 1 and + pointing to the included config file: + + Manually:: + + tailwindcss -c src/argus_htmx/tailwindtheme/tailwind.config.js -i src/argus_htmx/tailwindtheme/styles.css --output src/argus_htmx/static/styles.css + + Running with the ``--watch`` flag for automatic update on change seems + error-prone so we've made it very easy to run the command, with ``make`` or ``tox``:: + + make tailwind + tox -e tailwind + + Either will rebuild the styles for you. + + +Customization +------------- + +How to customize the look: + +* Override Argus' Tailwind CSS theme defaults and/or choose which daisyUI + color themes to include. You can do so by updating the default + ``TAILWIND_THEME_OVERRIDE`` and ``DAISYUI_THEMES`` values respectively + before running a ``tailwind_config`` management command: + + Via environment variables, for example:: + + TAILWIND_THEME_OVERRIDE = ' + { + "borderWidth": { + "DEFAULT": "1px" + }, + "extend": { + "borderRadius": { + "4xl": "2rem" + } + } + } + ' + DAISYUI_THEMES = ' + [ + "light", + "dark", + "cyberpunk", + "dim", + "autumn", + { "mytheme": { + "primary": "#009eb6", + "primary-content": "#00090c", + "secondary": "#00ac00", + "secondary-content": "#000b00", + "accent": "#ff0000", + "accent-content": "#160000", + "neutral": "#262c0e", + "neutral-content": "#cfd1ca", + "base-100": "#292129", + "base-200": "#221b22", + "base-300": "#1c161c", + "base-content": "#d0cdd0", + "info": "#00feff", + "info-content": "#001616", + "success": "#b1ea50", + "success-content": "#0c1302", + "warning": "#d86d00", + "warning-content": "#110400", + "error": "#ff6280", + "error-content": "#160306" + } + } + ] + ' + + Or by providing corresponding values in your local settings that star-imports from an `argus-server`_ settings file:: + + TAILWIND_THEME_OVERRIDE = {...} + DAISYUI_THEMES = [...] + + Some links that may be relevant for the customization values mentioned above: + * `daisyUI themes`_ + * `list of daisyUI color names`_ + * `Tailwind CSS theme customization`_ + +* Override the default main stylesheet path by setting + ``ARGUS_STYLESHEET_PATH`` in the environment. The path is under + ``STATIC_URL``. This depends on the context processor + ``argus_htmx.context_processors.path_to_stylesheet``. +* Include additional styles/stylesheets using the ``head`` block in your templates. +* Generate a Tailwind config file by running the ``tailwind_config`` management + command. By default the generated file will be based on + ``src/argus_htmx/tailwindtheme/tailwind.config.template.js`` and expected + values will be injected with reasonable defaults. + +UI Settings +=========== + +Incident table column customization +----------------------------------- +You can customize which columns are shown in the incidents listing table by overriding the +``INCIDENT_TABLE_COLUMNS`` setting. This setting takes a list of ``str`` or +``argus_htmx.incidents.customization.IncidentTableColumn`` instances. when given a ``str``, this +key must be available in the ``argus_htmx.incidents.customization.BUILTIN_COLUMNS`` dictionary. For +example:: + + from argus_htmx.incidents.customization import BUILTIN_COLUMNS, IncidentTableColumn + + INCIDENT_TABLE_COLUMNS = [ + "id", + "start_time", + BUILTIN_COLUMNS["description"], # equivalent to just "description" + IncidentTableColumn( # a new column definition + name="name", + label="Custom" + cell_template="/path/to/template.html" + context={ + "additional": "value" + } + ), + + ] + +For inbuilt support for other types of columns see the howtos in `the local docs `_. + + +.. _django-htmx: https://github.com/adamchainz/django-htmx +.. _argus-server: https://github.com/Uninett/Argus +.. _documentation for django-htmx: https://django-htmx.readthedocs.io/en/latest/ +.. _daisyUI themes: https://daisyui.com/docs/themes/ +.. _list of daisyUI color names: https://daisyui.com/docs/colors/#-2 +.. _tailwind-cli-extra: https://github.com/dobicinaitis/tailwind-cli-extra +.. _Tailwind CSS theme customization: https://tailwindcss.com/docs/theme + +Custom widget +------------- + +Argus supports showing an extra widget next to the menubar in the incidents listing. This box can +take the width of 1/3 of the window. You can add the widget by creating a context processor that +injects an ``incidents_extra_widget`` variable that points to an html template:: + + def extra_widget(request): + return { + "incidents_extra_widget": "path/to/_extra_widget.html", + } + +*note* Don't forget to include the context processor in your settings + +You could then create ``path/to/_extra_widget.html`` as following:: + +
+ My custom widget +
+ + +Page size +--------- + +By default, incidents are shown with a page size of ``10`` (ie. 10 rows at a time), and the user can +select a different page size from ``[10, 20, 50, 100]``. It possible to override these settings by +setting the ``ARGUS_INCIDENTS_DEFAULT_PAGE_SIZE`` and ``ARGUS_INCIDENTS_PAGE_SIZES`` setting +respectively. diff --git a/merge/docs/Makefile b/merge/docs/Makefile new file mode 100644 index 000000000..d4bb2cbb9 --- /dev/null +++ b/merge/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/merge/docs/conf.py b/merge/docs/conf.py new file mode 100644 index 000000000..4cd01f8d8 --- /dev/null +++ b/merge/docs/conf.py @@ -0,0 +1,26 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "argus-htmx-frontend" +copyright = "2024, Geant+Sikt" +author = "Geant+Sikt" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "alabaster" +html_static_path = ["_static"] diff --git a/merge/docs/development/dependencies.rst b/merge/docs/development/dependencies.rst new file mode 100644 index 000000000..f36f4053e --- /dev/null +++ b/merge/docs/development/dependencies.rst @@ -0,0 +1,30 @@ +Upgrading HTMX to a new version +=============================== + +To upgrade the HTMX to a new version, follow these steps: + +1. **Backup Current File**: + Ensure you have a backup of the current `htmx-*.min.js` file located in the `static` directory. + +2. **Download the Latest Version**: + Obtain the latest version of minified HTMX from the `UNPKG`_, for example: + `https://unpkg.com/htmx.org@2.0.2/dist/htmx.min.js` + +3. **Replace Old File**: + Replace the old HTMX file in your project with the new file from the latest version. This involves updating the `htmx-*.min.js` file in the `static` directory. Make sure to replace the `*` in the file name with the new version number. + +4. **Update References**: + Ensure that all references to HTMX in your project point to the new version. This includes updating the script tag in ``htmx_base.html``:: + + <-- HTMX --> + + + +Upgrading Hyperscript to a new version +====================================== + +To upgrade Hyperscript to a new version, use the same steps as for upgrading HTMX but for the +latest version of Hyperscript on `UNPKG`_, for example: +`https://unpkg.com/hyperscript.org@0.9.13/dist/_hyperscript.min.js` + +.. _UNPKG: https://unpkg.com diff --git a/merge/docs/howtos/column_for_specific_ack_group.rst b/merge/docs/howtos/column_for_specific_ack_group.rst new file mode 100644 index 000000000..752cf8d50 --- /dev/null +++ b/merge/docs/howtos/column_for_specific_ack_group.rst @@ -0,0 +1,21 @@ +================================================== +How to make a column for an ack with a known group +================================================== + +You need a template for the cell, in ``src/argus_htmx/templates/htmx/incidents/_incident_ack_by_group.html``:: + + {% load argus_htmx %} + {% if incident|is_acked_by:column.context.group %}X{% endif %} + +You also need to add the column to the column config:: + + INCIDENT_TABLE_COLUMNS = [ + .. + IncidentTableColumn( + "mygroup_ack", + "MYGROUP", + "htmx/incidents/_incident_ack_by_group.html", + context={"group": "MYGROUP"}, + ) + .. + ] diff --git a/merge/docs/howtos/column_for_specific_tag.rst b/merge/docs/howtos/column_for_specific_tag.rst new file mode 100644 index 000000000..72e443dca --- /dev/null +++ b/merge/docs/howtos/column_for_specific_tag.rst @@ -0,0 +1,16 @@ +======================================= +How to make a column for a specific tag +======================================= + +You only need to add the column to the column config:: + + INCIDENT_TABLE_COLUMNS = [ + .. + IncidentTableColumn( + "mytag", + "MYTAG", + "htmx/incidents/_incident_tag.html", + context={"tag": "MYTAG"}, + ) + .. + ] diff --git a/merge/docs/index.rst b/merge/docs/index.rst new file mode 100644 index 000000000..8da1daba3 --- /dev/null +++ b/merge/docs/index.rst @@ -0,0 +1,18 @@ +.. argus-htmx-frontend documentation master file, created by + sphinx-quickstart on Wed Jul 24 11:15:56 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +argus-htmx-frontend documentation +================================= + +Add your content using ``reStructuredText`` syntax. See the +`reStructuredText `_ +documentation for details. + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + howtos/* diff --git a/merge/docs/make.bat b/merge/docs/make.bat new file mode 100644 index 000000000..954237b9b --- /dev/null +++ b/merge/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/merge/manage.py b/merge/manage.py new file mode 100644 index 000000000..286534b29 --- /dev/null +++ b/merge/manage.py @@ -0,0 +1,22 @@ +#! /usr/bin/env python3 + +import django + +from django.conf import settings +from django.core.management import call_command + +settings.configure( + DEBUG=True, + INSTALLED_APPS=( + "django.contrib.contenttypes", + "django.contrib.auth", + "argus.auth", + "argus.incident", + "argus.notificationprofile", + "argus_htmx", + ), + MEDIA_PLUGINS=(), +) + +django.setup() +call_command("makemigrations", "argus_htmx") diff --git a/merge/pyproject.toml b/merge/pyproject.toml new file mode 100644 index 000000000..b2a491540 --- /dev/null +++ b/merge/pyproject.toml @@ -0,0 +1,45 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +description = "HTTP Frontend as a django app for argus-server using HTMx" +name = "argus-htmx-frontend" +readme = "README.rst" +authors = [{name = "Hanne Moa", email = "hanne.moa@sikt.no"}] +requires-python = ">=3.9" +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Framework :: Django", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", + "Programming Language :: Python :: 3 :: Only", + "Environment :: Web Environment", +] +dynamic = ["version"] +dependencies = [ + "argus-server>=1.27.0", + "django-htmx", + "django-widget-tweaks==1.5.0", + "social-auth-core>=4.1", + "social-auth-app-django>=5.0", +] + +[project.urls] +Github = "https://github.com/Uninett/argus-htmx-frontend" + +[project.optional-dependencies] +docs = ["sphinx"] + +[tool.flit.module] +name = "argus_htmx" + +[tool.ruff] +line-length = 120 +output-format = "full" + +[tool.djlint] +profile="django" +indent = 2 +ignore="H006" diff --git a/merge/runtests.py b/merge/runtests.py new file mode 100644 index 000000000..9d8e8de46 --- /dev/null +++ b/merge/runtests.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import os +import sys + +import django +from django.conf import settings +from django.test.utils import get_runner + +if __name__ == "__main__": + os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings" + django.setup() + TestRunner = get_runner(settings) + test_runner = TestRunner() + failures = test_runner.run_tests(["tests"]) + sys.exit(bool(failures)) diff --git a/merge/src/argus_htmx/__init__.py b/merge/src/argus_htmx/__init__.py new file mode 100644 index 000000000..718d00032 --- /dev/null +++ b/merge/src/argus_htmx/__init__.py @@ -0,0 +1 @@ +__version__ = "0.17" diff --git a/merge/src/argus_htmx/appconfig.py b/merge/src/argus_htmx/appconfig.py new file mode 100644 index 000000000..534e60156 --- /dev/null +++ b/merge/src/argus_htmx/appconfig.py @@ -0,0 +1,46 @@ +from argus.site.settings._serializers import ListAppSetting + + +__all__ = ["APP_SETTINGS"] + +_app_settings = [ + { + "settings": { + "LOGIN_URL": "/accounts/login/", + "LOGOUT_URL": "/accounts/logout/", + "LOGIN_REDIRECT_URL": "/incidents/", + "LOGOUT_REDIRECT_URL": "/incidents/", + "PUBLIC_URLS": [ + "/accounts/login/", + "/api/", + "/oidc/", + ], + }, + }, + { + "app_name": "argus_htmx", + "urls": { + "path": "", + "urlpatterns_module": "argus_htmx.urls", + }, + "context_processors": [ + "argus.auth.context_processors.preferences", + "argus_htmx.context_processors.path_to_stylesheet", + ], + "middleware": { + "argus_htmx.middleware.LoginRequiredMiddleware": "end", + "argus_htmx.middleware.HtmxMessageMiddleware": "end", + }, + }, + { + "app_name": "django_htmx", + "middleware": { + "django_htmx.middleware.HtmxMiddleware": "end", + }, + }, + { + "app_name": "widget_tweaks", # indent for readability + }, +] + +APP_SETTINGS = ListAppSetting(_app_settings).root diff --git a/merge/src/argus_htmx/apps.py b/merge/src/argus_htmx/apps.py new file mode 100644 index 000000000..cc6169b59 --- /dev/null +++ b/merge/src/argus_htmx/apps.py @@ -0,0 +1,16 @@ +import pathlib + +from django.apps import AppConfig + + +class HtmxFrontendConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + label = "argus_htmx" + name = "argus_htmx" + + def tailwind_css_files(self): + yield from pathlib.Path(__file__).parent.glob("tailwindtheme/snippets/*.css") + + def ready(self): + # Register checks + from .checks import check_for_valid_themes_list # noqa: F401 diff --git a/merge/src/argus_htmx/auth/__init__.py b/merge/src/argus_htmx/auth/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/auth/views.py b/merge/src/argus_htmx/auth/views.py new file mode 100644 index 000000000..09a62baa0 --- /dev/null +++ b/merge/src/argus_htmx/auth/views.py @@ -0,0 +1,53 @@ +from django.conf import settings +from django.contrib.auth.views import LoginView as DjangoLoginView +from django.urls import reverse + +from argus.auth.utils import ( + has_model_backend, + has_remote_user_backend, + get_psa_authentication_backends, + get_authentication_backend_classes, +) + + +REMOTE_USER_METHOD_NAME = getattr(settings, "ARGUS_REMOTE_USER_METHOD_NAME", "REMOTE_USER") +OIDC_METHOD_NAME = getattr(settings, "ARGUS_OIDC_METHOD_NAME", "OIDC") + + +def get_htmx_authentication_backend_name_and_type(): + # Needed for HTMX LoginView + backends = get_authentication_backend_classes() + + data = {} + if has_model_backend(backends): + data["local"] = { + "url": reverse("htmx:login"), + "display_name": "Log In", + } + + if has_remote_user_backend(backends): + remote_user_data = { + "url": "/", # Should probably also be a setting + "display_name": REMOTE_USER_METHOD_NAME, + } + data.setdefault("external", []).append(remote_user_data) + + for backend in get_psa_authentication_backends(backends): + display_name = backend.name + if backend.name == "oidc": + display_name = OIDC_METHOD_NAME + psa_backend_data = { + "url": reverse("social:begin", kwargs={"backend": backend.name}), + "display_name": display_name, + } + data.setdefault("external", []).append(psa_backend_data) + + return data + + +class LoginView(DjangoLoginView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + backends = get_htmx_authentication_backend_name_and_type() + context["backends"] = backends + return context diff --git a/merge/src/argus_htmx/checks.py b/merge/src/argus_htmx/checks.py new file mode 100644 index 000000000..6e55ad10d --- /dev/null +++ b/merge/src/argus_htmx/checks.py @@ -0,0 +1,29 @@ +from django.core.checks import Error, Warning, register +from django.core.exceptions import ImproperlyConfigured + +from .themes.utils import get_theme_names, get_stylesheet_path + + +@register +def check_for_valid_themes_list(app_configs, **kwargs): + styles_path = get_stylesheet_path() + themes = [] + try: + themes = get_theme_names(quiet=False) + except ImproperlyConfigured as e: + return [ + Warning( + str(e), + hint=f"Regenerate {styles_path}", + id="argus_htmx.T001", + ) + ] + if not themes: + return [ + Error( + "no themes installed", + hint=f'Check the settings "DAISYUI_THEMES" and "TAILWIND_THEME_OVERRIDE" and regenerate {styles_path}', + id="argus_htmx.T002", + ) + ] + return [] diff --git a/merge/src/argus_htmx/constants.py b/merge/src/argus_htmx/constants.py new file mode 100644 index 000000000..24fc8dae1 --- /dev/null +++ b/merge/src/argus_htmx/constants.py @@ -0,0 +1,16 @@ +from argus_htmx.dateformat.constants import DATETIME_DEFAULT, DATETIME_FORMATS, DATETIME_CHOICES +from argus_htmx.incidents.constants import DEFAULT_PAGE_SIZE, ALLOWED_PAGE_SIZES, PAGE_SIZE_CHOICES +from argus_htmx.themes.constants import THEME_CHOICES, THEME_NAMES, THEME_DEFAULT + + +__all__ = [ + "ALLOWED_PAGE_SIZES", + "DATETIME_CHOICES", + "DATETIME_DEFAULT", + "DATETIME_FORMATS", + "DEFAULT_PAGE_SIZE", + "PAGE_SIZE_CHOICES", + "THEME_CHOICES", + "THEME_DEFAULT", + "THEME_NAMES", +] diff --git a/merge/src/argus_htmx/context_processors.py b/merge/src/argus_htmx/context_processors.py new file mode 100644 index 000000000..3f2b002e9 --- /dev/null +++ b/merge/src/argus_htmx/context_processors.py @@ -0,0 +1,14 @@ +""" +How to use: + +Append the "context_processors" list for the TEMPLATES-backend +``django.template.backends.django.DjangoTemplates`` with the full dotted path. + +See django settings for ``TEMPLATES``. +""" + +from .settings import STYLESHEET_PATH + + +def path_to_stylesheet(request): + return {"path_to_stylesheet": STYLESHEET_PATH} diff --git a/merge/src/argus_htmx/dateformat/__init__.py b/merge/src/argus_htmx/dateformat/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/dateformat/constants.py b/merge/src/argus_htmx/dateformat/constants.py new file mode 100644 index 000000000..67de6361f --- /dev/null +++ b/merge/src/argus_htmx/dateformat/constants.py @@ -0,0 +1,24 @@ +from django.conf import settings + +__all__ = [ + "DATETIME_FALLBACK", + "DATETIME_FORMATS", + "DATETIME_DEFAULT", + "DATETIME_CHOICES", +] + + +DATETIME_FALLBACK = "LOCALE" +DATETIME_FORMATS = { + "LOCALE": "DATETIME_FORMAT", # default + "ISO": "Y-m-d H:i:s", + "RFC5322": "r", + "EPOCH": "U", +} +DATETIME_CHOICES = tuple((format, format) for format in DATETIME_FORMATS) + +_datetime_setting = getattr(settings, "ARGUS_FRONTEND_DATETIME_FORMAT", DATETIME_FALLBACK) + +DATETIME_DEFAULT = DATETIME_FALLBACK +if _datetime_setting in DATETIME_FORMATS: + DATETIME_DEFAULT = _datetime_setting diff --git a/merge/src/argus_htmx/dateformat/urls.py b/merge/src/argus_htmx/dateformat/urls.py new file mode 100644 index 000000000..f8cdf7773 --- /dev/null +++ b/merge/src/argus_htmx/dateformat/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + + +app_name = "htmx" +urlpatterns = [ + path("names/", views.dateformat_names, name="dateformat-names"), + path("change/", views.change_dateformat, name="change-dateformat"), +] diff --git a/merge/src/argus_htmx/dateformat/views.py b/merge/src/argus_htmx/dateformat/views.py new file mode 100644 index 000000000..966c30f5e --- /dev/null +++ b/merge/src/argus_htmx/dateformat/views.py @@ -0,0 +1,26 @@ +import logging + +from django.shortcuts import render + +from django.views.decorators.http import require_GET, require_POST +from django.http import HttpResponse +from django_htmx.http import HttpResponseClientRefresh + +from argus.auth.utils import save_preference + +from argus_htmx.incidents.views import HtmxHttpRequest +from .constants import DATETIME_FORMATS + +LOG = logging.getLogger(__name__) + + +@require_GET +def dateformat_names(request: HtmxHttpRequest) -> HttpResponse: + datetime_formats = DATETIME_FORMATS.keys() + return render(request, "htmx/dateformat/_dateformat_list.html", {"datetime_formats": datetime_formats}) + + +@require_POST +def change_dateformat(request: HtmxHttpRequest) -> HttpResponse: + save_preference(request, request.POST, "argus_htmx", "datetime_format_name") + return HttpResponseClientRefresh() diff --git a/merge/src/argus_htmx/destinations/__init__.py b/merge/src/argus_htmx/destinations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/destinations/urls.py b/merge/src/argus_htmx/destinations/urls.py new file mode 100644 index 000000000..377fae3ed --- /dev/null +++ b/merge/src/argus_htmx/destinations/urls.py @@ -0,0 +1,21 @@ +from django.http import HttpResponse +from django.template import Template, RequestContext +from django.urls import path + + +def placeholder(request): + template = Template( + """{% extends "htmx/base.html" %} + {% block main %} +

DESTINATION PLACEHOLDER

+ {% endblock main %} + """ + ) + context = RequestContext(request) + return HttpResponse(template.render(context)) + + +app_name = "htmx" +urlpatterns = [ + path("", placeholder, name="destination-placeholder"), +] diff --git a/merge/src/argus_htmx/incidents/__init__.py b/merge/src/argus_htmx/incidents/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/incidents/constants.py b/merge/src/argus_htmx/incidents/constants.py new file mode 100644 index 000000000..cd2208ea7 --- /dev/null +++ b/merge/src/argus_htmx/incidents/constants.py @@ -0,0 +1,13 @@ +from django.conf import settings + + +__all__ = [ + "DEFAULT_PAGE_SIZE", + "ALLOWED_PAGE_SIZES", + "PAGE_SIZE_CHOICES", +] + + +DEFAULT_PAGE_SIZE = getattr(settings, "ARGUS_INCIDENTS_DEFAULT_PAGE_SIZE", 10) +ALLOWED_PAGE_SIZES = getattr(settings, "ARGUS_INCIDENTS_PAGE_SIZES", [10, 20, 50, 100]) +PAGE_SIZE_CHOICES = tuple((ps, ps) for ps in ALLOWED_PAGE_SIZES) diff --git a/merge/src/argus_htmx/incidents/customization.py b/merge/src/argus_htmx/incidents/customization.py new file mode 100644 index 000000000..34603497f --- /dev/null +++ b/merge/src/argus_htmx/incidents/customization.py @@ -0,0 +1,71 @@ +""" +Definitions and defaults for UI customization on the incidents page. + +Currently customizable UI elements: +- table columns: configure what columns to show in the incidents listing +""" + +from dataclasses import dataclass +from typing import List, Optional, Union + +from django.conf import settings + +from argus_htmx import settings as argus_htmx_settings + + +@dataclass +class IncidentTableColumn: + """Class for defining a column in the incidents table. + + :param name: identifier for this column + :param label: what to show as the column header + :param cell_template: template to use when rendering a cell for this column + :param context: additional context to pass to the rendering cell. Will be made + available as ``cell_context`` in the cell template + """ + + name: str # identifier + label: str # display value + cell_template: str + header_template: Optional[str] = None + context: Optional[dict] = None + + +_BUILTIN_COLUMN_LIST = [ + IncidentTableColumn( + "row_select", + "Selected", + "htmx/incidents/_incident_checkbox.html", + "htmx/incidents/_selected_incidents_header.html", + ), + IncidentTableColumn("id", "ID", "htmx/incidents/_incident_pk.html"), + IncidentTableColumn( + "start_time", + "Timestamp", + "htmx/incidents/_incident_start_time.html", + "htmx/incidents/_incident_start_time_header.html", + ), + IncidentTableColumn("status", "Status", "htmx/incidents/_incident_status.html"), + IncidentTableColumn("level", "Severity level", "htmx/incidents/_incident_level.html"), + IncidentTableColumn("source", "Source", "htmx/incidents/_incident_source.html"), + IncidentTableColumn("description", "Description", "htmx/incidents/_incident_description.html"), + IncidentTableColumn("ack", "Ack", "htmx/incidents/_incident_ack.html"), + IncidentTableColumn("combined_status", "Status", "htmx/incidents/_incident_combined_status.html"), + IncidentTableColumn("ticket", "Ticket", "htmx/incidents/_incident_ticket.html"), + IncidentTableColumn("links", "Actions", "htmx/incidents/_incident_actions.html"), +] +BUILTIN_COLUMNS = {col.name: col for col in _BUILTIN_COLUMN_LIST} + + +def get_incident_table_columns() -> List[IncidentTableColumn]: + columns = getattr(settings, "INCIDENT_TABLE_COLUMNS", argus_htmx_settings.INCIDENT_TABLE_COLUMNS) + return [_resolve_column(col) for col in columns] + + +def _resolve_column(col: Union[str, IncidentTableColumn]): + if isinstance(col, str): + try: + col = BUILTIN_COLUMNS[col] + except KeyError: + raise ValueError(f"Column '{col}' is not defined") + return col diff --git a/merge/src/argus_htmx/incidents/filter.py b/merge/src/argus_htmx/incidents/filter.py new file mode 100644 index 000000000..1b4e27895 --- /dev/null +++ b/merge/src/argus_htmx/incidents/filter.py @@ -0,0 +1,79 @@ +from django import forms + +from argus.filter import get_filter_backend +from argus.incident.models import SourceSystem +from argus.incident.constants import Level + + +filter_backend = get_filter_backend() +QuerySetFilter = filter_backend.QuerySetFilter + + +class DropdownMultiSelect(forms.CheckboxSelectMultiple): + template_name = "htmx/incidents/_incident_source_select.html" + option_template_name = "htmx/forms/checkbox_select_multiple.html" + + +class IncidentFilterForm(forms.Form): + open = forms.BooleanField(required=False) + closed = forms.BooleanField(required=False) + acked = forms.BooleanField(required=False) + unacked = forms.BooleanField(required=False) + source = forms.MultipleChoiceField( + widget=DropdownMultiSelect(attrs={"placeholder": "select sources..."}), + choices=tuple(SourceSystem.objects.values_list("id", "name")), + required=False, + label="Sources", + ) + maxlevel = forms.IntegerField( + widget=forms.NumberInput( + attrs={"type": "range", "step": "1", "min": min(Level).value, "max": max(Level).value} + ), + label="Level <=", + initial=max(Level).value, + required=False, + ) + + def _tristate(self, onkey, offkey): + on = self.cleaned_data.get(onkey, None) + off = self.cleaned_data.get(offkey, None) + if on == off: + return None + if on and not off: + return True + if off and not on: + return False + + def to_filterblob(self): + if not self.is_valid(): + return {} + + filterblob = {} + + open = self._tristate("open", "closed") + if open is not None: + filterblob["open"] = open + + acked = self._tristate("acked", "unacked") + if acked is not None: + filterblob["acked"] = acked + + source = self.cleaned_data.get("source", []) + if source: + filterblob["sourceSystemIds"] = source + + maxlevel = self.cleaned_data.get("maxlevel", 0) + if maxlevel: + filterblob["maxlevel"] = maxlevel + + return filterblob + + +def incident_list_filter(request, qs): + # TODO: initialize with chosen Filter.filter if any + form = IncidentFilterForm(request.GET or None) + + if form.is_valid(): + filterblob = form.to_filterblob() + qs = QuerySetFilter.filtered_incidents(filterblob, qs) + return form, qs diff --git a/merge/src/argus_htmx/incidents/forms.py b/merge/src/argus_htmx/incidents/forms.py new file mode 100644 index 000000000..f536c33af --- /dev/null +++ b/merge/src/argus_htmx/incidents/forms.py @@ -0,0 +1,20 @@ +from django import forms + + +class AckForm(forms.Form): + description = forms.CharField() + expiration = forms.DateTimeField(required=False) + + +class DescriptionOptionalForm(forms.Form): + "For closing/reopening" + + description = forms.CharField(required=False) + + +class EditTicketUrlForm(forms.Form): + ticket_url = forms.URLField(required=False) + + +class AddTicketUrlForm(forms.Form): + ticket_url = forms.URLField(required=True) diff --git a/merge/src/argus_htmx/incidents/urls.py b/merge/src/argus_htmx/incidents/urls.py new file mode 100644 index 000000000..338e5c069 --- /dev/null +++ b/merge/src/argus_htmx/incidents/urls.py @@ -0,0 +1,12 @@ +from django.urls import path + +from . import views + + +app_name = "htmx" +urlpatterns = [ + path("", views.incident_list, name="incident-list"), + path("/", views.incident_detail, name="incident-detail"), + path("update//", views.incidents_update, name="incidents-update"), + path("filter/", views.filter_form, name="incidents-filter"), +] diff --git a/merge/src/argus_htmx/incidents/utils.py b/merge/src/argus_htmx/incidents/utils.py new file mode 100644 index 000000000..fe39d9a9c --- /dev/null +++ b/merge/src/argus_htmx/incidents/utils.py @@ -0,0 +1,15 @@ +import importlib + +from django.conf import settings + +FUNCTION_NAME = "incident_list_filter" +DEFAULT_MODULE = "argus_htmx.incidents.filter" + + +def get_filter_function(): + dotted_path = getattr(settings, "ARGUS_HTMX_FILTER_FUNCTION", DEFAULT_MODULE) + module = importlib.import_module(dotted_path) + function = getattr(module, FUNCTION_NAME, None) + if function: + return function + raise ImportError(f"Could not import {FUNCTION_NAME} from {dotted_path}") diff --git a/merge/src/argus_htmx/incidents/views.py b/merge/src/argus_htmx/incidents/views.py new file mode 100644 index 000000000..422316397 --- /dev/null +++ b/merge/src/argus_htmx/incidents/views.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import logging +from datetime import datetime + +from django import forms +from django.contrib.auth import get_user_model +from django.shortcuts import render, get_object_or_404 + +from django.views.decorators.http import require_POST, require_GET +from django.core.paginator import Paginator +from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest +from django_htmx.middleware import HtmxDetails +from django_htmx.http import HttpResponseClientRefresh + +from argus.auth.utils import get_preference, save_preference +from argus.incident.models import Incident +from argus.util.datetime_utils import make_aware + +from .constants import ALLOWED_PAGE_SIZES +from .customization import get_incident_table_columns +from .utils import get_filter_function +from .forms import AckForm, DescriptionOptionalForm, EditTicketUrlForm, AddTicketUrlForm +from ..utils import ( + bulk_change_incidents, + bulk_ack_queryset, + bulk_close_queryset, + bulk_reopen_queryset, + bulk_change_ticket_url_queryset, +) + +User = get_user_model() +LOG = logging.getLogger(__name__) + + +# Map request trigger to parameters for incidents update +INCIDENT_UPDATE_ACTIONS = { + "ack": (AckForm, bulk_ack_queryset), + "close": (DescriptionOptionalForm, bulk_close_queryset), + "reopen": (DescriptionOptionalForm, bulk_reopen_queryset), + "update-ticket": (EditTicketUrlForm, bulk_change_ticket_url_queryset), + "add-ticket": (AddTicketUrlForm, bulk_change_ticket_url_queryset), +} + + +def prefetch_incident_daughters(): + return Incident.objects.select_related("source").prefetch_related( + "incident_tag_relations", + "incident_tag_relations__tag", + "events", + "events__ack", + ) + + +class HtmxHttpRequest(HttpRequest): + htmx: HtmxDetails + + +# fetch with htmx +def incident_detail(request, pk: int): + incident = get_object_or_404(Incident, id=pk) + context = { + "incident": incident, + "page_title": str(incident), + } + return render(request, "htmx/incidents/incident_detail.html", context=context) + + +def get_form_data(request, formclass: forms.Form): + formdata = request.POST or None + incident_ids = [] + cleaned_form = None + if formdata: + incident_ids = request.POST.getlist("incident_ids", []) + form = formclass(formdata) + if form.is_valid(): + cleaned_form = form.cleaned_data + return cleaned_form, incident_ids + + +@require_POST +def incidents_update(request: HtmxHttpRequest, action: str): + try: + formclass, callback_func = INCIDENT_UPDATE_ACTIONS[action] + except KeyError: + LOG.error("Unrecognized action name %s when updating incidents.", action) + return HttpResponseBadRequest("Invalid update action") + formdata, incident_ids = get_form_data(request, formclass) + if formdata: + bulk_change_incidents(request.user, incident_ids, formdata, callback_func) + return HttpResponseClientRefresh() + + +@require_GET +def filter_form(request: HtmxHttpRequest): + incident_list_filter = get_filter_function() + filter_form, _ = incident_list_filter(request, None) + context = {"filter_form": filter_form} + return render(request, "htmx/incidents/_incident_filterbox.html", context=context) + + +def incident_list(request: HtmxHttpRequest) -> HttpResponse: + columns = get_incident_table_columns() + + # Load incidents + qs = prefetch_incident_daughters().order_by("-start_time") + total_count = qs.count() + last_refreshed = make_aware(datetime.now()) + + params = dict(request.GET.items()) + + incident_list_filter = get_filter_function() + filter_form, qs = incident_list_filter(request, qs) + filtered_count = qs.count() + + # Standard Django pagination + + page_size = get_preference(request, "argus_htmx", "page_size") + success = save_preference(request, request.GET, "argus_htmx", "page_size") + if success: + page_size = get_preference(request, "argus_htmx", "page_size") + paginator = Paginator(object_list=qs, per_page=page_size) + page_num = params.pop("page", "1") + page = paginator.get_page(page_num) + + # The htmx magic - use a different, minimal base template for htmx + # requests, allowing us to skip rendering the unchanging parts of the + # template. + if request.htmx: + base_template = "htmx/incidents/responses/_incidents_table_refresh.html" + else: + base_template = "htmx/incidents/_base.html" + context = { + "columns": columns, + "filtered_count": filtered_count, + "count": total_count, + "filter_form": filter_form, + "page_title": "Incidents", + "base": base_template, + "page": page, + "last_refreshed": last_refreshed, + "update_interval": 30, + "all_page_sizes": ALLOWED_PAGE_SIZES, + } + + return render(request, "htmx/incidents/incident_list.html", context=context) diff --git a/merge/src/argus_htmx/management/commands/__init__.py b/merge/src/argus_htmx/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/management/commands/tailwind_config.py b/merge/src/argus_htmx/management/commands/tailwind_config.py new file mode 100644 index 000000000..a01d1408d --- /dev/null +++ b/merge/src/argus_htmx/management/commands/tailwind_config.py @@ -0,0 +1,120 @@ +import json +import pathlib +import textwrap +from django.apps import apps +from django.core.management.base import BaseCommand +from django.conf import settings +from django.template import engines +from django.template.context import make_context +from django.template.loader import get_template + +from argus_htmx.themes.utils import get_raw_themes_setting +from argus_htmx import settings as argus_htmx_settings + + +# Copied from https://github.com/GEANT/geant-argus/pull/15 with minor modifications +class Command(BaseCommand): + help = """ + Uses the template specified in the TAILWIND_CONFIG_TEMPLATE setting + (default: tailwind/tailwind.config.js) to dynamically build a tailwind.config.js. + This file must be in an app's templates directory. The template may contain: + + - a '{{ projectpaths }} section without square brackets that will be + popuplated with auto discovered template dirs of installed apps + - a '{{ daisyuithemes }}' section without square brackets that will be + popuplated by the daisyUI theme list specified in the DAISYUI_THEMES + setting (default: ["dark", "light", {"argus"": {...}} ]) + - a '{{ themeoverride }}' section that will be popuplated by a dict containing + tailwind theme options specified in TAILWIND_THEME_OVERRIDE setting + (default: {}) + + This command also generates a `styles.css` that contains the base css file for + tailwind to create its final css file. It uses the template specified by the + `TAILWIND_CSS_TEMPLATE` setting (default: tailwind/styles.css). This file should + also be in an app's templates directory + + The `styles.css` template may iterate over a `cssfiles` context variable that + contains css files/snippets that should be included in the final css file. Apps + may define a `tailwind_css_files` method that gives the location (as a str or + pathlib.Path object) for every css file from this app to include. (see + argus_htmx.apps.HtmxFrontendConfig for an example) + + Additional settings that govern the functionality of this command are: + + - TAILWIND_CONFIG_TARGET: the target location for writing the tailwind.config.js + - TAILWIND_CSS_TARGET: override the base tailwind css file location + + These target locations are relative to the working directory where this command is + executed + """ + DEFAULT_CONFIG_TEMPLATE_NAME = "tailwind/tailwind.config.js" + DEFAULT_CSS_TEMPLATE_NAME = "tailwind/styles.css" + DEFAULT_CONFIG_TARGET = "src/argus_htmx/tailwindtheme/tailwind.config.js" + DEFAULT_CSS_TARGET = "src/argus_htmx/tailwindtheme/styles.css" + + def handle(self, *args, **options): + config_template_name = getattr(settings, "TAILWIND_CONFIG_TEMPLATE", self.DEFAULT_CONFIG_TEMPLATE_NAME) + config_target_path = pathlib.Path(getattr(settings, "TAILWIND_CONFIG_TARGET", self.DEFAULT_CONFIG_TARGET)) + css_template_name = getattr(settings, "TAILWIND_CSS_TEMPLATE", self.DEFAULT_CSS_TEMPLATE_NAME) + css_target_path = pathlib.Path(getattr(settings, "TAILWIND_CSS_TARGET", self.DEFAULT_CSS_TARGET)) + self.write_file( + config_template_name, + config_target_path, + context=self.get_context(target_dir=config_target_path.parent), + name="tailwind config", + ) + self.write_file( + css_template_name, + css_target_path, + context=self.get_context(target_dir=css_target_path.parent), + name="tailwind base css", + ) + + def get_context(self, target_dir: pathlib.Path): + return { + "themeoverride": getattr( + settings, + "TAILWIND_THEME_OVERRIDE", + argus_htmx_settings.TAILWIND_THEME_OVERRIDE, + ), + "daisyuithemes": textwrap.indent( + json.dumps(get_raw_themes_setting(), indent=2), + prefix=10 * " ", + predicate=lambda line: line != "[\n", # this is kinda hacky, but eh + ), + "projectpaths": "\n".join(f" '{d}/**/*.html'," for d in self.get_template_dirs()), + "cssfiles": self.get_css_files(target_dir), + } + + def write_file(self, template_name, target_path, context, name): + pathlib.Path(target_path).write_text(self.render(template_name=template_name, context=context)) + + self.stdout.write(f"Wrote {name} to '{target_path}'") + + @staticmethod + def render(template_name: str, context): + template = get_template(template_name) + return template.template.render(make_context(context, autoescape=False)) + + @staticmethod + def get_template_dirs(): + for engine in engines.all(): + yield from getattr(engine, "template_dirs", []) + + @classmethod + def get_css_files(cls, target_dir: pathlib.Path): + css_files = sorted(cls._iter_css_files(), key=lambda p: p.stem) + return (cls.make_relative(p, target_dir) for p in css_files) + + @staticmethod + def _iter_css_files(): + for app in apps.get_app_configs(): + if callable(css_files := getattr(app, "tailwind_css_files", None)): + yield from (pathlib.Path(p) for p in css_files()) + + @staticmethod + def make_relative(path: pathlib.Path, base_path: pathlib.Path): + try: + return path.relative_to(base_path.absolute()) + except ValueError: + return path diff --git a/merge/src/argus_htmx/middleware.py b/merge/src/argus_htmx/middleware.py new file mode 100644 index 000000000..ad352fb10 --- /dev/null +++ b/merge/src/argus_htmx/middleware.py @@ -0,0 +1,69 @@ +from django.conf import settings +from django.contrib.auth.views import redirect_to_login +from django.http import HttpRequest, HttpResponse +from django.template import loader +from django.utils.deprecation import MiddlewareMixin +from django.utils.encoding import force_str + + +class LoginRequiredMiddleware: + def __init__(self, get_response): + self.public_urls = getattr(settings, "PUBLIC_URLS", ()) + self.login_url = force_str(settings.LOGIN_URL) + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + return response + + def process_view(self, request, view_func, _view_args, _view_kwargs): + assert hasattr(request, "user"), ( + "The LoginRequiredMiddleware requires authentication middleware " + "to be installed. Edit your MIDDLEWARE%s setting to insert " + "'django.contrib.auth.middleware.AuthenticationMiddleware' " + "before this middleware." % ("_CLASSES" if settings.MIDDLEWARE is None else "") + ) + + # If path is public, allow + for url in self.public_urls: + if request.path.startswith(url): + return None + + # If CBV has the attribute login_required == False, allow + view_class = getattr(view_func, "view_class", None) + if view_class and not getattr(view_class, "login_required", True): + return None + + # If view_func.login_required == False, allow + if not getattr(view_func, "login_required", True): + return None + + # Allow authenticated users + if request.user.is_authenticated: + return None + + # Redirect unauthenticated users to login page + return redirect_to_login(request.get_full_path(), self.login_url, "next") + + +class HtmxMessageMiddleware(MiddlewareMixin): + """ + For htmx requests, adds messages to the #notification-messages div defined in + `templates/messages/_notification_messages.html` using htmx's hx-swap-oob feature + """ + + TEMPLATE = "messages/_notification_messages_htmx_append.html" + + def process_response(self, request: HttpRequest, response: HttpResponse) -> HttpResponse: + if not request.htmx: + return response + + # Ignore redirections because HTMX cannot read the headers + if 300 <= response.status_code < 400: + return response + + if not response.writable(): + return response + + response.write(loader.render_to_string(self.TEMPLATE, request=request)) + return response diff --git a/merge/src/argus_htmx/migrations/0001_initial.py b/merge/src/argus_htmx/migrations/0001_initial.py new file mode 100644 index 000000000..98b9837c3 --- /dev/null +++ b/merge/src/argus_htmx/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.16 on 2024-11-07 11:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("argus_auth", "0004_add_preferences_model"), + ] + + operations = [ + migrations.CreateModel( + name="ArgusHtmxPreferences", + fields=[], + options={ + "proxy": True, + "indexes": [], + "constraints": [], + }, + bases=("argus_auth.preferences",), + ), + ] diff --git a/merge/src/argus_htmx/migrations/__init__.py b/merge/src/argus_htmx/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/models.py b/merge/src/argus_htmx/models.py new file mode 100644 index 000000000..1054aca22 --- /dev/null +++ b/merge/src/argus_htmx/models.py @@ -0,0 +1,8 @@ +from django.db import models # noqa: F401 - unused-import + +from argus_htmx.user.preferences.models import ArgusHtmxPreferences + + +__all__ = [ + "ArgusHtmxPreferences", +] diff --git a/merge/src/argus_htmx/notificationprofiles/__init__.py b/merge/src/argus_htmx/notificationprofiles/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/notificationprofiles/urls.py b/merge/src/argus_htmx/notificationprofiles/urls.py new file mode 100644 index 000000000..e3d8ab649 --- /dev/null +++ b/merge/src/argus_htmx/notificationprofiles/urls.py @@ -0,0 +1,21 @@ +from django.http import HttpResponse +from django.template import Template, RequestContext +from django.urls import path + + +def placeholder(request): + template = Template( + """{% extends "htmx/base.html" %} + {% block main %} +

NOTIFICATION PROFILES PLACEHOLDER

+ {% endblock main %} + """ + ) + context = RequestContext(request) + return HttpResponse(template.render(context)) + + +app_name = "htmx" +urlpatterns = [ + path("", placeholder, name="notificationprofile-placeholder"), +] diff --git a/merge/src/argus_htmx/settings.py b/merge/src/argus_htmx/settings.py new file mode 100644 index 000000000..00f14bb02 --- /dev/null +++ b/merge/src/argus_htmx/settings.py @@ -0,0 +1,61 @@ +# customize the displayed columns in the incident table +# items in INCIDENT_TABLE_COLUMNS can be either a `str` referring to a key in +# argus_htmx.incidents.customization.BUILTIN_COLUMNS or an instance of +# argus_htmx.incidents.customization.IncidentTableColumn +from argus.site.settings import get_json_env, get_str_env + +INCIDENT_TABLE_COLUMNS = [ + "row_select", + "id", + "start_time", + "combined_status", + "level", + "source", + "description", + "ticket", +] + +# These templates are auto-discovered by the templating engine and are relative +# to the argus_htmx's `templates/` directory +TAILWIND_CONFIG_TEMPLATE = "tailwind/tailwind.config.js" +TAILWIND_CSS_TEMPLATE = "tailwind/styles.css" + +# These targets are relative to the working directory when running the `tailwind_config` +# management command, which should be done from the repostitory root +TAILWIND_CONFIG_TARGET = "src/argus_htmx/tailwindtheme/tailwind.config.js" +TAILWIND_CSS_TARGET = "src/argus_htmx/tailwindtheme/styles.css" + +STYLESHEET_PATH_DEFAULT = "styles.css" +DEFAULT_THEMES = [ + "dark", + "light", + { + "argus": { + "primary": "#006d91", + "primary-content": "#d1e1e9", + "secondary": "#f3b61f", + "secondary-content": "#140c00", + "accent": "#c84700", + "accent-content": "#f8dbd1", + "neutral": "#006d91", + "neutral-content": "#d1e1e9", + "base-100": "#edfaff", + "base-200": "#ced9de", + "base-300": "#b0babd", + "base-content": "#141516", + "info": "#0073e5", + "info-content": "#000512", + "success": "#008700", + "success-content": "#d3e7d1", + "warning": "#ee4900", + "warning-content": "#140200", + "error": "#e5545a", + "error-content": "#120203", + } + }, +] +DAISYUI_THEMES = get_json_env("DAISYUI_THEMES", DEFAULT_THEMES, quiet=True) +THEME_DEFAULT = get_str_env("ARGUS_THEME_DEFAULT", "argus") +DEFAULT_THEME_OVERRIDE = {} +TAILWIND_THEME_OVERRIDE = get_json_env("TAILWIND_THEME_OVERRIDE", DEFAULT_THEME_OVERRIDE, quiet=True) +STYLESHEET_PATH = get_str_env("ARGUS_STYLESHEET_PATH", STYLESHEET_PATH_DEFAULT) diff --git a/merge/src/argus_htmx/static/htmx-2.0.2.min.js b/merge/src/argus_htmx/static/htmx-2.0.2.min.js new file mode 100644 index 000000000..c11fbbdf4 --- /dev/null +++ b/merge/src/argus_htmx/static/htmx-2.0.2.min.js @@ -0,0 +1 @@ +var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=cn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true},parseInterval:null,_:null,version:"2.0.2"};Q.onLoad=$;Q.process=Dt;Q.on=be;Q.off=we;Q.trigger=de;Q.ajax=Hn;Q.find=r;Q.findAll=p;Q.closest=g;Q.remove=K;Q.addClass=Y;Q.removeClass=o;Q.toggleClass=W;Q.takeClass=ge;Q.swap=ze;Q.defineExtension=Bn;Q.removeExtension=Un;Q.logAll=z;Q.logNone=J;Q.parseInterval=h;Q._=_;const n={addTriggerHandler:Et,bodyContains:le,canAccessLocalStorage:j,findThisElement:Ee,filterValues:hn,swap:ze,hasAttribute:s,getAttributeValue:te,getClosestAttributeValue:re,getClosestMatch:T,getExpressionVars:Cn,getHeaders:dn,getInputValues:cn,getInternalData:ie,getSwapSpecification:pn,getTriggerSpecs:lt,getTarget:Ce,makeFragment:D,mergeObjects:ue,makeSettleInfo:xn,oobSwap:Te,querySelectorExt:ae,settleImmediately:Gt,shouldCancel:ht,triggerEvent:de,triggerErrorEvent:fe,withExtensions:Bt};const v=["get","post","put","delete","patch"];const O=v.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");const R=e("head");function e(e,t=false){return new RegExp(`<${e}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${e}>`,t?"gim":"im")}function h(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function ne(){return document}function H(e,t){return e.getRootNode?e.getRootNode({composed:t}):ne()}function T(e,t){while(e&&!t(e)){e=u(e)}return e||null}function q(e,t,n){const r=te(t,n);const o=te(t,"hx-disinherit");var i=te(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function re(t,n){let r=null;T(t,function(e){return!!(r=q(t,ce(e),n))});if(r!=="unset"){return r}}function f(e,t){const n=e instanceof Element&&(e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector);return!!n&&n.call(e,t)}function L(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function N(e){const t=new DOMParser;return t.parseFromString(e,"text/html")}function A(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function I(e){const t=ne().createElement("script");se(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function P(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function k(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(P(e)){const t=I(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){w(e)}finally{e.remove()}}})}function D(e){const t=e.replace(R,"");const n=L(t);let r;if(n==="html"){r=new DocumentFragment;const i=N(e);A(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=N(t);A(r,i.body);r.title=i.title}else{const i=N('");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){k(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function oe(e){if(e){e()}}function t(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function M(e){return typeof e==="function"}function X(e){return t(e,"Object")}function ie(e){const t="htmx-internal-data";let n=e[t];if(!n){n=e[t]={}}return n}function F(t){const n=[];if(t){for(let e=0;e=0}function le(e){const t=e.getRootNode&&e.getRootNode();if(t&&t instanceof window.ShadowRoot){return ne().body.contains(t.host)}else{return ne().body.contains(e)}}function U(e){return e.trim().split(/\s+/)}function ue(e,t){for(const n in t){if(t.hasOwnProperty(n)){e[n]=t[n]}}return e}function S(e){try{return JSON.parse(e)}catch(e){w(e);return null}}function j(){const e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function V(t){try{const e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function _(e){return vn(ne().body,function(){return eval(e)})}function $(t){const e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function z(){Q.logger=function(e,t,n){if(console){console.log(t,e,n)}}}function J(){Q.logger=null}function r(e,t){if(typeof e!=="string"){return e.querySelector(t)}else{return r(ne(),e)}}function p(e,t){if(typeof e!=="string"){return e.querySelectorAll(t)}else{return p(ne(),e)}}function E(){return window}function K(e,t){e=y(e);if(t){E().setTimeout(function(){K(e);e=null},t)}else{u(e).removeChild(e)}}function ce(e){return e instanceof Element?e:null}function G(e){return e instanceof HTMLElement?e:null}function Z(e){return typeof e==="string"?e:null}function d(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function Y(e,t,n){e=ce(y(e));if(!e){return}if(n){E().setTimeout(function(){Y(e,t);e=null},n)}else{e.classList&&e.classList.add(t)}}function o(e,t,n){let r=ce(y(e));if(!r){return}if(n){E().setTimeout(function(){o(r,t);r=null},n)}else{if(r.classList){r.classList.remove(t);if(r.classList.length===0){r.removeAttribute("class")}}}}function W(e,t){e=y(e);e.classList.toggle(t)}function ge(e,t){e=y(e);se(e.parentElement.children,function(e){o(e,t)});Y(ce(e),t)}function g(e,t){e=ce(y(e));if(e&&e.closest){return e.closest(t)}else{do{if(e==null||f(e,t)){return e}}while(e=e&&ce(u(e)));return null}}function l(e,t){return e.substring(0,t.length)===t}function pe(e,t){return e.substring(e.length-t.length)===t}function i(e){const t=e.trim();if(l(t,"<")&&pe(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function m(e,t,n){e=y(e);if(t.indexOf("closest ")===0){return[g(ce(e),i(t.substr(8)))]}else if(t.indexOf("find ")===0){return[r(d(e),i(t.substr(5)))]}else if(t==="next"){return[ce(e).nextElementSibling]}else if(t.indexOf("next ")===0){return[me(e,i(t.substr(5)),!!n)]}else if(t==="previous"){return[ce(e).previousElementSibling]}else if(t.indexOf("previous ")===0){return[ye(e,i(t.substr(9)),!!n)]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else if(t==="root"){return[H(e,!!n)]}else if(t.indexOf("global ")===0){return m(e,t.slice(7),true)}else{return F(d(H(e,!!n)).querySelectorAll(i(t)))}}var me=function(t,e,n){const r=d(H(t,n)).querySelectorAll(e);for(let e=0;e=0;e--){const o=r[e];if(o.compareDocumentPosition(t)===Node.DOCUMENT_POSITION_FOLLOWING){return o}}};function ae(e,t){if(typeof e!=="string"){return m(e,t)[0]}else{return m(ne().body,e)[0]}}function y(e,t){if(typeof e==="string"){return r(d(t)||document,e)}else{return e}}function xe(e,t,n){if(M(t)){return{target:ne().body,event:Z(e),listener:t}}else{return{target:y(e),event:Z(t),listener:n}}}function be(t,n,r){_n(function(){const e=xe(t,n,r);e.target.addEventListener(e.event,e.listener)});const e=M(n);return e?n:r}function we(t,n,r){_n(function(){const e=xe(t,n,r);e.target.removeEventListener(e.event,e.listener)});return M(n)?n:r}const ve=ne().createElement("output");function Se(e,t){const n=re(e,t);if(n){if(n==="this"){return[Ee(e,t)]}else{const r=m(e,n);if(r.length===0){w('The selector "'+n+'" on '+t+" returned no matches!");return[ve]}else{return r}}}}function Ee(e,t){return ce(T(e,function(e){return te(ce(e),t)!=null}))}function Ce(e){const t=re(e,"hx-target");if(t){if(t==="this"){return Ee(e,"hx-target")}else{return ae(e,t)}}else{const n=ie(e);if(n.boosted){return ne().body}else{return e}}}function Oe(t){const n=Q.config.attributesToSettle;for(let e=0;e0){s=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{s=e}const n=ne().querySelectorAll(t);if(n){se(n,function(e){let t;const n=o.cloneNode(true);t=ne().createDocumentFragment();t.appendChild(n);if(!He(s,e)){t=d(n)}const r={shouldSwap:true,target:e,fragment:t};if(!de(e,"htmx:oobBeforeSwap",r))return;e=r.target;if(r.shouldSwap){_e(s,e,e,t,i)}se(i.elts,function(e){de(e,"htmx:oobAfterSwap",r)})});o.parentNode.removeChild(o)}else{o.parentNode.removeChild(o);fe(ne().body,"htmx:oobErrorNoTarget",{content:o})}return e}function qe(e){se(p(e,"[hx-preserve], [data-hx-preserve]"),function(e){const t=te(e,"id");const n=ne().getElementById(t);if(n!=null){e.parentNode.replaceChild(n,e)}})}function Le(l,e,u){se(e.querySelectorAll("[id]"),function(t){const n=ee(t,"id");if(n&&n.length>0){const r=n.replace("'","\\'");const o=t.tagName.replace(":","\\:");const e=d(l);const i=e&&e.querySelector(o+"[id='"+r+"']");if(i&&i!==e){const s=t.cloneNode();Re(t,i);u.tasks.push(function(){Re(t,s)})}}})}function Ne(e){return function(){o(e,Q.config.addedClass);Dt(ce(e));Ae(d(e));de(e,"htmx:load")}}function Ae(e){const t="[autofocus]";const n=G(f(e,t)?e:e.querySelector(t));if(n!=null){n.focus()}}function c(e,t,n,r){Le(e,n,r);while(n.childNodes.length>0){const o=n.firstChild;Y(ce(o),Q.config.addedClass);e.insertBefore(o,t);if(o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE){r.tasks.push(Ne(o))}}}function Ie(e,t){let n=0;while(n0}function ze(e,t,r,o){if(!o){o={}}e=y(e);const n=document.activeElement;let i={};try{i={elt:n,start:n?n.selectionStart:null,end:n?n.selectionEnd:null}}catch(e){}const s=xn(e);if(r.swapStyle==="textContent"){e.textContent=t}else{let n=D(t);s.title=n.title;if(o.selectOOB){const u=o.selectOOB.split(",");for(let t=0;t0){E().setTimeout(l,r.settleDelay)}else{l()}}function Je(e,t,n){const r=e.getResponseHeader(t);if(r.indexOf("{")===0){const o=S(r);for(const i in o){if(o.hasOwnProperty(i)){let e=o[i];if(X(e)){n=e.target!==undefined?e.target:n}else{e={value:e}}de(n,i,e)}}}else{const s=r.split(",");for(let e=0;e0){const s=o[0];if(s==="]"){e--;if(e===0){if(n===null){t=t+"true"}o.shift();t+=")})";try{const l=vn(r,function(){return Function(t)()},function(){return true});l.source=t;return l}catch(e){fe(ne().body,"htmx:syntax:error",{error:e,source:t});return null}}}else if(s==="["){e++}if(nt(s,n,i)){t+="(("+i+"."+s+") ? ("+i+"."+s+") : (window."+s+"))"}else{t=t+s}n=o.shift()}}}function b(e,t){let n="";while(e.length>0&&!t.test(e[0])){n+=e.shift()}return n}function ot(e){let t;if(e.length>0&&Qe.test(e[0])){e.shift();t=b(e,et).trim();e.shift()}else{t=b(e,x)}return t}const it="input, textarea, select";function st(e,t,n){const r=[];const o=tt(t);do{b(o,We);const l=o.length;const u=b(o,/[,\[\s]/);if(u!==""){if(u==="every"){const c={trigger:"every"};b(o,We);c.pollInterval=h(b(o,/[,\[\s]/));b(o,We);var i=rt(e,o,"event");if(i){c.eventFilter=i}r.push(c)}else{const a={trigger:u};var i=rt(e,o,"event");if(i){a.eventFilter=i}while(o.length>0&&o[0]!==","){b(o,We);const f=o.shift();if(f==="changed"){a.changed=true}else if(f==="once"){a.once=true}else if(f==="consume"){a.consume=true}else if(f==="delay"&&o[0]===":"){o.shift();a.delay=h(b(o,x))}else if(f==="from"&&o[0]===":"){o.shift();if(Qe.test(o[0])){var s=ot(o)}else{var s=b(o,x);if(s==="closest"||s==="find"||s==="next"||s==="previous"){o.shift();const d=ot(o);if(d.length>0){s+=" "+d}}}a.from=s}else if(f==="target"&&o[0]===":"){o.shift();a.target=ot(o)}else if(f==="throttle"&&o[0]===":"){o.shift();a.throttle=h(b(o,x))}else if(f==="queue"&&o[0]===":"){o.shift();a.queue=b(o,x)}else if(f==="root"&&o[0]===":"){o.shift();a[f]=ot(o)}else if(f==="threshold"&&o[0]===":"){o.shift();a[f]=b(o,x)}else{fe(e,"htmx:syntax:error",{token:o.shift()})}}r.push(a)}}if(o.length===l){fe(e,"htmx:syntax:error",{token:o.shift()})}b(o,We)}while(o[0]===","&&o.shift());if(n){n[t]=r}return r}function lt(e){const t=te(e,"hx-trigger");let n=[];if(t){const r=Q.config.triggerSpecsCache;n=r&&r[t]||st(e,t,r)}if(n.length>0){return n}else if(f(e,"form")){return[{trigger:"submit"}]}else if(f(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(f(e,it)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function ut(e){ie(e).cancelled=true}function ct(e,t,n){const r=ie(e);r.timeout=E().setTimeout(function(){if(le(e)&&r.cancelled!==true){if(!pt(n,e,Xt("hx:poll:trigger",{triggerSpec:n,target:e}))){t(e)}ct(e,t,n)}},n.pollInterval)}function at(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function ft(e){return g(e,Q.config.disableSelector)}function dt(t,n,e){if(t instanceof HTMLAnchorElement&&at(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"&&String(ee(t,"method")).toLowerCase()!=="dialog"){n.boosted=true;let r,o;if(t.tagName==="A"){r="get";o=ee(t,"href")}else{const i=ee(t,"method");r=i?i.toLowerCase():"get";if(r==="get"){}o=ee(t,"action")}e.forEach(function(e){mt(t,function(e,t){const n=ce(e);if(ft(n)){a(n);return}he(r,o,n,t)},n,e,true)})}}function ht(e,t){const n=ce(t);if(!n){return false}if(e.type==="submit"||e.type==="click"){if(n.tagName==="FORM"){return true}if(f(n,'input[type="submit"], button')&&g(n,"form")!==null){return true}if(n instanceof HTMLAnchorElement&&n.href&&(n.getAttribute("href")==="#"||n.getAttribute("href").indexOf("#")!==0)){return true}}return false}function gt(e,t){return ie(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function pt(e,t,n){const r=e.eventFilter;if(r){try{return r.call(t,n)!==true}catch(e){const o=r.source;fe(ne().body,"htmx:eventFilter:error",{error:e,source:o});return true}}return false}function mt(s,l,e,u,c){const a=ie(s);let t;if(u.from){t=m(s,u.from)}else{t=[s]}if(u.changed){t.forEach(function(e){const t=ie(e);t.lastValue=e.value})}se(t,function(o){const i=function(e){if(!le(s)){o.removeEventListener(u.trigger,i);return}if(gt(s,e)){return}if(c||ht(e,s)){e.preventDefault()}if(pt(u,s,e)){return}const t=ie(e);t.triggerSpec=u;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(s)<0){t.handledFor.push(s);if(u.consume){e.stopPropagation()}if(u.target&&e.target){if(!f(ce(e.target),u.target)){return}}if(u.once){if(a.triggeredOnce){return}else{a.triggeredOnce=true}}if(u.changed){const n=ie(o);const r=o.value;if(n.lastValue===r){return}n.lastValue=r}if(a.delayed){clearTimeout(a.delayed)}if(a.throttle){return}if(u.throttle>0){if(!a.throttle){de(s,"htmx:trigger");l(s,e);a.throttle=E().setTimeout(function(){a.throttle=null},u.throttle)}}else if(u.delay>0){a.delayed=E().setTimeout(function(){de(s,"htmx:trigger");l(s,e)},u.delay)}else{de(s,"htmx:trigger");l(s,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:u.trigger,listener:i,on:o});o.addEventListener(u.trigger,i)})}let yt=false;let xt=null;function bt(){if(!xt){xt=function(){yt=true};window.addEventListener("scroll",xt);setInterval(function(){if(yt){yt=false;se(ne().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){wt(e)})}},200)}}function wt(e){if(!s(e,"data-hx-revealed")&&B(e)){e.setAttribute("data-hx-revealed","true");const t=ie(e);if(t.initHash){de(e,"revealed")}else{e.addEventListener("htmx:afterProcessNode",function(){de(e,"revealed")},{once:true})}}}function vt(e,t,n,r){const o=function(){if(!n.loaded){n.loaded=true;t(e)}};if(r>0){E().setTimeout(o,r)}else{o()}}function St(t,n,e){let i=false;se(v,function(r){if(s(t,"hx-"+r)){const o=te(t,"hx-"+r);i=true;n.path=o;n.verb=r;e.forEach(function(e){Et(t,e,n,function(e,t){const n=ce(e);if(g(n,Q.config.disableSelector)){a(n);return}he(r,o,n,t)})})}});return i}function Et(r,e,t,n){if(e.trigger==="revealed"){bt();mt(r,n,t,e);wt(ce(r))}else if(e.trigger==="intersect"){const o={};if(e.root){o.root=ae(r,e.root)}if(e.threshold){o.threshold=parseFloat(e.threshold)}const i=new IntersectionObserver(function(t){for(let e=0;e0){t.polling=true;ct(ce(r),n,e)}else{mt(r,n,t,e)}}function Ct(e){const t=ce(e);if(!t){return false}const n=t.attributes;for(let e=0;e", "+e).join(""));return o}else{return[]}}function qt(e){const t=g(ce(e.target),"button, input[type='submit']");const n=Nt(e);if(n){n.lastButtonClicked=t}}function Lt(e){const t=Nt(e);if(t){t.lastButtonClicked=null}}function Nt(e){const t=g(ce(e.target),"button, input[type='submit']");if(!t){return}const n=y("#"+ee(t,"form"),t.getRootNode())||g(t,"form");if(!n){return}return ie(n)}function At(e){e.addEventListener("click",qt);e.addEventListener("focusin",qt);e.addEventListener("focusout",Lt)}function It(t,e,n){const r=ie(t);if(!Array.isArray(r.onHandlers)){r.onHandlers=[]}let o;const i=function(e){vn(t,function(){if(ft(t)){return}if(!o){o=new Function("event",n)}o.call(t,e)})};t.addEventListener(e,i);r.onHandlers.push({event:e,listener:i})}function Pt(t){ke(t);for(let e=0;eQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(ne().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function _t(t){if(!j()){return null}t=V(t);const n=S(localStorage.getItem("htmx-history-cache"))||[];for(let e=0;e=200&&this.status<400){de(ne().body,"htmx:historyCacheMissLoad",i);const e=D(this.response);const t=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;const n=jt();const r=xn(n);Dn(e.title);Ve(n,t,r);Gt(r.tasks);Ut=o;de(ne().body,"htmx:historyRestore",{path:o,cacheMiss:true,serverResponse:this.response})}else{fe(ne().body,"htmx:historyCacheMissLoadError",i)}};e.send()}function Yt(e){zt();e=e||location.pathname+location.search;const t=_t(e);if(t){const n=D(t.content);const r=jt();const o=xn(r);Dn(n.title);Ve(r,n,o);Gt(o.tasks);E().setTimeout(function(){window.scrollTo(0,t.scroll)},0);Ut=e;de(ne().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{Zt(e)}}}function Wt(e){let t=Se(e,"hx-indicator");if(t==null){t=[e]}se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)+1;e.classList.add.call(e.classList,Q.config.requestClass)});return t}function Qt(e){let t=Se(e,"hx-disabled-elt");if(t==null){t=[]}se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","");e.setAttribute("data-disabled-by-htmx","")});return t}function en(e,t){se(e,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList.remove.call(e.classList,Q.config.requestClass)}});se(t,function(e){const t=ie(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled");e.removeAttribute("data-disabled-by-htmx")}})}function tn(t,n){for(let e=0;en.indexOf(e)<0)}else{e=e.filter(e=>e!==n)}r.delete(t);se(e,e=>r.append(t,e))}}function sn(t,n,r,o,i){if(o==null||tn(t,o)){return}else{t.push(o)}if(nn(o)){const s=ee(o,"name");let e=o.value;if(o instanceof HTMLSelectElement&&o.multiple){e=F(o.querySelectorAll("option:checked")).map(function(e){return e.value})}if(o instanceof HTMLInputElement&&o.files){e=F(o.files)}rn(s,e,n);if(i){ln(o,r)}}if(o instanceof HTMLFormElement){se(o.elements,function(e){if(t.indexOf(e)>=0){on(e.name,e.value,n)}else{t.push(e)}if(i){ln(e,r)}});new FormData(o).forEach(function(e,t){if(e instanceof File&&e.name===""){return}rn(t,e,n)})}}function ln(e,t){const n=e;if(n.willValidate){de(n,"htmx:validation:validate");if(!n.checkValidity()){t.push({elt:n,message:n.validationMessage,validity:n.validity});de(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})}}}function un(n,e){for(const t of e.keys()){n.delete(t)}e.forEach(function(e,t){n.append(t,e)});return n}function cn(e,t){const n=[];const r=new FormData;const o=new FormData;const i=[];const s=ie(e);if(s.lastButtonClicked&&!le(s.lastButtonClicked)){s.lastButtonClicked=null}let l=e instanceof HTMLFormElement&&e.noValidate!==true||te(e,"hx-validate")==="true";if(s.lastButtonClicked){l=l&&s.lastButtonClicked.formNoValidate!==true}if(t!=="get"){sn(n,o,i,g(e,"form"),l)}sn(n,r,i,e,l);if(s.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&ee(e,"type")==="submit"){const c=s.lastButtonClicked||e;const a=ee(c,"name");rn(a,c.value,o)}const u=Se(e,"hx-include");se(u,function(e){sn(n,r,i,ce(e),l);if(!f(e,"form")){se(d(e).querySelectorAll(it),function(e){sn(n,r,i,e,l)})}});un(r,o);return{errors:i,formData:r,values:An(r)}}function an(e,t,n){if(e!==""){e+="&"}if(String(n)==="[object Object]"){n=JSON.stringify(n)}const r=encodeURIComponent(n);e+=encodeURIComponent(t)+"="+r;return e}function fn(e){e=Ln(e);let n="";e.forEach(function(e,t){n=an(n,t,e)});return n}function dn(e,t,n){const r={"HX-Request":"true","HX-Trigger":ee(e,"id"),"HX-Trigger-Name":ee(e,"name"),"HX-Target":te(t,"id"),"HX-Current-URL":ne().location.href};wn(e,"hx-headers",false,r);if(n!==undefined){r["HX-Prompt"]=n}if(ie(e).boosted){r["HX-Boosted"]="true"}return r}function hn(n,e){const t=re(e,"hx-params");if(t){if(t==="none"){return new FormData}else if(t==="*"){return n}else if(t.indexOf("not ")===0){se(t.substr(4).split(","),function(e){e=e.trim();n.delete(e)});return n}else{const r=new FormData;se(t.split(","),function(t){t=t.trim();if(n.has(t)){n.getAll(t).forEach(function(e){r.append(t,e)})}});return r}}else{return n}}function gn(e){return!!ee(e,"href")&&ee(e,"href").indexOf("#")>=0}function pn(e,t){const n=t||re(e,"hx-swap");const r={swapStyle:ie(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ie(e).boosted&&!gn(e)){r.show="top"}if(n){const s=U(n);if(s.length>0){for(let e=0;e0?o.join(":"):null;r.scroll=c;r.scrollTarget=i}else if(l.indexOf("show:")===0){const a=l.substr(5);var o=a.split(":");const f=o.pop();var i=o.length>0?o.join(":"):null;r.show=f;r.showTarget=i}else if(l.indexOf("focus-scroll:")===0){const d=l.substr("focus-scroll:".length);r.focusScroll=d=="true"}else if(e==0){r.swapStyle=l}else{w("Unknown modifier in hx-swap: "+l)}}}}return r}function mn(e){return re(e,"hx-encoding")==="multipart/form-data"||f(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function yn(t,n,r){let o=null;Bt(n,function(e){if(o==null){o=e.encodeParameters(t,r,n)}});if(o!=null){return o}else{if(mn(n)){return un(new FormData,Ln(r))}else{return fn(r)}}}function xn(e){return{tasks:[],elts:[e]}}function bn(e,t){const n=e[0];const r=e[e.length-1];if(t.scroll){var o=null;if(t.scrollTarget){o=ce(ae(n,t.scrollTarget))}if(t.scroll==="top"&&(n||o)){o=o||n;o.scrollTop=0}if(t.scroll==="bottom"&&(r||o)){o=o||r;o.scrollTop=o.scrollHeight}}if(t.show){var o=null;if(t.showTarget){let e=t.showTarget;if(t.showTarget==="window"){e="body"}o=ce(ae(n,e))}if(t.show==="top"&&(n||o)){o=o||n;o.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(r||o)){o=o||r;o.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function wn(r,e,o,i){if(i==null){i={}}if(r==null){return i}const s=te(r,e);if(s){let e=s.trim();let t=o;if(e==="unset"){return null}if(e.indexOf("javascript:")===0){e=e.substr(11);t=true}else if(e.indexOf("js:")===0){e=e.substr(3);t=true}if(e.indexOf("{")!==0){e="{"+e+"}"}let n;if(t){n=vn(r,function(){return Function("return ("+e+")")()},{})}else{n=S(e)}for(const l in n){if(n.hasOwnProperty(l)){if(i[l]==null){i[l]=n[l]}}}}return wn(ce(u(r)),e,o,i)}function vn(e,t,n){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return n}}function Sn(e,t){return wn(e,"hx-vars",true,t)}function En(e,t){return wn(e,"hx-vals",false,t)}function Cn(e){return ue(Sn(e),En(e))}function On(t,n,r){if(r!==null){try{t.setRequestHeader(n,r)}catch(e){t.setRequestHeader(n,encodeURIComponent(r));t.setRequestHeader(n+"-URI-AutoEncoded","true")}}}function Rn(t){if(t.responseURL&&typeof URL!=="undefined"){try{const e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(ne().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function C(e,t){return t.test(e.getAllResponseHeaders())}function Hn(e,t,n){e=e.toLowerCase();if(n){if(n instanceof Element||typeof n==="string"){return he(e,t,null,null,{targetOverride:y(n),returnPromise:true})}else{return he(e,t,y(n.source),n.event,{handler:n.handler,headers:n.headers,values:n.values,targetOverride:y(n.target),swapOverride:n.swap,select:n.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Tn(e){const t=[];while(e){t.push(e);e=e.parentElement}return t}function qn(e,t,n){let r;let o;if(typeof URL==="function"){o=new URL(t,document.location.href);const i=document.location.origin;r=i===o.origin}else{o=t;r=l(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!r){return false}}return de(e,"htmx:validateUrl",ue({url:o,sameHost:r},n))}function Ln(e){if(e instanceof FormData)return e;const t=new FormData;for(const n in e){if(e.hasOwnProperty(n)){if(typeof e[n].forEach==="function"){e[n].forEach(function(e){t.append(n,e)})}else if(typeof e[n]==="object"&&!(e[n]instanceof Blob)){t.append(n,JSON.stringify(e[n]))}else{t.append(n,e[n])}}}return t}function Nn(r,o,e){return new Proxy(e,{get:function(t,e){if(typeof e==="number")return t[e];if(e==="length")return t.length;if(e==="push"){return function(e){t.push(e);r.append(o,e)}}if(typeof t[e]==="function"){return function(){t[e].apply(t,arguments);r.delete(o);t.forEach(function(e){r.append(o,e)})}}if(t[e]&&t[e].length===1){return t[e][0]}else{return t[e]}},set:function(e,t,n){e[t]=n;r.delete(o);e.forEach(function(e){r.append(o,e)});return true}})}function An(r){return new Proxy(r,{get:function(e,t){if(typeof t==="symbol"){return Reflect.get(e,t)}if(t==="toJSON"){return()=>Object.fromEntries(r)}if(t in e){if(typeof e[t]==="function"){return function(){return r[t].apply(r,arguments)}}else{return e[t]}}const n=r.getAll(t);if(n.length===0){return undefined}else if(n.length===1){return n[0]}else{return Nn(e,t,n)}},set:function(t,n,e){if(typeof n!=="string"){return false}t.delete(n);if(typeof e.forEach==="function"){e.forEach(function(e){t.append(n,e)})}else if(typeof e==="object"&&!(e instanceof Blob)){t.append(n,JSON.stringify(e))}else{t.append(n,e)}return true},deleteProperty:function(e,t){if(typeof t==="string"){e.delete(t)}return true},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function he(t,n,r,o,i,D){let s=null;let l=null;i=i!=null?i:{};if(i.returnPromise&&typeof Promise!=="undefined"){var e=new Promise(function(e,t){s=e;l=t})}if(r==null){r=ne().body}const M=i.handler||Mn;const X=i.select||null;if(!le(r)){oe(s);return e}const u=i.targetOverride||ce(Ce(r));if(u==null||u==ve){fe(r,"htmx:targetError",{target:te(r,"hx-target")});oe(l);return e}let c=ie(r);const a=c.lastButtonClicked;if(a){const L=ee(a,"formaction");if(L!=null){n=L}const N=ee(a,"formmethod");if(N!=null){if(N.toLowerCase()!=="dialog"){t=N}}}const f=re(r,"hx-confirm");if(D===undefined){const K=function(e){return he(t,n,r,o,i,!!e)};const G={target:u,elt:r,path:n,verb:t,triggeringEvent:o,etc:i,issueRequest:K,question:f};if(de(r,"htmx:confirm",G)===false){oe(s);return e}}let d=r;let h=re(r,"hx-sync");let g=null;let F=false;if(h){const A=h.split(":");const I=A[0].trim();if(I==="this"){d=Ee(r,"hx-sync")}else{d=ce(ae(r,I))}h=(A[1]||"drop").trim();c=ie(d);if(h==="drop"&&c.xhr&&c.abortable!==true){oe(s);return e}else if(h==="abort"){if(c.xhr){oe(s);return e}else{F=true}}else if(h==="replace"){de(d,"htmx:abort")}else if(h.indexOf("queue")===0){const Z=h.split(" ");g=(Z[1]||"last").trim()}}if(c.xhr){if(c.abortable){de(d,"htmx:abort")}else{if(g==null){if(o){const P=ie(o);if(P&&P.triggerSpec&&P.triggerSpec.queue){g=P.triggerSpec.queue}}if(g==null){g="last"}}if(c.queuedRequests==null){c.queuedRequests=[]}if(g==="first"&&c.queuedRequests.length===0){c.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(g==="all"){c.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(g==="last"){c.queuedRequests=[];c.queuedRequests.push(function(){he(t,n,r,o,i)})}oe(s);return e}}const p=new XMLHttpRequest;c.xhr=p;c.abortable=F;const m=function(){c.xhr=null;c.abortable=false;if(c.queuedRequests!=null&&c.queuedRequests.length>0){const e=c.queuedRequests.shift();e()}};const B=re(r,"hx-prompt");if(B){var y=prompt(B);if(y===null||!de(r,"htmx:prompt",{prompt:y,target:u})){oe(s);m();return e}}if(f&&!D){if(!confirm(f)){oe(s);m();return e}}let x=dn(r,u,y);if(t!=="get"&&!mn(r)){x["Content-Type"]="application/x-www-form-urlencoded"}if(i.headers){x=ue(x,i.headers)}const U=cn(r,t);let b=U.errors;const j=U.formData;if(i.values){un(j,Ln(i.values))}const V=Ln(Cn(r));const w=un(j,V);let v=hn(w,r);if(Q.config.getCacheBusterParam&&t==="get"){v.set("org.htmx.cache-buster",ee(u,"id")||"true")}if(n==null||n===""){n=ne().location.href}const S=wn(r,"hx-request");const _=ie(r).boosted;let E=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;const C={boosted:_,useUrlParams:E,formData:v,parameters:An(v),unfilteredFormData:w,unfilteredParameters:An(w),headers:x,target:u,verb:t,errors:b,withCredentials:i.credentials||S.credentials||Q.config.withCredentials,timeout:i.timeout||S.timeout||Q.config.timeout,path:n,triggeringEvent:o};if(!de(r,"htmx:configRequest",C)){oe(s);m();return e}n=C.path;t=C.verb;x=C.headers;v=Ln(C.parameters);b=C.errors;E=C.useUrlParams;if(b&&b.length>0){de(r,"htmx:validation:halted",C);oe(s);m();return e}const $=n.split("#");const z=$[0];const O=$[1];let R=n;if(E){R=z;const Y=!v.keys().next().done;if(Y){if(R.indexOf("?")<0){R+="?"}else{R+="&"}R+=fn(v);if(O){R+="#"+O}}}if(!qn(r,R,C)){fe(r,"htmx:invalidPath",C);oe(l);return e}p.open(t.toUpperCase(),R,true);p.overrideMimeType("text/html");p.withCredentials=C.withCredentials;p.timeout=C.timeout;if(S.noHeaders){}else{for(const k in x){if(x.hasOwnProperty(k)){const W=x[k];On(p,k,W)}}}const H={xhr:p,target:u,requestConfig:C,etc:i,boosted:_,select:X,pathInfo:{requestPath:n,finalRequestPath:R,responsePath:null,anchor:O}};p.onload=function(){try{const t=Tn(r);H.pathInfo.responsePath=Rn(p);M(r,H);if(H.keepIndicators!==true){en(T,q)}de(r,"htmx:afterRequest",H);de(r,"htmx:afterOnLoad",H);if(!le(r)){let e=null;while(t.length>0&&e==null){const n=t.shift();if(le(n)){e=n}}if(e){de(e,"htmx:afterRequest",H);de(e,"htmx:afterOnLoad",H)}}oe(s);m()}catch(e){fe(r,"htmx:onLoadError",ue({error:e},H));throw e}};p.onerror=function(){en(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendError",H);oe(l);m()};p.onabort=function(){en(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:sendAbort",H);oe(l);m()};p.ontimeout=function(){en(T,q);fe(r,"htmx:afterRequest",H);fe(r,"htmx:timeout",H);oe(l);m()};if(!de(r,"htmx:beforeRequest",H)){oe(s);m();return e}var T=Wt(r);var q=Qt(r);se(["loadstart","loadend","progress","abort"],function(t){se([p,p.upload],function(e){e.addEventListener(t,function(e){de(r,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});de(r,"htmx:beforeSend",H);const J=E?null:yn(p,r,v);p.send(J);return e}function In(e,t){const n=t.xhr;let r=null;let o=null;if(C(n,/HX-Push:/i)){r=n.getResponseHeader("HX-Push");o="push"}else if(C(n,/HX-Push-Url:/i)){r=n.getResponseHeader("HX-Push-Url");o="push"}else if(C(n,/HX-Replace-Url:/i)){r=n.getResponseHeader("HX-Replace-Url");o="replace"}if(r){if(r==="false"){return{}}else{return{type:o,path:r}}}const i=t.pathInfo.finalRequestPath;const s=t.pathInfo.responsePath;const l=re(e,"hx-push-url");const u=re(e,"hx-replace-url");const c=ie(e).boosted;let a=null;let f=null;if(l){a="push";f=l}else if(u){a="replace";f=u}else if(c){a="push";f=s||i}if(f){if(f==="false"){return{}}if(f==="true"){f=s||i}if(t.pathInfo.anchor&&f.indexOf("#")===-1){f=f+"#"+t.pathInfo.anchor}return{type:a,path:f}}else{return{}}}function Pn(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function kn(e){for(var t=0;t0){E().setTimeout(e,y.swapDelay)}else{e()}}if(f){fe(o,"htmx:responseError",ue({error:"Response Status Error Code "+s.status+" from "+i.pathInfo.requestPath},i))}}const Xn={};function Fn(){return{init:function(e){return null},getSelectors:function(){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,n){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,n,r){return false},encodeParameters:function(e,t,n){return null}}}function Bn(e,t){if(t.init){t.init(n)}Xn[e]=ue(Fn(),t)}function Un(e){delete Xn[e]}function jn(e,n,r){if(n==undefined){n=[]}if(e==undefined){return n}if(r==undefined){r=[]}const t=te(e,"hx-ext");if(t){se(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){r.push(e.slice(7));return}if(r.indexOf(e)<0){const t=Xn[e];if(t&&n.indexOf(t)<0){n.push(t)}}})}return jn(ce(u(e)),n,r)}var Vn=false;ne().addEventListener("DOMContentLoaded",function(){Vn=true});function _n(e){if(Vn||ne().readyState==="complete"){e()}else{ne().addEventListener("DOMContentLoaded",e)}}function $n(){if(Q.config.includeIndicatorStyles!==false){const e=Q.config.inlineStyleNonce?` nonce="${Q.config.inlineStyleNonce}"`:"";ne().head.insertAdjacentHTML("beforeend"," ."+Q.config.indicatorClass+"{opacity:0} ."+Q.config.requestClass+" ."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ."+Q.config.requestClass+"."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ")}}function zn(){const e=ne().querySelector('meta[name="htmx-config"]');if(e){return S(e.content)}else{return null}}function Jn(){const e=zn();if(e){Q.config=ue(Q.config,e)}}_n(function(){Jn();$n();let e=ne().body;Dt(e);const t=ne().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){const t=e.target;const n=ie(t);if(n&&n.xhr){n.xhr.abort()}});const n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){Yt();se(t,function(e){de(e,"htmx:restored",{document:ne(),triggerEvent:de})})}else{if(n){n(e)}}};E().setTimeout(function(){de(e,"htmx:load",{});e=null},0)});return Q}(); \ No newline at end of file diff --git a/merge/src/argus_htmx/static/hyperscript-0.9.13.min.js b/merge/src/argus_htmx/static/hyperscript-0.9.13.min.js new file mode 100644 index 000000000..810c119f5 --- /dev/null +++ b/merge/src/argus_htmx/static/hyperscript-0.9.13.min.js @@ -0,0 +1 @@ +(function(e,t){const r=t(e);if(typeof exports==="object"&&typeof exports["nodeName"]!=="string"){module.exports=r}else{e["_hyperscript"]=r;if("document"in e)e["_hyperscript"].browserInit()}})(typeof self!=="undefined"?self:this,(e=>{"use strict";const t={dynamicResolvers:[function(e,t){if(e==="Fixed"){return Number(t).toFixed()}else if(e.indexOf("Fixed:")===0){let r=e.split(":")[1];return Number(t).toFixed(parseInt(r))}}],String:function(e){if(e.toString){return e.toString()}else{return""+e}},Int:function(e){return parseInt(e)},Float:function(e){return parseFloat(e)},Number:function(e){return Number(e)},Date:function(e){return new Date(e)},Array:function(e){return Array.from(e)},JSON:function(e){return JSON.stringify(e)},Object:function(e){if(e instanceof String){e=e.toString()}if(typeof e==="string"){return JSON.parse(e)}else{return Object.assign({},e)}}};const r={attributes:"_, script, data-script",defaultTransition:"all 500ms ease-in",disableSelector:"[disable-scripting], [data-disable-scripting]",hideShowStrategies:{},conversions:t};class n{static OP_TABLE={"+":"PLUS","-":"MINUS","*":"MULTIPLY","/":"DIVIDE",".":"PERIOD","..":"ELLIPSIS","\\":"BACKSLASH",":":"COLON","%":"PERCENT","|":"PIPE","!":"EXCLAMATION","?":"QUESTION","#":"POUND","&":"AMPERSAND",$:"DOLLAR",";":"SEMI",",":"COMMA","(":"L_PAREN",")":"R_PAREN","<":"L_ANG",">":"R_ANG","<=":"LTE_ANG",">=":"GTE_ANG","==":"EQ","===":"EQQ","!=":"NEQ","!==":"NEQQ","{":"L_BRACE","}":"R_BRACE","[":"L_BRACKET","]":"R_BRACKET","=":"EQUALS"};static isValidCSSClassChar(e){return n.isAlpha(e)||n.isNumeric(e)||e==="-"||e==="_"||e===":"}static isValidCSSIDChar(e){return n.isAlpha(e)||n.isNumeric(e)||e==="-"||e==="_"||e===":"}static isWhitespace(e){return e===" "||e==="\t"||n.isNewline(e)}static positionString(e){return"[Line: "+e.line+", Column: "+e.column+"]"}static isNewline(e){return e==="\r"||e==="\n"}static isNumeric(e){return e>="0"&&e<="9"}static isAlpha(e){return e>="a"&&e<="z"||e>="A"&&e<="Z"}static isIdentifierChar(e,t){return e==="_"||e==="$"}static isReservedChar(e){return e==="`"||e==="^"}static isValidSingleQuoteStringStart(e){if(e.length>0){var t=e[e.length-1];if(t.type==="IDENTIFIER"||t.type==="CLASS_REF"||t.type==="ID_REF"){return false}if(t.op&&(t.value===">"||t.value===")")){return false}}return true}static tokenize(e,t){var r=[];var a=e;var o=0;var s=0;var u=1;var l="";var c=0;function f(){return t&&c===0}while(o=0){return this.consumeToken()}}requireToken(e,t){var r=this.matchToken(e,t);if(r){return r}else{this.raiseError(this,"Expected '"+e+"' but found '"+this.currentToken().value+"'")}}peekToken(e,t,r){t=t||0;r=r||"IDENTIFIER";if(this.tokens[t]&&this.tokens[t].value===e&&this.tokens[t].type===r){return this.tokens[t]}}matchToken(e,t){if(this.follows.indexOf(e)!==-1){return}t=t||"IDENTIFIER";if(this.currentToken()&&this.currentToken().value===e&&this.currentToken().type===t){return this.consumeToken()}}consumeToken(){var e=this.tokens.shift();this.consumed.push(e);this._lastConsumed=e;this.consumeWhitespace();return e}consumeUntil(e,t){var r=[];var n=this.token(0,true);while((t==null||n.type!==t)&&(e==null||n.value!==e)&&n.type!=="EOF"){var i=this.tokens.shift();this.consumed.push(i);r.push(n);n=this.token(0,true)}this.consumeWhitespace();return r}lastWhitespace(){if(this.consumed[this.consumed.length-1]&&this.consumed[this.consumed.length-1].type==="WHITESPACE"){return this.consumed[this.consumed.length-1].value}else{return""}}consumeUntilWhitespace(){return this.consumeUntil(null,"WHITESPACE")}hasMore(){return this.tokens.length>0}token(e,t){var r;var n=0;do{if(!t){while(this.tokens[n]&&this.tokens[n].type==="WHITESPACE"){n++}}r=this.tokens[n];e--;n++}while(e>-1);if(r){return r}else{return{type:"EOF",value:"<<>>"}}}currentToken(){return this.token(0)}lastMatch(){return this._lastConsumed}static sourceFor=function(){return this.programSource.substring(this.startToken.start,this.endToken.end)};static lineFor=function(){return this.programSource.split("\n")[this.startToken.line-1]};follows=[];pushFollow(e){this.follows.push(e)}popFollow(){this.follows.pop()}clearFollows(){var e=this.follows;this.follows=[];return e}restoreFollows(e){this.follows=e}}class a{constructor(e){this.runtime=e;this.possessivesDisabled=false;this.addGrammarElement("feature",(function(e,t,r){if(r.matchOpToken("(")){var n=e.requireElement("feature",r);r.requireOpToken(")");return n}var i=e.FEATURES[r.currentToken().value||""];if(i){return i(e,t,r)}}));this.addGrammarElement("command",(function(e,t,r){if(r.matchOpToken("(")){const t=e.requireElement("command",r);r.requireOpToken(")");return t}var n=e.COMMANDS[r.currentToken().value||""];let i;if(n){i=n(e,t,r)}else if(r.currentToken().type==="IDENTIFIER"){i=e.parseElement("pseudoCommand",r)}if(i){return e.parseElement("indirectStatement",r,i)}return i}));this.addGrammarElement("commandList",(function(e,t,r){if(r.hasMore()){var n=e.parseElement("command",r);if(n){r.matchToken("then");const t=e.parseElement("commandList",r);if(t)n.next=t;return n}}return{type:"emptyCommandListCommand",op:function(e){return t.findNext(this,e)},execute:function(e){return t.unifiedExec(this,e)}}}));this.addGrammarElement("leaf",(function(e,t,r){var n=e.parseAnyOf(e.LEAF_EXPRESSIONS,r);if(n==null){return e.parseElement("symbol",r)}return n}));this.addGrammarElement("indirectExpression",(function(e,t,r,n){for(var i=0;i{this.unifiedExec(e,t)})).catch((e=>{this.unifiedExec({op:function(){throw e}},t)}));return}else if(r===o.HALT){if(t.meta.finallyHandler&&!t.meta.handlingFinally){t.meta.handlingFinally=true;e=t.meta.finallyHandler}else{if(t.meta.onHalt){t.meta.onHalt()}if(t.meta.currentException){if(t.meta.reject){t.meta.reject(t.meta.currentException);return}else{throw t.meta.currentException}}else{return}}}else{e=r}}}unifiedEval(e,t){var r=[t];var n=false;var i=false;if(e.args){for(var a=0;a{r=this.wrapArrays(r);Promise.all(r).then((function(r){if(i){this.unwrapAsyncs(r)}try{var a=e.op.apply(e,r);t(a)}catch(e){n(e)}})).catch((function(e){n(e)}))}))}else{if(i){this.unwrapAsyncs(r)}return e.op.apply(e,r)}}_scriptAttrs=null;getScriptAttributes(){if(this._scriptAttrs==null){this._scriptAttrs=r.attributes.replace(/ /g,"").split(",")}return this._scriptAttrs}getScript(e){for(var t=0;t{this.initElement(e,e instanceof HTMLScriptElement&&e.type==="text/hyperscript"?document.body:e)}))}}initElement(e,t){if(e.closest&&e.closest(r.disableSelector)){return}var n=this.getInternalData(e);if(!n.initialized){var i=this.getScript(e);if(i){try{n.initialized=true;n.script=i;const r=this.lexer,s=this.parser;var a=r.tokenize(i);var o=s.parseHyperScript(a);if(!o)return;o.apply(t||e,e);setTimeout((()=>{this.triggerEvent(t||e,"load",{hyperscript:true})}),1)}catch(t){this.triggerEvent(e,"exception",{error:t});console.error("hyperscript errors were found on the following element:",e,"\n\n",t.message,t.stack)}}}}internalDataMap=new WeakMap;getInternalData(e){var t=this.internalDataMap.get(e);if(typeof t==="undefined"){this.internalDataMap.set(e,t={})}return t}typeCheck(e,t,r){if(e==null&&r){return true}var n=Object.prototype.toString.call(e).slice(8,-1);return n===t}getElementScope(e){var t=e.meta&&e.meta.owner;if(t){var r=this.getInternalData(t);var n="elementScope";if(e.meta.feature&&e.meta.feature.behavior){n=e.meta.feature.behavior+"Scope"}var i=h(r,n);return i}else{return{}}}isReservedWord(e){return["meta","it","result","locals","event","target","detail","sender","body"].includes(e)}isHyperscriptContext(e){return e instanceof f}resolveSymbol(t,r,n){if(t==="me"||t==="my"||t==="I"){return r.me}if(t==="it"||t==="its"||t==="result"){return r.result}if(t==="you"||t==="your"||t==="yourself"){return r.you}else{if(n==="global"){return e[t]}else if(n==="element"){var i=this.getElementScope(r);return i[t]}else if(n==="local"){return r.locals[t]}else{if(r.meta&&r.meta.context){var a=r.meta.context[t];if(typeof a!=="undefined"){return a}if(r.meta.context.detail){a=r.meta.context.detail[t];if(typeof a!=="undefined"){return a}}}if(this.isHyperscriptContext(r)&&!this.isReservedWord(t)){var o=r.locals[t]}else{var o=r[t]}if(typeof o!=="undefined"){return o}else{var i=this.getElementScope(r);o=i[t];if(typeof o!=="undefined"){return o}else{return e[t]}}}}}setSymbol(t,r,n,i){if(n==="global"){e[t]=i}else if(n==="element"){var a=this.getElementScope(r);a[t]=i}else if(n==="local"){r.locals[t]=i}else{if(this.isHyperscriptContext(r)&&!this.isReservedWord(t)&&typeof r.locals[t]!=="undefined"){r.locals[t]=i}else{var a=this.getElementScope(r);var o=a[t];if(typeof o!=="undefined"){a[t]=i}else{if(this.isHyperscriptContext(r)&&!this.isReservedWord(t)){r.locals[t]=i}else{r[t]=i}}}}}findNext(e,t){if(e){if(e.resolveNext){return e.resolveNext(t)}else if(e.next){return e.next}else{return this.findNext(e.parent,t)}}}flatGet(e,t,r){if(e!=null){var n=r(e,t);if(typeof n!=="undefined"){return n}if(this.shouldAutoIterate(e)){var i=[];for(var a of e){var o=r(a,t);i.push(o)}return i}}}resolveProperty(e,t){return this.flatGet(e,t,((e,t)=>e[t]))}resolveAttribute(e,t){return this.flatGet(e,t,((e,t)=>e.getAttribute&&e.getAttribute(t)))}resolveStyle(e,t){return this.flatGet(e,t,((e,t)=>e.style&&e.style[t]))}resolveComputedStyle(e,t){return this.flatGet(e,t,((e,t)=>getComputedStyle(e).getPropertyValue(t)))}assignToNamespace(t,r,n,i){let a;if(typeof document!=="undefined"&&t===document.body){a=e}else{a=this.getHyperscriptFeatures(t)}var o;while((o=r.shift())!==undefined){var s=a[o];if(s==null){s={};a[o]=s}a=s}a[n]=i}getHyperTrace(e,t){var r=[];var n=e;while(n.meta.caller){n=n.meta.caller}if(n.meta.traceMap){return n.meta.traceMap.get(t,r)}}registerHyperTrace(e,t){var r=[];var n=null;while(e!=null){r.push(e);n=e;e=e.meta.caller}if(n.meta.traceMap==null){n.meta.traceMap=new Map}if(!n.meta.traceMap.get(t)){var i={trace:r,print:function(e){e=e||console.error;e("hypertrace /// ");var t=0;for(var n=0;n",i.meta.feature.displayName.padEnd(t+2),"-",i.meta.owner)}}};n.meta.traceMap.set(t,i)}}escapeSelector(e){return e.replace(/:/g,(function(e){return"\\"+e}))}nullCheck(e,t){if(e==null){throw new Error("'"+t.sourceFor()+"' is null")}}isEmpty(e){return e==undefined||e.length===0}doesExist(e){if(e==null){return false}if(this.shouldAutoIterate(e)){for(const t of e){return true}return false}return true}getRootNode(e){if(e&&e instanceof Node){var t=e.getRootNode();if(t instanceof Document||t instanceof ShadowRoot)return t}return document}getEventQueueFor(e,t){let r=this.getInternalData(e);var n=r.eventQueues;if(n==null){n=new Map;r.eventQueues=n}var i=n.get(t);if(i==null){i={queue:[],executing:false};n.set(t,i)}return i}beepValueToConsole(e,t,r){if(this.triggerEvent(e,"hyperscript:beep",{element:e,expression:t,value:r})){var n;if(r){if(r instanceof m){n="ElementCollection"}else if(r.constructor){n=r.constructor.name}else{n="unknown"}}else{n="object (null)"}var a=r;if(n==="String"){a='"'+a+'"'}else if(r instanceof m){a=Array.from(r)}console.log("///_ BEEP! The expression ("+i.sourceFor.call(t).replace("beep! ","")+") evaluates to:",a,"of type "+n)}}hyperscriptUrl="document"in e&&document.currentScript?document.currentScript.src:null}function s(){let e=document.cookie.split("; ").map((e=>{let t=e.split("=");return{name:t[0],value:decodeURIComponent(t[1])}}));return e}function u(e){document.cookie=e+"=;expires=Thu, 01 Jan 1970 00:00:00 GMT"}function l(){for(const e of s()){u(e.name)}}const c=new Proxy({},{get(e,t){if(t==="then"||t==="asyncWrapper"){return null}else if(t==="length"){return s().length}else if(t==="clear"){return u}else if(t==="clearAll"){return l}else if(typeof t==="string"){if(!isNaN(t)){return s()[parseInt(t)]}else{let e=document.cookie.split("; ").find((e=>e.startsWith(t+"=")))?.split("=")[1];if(e){return decodeURIComponent(e)}}}else if(t===Symbol.iterator){return s()[t]}},set(e,t,r){var n=null;if("string"===typeof r){n=encodeURIComponent(r);n+=";samesite=lax"}else{n=encodeURIComponent(r.value);if(r.expires){n+=";expires="+r.maxAge}if(r.maxAge){n+=";max-age="+r.maxAge}if(r.partitioned){n+=";partitioned="+r.partitioned}if(r.path){n+=";path="+r.path}if(r.samesite){n+=";samesite="+r.path}if(r.secure){n+=";secure="+r.path}}document.cookie=t+"="+n;return true}});class f{constructor(t,r,n,i,a){this.meta={parser:a.parser,lexer:a.lexer,runtime:a,owner:t,feature:r,iterators:{},ctx:this};this.locals={cookies:c};this.me=n,this.you=undefined;this.result=undefined;this.event=i;this.target=i?i.target:null;this.detail=i?i.detail:null;this.sender=i?i.detail?i.detail.sender:null:null;this.body="document"in e?document.body:null;a.addFeatures(t,this)}}class m{constructor(e,t,r){this._css=e;this.relativeToElement=t;this.escape=r;this[p]=true}get css(){if(this.escape){return o.prototype.escapeSelector(this._css)}else{return this._css}}get className(){return this._css.substr(1)}get id(){return this.className()}contains(e){for(let t of this){if(t.contains(e)){return true}}return false}get length(){return this.selectMatches().length}[Symbol.iterator](){let e=this.selectMatches();return e[Symbol.iterator]()}selectMatches(){let e=o.prototype.getRootNode(this.relativeToElement).querySelectorAll(this.css);return e}}const p=Symbol();function h(e,t){var r=e[t];if(r){return r}else{var n={};e[t]=n;return n}}function v(e){try{return JSON.parse(e)}catch(e){d(e);return null}}function d(e){if(console.error){console.error(e)}else if(console.log){console.log("ERROR: ",e)}}function E(e,t){return new(e.bind.apply(e,[e].concat(t)))}function T(t){t.addLeafExpression("parenthesized",(function(e,t,r){if(r.matchOpToken("(")){var n=r.clearFollows();try{var i=e.requireElement("expression",r)}finally{r.restoreFollows(n)}r.requireOpToken(")");return i}}));t.addLeafExpression("string",(function(e,t,r){var i=r.matchTokenType("STRING");if(!i)return;var a=i.value;var o;if(i.template){var s=n.tokenize(a,true);o=e.parseStringTemplate(s)}else{o=[]}return{type:"string",token:i,args:o,op:function(e){var t="";for(var r=1;re instanceof Element))}get css(){let e="",t=0;for(const r of this.templateParts){if(r instanceof Element){e+="[data-hs-query-id='"+t+++"']"}else e+=r}return e}[Symbol.iterator](){this.elements.forEach(((e,t)=>e.dataset.hsQueryId=t));const e=super[Symbol.iterator]();this.elements.forEach((e=>e.removeAttribute("data-hs-query-id")));return e}}t.addLeafExpression("queryRef",(function(e,t,i){var a=i.matchOpToken("<");if(!a)return;var o=i.consumeUntil("/");i.requireOpToken("/");i.requireOpToken(">");var s=o.map((function(e){if(e.type==="STRING"){return'"'+e.value+'"'}else{return e.value}})).join("");var u,l,c;if(s.indexOf("$")>=0){u=true;l=n.tokenize(s,true);c=e.parseStringTemplate(l)}return{type:"queryRef",css:s,args:c,op:function(e,...t){if(u){return new r(s,e.me,t)}else{return new m(s,e.me)}},evaluate:function(e){return t.unifiedEval(this,e)}}}));t.addLeafExpression("attributeRef",(function(e,t,r){var n=r.matchTokenType("ATTRIBUTE_REF");if(!n)return;if(!n.value)return;var i=n.value;if(i.indexOf("[")===0){var a=i.substring(2,i.length-1)}else{var a=i.substring(1)}var o="["+a+"]";var s=a.split("=");var u=s[0];var l=s[1];if(l){if(l.indexOf('"')===0){l=l.substring(1,l.length-1)}}return{type:"attributeRef",name:u,css:o,value:l,op:function(e){var t=e.you||e.me;if(t){return t.getAttribute(u)}},evaluate:function(e){return t.unifiedEval(this,e)}}}));t.addLeafExpression("styleRef",(function(e,t,r){var n=r.matchTokenType("STYLE_REF");if(!n)return;if(!n.value)return;var i=n.value.substr(1);if(i.startsWith("computed-")){i=i.substr("computed-".length);return{type:"computedStyleRef",name:i,op:function(e){var r=e.you||e.me;if(r){return t.resolveComputedStyle(r,i)}},evaluate:function(e){return t.unifiedEval(this,e)}}}else{return{type:"styleRef",name:i,op:function(e){var r=e.you||e.me;if(r){return t.resolveStyle(r,i)}},evaluate:function(e){return t.unifiedEval(this,e)}}}}));t.addGrammarElement("objectKey",(function(e,t,r){var n;if(n=r.matchTokenType("STRING")){return{type:"objectKey",key:n.value,evaluate:function(){return n.value}}}else if(r.matchOpToken("[")){var i=e.parseElement("expression",r);r.requireOpToken("]");return{type:"objectKey",expr:i,args:[i],op:function(e,t){return t},evaluate:function(e){return t.unifiedEval(this,e)}}}else{var a="";do{n=r.matchTokenType("IDENTIFIER")||r.matchOpToken("-");if(n)a+=n.value}while(n);return{type:"objectKey",key:a,evaluate:function(){return a}}}}));t.addLeafExpression("objectLiteral",(function(e,t,r){if(!r.matchOpToken("{"))return;var n=[];var i=[];if(!r.matchOpToken("}")){do{var a=e.requireElement("objectKey",r);r.requireOpToken(":");var o=e.requireElement("expression",r);i.push(o);n.push(a)}while(r.matchOpToken(","));r.requireOpToken("}")}return{type:"objectLiteral",args:[n,i],op:function(e,t,r){var n={};for(var i=0;i");var a=e.requireElement("expression",r);return{type:"blockLiteral",args:n,expr:a,evaluate:function(e){var t=function(){for(var t=0;t=0;a--){var o=i[a];if(o.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return o}}if(n){return i[i.length-1]}};var l=function(e,t,r,n){var i=[];o.prototype.forEach(t,(function(t){if(t.matches(r)||t===e){i.push(t)}}));for(var a=0;a","<=",">=","==","===","!=","!==");var a=i?i.value:null;var o=true;var s=false;if(a==null){if(r.matchToken("is")||r.matchToken("am")){if(r.matchToken("not")){if(r.matchToken("in")){a="not in"}else if(r.matchToken("a")){a="not a";s=true}else if(r.matchToken("empty")){a="not empty";o=false}else{if(r.matchToken("really")){a="!=="}else{a="!="}if(r.matchToken("equal")){r.matchToken("to")}}}else if(r.matchToken("in")){a="in"}else if(r.matchToken("a")){a="a";s=true}else if(r.matchToken("empty")){a="empty";o=false}else if(r.matchToken("less")){r.requireToken("than");if(r.matchToken("or")){r.requireToken("equal");r.requireToken("to");a="<="}else{a="<"}}else if(r.matchToken("greater")){r.requireToken("than");if(r.matchToken("or")){r.requireToken("equal");r.requireToken("to");a=">="}else{a=">"}}else{if(r.matchToken("really")){a="==="}else{a="=="}if(r.matchToken("equal")){r.matchToken("to")}}}else if(r.matchToken("equals")){a="=="}else if(r.matchToken("really")){r.requireToken("equals");a="==="}else if(r.matchToken("exist")||r.matchToken("exists")){a="exist";o=false}else if(r.matchToken("matches")||r.matchToken("match")){a="match"}else if(r.matchToken("contains")||r.matchToken("contain")){a="contain"}else if(r.matchToken("includes")||r.matchToken("include")){a="include"}else if(r.matchToken("do")||r.matchToken("does")){r.requireToken("not");if(r.matchToken("matches")||r.matchToken("match")){a="not match"}else if(r.matchToken("contains")||r.matchToken("contain")){a="not contain"}else if(r.matchToken("exist")||r.matchToken("exist")){a="not exist";o=false}else if(r.matchToken("include")){a="not include"}else{e.raiseParseError(r,"Expected matches or contains")}}}if(a){var u,l,c;if(s){u=r.requireTokenType("IDENTIFIER");l=!r.matchOpToken("!")}else if(o){c=e.requireElement("mathExpression",r);if(a==="match"||a==="not match"){c=c.css?c.css:c}}var m=n;n={type:"comparisonOperator",operator:a,typeName:u,nullOk:l,lhs:n,rhs:c,args:[n,c],op:function(e,r,n){if(a==="=="){return r==n}else if(a==="!="){return r!=n}if(a==="==="){return r===n}else if(a==="!=="){return r!==n}if(a==="match"){return r!=null&&p(m,r,n)}if(a==="not match"){return r==null||!p(m,r,n)}if(a==="in"){return n!=null&&f(c,n,r)}if(a==="not in"){return n==null||!f(c,n,r)}if(a==="contain"){return r!=null&&f(m,r,n)}if(a==="not contain"){return r==null||!f(m,r,n)}if(a==="include"){return r!=null&&f(m,r,n)}if(a==="not include"){return r==null||!f(m,r,n)}if(a==="==="){return r===n}else if(a==="!=="){return r!==n}else if(a==="<"){return r"){return r>n}else if(a==="<="){return r<=n}else if(a===">="){return r>=n}else if(a==="empty"){return t.isEmpty(r)}else if(a==="not empty"){return!t.isEmpty(r)}else if(a==="exist"){return t.doesExist(r)}else if(a==="not exist"){return!t.doesExist(r)}else if(a==="a"){return t.typeCheck(r,u.value,l)}else if(a==="not a"){return!t.typeCheck(r,u.value,l)}else{throw"Unknown comparison : "+a}},evaluate:function(e){return t.unifiedEval(this,e)}}}return n}));t.addGrammarElement("comparisonExpression",(function(e,t,r){return e.parseAnyOf(["comparisonOperator","mathExpression"],r)}));t.addGrammarElement("logicalOperator",(function(e,t,r){var n=e.parseElement("comparisonExpression",r);var i,a=null;i=r.matchToken("and")||r.matchToken("or");while(i){a=a||i;if(a.value!==i.value){e.raiseParseError(r,"You must parenthesize logical operations with different operators")}var o=e.requireElement("comparisonExpression",r);const s=i.value;n={type:"logicalOperator",operator:s,lhs:n,rhs:o,args:[n,o],op:function(e,t,r){if(s==="and"){return t&&r}else{return t||r}},evaluate:function(e){return t.unifiedEval(this,e)}};i=r.matchToken("and")||r.matchToken("or")}return n}));t.addGrammarElement("logicalExpression",(function(e,t,r){return e.parseAnyOf(["logicalOperator","mathExpression"],r)}));t.addGrammarElement("asyncExpression",(function(e,t,r){if(r.matchToken("async")){var n=e.requireElement("logicalExpression",r);var i={type:"asyncExpression",value:n,evaluate:function(e){return{asyncWrapper:true,value:this.value.evaluate(e)}}};return i}else{return e.parseElement("logicalExpression",r)}}));t.addGrammarElement("expression",(function(e,t,r){r.matchToken("the");return e.parseElement("asyncExpression",r)}));t.addGrammarElement("assignableExpression",(function(e,t,r){r.matchToken("the");var n=e.parseElement("primaryExpression",r);if(n&&(n.type==="symbol"||n.type==="ofExpression"||n.type==="propertyAccess"||n.type==="attributeRefAccess"||n.type==="attributeRef"||n.type==="styleRef"||n.type==="arrayIndex"||n.type==="possessive")){return n}else{e.raiseParseError(r,"A target expression must be writable. The expression type '"+(n&&n.type)+"' is not.")}return n}));t.addGrammarElement("hyperscript",(function(e,t,r){var n=[];if(r.hasMore()){while(e.featureStart(r.currentToken())||r.currentToken().value==="("){var i=e.requireElement("feature",r);n.push(i);r.matchToken("end")}}return{type:"hyperscript",features:n,apply:function(e,t,r){for(const i of n){i.install(e,t,r)}}}}));var v=function(e){var t=[];if(e.token(0).value==="("&&(e.token(1).value===")"||e.token(2).value===","||e.token(2).value===")")){e.matchOpToken("(");do{t.push(e.requireTokenType("IDENTIFIER"))}while(e.matchOpToken(","));e.requireOpToken(")")}return t};t.addFeature("on",(function(e,t,r){if(!r.matchToken("on"))return;var n=false;if(r.matchToken("every")){n=true}var i=[];var a=null;do{var o=e.requireElement("eventName",r,"Expected event name");var s=o.evaluate();if(a){a=a+" or "+s}else{a="on "+s}var u=v(r);var l=null;if(r.matchOpToken("[")){l=e.requireElement("expression",r);r.requireOpToken("]")}var c,f,m;if(r.currentToken().type==="NUMBER"){var p=r.consumeToken();if(!p.value)return;c=parseInt(p.value);if(r.matchToken("to")){var h=r.consumeToken();if(!h.value)return;f=parseInt(h.value)}else if(r.matchToken("and")){m=true;r.requireToken("on")}}var d,E;if(s==="intersection"){d={};if(r.matchToken("with")){d["with"]=e.requireElement("expression",r).evaluate()}if(r.matchToken("having")){do{if(r.matchToken("margin")){d["rootMargin"]=e.requireElement("stringLike",r).evaluate()}else if(r.matchToken("threshold")){d["threshold"]=e.requireElement("expression",r).evaluate()}else{e.raiseParseError(r,"Unknown intersection config specification")}}while(r.matchToken("and"))}}else if(s==="mutation"){E={};if(r.matchToken("of")){do{if(r.matchToken("anything")){E["attributes"]=true;E["subtree"]=true;E["characterData"]=true;E["childList"]=true}else if(r.matchToken("childList")){E["childList"]=true}else if(r.matchToken("attributes")){E["attributes"]=true;E["attributeOldValue"]=true}else if(r.matchToken("subtree")){E["subtree"]=true}else if(r.matchToken("characterData")){E["characterData"]=true;E["characterDataOldValue"]=true}else if(r.currentToken().type==="ATTRIBUTE_REF"){var T=r.consumeToken();if(E["attributeFilter"]==null){E["attributeFilter"]=[]}if(T.value.indexOf("@")==0){E["attributeFilter"].push(T.value.substring(1))}else{e.raiseParseError(r,"Only shorthand attribute references are allowed here")}}else{e.raiseParseError(r,"Unknown mutation config specification")}}while(r.matchToken("or"))}else{E["attributes"]=true;E["characterData"]=true;E["childList"]=true}}var y=null;var k=false;if(r.matchToken("from")){if(r.matchToken("elsewhere")){k=true}else{r.pushFollow("or");try{y=e.requireElement("expression",r)}finally{r.popFollow()}if(!y){e.raiseParseError(r,'Expected either target value or "elsewhere".')}}}if(y===null&&k===false&&r.matchToken("elsewhere")){k=true}if(r.matchToken("in")){var x=e.parseElement("unaryExpression",r)}if(r.matchToken("debounced")){r.requireToken("at");var g=e.requireElement("unaryExpression",r);var b=g.evaluate({})}else if(r.matchToken("throttled")){r.requireToken("at");var g=e.requireElement("unaryExpression",r);var w=g.evaluate({})}i.push({execCount:0,every:n,on:s,args:u,filter:l,from:y,inExpr:x,elsewhere:k,startCount:c,endCount:f,unbounded:m,debounceTime:b,throttleTime:w,mutationSpec:E,intersectionSpec:d,debounced:undefined,lastExec:undefined})}while(r.matchToken("or"));var S=true;if(!n){if(r.matchToken("queue")){if(r.matchToken("all")){var N=true;var S=false}else if(r.matchToken("first")){var q=true}else if(r.matchToken("none")){var I=true}else{r.requireToken("last")}}}var C=e.requireElement("commandList",r);e.ensureTerminated(C);var R,A;if(r.matchToken("catch")){R=r.requireTokenType("IDENTIFIER").value;A=e.requireElement("commandList",r);e.ensureTerminated(A)}if(r.matchToken("finally")){var L=e.requireElement("commandList",r);e.ensureTerminated(L)}var O={displayName:a,events:i,start:C,every:n,execCount:0,errorHandler:A,errorSymbol:R,execute:function(e){let r=t.getEventQueueFor(e.me,O);if(r.executing&&n===false){if(I||q&&r.queue.length>0){return}if(S){r.queue.length=0}r.queue.push(e);return}O.execCount++;r.executing=true;e.meta.onHalt=function(){r.executing=false;var e=r.queue.shift();if(e){setTimeout((function(){O.execute(e)}),1)}};e.meta.reject=function(r){console.error(r.message?r.message:r);var n=t.getHyperTrace(e,r);if(n){n.print()}t.triggerEvent(e.me,"exception",{error:r})};C.execute(e)},install:function(e,r){for(const r of O.events){var n;if(r.elsewhere){n=[document]}else if(r.from){n=r.from.evaluate(t.makeContext(e,O,e,null))}else{n=[e]}t.implicitLoop(n,(function(n){var i=r.on;if(n==null){console.warn("'%s' feature ignored because target does not exists:",a,e);return}if(r.mutationSpec){i="hyperscript:mutation";const e=new MutationObserver((function(e,r){if(!O.executing){t.triggerEvent(n,i,{mutationList:e,observer:r})}}));e.observe(n,r.mutationSpec)}if(r.intersectionSpec){i="hyperscript:intersection";const e=new IntersectionObserver((function(r){for(const o of r){var a={observer:e};a=Object.assign(a,o);a["intersecting"]=o.isIntersecting;t.triggerEvent(n,i,a)}}),r.intersectionSpec);e.observe(n)}var o=n.addEventListener||n.on;o.call(n,i,(function a(o){if(typeof Node!=="undefined"&&e instanceof Node&&n!==e&&!e.isConnected){n.removeEventListener(i,a);return}var s=t.makeContext(e,O,e,o);if(r.elsewhere&&e.contains(o.target)){return}if(r.from){s.result=n}for(const e of r.args){let t=s.event[e.value];if(t!==undefined){s.locals[e.value]=t}else if("detail"in s.event){s.locals[e.value]=s.event["detail"][e.value]}}s.meta.errorHandler=A;s.meta.errorSymbol=R;s.meta.finallyHandler=L;if(r.filter){var u=s.meta.context;s.meta.context=s.event;try{var l=r.filter.evaluate(s);if(l){}else{return}}finally{s.meta.context=u}}if(r.inExpr){var c=o.target;while(true){if(c.matches&&c.matches(r.inExpr.css)){s.result=c;break}else{c=c.parentElement;if(c==null){return}}}}r.execCount++;if(r.startCount){if(r.endCount){if(r.execCountr.endCount){return}}else if(r.unbounded){if(r.execCount{var a=false;for(const s of i){var o=n=>{e.result=n;if(s.args){for(const t of s.args){e.locals[t.value]=n[t.value]||(n.detail?n.detail[t.value]:null)}}if(!a){a=true;r(t.findNext(this,e))}};if(s.name){n.addEventListener(s.name,o,{once:true})}else if(s.time!=null){setTimeout(o,s.time,s.time)}}}))}};return n}else{var s;if(r.matchToken("a")){r.requireToken("tick");s=0}else{s=e.requireElement("expression",r)}n={type:"waitCmd",time:s,args:[s],op:function(e,r){return new Promise((n=>{setTimeout((()=>{n(t.findNext(this,e))}),r)}))},execute:function(e){return t.unifiedExec(this,e)}};return n}}));t.addGrammarElement("dotOrColonPath",(function(e,t,r){var n=r.matchTokenType("IDENTIFIER");if(n){var i=[n.value];var a=r.matchOpToken(".")||r.matchOpToken(":");if(a){do{i.push(r.requireTokenType("IDENTIFIER","NUMBER").value)}while(r.matchOpToken(a.value))}return{type:"dotOrColonPath",path:i,evaluate:function(){return i.join(a?a.value:"")}}}}));t.addGrammarElement("eventName",(function(e,t,r){var n;if(n=r.matchTokenType("STRING")){return{evaluate:function(){return n.value}}}return e.parseElement("dotOrColonPath",r)}));function d(e,t,r,n){var i=t.requireElement("eventName",n);var a=t.parseElement("namedArgumentList",n);if(e==="send"&&n.matchToken("to")||e==="trigger"&&n.matchToken("on")){var o=t.requireElement("expression",n)}else{var o=t.requireElement("implicitMeTarget",n)}var s={eventName:i,details:a,to:o,args:[o,i,a],op:function(e,t,n,i){r.nullCheck(t,o);r.implicitLoop(t,(function(t){r.triggerEvent(t,n,i,e.me)}));return r.findNext(s,e)}};return s}t.addCommand("trigger",(function(e,t,r){if(r.matchToken("trigger")){return d("trigger",e,t,r)}}));t.addCommand("send",(function(e,t,r){if(r.matchToken("send")){return d("send",e,t,r)}}));var T=function(e,t,r,n){if(n){if(e.commandBoundary(r.currentToken())){e.raiseParseError(r,"'return' commands must return a value. If you do not wish to return a value, use 'exit' instead.")}else{var i=e.requireElement("expression",r)}}var a={value:i,args:[i],op:function(e,r){var n=e.meta.resolve;e.meta.returned=true;e.meta.returnValue=r;if(n){if(r){n(r)}else{n()}}return t.HALT}};return a};t.addCommand("return",(function(e,t,r){if(r.matchToken("return")){return T(e,t,r,true)}}));t.addCommand("exit",(function(e,t,r){if(r.matchToken("exit")){return T(e,t,r,false)}}));t.addCommand("halt",(function(e,t,r){if(r.matchToken("halt")){if(r.matchToken("the")){r.requireToken("event");if(r.matchOpToken("'")){r.requireToken("s")}var n=true}if(r.matchToken("bubbling")){var i=true}else if(r.matchToken("default")){var a=true}var o=T(e,t,r,false);var s={keepExecuting:true,bubbling:i,haltDefault:a,exit:o,op:function(e){if(e.event){if(i){e.event.stopPropagation()}else if(a){e.event.preventDefault()}else{e.event.stopPropagation();e.event.preventDefault()}if(n){return t.findNext(this,e)}else{return o}}}};return s}}));t.addCommand("log",(function(e,t,r){if(!r.matchToken("log"))return;var n=[e.parseElement("expression",r)];while(r.matchOpToken(",")){n.push(e.requireElement("expression",r))}if(r.matchToken("with")){var i=e.requireElement("expression",r)}var a={exprs:n,withExpr:i,args:[i,n],op:function(e,r,n){if(r){r.apply(null,n)}else{console.log.apply(null,n)}return t.findNext(this,e)}};return a}));t.addCommand("beep!",(function(e,t,r){if(!r.matchToken("beep!"))return;var n=[e.parseElement("expression",r)];while(r.matchOpToken(",")){n.push(e.requireElement("expression",r))}var i={exprs:n,args:[n],op:function(e,r){for(let i=0;i{if(!r.matchToken("pick"))return;r.matchToken("the");if(r.matchToken("item")||r.matchToken("items")||r.matchToken("character")||r.matchToken("characters")){const n=g(e,t,r);r.requireToken("from");const i=e.requireElement("expression",r);return{args:[i,n.from,n.to],op(e,r,i,a){if(n.toEnd)a=r.length;if(!n.includeStart)i++;if(n.includeEnd)a++;if(a==null||a==undefined)a=i+1;e.result=r.slice(i,a);return t.findNext(this,e)}}}if(r.matchToken("match")){r.matchToken("of");const n=e.parseElement("expression",r);let i="";if(r.matchOpToken("|")){i=r.requireToken("identifier").value}r.requireToken("from");const a=e.parseElement("expression",r);return{args:[a,n],op(e,r,n){e.result=new RegExp(n,i).exec(r);return t.findNext(this,e)}}}if(r.matchToken("matches")){r.matchToken("of");const n=e.parseElement("expression",r);let i="gu";if(r.matchOpToken("|")){i="g"+r.requireToken("identifier").value.replace("g","")}console.log("flags",i);r.requireToken("from");const a=e.parseElement("expression",r);return{args:[a,n],op(e,r,n){e.result=new w(n,i,r);return t.findNext(this,e)}}}}));t.addCommand("increment",(function(e,t,r){if(!r.matchToken("increment"))return;var n;var i=e.parseElement("assignableExpression",r);if(r.matchToken("by")){n=e.requireElement("expression",r)}var a={type:"implicitIncrementOp",target:i,args:[i,n],op:function(e,t,r){t=t?parseFloat(t):0;r=n?parseFloat(r):1;var i=t+r;e.result=i;return i},evaluate:function(e){return t.unifiedEval(this,e)}};return k(e,t,r,i,a)}));t.addCommand("decrement",(function(e,t,r){if(!r.matchToken("decrement"))return;var n;var i=e.parseElement("assignableExpression",r);if(r.matchToken("by")){n=e.requireElement("expression",r)}var a={type:"implicitDecrementOp",target:i,args:[i,n],op:function(e,t,r){t=t?parseFloat(t):0;r=n?parseFloat(r):1;var i=t-r;e.result=i;return i},evaluate:function(e){return t.unifiedEval(this,e)}};return k(e,t,r,i,a)}));function S(e,t){var r="text";var n;e.matchToken("a")||e.matchToken("an");if(e.matchToken("json")||e.matchToken("Object")){r="json"}else if(e.matchToken("response")){r="response"}else if(e.matchToken("html")){r="html"}else if(e.matchToken("text")){}else{n=t.requireElement("dotOrColonPath",e).evaluate()}return{type:r,conversion:n}}t.addCommand("fetch",(function(e,t,r){if(!r.matchToken("fetch"))return;var n=e.requireElement("stringLike",r);if(r.matchToken("as")){var i=S(r,e)}if(r.matchToken("with")&&r.currentToken().value!=="{"){var a=e.parseElement("nakedNamedArgumentList",r)}else{var a=e.parseElement("objectLiteral",r)}if(i==null&&r.matchToken("as")){i=S(r,e)}var o=i?i.type:"text";var s=i?i.conversion:null;var u={url:n,argExpressions:a,args:[n,a],op:function(e,r,n){var i=n||{};i["sender"]=e.me;i["headers"]=i["headers"]||{};var a=new AbortController;let l=e.me.addEventListener("fetch:abort",(function(){a.abort()}),{once:true});i["signal"]=a.signal;t.triggerEvent(e.me,"hyperscript:beforeFetch",i);t.triggerEvent(e.me,"fetch:beforeRequest",i);n=i;var c=false;if(n.timeout){setTimeout((function(){if(!c){a.abort()}}),n.timeout)}return fetch(r,n).then((function(r){let n={response:r};t.triggerEvent(e.me,"fetch:afterResponse",n);r=n.response;if(o==="response"){e.result=r;t.triggerEvent(e.me,"fetch:afterRequest",{result:r});c=true;return t.findNext(u,e)}if(o==="json"){return r.json().then((function(r){e.result=r;t.triggerEvent(e.me,"fetch:afterRequest",{result:r});c=true;return t.findNext(u,e)}))}return r.text().then((function(r){if(s)r=t.convertValue(r,s);if(o==="html")r=t.convertValue(r,"Fragment");e.result=r;t.triggerEvent(e.me,"fetch:afterRequest",{result:r});c=true;return t.findNext(u,e)}))})).catch((function(r){t.triggerEvent(e.me,"fetch:error",{reason:r});throw r})).finally((function(){e.me.removeEventListener("fetch:abort",l)}))}};return u}))}function y(e){e.addCommand("settle",(function(e,t,r){if(r.matchToken("settle")){if(!e.commandBoundary(r.currentToken())){var n=e.requireElement("expression",r)}else{var n=e.requireElement("implicitMeTarget",r)}var i={type:"settleCmd",args:[n],op:function(e,r){t.nullCheck(r,n);var a=null;var o=false;var s=false;var u=new Promise((function(e){a=e}));r.addEventListener("transitionstart",(function(){s=true}),{once:true});setTimeout((function(){if(!s&&!o){a(t.findNext(i,e))}}),500);r.addEventListener("transitionend",(function(){if(!o){a(t.findNext(i,e))}}),{once:true});return u},execute:function(e){return t.unifiedExec(this,e)}};return i}}));e.addCommand("add",(function(e,t,r){if(r.matchToken("add")){var n=e.parseElement("classRef",r);var i=null;var a=null;if(n==null){i=e.parseElement("attributeRef",r);if(i==null){a=e.parseElement("styleLiteral",r);if(a==null){e.raiseParseError(r,"Expected either a class reference or attribute expression")}}}else{var o=[n];while(n=e.parseElement("classRef",r)){o.push(n)}}if(r.matchToken("to")){var s=e.requireElement("expression",r)}else{var s=e.requireElement("implicitMeTarget",r)}if(r.matchToken("when")){if(a){e.raiseParseError(r,"Only class and properties are supported with a when clause")}var u=e.requireElement("expression",r)}if(o){return{classRefs:o,to:s,args:[s,o],op:function(e,r,n){t.nullCheck(r,s);t.forEach(n,(function(n){t.implicitLoop(r,(function(r){if(u){e.result=r;let i=t.evaluateNoPromise(u,e);if(i){if(r instanceof Element)r.classList.add(n.className)}else{if(r instanceof Element)r.classList.remove(n.className)}e.result=null}else{if(r instanceof Element)r.classList.add(n.className)}}))}));return t.findNext(this,e)}}}else if(i){return{type:"addCmd",attributeRef:i,to:s,args:[s],op:function(e,r,n){t.nullCheck(r,s);t.implicitLoop(r,(function(r){if(u){e.result=r;let n=t.evaluateNoPromise(u,e);if(n){r.setAttribute(i.name,i.value)}else{r.removeAttribute(i.name)}e.result=null}else{r.setAttribute(i.name,i.value)}}));return t.findNext(this,e)},execute:function(e){return t.unifiedExec(this,e)}}}else{return{type:"addCmd",cssDeclaration:a,to:s,args:[s,a],op:function(e,r,n){t.nullCheck(r,s);t.implicitLoop(r,(function(e){e.style.cssText+=n}));return t.findNext(this,e)},execute:function(e){return t.unifiedExec(this,e)}}}}}));e.addGrammarElement("styleLiteral",(function(e,t,r){if(!r.matchOpToken("{"))return;var n=[""];var i=[];while(r.hasMore()){if(r.matchOpToken("\\")){r.consumeToken()}else if(r.matchOpToken("}")){break}else if(r.matchToken("$")){var a=r.matchOpToken("{");var o=e.parseElement("expression",r);if(a)r.requireOpToken("}");i.push(o);n.push("")}else{var s=r.consumeToken();n[n.length-1]+=r.source.substring(s.start,s.end)}n[n.length-1]+=r.lastWhitespace()}return{type:"styleLiteral",args:[i],op:function(e,t){var r="";n.forEach((function(e,n){r+=e;if(n in t)r+=t[n]}));return r},evaluate:function(e){return t.unifiedEval(this,e)}}}));e.addCommand("remove",(function(e,t,r){if(r.matchToken("remove")){var n=e.parseElement("classRef",r);var i=null;var a=null;if(n==null){i=e.parseElement("attributeRef",r);if(i==null){a=e.parseElement("expression",r);if(a==null){e.raiseParseError(r,"Expected either a class reference, attribute expression or value expression")}}}else{var o=[n];while(n=e.parseElement("classRef",r)){o.push(n)}}if(r.matchToken("from")){var s=e.requireElement("expression",r)}else{if(a==null){var s=e.requireElement("implicitMeTarget",r)}}if(a){return{elementExpr:a,from:s,args:[a,s],op:function(e,r,n){t.nullCheck(r,a);t.implicitLoop(r,(function(e){if(e.parentElement&&(n==null||n.contains(e))){e.parentElement.removeChild(e)}}));return t.findNext(this,e)}}}else{return{classRefs:o,attributeRef:i,elementExpr:a,from:s,args:[o,s],op:function(e,r,n){t.nullCheck(n,s);if(r){t.forEach(r,(function(e){t.implicitLoop(n,(function(t){t.classList.remove(e.className)}))}))}else{t.implicitLoop(n,(function(e){e.removeAttribute(i.name)}))}return t.findNext(this,e)}}}}}));e.addCommand("toggle",(function(e,t,r){if(r.matchToken("toggle")){r.matchAnyToken("the","my");if(r.currentToken().type==="STYLE_REF"){let t=r.consumeToken();var n=t.value.substr(1);var a=true;var o=i(e,r,n);if(r.matchToken("of")){r.pushFollow("with");try{var s=e.requireElement("expression",r)}finally{r.popFollow()}}else{var s=e.requireElement("implicitMeTarget",r)}}else if(r.matchToken("between")){var u=true;var l=e.parseElement("classRef",r);r.requireToken("and");var c=e.requireElement("classRef",r)}else{var l=e.parseElement("classRef",r);var f=null;if(l==null){f=e.parseElement("attributeRef",r);if(f==null){e.raiseParseError(r,"Expected either a class reference or attribute expression")}}else{var m=[l];while(l=e.parseElement("classRef",r)){m.push(l)}}}if(a!==true){if(r.matchToken("on")){var s=e.requireElement("expression",r)}else{var s=e.requireElement("implicitMeTarget",r)}}if(r.matchToken("for")){var p=e.requireElement("expression",r)}else if(r.matchToken("until")){var h=e.requireElement("dotOrColonPath",r,"Expected event name");if(r.matchToken("from")){var v=e.requireElement("expression",r)}}var d={classRef:l,classRef2:c,classRefs:m,attributeRef:f,on:s,time:p,evt:h,from:v,toggle:function(e,r,n,i){t.nullCheck(e,s);if(a){t.implicitLoop(e,(function(e){o("toggle",e)}))}else if(u){t.implicitLoop(e,(function(e){if(e.classList.contains(r.className)){e.classList.remove(r.className);e.classList.add(n.className)}else{e.classList.add(r.className);e.classList.remove(n.className)}}))}else if(i){t.forEach(i,(function(r){t.implicitLoop(e,(function(e){e.classList.toggle(r.className)}))}))}else{t.implicitLoop(e,(function(e){if(e.hasAttribute(f.name)){e.removeAttribute(f.name)}else{e.setAttribute(f.name,f.value)}}))}},args:[s,p,h,v,l,c,m],op:function(e,r,n,i,a,o,s,u){if(n){return new Promise((function(i){d.toggle(r,o,s,u);setTimeout((function(){d.toggle(r,o,s,u);i(t.findNext(d,e))}),n)}))}else if(i){return new Promise((function(n){var l=a||e.me;l.addEventListener(i,(function(){d.toggle(r,o,s,u);n(t.findNext(d,e))}),{once:true});d.toggle(r,o,s,u)}))}else{this.toggle(r,o,s,u);return t.findNext(d,e)}}};return d}}));var t={display:function(r,n,i){if(i){n.style.display=i}else if(r==="toggle"){if(getComputedStyle(n).display==="none"){t.display("show",n,i)}else{t.display("hide",n,i)}}else if(r==="hide"){const t=e.runtime.getInternalData(n);if(t.originalDisplay==null){t.originalDisplay=n.style.display}n.style.display="none"}else{const t=e.runtime.getInternalData(n);if(t.originalDisplay&&t.originalDisplay!=="none"){n.style.display=t.originalDisplay}else{n.style.removeProperty("display")}}},visibility:function(e,r,n){if(n){r.style.visibility=n}else if(e==="toggle"){if(getComputedStyle(r).visibility==="hidden"){t.visibility("show",r,n)}else{t.visibility("hide",r,n)}}else if(e==="hide"){r.style.visibility="hidden"}else{r.style.visibility="visible"}},opacity:function(e,r,n){if(n){r.style.opacity=n}else if(e==="toggle"){if(getComputedStyle(r).opacity==="0"){t.opacity("show",r,n)}else{t.opacity("hide",r,n)}}else if(e==="hide"){r.style.opacity="0"}else{r.style.opacity="1"}}};var n=function(e,t,r){var n;var i=r.currentToken();if(i.value==="when"||i.value==="with"||e.commandBoundary(i)){n=e.parseElement("implicitMeTarget",r)}else{n=e.parseElement("expression",r)}return n};var i=function(e,n,i){var a=r.defaultHideShowStrategy;var o=t;if(r.hideShowStrategies){o=Object.assign(o,r.hideShowStrategies)}i=i||a||"display";var s=o[i];if(s==null){e.raiseParseError(n,"Unknown show/hide strategy : "+i)}return s};e.addCommand("hide",(function(e,t,r){if(r.matchToken("hide")){var a=n(e,t,r);var o=null;if(r.matchToken("with")){o=r.requireTokenType("IDENTIFIER","STYLE_REF").value;if(o.indexOf("*")===0){o=o.substr(1)}}var s=i(e,r,o);return{target:a,args:[a],op:function(e,r){t.nullCheck(r,a);t.implicitLoop(r,(function(e){s("hide",e)}));return t.findNext(this,e)}}}}));e.addCommand("show",(function(e,t,r){if(r.matchToken("show")){var a=n(e,t,r);var o=null;if(r.matchToken("with")){o=r.requireTokenType("IDENTIFIER","STYLE_REF").value;if(o.indexOf("*")===0){o=o.substr(1)}}var s=null;if(r.matchOpToken(":")){var u=r.consumeUntilWhitespace();r.matchTokenType("WHITESPACE");s=u.map((function(e){return e.value})).join("")}if(r.matchToken("when")){var l=e.requireElement("expression",r)}var c=i(e,r,o);return{target:a,when:l,args:[a],op:function(e,r){t.nullCheck(r,a);t.implicitLoop(r,(function(r){if(l){e.result=r;let n=t.evaluateNoPromise(l,e);if(n){c("show",r,s)}else{c("hide",r)}e.result=null}else{c("show",r,s)}}));return t.findNext(this,e)}}}}));e.addCommand("take",(function(e,t,r){if(r.matchToken("take")){let u=null;let l=[];while(u=e.parseElement("classRef",r)){l.push(u)}var n=null;var i=null;let c=l.length>0;if(!c){n=e.parseElement("attributeRef",r);if(n==null){e.raiseParseError(r,"Expected either a class reference or attribute expression")}if(r.matchToken("with")){i=e.requireElement("expression",r)}}if(r.matchToken("from")){var a=e.requireElement("expression",r)}if(r.matchToken("for")){var o=e.requireElement("expression",r)}else{var o=e.requireElement("implicitMeTarget",r)}if(c){var s={classRefs:l,from:a,forElt:o,args:[l,a,o],op:function(e,r,n,i){t.nullCheck(i,o);t.implicitLoop(r,(function(e){var r=e.className;if(n){t.implicitLoop(n,(function(e){e.classList.remove(r)}))}else{t.implicitLoop(e,(function(e){e.classList.remove(r)}))}t.implicitLoop(i,(function(e){e.classList.add(r)}))}));return t.findNext(this,e)}};return s}else{var s={attributeRef:n,from:a,forElt:o,args:[a,o,i],op:function(e,r,i,s){t.nullCheck(r,a);t.nullCheck(i,o);t.implicitLoop(r,(function(e){if(!s){e.removeAttribute(n.name)}else{e.setAttribute(n.name,s)}}));t.implicitLoop(i,(function(e){e.setAttribute(n.name,n.value||"")}));return t.findNext(this,e)}};return s}}}));function a(t,r,n,i){if(n!=null){var a=t.resolveSymbol(n,r)}else{var a=r}if(a instanceof Element||a instanceof HTMLDocument){while(a.firstChild)a.removeChild(a.firstChild);a.append(e.runtime.convertValue(i,"Fragment"));t.processNode(a)}else{if(n!=null){t.setSymbol(n,r,null,i)}else{throw"Don't know how to put a value into "+typeof r}}}e.addCommand("put",(function(e,t,r){if(r.matchToken("put")){var n=e.requireElement("expression",r);var i=r.matchAnyToken("into","before","after");if(i==null&&r.matchToken("at")){r.matchToken("the");i=r.matchAnyToken("start","end");r.requireToken("of")}if(i==null){e.raiseParseError(r,"Expected one of 'into', 'before', 'at start of', 'at end of', 'after'")}var o=e.requireElement("expression",r);var s=i.value;var u=false;var l=false;var c=null;var f=null;if(o.type==="arrayIndex"&&s==="into"){u=true;f=o.prop;c=o.root}else if(o.prop&&o.root&&s==="into"){f=o.prop.value;c=o.root}else if(o.type==="symbol"&&s==="into"){l=true;f=o.name}else if(o.type==="attributeRef"&&s==="into"){var m=true;f=o.name;c=e.requireElement("implicitMeTarget",r)}else if(o.type==="styleRef"&&s==="into"){var p=true;f=o.name;c=e.requireElement("implicitMeTarget",r)}else if(o.attribute&&s==="into"){var m=o.attribute.type==="attributeRef";var p=o.attribute.type==="styleRef";f=o.attribute.name;c=o.root}else{c=o}var h={target:o,operation:s,symbolWrite:l,value:n,args:[c,f,n],op:function(e,r,n,i){if(l){a(t,e,n,i)}else{t.nullCheck(r,c);if(s==="into"){if(m){t.implicitLoop(r,(function(e){e.setAttribute(n,i)}))}else if(p){t.implicitLoop(r,(function(e){e.style[n]=i}))}else if(u){r[n]=i}else{t.implicitLoop(r,(function(e){a(t,e,n,i)}))}}else{var o=s==="before"?Element.prototype.before:s==="after"?Element.prototype.after:s==="start"?Element.prototype.prepend:s==="end"?Element.prototype.append:Element.prototype.append;t.implicitLoop(r,(function(e){o.call(e,i instanceof Node?i:t.convertValue(i,"Fragment"));if(e.parentElement){t.processNode(e.parentElement)}else{t.processNode(e)}}))}}return t.findNext(this,e)}};return h}}));function o(e,t,r){var n;if(r.matchToken("the")||r.matchToken("element")||r.matchToken("elements")||r.currentToken().type==="CLASS_REF"||r.currentToken().type==="ID_REF"||r.currentToken().op&&r.currentToken().value==="<"){e.possessivesDisabled=true;try{n=e.parseElement("expression",r)}finally{delete e.possessivesDisabled}if(r.matchOpToken("'")){r.requireToken("s")}}else if(r.currentToken().type==="IDENTIFIER"&&r.currentToken().value==="its"){var i=r.matchToken("its");n={type:"pseudopossessiveIts",token:i,name:i.value,evaluate:function(e){return t.resolveSymbol("it",e)}}}else{r.matchToken("my")||r.matchToken("me");n=e.parseElement("implicitMeTarget",r)}return n}e.addCommand("transition",(function(e,t,n){if(n.matchToken("transition")){var i=o(e,t,n);var a=[];var s=[];var u=[];var l=n.currentToken();while(!e.commandBoundary(l)&&l.value!=="over"&&l.value!=="using"){if(n.currentToken().type==="STYLE_REF"){let e=n.consumeToken();let t=e.value.substr(1);a.push({type:"styleRefValue",evaluate:function(){return t}})}else{a.push(e.requireElement("stringLike",n))}if(n.matchToken("from")){s.push(e.requireElement("expression",n))}else{s.push(null)}n.requireToken("to");if(n.matchToken("initial")){u.push({type:"initial_literal",evaluate:function(){return"initial"}})}else{u.push(e.requireElement("expression",n))}l=n.currentToken()}if(n.matchToken("over")){var c=e.requireElement("expression",n)}else if(n.matchToken("using")){var f=e.requireElement("expression",n)}var m={to:u,args:[i,a,s,u,f,c],op:function(e,n,a,o,s,u,l){t.nullCheck(n,i);var c=[];t.implicitLoop(n,(function(e){var n=new Promise((function(n,i){var c=e.style.transition;if(l){e.style.transition="all "+l+"ms ease-in"}else if(u){e.style.transition=u}else{e.style.transition=r.defaultTransition}var f=t.getInternalData(e);var m=getComputedStyle(e);var p={};for(var h=0;he.forEach((e=>S(e))))).then((()=>n((function(){a();k.processNode(document.documentElement);e.document.addEventListener("htmx:load",(function(e){k.processNode(e.detail.elt)}))}))));function n(e){if(document.readyState!=="loading"){setTimeout(e)}else{document.addEventListener("DOMContentLoaded",e)}}function i(){var e=document.querySelector('meta[name="htmx-config"]');if(e){return v(e.content)}else{return null}}function a(){var e=i();if(e){Object.assign(r,e)}}}const S=Object.assign(b,{config:r,use(e){e(S)},internals:{lexer:x,parser:g,runtime:k,Lexer:n,Tokens:i,Parser:a,Runtime:o},ElementCollection:m,addFeature:g.addFeature.bind(g),addCommand:g.addCommand.bind(g),addLeafExpression:g.addLeafExpression.bind(g),addIndirectExpression:g.addIndirectExpression.bind(g),evaluate:k.evaluate.bind(k),parse:k.parse.bind(k),processNode:k.processNode.bind(k),version:"0.9.13",browserInit:w});return S})); diff --git a/merge/src/argus_htmx/static/logo_white.svg b/merge/src/argus_htmx/static/logo_white.svg new file mode 100644 index 000000000..b44680164 --- /dev/null +++ b/merge/src/argus_htmx/static/logo_white.svg @@ -0,0 +1,37 @@ + \ No newline at end of file diff --git a/merge/src/argus_htmx/static/styles.css b/merge/src/argus_htmx/static/styles.css new file mode 100644 index 000000000..b304384f8 --- /dev/null +++ b/merge/src/argus_htmx/static/styles.css @@ -0,0 +1,4286 @@ +/* +! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +:root, +[data-theme] { + background-color: var(--fallback-b1,oklch(var(--b1)/1)); + color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +@supports not (color: oklch(0% 0 0)) { + :root { + color-scheme: light; + --fallback-p: #491eff; + --fallback-pc: #d4dbff; + --fallback-s: #ff41c7; + --fallback-sc: #fff9fc; + --fallback-a: #00cfbd; + --fallback-ac: #00100d; + --fallback-n: #2b3440; + --fallback-nc: #d7dde4; + --fallback-b1: #ffffff; + --fallback-b2: #e5e6e6; + --fallback-b3: #e5e6e6; + --fallback-bc: #1f2937; + --fallback-in: #00b3f0; + --fallback-inc: #000000; + --fallback-su: #00ca92; + --fallback-suc: #000000; + --fallback-wa: #ffc22d; + --fallback-wac: #000000; + --fallback-er: #ff6f70; + --fallback-erc: #000000; + } + + @media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --fallback-p: #7582ff; + --fallback-pc: #050617; + --fallback-s: #ff71cf; + --fallback-sc: #190211; + --fallback-a: #00c7b5; + --fallback-ac: #000e0c; + --fallback-n: #2a323c; + --fallback-nc: #a6adbb; + --fallback-b1: #1d232a; + --fallback-b2: #191e24; + --fallback-b3: #15191e; + --fallback-bc: #a6adbb; + --fallback-in: #00b3f0; + --fallback-inc: #000000; + --fallback-su: #00ca92; + --fallback-suc: #000000; + --fallback-wa: #ffc22d; + --fallback-wac: #000000; + --fallback-er: #ff6f70; + --fallback-erc: #000000; + } + } +} + +html { + -webkit-tap-highlight-color: transparent; +} + +* { + scrollbar-color: color-mix(in oklch, currentColor 35%, transparent) transparent; +} + +*:hover { + scrollbar-color: color-mix(in oklch, currentColor 60%, transparent) transparent; +} + +:root { + color-scheme: dark; + --in: 72.06% 0.191 231.6; + --su: 64.8% 0.150 160; + --wa: 84.71% 0.199 83.87; + --er: 71.76% 0.221 22.18; + --pc: 13.138% 0.0392 275.75; + --sc: 14.96% 0.052 342.55; + --ac: 14.902% 0.0334 183.61; + --inc: 0% 0 0; + --suc: 0% 0 0; + --wac: 0% 0 0; + --erc: 0% 0 0; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 65.69% 0.196 275.75; + --s: 74.8% 0.26 342.55; + --a: 74.51% 0.167 183.61; + --n: 31.3815% 0.021108 254.139175; + --nc: 74.6477% 0.0216 264.435964; + --b1: 25.3267% 0.015896 252.417568; + --b2: 23.2607% 0.013807 253.100675; + --b3: 21.1484% 0.01165 254.087939; + --bc: 74.6477% 0.0216 264.435964; +} + +[data-theme=dark] { + color-scheme: dark; + --in: 72.06% 0.191 231.6; + --su: 64.8% 0.150 160; + --wa: 84.71% 0.199 83.87; + --er: 71.76% 0.221 22.18; + --pc: 13.138% 0.0392 275.75; + --sc: 14.96% 0.052 342.55; + --ac: 14.902% 0.0334 183.61; + --inc: 0% 0 0; + --suc: 0% 0 0; + --wac: 0% 0 0; + --erc: 0% 0 0; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 65.69% 0.196 275.75; + --s: 74.8% 0.26 342.55; + --a: 74.51% 0.167 183.61; + --n: 31.3815% 0.021108 254.139175; + --nc: 74.6477% 0.0216 264.435964; + --b1: 25.3267% 0.015896 252.417568; + --b2: 23.2607% 0.013807 253.100675; + --b3: 21.1484% 0.01165 254.087939; + --bc: 74.6477% 0.0216 264.435964; +} + +:root:has(input.theme-controller[value=dark]:checked) { + color-scheme: dark; + --in: 72.06% 0.191 231.6; + --su: 64.8% 0.150 160; + --wa: 84.71% 0.199 83.87; + --er: 71.76% 0.221 22.18; + --pc: 13.138% 0.0392 275.75; + --sc: 14.96% 0.052 342.55; + --ac: 14.902% 0.0334 183.61; + --inc: 0% 0 0; + --suc: 0% 0 0; + --wac: 0% 0 0; + --erc: 0% 0 0; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 65.69% 0.196 275.75; + --s: 74.8% 0.26 342.55; + --a: 74.51% 0.167 183.61; + --n: 31.3815% 0.021108 254.139175; + --nc: 74.6477% 0.0216 264.435964; + --b1: 25.3267% 0.015896 252.417568; + --b2: 23.2607% 0.013807 253.100675; + --b3: 21.1484% 0.01165 254.087939; + --bc: 74.6477% 0.0216 264.435964; +} + +[data-theme=light] { + color-scheme: light; + --in: 72.06% 0.191 231.6; + --su: 64.8% 0.150 160; + --wa: 84.71% 0.199 83.87; + --er: 71.76% 0.221 22.18; + --pc: 89.824% 0.06192 275.75; + --ac: 15.352% 0.0368 183.61; + --inc: 0% 0 0; + --suc: 0% 0 0; + --wac: 0% 0 0; + --erc: 0% 0 0; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 49.12% 0.3096 275.75; + --s: 69.71% 0.329 342.55; + --sc: 98.71% 0.0106 342.55; + --a: 76.76% 0.184 183.61; + --n: 32.1785% 0.02476 255.701624; + --nc: 89.4994% 0.011585 252.096176; + --b1: 100% 0 0; + --b2: 96.1151% 0 0; + --b3: 92.4169% 0.00108 197.137559; + --bc: 27.8078% 0.029596 256.847952; +} + +:root:has(input.theme-controller[value=light]:checked) { + color-scheme: light; + --in: 72.06% 0.191 231.6; + --su: 64.8% 0.150 160; + --wa: 84.71% 0.199 83.87; + --er: 71.76% 0.221 22.18; + --pc: 89.824% 0.06192 275.75; + --ac: 15.352% 0.0368 183.61; + --inc: 0% 0 0; + --suc: 0% 0 0; + --wac: 0% 0 0; + --erc: 0% 0 0; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --p: 49.12% 0.3096 275.75; + --s: 69.71% 0.329 342.55; + --sc: 98.71% 0.0106 342.55; + --a: 76.76% 0.184 183.61; + --n: 32.1785% 0.02476 255.701624; + --nc: 89.4994% 0.011585 252.096176; + --b1: 100% 0 0; + --b2: 96.1151% 0 0; + --b3: 92.4169% 0.00108 197.137559; + --bc: 27.8078% 0.029596 256.847952; +} + +[data-theme=argus] { + --p: 50.0719% 0.099708 229.935495; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --pc: 90.0131% 0.020378 229.062151; + --s: 81.1089% 0.160737 83.094462; + --sc: 16.0976% 0.033017 84.44886; + --a: 57.0203% 0.176029 40.82272; + --ac: 91.2668% 0.035092 39.983706; + --n: 50.0719% 0.099708 229.935495; + --nc: 90.0131% 0.020378 229.062151; + --b1: 97.6898% 0.015282 222.677002; + --b2: 87.8396% 0.013785 226.581051; + --b3: 78.1992% 0.011943 217.566813; + --bc: 19.5043% 0.002558 247.957994; + --in: 56.8673% 0.19351 255.909793; + --inc: 11.5624% 0.036526 253.76502; + --su: 54.0146% 0.183798 142.495339; + --suc: 90.7096% 0.036394 142.726976; + --wa: 63.5453% 0.210841 37.389442; + --wac: 12.8885% 0.041866 38.356211; + --er: 64.1045% 0.179995 21.389979; + --erc: 12.5861% 0.036535 17.51583; +} + +:root:has(input.theme-controller[value=argus]:checked) { + --p: 50.0719% 0.099708 229.935495; + --rounded-box: 1rem; + --rounded-btn: 0.5rem; + --rounded-badge: 1.9rem; + --animation-btn: 0.25s; + --animation-input: .2s; + --btn-focus-scale: 0.95; + --border-btn: 1px; + --tab-border: 1px; + --tab-radius: 0.5rem; + --pc: 90.0131% 0.020378 229.062151; + --s: 81.1089% 0.160737 83.094462; + --sc: 16.0976% 0.033017 84.44886; + --a: 57.0203% 0.176029 40.82272; + --ac: 91.2668% 0.035092 39.983706; + --n: 50.0719% 0.099708 229.935495; + --nc: 90.0131% 0.020378 229.062151; + --b1: 97.6898% 0.015282 222.677002; + --b2: 87.8396% 0.013785 226.581051; + --b3: 78.1992% 0.011943 217.566813; + --bc: 19.5043% 0.002558 247.957994; + --in: 56.8673% 0.19351 255.909793; + --inc: 11.5624% 0.036526 253.76502; + --su: 54.0146% 0.183798 142.495339; + --suc: 90.7096% 0.036394 142.726976; + --wa: 63.5453% 0.210841 37.389442; + --wac: 12.8885% 0.041866 38.356211; + --er: 64.1045% 0.179995 21.389979; + --erc: 12.5861% 0.036535 17.51583; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.alert { + display: grid; + width: 100%; + grid-auto-flow: row; + align-content: flex-start; + align-items: center; + justify-items: center; + gap: 1rem; + text-align: center; + border-radius: var(--rounded-box, 1rem); + border-width: 1px; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + padding: 1rem; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --alert-bg: var(--fallback-b2,oklch(var(--b2)/1)); + --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1)); + background-color: var(--alert-bg); +} + +@media (min-width: 640px) { + .alert { + grid-auto-flow: column; + grid-template-columns: auto minmax(auto,1fr); + justify-items: start; + text-align: start; + } +} + +.avatar { + position: relative; + display: inline-flex; +} + +.avatar > div { + display: block; + aspect-ratio: 1 / 1; + overflow: hidden; +} + +.avatar img { + height: 100%; + width: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.avatar.placeholder > div { + display: flex; + align-items: center; + justify-content: center; +} + +.badge { + display: inline-flex; + align-items: center; + justify-content: center; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + height: 1.25rem; + font-size: 0.875rem; + line-height: 1.25rem; + width: -moz-fit-content; + width: fit-content; + padding-left: 0.563rem; + padding-right: 0.563rem; + border-radius: var(--rounded-badge, 1.9rem); + border-width: 1px; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + +@media (hover:hover) { + .checkbox-primary:hover { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + } + + .checkbox-accent:hover { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); + } + + .label a:hover { + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + } + + .menu li > *:not(ul, .menu-title, details, .btn):active, +.menu li > *:not(ul, .menu-title, details, .btn).active, +.menu li > details > summary:active { + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + } + + .tab:hover { + --tw-text-opacity: 1; + } + + .table tr.hover:hover, + .table tr.hover:nth-child(even):hover { + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + } + + .table-zebra tr.hover:hover, + .table-zebra tr.hover:nth-child(even):hover { + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + } +} + +.btn { + display: inline-flex; + height: 3rem; + min-height: 3rem; + flex-shrink: 0; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + border-radius: var(--rounded-btn, 0.5rem); + border-color: transparent; + border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity)); + padding-left: 1rem; + padding-right: 1rem; + text-align: center; + font-size: 0.875rem; + line-height: 1em; + gap: 0.5rem; + font-weight: 600; + text-decoration-line: none; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + border-width: var(--border-btn, 1px); + transition-property: color, background-color, border-color, opacity, box-shadow, transform; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); + background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity)); + --tw-bg-opacity: 1; + --tw-border-opacity: 1; +} + +.btn-disabled, + .btn[disabled], + .btn:disabled { + pointer-events: none; +} + +.btn-circle { + height: 3rem; + width: 3rem; + border-radius: 9999px; + padding: 0px; +} + +:where(.btn:is(input[type="checkbox"])), +:where(.btn:is(input[type="radio"])) { + width: auto; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.btn:is(input[type="checkbox"]):after, +.btn:is(input[type="radio"]):after { + --tw-content: attr(aria-label); + content: var(--tw-content); +} + +.card { + position: relative; + display: flex; + flex-direction: column; + border-radius: var(--rounded-box, 1rem); +} + +.card:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.card-body { + display: flex; + flex: 1 1 auto; + flex-direction: column; + padding: var(--padding-card, 2rem); + gap: 0.5rem; +} + +.card-body :where(p) { + flex-grow: 1; +} + +.card-actions { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 0.5rem; +} + +.card figure { + display: flex; + align-items: center; + justify-content: center; +} + +.card.image-full { + display: grid; +} + +.card.image-full:before { + position: relative; + content: ""; + z-index: 10; + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + opacity: 0.75; +} + +.card.image-full:before, + .card.image-full > * { + grid-column-start: 1; + grid-row-start: 1; +} + +.card.image-full > figure img { + height: 100%; + -o-object-fit: cover; + object-fit: cover; +} + +.card.image-full > .card-body { + position: relative; + z-index: 20; + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +.checkbox { + flex-shrink: 0; + --chkbg: var(--fallback-bc,oklch(var(--bc)/1)); + --chkfg: var(--fallback-b1,oklch(var(--b1)/1)); + height: 1.5rem; + width: 1.5rem; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; +} + +.divider { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; + margin-top: 1rem; + margin-bottom: 1rem; + height: 1rem; + white-space: nowrap; +} + +.divider:before, + .divider:after { + height: 0.125rem; + width: 100%; + flex-grow: 1; + --tw-content: ''; + content: var(--tw-content); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); +} + +.divider-start:before { + display: none; +} + +.divider-end:after { + display: none; +} + +.dropdown { + position: relative; + display: inline-block; +} + +.dropdown > *:not(summary):focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.dropdown .dropdown-content { + position: absolute; +} + +.dropdown:is(:not(details)) .dropdown-content { + visibility: hidden; + opacity: 0; + transform-origin: top; + --tw-scale-x: .95; + --tw-scale-y: .95; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; +} + +.dropdown-end .dropdown-content { + inset-inline-end: 0px; +} + +.dropdown-left .dropdown-content { + bottom: auto; + inset-inline-end: 100%; + top: 0px; + transform-origin: right; +} + +.dropdown-right .dropdown-content { + bottom: auto; + inset-inline-start: 100%; + top: 0px; + transform-origin: left; +} + +.dropdown-bottom .dropdown-content { + bottom: auto; + top: 100%; + transform-origin: top; +} + +.dropdown-top .dropdown-content { + bottom: 100%; + top: auto; + transform-origin: bottom; +} + +.dropdown-end.dropdown-right .dropdown-content { + bottom: 0px; + top: auto; +} + +.dropdown-end.dropdown-left .dropdown-content { + bottom: 0px; + top: auto; +} + +.dropdown.dropdown-open .dropdown-content, +.dropdown:not(.dropdown-hover):focus .dropdown-content, +.dropdown:focus-within .dropdown-content { + visibility: visible; + opacity: 1; +} + +@media (hover: hover) { + .dropdown.dropdown-hover:hover .dropdown-content { + visibility: visible; + opacity: 1; + } + + .btn:hover { + --tw-border-opacity: 1; + border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn:hover { + background-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, + black + ); + border-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, + black + ); + } + } + + @supports not (color: oklch(0% 0 0)) { + .btn:hover { + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + } + + .btn.glass:hover { + --glass-opacity: 25%; + --glass-border-opacity: 15%; + } + + .btn-ghost:hover { + border-color: transparent; + } + + @supports (color: oklch(0% 0 0)) { + .btn-ghost:hover { + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + } + } + + .btn-outline.btn-primary:hover { + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-primary:hover { + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } + } + + .btn-outline.btn-accent:hover { + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-accent:hover { + background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + } + } + + .btn-disabled:hover, + .btn[disabled]:hover, + .btn:disabled:hover { + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover { + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } + } + + .dropdown.dropdown-hover:hover .dropdown-content { + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + } + + :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover { + cursor: pointer; + outline: 2px solid transparent; + outline-offset: 2px; + } + + @supports (color: oklch(0% 0 0)) { + :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover { + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + } + } + + .tab[disabled], + .tab[disabled]:hover { + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } +} + +.dropdown:is(details) summary::-webkit-details-marker { + display: none; +} + +.footer { + display: grid; + width: 100%; + grid-auto-flow: row; + place-items: start; + -moz-column-gap: 1rem; + column-gap: 1rem; + row-gap: 2.5rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.footer > * { + display: grid; + place-items: start; + gap: 0.5rem; +} + +@media (min-width: 48rem) { + .footer { + grid-auto-flow: column; + } + + .footer-center { + grid-auto-flow: row dense; + } +} + +.form-control { + display: flex; + flex-direction: column; +} + +.label { + display: flex; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + align-items: center; + justify-content: space-between; + padding-left: 0.25rem; + padding-right: 0.25rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.indicator { + position: relative; + display: inline-flex; + width: -moz-max-content; + width: max-content; +} + +.indicator :where(.indicator-item) { + z-index: 1; + position: absolute; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + white-space: nowrap; +} + +.input { + flex-shrink: 1; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 1rem; + line-height: 2; + line-height: 1.5rem; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.input[type="number"]::-webkit-inner-spin-button, +.input-md[type="number"]::-webkit-inner-spin-button { + margin-top: -1rem; + margin-bottom: -1rem; + margin-inline-end: -1rem; +} + +.join { + display: inline-flex; + align-items: stretch; + border-radius: var(--rounded-btn, 0.5rem); +} + +.join :where(.join-item) { + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:not(:first-child):not(:last-child), + .join *:not(:first-child):not(:last-child) .join-item { + border-start-end-radius: 0; + border-end-end-radius: 0; + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .join-item { + border-start-end-radius: 0; + border-end-end-radius: 0; +} + +.join .dropdown .join-item:first-child:not(:last-child), + .join *:first-child:not(:last-child) .dropdown .join-item { + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +.join :where(.join-item:first-child:not(:last-child)), + .join :where(*:first-child:not(:last-child) .join-item) { + border-end-start-radius: inherit; + border-start-start-radius: inherit; +} + +.join .join-item:last-child:not(:first-child), + .join *:last-child:not(:first-child) .join-item { + border-end-start-radius: 0; + border-start-start-radius: 0; +} + +.join :where(.join-item:last-child:not(:first-child)), + .join :where(*:last-child:not(:first-child) .join-item) { + border-start-end-radius: inherit; + border-end-end-radius: inherit; +} + +@supports not selector(:has(*)) { + :where(.join *) { + border-radius: inherit; + } +} + +@supports selector(:has(*)) { + :where(.join *:has(.join-item)) { + border-radius: inherit; + } +} + +.link { + cursor: pointer; + text-decoration-line: underline; +} + +.mask { + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; +} + +.menu { + display: flex; + flex-direction: column; + flex-wrap: wrap; + font-size: 0.875rem; + line-height: 1.25rem; + padding: 0.5rem; +} + +.menu :where(li ul) { + position: relative; + white-space: nowrap; + margin-inline-start: 1rem; + padding-inline-start: 0.5rem; +} + +.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + display: grid; + grid-auto-flow: column; + align-content: flex-start; + align-items: center; + gap: 0.5rem; + grid-auto-columns: minmax(auto, max-content) auto max-content; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.menu li.disabled { + cursor: not-allowed; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + color: var(--fallback-bc,oklch(var(--bc)/0.3)); +} + +.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)) { + display: none; +} + +:where(.menu li) { + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + flex-wrap: wrap; + align-items: stretch; +} + +:where(.menu li) .badge { + justify-self: end; +} + +.modal { + pointer-events: none; + position: fixed; + inset: 0px; + margin: 0px; + display: grid; + height: 100%; + max-height: none; + width: 100%; + max-width: none; + justify-items: center; + padding: 0px; + opacity: 0; + overscroll-behavior: contain; + z-index: 999; + background-color: transparent; + color: inherit; + transition-duration: 200ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-property: transform, opacity, visibility; + overflow-y: hidden; +} + +:where(.modal) { + align-items: center; +} + +.modal-box { + max-height: calc(100vh - 5em); + grid-column-start: 1; + grid-row-start: 1; + width: 91.666667%; + max-width: 32rem; + --tw-scale-x: .9; + --tw-scale-y: .9; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + padding: 1.5rem; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + box-shadow: rgba(0, 0, 0, 0.25) 0px 25px 50px -12px; + overflow-y: auto; + overscroll-behavior: contain; +} + +.modal-open, +.modal:target, +.modal-toggle:checked + .modal, +.modal[open] { + pointer-events: auto; + visibility: visible; + opacity: 1; +} + +.modal-action { + display: flex; + margin-top: 1.5rem; + justify-content: flex-end; +} + +:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) { + overflow: hidden; + scrollbar-gutter: stable; +} + +.navbar { + display: flex; + align-items: center; + padding: var(--navbar-padding, 0.5rem); + min-height: 4rem; + width: 100%; +} + +:where(.navbar > *:not(script, style)) { + display: inline-flex; + align-items: center; +} + +.navbar-start { + width: 50%; + justify-content: flex-start; +} + +.navbar-center { + flex-shrink: 0; +} + +.navbar-end { + width: 50%; + justify-content: flex-end; +} + +.radio { + flex-shrink: 0; + --chkbg: var(--bc); + height: 1.5rem; + width: 1.5rem; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 9999px; + border-width: 1px; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; +} + +.range { + height: 1.5rem; + width: 100%; + cursor: pointer; + -moz-appearance: none; + appearance: none; + -webkit-appearance: none; + --range-shdw: var(--fallback-bc,oklch(var(--bc)/1)); + overflow: hidden; + border-radius: var(--rounded-box, 1rem); + background-color: transparent; +} + +.range:focus { + outline: none; +} + +.select { + display: inline-flex; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 3rem; + min-height: 3rem; + padding-inline-start: 1rem; + padding-inline-end: 2.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + border-radius: var(--rounded-btn, 0.5rem); + border-width: 1px; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), + linear-gradient(135deg, currentColor 50%, transparent 50%); + background-position: calc(100% - 20px) calc(1px + 50%), + calc(100% - 16.1px) calc(1px + 50%); + background-size: 4px 4px, + 4px 4px; + background-repeat: no-repeat; +} + +.select[multiple] { + height: auto; +} + +.stats { + display: inline-grid; + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + +:where(.stats) { + grid-auto-flow: column; + overflow-x: auto; +} + +.stat { + display: inline-grid; + width: 100%; + grid-template-columns: repeat(1, 1fr); + -moz-column-gap: 1rem; + column-gap: 1rem; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.1; + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-top: 1rem; + padding-bottom: 1rem; +} + +.stat-title { + grid-column-start: 1; + white-space: nowrap; + color: var(--fallback-bc,oklch(var(--bc)/0.6)); +} + +.stat-value { + grid-column-start: 1; + white-space: nowrap; + font-size: 2.25rem; + line-height: 2.5rem; + font-weight: 800; +} + +.swap { + position: relative; + display: inline-grid; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + place-content: center; + cursor: pointer; +} + +.swap > * { + grid-column-start: 1; + grid-row-start: 1; + transition-duration: 300ms; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-property: transform, opacity; +} + +.swap input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.swap .swap-on, +.swap .swap-indeterminate, +.swap input:indeterminate ~ .swap-on { + opacity: 0; +} + +.swap input:checked ~ .swap-off, +.swap-active .swap-off, +.swap input:indeterminate ~ .swap-off { + opacity: 0; +} + +.swap input:checked ~ .swap-on, +.swap-active .swap-on, +.swap input:indeterminate ~ .swap-indeterminate { + opacity: 1; +} + +.tabs { + display: grid; + align-items: flex-end; +} + +.tabs-lifted:has(.tab-content[class^="rounded-"]) + .tab:first-child:not(:is(.tab-active, [aria-selected="true"])), .tabs-lifted:has(.tab-content[class*=" rounded-"]) + .tab:first-child:not(:is(.tab-active, [aria-selected="true"])) { + border-bottom-color: transparent; +} + +.tab { + position: relative; + grid-row-start: 1; + display: inline-flex; + height: 2rem; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + flex-wrap: wrap; + align-items: center; + justify-content: center; + text-align: center; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; + --tw-text-opacity: 0.5; + --tab-color: var(--fallback-bc,oklch(var(--bc)/1)); + --tab-bg: var(--fallback-b1,oklch(var(--b1)/1)); + --tab-border-color: var(--fallback-b3,oklch(var(--b3)/1)); + color: var(--tab-color); + padding-inline-start: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); +} + +.tab:is(input[type="radio"]) { + width: auto; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + +.tab:is(input[type="radio"]):after { + --tw-content: attr(aria-label); + content: var(--tw-content); +} + +.tab:not(input):empty { + cursor: default; + grid-column-start: span 9999; +} + +.tab-content { + grid-column-start: 1; + grid-column-end: span 9999; + grid-row-start: 2; + margin-top: calc(var(--tab-border) * -1); + display: none; + border-color: transparent; + border-width: var(--tab-border, 0); +} + +:checked + .tab-content:nth-child(2), + :is(.tab-active, [aria-selected="true"]) + .tab-content:nth-child(2) { + border-start-start-radius: 0px; +} + +input.tab:checked + .tab-content, +:is(.tab-active, [aria-selected="true"]) + .tab-content { + display: block; +} + +.table { + position: relative; + width: 100%; + border-radius: var(--rounded-box, 1rem); + text-align: left; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.table :where(.table-pin-rows thead tr) { + position: sticky; + top: 0px; + z-index: 1; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.table :where(.table-pin-rows tfoot tr) { + position: sticky; + bottom: 0px; + z-index: 1; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.table :where(.table-pin-cols tr th) { + position: sticky; + left: 0px; + right: 0px; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.toast { + position: fixed; + display: flex; + min-width: -moz-fit-content; + min-width: fit-content; + flex-direction: column; + white-space: nowrap; + gap: 0.5rem; + padding: 1rem; +} + +.avatar-group :where(.avatar) { + overflow: hidden; + border-radius: 9999px; + border-width: 4px; + --tw-border-opacity: 1; + border-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-border-opacity))); +} + +.badge-neutral { + --tw-border-opacity: 1; + border-color: var(--fallback-n,oklch(var(--n)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +.badge-primary { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +.badge-accent { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); +} + +.badge-outline.badge-neutral { + --tw-text-opacity: 1; + color: var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity))); +} + +.badge-outline.badge-primary { + --tw-text-opacity: 1; + color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity))); +} + +.badge-outline.badge-accent { + --tw-text-opacity: 1; + color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity))); +} + +.btm-nav > * .label { + font-size: 1rem; + line-height: 1.5rem; +} + +@media (prefers-reduced-motion: no-preference) { + .btn { + animation: button-pop var(--animation-btn, 0.25s) ease-out; + } +} + +.btn:active:hover, + .btn:active:focus { + animation: button-pop 0s ease-out; + transform: scale(var(--btn-focus-scale, 0.97)); +} + +@supports not (color: oklch(0% 0 0)) { + .btn { + background-color: var(--btn-color, var(--fallback-b2)); + border-color: var(--btn-color, var(--fallback-b2)); + } + + .btn-primary { + --btn-color: var(--fallback-p); + } + + .btn-accent { + --btn-color: var(--fallback-a); + } + + .btn-neutral { + --btn-color: var(--fallback-n); + } +} + +@supports (color: color-mix(in oklab, black, black)) { + .btn-active { + background-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b3)) / var(--tw-bg-opacity, 1)) 90%, + black + ); + border-color: color-mix( + in oklab, + oklch(var(--btn-color, var(--b3)) / var(--tw-border-opacity, 1)) 90%, + black + ); + } + + .btn-outline.btn-primary.btn-active { + background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); + } + + .btn-outline.btn-secondary.btn-active { + background-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-s,oklch(var(--s)/1)) 90%, black); + } + + .btn-outline.btn-accent.btn-active { + background-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-a,oklch(var(--a)/1)) 90%, black); + } + + .btn-outline.btn-success.btn-active { + background-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-su,oklch(var(--su)/1)) 90%, black); + } + + .btn-outline.btn-info.btn-active { + background-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-in,oklch(var(--in)/1)) 90%, black); + } + + .btn-outline.btn-warning.btn-active { + background-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-wa,oklch(var(--wa)/1)) 90%, black); + } + + .btn-outline.btn-error.btn-active { + background-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black); + border-color: color-mix(in oklab, var(--fallback-er,oklch(var(--er)/1)) 90%, black); + } +} + +.btn:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; +} + +.btn-primary { + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +@supports (color: oklch(0% 0 0)) { + .btn-primary { + --btn-color: var(--p); + } + + .btn-accent { + --btn-color: var(--a); + } + + .btn-neutral { + --btn-color: var(--n); + } +} + +.btn-accent { + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); + outline-color: var(--fallback-a,oklch(var(--a)/1)); +} + +.btn-neutral { + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); + outline-color: var(--fallback-n,oklch(var(--n)/1)); +} + +.btn.glass { + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn.glass.btn-active { + --glass-opacity: 25%; + --glass-border-opacity: 15%; +} + +.btn-ghost { + border-width: 1px; + border-color: transparent; + background-color: transparent; + color: currentColor; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + outline-color: currentColor; +} + +.btn-ghost.btn-active { + border-color: transparent; + background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.btn-link.btn-active { + border-color: transparent; + background-color: transparent; + text-decoration-line: underline; +} + +.btn-outline.btn-active { + --tw-border-opacity: 1; + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity))); +} + +.btn-outline.btn-primary { + --tw-text-opacity: 1; + color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity))); +} + +.btn-outline.btn-primary.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-secondary.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-accent { + --tw-text-opacity: 1; + color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity))); +} + +.btn-outline.btn-accent.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); +} + +.btn-outline.btn-success.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-info.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity))); +} + +.btn-outline.btn-warning.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity))); +} + +.btn-outline.btn-error.btn-active { + --tw-text-opacity: 1; + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); +} + +.btn.btn-disabled, + .btn[disabled], + .btn:disabled { + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.2; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.btn:is(input[type="checkbox"]:checked), +.btn:is(input[type="radio"]:checked) { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible { + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +@keyframes button-pop { + 0% { + transform: scale(var(--btn-focus-scale, 0.98)); + } + + 40% { + transform: scale(1.02); + } + + 100% { + transform: scale(1); + } +} + +.card :where(figure:first-child) { + overflow: hidden; + border-start-start-radius: inherit; + border-start-end-radius: inherit; + border-end-start-radius: unset; + border-end-end-radius: unset; +} + +.card :where(figure:last-child) { + overflow: hidden; + border-start-start-radius: unset; + border-start-end-radius: unset; + border-end-start-radius: inherit; + border-end-end-radius: inherit; +} + +.card:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +.card.bordered { + border-width: 1px; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); +} + +.card.compact .card-body { + padding: 1rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.card-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.25rem; + line-height: 1.75rem; + font-weight: 600; +} + +.card.image-full :where(figure) { + overflow: hidden; + border-radius: inherit; +} + +.checkbox:focus { + box-shadow: none; +} + +.checkbox:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.checkbox:disabled { + border-width: 0px; + cursor: not-allowed; + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + opacity: 0.2; +} + +.checkbox:checked, + .checkbox[aria-checked="true"] { + background-repeat: no-repeat; + animation: checkmark var(--animation-input, 0.2s) ease-out; + background-color: var(--chkbg); + background-image: linear-gradient(-45deg, transparent 65%, var(--chkbg) 65.99%), + linear-gradient(45deg, transparent 75%, var(--chkbg) 75.99%), + linear-gradient(-45deg, var(--chkbg) 40%, transparent 40.99%), + linear-gradient( + 45deg, + var(--chkbg) 30%, + var(--chkfg) 30.99%, + var(--chkfg) 40%, + transparent 40.99% + ), + linear-gradient(-45deg, var(--chkfg) 50%, var(--chkbg) 50.99%); +} + +.checkbox:indeterminate { + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + background-repeat: no-repeat; + animation: checkmark var(--animation-input, 0.2s) ease-out; + background-image: linear-gradient(90deg, transparent 80%, var(--chkbg) 80%), + linear-gradient(-90deg, transparent 80%, var(--chkbg) 80%), + linear-gradient(0deg, var(--chkbg) 43%, var(--chkfg) 43%, var(--chkfg) 57%, var(--chkbg) 57%); +} + +.checkbox-primary { + --chkbg: var(--fallback-p,oklch(var(--p)/1)); + --chkfg: var(--fallback-pc,oklch(var(--pc)/1)); + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); +} + +.checkbox-primary:focus-visible { + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +.checkbox-primary:checked, + .checkbox-primary[aria-checked="true"] { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); +} + +.checkbox-accent { + --chkbg: var(--fallback-a,oklch(var(--a)/1)); + --chkfg: var(--fallback-ac,oklch(var(--ac)/1)); + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); +} + +.checkbox-accent:focus-visible { + outline-color: var(--fallback-a,oklch(var(--a)/1)); +} + +.checkbox-accent:checked, + .checkbox-accent[aria-checked="true"] { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); +} + +@keyframes checkmark { + 0% { + background-position-y: 5px; + } + + 50% { + background-position-y: -2px; + } + + 100% { + background-position-y: 0; + } +} + +.divider:not(:empty) { + gap: 1rem; +} + +.divider-secondary:before, + .divider-secondary:after { + --tw-bg-opacity: 1; + background-color: var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity))); +} + +.dropdown.dropdown-open .dropdown-content, +.dropdown:focus .dropdown-content, +.dropdown:focus-within .dropdown-content { + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.label-text { + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + +.input input { + --tw-bg-opacity: 1; + background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); + background-color: transparent; +} + +.input input:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.input[list]::-webkit-calendar-picker-indicator { + line-height: 1em; +} + +.input-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.input:focus, + .input:focus-within { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.input-accent { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); +} + +.input-accent:focus, + .input-accent:focus-within { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); + outline-color: var(--fallback-a,oklch(var(--a)/1)); +} + +.input:has(> input[disabled]), + .input-disabled, + .input:disabled, + .input[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + color: var(--fallback-bc,oklch(var(--bc)/0.4)); +} + +.input:has(> input[disabled])::-moz-placeholder, .input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.input:has(> input[disabled])::placeholder, + .input-disabled::placeholder, + .input:disabled::placeholder, + .input[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.input:has(> input[disabled]) > input[disabled] { + cursor: not-allowed; +} + +.input::-webkit-date-and-time-value { + text-align: inherit; +} + +.join > :where(*:not(:first-child)) { + margin-top: 0px; + margin-bottom: 0px; + margin-inline-start: -1px; +} + +.join > :where(*:not(:first-child)):is(.btn) { + margin-inline-start: calc(var(--border-btn) * -1); +} + +.join-item:focus { + isolation: isolate; +} + +.link:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.link:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} + +.loading { + pointer-events: none; + display: inline-block; + aspect-ratio: 1 / 1; + width: 1.5rem; + background-color: currentColor; + -webkit-mask-size: 100%; + mask-size: 100%; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +} + +.loading-spinner { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +} + +.loading-lg { + width: 2.5rem; +} + +.mask-circle { + -webkit-mask-image: url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e"); + mask-image: url("data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e"); +} + +:where(.menu li:empty) { + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + opacity: 0.1; + margin: 0.5rem 1rem; + height: 1px; +} + +.menu :where(li ul):before { + position: absolute; + bottom: 0.75rem; + inset-inline-start: 0px; + top: 0.75rem; + width: 1px; + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + opacity: 0.1; + content: ""; +} + +.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), +.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + border-radius: var(--rounded-btn, 0.5rem); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + text-align: start; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: 200ms; + text-wrap: balance; +} + +:where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):is(summary):not(.active, .btn):focus-visible, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):is(summary):not(.active, .btn):focus-visible { + cursor: pointer; + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + outline: 2px solid transparent; + outline-offset: 2px; +} + +.menu li > *:not(ul, .menu-title, details, .btn):active, +.menu li > *:not(ul, .menu-title, details, .btn).active, +.menu li > details > summary:active { + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +.menu :where(li > details > summary)::-webkit-details-marker { + display: none; +} + +.menu :where(li > details > summary):after, +.menu :where(li > .menu-dropdown-toggle):after { + justify-self: end; + display: block; + margin-top: -0.5rem; + height: 0.5rem; + width: 0.5rem; + transform: rotate(45deg); + transition-property: transform, margin-top; + transition-duration: 0.3s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + content: ""; + transform-origin: 75% 75%; + box-shadow: 2px 2px; + pointer-events: none; +} + +.menu :where(li > details[open] > summary):after, +.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after { + transform: rotate(225deg); + margin-top: 0; +} + +.menu-title { + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 700; + color: var(--fallback-bc,oklch(var(--bc)/0.4)); +} + +.mockup-browser .mockup-browser-toolbar .input { + position: relative; + margin-left: auto; + margin-right: auto; + display: block; + height: 1.75rem; + width: 24rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + padding-left: 2rem; + direction: ltr; +} + +.mockup-browser .mockup-browser-toolbar .input:before { + content: ""; + position: absolute; + left: 0.5rem; + top: 50%; + aspect-ratio: 1 / 1; + height: 0.75rem; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 2px; + border-color: currentColor; + opacity: 0.6; +} + +.mockup-browser .mockup-browser-toolbar .input:after { + content: ""; + position: absolute; + left: 1.25rem; + top: 50%; + height: 0.5rem; + --tw-translate-y: 25%; + --tw-rotate: -45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-radius: 9999px; + border-width: 1px; + border-color: currentColor; + opacity: 0.6; +} + +.modal:not(dialog:not(.modal-open)), + .modal::backdrop { + background-color: #0006; + animation: modal-pop 0.2s ease-out; +} + +.modal-backdrop { + z-index: -1; + grid-column-start: 1; + grid-row-start: 1; + display: grid; + align-self: stretch; + justify-self: stretch; + color: transparent; +} + +.modal-open .modal-box, +.modal-toggle:checked + .modal .modal-box, +.modal:target .modal-box, +.modal[open] .modal-box { + --tw-translate-y: 0px; + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.modal-action > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +@keyframes modal-pop { + 0% { + opacity: 0; + } +} + +@keyframes progress-loading { + 50% { + background-position-x: -115%; + } +} + +.radio:focus { + box-shadow: none; +} + +.radio:focus-visible { + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/1)); +} + +.radio:checked, + .radio[aria-checked="true"] { + --tw-bg-opacity: 1; + background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + background-image: none; + animation: radiomark var(--animation-input, 0.2s) ease-out; + box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; +} + +.radio:disabled { + cursor: not-allowed; + opacity: 0.2; +} + +@keyframes radiomark { + 0% { + box-shadow: 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset; + } + + 50% { + box-shadow: 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset; + } + + 100% { + box-shadow: 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset, + 0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset; + } +} + +.range:focus-visible::-webkit-slider-thumb { + --focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset; +} + +.range:focus-visible::-moz-range-thumb { + --focus-shadow: 0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset, 0 0 0 2rem var(--range-shdw) inset; +} + +.range::-webkit-slider-runnable-track { + height: 0.5rem; + width: 100%; + border-radius: var(--rounded-box, 1rem); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); +} + +.range::-moz-range-track { + height: 0.5rem; + width: 100%; + border-radius: var(--rounded-box, 1rem); + background-color: var(--fallback-bc,oklch(var(--bc)/0.1)); +} + +.range::-webkit-slider-thumb { + position: relative; + height: 1.5rem; + width: 1.5rem; + border-radius: var(--rounded-box, 1rem); + border-style: none; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + appearance: none; + -webkit-appearance: none; + top: 50%; + color: var(--range-shdw); + transform: translateY(-50%); + --filler-size: 100rem; + --filler-offset: 0.6rem; + box-shadow: 0 0 0 3px var(--range-shdw) inset, + var(--focus-shadow, 0 0), + calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size); +} + +.range::-moz-range-thumb { + position: relative; + height: 1.5rem; + width: 1.5rem; + border-radius: var(--rounded-box, 1rem); + border-style: none; + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + top: 50%; + color: var(--range-shdw); + --filler-size: 100rem; + --filler-offset: 0.5rem; + box-shadow: 0 0 0 3px var(--range-shdw) inset, + var(--focus-shadow, 0 0), + calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size); +} + +.range-primary { + --range-shdw: var(--fallback-p,oklch(var(--p)/1)); +} + +@keyframes rating-pop { + 0% { + transform: translateY(-0.125em); + } + + 40% { + transform: translateY(-0.125em); + } + + 100% { + transform: translateY(0); + } +} + +.select:focus { + box-shadow: none; + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); + outline-style: solid; + outline-width: 2px; + outline-offset: 2px; + outline-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.select-accent { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); +} + +.select-accent:focus { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); + outline-color: var(--fallback-a,oklch(var(--a)/1)); +} + +.select-disabled, + .select:disabled, + .select[disabled] { + cursor: not-allowed; + --tw-border-opacity: 1; + border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); + color: var(--fallback-bc,oklch(var(--bc)/0.4)); +} + +.select-disabled::-moz-placeholder, .select:disabled::-moz-placeholder, .select[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.select-disabled::placeholder, + .select:disabled::placeholder, + .select[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.select-multiple, + .select[multiple], + .select[size].select:not([size="1"]) { + background-image: none; + padding-right: 1rem; +} + +[dir="rtl"] .select { + background-position: calc(0% + 12px) calc(1px + 50%), + calc(0% + 16px) calc(1px + 50%); +} + +@keyframes skeleton { + from { + background-position: 150%; + } + + to { + background-position: -50%; + } +} + +:where(.stats) > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(1px * var(--tw-divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); + --tw-divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(0px * var(--tw-divide-y-reverse)); +} + +:is([dir="rtl"] .stats > :not([hidden]) ~ :not([hidden])) { + --tw-divide-x-reverse: 1; +} + +.tabs-lifted > .tab:focus-visible { + border-end-end-radius: 0; + border-end-start-radius: 0; +} + +.tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tab:is(input:checked) { + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 1; + --tw-text-opacity: 1; +} + +.tab:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.tab:focus-visible { + outline: 2px solid currentColor; + outline-offset: -5px; +} + +.tab-disabled, + .tab[disabled] { + cursor: not-allowed; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + +.tabs-bordered > .tab { + border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); + --tw-border-opacity: 0.2; + border-style: solid; + border-bottom-width: calc(var(--tab-border, 1px) + 1px); +} + +.tabs-lifted > .tab { + border: var(--tab-border, 1px) solid transparent; + border-width: 0 0 var(--tab-border, 1px) 0; + border-start-start-radius: var(--tab-radius, 0.5rem); + border-start-end-radius: var(--tab-radius, 0.5rem); + border-bottom-color: var(--tab-border-color); + padding-inline-start: var(--tab-padding, 1rem); + padding-inline-end: var(--tab-padding, 1rem); + padding-top: var(--tab-border, 1px); +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tabs-lifted > .tab:is(input:checked) { + background-color: var(--tab-bg); + border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px); + border-inline-start-color: var(--tab-border-color); + border-inline-end-color: var(--tab-border-color); + border-top-color: var(--tab-border-color); + padding-inline-start: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-inline-end: calc(var(--tab-padding, 1rem) - var(--tab-border, 1px)); + padding-bottom: var(--tab-border, 1px); + padding-top: 0; +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked):before { + z-index: 1; + content: ""; + display: block; + position: absolute; + width: calc(100% + var(--tab-radius, 0.5rem) * 2); + height: var(--tab-radius, 0.5rem); + bottom: 0; + background-size: var(--tab-radius, 0.5rem); + background-position: top left, + top right; + background-repeat: no-repeat; + --tab-grad: calc(69% - var(--tab-border, 1px)); + --radius-start: radial-gradient( + circle at top left, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + --radius-end: radial-gradient( + circle at top right, + transparent var(--tab-grad), + var(--tab-border-color) calc(var(--tab-grad) + 0.25px), + var(--tab-border-color) calc(var(--tab-grad) + var(--tab-border, 1px)), + var(--tab-bg) calc(var(--tab-grad) + var(--tab-border, 1px) + 0.25px) + ); + background-image: var(--radius-start), var(--radius-end); +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted > .tab:is(input:checked):first-child:before { + background-image: var(--radius-end); + background-position: top right; +} + +[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):first-child:before { + background-image: var(--radius-start); + background-position: top left; +} + +.tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted > .tab:is(input:checked):last-child:before { + background-image: var(--radius-start); + background-position: top left; +} + +[dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):last-child:before { + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-lifted + > :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]) + + .tabs-lifted + :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before { + background-image: var(--radius-end); + background-position: top right; +} + +.tabs-boxed .tab { + border-radius: var(--rounded-btn, 0.5rem); +} + +.table:where([dir="rtl"], [dir="rtl"] *) { + text-align: right; +} + +.table :where(th, td) { + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + vertical-align: middle; +} + +.table tr.active, + .table tr.active:nth-child(even), + .table-zebra tbody tr:nth-child(even) { + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); +} + +.table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) { + border-bottom-width: 1px; + --tw-border-opacity: 1; + border-bottom-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); +} + +.table :where(thead, tfoot) { + white-space: nowrap; + font-size: 0.75rem; + line-height: 1rem; + font-weight: 700; + color: var(--fallback-bc,oklch(var(--bc)/0.6)); +} + +.table :where(tfoot) { + border-top-width: 1px; + --tw-border-opacity: 1; + border-top-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); +} + +.toast > * { + animation: toast-pop 0.25s ease-out; +} + +@keyframes toast-pop { + 0% { + transform: scale(0.9); + opacity: 0; + } + + 100% { + transform: scale(1); + opacity: 1; + } +} + +.glass, + .glass.btn-active { + border: none; + -webkit-backdrop-filter: blur(var(--glass-blur, 40px)); + backdrop-filter: blur(var(--glass-blur, 40px)); + background-color: transparent; + background-image: linear-gradient( + 135deg, + rgb(255 255 255 / var(--glass-opacity, 30%)) 0%, + rgb(0 0 0 / 0%) 100% + ), + linear-gradient( + var(--glass-reflex-degree, 100deg), + rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%, + rgb(0 0 0 / 0%) 25% + ); + box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset, + 0 0 0 2px rgb(0 0 0 / 5%); + text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%)); +} + +@media (hover: hover) { + .glass.btn-active { + border: none; + -webkit-backdrop-filter: blur(var(--glass-blur, 40px)); + backdrop-filter: blur(var(--glass-blur, 40px)); + background-color: transparent; + background-image: linear-gradient( + 135deg, + rgb(255 255 255 / var(--glass-opacity, 30%)) 0%, + rgb(0 0 0 / 0%) 100% + ), + linear-gradient( + var(--glass-reflex-degree, 100deg), + rgb(255 255 255 / var(--glass-reflex-opacity, 10%)) 25%, + rgb(0 0 0 / 0%) 25% + ); + box-shadow: 0 0 0 1px rgb(255 255 255 / var(--glass-border-opacity, 10%)) inset, + 0 0 0 2px rgb(0 0 0 / 5%); + text-shadow: 0 1px rgb(0 0 0 / var(--glass-text-shadow-opacity, 5%)); + } +} + +.btn-sm { + height: 2rem; + min-height: 2rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + font-size: 0.875rem; +} + +.btn-block { + width: 100%; +} + +.btn-square:where(.btn-sm) { + height: 2rem; + width: 2rem; + padding: 0px; +} + +.btn-circle:where(.btn-xs) { + height: 1.5rem; + width: 1.5rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-circle:where(.btn-sm) { + height: 2rem; + width: 2rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-circle:where(.btn-md) { + height: 3rem; + width: 3rem; + border-radius: 9999px; + padding: 0px; +} + +.btn-circle:where(.btn-lg) { + height: 4rem; + width: 4rem; + border-radius: 9999px; + padding: 0px; +} + +[type="checkbox"].checkbox-sm { + height: 1.25rem; + width: 1.25rem; +} + +.indicator :where(.indicator-item) { + bottom: auto; + inset-inline-end: 0px; + inset-inline-start: auto; + top: 0px; + --tw-translate-y: -50%; + --tw-translate-x: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item):where([dir="rtl"], [dir="rtl"] *) { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-start) { + inset-inline-end: auto; + inset-inline-start: 0px; + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-start):where([dir="rtl"], [dir="rtl"] *) { + --tw-translate-x: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-center) { + inset-inline-end: 50%; + inset-inline-start: 50%; + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-center):where([dir="rtl"], [dir="rtl"] *) { + --tw-translate-x: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-end) { + inset-inline-end: 0px; + inset-inline-start: auto; + --tw-translate-x: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-end):where([dir="rtl"], [dir="rtl"] *) { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-bottom) { + bottom: 0px; + top: auto; + --tw-translate-y: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-middle) { + bottom: 50%; + top: 50%; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.indicator :where(.indicator-item.indicator-top) { + bottom: auto; + top: 0px; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.input-md { + height: 3rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; +} + +.join.join-vertical { + flex-direction: column; +} + +.join.join-vertical .join-item:first-child:not(:last-child), + .join.join-vertical *:first-child:not(:last-child) .join-item { + border-end-start-radius: 0; + border-end-end-radius: 0; + border-start-start-radius: inherit; + border-start-end-radius: inherit; +} + +.join.join-vertical .join-item:last-child:not(:first-child), + .join.join-vertical *:last-child:not(:first-child) .join-item { + border-start-start-radius: 0; + border-start-end-radius: 0; + border-end-start-radius: inherit; + border-end-end-radius: inherit; +} + +.join.join-horizontal { + flex-direction: row; +} + +.join.join-horizontal .join-item:first-child:not(:last-child), + .join.join-horizontal *:first-child:not(:last-child) .join-item { + border-end-end-radius: 0; + border-start-end-radius: 0; + border-end-start-radius: inherit; + border-start-start-radius: inherit; +} + +.join.join-horizontal .join-item:last-child:not(:first-child), + .join.join-horizontal *:last-child:not(:first-child) .join-item { + border-end-start-radius: 0; + border-start-start-radius: 0; + border-end-end-radius: inherit; + border-start-end-radius: inherit; +} + +.menu-horizontal { + display: inline-flex; + flex-direction: row; +} + +.menu-horizontal > li:not(.menu-title) > details > ul { + position: absolute; +} + +.menu-vertical { + display: flex; + flex-direction: column; +} + +.menu-vertical > li:not(.menu-title) > details > ul { + position: relative; +} + +.range-xs { + height: 1rem; +} + +.range-xs::-webkit-slider-runnable-track { + height: 0.25rem; +} + +.range-xs::-moz-range-track { + height: 0.25rem; +} + +.range-xs::-webkit-slider-thumb { + height: 1rem; + width: 1rem; + --filler-offset: 0.4rem; +} + +.range-xs::-moz-range-thumb { + height: 1rem; + width: 1rem; + --filler-offset: 0.4rem; +} + +.select-xs { + height: 1.5rem; + min-height: 1.5rem; + padding-left: 0.5rem; + padding-right: 2rem; + font-size: 0.75rem; + line-height: 1rem; + line-height: 1.625; +} + +[dir="rtl"] .select-xs { + padding-left: 2rem; + padding-right: 0.5rem; +} + +.stats-horizontal { + grid-auto-flow: column; +} + +.tabs-md :where(.tab) { + height: 2rem; + font-size: 0.875rem; + line-height: 1.25rem; + line-height: 2; + --tab-padding: 1rem; +} + +.tabs-lg :where(.tab) { + height: 3rem; + font-size: 1.125rem; + line-height: 1.75rem; + line-height: 2; + --tab-padding: 1.25rem; +} + +.tabs-sm :where(.tab) { + height: 1.5rem; + font-size: 0.875rem; + line-height: .75rem; + --tab-padding: 0.75rem; +} + +.tabs-xs :where(.tab) { + height: 1.25rem; + font-size: 0.75rem; + line-height: .75rem; + --tab-padding: 0.5rem; +} + +:where(.toast) { + bottom: 0px; + inset-inline-end: 0px; + inset-inline-start: auto; + top: auto; + --tw-translate-x: 0px; + --tw-translate-y: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-start) { + inset-inline-end: auto; + inset-inline-start: 0px; + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-center) { + inset-inline-end: 50%; + inset-inline-start: 50%; + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-center):where([dir="rtl"], [dir="rtl"] *) { + --tw-translate-x: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-end) { + inset-inline-end: 0px; + inset-inline-start: auto; + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-bottom) { + bottom: 0px; + top: auto; + --tw-translate-y: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-middle) { + bottom: auto; + top: 50%; + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.toast:where(.toast-top) { + bottom: auto; + top: 0px; + --tw-translate-y: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.avatar.online:before { + content: ""; + position: absolute; + z-index: 10; + display: block; + border-radius: 9999px; + --tw-bg-opacity: 1; + background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity))); + outline-style: solid; + outline-width: 2px; + outline-color: var(--fallback-b1,oklch(var(--b1)/1)); + width: 15%; + height: 15%; + top: 7%; + right: 7%; +} + +.avatar.offline:before { + content: ""; + position: absolute; + z-index: 10; + display: block; + border-radius: 9999px; + --tw-bg-opacity: 1; + background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); + outline-style: solid; + outline-width: 2px; + outline-color: var(--fallback-b1,oklch(var(--b1)/1)); + width: 15%; + height: 15%; + top: 7%; + right: 7%; +} + +.card-compact .card-body { + padding: 1rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.card-compact .card-title { + margin-bottom: 0.25rem; +} + +.card-normal .card-body { + padding: var(--padding-card, 2rem); + font-size: 1rem; + line-height: 1.5rem; +} + +.card-normal .card-title { + margin-bottom: 0.75rem; +} + +.join.join-vertical > :where(*:not(:first-child)) { + margin-left: 0px; + margin-right: 0px; + margin-top: -1px; +} + +.join.join-vertical > :where(*:not(:first-child)):is(.btn) { + margin-top: calc(var(--border-btn) * -1); +} + +.join.join-horizontal > :where(*:not(:first-child)) { + margin-top: 0px; + margin-bottom: 0px; + margin-inline-start: -1px; +} + +.join.join-horizontal > :where(*:not(:first-child)):is(.btn) { + margin-inline-start: calc(var(--border-btn) * -1); +} + +.menu-horizontal > li:not(.menu-title) > details > ul { + margin-inline-start: 0px; + margin-top: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-inline-end: 0.5rem; +} + +.menu-horizontal > li > details > ul:before { + content: none; +} + +:where(.menu-horizontal > li:not(.menu-title) > details > ul) { + border-radius: var(--rounded-box, 1rem); + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.menu-vertical > li:not(.menu-title) > details > ul { + margin-inline-start: 1rem; + margin-top: 0px; + padding-top: 0px; + padding-bottom: 0px; + padding-inline-end: 0px; +} + +.menu-xs .menu-title { + padding-left: 0.5rem; + padding-right: 0.5rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.menu-sm :where(li:not(.menu-title) > *:not(ul, details, .menu-title)), .menu-sm :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + border-radius: var(--rounded-btn, 0.5rem); + padding-left: 0.75rem; + padding-right: 0.75rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.menu-sm .menu-title { + padding-left: 0.75rem; + padding-right: 0.75rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.menu-md :where(li:not(.menu-title) > *:not(ul, details, .menu-title)), .menu-md :where(li:not(.menu-title) > details > summary:not(.menu-title)) { + border-radius: var(--rounded-btn, 0.5rem); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; +} + +.menu-md .menu-title { + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.menu-lg .menu-title { + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.modal-top :where(.modal-box) { + width: 100%; + max-width: none; + --tw-translate-y: -2.5rem; + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} + +.modal-middle :where(.modal-box) { + width: 91.666667%; + max-width: 32rem; + --tw-translate-y: 0px; + --tw-scale-x: .9; + --tw-scale-y: .9; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + border-bottom-right-radius: var(--rounded-box, 1rem); + border-bottom-left-radius: var(--rounded-box, 1rem); +} + +.modal-bottom :where(.modal-box) { + width: 100%; + max-width: none; + --tw-translate-y: 2.5rem; + --tw-scale-x: 1; + --tw-scale-y: 1; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + border-top-left-radius: var(--rounded-box, 1rem); + border-top-right-radius: var(--rounded-box, 1rem); + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + +.stats-horizontal > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(1px * var(--tw-divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); + --tw-divide-y-reverse: 0; + border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(0px * var(--tw-divide-y-reverse)); +} + +.stats-horizontal { + overflow-x: auto; +} + +.stats-horizontal:where([dir="rtl"], [dir="rtl"] *) { + --tw-divide-x-reverse: 1; +} + +.alert.info { + --tw-border-opacity: 1; + border-color: var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity))); + background-color: color-mix(in srgb, oklch(var(--in)), oklch(var(--b1)) 85%); +} + +.alert.success { + --tw-border-opacity: 1; + border-color: var(--fallback-su,oklch(var(--su)/var(--tw-border-opacity))); + background-color: color-mix(in srgb, oklch(var(--su)), oklch(var(--b1)) 85%); +} + +.alert.warning { + --tw-border-opacity: 1; + border-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-border-opacity))); + background-color: color-mix(in srgb, oklch(var(--wa)), oklch(var(--b1)) 85%); +} + +.alert.error { + --tw-border-opacity: 1; + border-color: var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity))); + background-color: color-mix(in srgb, oklch(var(--er)), oklch(var(--b1)) 85%); +} + +/* + * Example usage: + *
+ * + *
+ * + *
+ *
+ */ + +.loading-box { + display: grid; + place-items: center; +} + +/* Stacks direct children on top of each other */ + +.loading-box > * { + grid-column-start: 1; + grid-row-start: 1; +} + +/* Adds overlay */ + +.loading-box:has(> .htmx-indicator.htmx-request) > *:not(.htmx-indicator) { + opacity: 0.3; +} + +/* Disables mouse interactions */ + +.loading-box:has(> .htmx-indicator.htmx-request) * { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.visible { + visibility: visible; +} + +.static { + position: static; +} + +.z-\[1\] { + z-index: 1; +} + +.m-4 { + margin: 1rem; +} + +.my-0 { + margin-top: 0px; + margin-bottom: 0px; +} + +.my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +.-ml-2 { + margin-left: -0.5rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.mt-0\.5 { + margin-top: 0.125rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.table { + display: table; +} + +.hidden { + display: none; +} + +.size-10 { + width: 2.5rem; + height: 2.5rem; +} + +.size-5 { + width: 1.25rem; + height: 1.25rem; +} + +.h-14 { + height: 3.5rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-auto { + height: auto; +} + +.max-h-16 { + max-height: 4rem; +} + +.max-h-64 { + max-height: 16rem; +} + +.max-h-80 { + max-height: 20rem; +} + +.max-h-svh { + max-height: 100svh; +} + +.min-h-8 { + min-height: 2rem; +} + +.min-h-fit { + min-height: -moz-fit-content; + min-height: fit-content; +} + +.w-1\/2 { + width: 50%; +} + +.w-1\/3 { + width: 33.333333%; +} + +.w-2\/3 { + width: 66.666667%; +} + +.w-24 { + width: 6rem; +} + +.w-52 { + width: 13rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-fit { + width: -moz-fit-content; + width: fit-content; +} + +.w-full { + width: 100%; +} + +.min-w-48 { + min-width: 12rem; +} + +.min-w-fit { + min-width: -moz-fit-content; + min-width: fit-content; +} + +.max-w-xs { + max-width: 20rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-none { + flex: none; +} + +.grow { + flex-grow: 1; +} + +.border-separate { + border-collapse: separate; +} + +.border-spacing-1 { + --tw-border-spacing-x: 0.25rem; + --tw-border-spacing-y: 0.25rem; + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); +} + +.cursor-pointer { + cursor: pointer; +} + +.appearance-none { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.flex-nowrap { + flex-wrap: nowrap; +} + +.items-end { + align-items: flex-end; +} + +.items-center { + align-items: center; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-between { + justify-content: space-between; +} + +.gap-0\.5 { + gap: 0.125rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-6 { + gap: 1.5rem; +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(2px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(2px * var(--tw-divide-y-reverse)); +} + +.divide-solid > :not([hidden]) ~ :not([hidden]) { + border-style: solid; +} + +.divide-none > :not([hidden]) ~ :not([hidden]) { + border-style: none; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.overflow-y-scroll { + overflow-y: scroll; +} + +.text-nowrap { + text-wrap: nowrap; +} + +.rounded-box { + border-radius: var(--rounded-box, 1rem); +} + +.rounded-full { + border-radius: 9999px; +} + +.border { + border-width: 2px; +} + +.border-2 { + border-width: 2px; +} + +.border-3 { + border-width: 3px; +} + +.border-b { + border-bottom-width: 2px; +} + +.border-t { + border-top-width: 2px; +} + +.border-none { + border-style: none; +} + +.border-accent { + --tw-border-opacity: 1; + border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); +} + +.border-primary { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); +} + +.border-opacity-100 { + --tw-border-opacity: 1; +} + +.bg-base-100 { + --tw-bg-opacity: 1; + background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity))); +} + +.bg-base-200 { + --tw-bg-opacity: 1; + background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); +} + +.bg-neutral { + --tw-bg-opacity: 1; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); +} + +.bg-transparent { + background-color: transparent; +} + +.fill-accent { + fill: var(--fallback-a,oklch(var(--a)/1)); +} + +.object-scale-down { + -o-object-fit: scale-down; + object-fit: scale-down; +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-6 { + padding: 1.5rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.py-1\.5 { + padding-top: 0.375rem; + padding-bottom: 0.375rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.text-start { + text-align: start; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.capitalize { + text-transform: capitalize; +} + +.italic { + font-style: italic; +} + +.leading-9 { + line-height: 2.25rem; +} + +.leading-none { + line-height: 1; +} + +.leading-tight { + line-height: 1.25; +} + +.text-base-content { + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); +} + +.text-base-content\/50 { + color: var(--fallback-bc,oklch(var(--bc)/0.5)); +} + +.text-info { + --tw-text-opacity: 1; + color: var(--fallback-in,oklch(var(--in)/var(--tw-text-opacity))); +} + +.text-neutral-content { + --tw-text-opacity: 1; + color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); +} + +.text-neutral-content\/80 { + color: var(--fallback-nc,oklch(var(--nc)/0.8)); +} + +.text-primary { + --tw-text-opacity: 1; + color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity))); +} + +.text-warning { + --tw-text-opacity: 1; + color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity))); +} + +.antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.ring-inset { + --tw-ring-inset: inset; +} + +.ring-base-200 { + --tw-ring-opacity: 1; + --tw-ring-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-ring-opacity))); +} + +.ring-gray-500\/50 { + --tw-ring-color: rgb(107 114 128 / 0.5); +} + +.ring-offset-2 { + --tw-ring-offset-width: 2px; +} + +.ring-offset-primary { + --tw-ring-offset-color: var(--fallback-p,oklch(var(--p)/1)); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.\[--tab-border-color\:theme\(colors\.primary\)\] { + --tab-border-color: var(--fallback-p,oklch(var(--p)/1)); +} + +.\[--tab-border\:theme\(borderWidth\.DEFAULT\)\] { + --tab-border: 2px; +} + +/* use color-mix to give the illusion of transparency while not actually being transparent */ + +.hover\:bg-gray-100\/10:hover { + background-color: rgb(243 244 246 / 0.1); +} + +.hover\:ring-1:hover { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +@media (min-width: 640px) { + .sm\:w-96 { + width: 24rem; + } +} diff --git a/merge/src/argus_htmx/tailwindtheme/README.md b/merge/src/argus_htmx/tailwindtheme/README.md new file mode 100644 index 000000000..4a550f415 --- /dev/null +++ b/merge/src/argus_htmx/tailwindtheme/README.md @@ -0,0 +1,6 @@ +# Files for tailwindcss + +`styles.css` and `tailwind.config.js` are automatically generated by the `tailwind_config` django +management command. The source for these files are in `src/argus_htmx/templates/tailwind/` + +The `snippets` contains additional css snippets that are included in the final `styles.css` diff --git a/merge/src/argus_htmx/tailwindtheme/snippets/10-tailwind.css b/merge/src/argus_htmx/tailwindtheme/snippets/10-tailwind.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/merge/src/argus_htmx/tailwindtheme/snippets/10-tailwind.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/merge/src/argus_htmx/tailwindtheme/snippets/15-alert-message.css b/merge/src/argus_htmx/tailwindtheme/snippets/15-alert-message.css new file mode 100644 index 000000000..d737fdace --- /dev/null +++ b/merge/src/argus_htmx/tailwindtheme/snippets/15-alert-message.css @@ -0,0 +1,19 @@ +/* use color-mix to give the illusion of transparency while not actually being transparent */ +@layer components { + .alert.info { + @apply border-info; + background-color: color-mix(in srgb, oklch(var(--in)), oklch(var(--b1)) 85%); + } + .alert.success { + @apply border-success; + background-color: color-mix(in srgb, oklch(var(--su)), oklch(var(--b1)) 85%); + } + .alert.warning { + @apply border-warning; + background-color: color-mix(in srgb, oklch(var(--wa)), oklch(var(--b1)) 85%); + } + .alert.error { + @apply border-error; + background-color: color-mix(in srgb, oklch(var(--er)), oklch(var(--b1)) 85%); + } +} diff --git a/merge/src/argus_htmx/tailwindtheme/snippets/20-loading-box.css b/merge/src/argus_htmx/tailwindtheme/snippets/20-loading-box.css new file mode 100644 index 000000000..0dd0d83ef --- /dev/null +++ b/merge/src/argus_htmx/tailwindtheme/snippets/20-loading-box.css @@ -0,0 +1,29 @@ +@layer components { + /* + * Example usage: + *
+ * + *
+ * + *
+ *
+ */ + .loading-box { + display: grid; + place-items: center; + } + /* Stacks direct children on top of each other */ + .loading-box > * { + grid-column-start: 1; + grid-row-start: 1; + } + /* Adds overlay */ + .loading-box:has(> .htmx-indicator.htmx-request) > *:not(.htmx-indicator) { + opacity: 0.3; + } + /* Disables mouse interactions */ + .loading-box:has(> .htmx-indicator.htmx-request) * { + pointer-events: none; + user-select: none; + } +} diff --git a/merge/src/argus_htmx/tailwindtheme/styles.css b/merge/src/argus_htmx/tailwindtheme/styles.css new file mode 100644 index 000000000..5597f7256 --- /dev/null +++ b/merge/src/argus_htmx/tailwindtheme/styles.css @@ -0,0 +1,3 @@ +@import 'snippets/10-tailwind.css'; +@import 'snippets/15-alert-message.css'; +@import 'snippets/20-loading-box.css'; diff --git a/merge/src/argus_htmx/tailwindtheme/tailwind.config.js b/merge/src/argus_htmx/tailwindtheme/tailwind.config.js new file mode 100644 index 000000000..b97cf09f8 --- /dev/null +++ b/merge/src/argus_htmx/tailwindtheme/tailwind.config.js @@ -0,0 +1,61 @@ +const projectPaths = [ + '../templates/**/*.html', + './**/templates/**/*.html', + 'src/argus_htmx/templates/**/*.html' +]; + +const contentPaths = [...projectPaths]; +console.log(`tailwindcss will scan ${contentPaths}`); + +module.exports = { + content: contentPaths, + theme: { + borderWidth: { + DEFAULT: '2px', + '0': '0', + '1': '1px', + '2': '2px', + '3': '3px', + '4': '4px', + '6': '6px', + '8': '8px', + }, + ...{}, + }, + safelist: [ + "htmx-request" + ], + daisyui: { + themes: [ + "dark", + "light", + { + "argus": { + "primary": "#006d91", + "primary-content": "#d1e1e9", + "secondary": "#f3b61f", + "secondary-content": "#140c00", + "accent": "#c84700", + "accent-content": "#f8dbd1", + "neutral": "#006d91", + "neutral-content": "#d1e1e9", + "base-100": "#edfaff", + "base-200": "#ced9de", + "base-300": "#b0babd", + "base-content": "#141516", + "info": "#0073e5", + "info-content": "#000512", + "success": "#008700", + "success-content": "#d3e7d1", + "warning": "#ee4900", + "warning-content": "#140200", + "error": "#e5545a", + "error-content": "#120203" + } + } + ], + }, + plugins: [ + require("daisyui"), + ] +} diff --git a/merge/src/argus_htmx/templates/htmx/_base_form_modal.html b/merge/src/argus_htmx/templates/htmx/_base_form_modal.html new file mode 100644 index 000000000..5098ac90e --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/_base_form_modal.html @@ -0,0 +1,35 @@ + + + + + diff --git a/merge/src/argus_htmx/templates/htmx/base.html b/merge/src/argus_htmx/templates/htmx/base.html new file mode 100644 index 000000000..3b366edb3 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/base.html @@ -0,0 +1,66 @@ +{% extends "htmx_base.html" %} +{% load static %} +{% block header %} +
+ +
+{% endblock header %} diff --git a/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_dropdown.html b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_dropdown.html new file mode 100644 index 000000000..a684199db --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_dropdown.html @@ -0,0 +1,14 @@ +
+ + Date format + {{ preferences.argus_htmx.datetime_format_name }} + +
    +
  • +

    loading...

    +
  • +
+
diff --git a/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_list.html b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_list.html new file mode 100644 index 000000000..7f850fddf --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/dateformat/_dateformat_list.html @@ -0,0 +1,16 @@ +{% for item in datetime_formats %} +
  • + +
  • +{% empty %} +
  • +

    No date formats configured

    +
  • +{% endfor %} diff --git a/merge/src/argus_htmx/templates/htmx/forms/checkbox_select_multiple.html b/merge/src/argus_htmx/templates/htmx/forms/checkbox_select_multiple.html new file mode 100644 index 000000000..36a57eb10 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/forms/checkbox_select_multiple.html @@ -0,0 +1,12 @@ +{% if widget.wrap_label %} + +{% endif %} diff --git a/merge/src/argus_htmx/templates/htmx/forms/dropdown_select_multiple.html b/merge/src/argus_htmx/templates/htmx/forms/dropdown_select_multiple.html new file mode 100644 index 000000000..747276902 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/forms/dropdown_select_multiple.html @@ -0,0 +1,28 @@ + diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_base.html b/merge/src/argus_htmx/templates/htmx/incidents/_base.html new file mode 100644 index 000000000..3247dee34 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_base.html @@ -0,0 +1,2 @@ +{% extends "htmx/base.html" %} +{% load django_htmx %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_base_incident_update_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_base_incident_update_modal.html new file mode 100644 index 000000000..fa8833ebb --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_base_incident_update_modal.html @@ -0,0 +1,9 @@ +{% extends "htmx/_base_form_modal.html" %} +{% block form_control %} + hx-post="{% url endpoint|default:'htmx:incidents-update' action=action %}" + {% if action_type == "bulk-update" %} + hx-include="[name='incident_ids']" + {% else %} + hx-vals='{"incident_ids": [{{ incident.pk }}] }' + {% endif %} +{% endblock form_control %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ack.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ack.html new file mode 100644 index 000000000..5c700e798 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ack.html @@ -0,0 +1,5 @@ +{% if incident.acked %} + Acked +{% else %} + Unacked +{% endif %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_acknowledge_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_acknowledge_modal.html new file mode 100644 index 000000000..0bd5578c3 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_acknowledge_modal.html @@ -0,0 +1,19 @@ +{% extends "htmx/incidents/_base_incident_update_modal.html" %} +{% block dialogform %} + + +{% endblock dialogform %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_actions.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_actions.html new file mode 100644 index 000000000..5a607ff5b --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_actions.html @@ -0,0 +1 @@ +

    X

    diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_checkbox.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_checkbox.html new file mode 100644 index 000000000..7e425d503 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_checkbox.html @@ -0,0 +1,9 @@ + + diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_close_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_close_modal.html new file mode 100644 index 000000000..f9964d168 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_close_modal.html @@ -0,0 +1,10 @@ +{% extends "htmx/incidents/_base_incident_update_modal.html" %} +{% block dialogform %} + +{% endblock dialogform %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_combined_status.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_combined_status.html new file mode 100644 index 000000000..dd416b15c --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_combined_status.html @@ -0,0 +1,2 @@ +{% include "htmx/incidents/_incident_status.html" %} +{% include "htmx/incidents/_incident_ack.html" %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_description.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_description.html new file mode 100644 index 000000000..1cdd1d3cb --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_description.html @@ -0,0 +1 @@ +{{ incident.description }} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_filterbox.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_filterbox.html new file mode 100644 index 000000000..b3146e5a9 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_filterbox.html @@ -0,0 +1,50 @@ +{% load widget_tweaks %} +
    +
    + Filter incidents + +
    +
    diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_level.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_level.html new file mode 100644 index 000000000..4933e86dd --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_level.html @@ -0,0 +1,2 @@ +{% load argus_htmx %} +{{ incident.level }} - {{ incident.level|pp_level }} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_pk.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_pk.html new file mode 100644 index 000000000..7cb82c735 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_pk.html @@ -0,0 +1 @@ +{{ incident.pk }} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_reopen_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_reopen_modal.html new file mode 100644 index 000000000..96c71bbbf --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_reopen_modal.html @@ -0,0 +1,10 @@ +{% extends "htmx/incidents/_base_incident_update_modal.html" %} +{% block dialogform %} + +{% endblock dialogform %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_source.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source.html new file mode 100644 index 000000000..72e1a8492 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source.html @@ -0,0 +1 @@ +{{ incident.source.name }} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_source_select.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source_select.html new file mode 100644 index 000000000..84b718488 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_source_select.html @@ -0,0 +1,8 @@ +{% extends "htmx/forms/dropdown_select_multiple.html" %} +{% block field_control %} + {{ block.super }} + hx-get="{% url 'htmx:incidents-filter' %}" +{% endblock field_control %} +{% block show_selected %} + {{ option.label }} +{% endblock show_selected %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time.html new file mode 100644 index 000000000..339965f7a --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time.html @@ -0,0 +1 @@ +{{ incident.start_time|date:preferences.argus_htmx.datetime_format }} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time_header.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time_header.html new file mode 100644 index 000000000..5d4784821 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_start_time_header.html @@ -0,0 +1 @@ +
    Timestamp
    diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_status.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_status.html new file mode 100644 index 000000000..a8b6c7bc0 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_status.html @@ -0,0 +1,5 @@ +{% if incident.open %} + Open +{% else %} + Closed +{% endif %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_table.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table.html new file mode 100644 index 000000000..f1e8c3442 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table.html @@ -0,0 +1,102 @@ + + + + {% block columns %} + {% for col in columns %} + + {% empty %} + + {% endfor %} + {% endblock columns %} + + + + {% block incident_rows %} + {% include "htmx/incidents/_incident_table_rows.html" with incident_list=page.object_list %} + {% endblock incident_rows %} + + + + + + +
    + {% if col.header_template %} + {% include col.header_template with label=col.label %} + {% else %} + {{ col.label }} + {% endif %} + No columns configured
    +
    + {% block refresh_info %} + {% include "htmx/incidents/_incidents_refresh_info.html" %} + {% endblock refresh_info %} + +
      + {% if page.number != 1 %} +
    • + + « First +
    • + {% endif %} + {% if page.has_previous %} +
    • + {{ page.previous_page_number }} +
    • + {% endif %} + {% if page.paginator.num_pages > 1 %} +
    • + +
    • + {% else %} +
    • + +
    • + {% endif %} + {% if page.has_next %} +
    • + {{ page.next_page_number }} +
    • + {% endif %} + {% if page.number != page.paginator.num_pages %} +
    • + » Last +
    • + {% endif %} +
    +
    +
    diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_row.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_row.html new file mode 100644 index 000000000..366395466 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_row.html @@ -0,0 +1,13 @@ + + {% block columns %} + {% for col in columns %} + + {% if col.cell_template %} + {% include col.cell_template with column=col %} + {% else %} + UNDEFINED + {% endif %} + + {% endfor %} + {% endblock columns %} + diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_rows.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_rows.html new file mode 100644 index 000000000..792b0f298 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_table_rows.html @@ -0,0 +1,15 @@ +{% for incident in incident_list %} + {% block incident %} + {% include "htmx/incidents/_incident_table_row.html" %} + {% endblock incident %} +{% empty %} + + + {% if count %} + No incidents found that fit all the filter criteria + {% else %} + No incidents on record + {% endif %} + + +{% endfor %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_tag.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_tag.html new file mode 100644 index 000000000..da916fc51 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_tag.html @@ -0,0 +1,2 @@ +{% load argus_htmx %} +{{ incident|tagvalues:column.context.tag|first }} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket.html new file mode 100644 index 000000000..a50190274 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket.html @@ -0,0 +1,11 @@ +{% if incident.ticket_url %} + + + +{% endif %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_autocreate_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_autocreate_modal.html new file mode 100644 index 000000000..126094a81 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_autocreate_modal.html @@ -0,0 +1 @@ +{% extends "htmx/incidents/_base_incident_update_modal.html" %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_edit_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_edit_modal.html new file mode 100644 index 000000000..e6787a56c --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_edit_modal.html @@ -0,0 +1,10 @@ +{% extends "htmx/incidents/_base_incident_update_modal.html" %} +{% block dialogform %} + +{% endblock dialogform %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_manual_create_modal.html b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_manual_create_modal.html new file mode 100644 index 000000000..d2155c45c --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incident_ticket_manual_create_modal.html @@ -0,0 +1,10 @@ +{% extends "htmx/incidents/_base_incident_update_modal.html" %} +{% block dialogform %} + +{% endblock dialogform %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incidents_menubar.html b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_menubar.html new file mode 100644 index 000000000..1a0132f07 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_menubar.html @@ -0,0 +1,23 @@ +
    + {% block menu_tabs %} + +
    + {% include "htmx/incidents/_incident_filterbox.html" %} +
    + +
    + {% include "htmx/incidents/_incidents_update_menu.html" %} +
    + {% endblock menu_tabs %} +
    diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incidents_refresh_info.html b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_refresh_info.html new file mode 100644 index 000000000..d6f7a8cc3 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_refresh_info.html @@ -0,0 +1,46 @@ +
    +
    +
    +
    Total, all time
    +
    + {{ count }} +
    +
    +
    +
    After filtering
    +
    + {{ filtered_count }} +
    +
    +
    +
    Per page
    +
    + +
    +
    +
    +
    Last refreshed
    +
    + {{ last_refreshed|date:preferences.argus_htmx.datetime_format|default:"?" }} +
    +
    +
    +
    Updating every
    +
    + {{ update_interval|default:"?" }}s +
    +
    +
    +
    diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_incidents_update_menu.html b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_update_menu.html new file mode 100644 index 000000000..2dbaed15f --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_incidents_update_menu.html @@ -0,0 +1,10 @@ + diff --git a/merge/src/argus_htmx/templates/htmx/incidents/_selected_incidents_header.html b/merge/src/argus_htmx/templates/htmx/incidents/_selected_incidents_header.html new file mode 100644 index 000000000..c6e07094e --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/_selected_incidents_header.html @@ -0,0 +1,8 @@ +

    {{ label }}

    + + diff --git a/merge/src/argus_htmx/templates/htmx/incidents/incident_detail.html b/merge/src/argus_htmx/templates/htmx/incidents/incident_detail.html new file mode 100644 index 000000000..64d4c627e --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/incident_detail.html @@ -0,0 +1,123 @@ +{% extends "htmx/base.html" %} +{% block main %} +
    + {% block incident_detail %} +

    {{ incident.pk }}: {{ incident.description }}

    +
    +
    +
    +

    Level

    +

    {{ incident.level }}

    +
    +
    +

    Status

    +

    + {% if incident.open %} + Open + {% else %} + Closed + {% endif %} + {% if incident.acked %} + Acknowledged + {% else %} + Unacknowledged + {% endif %} + {% if incident.ticket_url %} + Ticket {{ incident.ticket_url }} + {% else %} + No ticket + {% endif %} +

    +
    +
    +

    Tags

    +

    + {% for tag in incident.deprecated_tags %}{{ tag }}{% endfor %} +

    +
    +
    +

    Primary details (#{{ incident.pk }})

    +
    +

    Description

    +

    {{ incident.description }}

    +

    Start time

    +

    {{ incident.start_time|date:preferences.argus_htmx.datetime_format }}

    +

    Duration

    +

    {{ incident.end_time|date:preferences.argus_htmx.datetime_format }}

    +

    Source

    +

    {{ incident.source.name }}

    +

    Incident id in {{ incident.source.name }}

    +

    {{ incident.source_incident_id }}

    +

    More details at

    +

    + {% if incident.details_url %} + {{ incident.details_url }} + {% else %} + — + {% endif %} +

    +

    Ticket

    +

    + {% if incident.ticket_url %} + {{ incident.ticket_url }} + {% else %} + — + {% endif %} +

    +
    + {% if incident.stateful %} + {% if incident.open %} + {% include "htmx/incidents/_incident_close_modal.html" with action="close" dialog_id="close-incident-dialog" button_title="Close" header="Manually close incident" explanation="Write a message describing why the incident was manually closed" cancel_text="Cancel" submit_text="Close now" %} + {% else %} + {% include "htmx/incidents/_incident_reopen_modal.html" with action="reopen" dialog_id="reopen-incident-dialog" button_title="Reopen" header="Manually reopen incident" explanation="Write a message describing why the incident was manually reopend" cancel_text="Cancel" submit_text="Reopen now" %} + {% endif %} + {% endif %} + {% if incident.ticket_url %} + {% include "htmx/incidents/_incident_ticket_edit_modal.html" with action="update-ticket" dialog_id="edit-ticket-dialog" button_title="Edit ticket url" header="Edit ticket" explanation="" cancel_text="Cancel" submit_text="Update" %} + {% else %} + + {% include "htmx/incidents/_incident_ticket_edit_modal.html" with action="add-ticket" dialog_id="manual-create-ticket-dialog" button_title="Add ticket url" header="Add url to existing ticket" explanation="Are you sure you want to store this url to an existing ticket on this incident?" cancel_text="Cancel" submit_text="Add ticket" %} + {% endif %} +
    +
    +
    +
    +
    +

    Acknowledgements

    +
    + {% for ack in incident.acks %} +
    +

    {{ ack.event.description }}

    +

    + {{ ack.event.actor }} + {{ ack.event.timestamp|date:preferences.argus_htmx.datetime_format }} +

    + {% if ack.expiration %}

    Expires: {{ ack.expiration|date:preferences.argus_htmx.datetime_format }}

    {% endif %} +
    + {% endfor %} +
    + {% include "htmx/incidents/_incident_acknowledge_modal.html" with action="ack" dialog_id="create-acknowledgment-dialog" button_title="Create acknowledgment" header="Submit acknowledgment" explanation="Write a message describing why this incident was acknowledged" cancel_text="Cancel" submit_text="Submit" %} +
    +
    +
    +
    +

    Related events

    +
    + {% for event in incident.events.all %} + {% if not event.ack %} +
    +

    {{ event.get_type_display }}

    +

    {{ event.description }}

    +

    + {{ event.actor }} + {{ event.timestamp|date:preferences.argus_htmx.datetime_format }} +

    +
    + {% endif %} + {% endfor %} +
    +
    +
    + {% endblock incident_detail %} +
    +{% endblock main %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/incident_list.html b/merge/src/argus_htmx/templates/htmx/incidents/incident_list.html new file mode 100644 index 000000000..2e2e708df --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/incident_list.html @@ -0,0 +1,19 @@ +{% extends base %} +{% block main %} +
    + {% if incidents_extra_widget %} +
    +
    {% include "htmx/incidents/_incidents_menubar.html" %}
    +
    {% include incidents_extra_widget %}
    +
    + {% else %} + {% include "htmx/incidents/_incidents_menubar.html" %} + {% endif %} +
    +
    + {% block table %} + {% include "htmx/incidents/_incident_table.html" %} + {% endblock table %} +
    +
    +{% endblock main %} diff --git a/merge/src/argus_htmx/templates/htmx/incidents/responses/_incidents_table_refresh.html b/merge/src/argus_htmx/templates/htmx/incidents/responses/_incidents_table_refresh.html new file mode 100644 index 000000000..7dded7ba9 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/incidents/responses/_incidents_table_refresh.html @@ -0,0 +1,2 @@ +{% block table %} +{% endblock table %} diff --git a/merge/src/argus_htmx/templates/htmx/page_size/_page_size_dropdown.html b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_dropdown.html new file mode 100644 index 000000000..2bc1d20ab --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_dropdown.html @@ -0,0 +1,14 @@ +
    + + Page size + {{ preferences.argus_htmx.page_size }} + +
      +
    • +

      loading...

      +
    • +
    +
    diff --git a/merge/src/argus_htmx/templates/htmx/page_size/_page_size_list.html b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_list.html new file mode 100644 index 000000000..7b7cee174 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/page_size/_page_size_list.html @@ -0,0 +1,16 @@ +{% for item in page_sizes %} +
  • + +
  • +{% empty %} +
  • +

    No page sizes configured

    +
  • +{% endfor %} diff --git a/merge/src/argus_htmx/templates/htmx/themes/_current_theme.html b/merge/src/argus_htmx/templates/htmx/themes/_current_theme.html new file mode 100644 index 000000000..a1cbeb260 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/themes/_current_theme.html @@ -0,0 +1 @@ +{{ preferences.argus_htmx.theme }} diff --git a/merge/src/argus_htmx/templates/htmx/themes/_theme_dropdown.html b/merge/src/argus_htmx/templates/htmx/themes/_theme_dropdown.html new file mode 100644 index 000000000..6f5c4c8a3 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/themes/_theme_dropdown.html @@ -0,0 +1,14 @@ +
    + + Theme + {% include "./_current_theme.html" %} + +
      +
    • +

      loading...

      +
    • +
    +
    diff --git a/merge/src/argus_htmx/templates/htmx/themes/_theme_list.html b/merge/src/argus_htmx/templates/htmx/themes/_theme_list.html new file mode 100644 index 000000000..f81e79f72 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/themes/_theme_list.html @@ -0,0 +1,18 @@ +{% for item in theme_list %} +
  • + +
  • +{% empty %} +
  • +

    empty

    +
  • +{% endfor %} diff --git a/merge/src/argus_htmx/templates/htmx/user/preferences.html b/merge/src/argus_htmx/templates/htmx/user/preferences.html new file mode 100644 index 000000000..c81ebec63 --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx/user/preferences.html @@ -0,0 +1,18 @@ +{% extends "htmx/base.html" %} +{% block main %} +
    +

    {{ page_title }}

    +
    +

    User preferences

    + +
    +
    +{% endblock main %} diff --git a/merge/src/argus_htmx/templates/htmx_base.html b/merge/src/argus_htmx/templates/htmx_base.html new file mode 100644 index 000000000..54cbd6acb --- /dev/null +++ b/merge/src/argus_htmx/templates/htmx_base.html @@ -0,0 +1,41 @@ + +{% load static %} + + + + + + + + {% block title %} + Argus Server: {{ page_title }} + {% endblock title %} + + + + + {% block head %} + {% endblock head %} + + + {% block header %} +

    Argus Server: {{ page_title }}

    + {% endblock header %} +
    + {% block main %} + {% endblock main %} +
    + {% block footer %} + {% endblock footer %} + {% block tail %} + {% endblock tail %} + {% block messages %} + {% include "messages/_notification_messages.html" %} + {% endblock messages %} + + diff --git a/merge/src/argus_htmx/templates/messages/_notification_message.html b/merge/src/argus_htmx/templates/messages/_notification_message.html new file mode 100644 index 000000000..ae50a8bbc --- /dev/null +++ b/merge/src/argus_htmx/templates/messages/_notification_message.html @@ -0,0 +1,17 @@ +
    + {% block message-content %} +
    {{ message }}
    + {% endblock message-content %} + +
    diff --git a/merge/src/argus_htmx/templates/messages/_notification_messages.html b/merge/src/argus_htmx/templates/messages/_notification_messages.html new file mode 100644 index 000000000..02987c00e --- /dev/null +++ b/merge/src/argus_htmx/templates/messages/_notification_messages.html @@ -0,0 +1,7 @@ +
    +
    + {% for message in messages %} + {% include "./_notification_message.html" %} + {% endfor %} +
    +
    diff --git a/merge/src/argus_htmx/templates/messages/_notification_messages_htmx_append.html b/merge/src/argus_htmx/templates/messages/_notification_messages_htmx_append.html new file mode 100644 index 000000000..2c1b6805b --- /dev/null +++ b/merge/src/argus_htmx/templates/messages/_notification_messages_htmx_append.html @@ -0,0 +1,5 @@ +
    + {% for message in messages %} + {% include "./_notification_message.html" %} + {% endfor %} +
    diff --git a/merge/src/argus_htmx/templates/registration/login.html b/merge/src/argus_htmx/templates/registration/login.html new file mode 100644 index 000000000..4c89664b8 --- /dev/null +++ b/merge/src/argus_htmx/templates/registration/login.html @@ -0,0 +1,42 @@ +{% extends "htmx/base.html" %} +{% load widget_tweaks %} +{% block main %} +
    +
    +

    Log In

    +
    + {% if "local" in backends %} +
    + {% csrf_token %} + {% for field in form %} + + {% endfor %} + +
    + {% endif %} + {% if backends.external %} +

    Login with

    + {% for backend in backends.external %} + {{ backend.display_name }} + {% endfor %} + {% endif %} +
    +
    +
    + {% for message in messages %} +
    {{ message }}
    + {% endfor %} +
    +
    +{% endblock main %} +{% comment %} disable default toast messages {% endcomment %} +{% block messages %} +{% endblock messages %} diff --git a/merge/src/argus_htmx/templates/tailwind/styles.css b/merge/src/argus_htmx/templates/tailwind/styles.css new file mode 100644 index 000000000..d26ee7786 --- /dev/null +++ b/merge/src/argus_htmx/templates/tailwind/styles.css @@ -0,0 +1,2 @@ +{% for cssfile in cssfiles %}@import '{{ cssfile }}'; +{% endfor %} diff --git a/merge/src/argus_htmx/templates/tailwind/tailwind.config.js b/merge/src/argus_htmx/templates/tailwind/tailwind.config.js new file mode 100644 index 000000000..68f4c9cb4 --- /dev/null +++ b/merge/src/argus_htmx/templates/tailwind/tailwind.config.js @@ -0,0 +1,34 @@ +const projectPaths = [ + '../templates/**/*.html', + './**/templates/**/*.html', + 'src/argus_htmx/templates/**/*.html' +]; + +const contentPaths = [...projectPaths]; +console.log(`tailwindcss will scan ${contentPaths}`); + +module.exports = { + content: contentPaths, + theme: { + borderWidth: { + DEFAULT: '2px', + '0': '0', + '1': '1px', + '2': '2px', + '3': '3px', + '4': '4px', + '6': '6px', + '8': '8px', + }, + ...{{ themeoverride }}, + }, + safelist: [ + "htmx-request" + ], + daisyui: { + themes: {{ daisyuithemes }}, + }, + plugins: [ + require("daisyui"), + ] +} diff --git a/merge/src/argus_htmx/templatetags/__init__.py b/merge/src/argus_htmx/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/templatetags/argus_htmx.py b/merge/src/argus_htmx/templatetags/argus_htmx.py new file mode 100644 index 000000000..96a7f0618 --- /dev/null +++ b/merge/src/argus_htmx/templatetags/argus_htmx.py @@ -0,0 +1,34 @@ +from django import template + + +register = template.Library() + + +@register.filter +def tagvalues(incident, key) -> list: + """Return values of tags with key KEY for incident INCIDENT + + There can be multiple tags with the same key + """ + tags = incident.deprecated_tags + return [str(tag.value) for tag in tags if tag.key == key] + + +@register.filter +def is_acked_by(incident, group: str) -> bool: + return incident.is_acked_by(group) + + +@register.filter +def pp_level(level: int) -> str: + level = str(level) + mapping = { + "1": "Critical", + "2": "High", + "3": "Moderate", + "4": "Low", + "5": "Information", + } + if level not in mapping: + return mapping["5"] + return mapping[level] diff --git a/merge/src/argus_htmx/themes/__init__.py b/merge/src/argus_htmx/themes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/themes/constants.py b/merge/src/argus_htmx/themes/constants.py new file mode 100644 index 000000000..46f1a72e8 --- /dev/null +++ b/merge/src/argus_htmx/themes/constants.py @@ -0,0 +1,13 @@ +from argus_htmx.themes.utils import get_theme_default, get_theme_names + + +__all__ = [ + "THEME_CHOICES", + "THEME_DEFAULT", + "THEME_NAMES", +] + + +THEME_NAMES = sorted(get_theme_names()) +THEME_CHOICES = tuple((theme, theme) for theme in THEME_NAMES) +THEME_DEFAULT = get_theme_default() diff --git a/merge/src/argus_htmx/themes/urls.py b/merge/src/argus_htmx/themes/urls.py new file mode 100644 index 000000000..267667b8b --- /dev/null +++ b/merge/src/argus_htmx/themes/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + + +app_name = "htmx" +urlpatterns = [ + path("names/", views.theme_names, name="theme-names"), + path("change/", views.change_theme, name="change-theme"), +] diff --git a/merge/src/argus_htmx/themes/utils.py b/merge/src/argus_htmx/themes/utils.py new file mode 100644 index 000000000..2cecbdf2d --- /dev/null +++ b/merge/src/argus_htmx/themes/utils.py @@ -0,0 +1,68 @@ +import logging +from pathlib import Path +from re import findall + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.contrib.staticfiles.finders import find + +from argus_htmx import settings as fallbacks + + +__all__ = [ + "get_raw_themes_setting", + "get_theme_names", + "get_theme_default", +] + + +LOG = logging.getLogger(__name__) + + +def get_raw_themes_setting(): + return getattr(settings, "DAISYUI_THEMES", fallbacks.DAISYUI_THEMES) + + +def get_themes_from_setting(): + themes_setting = get_raw_themes_setting() + theme_names = [] + for theme in themes_setting: + if isinstance(theme, str): + theme_names.append(theme) + elif isinstance(theme, dict): + theme_names.extend(theme.keys()) + return theme_names + + +def get_stylesheet_path(): + return getattr(settings, "STYLESHEET_PATH", fallbacks.STYLESHEET_PATH) + + +def get_themes_from_css(): + THEME_NAME_RE = "(?P[-_\w]+)" + DATA_THEME_RE = f"\[data-theme={THEME_NAME_RE}\]" + + absolute_stylesheet_path = Path(find(get_stylesheet_path())) + styles_css = absolute_stylesheet_path.read_text() + + return findall(DATA_THEME_RE, styles_css) + + +def get_theme_names(quiet=True): + ERROR_MSG = "Themes in settings are out of sync with themes installed" + + themes_from_setting = set(get_themes_from_setting()) + themes_from_css = set(get_themes_from_css()) + installed_themes = themes_from_setting & themes_from_css + + all_themes = themes_from_setting | themes_from_css + if all_themes != installed_themes: + LOG.warning(ERROR_MSG) + if not quiet: + raise ImproperlyConfigured(ERROR_MSG) + + return installed_themes + + +def get_theme_default(): + return getattr(settings, "THEME_DEFAULT", fallbacks.THEME_DEFAULT) diff --git a/merge/src/argus_htmx/themes/views.py b/merge/src/argus_htmx/themes/views.py new file mode 100644 index 000000000..1355b1c74 --- /dev/null +++ b/merge/src/argus_htmx/themes/views.py @@ -0,0 +1,29 @@ +import logging + +from django.shortcuts import render + +from django.views.decorators.http import require_GET, require_POST +from django.http import HttpResponse +from django_htmx.http import HttpResponseClientRefresh + +from argus.auth.utils import save_preference + +from argus_htmx.constants import THEME_NAMES +from argus_htmx.incidents.views import HtmxHttpRequest + +LOG = logging.getLogger(__name__) +THEMES_MODULE = "argus_htmx" + + +@require_GET +def theme_names(request: HtmxHttpRequest) -> HttpResponse: + themes = THEME_NAMES + return render(request, "htmx/themes/_theme_list.html", {"theme_list": themes}) + + +@require_POST +def change_theme(request: HtmxHttpRequest) -> HttpResponse: + success = save_preference(request, request.POST, "argus_htmx", "theme") + if success: + return render(request, "htmx/themes/_current_theme.html") + return HttpResponseClientRefresh() diff --git a/merge/src/argus_htmx/timeslots/__init__.py b/merge/src/argus_htmx/timeslots/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/timeslots/urls.py b/merge/src/argus_htmx/timeslots/urls.py new file mode 100644 index 000000000..efbfffc31 --- /dev/null +++ b/merge/src/argus_htmx/timeslots/urls.py @@ -0,0 +1,21 @@ +from django.http import HttpResponse +from django.template import Template, RequestContext +from django.urls import path + + +def placeholder(request): + template = Template( + """{% extends "htmx/base.html" %} + {% block main %} +

    TIMESLOT PLACEHOLDER

    + {% endblock main %} + """ + ) + context = RequestContext(request) + return HttpResponse(template.render(context)) + + +app_name = "htmx" +urlpatterns = [ + path("", placeholder, name="timeslot-placeholder"), +] diff --git a/merge/src/argus_htmx/urls.py b/merge/src/argus_htmx/urls.py new file mode 100644 index 000000000..3b0e3715a --- /dev/null +++ b/merge/src/argus_htmx/urls.py @@ -0,0 +1,25 @@ +from django.contrib.auth import views as django_auth_views +from django.urls import path, include + +from .auth import views as auth_views +from .incidents.urls import urlpatterns as incident_urls +from .timeslots.urls import urlpatterns as timeslot_urls +from .notificationprofiles.urls import urlpatterns as notificationprofile_urls +from .destinations.urls import urlpatterns as destination_urls +from .themes.urls import urlpatterns as theme_urls +from .dateformat.urls import urlpatterns as dateformat_urls +from .user.urls import urlpatterns as user_urls + +app_name = "htmx" +urlpatterns = [ + path("accounts/login/", auth_views.LoginView.as_view(), name="login"), + path("accounts/logout/", django_auth_views.LogoutView.as_view(), name="logout"), + # path("accounts/", include("django.contrib.auth.urls")), + path("incidents/", include(incident_urls)), + path("timeslots/", include(timeslot_urls)), + path("notificationprofiles/", include(notificationprofile_urls)), + path("destinations/", include(destination_urls)), + path("themes/", include(theme_urls)), + path("dateformat/", include(dateformat_urls)), + path("user/", include(user_urls)), +] diff --git a/merge/src/argus_htmx/user/__init__.py b/merge/src/argus_htmx/user/__init__.py new file mode 100644 index 000000000..2f037b505 --- /dev/null +++ b/merge/src/argus_htmx/user/__init__.py @@ -0,0 +1 @@ +"""User preferences pages""" diff --git a/merge/src/argus_htmx/user/preferences/__init__.py b/merge/src/argus_htmx/user/preferences/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/src/argus_htmx/user/preferences/models.py b/merge/src/argus_htmx/user/preferences/models.py new file mode 100644 index 000000000..b87f9b3fb --- /dev/null +++ b/merge/src/argus_htmx/user/preferences/models.py @@ -0,0 +1,44 @@ +from django import forms + +from argus.auth.models import preferences + +from argus_htmx.constants import ( + DATETIME_FORMATS, + DATETIME_DEFAULT, + DATETIME_CHOICES, + PAGE_SIZE_CHOICES, + DEFAULT_PAGE_SIZE, + THEME_CHOICES, + THEME_DEFAULT, +) + + +class DateTimeFormatForm(forms.Form): + datetime_format_name = forms.ChoiceField(required=False, choices=DATETIME_CHOICES) + + +class PageSizeForm(forms.Form): + page_size = forms.TypedChoiceField(required=False, choices=PAGE_SIZE_CHOICES, coerce=int) + + +class ThemeForm(forms.Form): + theme = forms.ChoiceField(choices=THEME_CHOICES) + + +@preferences(namespace="argus_htmx") +class ArgusHtmxPreferences: + FORMS = { + "datetime_format_name": DateTimeFormatForm, + "page_size": PageSizeForm, + "theme": ThemeForm, + } + _FIELD_DEFAULTS = { + "datetime_format_name": DATETIME_DEFAULT, + "page_size": DEFAULT_PAGE_SIZE, + "theme": THEME_DEFAULT, + } + + def update_context(self, context): + datetime_format_name = context.get("datetime_format_name", DATETIME_DEFAULT) + datetime_format = DATETIME_FORMATS[datetime_format_name] + return {"datetime_format": datetime_format} diff --git a/merge/src/argus_htmx/user/urls.py b/merge/src/argus_htmx/user/urls.py new file mode 100644 index 000000000..15b8667b8 --- /dev/null +++ b/merge/src/argus_htmx/user/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + + +app_name = "htmx" +urlpatterns = [ + path("", views.user_preferences, name="user-preferences"), + path("page_size/names/", views.page_size_names, name="page-sizes"), + path("page_size/change/", views.change_page_size, name="change-page-size"), +] diff --git a/merge/src/argus_htmx/user/views.py b/merge/src/argus_htmx/user/views.py new file mode 100644 index 000000000..135c2683b --- /dev/null +++ b/merge/src/argus_htmx/user/views.py @@ -0,0 +1,29 @@ +from django.http import HttpResponse +from django.shortcuts import render +from django.views.decorators.http import require_GET, require_POST +from django_htmx.http import HttpResponseClientRefresh + +from argus.auth.utils import save_preference + +from argus_htmx.constants import ALLOWED_PAGE_SIZES +from argus_htmx.incidents.views import HtmxHttpRequest + + +@require_GET +def page_size_names(request: HtmxHttpRequest) -> HttpResponse: + page_sizes = sorted(ALLOWED_PAGE_SIZES) + return render(request, "htmx/page_size/_page_size_list.html", {"page_sizes": page_sizes}) + + +@require_POST +def change_page_size(request: HtmxHttpRequest) -> HttpResponse: + save_preference(request, request.POST, "argus_htmx", "page_size") + return HttpResponseClientRefresh() + + +def user_preferences(request) -> HttpResponse: + """Renders the main preferences page for a user""" + context = { + "page_title": "User preferences", + } + return render(request, "htmx/user/preferences.html", context=context) diff --git a/merge/src/argus_htmx/utils.py b/merge/src/argus_htmx/utils.py new file mode 100644 index 000000000..3d94f24ba --- /dev/null +++ b/merge/src/argus_htmx/utils.py @@ -0,0 +1,87 @@ +# Should be in argus proper and merged with api bulk actions +# +# THIS IS LESS DRY-ABLE THAN YOU (or SonarCloud) THINK(S) +# +# Uses composition, see bulk_change_incidents() + +from typing import List, Dict, Any + +from django.utils import timezone + +from argus.incident.models import Incident +from argus.util.signals import bulk_changed + + +def send_changed_incidents(incidents): + bulk_changed.send(sender=Incident, instances=incidents) + + +def get_qs_for_incident_ids(incident_ids: List[int], qs=None): + # setup + if qs is None: + qs = Incident.objects.all() + qs = qs.filter(pk__in=incident_ids) + found_ids = set(qs.values_list("id", flat=True)) + + # wash ids + missing_ids = set(incident_ids) - found_ids + return qs, missing_ids + + +def bulk_ack_queryset(actor, qs, data: Dict[str, Any]): + timestamp = data["timestamp"] + description = data.get("description", "") + expiration = data.get("expiration", None) + acks = qs.create_acks(actor, timestamp, description, expiration) + incidents = [] + for ack in acks: + incidents.append(ack.event.incident) + return incidents + + +def bulk_close_queryset(actor, qs, data: Dict[str, Any]): + timestamp = data["timestamp"] + description = data.get("description", "") + events = qs.close(actor, timestamp, description) + incidents = [] + for event in events: + incidents.append(event.incident) + return incidents + + +def bulk_reopen_queryset(actor, qs, data: Dict[str, Any]): + timestamp = data["timestamp"] + description = data.get("description", "") + events = qs.reopen(actor, timestamp, description) + incidents = [] + for event in events: + incidents.append(event.incident) + return incidents + + +def bulk_change_ticket_url_queryset(actor, qs, data: Dict[str, Any]): + timestamp = data["timestamp"] + ticket_url = data.get("ticket_url", "") + return qs.update_ticket_url(actor, ticket_url, timestamp=timestamp) + + +def bulk_change_incidents(actor, incident_ids: List[int], data: Dict[str, Any], func, qs=None): + """ + Update incidents in bulk + + Applies ``func`` to the incidents in ``incident_ids`` with the + pre-validated bulk-type dependent key-value pairs in ``data``. Blames it on + the user ``actor``. Adds ``timestamp`` to ``data`` if it is not already present. + + Returns a queryset of the changed incidents and a list of incident ids that + could not be changed due to for instance + + - The incidents have been deleted in the meantime + - We're working on a subset of incidents and the ids are not in that subset + """ + qs, missing_ids = get_qs_for_incident_ids(incident_ids, qs) + if not data.get("timestamp"): + data["timestamp"] = timezone.now() + incidents = func(actor, qs, data) + send_changed_incidents(incidents) + return incidents, missing_ids diff --git a/merge/tests/__init__.py b/merge/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/merge/tests/settings.py b/merge/tests/settings.py new file mode 100644 index 000000000..5d68b97be --- /dev/null +++ b/merge/tests/settings.py @@ -0,0 +1,14 @@ +SECRET_KEY = "fake-key" +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } +} +INSTALLED_APPS = [ + "django.contrib.auth", + "django.contrib.contenttypes", + # "argus.auth", + "tests", +] +ROOT_URLCONF = "tests.urls" diff --git a/merge/tests/test_middleware.py b/merge/tests/test_middleware.py new file mode 100644 index 000000000..3947d2ec4 --- /dev/null +++ b/merge/tests/test_middleware.py @@ -0,0 +1,50 @@ +from unittest.mock import Mock + +from django import test +from django.http import HttpResponseRedirect +from django.test.client import RequestFactory + +from argus_htmx.middleware import LoginRequiredMiddleware + + +class TestLoginRequiredMiddleware(test.TestCase): + def setUp(self): + request = RequestFactory().get("/foo") + request.login_url = "/login" + request.user = Mock() + request.get_full_path = Mock(return_value="/bar") + self.request = request + + def test_process_view_login_required_false(self): + def view_func(): + return None + + view_func.login_required = False + result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {}) + self.assertIsNone(result) + + @test.override_settings(PUBLIC_URLS=("/foo",)) + def test_process_view_public_urls(self): + def view_func(): + return None + + result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {}) + self.assertIsNone(result) + + def test_process_view_authenticated(self): + def view_func(): + return None + + self.request.user.is_authenticated = True + result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {}) + self.assertIsNone(result) + delattr(self.request.user, "is_authenticated") + + def test_process_view_redirect(self): + def view_func(): + return None + + self.request.user.is_authenticated = False + result = LoginRequiredMiddleware(lambda x: x).process_view(self.request, view_func, None, {}) + self.assertIsNotNone(result) + self.assertIsInstance(result, HttpResponseRedirect) diff --git a/merge/tests/urls.py b/merge/tests/urls.py new file mode 100644 index 000000000..060c75da8 --- /dev/null +++ b/merge/tests/urls.py @@ -0,0 +1,6 @@ +from django.urls import path, include + +app_name = "htmx" +urlpatterns = [ + path("accounts/", include("django.contrib.auth.urls")), +] diff --git a/merge/tox.ini b/merge/tox.ini new file mode 100644 index 000000000..4e885d693 --- /dev/null +++ b/merge/tox.ini @@ -0,0 +1,24 @@ +[tox] +requires = + tox>=4 + +env_list = py{310,311,312}-django{42,50,51} + +[testenv:tailwind] +setenv = + TAILWINDDIR=src/argus_htmx/tailwindtheme + STATICDIR=src/argus_htmx +allowlist_externals = + tailwindcss +commands = + tailwindcss -c {env:TAILWINDDIR}/tailwind.config.js -i {env:TAILWINDDIR}/styles.css --output {env:STATICDIR}/static/styles.css + +[testenv] +setenv = + PYTHONPATH = {toxinidir}/src +commands = + python runtests.py +deps = + django42: django>=4.2,<5 + django50: django>=5.0,<5.1 + django51: django>=5.1,<5.2