feat: add dashboard view toggle
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 13s
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 13s
This commit is contained in:
60
app.js
60
app.js
@@ -18,10 +18,12 @@ let sections = [];
|
||||
let config = null;
|
||||
let favorites = [];
|
||||
let activeFilter = 'all';
|
||||
let activeView = window.localStorage.getItem('mischlabs.dashboard.view') || 'all';
|
||||
let categoryById = new Map();
|
||||
|
||||
const searchInput = document.querySelector('#serviceSearch');
|
||||
const segments = [...document.querySelectorAll('.segment')];
|
||||
const viewOptions = [...document.querySelectorAll('.view-option')];
|
||||
const emptyState = document.querySelector('#emptyState');
|
||||
const onlineCount = document.querySelector('#onlineCount');
|
||||
const serviceCount = document.querySelector('#serviceCount');
|
||||
@@ -183,6 +185,12 @@ function createFavoriteCard(favorite) {
|
||||
card.rel = 'noopener';
|
||||
card.className = 'card favorite-card';
|
||||
card.style.setProperty('--accent', favorite.color || '#67e8f9');
|
||||
card.dataset.name = [
|
||||
favorite.name,
|
||||
favorite.description,
|
||||
favorite.group,
|
||||
favorite.url
|
||||
].join(' ');
|
||||
|
||||
const safeLogo = favorite.logoUrl ? escapeHtml(favorite.logoUrl) : '';
|
||||
const safeName = escapeHtml(favorite.name);
|
||||
@@ -225,6 +233,7 @@ function renderFavorites() {
|
||||
</section>
|
||||
`;
|
||||
document.querySelector('#favoritesLoginCallout')?.addEventListener('click', () => MischLabsAuth.login());
|
||||
updateVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,6 +252,7 @@ function renderFavorites() {
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
updateVisibility();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -281,6 +291,8 @@ function renderFavorites() {
|
||||
items.forEach((favorite) => grid.appendChild(createFavoriteCard(favorite)));
|
||||
container.appendChild(section);
|
||||
});
|
||||
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
async function loadFavorites() {
|
||||
@@ -348,22 +360,48 @@ function updateOverviewHeading(visibleCards) {
|
||||
function updateVisibility() {
|
||||
const term = normalize(searchInput.value);
|
||||
let visibleCards = 0;
|
||||
let visibleFavorites = 0;
|
||||
const showServices = activeView === 'all' || activeView === 'services';
|
||||
const showFavorites = activeView === 'all' || activeView === 'favorites';
|
||||
|
||||
if (dashboardSections) {
|
||||
dashboardSections.hidden = !showServices;
|
||||
}
|
||||
|
||||
if (favoritesSections) {
|
||||
favoritesSections.hidden = !showFavorites;
|
||||
}
|
||||
|
||||
cards.forEach((card) => {
|
||||
const categoryMatches = activeFilter === 'all' || card.dataset.category === activeFilter;
|
||||
const searchMatches = !term || normalize(card.dataset.name).includes(term);
|
||||
const isVisible = categoryMatches && searchMatches;
|
||||
const isVisible = showServices && categoryMatches && searchMatches;
|
||||
card.hidden = !isVisible;
|
||||
if (isVisible) visibleCards += 1;
|
||||
});
|
||||
|
||||
sections.forEach((section) => {
|
||||
const hasVisibleCards = [...section.querySelectorAll('.service-card')].some((card) => !card.hidden);
|
||||
section.hidden = !hasVisibleCards;
|
||||
section.hidden = !showServices || !hasVisibleCards;
|
||||
});
|
||||
|
||||
document.querySelectorAll('.favorite-card').forEach((card) => {
|
||||
const isVisible = showFavorites && (!term || normalize(card.dataset.name).includes(term));
|
||||
card.hidden = !isVisible;
|
||||
if (isVisible) visibleFavorites += 1;
|
||||
});
|
||||
|
||||
document.querySelectorAll('.favorite-group').forEach((group) => {
|
||||
const hasVisibleCards = [...group.querySelectorAll('.favorite-card')].some((card) => !card.hidden);
|
||||
group.hidden = !showFavorites || !hasVisibleCards;
|
||||
});
|
||||
|
||||
updateOverviewHeading(visibleCards);
|
||||
emptyState.hidden = visibleCards !== 0;
|
||||
const hasVisibleContent = visibleCards + visibleFavorites > 0;
|
||||
emptyState.hidden = hasVisibleContent;
|
||||
emptyState.textContent = term
|
||||
? 'Kein Eintrag passt zur Suche.'
|
||||
: 'In dieser Ansicht gibt es noch keine Eintraege.';
|
||||
}
|
||||
|
||||
function setActiveFilter(nextFilter) {
|
||||
@@ -376,6 +414,17 @@ function setActiveFilter(nextFilter) {
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
function setActiveView(nextView) {
|
||||
activeView = nextView;
|
||||
window.localStorage.setItem('mischlabs.dashboard.view', nextView);
|
||||
viewOptions.forEach((option) => {
|
||||
const isActive = option.dataset.view === nextView;
|
||||
option.classList.toggle('is-active', isActive);
|
||||
option.setAttribute('aria-selected', String(isActive));
|
||||
});
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
async function probeService(card) {
|
||||
const controller = new AbortController();
|
||||
const timeout = window.setTimeout(() => controller.abort(), 4500);
|
||||
@@ -435,6 +484,10 @@ segments.forEach((segment) => {
|
||||
segment.addEventListener('click', () => setActiveFilter(segment.dataset.filter));
|
||||
});
|
||||
|
||||
viewOptions.forEach((option) => {
|
||||
option.addEventListener('click', () => setActiveView(option.dataset.view));
|
||||
});
|
||||
|
||||
searchInput.addEventListener('input', updateVisibility);
|
||||
|
||||
function updateAuthNav() {
|
||||
@@ -466,6 +519,7 @@ MischLabsAuth.handleCallback()
|
||||
config = nextConfig;
|
||||
updateAuthNav();
|
||||
renderDashboard(config);
|
||||
setActiveView(activeView);
|
||||
updateVisibility();
|
||||
loadFavorites();
|
||||
scheduleStatusRefresh();
|
||||
|
||||
Reference in New Issue
Block a user