From f3c818a48f072601ac91ba587816e61bfb0efefd Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sat, 21 Mar 2026 19:14:51 +0000 Subject: [PATCH] Add CSS via SCSS to the frontend build pipeline --- build.js | 10 +- default.nix | 22 +- html/template/sync/layout/authenticated.html | 78 --- package.json | 5 + pnpm-lock.yaml | 558 ++++++++++++++++++- pnpm-workspace.yaml | 1 + static/gen.go | 4 +- ts/global.d.ts | 12 + ts/main.ts | 50 ++ ts/sidebar.ts | 75 +++ ts/style/sidebar.scss | 140 +++++ ts/style/style.scss | 63 +++ 12 files changed, 924 insertions(+), 94 deletions(-) create mode 100644 ts/global.d.ts create mode 100644 ts/sidebar.ts create mode 100644 ts/style/sidebar.scss create mode 100644 ts/style/style.scss diff --git a/build.js b/build.js index 032f7d82..888f3f67 100644 --- a/build.js +++ b/build.js @@ -1,5 +1,6 @@ import esbuild from "esbuild"; import vue from "esbuild-plugin-vue3"; +import { sassPlugin } from "esbuild-sass-plugin"; const args = process.argv.slice(2); const watch = args.includes("--watch"); @@ -9,7 +10,14 @@ const config = { entryPoints: ["ts/main.ts"], bundle: true, format: "esm", - plugins: [vue()], + plugins: [ + sassPlugin({ + quietDeps: true, + silenceDeprecations: ["import"], // silence known issue with Bootstrap #40962 + type: "css", + }), + vue(), + ], define: { __VUE_OPTIONS_API__: "true", __VUE_PROD_DEVTOOLS__: "false", diff --git a/default.nix b/default.nix index 6a5e0947..da392bfc 100644 --- a/default.nix +++ b/default.nix @@ -39,21 +39,21 @@ pnpm config set store-dir $HOME/.pnpm-store # Compile SCSS SASS_SRC_DIR="./scss" -CSS_OUTPUT_DIR="./static/gen/css" +GEN_OUTPUT_DIR="./static/gen" -mkdir -p "$CSS_OUTPUT_DIR" +mkdir -p "$GEN_OUTPUT_DIR" -echo "Compiling $SASS_SRC_DIR/style.scss to $CSS_OUTPUT_DIR/style.css..." -sass --style=compressed --trace "$SASS_SRC_DIR/style.scss":"$CSS_OUTPUT_DIR/style.css" +echo "Compiling $SASS_SRC_DIR/style.scss to $GEN_OUTPUT_DIR/main.css..." +sass --style=compressed --trace "$SASS_SRC_DIR/style.scss":"$GEN_OUTPUT_DIR/main.css" # Generate hash and rename style -STYLE_HASH=$(sha256sum "$CSS_OUTPUT_DIR/style.css" | cut -c1-12) -mv "$CSS_OUTPUT_DIR/style.css" "$CSS_OUTPUT_DIR/style.$STYLE_HASH.css" +STYLE_HASH=$(sha256sum "$GEN_OUTPUT_DIR/main.css" | cut -c1-12) +mv "$GEN_OUTPUT_DIR/main.css" "$GEN_OUTPUT_DIR/style.$STYLE_HASH.css" echo "Generated CSS style with hash: $STYLE_HASH" # Bundle TypeScript -JS_OUTPUT_DIR="./static/gen/js" -mkdir -p "$JS_OUTPUT_DIR" +GEN_OUTPUT_DIR="./static/gen" +mkdir -p "$GEN_OUTPUT_DIR" echo "Bundling TypeScript with Vue..." esbuild ts/main.ts \ @@ -64,11 +64,11 @@ esbuild ts/main.ts \ --define:__VUE_PROD_DEVTOOLS__=false \ --define:__VUE_PROD_HYDRATION_MISMATCH_DETAILS__=false \ --alias:vue=vue/dist/vue.esm-bundler.js \ - --outfile="$JS_OUTPUT_DIR/bundle.js" + --outfile="$GEN_OUTPUT_DIR/main.js" # Generate hash and rename bundle -BUNDLE_HASH=$(sha256sum "$JS_OUTPUT_DIR/bundle.js" | cut -c1-12) -mv "$JS_OUTPUT_DIR/bundle.js" "$JS_OUTPUT_DIR/bundle.$BUNDLE_HASH.js" +BUNDLE_HASH=$(sha256sum "$GEN_OUTPUT_DIR/main.js" | cut -c1-12) +mv "$GEN_OUTPUT_DIR/main.js" "$GEN_OUTPUT_DIR/bundle.$BUNDLE_HASH.js" echo "Generated JS bundle with hash: $BUNDLE_HASH" # Generate gen.go with bundle path diff --git a/html/template/sync/layout/authenticated.html b/html/template/sync/layout/authenticated.html index 97bd8d6f..b9b8d07f 100644 --- a/html/template/sync/layout/authenticated.html +++ b/html/template/sync/layout/authenticated.html @@ -12,72 +12,6 @@ {{ block "extraheader" . }}{{ end }} {{ if not .Config.IsProductionEnvironment }} {{ end }} @@ -94,17 +28,5 @@ {{ template "content" . }} - diff --git a/package.json b/package.json index 24308862..5cc4606d 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,17 @@ "private": true, "type": "module", "dependencies": { + "@popperjs/core": "^2.11.8", + "bootstrap": "^5.3.8", + "bootstrap-icons": "^1.13.1", "maplibre-gl": "^5.21.0", "vue": "^3.5.30" }, "devDependencies": { "esbuild": "^0.25.5", "esbuild-plugin-vue3": "^0.5.1", + "esbuild-sass-plugin": "^3.7.0", + "sass": "^1.98.0", "typescript": "^5.9.3" }, "scripts": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 032dcf34..dfd97b11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@popperjs/core': + specifier: ^2.11.8 + version: 2.11.8 + bootstrap: + specifier: ^5.3.8 + version: 5.3.8(@popperjs/core@2.11.8) + bootstrap-icons: + specifier: ^1.13.1 + version: 1.13.1 maplibre-gl: specifier: ^5.21.0 version: 5.21.0 @@ -20,7 +29,13 @@ importers: version: 0.25.12 esbuild-plugin-vue3: specifier: ^0.5.1 - version: 0.5.1(vue@3.5.30(typescript@5.9.3)) + version: 0.5.1(sass@1.98.0)(vue@3.5.30(typescript@5.9.3)) + esbuild-sass-plugin: + specifier: ^3.7.0 + version: 3.7.0(esbuild@0.25.12)(sass-embedded@1.98.0) + sass: + specifier: ^1.98.0 + version: 1.98.0 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -44,6 +59,9 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@bufbuild/protobuf@2.11.0': + resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -239,6 +257,91 @@ packages: '@maplibre/vt-pbf@4.3.0': resolution: {integrity: sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w==} + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} + engines: {node: '>= 10.0.0'} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -274,9 +377,28 @@ packages: '@vue/shared@3.5.30': resolution: {integrity: sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==} + bootstrap-icons@1.13.1: + resolution: {integrity: sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==} + + bootstrap@5.3.8: + resolution: {integrity: sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==} + peerDependencies: + '@popperjs/core': ^2.11.8 + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + colorjs.io@0.5.2: + resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + earcut@3.0.2: resolution: {integrity: sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==} @@ -302,6 +424,12 @@ packages: sass: optional: true + esbuild-sass-plugin@3.7.0: + resolution: {integrity: sha512-vxNSXFx3/0ZFApKo9036ek2iRfsT+yVO99qIYqa+JaDSuJuId2/N4s1TY+xfK+5LRpAMQkfdBVUTxb/1r2bq1A==} + peerDependencies: + esbuild: '>=0.27.3' + sass-embedded: ^1.97.3 + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -310,9 +438,35 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gl-matrix@3.4.4: resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + immutable@5.1.5: + resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + json-stringify-pretty-compact@4.0.0: resolution: {integrity: sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==} @@ -337,6 +491,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + pbf@4.0.1: resolution: {integrity: sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==} hasBin: true @@ -344,6 +504,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -357,12 +521,138 @@ packages: quickselect@3.0.0: resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + sass-embedded-all-unknown@1.98.0: + resolution: {integrity: sha512-6n4RyK7/1mhdfYvpP3CClS3fGoYqDvRmLClCESS6I7+SAzqjxvGG6u5Fo+cb1nrPNbbilgbM4QKdgcgWHO9NCA==} + cpu: ['!arm', '!arm64', '!riscv64', '!x64'] + + sass-embedded-android-arm64@1.98.0: + resolution: {integrity: sha512-M9Ra98A6vYJHpwhoC/5EuH1eOshQ9ZyNwC8XifUDSbRl/cGeQceT1NReR9wFj3L7s1pIbmes1vMmaY2np0uAKQ==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + + sass-embedded-android-arm@1.98.0: + resolution: {integrity: sha512-LjGiMhHgu7VL1n7EJxTCre1x14bUsWd9d3dnkS2rku003IWOI/fxc7OXgaKagoVzok1kv09rzO3vFXJR5ZeONQ==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + + sass-embedded-android-riscv64@1.98.0: + resolution: {integrity: sha512-WPe+0NbaJIZE1fq/RfCZANMeIgmy83x4f+SvFOG7LhUthHpZWcOcrPTsCKKmN3xMT3iw+4DXvqTYOCYGRL3hcQ==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [android] + + sass-embedded-android-x64@1.98.0: + resolution: {integrity: sha512-zrD25dT7OHPEgLWuPEByybnIfx4rnCtfge4clBgjZdZ3lF6E7qNLRBtSBmoFflh6Vg0RlEjJo5VlpnTMBM5MQQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + + sass-embedded-darwin-arm64@1.98.0: + resolution: {integrity: sha512-cgr1z9rBnCdMf8K+JabIaYd9Rag2OJi5mjq08XJfbJGMZV/TA6hFJCLGkr5/+ZOn4/geTM5/3aSfQ8z5EIJAOg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + + sass-embedded-darwin-x64@1.98.0: + resolution: {integrity: sha512-OLBOCs/NPeiMqTdOrMFbVHBQFj19GS3bSVSxIhcCq16ZyhouUkYJEZjxQgzv9SWA2q6Ki8GCqp4k6jMeUY9dcA==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + + sass-embedded-linux-arm64@1.98.0: + resolution: {integrity: sha512-axOE3t2MTBwCtkUCbrdM++Gj0gC0fdHJPrgzQ+q1WUmY9NoNMGqflBtk5mBZaWUeha2qYO3FawxCB8lctFwCtw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-arm@1.98.0: + resolution: {integrity: sha512-03baQZCxVyEp8v1NWBRlzGYrmVT/LK7ZrHlF1piscGiGxwfdxoLXVuxsylx3qn/dD/4i/rh7Bzk7reK1br9jvQ==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-musl-arm64@1.98.0: + resolution: {integrity: sha512-LeqNxQA8y4opjhe68CcFvMzCSrBuJqYVFbwElEj9bagHXQHTp9xVPJRn6VcrC+0VLEDq13HVXMv7RslIuU0zmA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-musl-arm@1.98.0: + resolution: {integrity: sha512-OBkjTDPYR4hSaueOGIM6FDpl9nt/VZwbSRpbNu9/eEJcxE8G/vynRugW8KRZmCFjPy8j/jkGBvvS+k9iOqKV3g==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-musl-riscv64@1.98.0: + resolution: {integrity: sha512-7w6hSuOHKt8FZsmjRb3iGSxEzM87fO9+M8nt5JIQYMhHTj5C+JY/vcske0v715HCVj5e1xyTnbGXf8FcASeAIw==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-musl-x64@1.98.0: + resolution: {integrity: sha512-QikNyDEJOVqPmxyCFkci8ZdCwEssdItfjQFJB+D+Uy5HFqcS5Lv3d3GxWNX/h1dSb23RPyQdQc267ok5SbEyJw==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-linux-riscv64@1.98.0: + resolution: {integrity: sha512-E7fNytc/v4xFBQKzgzBddV/jretA4ULAPO6XmtBiQu4zZBdBozuSxsQLe2+XXeb0X4S2GIl72V7IPABdqke/vA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-x64@1.98.0: + resolution: {integrity: sha512-VsvP0t/uw00mMNPv3vwyYKUrFbqzxQHnRMO+bHdAMjvLw4NFf6mscpym9Bzf+NXwi1ZNKnB6DtXjmcpcvqFqYg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-unknown-all@1.98.0: + resolution: {integrity: sha512-C4MMzcAo3oEDQnW7L8SBgB9F2Fq5qHPnaYTZRMOH3Mp/7kM4OooBInXpCiiFjLnjY95hzP4KyctVx0uYR6MYlQ==} + os: ['!android', '!darwin', '!linux', '!win32'] + + sass-embedded-win32-arm64@1.98.0: + resolution: {integrity: sha512-nP/10xbAiPbhQkMr3zQfXE4TuOxPzWRQe1Hgbi90jv2R4TbzbqQTuZVOaJf7KOAN4L2Bo6XCTRjK5XkVnwZuwQ==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + + sass-embedded-win32-x64@1.98.0: + resolution: {integrity: sha512-/lbrVsfbcbdZQ5SJCWcV0NVPd6YRs+FtAnfedp4WbCkO/ZO7Zt/58MvI4X2BVpRY/Nt5ZBo1/7v2gYcQ+J4svQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + + sass-embedded@1.98.0: + resolution: {integrity: sha512-Do7u6iRb6K+lrllcTkB1BXcHwOxcKe3rEfOF/GcCLE2w3WpddakRAosJOHFUR37DpsvimQXEt5abs3NzUjEIqg==} + engines: {node: '>=16.0.0'} + hasBin: true + + sass@1.98.0: + resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==} + engines: {node: '>=14.0.0'} + hasBin: true + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -370,9 +660,28 @@ packages: supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + sync-child-process@1.0.2: + resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} + engines: {node: '>=16.0.0'} + + sync-message-port@1.2.0: + resolution: {integrity: sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==} + engines: {node: '>=16.0.0'} + tinyqueue@3.0.0: resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -383,6 +692,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + vue@3.5.30: resolution: {integrity: sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==} peerDependencies: @@ -406,6 +718,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@bufbuild/protobuf@2.11.0': {} + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -532,6 +846,69 @@ snapshots: pbf: 4.0.1 supercluster: 8.0.1 + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.3 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + optional: true + + '@popperjs/core@2.11.8': {} + '@types/geojson@7946.0.16': {} '@types/supercluster@7.1.3': @@ -592,16 +969,40 @@ snapshots: '@vue/shared@3.5.30': {} + bootstrap-icons@1.13.1: {} + + bootstrap@5.3.8(@popperjs/core@2.11.8): + dependencies: + '@popperjs/core': 2.11.8 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + colorjs.io@0.5.2: {} + csstype@3.2.3: {} + detect-libc@2.1.2: + optional: true + earcut@3.0.2: {} entities@7.0.1: {} - esbuild-plugin-vue3@0.5.1(vue@3.5.30(typescript@5.9.3)): + esbuild-plugin-vue3@0.5.1(sass@1.98.0)(vue@3.5.30(typescript@5.9.3)): dependencies: typescript: 4.9.5 vue: 3.5.30(typescript@5.9.3) + optionalDependencies: + sass: 1.98.0 + + esbuild-sass-plugin@3.7.0(esbuild@0.25.12)(sass-embedded@1.98.0): + dependencies: + esbuild: 0.25.12 + resolve: 1.22.11 + sass: 1.98.0 + sass-embedded: 1.98.0 esbuild@0.25.12: optionalDependencies: @@ -634,8 +1035,30 @@ snapshots: estree-walker@2.0.2: {} + function-bind@1.1.2: {} + gl-matrix@3.4.4: {} + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + immutable@5.1.5: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: + optional: true + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + optional: true + json-stringify-pretty-compact@4.0.0: {} kdbush@4.0.2: {} @@ -672,12 +1095,20 @@ snapshots: nanoid@3.3.11: {} + node-addon-api@7.1.1: + optional: true + + path-parse@1.0.7: {} + pbf@4.0.1: dependencies: resolve-protobuf-schema: 2.1.0 picocolors@1.1.1: {} + picomatch@4.0.3: + optional: true + postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -690,24 +1121,147 @@ snapshots: quickselect@3.0.0: {} + readdirp@4.1.2: {} + resolve-protobuf-schema@2.1.0: dependencies: protocol-buffers-schema: 3.6.0 + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + rw@1.3.3: {} + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + sass-embedded-all-unknown@1.98.0: + dependencies: + sass: 1.98.0 + optional: true + + sass-embedded-android-arm64@1.98.0: + optional: true + + sass-embedded-android-arm@1.98.0: + optional: true + + sass-embedded-android-riscv64@1.98.0: + optional: true + + sass-embedded-android-x64@1.98.0: + optional: true + + sass-embedded-darwin-arm64@1.98.0: + optional: true + + sass-embedded-darwin-x64@1.98.0: + optional: true + + sass-embedded-linux-arm64@1.98.0: + optional: true + + sass-embedded-linux-arm@1.98.0: + optional: true + + sass-embedded-linux-musl-arm64@1.98.0: + optional: true + + sass-embedded-linux-musl-arm@1.98.0: + optional: true + + sass-embedded-linux-musl-riscv64@1.98.0: + optional: true + + sass-embedded-linux-musl-x64@1.98.0: + optional: true + + sass-embedded-linux-riscv64@1.98.0: + optional: true + + sass-embedded-linux-x64@1.98.0: + optional: true + + sass-embedded-unknown-all@1.98.0: + dependencies: + sass: 1.98.0 + optional: true + + sass-embedded-win32-arm64@1.98.0: + optional: true + + sass-embedded-win32-x64@1.98.0: + optional: true + + sass-embedded@1.98.0: + dependencies: + '@bufbuild/protobuf': 2.11.0 + colorjs.io: 0.5.2 + immutable: 5.1.5 + rxjs: 7.8.2 + supports-color: 8.1.1 + sync-child-process: 1.0.2 + varint: 6.0.0 + optionalDependencies: + sass-embedded-all-unknown: 1.98.0 + sass-embedded-android-arm: 1.98.0 + sass-embedded-android-arm64: 1.98.0 + sass-embedded-android-riscv64: 1.98.0 + sass-embedded-android-x64: 1.98.0 + sass-embedded-darwin-arm64: 1.98.0 + sass-embedded-darwin-x64: 1.98.0 + sass-embedded-linux-arm: 1.98.0 + sass-embedded-linux-arm64: 1.98.0 + sass-embedded-linux-musl-arm: 1.98.0 + sass-embedded-linux-musl-arm64: 1.98.0 + sass-embedded-linux-musl-riscv64: 1.98.0 + sass-embedded-linux-musl-x64: 1.98.0 + sass-embedded-linux-riscv64: 1.98.0 + sass-embedded-linux-x64: 1.98.0 + sass-embedded-unknown-all: 1.98.0 + sass-embedded-win32-arm64: 1.98.0 + sass-embedded-win32-x64: 1.98.0 + + sass@1.98.0: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.5 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + source-map-js@1.2.1: {} supercluster@8.0.1: dependencies: kdbush: 4.0.2 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + sync-child-process@1.0.2: + dependencies: + sync-message-port: 1.2.0 + + sync-message-port@1.2.0: {} + tinyqueue@3.0.0: {} + tslib@2.8.1: {} + typescript@4.9.5: {} typescript@5.9.3: {} + varint@6.0.0: {} + vue@3.5.30(typescript@5.9.3): dependencies: '@vue/compiler-dom': 3.5.30 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index efc037aa..012c4045 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ onlyBuiltDependencies: + - '@parcel/watcher' - esbuild diff --git a/static/gen.go b/static/gen.go index 1428a512..a40699fd 100644 --- a/static/gen.go +++ b/static/gen.go @@ -2,5 +2,5 @@ package static // gen.go - checked into git // This file is overwritten during Nix builds with hashed paths -const BundlePathCSS = "/static/gen/css/style.css" -const BundlePathJS = "/static/gen/js/bundle.js" +const BundlePathCSS = "/static/gen/main.css" +const BundlePathJS = "/static/gen/main.js" diff --git a/ts/global.d.ts b/ts/global.d.ts new file mode 100644 index 00000000..a1a2dc8b --- /dev/null +++ b/ts/global.d.ts @@ -0,0 +1,12 @@ +import * as bootstrap from 'bootstrap'; + +declare global { + interface Window { + Alpine: any; + SSEManager: any; + createAppPlanning: any; + bootstrap: typeof bootstrap; + } +} + +export {}; diff --git a/ts/main.ts b/ts/main.ts index 30f82072..7879d88d 100644 --- a/ts/main.ts +++ b/ts/main.ts @@ -1,5 +1,55 @@ +import Alpine from './vendor/alpinejs-3.15.8.js'; import { createApp } from 'vue'; import App from './app.vue'; +import { SSEManager } from './sse-manager'; +import { SetupSidebar } from "./sidebar"; import 'maplibre-gl/dist/maplibre-gl.css'; +// Import Bootstrap SCSS +import './style/style.scss'; + +// Import Bootstrap JavaScript and make it available globally +import * as bootstrap from 'bootstrap'; +window.bootstrap = bootstrap; + +import { Planning } from './app/planning'; + +// Make Alpine available on window for inline Alpine +window.Alpine = Alpine; + +// Make SSEManager available to all the JavaScript +window.SSEManager = SSEManager; + +function createAppPlanning() { + const app = createApp({ + data() { + return { + count: 0 + } + } + }); +} +window.createAppPlanning = createAppPlanning; + +// Wait for DOM to be ready, then initialize Alpine +document.addEventListener("DOMContentLoaded", () => { + Alpine.start(); + SSEManager.connect("/api/events"); + SetupSidebar(); +}); +interface GreetingComponent { + message: string; + name: string; + updateMessage(): void; +} + +Alpine.data('greeting', (): GreetingComponent => ({ + message: 'Welcome to Alpine + TypeScript!', + name: 'World', + + updateMessage() { + this.message = 'Message updated at ' + new Date().toLocaleTimeString(); + } +})); + createApp(App).mount('#app'); diff --git a/ts/sidebar.ts b/ts/sidebar.ts new file mode 100644 index 00000000..92dd3665 --- /dev/null +++ b/ts/sidebar.ts @@ -0,0 +1,75 @@ +export function SetupSidebar() { + console.log("setting up sidebar"); + var popoverTriggerList = [].slice.call( + document.querySelectorAll('[data-bs-toggle="popover"]'), + ); + var popoverList = popoverTriggerList.map(function (popoverTriggerEl) { + return new bootstrap.Popover(popoverTriggerEl); + }); + console.log("Initialized ", popoverTriggerList.length, " popovers"); + + var tooltipTriggerList = [].slice.call( + document.querySelectorAll('[data-bs-toggle="tooltip"]'), + ); + var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + let t = new bootstrap.Tooltip(tooltipTriggerEl); + return t; + }); + console.log("Initialized ", tooltipTriggerList.length, " tooltips"); + restoreLocalStorage(); + setTooltipsForSidebar(); + SSEManager.subscribe("*", function (e) { + if (e.type != "heartbeat") { + updateUserState(); + } + }); + document.addEventListener("alpine:init", () => { + Alpine.store("user", USER); + }) + document.getElementById("sidebarToggle").addEventListener("click", () => { + const sidebar = document.getElementById("sidebar"); + sidebar.classList.toggle("collapsed"); + document.getElementById("content").classList.toggle("expanded"); + setTooltipsForSidebar(); + localStorage.setItem( + "sidebar.expanded", + (!sidebar.classList.contains("collapsed")).toString(), + ); + }); +} +function restoreLocalStorage() { + const expanded = localStorage.getItem("sidebar.expanded"); + if (expanded == "false") { + document.getElementById("sidebar").classList.add("collapsed"); + document.getElementById("content").classList.add("expanded"); + } else { + document.getElementById("sidebar").classList.remove("collapsed"); + document.getElementById("content").classList.remove("expanded"); + localStorage.setItem("sidebar.expanded", "true"); + } +} +function setTooltipsForSidebar() { + const sidebarTooltips = document.querySelectorAll( + '#sidebar [data-bs-toggle="tooltip"]', + ); + const isExpanded = document + .getElementById("content") + .classList.contains("expanded"); + sidebarTooltips.forEach((t) => { + const tooltip = bootstrap.Tooltip.getOrCreateInstance(t); + if (isExpanded) { + tooltip.enable(); + } else { + tooltip.disable(); + } + }); +} +async function updateUserState() { + const response = await fetch("/api/user/self"); + const data = await response.json(); + // Update properties instead of replacing the whole store which leverages Alpine's reactivity + const store_user = Alpine.store("user"); + Object.keys(data).forEach(key => { + store_user[key] = data[key]; + }); +} diff --git a/ts/style/sidebar.scss b/ts/style/sidebar.scss new file mode 100644 index 00000000..8aa2e84e --- /dev/null +++ b/ts/style/sidebar.scss @@ -0,0 +1,140 @@ +.logo-container { + display: flex; + justify-content: center; + align-items: center; + transition: all 0.3s ease; +} + +.logo { + max-width: 100%; + height: auto; + transition: all 0.3s ease; +} + +#sidebar { + background-color: $off-white; + min-height: 100vh; + transition: all 0.3s; + width: 250px; + position: fixed; + z-index: 1000; + padding: 20px; +} + +#sidebar.collapsed { + width: 70px; + padding: 20px 10px; +} +/* Logo style when sidebar is collapsed */ +#sidebar.collapsed .logo-container { + width: 100%; +} + +#sidebar.collapsed .logo-img { + max-width: 40px; /* smaller size for collapsed state */ +} +#content { + transition: all 0.3s; + margin-left: 250px; + padding: 10px; + width: calc(100% - 250px); +} + +#content.expanded { + margin-left: 70px; + width: calc(100% - 70px); +} + +.sidebar-header { + padding-bottom: 20px; + border-bottom: 1px solid $off-black; + margin-bottom: 20px; + overflow: hidden; + white-space: nowrap; + display: flex; + justify-content: center; /* Center for the logo */ +} + +.sidebar-menu { + list-style: none; + padding: 0; +} + +.sidebar-menu li { + padding: 10px 0; +} + +.sidebar-menu li a { + text-decoration: none; + color: $off-black; + display: flex; + align-items: center; + overflow: hidden; + white-space: nowrap; +} + +.sidebar-menu li a:hover { + color: $primary; +} + +.sidebar-menu .menu-icon { + font-size: 1.2rem; + min-width: 30px; + display: flex; + justify-content: center; +} +.sidebar-menu .menu-icon svg { + width: 1.5em; + height: 1.5em; +} +.sidebar-menu .menu-text { + transition: opacity 0.3s; +} + +#sidebar.collapsed .menu-text { + opacity: 0; + visibility: hidden; + width: 0; +} + +#sidebar.collapsed .sidebar-header h4 { + opacity: 0; + visibility: hidden; +} + +#sidebar.collapsed .sidebar-menu .menu-icon { + min-width: 100%; + font-size: 1.5rem; +} + +#sidebarToggle { + position: absolute; + left: calc(250px - 15px); + top: 50%; + transform: translateY(-50%); + z-index: 1050; + width: 30px; + height: 30px; + border-radius: 50%; + border: 1px solid #dee2e6; + display: flex; + align-items: center; + transition: left 0.3s; + padding: 0; +} +#sidebarToggle i { + transition: transform 0.3s; +} + +#sidebar.collapsed > #sidebarToggle { + left: calc(70px - 15px); +} + +#sidebar > #sidebarToggle i { + position: relative; + left: 5px; +} + +#sidebar.collapsed > #sidebarToggle i { + transform: rotate(180deg); +} diff --git a/ts/style/style.scss b/ts/style/style.scss new file mode 100644 index 00000000..eae7dee4 --- /dev/null +++ b/ts/style/style.scss @@ -0,0 +1,63 @@ +@use "sass:map"; +// 1. Include specific theme variables +$primary: #F76436; +$secondary: #3C552D; +$success: #8BAE67; +$warning: #FFC01B; +$danger: #6b2737; +$info: #D7B26D; +$dark: #3b1002; +$light: #fde1d8; + +$off-white: #F8F9FA; +$off-black: #495057; + +$primary-light-4: #FAA489; +// 2. Configure color contrast +$color-contrast-dark: #000; +$color-contrast-light: #fff; +$min-contrast-ratio: 2.0; + +$custom-colors: ( + "color1": $primary, + "color2": $secondary, + "color3": $success, + "color4": $danger, + "color5": $warning, + "color6": $info, +); +$theme-colors: map.merge( + ( + "primary": $primary, + "secondary": $secondary, + "success": $success, + "danger": $danger, + "warning": $warning, + "info": $info, + "dark": $dark, + "light": $light + ), + $custom-colors +); + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +// Make custom SVG icons about the same size as other icons +i.bi svg { + height: 18px; + width: 18px; +} +@import "bootstrap/scss/functions"; +// Import Bootstrap's variables (this merges with your custom variables) +@import "bootstrap/scss/variables"; + +// Import Bootstrap's mixins +@import "bootstrap/scss/mixins"; + +// Import all of Bootstrap (or pick specific components) +@import "bootstrap/scss/bootstrap"; + +@import "./sidebar.scss";