/**
* ComfyUI Mobile Interface - Main Application
* Initializes and manages the mobile interface
*/
class MobileApp {
constructor() {
this.apiClient = null;
this.mobileInterface = null;
this.isInitialized = false;
this.startTime = Date.now();
}
/**
* Initialize the mobile application
*/
async initialize() {
if (this.isInitialized) return;
console.log('Initializing ComfyUI Mobile Interface...');
try {
// Show loading state
this.showLoadingState();
// Initialize API client
this.apiClient = new ComfyUIAPIClient();
// Wait for initial connection
await this.waitForConnection();
// Initialize mobile interface
this.mobileInterface = new MobileInterface(this.apiClient);
// Setup global error handling
this.setupGlobalErrorHandling();
// Setup performance monitoring
this.setupPerformanceMonitoring();
// Setup viewport handling
this.setupViewportHandling();
// Initialize service worker if available
this.initializeServiceWorker();
this.isInitialized = true;
console.log('ComfyUI Mobile Interface initialized successfully');
// Hide loading state
this.hideLoadingState();
// Log initialization time
const initTime = Date.now() - this.startTime;
console.log(`Mobile interface initialized in ${initTime}ms`);
} catch (error) {
console.error('Failed to initialize mobile interface:', error);
this.showErrorState(error);
}
}
/**
* Wait for API connection
* @returns {Promise} Connection promise
*/
waitForConnection() {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Connection timeout'));
}, 10000);
const checkConnection = () => {
if (this.apiClient.isWebSocketConnected()) {
clearTimeout(timeout);
resolve();
} else {
// Listen for connection event
this.apiClient.on('connected', () => {
clearTimeout(timeout);
resolve();
});
this.apiClient.on('connection_error', (error) => {
clearTimeout(timeout);
reject(error);
});
}
};
// Check immediately and then wait for events
checkConnection();
});
}
/**
* Show loading state
*/
showLoadingState() {
document.body.classList.add('loading');
// Add loading overlay if not exists
if (!document.getElementById('loadingOverlay')) {
const overlay = document.createElement('div');
overlay.id = 'loadingOverlay';
overlay.innerHTML = `
Loading ComfyUI Mobile
Connecting to server...
`;
document.body.appendChild(overlay);
// Add loading styles
const style = document.createElement('style');
style.textContent = `
#loadingOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--background-color);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
text-align: center;
padding: var(--spacing-xl);
}
.loading-spinner {
font-size: 2rem;
color: var(--primary-color);
margin-bottom: var(--spacing-lg);
}
.loading-content h3 {
margin-bottom: var(--spacing-sm);
color: var(--text-primary);
}
.loading-content p {
color: var(--text-secondary);
}
`;
document.head.appendChild(style);
}
}
/**
* Hide loading state
*/
hideLoadingState() {
document.body.classList.remove('loading');
const overlay = document.getElementById('loadingOverlay');
if (overlay) {
overlay.style.opacity = '0';
setTimeout(() => overlay.remove(), 300);
}
}
/**
* Show error state
* @param {Error} error - Error object
*/
showErrorState(error) {
const errorOverlay = document.createElement('div');
errorOverlay.id = 'errorOverlay';
errorOverlay.innerHTML = `
Connection Error
${error.message}
`;
// Add error styles
const style = document.createElement('style');
style.textContent = `
#errorOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--background-color);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.error-content {
text-align: center;
padding: var(--spacing-xl);
max-width: 400px;
}
.error-icon {
font-size: 3rem;
color: var(--error-color);
margin-bottom: var(--spacing-lg);
}
.error-content h3 {
margin-bottom: var(--spacing-sm);
color: var(--text-primary);
}
.error-content p {
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
}
.error-content button {
margin: var(--spacing-sm);
}
`;
document.head.appendChild(style);
// Replace loading overlay
const loadingOverlay = document.getElementById('loadingOverlay');
if (loadingOverlay) {
loadingOverlay.remove();
}
document.body.appendChild(errorOverlay);
// Add event listeners
document.getElementById('retryBtn').addEventListener('click', () => {
errorOverlay.remove();
this.initialize();
});
document.getElementById('desktopBtn').addEventListener('click', () => {
window.location.href = '/';
});
}
/**
* Setup global error handling
*/
setupGlobalErrorHandling() {
// Handle uncaught errors
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
Utils.showToast('An error occurred: ' + event.error.message, 'error');
});
// Handle unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
Utils.showToast('An error occurred: ' + event.reason, 'error');
});
// Handle API errors
this.apiClient.on('connection_error', (error) => {
console.error('API connection error:', error);
Utils.showToast('Connection error: ' + error.message, 'error');
});
}
/**
* Setup performance monitoring
*/
setupPerformanceMonitoring() {
// Monitor memory usage
if ('memory' in performance) {
setInterval(() => {
const memory = performance.memory;
if (memory.usedJSHeapSize > memory.jsHeapSizeLimit * 0.9) {
console.warn('High memory usage detected');
}
}, 30000); // Check every 30 seconds
}
// Monitor long tasks
if ('PerformanceObserver' in window) {
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
if (entry.duration > 50) { // Tasks longer than 50ms
console.warn('Long task detected:', entry.duration + 'ms');
}
});
});
observer.observe({ entryTypes: ['longtask'] });
} catch (e) {
console.log('Long task monitoring not supported');
}
}
}
/**
* Setup viewport handling
*/
setupViewportHandling() {
// Handle orientation changes
window.addEventListener('orientationchange', () => {
setTimeout(() => {
// Refresh layout after orientation change
if (this.mobileInterface) {
this.mobileInterface.refreshWorkflow();
}
}, 100);
});
// Handle viewport changes (e.g., virtual keyboard)
const viewportHandler = Utils.debounce(() => {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
}, 100);
window.addEventListener('resize', viewportHandler);
window.addEventListener('orientationchange', viewportHandler);
// Initial viewport setup
viewportHandler();
// Handle visibility changes
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// App is hidden
console.log('App hidden');
} else {
// App is visible
console.log('App visible');
if (this.mobileInterface) {
this.mobileInterface.refreshWorkflow();
}
}
});
}
/**
* Initialize service worker
*/
async initializeServiceWorker() {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service worker registered:', registration);
} catch (error) {
console.log('Service worker registration failed:', error);
}
}
}
/**
* Setup keyboard shortcuts
*/
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + S to save
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
if (this.mobileInterface) {
this.mobileInterface.showSaveWorkflowDialog();
}
}
// Ctrl/Cmd + O to open
if ((e.ctrlKey || e.metaKey) && e.key === 'o') {
e.preventDefault();
if (this.mobileInterface) {
this.mobileInterface.showLoadWorkflowDialog();
}
}
// Escape to cancel connection
if (e.key === 'Escape') {
if (this.mobileInterface && this.mobileInterface.connectionState.active) {
this.mobileInterface.cancelConnection();
}
}
// Space to queue prompt
if (e.key === ' ' && !e.target.matches('input, textarea')) {
e.preventDefault();
if (this.mobileInterface) {
this.mobileInterface.queueCurrentWorkflow();
}
}
});
}
/**
* Get app statistics
* @returns {Object} App statistics
*/
getStatistics() {
return {
initialized: this.isInitialized,
initTime: Date.now() - this.startTime,
connected: this.apiClient ? this.apiClient.isWebSocketConnected() : false,
nodesCount: this.mobileInterface ? this.mobileInterface.linearizedNodes.length : 0,
memory: performance.memory ? {
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024),
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024),
limit: Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024)
} : null,
viewport: Utils.getViewportSize(),
touchDevice: Utils.isTouchDevice()
};
}
/**
* Cleanup application
*/
cleanup() {
if (this.apiClient) {
this.apiClient.disconnect();
}
// Remove event listeners
window.removeEventListener('error', this.handleError);
window.removeEventListener('unhandledrejection', this.handleRejection);
this.isInitialized = false;
console.log('Mobile application cleaned up');
}
}
// Initialize the mobile app when DOM is ready
let mobileApp;
let mobileInterface;
document.addEventListener('DOMContentLoaded', async () => {
// Check if this is a mobile device or small screen
const isMobile = Utils.isTouchDevice() || window.innerWidth < 768;
if (isMobile) {
console.log('Mobile device detected, initializing mobile interface...');
} else {
console.log('Desktop device detected, mobile interface available');
}
// Initialize the app
mobileApp = new MobileApp();
await mobileApp.initialize();
// Make mobile interface globally available for debugging
mobileInterface = mobileApp.mobileInterface;
// Expose to global scope for debugging
window.mobileApp = mobileApp;
window.mobileInterface = mobileInterface;
// Setup keyboard shortcuts
mobileApp.setupKeyboardShortcuts();
// Log app statistics
console.log('App Statistics:', mobileApp.getStatistics());
});
// Handle page unload
window.addEventListener('beforeunload', () => {
if (mobileApp) {
mobileApp.cleanup();
}
});
// Handle app install prompt (PWA)
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
// Show install button/banner
Utils.showToast('Install ComfyUI Mobile for offline access', 'info', 5000);
});
// Handle successful app installation
window.addEventListener('appinstalled', (evt) => {
console.log('App installed successfully');
Utils.showToast('ComfyUI Mobile installed successfully', 'success');
});
// Export for use in other contexts
if (typeof module !== 'undefined' && module.exports) {
module.exports = { MobileApp, Utils, GraphLinearization, ComfyUIAPIClient, MobileInterface };
}