diff --git a/Taskfile.yml b/Taskfile.yml index 0ac98370..50cd5434 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -78,7 +78,7 @@ tasks: - mkdir -p assets - curl -sL https://unpkg.com/leaflet/dist/leaflet.js -o assets/leaflet.js - curl -sL https://unpkg.com/leaflet/dist/leaflet.css -o assets/leaflet.css - - curl -sL https://unpkg.com/htmx.org -o assets/htmx.min.js + - curl -sL https://unpkg.com/htmx.org@2.0 -o assets/htmx.min.js - curl -sL https://unpkg.com/alpinejs -o assets/alpinejs.min.js generate-tailwindcss: diff --git a/assets/custom.css b/assets/custom.css index 45b37433..94430e5a 100644 --- a/assets/custom.css +++ b/assets/custom.css @@ -14,7 +14,13 @@ html, body { height: 25px; } -.confidence-ball { +.confidence-container { + display: inline-flex; + gap: 4px; + align-items: center; +} + +.confidence-badge { width: 54px; height: 25px; display: flex; @@ -24,12 +30,38 @@ html, body { font-size: 0.75rem; } +.review-badge { + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 9999px; + font-size: 0.8rem; + font-weight: bold; + color: white; +} + +.review-badge.correct { + background-color: hsl(142, 76%, 36%); +} + +.review-badge.false_positive { + background-color: hsl(0, 74%, 42%); +} + @media (max-width: 1024px) { - .confidence-ball { + .confidence-badge { width: 40px; height: 20px; font-size: 0.65rem; } + + .review-badge { + width: 26px; + height: 26px; + font-size: 0.75rem; + } } input.invalid { diff --git a/assets/tailwind.css b/assets/tailwind.css index e6e3fb5e..500d474d 100644 --- a/assets/tailwind.css +++ b/assets/tailwind.css @@ -995,6 +995,11 @@ html { color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity))); } + .radio-primary:hover { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); + } + .tab:hover { --tw-text-opacity: 1; } @@ -1171,7 +1176,7 @@ html { position: relative; display: grid; overflow: hidden; - grid-template-rows: auto 0fr; + grid-template-rows: max-content 0fr; transition: grid-template-rows 0.2s; width: 100%; border-radius: var(--rounded-box, 1rem); @@ -1193,6 +1198,13 @@ 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; @@ -1209,12 +1221,12 @@ html { .collapse[open], .collapse-open, .collapse:focus:not(.collapse-close) { - grid-template-rows: auto 1fr; + grid-template-rows: max-content 1fr; } .collapse:not(.collapse-close):has(> input[type="checkbox"]:checked), .collapse:not(.collapse-close):has(> input[type="radio"]:checked) { - grid-template-rows: auto 1fr; + grid-template-rows: max-content 1fr; } .collapse[open] > .collapse-content, @@ -1331,28 +1343,109 @@ html { transform: translateX(0%); } -.drawer-end .drawer-toggle ~ .drawer-content { +.drawer-end > .drawer-toggle ~ .drawer-content { grid-column-start: 1; } -.drawer-end .drawer-toggle ~ .drawer-side { +.drawer-end > .drawer-toggle ~ .drawer-side { grid-column-start: 2; justify-items: end; } -.drawer-end .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { +.drawer-end > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { transform: translateX(100%); } -[dir="rtl"] .drawer-end .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { +[dir="rtl"] .drawer-end > .drawer-toggle ~ .drawer-side > *:not(.drawer-overlay) { transform: translateX(-100%); } -.drawer-end .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) { +.drawer-end > .drawer-toggle:checked ~ .drawer-side > *:not(.drawer-overlay) { transform: translateX(0%); } +.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; + } + .btm-nav > *.disabled:hover, .btm-nav > *[disabled]:hover { pointer-events: none; @@ -1517,6 +1610,12 @@ html { } } + .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; @@ -1537,6 +1636,10 @@ html { } } +.dropdown:is(details) summary::-webkit-details-marker { + display: none; +} + .form-control { display: flex; flex-direction: column; @@ -1831,6 +1934,21 @@ html { background-color: var(--fallback-bc,oklch(var(--bc)/0.2)); } +.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%; @@ -2070,6 +2188,23 @@ input.tab:checked + .tab-content, background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity))); } +.textarea { + min-height: 3rem; + flex-shrink: 1; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.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))); +} + .toggle { flex-shrink: 0; --tglbg: var(--fallback-b1,oklch(var(--b1)/1)); @@ -2127,11 +2262,46 @@ input.tab:checked + .tab-content, color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); } +.badge-success { + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity))); +} + +.badge-error { + border-color: transparent; + --tw-bg-opacity: 1; + background-color: var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity))); + --tw-text-opacity: 1; + color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); +} + +.badge-ghost { + --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))); + --tw-text-opacity: 1; + color: var(--fallback-bc,oklch(var(--bc)/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-success { + --tw-text-opacity: 1; + color: var(--fallback-su,oklch(var(--su)/var(--tw-text-opacity))); +} + +.badge-outline.badge-error { + --tw-text-opacity: 1; + color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity))); +} + .btm-nav > *:where(.active) { border-top-width: 2px; --tw-bg-opacity: 1; @@ -2625,15 +2795,9 @@ 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; @@ -2682,6 +2846,14 @@ details.collapse summary::-webkit-details-marker { outline-offset: 2px; } +.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; @@ -2892,13 +3064,13 @@ 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='%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"); + -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"); } .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"); + -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"); } :where(.menu li:empty) { @@ -3113,6 +3285,10 @@ 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; @@ -3121,8 +3297,7 @@ details.collapse summary::-webkit-details-marker { .progress::-moz-progress-bar { border-radius: var(--rounded-box, 1rem); - --tw-bg-opacity: 1; - background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + background-color: currentColor; } .progress-primary::-moz-progress-bar { @@ -3156,8 +3331,7 @@ details.collapse summary::-webkit-details-marker { .progress::-webkit-progress-value { border-radius: var(--rounded-box, 1rem); - --tw-bg-opacity: 1; - background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity))); + background-color: currentColor; } .progress-primary::-webkit-progress-value { @@ -3185,6 +3359,52 @@ details.collapse summary::-webkit-details-marker { } } +.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-primary { + --chkbg: var(--p); + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); +} + +.radio-primary:focus-visible { + outline-color: var(--fallback-p,oklch(var(--p)/1)); +} + +.radio-primary:checked, + .radio-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))); +} + +.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, @@ -3597,6 +3817,42 @@ details.collapse summary::-webkit-details-marker { border-top-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); } +.textarea-bordered { + border-color: var(--fallback-bc,oklch(var(--bc)/0.2)); +} + +.textarea: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)); +} + +.textarea-disabled, + .textarea:disabled, + .textarea[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)); +} + +.textarea-disabled::-moz-placeholder, .textarea:disabled::-moz-placeholder, .textarea[disabled]::-moz-placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + +.textarea-disabled::placeholder, + .textarea:disabled::placeholder, + .textarea[disabled]::placeholder { + color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity))); + --tw-placeholder-opacity: 0.2; +} + @keyframes toast-pop { 0% { transform: scale(0.9); @@ -3904,6 +4160,11 @@ html:has(.drawer-toggle:checked) { place-items: end; } +[type="radio"].radio-xs { + height: 1rem; + width: 1rem; +} + .range-xs { height: 1rem; } @@ -4263,6 +4524,10 @@ html:has(.drawer-toggle:checked) { pointer-events: none; } +.pointer-events-auto { + pointer-events: auto; +} + .invisible { visibility: hidden; } @@ -4283,10 +4548,6 @@ html:has(.drawer-toggle:checked) { position: relative; } -.sticky { - position: sticky; -} - .inset-0 { inset: 0px; } @@ -4356,6 +4617,10 @@ html:has(.drawer-toggle:checked) { z-index: 50; } +.z-\[1\] { + z-index: 1; +} + .col-span-1 { grid-column: span 1 / span 1; } @@ -4387,6 +4652,10 @@ html:has(.drawer-toggle:checked) { margin-bottom: 0.125rem; } +.mb-1 { + margin-bottom: 0.25rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -4399,6 +4668,10 @@ html:has(.drawer-toggle:checked) { margin-bottom: 1rem; } +.mb-6 { + margin-bottom: 1.5rem; +} + .mb-8 { margin-bottom: 2rem; } @@ -4419,12 +4692,12 @@ html:has(.drawer-toggle:checked) { margin-left: 2rem; } -.mr-2 { - margin-right: 0.5rem; +.ml-auto { + margin-left: auto; } -.mt-0\.5 { - margin-top: 0.125rem; +.mr-2 { + margin-right: 0.5rem; } .mt-2 { @@ -4492,6 +4765,14 @@ html:has(.drawer-toggle:checked) { height: 3rem; } +.h-24 { + height: 6rem; +} + +.h-3 { + height: 0.75rem; +} + .h-4 { height: 1rem; } @@ -4504,6 +4785,10 @@ html:has(.drawer-toggle:checked) { height: 1.5rem; } +.h-8 { + height: 2rem; +} + .h-\[100dvh\] { height: 100dvh; } @@ -4556,6 +4841,10 @@ html:has(.drawer-toggle:checked) { width: 6rem; } +.w-3 { + width: 0.75rem; +} + .w-4 { width: 1rem; } @@ -4564,6 +4853,10 @@ html:has(.drawer-toggle:checked) { width: 1.25rem; } +.w-52 { + width: 13rem; +} + .w-6 { width: 1.5rem; } @@ -4572,6 +4865,10 @@ html:has(.drawer-toggle:checked) { width: 16rem; } +.w-8 { + width: 2rem; +} + .w-auto { width: auto; } @@ -4584,6 +4881,10 @@ html:has(.drawer-toggle:checked) { min-width: 50px; } +.max-w-3xl { + max-width: 48rem; +} + .max-w-4xl { max-width: 56rem; } @@ -4612,6 +4913,10 @@ html:has(.drawer-toggle:checked) { flex-shrink: 0; } +.shrink-0 { + flex-shrink: 0; +} + .flex-grow { flex-grow: 1; } @@ -4634,11 +4939,6 @@ html:has(.drawer-toggle:checked) { 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)); } -.-translate-y-2 { - --tw-translate-y: -0.5rem; - 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)); -} - .translate-x-0 { --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)); @@ -4649,13 +4949,18 @@ html:has(.drawer-toggle:checked) { 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)); } -.translate-y-0 { - --tw-translate-y: 0px; +.transform { 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)); } -.transform { - 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)); +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; } .cursor-not-allowed { @@ -4724,6 +5029,10 @@ html:has(.drawer-toggle:checked) { justify-content: space-between; } +.gap-1 { + gap: 0.25rem; +} + .gap-2 { gap: 0.5rem; } @@ -4751,6 +5060,12 @@ html:has(.drawer-toggle:checked) { margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); } +.space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} + .space-x-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(1rem * var(--tw-space-x-reverse)); @@ -4787,6 +5102,10 @@ html:has(.drawer-toggle:checked) { border-radius: 0.25rem; } +.rounded-box { + border-radius: var(--rounded-box, 1rem); +} + .rounded-full { border-radius: 9999px; } @@ -4799,10 +5118,6 @@ html:has(.drawer-toggle:checked) { border-radius: 0.375rem; } -.rounded-sm { - border-radius: 0.125rem; -} - .rounded-b-md { border-bottom-right-radius: 0.375rem; border-bottom-left-radius: 0.375rem; @@ -4812,8 +5127,8 @@ html:has(.drawer-toggle:checked) { border-width: 1px; } -.border-2 { - border-width: 2px; +.border-b-2 { + border-bottom-width: 2px; } .border-l-4 { @@ -4825,8 +5140,14 @@ html:has(.drawer-toggle:checked) { border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity))); } -.border-base-content\/30 { - border-color: var(--fallback-bc,oklch(var(--bc)/0.3)); +.border-base-300 { + --tw-border-opacity: 1; + border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); +} + +.border-primary { + --tw-border-opacity: 1; + border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); } .border-red-500 { @@ -4920,6 +5241,10 @@ html:has(.drawer-toggle:checked) { stroke: currentColor; } +.stroke-info { + stroke: var(--fallback-in,oklch(var(--in)/1)); +} + .stroke-white { stroke: #fff; } @@ -4998,6 +5323,11 @@ html:has(.drawer-toggle:checked) { padding-bottom: 0.5rem; } +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + .pb-0 { padding-bottom: 0px; } @@ -5126,10 +5456,6 @@ html:has(.drawer-toggle:checked) { text-transform: capitalize; } -.normal-case { - text-transform: none; -} - .italic { font-style: italic; } @@ -5147,6 +5473,10 @@ html:has(.drawer-toggle:checked) { color: var(--fallback-bc,oklch(var(--bc)/0.5)); } +.text-base-content\/60 { + color: var(--fallback-bc,oklch(var(--bc)/0.6)); +} + .text-base-content\/70 { color: var(--fallback-bc,oklch(var(--bc)/0.7)); } @@ -5156,9 +5486,9 @@ html:has(.drawer-toggle:checked) { color: rgb(0 0 0 / var(--tw-text-opacity)); } -.text-error-content { +.text-error { --tw-text-opacity: 1; - color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity))); + color: var(--fallback-er,oklch(var(--er)/var(--tw-text-opacity))); } .text-gray-100 { @@ -5232,6 +5562,12 @@ html:has(.drawer-toggle:checked) { opacity: 0.5; } +.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-lg { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); @@ -5267,26 +5603,12 @@ html:has(.drawer-toggle:checked) { transition-duration: 150ms; } -.transition-all { - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - .transition-opacity { transition-property: opacity; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; } -.duration-150 { - transition-duration: 150ms; -} - -.duration-200 { - transition-duration: 200ms; -} - .duration-300 { transition-duration: 300ms; } @@ -5318,13 +5640,13 @@ html { 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"); + -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"); } .xs\: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"); + -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"); } } @@ -5537,10 +5859,6 @@ html { margin-left: 0px; } - .xs\:block { - display: block; - } - .xs\:flex { display: flex; } @@ -5575,6 +5893,10 @@ html { margin-right: 0px; } + .sm\:mb-0 { + margin-bottom: 0px; + } + .sm\:block { display: block; } diff --git a/assets/util.js b/assets/util.js index 3d6f11ef..9aca3e19 100644 --- a/assets/util.js +++ b/assets/util.js @@ -58,17 +58,23 @@ function initializeDatePicker() { } htmx.on('htmx:afterSettle', function (event) { - if (event.detail.target.id.endsWith('-content')) { - // Find all chart containers in the newly loaded content and render them - event.detail.target.querySelectorAll('[id$="-chart"]').forEach(function (chartContainer) { - renderChart(chartContainer.id, chartContainer.dataset.chartOptions); - }); - } - - // Initialize date picker if we're on the dashboard - if (isLocationDashboard()) { - initializeDatePicker(); - } + // Skip if target or id is not available + if (!event.detail?.target) return; + + // Get the target id, ensuring it's a string + const targetId = String(event.detail.target?.id || ''); + + if (targetId.endsWith('-content')) { + // Find all chart containers in the newly loaded content and render them + event.detail.target.querySelectorAll('[id$="-chart"]').forEach(function (chartContainer) { + renderChart(chartContainer.id, chartContainer.dataset.chartOptions); + }); + } + + // Initialize date picker if we're on the dashboard + if (isLocationDashboard()) { + initializeDatePicker(); + } }); // Add document ready listener to handle initial page load diff --git a/internal/datastore/interfaces.go b/internal/datastore/interfaces.go index e6d049a0..4a857898 100644 --- a/internal/datastore/interfaces.go +++ b/internal/datastore/interfaces.go @@ -33,6 +33,7 @@ type Interface interface { GetNoteClipPath(noteID string) (string, error) DeleteNoteClipPath(noteID string) error GetClipsQualifyingForRemoval(minHours int, minClips int) ([]ClipForRemoval, error) + UpdateNote(id string, updates map[string]interface{}) error // weather data SaveDailyEvents(dailyEvents *DailyEvents) error GetDailyEvents(date string) (DailyEvents, error) @@ -550,3 +551,24 @@ func getHourRange(hour string, duration int) (startTime, endTime string) { endTime = fmt.Sprintf("%02d:00:00", endHour) return startTime, endTime } + +// UpdateNote updates specific fields of a note. It validates the input parameters +// and returns appropriate errors if the note doesn't exist or if the update fails. +func (ds *DataStore) UpdateNote(id string, updates map[string]interface{}) error { + if id == "" { + return fmt.Errorf("invalid id: must not be empty") + } + if len(updates) == 0 { + return fmt.Errorf("no updates provided") + } + + result := ds.DB.Model(&Note{}).Where("id = ?", id).Updates(updates) + if result.Error != nil { + return fmt.Errorf("failed to update note: %w", result.Error) + } + if result.RowsAffected == 0 { + return fmt.Errorf("note with id %s not found", id) + } + + return nil +} diff --git a/internal/datastore/model.go b/internal/datastore/model.go index af33bac8..53d02bd1 100644 --- a/internal/datastore/model.go +++ b/internal/datastore/model.go @@ -22,6 +22,7 @@ type Note struct { Threshold float64 Sensitivity float64 ClipName string + Verified string `gorm:"type:enum('unverified','correct','false_positive');default:'unverified'"` Comment string `gorm:"type:text"` ProcessingTime time.Duration Results []Results `gorm:"foreignKey:NoteID"` diff --git a/internal/datastore/mysql.go b/internal/datastore/mysql.go index 8bdcdf11..58d9b840 100644 --- a/internal/datastore/mysql.go +++ b/internal/datastore/mysql.go @@ -69,3 +69,10 @@ func (store *MySQLStore) Close() error { return nil } + +// UpdateNote updates specific fields of a note in MySQL +func (m *MySQLStore) UpdateNote(id string, updates map[string]interface{}) error { + return m.DB.Model(&Note{}).Where("id = ?", id).Updates(updates).Error +} + +// Save stores a note and its associated results as a single transaction in the database. diff --git a/internal/datastore/sqlite.go b/internal/datastore/sqlite.go index f819ef18..7b4cbb83 100644 --- a/internal/datastore/sqlite.go +++ b/internal/datastore/sqlite.go @@ -86,3 +86,10 @@ func (store *SQLiteStore) Close() error { return nil } + +// UpdateNote updates specific fields of a note in SQLite +func (s *SQLiteStore) UpdateNote(id string, updates map[string]interface{}) error { + return s.DB.Model(&Note{}).Where("id = ?", id).Updates(updates).Error +} + +// Save stores a note and its associated results as a single transaction in the database. diff --git a/internal/httpcontroller/handlers/dashboard.go b/internal/httpcontroller/handlers/dashboard.go index 3b11916f..e9c3a2a0 100644 --- a/internal/httpcontroller/handlers/dashboard.go +++ b/internal/httpcontroller/handlers/dashboard.go @@ -2,10 +2,8 @@ package handlers import ( - "fmt" "log" "net/http" - "os" "sort" "strconv" "time" @@ -145,36 +143,3 @@ func (h *Handlers) GetAllNotes(c echo.Context) error { return c.JSON(http.StatusOK, notes) } - -// deleteNoteHandler deletes note object from database and its associated audio file -func (h *Handlers) DeleteNote(c echo.Context) error { - noteID := c.QueryParam("id") - if noteID == "" { - return h.NewHandlerError(fmt.Errorf("empty note ID"), "Note ID is required", http.StatusBadRequest) - } - - // Retrieve the path to the audio file before deleting the note - clipPath, err := h.DS.GetNoteClipPath(noteID) - if err != nil { - return h.NewHandlerError(err, "Failed to retrieve audio clip path", http.StatusInternalServerError) - } - - // Delete the note from the database - err = h.DS.Delete(noteID) - if err != nil { - return h.NewHandlerError(err, "Failed to delete note", http.StatusInternalServerError) - } - - // If there's an associated clip, delete the file - if clipPath != "" { - err = os.Remove(clipPath) - if err != nil { - h.logError(&HandlerError{Err: err, Message: "Failed to delete audio clip", Code: http.StatusInternalServerError}) - } else { - h.logInfo(fmt.Sprintf("Deleted audio clip: %s", clipPath)) - } - } - - // Pass this struct to the template or return a success message - return c.HTML(http.StatusOK, `
Recording | + + +@@ -103,10 +113,19 @@ {{if ne $.QueryType "species"}} |
-
- {{.CommonName}}
-
+
+
+ {{.CommonName}}
+
+ {{if .Verified}}
+ {{if eq .Verified "correct"}}
+ ✓
+ {{else if eq .Verified "false_positive"}}
+ ✗
+ {{end}}
+ {{end}}
+
|
{{end}}
@@ -128,11 +147,20 @@
-
-
- {{confidence .Confidence}}
-
+
+
+ {{if .Verified}}
+ {{if eq .Verified "correct"}}
+
✓
+ {{else if eq .Verified "false_positive"}}
+ ✗
+ {{end}}
+ {{end}}
|
@@ -201,6 +229,11 @@
+
+
+ + {{template "actionMenu" .}} + | {{end}} @@ -215,7 +248,7 @@ {{if gt .CurrentPage 1}} {{else}} @@ -226,7 +259,7 @@ {{if lt .CurrentPage .TotalPages}} {{else}} @@ -243,5 +276,7 @@
---|
Date | -Time | -Common Name | - {{if .DashboardSettings.Thumbnails.Recent}} -Thumbnail | - {{end}} -Confidence | -Recording | -||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
{{.Date}} | - - -{{.Time}} | - - -- - {{ .CommonName}} - - | - - - {{if $.DashboardSettings.Thumbnails.Recent}} -
-
+
- {{range .Notes}}
-
-
-
-
- {{.Time}}
-
-
-
- {{title .CommonName}}
-
-
-
-
-
+
- Confid.
-
-
- {{confidence .Confidence}}
-
- |
+
+
+ + {{template "actionMenu" .}} + | +