Implement responsive sidebar functionality that auto-opens on desktop (≥1024px) and auto-closes on mobile, with a toggle button for mobile users. Added overlay for mobile sidebar dismissal and CSS for collapsed state on desktop. Minor adjustments to dashboard chart borders and grid layouts.
222 lines
9.4 KiB
JavaScript
222 lines
9.4 KiB
JavaScript
/**
|
|
* Antigravity Console - Main Entry
|
|
*
|
|
* This file orchestrates Alpine.js initialization.
|
|
* Components are loaded via separate script files that register themselves
|
|
* to window.Components before this script runs.
|
|
*/
|
|
|
|
document.addEventListener('alpine:init', () => {
|
|
// Register Components (loaded from separate files via window.Components)
|
|
Alpine.data('dashboard', window.Components.dashboard);
|
|
Alpine.data('models', window.Components.models);
|
|
Alpine.data('accountManager', window.Components.accountManager);
|
|
Alpine.data('claudeConfig', window.Components.claudeConfig);
|
|
Alpine.data('logsViewer', window.Components.logsViewer);
|
|
|
|
// View Loader Directive
|
|
Alpine.directive('load-view', (el, { expression }, { evaluate }) => {
|
|
if (!window.viewCache) window.viewCache = new Map();
|
|
|
|
// Evaluate the expression to get the actual view name (removes quotes)
|
|
const viewName = evaluate(expression);
|
|
|
|
if (window.viewCache.has(viewName)) {
|
|
el.innerHTML = window.viewCache.get(viewName);
|
|
Alpine.initTree(el);
|
|
return;
|
|
}
|
|
|
|
fetch(`views/${viewName}.html?t=${Date.now()}`)
|
|
.then(response => {
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return response.text();
|
|
})
|
|
.then(html => {
|
|
// Update cache (optional, or remove if we want always-fresh)
|
|
// keeping cache for session performance, but initial load will now bypass browser cache
|
|
window.viewCache.set(viewName, html);
|
|
el.innerHTML = html;
|
|
Alpine.initTree(el);
|
|
})
|
|
.catch(err => {
|
|
console.error('Failed to load view:', viewName, err);
|
|
el.innerHTML = `<div class="p-4 border border-red-500/50 bg-red-500/10 rounded-lg text-red-400 font-mono text-sm">
|
|
Error loading view: ${viewName}<br>
|
|
<span class="text-xs opacity-75">${err.message}</span>
|
|
</div>`;
|
|
});
|
|
});
|
|
|
|
// Main App Controller
|
|
Alpine.data('app', () => ({
|
|
get connectionStatus() {
|
|
return Alpine.store('data')?.connectionStatus || 'connecting';
|
|
},
|
|
get loading() {
|
|
return Alpine.store('data')?.loading || false;
|
|
},
|
|
|
|
sidebarOpen: window.innerWidth >= 1024,
|
|
toggleSidebar() {
|
|
this.sidebarOpen = !this.sidebarOpen;
|
|
},
|
|
|
|
init() {
|
|
console.log('App controller initialized');
|
|
|
|
// Handle responsive sidebar transitions
|
|
let lastWidth = window.innerWidth;
|
|
window.addEventListener('resize', () => {
|
|
const currentWidth = window.innerWidth;
|
|
const lgBreakpoint = 1024;
|
|
|
|
// Desktop -> Mobile: Auto-close sidebar to prevent overlay blocking screen
|
|
if (lastWidth >= lgBreakpoint && currentWidth < lgBreakpoint) {
|
|
this.sidebarOpen = false;
|
|
}
|
|
|
|
// Mobile -> Desktop: Auto-open sidebar (restore standard desktop layout)
|
|
if (lastWidth < lgBreakpoint && currentWidth >= lgBreakpoint) {
|
|
this.sidebarOpen = true;
|
|
}
|
|
|
|
lastWidth = currentWidth;
|
|
});
|
|
|
|
// Theme setup
|
|
document.documentElement.setAttribute('data-theme', 'black');
|
|
document.documentElement.classList.add('dark');
|
|
|
|
// Chart Defaults
|
|
if (typeof Chart !== 'undefined') {
|
|
Chart.defaults.color = window.utils.getThemeColor('--color-text-dim');
|
|
Chart.defaults.borderColor = window.utils.getThemeColor('--color-space-border');
|
|
Chart.defaults.font.family = '"JetBrains Mono", monospace';
|
|
}
|
|
|
|
// Start Data Polling
|
|
this.startAutoRefresh();
|
|
document.addEventListener('refresh-interval-changed', () => this.startAutoRefresh());
|
|
|
|
// Initial Fetch
|
|
Alpine.store('data').fetchData();
|
|
},
|
|
|
|
refreshTimer: null,
|
|
|
|
fetchData() {
|
|
Alpine.store('data').fetchData();
|
|
},
|
|
|
|
startAutoRefresh() {
|
|
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
const interval = parseInt(Alpine.store('settings')?.refreshInterval || 60);
|
|
if (interval > 0) {
|
|
this.refreshTimer = setInterval(() => Alpine.store('data').fetchData(), interval * 1000);
|
|
}
|
|
},
|
|
|
|
t(key) {
|
|
return Alpine.store('global')?.t(key) || key;
|
|
},
|
|
|
|
async addAccountWeb(reAuthEmail = null) {
|
|
const password = Alpine.store('global').webuiPassword;
|
|
try {
|
|
const urlPath = reAuthEmail
|
|
? `/api/auth/url?email=${encodeURIComponent(reAuthEmail)}`
|
|
: '/api/auth/url';
|
|
|
|
const { response, newPassword } = await window.utils.request(urlPath, {}, password);
|
|
if (newPassword) Alpine.store('global').webuiPassword = newPassword;
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.status === 'ok') {
|
|
// Show info toast that OAuth is in progress
|
|
Alpine.store('global').showToast(Alpine.store('global').t('oauthInProgress'), 'info');
|
|
|
|
// Open OAuth window
|
|
const oauthWindow = window.open(data.url, 'google_oauth', 'width=600,height=700,scrollbars=yes');
|
|
|
|
// Poll for account changes instead of relying on postMessage
|
|
// (since OAuth callback is now on port 51121, not this server)
|
|
const initialAccountCount = Alpine.store('data').accounts.length;
|
|
let pollCount = 0;
|
|
const maxPolls = 60; // 2 minutes (2 second intervals)
|
|
let cancelled = false;
|
|
|
|
// Show progress modal
|
|
Alpine.store('global').oauthProgress = {
|
|
active: true,
|
|
current: 0,
|
|
max: maxPolls,
|
|
cancel: () => {
|
|
cancelled = true;
|
|
clearInterval(pollInterval);
|
|
Alpine.store('global').oauthProgress.active = false;
|
|
Alpine.store('global').showToast(Alpine.store('global').t('oauthCancelled'), 'info');
|
|
if (oauthWindow && !oauthWindow.closed) {
|
|
oauthWindow.close();
|
|
}
|
|
}
|
|
};
|
|
|
|
const pollInterval = setInterval(async () => {
|
|
if (cancelled) {
|
|
clearInterval(pollInterval);
|
|
return;
|
|
}
|
|
|
|
pollCount++;
|
|
Alpine.store('global').oauthProgress.current = pollCount;
|
|
|
|
// Check if OAuth window was closed manually
|
|
if (oauthWindow && oauthWindow.closed && !cancelled) {
|
|
clearInterval(pollInterval);
|
|
Alpine.store('global').oauthProgress.active = false;
|
|
Alpine.store('global').showToast(Alpine.store('global').t('oauthWindowClosed'), 'warning');
|
|
return;
|
|
}
|
|
|
|
// Refresh account list
|
|
await Alpine.store('data').fetchData();
|
|
|
|
// Check if new account was added
|
|
const currentAccountCount = Alpine.store('data').accounts.length;
|
|
if (currentAccountCount > initialAccountCount) {
|
|
clearInterval(pollInterval);
|
|
Alpine.store('global').oauthProgress.active = false;
|
|
|
|
const actionKey = reAuthEmail ? 'accountReauthSuccess' : 'accountAddedSuccess';
|
|
Alpine.store('global').showToast(
|
|
Alpine.store('global').t(actionKey),
|
|
'success'
|
|
);
|
|
document.getElementById('add_account_modal')?.close();
|
|
|
|
if (oauthWindow && !oauthWindow.closed) {
|
|
oauthWindow.close();
|
|
}
|
|
}
|
|
|
|
// Stop polling after max attempts
|
|
if (pollCount >= maxPolls) {
|
|
clearInterval(pollInterval);
|
|
Alpine.store('global').oauthProgress.active = false;
|
|
Alpine.store('global').showToast(
|
|
Alpine.store('global').t('oauthTimeout'),
|
|
'warning'
|
|
);
|
|
}
|
|
}, 2000); // Poll every 2 seconds
|
|
} else {
|
|
Alpine.store('global').showToast(data.error || Alpine.store('global').t('failedToGetAuthUrl'), 'error');
|
|
}
|
|
} catch (e) {
|
|
Alpine.store('global').showToast(Alpine.store('global').t('failedToStartOAuth') + ': ' + e.message, 'error');
|
|
}
|
|
}
|
|
}));
|
|
}); |