From d2a682f2616b2dba130bac7cb64259955f8d4c87 Mon Sep 17 00:00:00 2001 From: Johanna England Date: Wed, 22 Jan 2025 10:05:15 +0100 Subject: [PATCH] Add search for tags --- src/argus/htmx/incident/filter.py | 17 +- src/argus/htmx/incident/urls.py | 1 + src/argus/htmx/incident/views.py | 25 +- src/argus/htmx/static/styles.css | 237 ++++++++++++++++-- .../search_dropdown_select_multiple.html | 30 +++ .../templates/htmx/forms/search_results.html | 17 ++ .../htmx/forms/selected_choices.html | 19 ++ src/argus/htmx/widgets.py | 4 + 8 files changed, 319 insertions(+), 31 deletions(-) create mode 100644 src/argus/htmx/templates/htmx/forms/search_dropdown_select_multiple.html create mode 100644 src/argus/htmx/templates/htmx/forms/search_results.html create mode 100644 src/argus/htmx/templates/htmx/forms/selected_choices.html diff --git a/src/argus/htmx/incident/filter.py b/src/argus/htmx/incident/filter.py index 209af1bcb..f2d0e6c39 100644 --- a/src/argus/htmx/incident/filter.py +++ b/src/argus/htmx/incident/filter.py @@ -3,10 +3,11 @@ from django.views.generic import ListView from argus.filter import get_filter_backend -from argus.incident.models import SourceSystem +from argus.incident.models import SourceSystem, Tag from argus.incident.constants import Level -from argus.htmx.widgets import BadgeDropdownMultiSelect from argus.notificationprofile.models import Filter +from argus.htmx.widgets import BadgeDropdownMultiSelect, SearchDropdownMultiSelect + filter_backend = get_filter_backend() QuerySetFilter = filter_backend.QuerySetFilter @@ -47,13 +48,14 @@ class IncidentFilterForm(forms.Form): required=False, label="Sources", ) - tags = forms.CharField( - widget=forms.TextInput( + tags = forms.MultipleChoiceField( + widget=SearchDropdownMultiSelect( attrs={ - "placeholder": "enter tags...", - "class": "show-selected-box input input-accent input-bordered input-md border overflow-y-auto min-h-8 h-auto max-h-16 max-w-xs leading-tight", - } + "placeholder": "search tags...", + }, + partial_get=None, ), + choices=((tag.id, str(tag)) for tag in Tag.objects.all()), required=False, label="Tags", ) @@ -70,6 +72,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # mollify tests self.fields["sourceSystemIds"].widget.partial_get = reverse("htmx:incident-filter") + self.fields["tags"].widget.partial_get = reverse("htmx:search-tags") def _tristate(self, onkey, offkey): on = self.cleaned_data.get(onkey, None) diff --git a/src/argus/htmx/incident/urls.py b/src/argus/htmx/incident/urls.py index 9fc9564e5..a61d3bc8b 100644 --- a/src/argus/htmx/incident/urls.py +++ b/src/argus/htmx/incident/urls.py @@ -12,4 +12,5 @@ path("filter-list/", filter.FilterListView.as_view(), name="filter-list"), path("select-filter/", views.filter_select, name="select-filter"), path("filter-create/", views.create_filter, name="filter-create"), + path("search-tags/", views.search_tags, name="search-tags"), ] diff --git a/src/argus/htmx/incident/views.py b/src/argus/htmx/incident/views.py index ac4c792e1..bd5223067 100644 --- a/src/argus/htmx/incident/views.py +++ b/src/argus/htmx/incident/views.py @@ -14,7 +14,7 @@ from django_htmx.http import HttpResponseClientRefresh, reswap, retarget from argus.auth.utils import get_or_update_preference -from argus.incident.models import Incident +from argus.incident.models import Incident, Tag from argus.notificationprofile.models import Filter from argus.util.datetime_utils import make_aware @@ -134,6 +134,29 @@ def filter_select(request: HtmxHttpRequest): return retarget(HttpResponse(), "#incident-filter-select") +@require_GET +def search_tags(request: HtmxHttpRequest): + query = request.GET.get("search", None) + + if not query: + return render( + request, + "htmx/forms/selected_choices.html", + ) + + if Tag.TAG_DELIMITER in query: + key, value = Tag.split(query) + tags = Tag.objects.filter(key=key, value__icontains=value)[:50] + else: + tags = Tag.objects.filter(key__icontains=query)[:50] + + return render( + request, + "htmx/forms/search_results.html", + {"tags": tags}, + ) + + @require_GET def incident_list(request: HtmxHttpRequest) -> HttpResponse: columns = get_incident_table_columns() diff --git a/src/argus/htmx/static/styles.css b/src/argus/htmx/static/styles.css index e8c0cc19a..d800d10d6 100644 --- a/src/argus/htmx/static/styles.css +++ b/src/argus/htmx/static/styles.css @@ -107,7 +107,7 @@ } /* -! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.16 | MIT License | https://tailwindcss.com */ /* @@ -947,6 +947,11 @@ html { border-color: var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity))); } + .checkbox-info:hover { + --tw-border-opacity: 1; + border-color: var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity))); + } + .label a:hover { --tw-text-opacity: 1; color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); @@ -1137,7 +1142,7 @@ html { position: relative; display: grid; overflow: hidden; - grid-template-rows: max-content 0fr; + grid-template-rows: auto 0fr; transition: grid-template-rows 0.2s; width: 100%; border-radius: var(--rounded-box, 1rem); @@ -1159,13 +1164,6 @@ html { opacity: 0; } -:where(.collapse > input[type="checkbox"]), -:where(.collapse > input[type="radio"]) { - height: 100%; - width: 100%; - z-index: 1; -} - .collapse-content { visibility: hidden; grid-column-start: 1; @@ -1182,12 +1180,12 @@ html { .collapse[open], .collapse-open, .collapse:focus:not(.collapse-close) { - grid-template-rows: max-content 1fr; + grid-template-rows: auto 1fr; } .collapse:not(.collapse-close):has(> input[type="checkbox"]:checked), .collapse:not(.collapse-close):has(> input[type="radio"]:checked) { - grid-template-rows: max-content 1fr; + grid-template-rows: auto 1fr; } .collapse[open] > .collapse-content, @@ -1311,6 +1309,16 @@ html { opacity: 1; } + .btm-nav > *.disabled:hover, + .btm-nav > *[disabled]:hover { + pointer-events: none; + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; + } + .btn:hover { --tw-border-opacity: 1; border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); @@ -1355,6 +1363,15 @@ html { } } + .btn-outline:hover { + --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:hover { --tw-text-opacity: 1; color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); @@ -1391,6 +1408,54 @@ html { } } + .btn-outline.btn-success:hover { + --tw-text-opacity: 1; + color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-success:hover { + 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:hover { + --tw-text-opacity: 1; + color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-info:hover { + 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:hover { + --tw-text-opacity: 1; + color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-warning:hover { + 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:hover { + --tw-text-opacity: 1; + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); + } + + @supports (color: color-mix(in oklab, black, black)) { + .btn-outline.btn-error:hover { + 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-disabled:hover, .btn[disabled]:hover, .btn:disabled:hover { @@ -2104,6 +2169,16 @@ input.tab:checked + .tab-content, color: var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity))); } +.btm-nav > *.disabled, + .btm-nav > *[disabled] { + pointer-events: none; + --tw-border-opacity: 0; + background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); + --tw-bg-opacity: 0.1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --tw-text-opacity: 0.2; +} + .btm-nav > * .label { font-size: 1rem; line-height: 1.5rem; @@ -2142,6 +2217,14 @@ input.tab:checked + .tab-content, .btn-neutral { --btn-color: var(--fallback-n); } + + .btn-info { + --btn-color: var(--fallback-in); + } + + .btn-error { + --btn-color: var(--fallback-er); + } } @supports (color: color-mix(in oklab, black, black)) { @@ -2222,6 +2305,14 @@ input.tab:checked + .tab-content, .btn-neutral { --btn-color: var(--n); } + + .btn-info { + --btn-color: var(--in); + } + + .btn-error { + --btn-color: var(--er); + } } .btn-secondary { @@ -2242,6 +2333,18 @@ input.tab:checked + .tab-content, outline-color: var(--fallback-n,oklch(var(--n)/1)); } +.btn-info { + --tw-text-opacity: 1; + color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity))); + outline-color: var(--fallback-in,oklch(var(--in)/1)); +} + +.btn-error { + --tw-text-opacity: 1; + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); + outline-color: var(--fallback-er,oklch(var(--er)/1)); +} + .btn.glass { --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; @@ -2276,6 +2379,16 @@ input.tab:checked + .tab-content, text-decoration-line: underline; } +.btn-outline { + border-color: currentColor; + background-color: transparent; + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); + --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); +} + .btn-outline.btn-active { --tw-border-opacity: 1; border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity))); @@ -2315,21 +2428,41 @@ input.tab:checked + .tab-content, color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); } +.btn-outline.btn-success { + --tw-text-opacity: 1; + color: var(--fallback-su,oklch(var(--su)/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 { + --tw-text-opacity: 1; + color: var(--fallback-in,oklch(var(--in)/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 { + --tw-text-opacity: 1; + color: var(--fallback-wa,oklch(var(--wa)/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 { + --tw-text-opacity: 1; + color: var(--fallback-er,oklch(var(--er)/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))); @@ -2510,6 +2643,27 @@ input.tab:checked + .tab-content, color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity))); } +.checkbox-info { + --chkbg: var(--fallback-in,oklch(var(--in)/1)); + --chkfg: var(--fallback-inc,oklch(var(--inc)/1)); + --tw-border-opacity: 1; + border-color: var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity))); +} + +.checkbox-info:focus-visible { + outline-color: var(--fallback-in,oklch(var(--in)/1)); +} + +.checkbox-info:checked, + .checkbox-info[aria-checked="true"] { + --tw-border-opacity: 1; + border-color: var(--fallback-in,oklch(var(--in)/var(--tw-border-opacity))); + --tw-bg-opacity: 1; + background-color: var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity))); +} + @keyframes checkmark { 0% { background-position-y: 5px; @@ -2605,9 +2759,15 @@ details.collapse summary::-webkit-details-marker { position: relative; } +:where(.collapse > input[type="checkbox"]), +:where(.collapse > input[type="radio"]) { + z-index: 1; +} + .collapse-title, :where(.collapse > input[type="checkbox"]), :where(.collapse > input[type="radio"]) { + width: 100%; padding: 1rem; padding-inline-end: 3rem; min-height: 3.75rem; @@ -2784,13 +2944,18 @@ details.collapse summary::-webkit-details-marker { 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='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); - mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); + -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='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); - mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); + -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-dots { + -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_qM83%7Banimation:spinner_8HQG 1.05s infinite%7D.spinner_oXPr%7Banimation-delay:.1s%7D.spinner_ZTLf%7Banimation-delay:.2s%7D@keyframes spinner_8HQG%7B0%25,57.14%25%7Banimation-timing-function:cubic-bezier(0.33,.66,.66,1);transform:translate(0)%7D28.57%25%7Banimation-timing-function:cubic-bezier(0.33,0,.66,.33);transform:translateY(-6px)%7D100%25%7Btransform:translate(0)%7D%7D%3C/style%3E%3Ccircle class='spinner_qM83' cx='4' cy='12' r='3'/%3E%3Ccircle class='spinner_qM83 spinner_oXPr' cx='12' cy='12' r='3'/%3E%3Ccircle class='spinner_qM83 spinner_ZTLf' cx='20' cy='12' r='3'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_qM83%7Banimation:spinner_8HQG 1.05s infinite%7D.spinner_oXPr%7Banimation-delay:.1s%7D.spinner_ZTLf%7Banimation-delay:.2s%7D@keyframes spinner_8HQG%7B0%25,57.14%25%7Banimation-timing-function:cubic-bezier(0.33,.66,.66,1);transform:translate(0)%7D28.57%25%7Banimation-timing-function:cubic-bezier(0.33,0,.66,.33);transform:translateY(-6px)%7D100%25%7Btransform:translate(0)%7D%7D%3C/style%3E%3Ccircle class='spinner_qM83' cx='4' cy='12' r='3'/%3E%3Ccircle class='spinner_qM83 spinner_oXPr' cx='12' cy='12' r='3'/%3E%3Ccircle class='spinner_qM83 spinner_ZTLf' cx='20' cy='12' r='3'/%3E%3C/svg%3E"); } .loading-lg { @@ -2973,10 +3138,6 @@ details.collapse summary::-webkit-details-marker { margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } -.modal-action:where([dir="rtl"], [dir="rtl"] *) > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 1; -} - @keyframes modal-pop { 0% { opacity: 0; @@ -3525,6 +3686,11 @@ details.collapse summary::-webkit-details-marker { width: 1.25rem; } +[type="checkbox"].checkbox-lg { + height: 2rem; + width: 2rem; +} + .indicator :where(.indicator-item) { bottom: auto; inset-inline-end: 0px; @@ -4154,6 +4320,11 @@ details.collapse summary::-webkit-details-marker { margin-bottom: 1rem; } +.mx-auto { + margin-left: auto; + margin-right: auto; +} + .-ml-2 { margin-left: -0.5rem; } @@ -4174,10 +4345,18 @@ details.collapse summary::-webkit-details-marker { margin-top: 0.75rem; } +.mr-4 { + margin-right: 1rem; +} + .block { display: block; } +.inline { + display: inline; +} + .flex { display: flex; } @@ -4320,10 +4499,6 @@ details.collapse summary::-webkit-details-marker { flex-grow: 1; } -.border-collapse { - border-collapse: collapse; -} - .border-separate { border-collapse: separate; } @@ -4604,6 +4779,11 @@ details.collapse summary::-webkit-details-marker { line-height: 1rem; } +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + .font-bold { font-weight: 700; } @@ -4674,6 +4854,11 @@ details.collapse summary::-webkit-details-marker { color: var(--fallback-wa,oklch(var(--wa)/var(--tw-text-opacity, 1))); } +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity, 1)); +} + .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -4697,6 +4882,12 @@ details.collapse summary::-webkit-details-marker { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px 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); diff --git a/src/argus/htmx/templates/htmx/forms/search_dropdown_select_multiple.html b/src/argus/htmx/templates/htmx/forms/search_dropdown_select_multiple.html new file mode 100644 index 000000000..5b7f2dc67 --- /dev/null +++ b/src/argus/htmx/templates/htmx/forms/search_dropdown_select_multiple.html @@ -0,0 +1,30 @@ + diff --git a/src/argus/htmx/templates/htmx/forms/search_results.html b/src/argus/htmx/templates/htmx/forms/search_results.html new file mode 100644 index 000000000..2c526fe16 --- /dev/null +++ b/src/argus/htmx/templates/htmx/forms/search_results.html @@ -0,0 +1,17 @@ +{% with id=widget.attrs.id %} +
+ {% for group, options, index in widget.optgroups %} + {% if group %} +
+ + {% endif %} + {% for option in options %} +
{% include option.template_name with widget=option %}
+ {% endfor %} + {% if group %}
{% endif %} + {% empty %} +
No search results. Please type at least one letter.
+ {% endfor %} +
+{% endwith %} diff --git a/src/argus/htmx/templates/htmx/forms/selected_choices.html b/src/argus/htmx/templates/htmx/forms/selected_choices.html new file mode 100644 index 000000000..557f56cc2 --- /dev/null +++ b/src/argus/htmx/templates/htmx/forms/selected_choices.html @@ -0,0 +1,19 @@ +{% with id=widget.attrs.id %} +
+ {% for group, options, index in widget.optgroups %} + {% if group %} +
+ + {% endif %} + {% for option in options %} + {% if option.selected %} +
{% include option.template_name with widget=option %}
+ {% endif %} + {% endfor %} + {% if group %}
{% endif %} + {% empty %} +
No search results. Please type at least one letter.
+ {% endfor %} +
+{% endwith %} diff --git a/src/argus/htmx/widgets.py b/src/argus/htmx/widgets.py index a810cbe44..94011ecc7 100644 --- a/src/argus/htmx/widgets.py +++ b/src/argus/htmx/widgets.py @@ -49,3 +49,7 @@ def has_selected(self, name, value, attrs): class BadgeDropdownMultiSelect(DropdownMultiSelect): template_name = "htmx/forms/badge_dropdown_select_multiple.html" + + +class SearchDropdownMultiSelect(DropdownMultiSelect): + template_name = "htmx/forms/search_dropdown_select_multiple.html"