feat: Implement dedicated data fetchers and utilities for categories, products, and images, and add server compression.
This commit is contained in:
185
index.html
185
index.html
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user