Add CSS via SCSS to the frontend build pipeline
This commit is contained in:
parent
1e67c0090d
commit
f3c818a48f
12 changed files with 924 additions and 94 deletions
12
ts/global.d.ts
vendored
Normal file
12
ts/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import * as bootstrap from 'bootstrap';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Alpine: any;
|
||||
SSEManager: any;
|
||||
createAppPlanning: any;
|
||||
bootstrap: typeof bootstrap;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
50
ts/main.ts
50
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');
|
||||
|
|
|
|||
75
ts/sidebar.ts
Normal file
75
ts/sidebar.ts
Normal file
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
140
ts/style/sidebar.scss
Normal file
140
ts/style/sidebar.scss
Normal file
|
|
@ -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);
|
||||
}
|
||||
63
ts/style/style.scss
Normal file
63
ts/style/style.scss
Normal file
|
|
@ -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";
|
||||
Loading…
Add table
Add a link
Reference in a new issue