diff --git a/staticfiles/css/tailwind.css b/staticfiles/css/tailwind.css index 97a1eb47..dab10d4b 100644 --- a/staticfiles/css/tailwind.css +++ b/staticfiles/css/tailwind.css @@ -1,5 +1,113 @@ +*, ::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: ; +} + /* -! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com */ /* @@ -641,112 +749,8 @@ select { outline: 1px auto -webkit-focus-ring-color; } -*, ::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: ; +.\!container { + width: 100% !important; } .container { @@ -754,30 +758,50 @@ select { } @media (min-width: 640px) { + .\!container { + max-width: 640px !important; + } + .container { max-width: 640px; } } @media (min-width: 768px) { + .\!container { + max-width: 768px !important; + } + .container { max-width: 768px; } } @media (min-width: 1024px) { + .\!container { + max-width: 1024px !important; + } + .container { max-width: 1024px; } } @media (min-width: 1280px) { + .\!container { + max-width: 1280px !important; + } + .container { max-width: 1280px; } } @media (min-width: 1536px) { + .\!container { + max-width: 1536px !important; + } + .container { max-width: 1536px; } @@ -2157,6 +2181,10 @@ select { justify-content: center; } +.visible { + visibility: visible; +} + .static { position: static; } @@ -2225,6 +2253,14 @@ select { z-index: 60; } +.col-span-1 { + grid-column: span 1 / span 1; +} + +.col-span-12 { + grid-column: span 12 / span 12; +} + .col-span-2 { grid-column: span 2 / span 2; } @@ -2237,11 +2273,26 @@ select { grid-column: 1 / -1; } +.-mx-1\.5 { + margin-left: -0.375rem; + margin-right: -0.375rem; +} + +.-my-1\.5 { + margin-top: -0.375rem; + margin-bottom: -0.375rem; +} + .mx-1 { margin-left: 0.25rem; margin-right: 0.25rem; } +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + .mx-4 { margin-left: 1rem; margin-right: 1rem; @@ -2257,6 +2308,11 @@ select { margin-right: auto; } +.my-auto { + margin-top: auto; + margin-bottom: auto; +} + .-mb-px { margin-bottom: -1px; } @@ -2265,6 +2321,10 @@ select { margin-bottom: 0.25rem; } +.mb-10 { + margin-bottom: 2.5rem; +} + .mb-12 { margin-bottom: 3rem; } @@ -2289,34 +2349,66 @@ select { margin-bottom: 2rem; } +.ml-0\.5 { + margin-left: 0.125rem; +} + .ml-1 { margin-left: 0.25rem; } +.ml-1\.5 { + margin-left: 0.375rem; +} + .ml-2 { margin-left: 0.5rem; } +.ml-3 { + margin-left: 0.75rem; +} + .ml-6 { margin-left: 1.5rem; } +.ml-auto { + margin-left: auto; +} + .mr-1 { margin-right: 0.25rem; } +.mr-1\.5 { + margin-right: 0.375rem; +} + .mr-2 { margin-right: 0.5rem; } +.mr-2\.5 { + margin-right: 0.625rem; +} + .mr-3 { margin-right: 0.75rem; } +.mt-0\.5 { + margin-top: 0.125rem; +} + .mt-1 { margin-top: 0.25rem; } +.mt-1\.5 { + margin-top: 0.375rem; +} + .mt-2 { margin-top: 0.5rem; } @@ -2401,6 +2493,10 @@ select { height: 300px; } +.h-auto { + height: auto; +} + .h-full { height: 100%; } @@ -2413,10 +2509,6 @@ select { max-height: 90vh; } -.min-h-\[calc\(100vh-16rem\)\] { - min-height: calc(100vh - 16rem); -} - .min-h-screen { min-height: 100vh; } @@ -2462,6 +2554,10 @@ select { width: 100%; } +.min-w-\[200px\] { + min-width: 200px; +} + .max-w-2xl { max-width: 42rem; } @@ -2474,10 +2570,18 @@ select { max-width: 56rem; } +.max-w-6xl { + max-width: 72rem; +} + .max-w-7xl { max-width: 80rem; } +.max-w-\[800px\] { + max-width: 800px; +} + .max-w-lg { max-width: 32rem; } @@ -2490,10 +2594,18 @@ select { max-width: none; } +.max-w-xs { + max-width: 20rem; +} + .flex-1 { flex: 1 1 0%; } +.flex-shrink-0 { + flex-shrink: 0; +} + .flex-grow { flex-grow: 1; } @@ -2513,6 +2625,11 @@ select { 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-full { + --tw-translate-y: 100%; + 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)); +} + .scale-100 { --tw-scale-x: 1; --tw-scale-y: 1; @@ -2543,18 +2660,26 @@ select { cursor: pointer; } -.resize { - resize: both; +.resize-none { + resize: none; } .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } +.grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); +} + .grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + .grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } @@ -2591,10 +2716,18 @@ select { justify-content: space-between; } +.gap-1 { + gap: 0.25rem; +} + .gap-2 { gap: 0.5rem; } +.gap-3 { + gap: 0.75rem; +} + .gap-4 { gap: 1rem; } @@ -2603,12 +2736,24 @@ select { gap: 1.5rem; } +.space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} + .space-x-2 > :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))); } +.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)); @@ -2653,6 +2798,10 @@ select { overflow: hidden; } +.overflow-y-auto { + overflow-y: auto; +} + .rounded { border-radius: 0.25rem; } @@ -2669,16 +2818,31 @@ select { border-radius: 0.375rem; } +.rounded-b-lg { + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + .rounded-l-lg { border-top-left-radius: 0.5rem; border-bottom-left-radius: 0.5rem; } +.rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + .rounded-r-lg { border-top-right-radius: 0.5rem; border-bottom-right-radius: 0.5rem; } +.rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + .rounded-t-lg { border-top-left-radius: 0.5rem; border-top-right-radius: 0.5rem; @@ -2712,6 +2876,10 @@ select { border-style: dashed; } +.border-blue-200\/50 { + border-color: rgb(191 219 254 / 0.5); +} + .border-blue-500 { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); @@ -2736,11 +2904,21 @@ select { border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.border-gray-700 { + --tw-border-opacity: 1; + border-color: rgb(55 65 81 / var(--tw-border-opacity)); +} + .border-green-500 { --tw-border-opacity: 1; border-color: rgb(34 197 94 / var(--tw-border-opacity)); } +.border-primary { + --tw-border-opacity: 1; + border-color: rgb(79 70 229 / var(--tw-border-opacity)); +} + .border-red-400 { --tw-border-opacity: 1; border-color: rgb(248 113 113 / var(--tw-border-opacity)); @@ -2788,6 +2966,15 @@ select { background-color: rgb(37 99 235 / var(--tw-bg-opacity)); } +.bg-blue-900\/40 { + background-color: rgb(30 58 138 / 0.4); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + .bg-gray-200 { --tw-bg-opacity: 1; background-color: rgb(229 231 235 / var(--tw-bg-opacity)); @@ -2798,6 +2985,25 @@ select { background-color: rgb(249 250 251 / var(--tw-bg-opacity)); } +.bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.bg-gray-900 { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.bg-gray-900\/80 { + background-color: rgb(17 24 39 / 0.8); +} + .bg-green-100 { --tw-bg-opacity: 1; background-color: rgb(220 252 231 / var(--tw-bg-opacity)); @@ -2813,6 +3019,10 @@ select { background-color: rgb(22 163 74 / var(--tw-bg-opacity)); } +.bg-green-900\/40 { + background-color: rgb(20 83 45 / 0.4); +} + .bg-red-100 { --tw-bg-opacity: 1; background-color: rgb(254 226 226 / var(--tw-bg-opacity)); @@ -2828,6 +3038,10 @@ select { background-color: rgb(220 38 38 / var(--tw-bg-opacity)); } +.bg-red-900\/40 { + background-color: rgb(127 29 29 / 0.4); +} + .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -2837,6 +3051,10 @@ select { background-color: rgb(255 255 255 / 0.1); } +.bg-white\/80 { + background-color: rgb(255 255 255 / 0.8); +} + .bg-white\/90 { background-color: rgb(255 255 255 / 0.9); } @@ -2856,10 +3074,18 @@ select { background-color: rgb(202 138 4 / var(--tw-bg-opacity)); } +.bg-yellow-900\/40 { + background-color: rgb(113 63 18 / 0.4); +} + .bg-opacity-50 { --tw-bg-opacity: 0.5; } +.bg-opacity-75 { + --tw-bg-opacity: 0.75; +} + .bg-opacity-90 { --tw-bg-opacity: 0.9; } @@ -2902,11 +3128,28 @@ select { background-clip: text; } +.object-contain { + -o-object-fit: contain; + object-fit: contain; +} + .object-cover { -o-object-fit: cover; object-fit: cover; } +.p-0\.5 { + padding: 0.125rem; +} + +.p-1 { + padding: 0.25rem; +} + +.p-1\.5 { + padding: 0.375rem; +} + .p-2 { padding: 0.5rem; } @@ -2972,6 +3215,11 @@ select { padding-bottom: 0.5rem; } +.py-2\.5 { + padding-top: 0.625rem; + padding-bottom: 0.625rem; +} + .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; @@ -2996,6 +3244,10 @@ select { padding-bottom: 1rem; } +.text-left { + text-align: left; +} + .text-center { text-align: center; } @@ -3015,6 +3267,11 @@ select { line-height: 2.5rem; } +.text-5xl { + font-size: 3rem; + line-height: 1; +} + .text-lg { font-size: 1.125rem; line-height: 1.75rem; @@ -3043,10 +3300,31 @@ select { font-weight: 500; } +.font-normal { + font-weight: 400; +} + .font-semibold { font-weight: 600; } +.uppercase { + text-transform: uppercase; +} + +.lowercase { + text-transform: lowercase; +} + +.leading-tight { + line-height: 1.25; +} + +.text-blue-400 { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity)); +} + .text-blue-500 { --tw-text-opacity: 1; color: rgb(59 130 246 / var(--tw-text-opacity)); @@ -3057,16 +3335,21 @@ select { color: rgb(37 99 235 / var(--tw-text-opacity)); } -.text-blue-700 { - --tw-text-opacity: 1; - color: rgb(29 78 216 / var(--tw-text-opacity)); -} - .text-blue-800 { --tw-text-opacity: 1; color: rgb(30 64 175 / var(--tw-text-opacity)); } +.text-blue-900 { + --tw-text-opacity: 1; + color: rgb(30 58 138 / var(--tw-text-opacity)); +} + +.text-gray-200 { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); +} + .text-gray-300 { --tw-text-opacity: 1; color: rgb(209 213 219 / var(--tw-text-opacity)); @@ -3097,6 +3380,21 @@ select { color: rgb(17 24 39 / var(--tw-text-opacity)); } +.text-green-400 { + --tw-text-opacity: 1; + color: rgb(74 222 128 / var(--tw-text-opacity)); +} + +.text-green-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.text-green-700 { + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity)); +} + .text-green-800 { --tw-text-opacity: 1; color: rgb(22 101 52 / var(--tw-text-opacity)); @@ -3107,6 +3405,11 @@ select { color: rgb(79 70 229 / var(--tw-text-opacity)); } +.text-red-400 { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); +} + .text-red-500 { --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); @@ -3127,6 +3430,11 @@ select { color: rgb(153 27 27 / var(--tw-text-opacity)); } +.text-sky-900 { + --tw-text-opacity: 1; + color: rgb(12 74 110 / var(--tw-text-opacity)); +} + .text-transparent { color: transparent; } @@ -3151,16 +3459,16 @@ select { color: rgb(202 138 4 / var(--tw-text-opacity)); } +.text-yellow-700 { + --tw-text-opacity: 1; + color: rgb(161 98 7 / var(--tw-text-opacity)); +} + .text-yellow-800 { --tw-text-opacity: 1; color: rgb(133 77 14 / var(--tw-text-opacity)); } -.text-green-600 { - --tw-text-opacity: 1; - color: rgb(22 163 74 / var(--tw-text-opacity)); -} - .opacity-0 { opacity: 0; } @@ -3224,6 +3532,12 @@ select { backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); } +.backdrop-blur-sm { + --tw-backdrop-blur: blur(4px); + -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); +} + .transition { 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; @@ -3250,6 +3564,12 @@ select { transition-duration: 150ms; } +.transition-shadow { + transition-property: box-shadow; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + .transition-transform { transition-property: transform; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); @@ -3310,22 +3630,33 @@ select { 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)); } -.hover\:translate-x-2:hover { - --tw-translate-x: 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)); -} - .hover\:scale-105:hover { --tw-scale-x: 1.05; --tw-scale-y: 1.05; 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)); } +.hover\:scale-\[1\.02\]:hover { + --tw-scale-x: 1.02; + --tw-scale-y: 1.02; + 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)); +} + .hover\:border-gray-300:hover { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.hover\:bg-blue-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); +} + +.hover\:bg-blue-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); +} + .hover\:bg-blue-700:hover { --tw-bg-opacity: 1; background-color: rgb(29 78 216 / var(--tw-bg-opacity)); @@ -3336,6 +3667,11 @@ select { background-color: rgb(243 244 246 / var(--tw-bg-opacity)); } +.hover\:bg-gray-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + .hover\:bg-gray-300:hover { --tw-bg-opacity: 1; background-color: rgb(209 213 219 / var(--tw-bg-opacity)); @@ -3346,9 +3682,9 @@ select { background-color: rgb(249 250 251 / var(--tw-bg-opacity)); } -.hover\:bg-green-700:hover { +.hover\:bg-green-500:hover { --tw-bg-opacity: 1; - background-color: rgb(21 128 61 / var(--tw-bg-opacity)); + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); } .hover\:bg-red-50:hover { @@ -3356,6 +3692,11 @@ select { background-color: rgb(254 242 242 / var(--tw-bg-opacity)); } +.hover\:bg-red-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); +} + .hover\:bg-red-700:hover { --tw-bg-opacity: 1; background-color: rgb(185 28 28 / var(--tw-bg-opacity)); @@ -3365,6 +3706,11 @@ select { background-color: rgb(255 255 255 / 0.2); } +.hover\:bg-yellow-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(234 179 8 / var(--tw-bg-opacity)); +} + .hover\:bg-yellow-600:hover { --tw-bg-opacity: 1; background-color: rgb(202 138 4 / var(--tw-bg-opacity)); @@ -3385,6 +3731,16 @@ select { color: rgb(29 78 216 / var(--tw-text-opacity)); } +.hover\:text-blue-800:hover { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity)); +} + +.hover\:text-blue-900:hover { + --tw-text-opacity: 1; + color: rgb(30 58 138 / var(--tw-text-opacity)); +} + .hover\:text-gray-300:hover { --tw-text-opacity: 1; color: rgb(209 213 219 / var(--tw-text-opacity)); @@ -3400,6 +3756,11 @@ select { color: rgb(55 65 81 / var(--tw-text-opacity)); } +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + .hover\:text-primary:hover { --tw-text-opacity: 1; color: rgb(79 70 229 / var(--tw-text-opacity)); @@ -3409,10 +3770,27 @@ select { color: rgb(79 70 229 / 0.8); } +.hover\:text-sky-800:hover { + --tw-text-opacity: 1; + color: rgb(7 89 133 / var(--tw-text-opacity)); +} + .hover\:underline:hover { text-decoration-line: underline; } +.hover\:shadow-md:hover { + --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); +} + +.hover\:shadow-xl:hover { + --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); +} + .focus\:border-blue-500:focus { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); @@ -3446,6 +3824,10 @@ select { --tw-ring-offset-width: 2px; } +.disabled\:opacity-50:disabled { + opacity: 0.5; +} + .group:hover .group-hover\:scale-105 { --tw-scale-x: 1.05; --tw-scale-y: 1.05; @@ -3456,6 +3838,10 @@ select { opacity: 1; } +.dark\:border-blue-700\/50:is(.dark *) { + border-color: rgb(29 78 216 / 0.5); +} + .dark\:border-gray-600:is(.dark *) { --tw-border-opacity: 1; border-color: rgb(75 85 99 / var(--tw-border-opacity)); @@ -3484,13 +3870,12 @@ select { background-color: rgb(29 78 216 / var(--tw-bg-opacity)); } -.dark\:bg-blue-900:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(30 58 138 / var(--tw-bg-opacity)); +.dark\:bg-blue-900\/30:is(.dark *) { + background-color: rgb(30 58 138 / 0.3); } -.dark\:bg-blue-900\/50:is(.dark *) { - background-color: rgb(30 58 138 / 0.5); +.dark\:bg-blue-900\/40:is(.dark *) { + background-color: rgb(30 58 138 / 0.4); } .dark\:bg-gray-600:is(.dark *) { @@ -3516,19 +3901,23 @@ select { background-color: rgb(31 41 55 / 0.9); } +.dark\:bg-gray-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + +.dark\:bg-gray-900\/80:is(.dark *) { + background-color: rgb(17 24 39 / 0.8); +} + .dark\:bg-green-200:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(187 247 208 / var(--tw-bg-opacity)); } -.dark\:bg-green-500:is(.dark *) { +.dark\:bg-green-700:is(.dark *) { --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); -} - -.dark\:bg-green-900:is(.dark *) { - --tw-bg-opacity: 1; - background-color: rgb(20 83 45 / var(--tw-bg-opacity)); + background-color: rgb(21 128 61 / var(--tw-bg-opacity)); } .dark\:bg-red-200:is(.dark *) { @@ -3536,14 +3925,14 @@ select { background-color: rgb(254 202 202 / var(--tw-bg-opacity)); } -.dark\:bg-red-500:is(.dark *) { +.dark\:bg-red-700:is(.dark *) { --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); } -.dark\:bg-red-900:is(.dark *) { +.dark\:bg-yellow-200:is(.dark *) { --tw-bg-opacity: 1; - background-color: rgb(127 29 29 / var(--tw-bg-opacity)); + background-color: rgb(254 240 138 / var(--tw-bg-opacity)); } .dark\:bg-yellow-400\/30:is(.dark *) { @@ -3555,9 +3944,13 @@ select { background-color: rgb(202 138 4 / var(--tw-bg-opacity)); } -.dark\:bg-yellow-900:is(.dark *) { +.dark\:bg-yellow-700:is(.dark *) { --tw-bg-opacity: 1; - background-color: rgb(113 63 18 / var(--tw-bg-opacity)); + background-color: rgb(161 98 7 / var(--tw-bg-opacity)); +} + +.dark\:bg-yellow-900\/50:is(.dark *) { + background-color: rgb(113 63 18 / 0.5); } .dark\:from-gray-950:is(.dark *) { @@ -3580,6 +3973,11 @@ select { color: rgb(191 219 254 / var(--tw-text-opacity)); } +.dark\:text-blue-300:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity)); +} + .dark\:text-blue-400:is(.dark *) { --tw-text-opacity: 1; color: rgb(96 165 250 / var(--tw-text-opacity)); @@ -3620,9 +4018,19 @@ select { color: rgb(75 85 99 / var(--tw-text-opacity)); } -.dark\:text-green-200:is(.dark *) { +.dark\:text-green-400:is(.dark *) { --tw-text-opacity: 1; - color: rgb(187 247 208 / var(--tw-text-opacity)); + color: rgb(74 222 128 / var(--tw-text-opacity)); +} + +.dark\:text-green-50:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(240 253 244 / var(--tw-text-opacity)); +} + +.dark\:text-green-800:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(22 101 52 / var(--tw-text-opacity)); } .dark\:text-green-900:is(.dark *) { @@ -3630,11 +4038,6 @@ select { color: rgb(20 83 45 / var(--tw-text-opacity)); } -.dark\:text-red-200:is(.dark *) { - --tw-text-opacity: 1; - color: rgb(254 202 202 / var(--tw-text-opacity)); -} - .dark\:text-red-400:is(.dark *) { --tw-text-opacity: 1; color: rgb(248 113 113 / var(--tw-text-opacity)); @@ -3650,6 +4053,11 @@ select { color: rgb(127 29 29 / var(--tw-text-opacity)); } +.dark\:text-sky-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(56 189 248 / var(--tw-text-opacity)); +} + .dark\:text-white:is(.dark *) { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); @@ -3675,9 +4083,9 @@ select { color: rgb(254 252 232 / var(--tw-text-opacity)); } -.dark\:text-green-400:is(.dark *) { +.dark\:text-yellow-800:is(.dark *) { --tw-text-opacity: 1; - color: rgb(74 222 128 / var(--tw-text-opacity)); + color: rgb(133 77 14 / var(--tw-text-opacity)); } .dark\:ring-1:is(.dark *) { @@ -3694,6 +4102,11 @@ select { --tw-ring-color: rgb(250 204 21 / 0.3); } +.dark\:hover\:bg-blue-500:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); +} + .dark\:hover\:bg-blue-600:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(37 99 235 / var(--tw-bg-opacity)); @@ -3704,6 +4117,10 @@ select { background-color: rgb(30 64 175 / var(--tw-bg-opacity)); } +.dark\:hover\:bg-blue-900\/40:hover:is(.dark *) { + background-color: rgb(30 58 138 / 0.4); +} + .dark\:hover\:bg-gray-500:hover:is(.dark *) { --tw-bg-opacity: 1; background-color: rgb(107 114 128 / var(--tw-bg-opacity)); @@ -3733,6 +4150,11 @@ select { background-color: rgb(127 29 29 / 0.2); } +.dark\:hover\:bg-yellow-600:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(202 138 4 / var(--tw-bg-opacity)); +} + .dark\:hover\:text-blue-300:hover:is(.dark *) { --tw-text-opacity: 1; color: rgb(147 197 253 / var(--tw-text-opacity)); @@ -3743,6 +4165,11 @@ select { color: rgb(96 165 250 / var(--tw-text-opacity)); } +.dark\:hover\:text-gray-200:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); +} + .dark\:hover\:text-gray-300:hover:is(.dark *) { --tw-text-opacity: 1; color: rgb(209 213 219 / var(--tw-text-opacity)); @@ -3753,11 +4180,61 @@ select { color: rgb(79 70 229 / var(--tw-text-opacity)); } +.dark\:hover\:text-sky-300:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(125 211 252 / var(--tw-text-opacity)); +} + +.dark\:hover\:text-white:hover:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + @media (min-width: 640px) { + .sm\:col-span-3 { + grid-column: span 3 / span 3; + } + + .sm\:col-span-4 { + grid-column: span 4 / span 4; + } + + .sm\:col-span-8 { + grid-column: span 8 / span 8; + } + + .sm\:col-span-9 { + grid-column: span 9 / span 9; + } + + .sm\:mb-16 { + margin-bottom: 4rem; + } + + .sm\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .sm\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .sm\:flex-row { + flex-direction: row; + } + + .sm\:items-end { + align-items: flex-end; + } + + .sm\:items-center { + align-items: center; + } + .sm\:space-x-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(1rem * var(--tw-space-x-reverse)); @@ -3769,11 +4246,63 @@ select { margin-right: calc(1.5rem * var(--tw-space-x-reverse)); margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); } + + .sm\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .sm\:text-2xl { + font-size: 1.5rem; + line-height: 2rem; + } + + .sm\:text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; + } + + .sm\:text-base { + font-size: 1rem; + line-height: 1.5rem; + } + + .sm\:text-lg { + font-size: 1.125rem; + line-height: 1.75rem; + } + + .sm\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } + + .sm\:text-xl { + font-size: 1.25rem; + line-height: 1.75rem; + } + + .sm\:text-xs { + font-size: 0.75rem; + line-height: 1rem; + } } @media (min-width: 768px) { - .md\:mt-0 { - margin-top: 0px; + .md\:col-span-1 { + grid-column: span 1 / span 1; + } + + .md\:col-span-2 { + grid-column: span 2 / span 2; + } + + .md\:mb-8 { + margin-bottom: 2rem; + } + + .md\:h-\[140px\] { + height: 140px; } .md\:grid-cols-2 { @@ -3796,6 +4325,17 @@ select { align-items: center; } + .md\: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))); + } + + .md\:py-12 { + padding-top: 3rem; + padding-bottom: 3rem; + } + .md\:text-2xl { font-size: 1.5rem; line-height: 2rem; @@ -3805,6 +4345,11 @@ select { font-size: 3rem; line-height: 1; } + + .md\:text-lg { + font-size: 1.125rem; + line-height: 1.75rem; + } } @media (min-width: 1024px) { @@ -3816,14 +4361,6 @@ select { grid-column: span 2 / span 2; } - .lg\:mb-0 { - margin-bottom: 0px; - } - - .lg\:mr-6 { - margin-right: 1.5rem; - } - .lg\:flex { display: flex; } @@ -3832,14 +4369,6 @@ select { display: none; } - .lg\:w-1\/3 { - width: 33.333333%; - } - - .lg\:flex-1 { - flex: 1 1 0%; - } - .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } @@ -3848,20 +4377,38 @@ select { grid-template-columns: repeat(4, minmax(0, 1fr)); } - .lg\:flex-row { - flex-direction: row; + .lg\:px-8 { + padding-left: 2rem; + padding-right: 2rem; } - .lg\:items-start { - align-items: flex-start; + .lg\:text-2xl { + font-size: 1.5rem; + line-height: 2rem; } - .lg\:justify-between { - justify-content: space-between; + .lg\:text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; + } + + .lg\:text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; } .lg\:text-6xl { font-size: 3.75rem; line-height: 1; } + + .lg\:text-base { + font-size: 1rem; + line-height: 1.5rem; + } + + .lg\:text-lg { + font-size: 1.125rem; + line-height: 1.75rem; + } } diff --git a/staticfiles/gis/css/ol3.css b/staticfiles/gis/css/ol3.css new file mode 100644 index 00000000..ac8f0a8e --- /dev/null +++ b/staticfiles/gis/css/ol3.css @@ -0,0 +1,39 @@ +.dj_map_wrapper { + position: relative; + float: left; +} +html[dir="rtl"] .dj_map_wrapper { + float: right; +} + +.switch-type { + background-repeat: no-repeat; + cursor: pointer; + top: 0.5em; + width: 22px; + height: 20px; +} + +.type-Point { + background-image: url("../img/draw_point_off.svg"); + right: 5px; +} +.type-Point.type-active { + background-image: url("../img/draw_point_on.svg"); +} + +.type-LineString { + background-image: url("../img/draw_line_off.svg"); + right: 30px; +} +.type-LineString.type-active { + background-image: url("../img/draw_line_on.svg"); +} + +.type-Polygon { + background-image: url("../img/draw_polygon_off.svg"); + right: 55px; +} +.type-Polygon.type-active { + background-image: url("../img/draw_polygon_on.svg"); +} diff --git a/staticfiles/gis/img/draw_line_off.svg b/staticfiles/gis/img/draw_line_off.svg new file mode 100644 index 00000000..c403af60 --- /dev/null +++ b/staticfiles/gis/img/draw_line_off.svg @@ -0,0 +1 @@ + diff --git a/staticfiles/gis/img/draw_line_on.svg b/staticfiles/gis/img/draw_line_on.svg new file mode 100644 index 00000000..5eb0d6d1 --- /dev/null +++ b/staticfiles/gis/img/draw_line_on.svg @@ -0,0 +1 @@ + diff --git a/staticfiles/gis/img/draw_point_off.svg b/staticfiles/gis/img/draw_point_off.svg new file mode 100644 index 00000000..5a290342 --- /dev/null +++ b/staticfiles/gis/img/draw_point_off.svg @@ -0,0 +1 @@ + diff --git a/staticfiles/gis/img/draw_point_on.svg b/staticfiles/gis/img/draw_point_on.svg new file mode 100644 index 00000000..f006524a --- /dev/null +++ b/staticfiles/gis/img/draw_point_on.svg @@ -0,0 +1 @@ + diff --git a/staticfiles/gis/img/draw_polygon_off.svg b/staticfiles/gis/img/draw_polygon_off.svg new file mode 100644 index 00000000..b02dd357 --- /dev/null +++ b/staticfiles/gis/img/draw_polygon_off.svg @@ -0,0 +1 @@ + diff --git a/staticfiles/gis/img/draw_polygon_on.svg b/staticfiles/gis/img/draw_polygon_on.svg new file mode 100644 index 00000000..303a02a2 --- /dev/null +++ b/staticfiles/gis/img/draw_polygon_on.svg @@ -0,0 +1 @@ + diff --git a/staticfiles/gis/js/OLMapWidget.js b/staticfiles/gis/js/OLMapWidget.js new file mode 100644 index 00000000..a545036c --- /dev/null +++ b/staticfiles/gis/js/OLMapWidget.js @@ -0,0 +1,233 @@ +/* global ol */ +'use strict'; +class GeometryTypeControl extends ol.control.Control { + // Map control to switch type when geometry type is unknown + constructor(opt_options) { + const options = opt_options || {}; + + const element = document.createElement('div'); + element.className = 'switch-type type-' + options.type + ' ol-control ol-unselectable'; + if (options.active) { + element.classList.add("type-active"); + } + + super({ + element: element, + target: options.target + }); + const self = this; + const switchType = function(e) { + e.preventDefault(); + if (options.widget.currentGeometryType !== self) { + options.widget.map.removeInteraction(options.widget.interactions.draw); + options.widget.interactions.draw = new ol.interaction.Draw({ + features: options.widget.featureCollection, + type: options.type + }); + options.widget.map.addInteraction(options.widget.interactions.draw); + options.widget.currentGeometryType.element.classList.remove('type-active'); + options.widget.currentGeometryType = self; + element.classList.add("type-active"); + } + }; + + element.addEventListener('click', switchType, false); + element.addEventListener('touchstart', switchType, false); + } +} + +// TODO: allow deleting individual features (#8972) +class MapWidget { + constructor(options) { + this.map = null; + this.interactions = {draw: null, modify: null}; + this.typeChoices = false; + this.ready = false; + + // Default options + this.options = { + default_lat: 0, + default_lon: 0, + default_zoom: 12, + is_collection: options.geom_name.includes('Multi') || options.geom_name.includes('Collection') + }; + + // Altering using user-provided options + for (const property in options) { + if (Object.hasOwn(options, property)) { + this.options[property] = options[property]; + } + } + if (!options.base_layer) { + this.options.base_layer = new ol.layer.Tile({source: new ol.source.OSM()}); + } + + this.map = this.createMap(); + this.featureCollection = new ol.Collection(); + this.featureOverlay = new ol.layer.Vector({ + map: this.map, + source: new ol.source.Vector({ + features: this.featureCollection, + useSpatialIndex: false // improve performance + }), + updateWhileAnimating: true, // optional, for instant visual feedback + updateWhileInteracting: true // optional, for instant visual feedback + }); + + // Populate and set handlers for the feature container + const self = this; + this.featureCollection.on('add', function(event) { + const feature = event.element; + feature.on('change', function() { + self.serializeFeatures(); + }); + if (self.ready) { + self.serializeFeatures(); + if (!self.options.is_collection) { + self.disableDrawing(); // Only allow one feature at a time + } + } + }); + + const initial_value = document.getElementById(this.options.id).value; + if (initial_value) { + const jsonFormat = new ol.format.GeoJSON(); + const features = jsonFormat.readFeatures('{"type": "Feature", "geometry": ' + initial_value + '}'); + const extent = ol.extent.createEmpty(); + features.forEach(function(feature) { + this.featureOverlay.getSource().addFeature(feature); + ol.extent.extend(extent, feature.getGeometry().getExtent()); + }, this); + // Center/zoom the map + this.map.getView().fit(extent, {minResolution: 1}); + } else { + this.map.getView().setCenter(this.defaultCenter()); + } + this.createInteractions(); + if (initial_value && !this.options.is_collection) { + this.disableDrawing(); + } + const clearNode = document.getElementById(this.map.getTarget()).nextElementSibling; + if (clearNode.classList.contains('clear_features')) { + clearNode.querySelector('a').addEventListener('click', (ev) => { + ev.preventDefault(); + self.clearFeatures(); + }); + } + this.ready = true; + } + + createMap() { + return new ol.Map({ + target: this.options.map_id, + layers: [this.options.base_layer], + view: new ol.View({ + zoom: this.options.default_zoom + }) + }); + } + + createInteractions() { + // Initialize the modify interaction + this.interactions.modify = new ol.interaction.Modify({ + features: this.featureCollection, + deleteCondition: function(event) { + return ol.events.condition.shiftKeyOnly(event) && + ol.events.condition.singleClick(event); + } + }); + + // Initialize the draw interaction + let geomType = this.options.geom_name; + if (geomType === "Geometry" || geomType === "GeometryCollection") { + // Default to Point, but create icons to switch type + geomType = "Point"; + this.currentGeometryType = new GeometryTypeControl({widget: this, type: "Point", active: true}); + this.map.addControl(this.currentGeometryType); + this.map.addControl(new GeometryTypeControl({widget: this, type: "LineString", active: false})); + this.map.addControl(new GeometryTypeControl({widget: this, type: "Polygon", active: false})); + this.typeChoices = true; + } + this.interactions.draw = new ol.interaction.Draw({ + features: this.featureCollection, + type: geomType + }); + + this.map.addInteraction(this.interactions.draw); + this.map.addInteraction(this.interactions.modify); + } + + defaultCenter() { + const center = [this.options.default_lon, this.options.default_lat]; + if (this.options.map_srid) { + return ol.proj.transform(center, 'EPSG:4326', this.map.getView().getProjection()); + } + return center; + } + + enableDrawing() { + this.interactions.draw.setActive(true); + if (this.typeChoices) { + // Show geometry type icons + const divs = document.getElementsByClassName("switch-type"); + for (let i = 0; i !== divs.length; i++) { + divs[i].style.visibility = "visible"; + } + } + } + + disableDrawing() { + if (this.interactions.draw) { + this.interactions.draw.setActive(false); + if (this.typeChoices) { + // Hide geometry type icons + const divs = document.getElementsByClassName("switch-type"); + for (let i = 0; i !== divs.length; i++) { + divs[i].style.visibility = "hidden"; + } + } + } + } + + clearFeatures() { + this.featureCollection.clear(); + // Empty textarea widget + document.getElementById(this.options.id).value = ''; + this.enableDrawing(); + } + + serializeFeatures() { + // Three use cases: GeometryCollection, multigeometries, and single geometry + let geometry = null; + const features = this.featureOverlay.getSource().getFeatures(); + if (this.options.is_collection) { + if (this.options.geom_name === "GeometryCollection") { + const geometries = []; + for (let i = 0; i < features.length; i++) { + geometries.push(features[i].getGeometry()); + } + geometry = new ol.geom.GeometryCollection(geometries); + } else { + geometry = features[0].getGeometry().clone(); + for (let j = 1; j < features.length; j++) { + switch (geometry.getType()) { + case "MultiPoint": + geometry.appendPoint(features[j].getGeometry().getPoint(0)); + break; + case "MultiLineString": + geometry.appendLineString(features[j].getGeometry().getLineString(0)); + break; + case "MultiPolygon": + geometry.appendPolygon(features[j].getGeometry().getPolygon(0)); + } + } + } + } else { + if (features[0]) { + geometry = features[0].getGeometry(); + } + } + const jsonFormat = new ol.format.GeoJSON(); + document.getElementById(this.options.id).value = jsonFormat.writeGeometry(geometry); + } +} diff --git a/staticfiles/js/photo-gallery.js b/staticfiles/js/photo-gallery.js new file mode 100644 index 00000000..2705dcab --- /dev/null +++ b/staticfiles/js/photo-gallery.js @@ -0,0 +1,91 @@ +document.addEventListener('alpine:init', () => { + Alpine.data('photoDisplay', ({ photos, contentType, objectId, csrfToken, uploadUrl }) => ({ + photos, + fullscreenPhoto: null, + uploading: false, + uploadProgress: 0, + error: null, + showSuccess: false, + + showFullscreen(photo) { + this.fullscreenPhoto = photo; + }, + + async handleFileSelect(event) { + const files = Array.from(event.target.files); + if (!files.length) { + return; + } + + this.uploading = true; + this.uploadProgress = 0; + this.error = null; + this.showSuccess = false; + + const totalFiles = files.length; + let completedFiles = 0; + + for (const file of files) { + const formData = new FormData(); + formData.append('image', file); + formData.append('app_label', contentType.split('.')[0]); + formData.append('model', contentType.split('.')[1]); + formData.append('object_id', objectId); + + try { + const response = await fetch(uploadUrl, { + method: 'POST', + headers: { + 'X-CSRFToken': csrfToken, + }, + body: formData + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || 'Upload failed'); + } + + const photo = await response.json(); + this.photos.push(photo); + completedFiles++; + this.uploadProgress = (completedFiles / totalFiles) * 100; + } catch (err) { + this.error = err.message || 'Failed to upload photo. Please try again.'; + console.error('Upload error:', err); + break; + } + } + + this.uploading = false; + event.target.value = ''; // Reset file input + + if (!this.error) { + this.showSuccess = true; + setTimeout(() => { + this.showSuccess = false; + }, 3000); + } + }, + + async sharePhoto(photo) { + if (navigator.share) { + try { + await navigator.share({ + title: photo.caption || 'Shared photo', + url: photo.url + }); + } catch (err) { + if (err.name !== 'AbortError') { + console.error('Error sharing:', err); + } + } + } else { + // Fallback: copy URL to clipboard + navigator.clipboard.writeText(photo.url) + .then(() => alert('Photo URL copied to clipboard!')) + .catch(err => console.error('Error copying to clipboard:', err)); + } + } + })); +});