botserver/web/desktop/js/layout.js
Rodrigo Rodriguez (Pragmatismo) 5a13a99e71 refactor(web): add SPA fallback route and clean up server
- Removed the unused `serve_html` handler and its import.
- Added a fallback static file service that serves `index.html` for any unmatched path, enabling proper SPA routing.
- Reordered service registration to place the fallback before the explicit index route.
- Cleaned up redundant blank lines and imports in `mod.rs`.

**Client-side (chat.js) updates**
- Renamed the message input variable from `input` to `messageInputEl` for clarity.
- Introduced `pendingContextChange` placeholder for future context handling.
- Switched initialization event from `document 'ready'` to `window 'load'`.
- Updated DOM element assignments and focus calls to use the new variable name.
- Removed unused sidebar auto‑close logic and obsolete session loading functions (`loadSessions`, `loadSessionHistory`).
- Minor refactoring and comment adjustments to improve readability and eliminate dead code.
2025-11-17 12:11:13 -03:00

139 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const sections = {
drive: 'drive/drive.html',
tasks: 'tasks/tasks.html',
mail: 'mail/mail.html',
chat: 'chat/chat.html',
};
const sectionCache = {};
function getBasePath() {
// All static assets (HTML, CSS, JS) are served from the site root.
// Returning a leading slash ensures URLs like "/drive/drive.html" resolve correctly
// with the Actix static file configuration.
return '/';
}
async function loadSectionHTML(path) {
const fullPath = getBasePath() + path;
const response = await fetch(fullPath);
if (!response.ok) throw new Error('Failed to load section: ' + fullPath);
return await response.text();
}
async function switchSection(section) {
const mainContent = document.getElementById('main-content');
try {
const htmlPath = sections[section];
console.log('Loading section:', section, 'from', htmlPath);
// Resolve CSS path relative to the base directory.
const cssPath = getBasePath() + htmlPath.replace('.html', '.css');
// Remove any existing section CSS
document.querySelectorAll('link[data-section-css]').forEach(link => link.remove());
// Load CSS first (skip if already loaded)
let cssLink = document.querySelector(`link[href="${cssPath}"]`);
if (!cssLink) {
cssLink = document.createElement('link');
cssLink.rel = 'stylesheet';
cssLink.href = cssPath;
cssLink.setAttribute('data-section-css', 'true');
document.head.appendChild(cssLink);
}
// Hide previously loaded sections and show the requested one
// Ensure a container exists for sections
let container = document.getElementById('section-container');
if (!container) {
container = document.createElement('div');
container.id = 'section-container';
mainContent.appendChild(container);
}
const targetDiv = document.getElementById(`section-${section}`);
if (targetDiv) {
// Section already loaded: hide others, show this one
container.querySelectorAll('.section').forEach(div => {
div.style.display = 'none';
});
targetDiv.style.display = 'block';
} else {
// Show loading placeholder inside the container
const loadingDiv = document.createElement('div');
loadingDiv.className = 'loading';
loadingDiv.textContent = 'Loading…';
container.appendChild(loadingDiv);
// Load HTML
const html = await loadSectionHTML(htmlPath);
// Create wrapper for the new section
const wrapper = document.createElement('div');
wrapper.id = `section-${section}`;
wrapper.className = 'section';
wrapper.innerHTML = html;
// Hide any existing sections
container.querySelectorAll('.section').forEach(div => {
div.style.display = 'none';
});
// Remove loading placeholder
container.removeChild(loadingDiv);
// Add the new section to the container and cache it
container.appendChild(wrapper);
sectionCache[section] = wrapper;
// Ensure the new section is visible
wrapper.style.display = 'block';
}
// Then load JS after HTML is inserted (skip if already loaded)
// Resolve JS path relative to the base directory.
const jsPath = getBasePath() + htmlPath.replace('.html', '.js');
const existingScript = document.querySelector(`script[src="${jsPath}"]`);
if (!existingScript) {
const script = document.createElement('script');
script.src = jsPath;
script.defer = true;
document.body.appendChild(script);
}
window.history.pushState({}, '', `#${section}`);
Alpine.initTree(mainContent);
} catch (err) {
console.error('Error loading section:', err);
mainContent.innerHTML = `<div class="error">Failed to load ${section} section</div>`;
}
}
// Handle initial load based on URL hash
function getInitialSection() {
// 1⃣ Prefer hash fragment (e.g., #chat)
let section = window.location.hash.substring(1);
// 2⃣ Fallback to pathname segments (e.g., /chat)
if (!section) {
const parts = window.location.pathname.split('/').filter(p => p);
const last = parts[parts.length - 1];
if (['drive', 'tasks', 'mail', 'chat'].includes(last)) {
section = last;
}
}
// 3⃣ As a last resort, inspect the full URL for known sections
if (!section) {
const match = window.location.href.match(/\/(drive|tasks|mail|chat)(?:\.html)?(?:[?#]|$)/i);
if (match) {
section = match[1].toLowerCase();
}
}
// Default to drive if nothing matches
return section || 'drive';
}
window.addEventListener('DOMContentLoaded', () => {
switchSection(getInitialSection());
});
// Handle browser back/forward navigation
window.addEventListener('popstate', () => {
switchSection(getInitialSection());
});