- Full IDE with terminal integration using xterm.js - Session management with local and web sessions - HTML preview functionality - Multi-terminal support with session picker Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
231 lines
5.9 KiB
JavaScript
231 lines
5.9 KiB
JavaScript
/**
|
|
* Live Preview Manager
|
|
* Manages application preview in an iframe
|
|
*/
|
|
|
|
class PreviewManager {
|
|
constructor() {
|
|
this.previewUrl = null;
|
|
this.previewServer = null;
|
|
this.previewPort = null;
|
|
this.isPreviewRunning = false;
|
|
}
|
|
|
|
/**
|
|
* Start preview server
|
|
*/
|
|
async startPreview(sessionId, workingDir) {
|
|
console.log('Starting preview for session:', sessionId);
|
|
|
|
try {
|
|
const res = await fetch(`/claude/api/claude/sessions/${sessionId}/preview/start`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ workingDir })
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error(data.error || 'Failed to start preview');
|
|
}
|
|
|
|
this.previewUrl = data.url;
|
|
this.previewPort = data.port;
|
|
this.previewServer = data.processId;
|
|
this.isPreviewRunning = true;
|
|
|
|
console.log('Preview started:', this.previewUrl);
|
|
|
|
// Show preview panel
|
|
this.showPreviewPanel(this.previewUrl);
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error starting preview:', error);
|
|
this.showError(error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop preview server
|
|
*/
|
|
async stopPreview(sessionId) {
|
|
console.log('Stopping preview for session:', sessionId);
|
|
|
|
try {
|
|
const res = await fetch(`/claude/api/claude/sessions/${sessionId}/preview/stop`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
throw new Error(data.error || 'Failed to stop preview');
|
|
}
|
|
|
|
this.isPreviewRunning = false;
|
|
this.previewUrl = null;
|
|
this.previewServer = null;
|
|
|
|
// Hide preview panel
|
|
this.hidePreviewPanel();
|
|
|
|
console.log('Preview stopped');
|
|
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error stopping preview:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh preview
|
|
*/
|
|
refreshPreview() {
|
|
if (!this.previewUrl || !this.isPreviewRunning) {
|
|
console.warn('No preview to refresh');
|
|
return;
|
|
}
|
|
|
|
const iframe = document.getElementById('preview-iframe');
|
|
if (iframe) {
|
|
iframe.src = iframe.src; // Reload iframe
|
|
console.log('Preview refreshed');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show preview panel
|
|
*/
|
|
showPreviewPanel(url) {
|
|
// Remove existing panel
|
|
const existing = document.querySelector('.preview-panel');
|
|
if (existing) {
|
|
existing.remove();
|
|
}
|
|
|
|
// Create preview panel
|
|
const previewPanel = document.createElement('div');
|
|
previewPanel.className = 'preview-panel';
|
|
previewPanel.innerHTML = `
|
|
<div class="preview-header">
|
|
<div class="preview-title">
|
|
<h3>Live Preview</h3>
|
|
<span class="preview-url">${url}</span>
|
|
</div>
|
|
<div class="preview-actions">
|
|
<button class="btn btn-secondary btn-sm" onclick="previewManager.refreshPreview()" title="Refresh">
|
|
🔄 Refresh
|
|
</button>
|
|
<button class="btn btn-secondary btn-sm" onclick="previewManager.openInNewTab()" title="Open in new tab">
|
|
🔗 Open
|
|
</button>
|
|
<button class="btn btn-danger btn-sm" onclick="stopCurrentPreview()" title="Stop preview">
|
|
✕ Stop
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="preview-content">
|
|
<iframe id="preview-iframe" src="${url}" sandbox="allow-scripts allow-same-origin allow-forms allow-popups"></iframe>
|
|
</div>
|
|
<div class="preview-status">
|
|
<span class="status-dot status-running"></span>
|
|
<span>Preview running on ${this.previewPort || 'local port'}</span>
|
|
</div>
|
|
`;
|
|
|
|
// Add to page - find best place to insert
|
|
const chatView = document.getElementById('chat-view');
|
|
if (chatView) {
|
|
chatView.appendChild(previewPanel);
|
|
}
|
|
|
|
previewPanel.style.display = 'block';
|
|
}
|
|
|
|
/**
|
|
* Hide preview panel
|
|
*/
|
|
hidePreviewPanel() {
|
|
const panel = document.querySelector('.preview-panel');
|
|
if (panel) {
|
|
panel.remove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open preview in new tab
|
|
*/
|
|
openInNewTab() {
|
|
if (this.previewUrl) {
|
|
window.open(this.previewUrl, '_blank');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show error message
|
|
*/
|
|
showError(message) {
|
|
// Remove existing error
|
|
const existing = document.querySelector('.preview-error');
|
|
if (existing) {
|
|
existing.remove();
|
|
}
|
|
|
|
const error = document.createElement('div');
|
|
error.className = 'preview-error';
|
|
error.innerHTML = `
|
|
<div class="error-header">
|
|
<span class="error-icon">⚠️</span>
|
|
<h4>Preview Error</h4>
|
|
<button class="btn-close" onclick="this.closest('.preview-error').remove()">✕</button>
|
|
</div>
|
|
<div class="error-message">${message}</div>
|
|
`;
|
|
|
|
const chatView = document.getElementById('chat-view');
|
|
if (chatView) {
|
|
chatView.appendChild(error);
|
|
}
|
|
|
|
error.style.display = 'block';
|
|
|
|
// Auto-hide after 5 seconds
|
|
setTimeout(() => {
|
|
error.remove();
|
|
}, 5000);
|
|
}
|
|
|
|
/**
|
|
* Update preview status
|
|
*/
|
|
updateStatus(status, message) {
|
|
const statusElement = document.querySelector('.preview-status');
|
|
if (statusElement) {
|
|
const dotClass = status === 'running' ? 'status-running' : 'status-stopped';
|
|
statusElement.innerHTML = `
|
|
<span class="status-dot ${dotClass}"></span>
|
|
<span>${message}</span>
|
|
`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global instance
|
|
const previewManager = new PreviewManager();
|
|
|
|
// Global function to stop preview
|
|
window.stopCurrentPreview = async function() {
|
|
if (window.chatSessionId) {
|
|
await previewManager.stopPreview(window.chatSessionId);
|
|
}
|
|
};
|
|
|
|
// Export for use in other files
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = PreviewManager;
|
|
}
|