Re-create dynamic nature of the sidebar

This commit is contained in:
Eli Ribble 2026-03-21 21:35:32 +00:00
parent 48d44487da
commit e5af41b703
No known key found for this signature in database

View file

@ -1,11 +1,12 @@
<template>
<div id="sidebar" x-data="$store.user">
<div id="sidebar" :class="{ collapsed: isCollapsed }">
<div class="sidebar-header">
<div class="logo-container">
<img class="logo" src="/static/img/nidus-logo-256-transparent.png" />
</div>
</div>
<button id="sidebarToggle" class="btn btn-sm p-0">
<button id="sidebarToggle" class="btn btn-sm p-0" @click="toggleSidebar">
<i id="sidebarToggleIcon" class="bi bi-chevron-left"></i>
</button>
@ -42,13 +43,14 @@
<div class="menu-icon"><i class="bi bi-messaging"></i></div>
<span class="menu-text ms-2">Communication</span>
<span
x-show="notification_counts.communication > 0"
x-cloak
v-show="notificationCounts.communication > 0"
class="position-absolute translate-middle badge rounded-pill bg-primary"
>
<span
x-text="notification_counts.communication > 99 ? '99+' : notification_counts.communication"
></span>
<span>{{
notificationCounts.communication > 99
? "99+"
: notificationCounts.communication
}}</span>
<span class="visually-hidden">unread notifications</span>
</span>
</a>
@ -85,13 +87,12 @@
<div class="menu-icon"><i class="bi bi-review"></i></div>
<span class="menu-text ms-2">Review</span>
<span
x-show="notification_counts.review > 0"
x-cloak
v-show="notificationCounts.review > 0"
class="position-absolute translate-middle badge rounded-pill bg-primary"
>
<span
x-text="notification_counts.review > 99 ? '99+' : notification_counts.review"
></span>
<span>{{
notificationCounts.review > 99 ? "99+" : notificationCounts.review
}}</span>
<span class="visually-hidden">unread notifications</span>
</span>
</a>
@ -122,22 +123,132 @@
</div>
</template>
<script setup lang="ts">
import NavigationLink from "../common/NavigationLink.vue";
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from "vue";
import { Tooltip, Popover } from "bootstrap";
// Reactive state
const isCollapsed = ref(false);
const notificationCounts = reactive({
communication: 0,
review: 0,
});
const userData = reactive({});
// Bootstrap tooltip instances
let tooltipInstances = [];
let sseUnsubscribe = null;
// Initialize Bootstrap components
const initializeBootstrap = () => {
// Initialize popovers
const popoverElements = document.querySelectorAll(
'[data-bs-toggle="popover"]',
);
popoverElements.forEach((el) => new Popover(el));
console.log("Initialized", popoverElements.length, "popovers");
// Initialize tooltips
const tooltipElements = document.querySelectorAll(
'[data-bs-toggle="tooltip"]',
);
tooltipInstances = Array.from(tooltipElements).map((el) => new Tooltip(el));
console.log("Initialized", tooltipElements.length, "tooltips");
};
// Restore sidebar state from localStorage
const restoreLocalStorage = () => {
const expanded = localStorage.getItem("sidebar.expanded");
if (expanded === "false") {
isCollapsed.value = true;
document.getElementById("content")?.classList.add("expanded");
} else {
isCollapsed.value = false;
document.getElementById("content")?.classList.remove("expanded");
localStorage.setItem("sidebar.expanded", "true");
}
};
// Toggle sidebar collapsed state
const toggleSidebar = () => {
isCollapsed.value = !isCollapsed.value;
document.getElementById("content")?.classList.toggle("expanded");
setTooltipsForSidebar();
localStorage.setItem("sidebar.expanded", (!isCollapsed.value).toString());
};
// Enable/disable tooltips based on sidebar state
const setTooltipsForSidebar = () => {
const sidebarTooltips = document.querySelectorAll(
'#sidebar [data-bs-toggle="tooltip"]',
);
const isExpanded = document
.getElementById("content")
?.classList.contains("expanded");
sidebarTooltips.forEach((el) => {
const tooltip = Tooltip.getOrCreateInstance(el);
if (isExpanded) {
tooltip.enable();
} else {
tooltip.disable();
}
});
};
// Fetch user state from API
const updateUserState = async () => {
try {
const response = await fetch("/api/user/self");
const data = await response.json();
// Update reactive data
Object.keys(data).forEach((key) => {
if (key === "notification_counts") {
Object.assign(notificationCounts, data[key]);
} else {
userData[key] = data[key];
}
});
} catch (error) {
console.error("Failed to update user state:", error);
}
};
// Lifecycle hooks
onMounted(async () => {
restoreLocalStorage();
await nextTick();
initializeBootstrap();
setTooltipsForSidebar();
// Subscribe to SSE events (assuming SSEManager is globally available)
if (window.SSEManager) {
sseUnsubscribe = window.SSEManager.subscribe("*", (e) => {
if (e.type !== "heartbeat") {
updateUserState();
}
});
}
// Initial user state fetch
updateUserState();
});
onBeforeUnmount(() => {
// Cleanup Bootstrap tooltips
tooltipInstances.forEach((tooltip) => tooltip.dispose());
// Unsubscribe from SSE
if (sseUnsubscribe) {
sseUnsubscribe();
}
});
</script>
<style scoped>
.sidebar {
width: 250px;
background-color: #2c3e50;
color: white;
padding: 20px;
}
nav {
margin-top: 30px;
display: flex;
flex-direction: column;
gap: 10px;
}
/* Add any component-specific styles here */
</style>