feat: Implement dedicated data fetchers and utilities for categories, products, and images, and add server compression.

This commit is contained in:
sebseb7
2025-11-24 12:02:44 +01:00
parent 27474cb9ec
commit 6dbac0d3c1
14 changed files with 1022 additions and 598 deletions

View File

@@ -5,6 +5,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Category Tree Viewer</title>
<!-- Resource hints to optimize loading -->
<link rel="preload" href="/socket.io/socket.io.js" as="script">
<link rel="preconnect" href="/api">
<style>
* {
margin: 0;
@@ -421,11 +426,103 @@
</div>
<button id="version-reload-btn" onclick="location.reload()">New version - [reload]</button>
<script src="/socket.io/socket.io.js"></script>
<script src="/socket.io/socket.io.js" async></script>
<script>
const socket = io({
transports: ['websocket']
});
// Initialize socket when io is available (async)
function initSocket() {
if (typeof io === 'undefined') {
// Socket.io not loaded yet, try again soon
setTimeout(initSocket, 50);
return;
}
const socket = io({
transports: ['websocket']
});
// Version checking
const clientEtag = document.querySelector('meta[name="app-version"]')?.content;
const reloadBtn = document.getElementById('version-reload-btn');
// Socket Events
socket.on('connect', () => {
console.log('🔌 Connected to server via WebSocket');
// Check version on connect and reconnect
if (clientEtag) {
socket.emit('checkVersion', clientEtag);
}
});
socket.on('versionMismatch', ({ serverEtag }) => {
console.log('⚠️ New version available on server');
if (reloadBtn) {
reloadBtn.classList.add('show');
}
});
socket.on('categoriesUpdated', () => {
console.log('🔄 Categories updated, reloading tree...');
loadCategories();
});
socket.on('categoryProductsUpdated', ({ id }) => {
console.log(`🔄 Products for category ${id} updated, reloading...`);
updateCategoryProducts(id);
});
// Debounce function
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const debouncedSearch = debounce((value) => {
socket.emit('search', value);
}, 300);
// Event Listeners
filterInput.addEventListener('input', (e) => {
const value = e.target.value;
state.filter = value;
// Toggle clear button visibility
if (value) {
clearBtn.classList.add('visible');
} else {
clearBtn.classList.remove('visible');
}
if (value.trim()) {
debouncedSearch(value);
} else {
// Clear matches and collapse all categories
resetMatches(state.categories);
resetExpansion(state.categories);
collapseAllProducts(state.categories);
render();
}
});
socket.on('searchResults', ({ query, matches }) => {
if (query !== state.filter) return;
const matchSet = new Set(matches);
// Reset expansion and matches
resetExpansion(state.categories);
// Mark matches and expand
markMatches(state.categories, matchSet);
render();
});
}
// Start socket initialization (async, non-blocking)
initSocket();
// State management
const state = {
@@ -444,72 +541,6 @@
filterInput.focus();
});
// Version checking
const clientEtag = document.querySelector('meta[name="app-version"]')?.content;
const reloadBtn = document.getElementById('version-reload-btn');
// Socket Events
socket.on('connect', () => {
console.log('🔌 Connected to server via WebSocket');
// Check version on connect and reconnect
if (clientEtag) {
socket.emit('checkVersion', clientEtag);
}
});
socket.on('versionMismatch', ({ serverEtag }) => {
console.log('⚠️ New version available on server');
if (reloadBtn) {
reloadBtn.classList.add('show');
}
});
socket.on('categoriesUpdated', () => {
console.log('🔄 Categories updated, reloading tree...');
loadCategories();
});
socket.on('categoryProductsUpdated', ({ id }) => {
console.log(`🔄 Products for category ${id} updated, reloading...`);
updateCategoryProducts(id);
});
// Debounce function
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const debouncedSearch = debounce((value) => {
socket.emit('search', value);
}, 300);
// Event Listeners
filterInput.addEventListener('input', (e) => {
const value = e.target.value;
state.filter = value;
// Toggle clear button visibility
if (value) {
clearBtn.classList.add('visible');
} else {
clearBtn.classList.remove('visible');
}
if (value.trim()) {
debouncedSearch(value);
} else {
// Clear matches and collapse all categories
resetMatches(state.categories);
resetExpansion(state.categories);
collapseAllProducts(state.categories);
render();
}
});
// Clear button functionality
clearBtn.addEventListener('click', () => {
filterInput.value = '';
@@ -526,20 +557,6 @@
filterInput.focus();
});
socket.on('searchResults', ({ query, matches }) => {
if (query !== state.filter) return;
const matchSet = new Set(matches);
// Reset expansion and matches
resetExpansion(state.categories);
// Mark matches and expand
markMatches(state.categories, matchSet);
render();
});
function resetMatches(nodes) {
nodes.forEach(node => {
node._hasMatch = false;