Files
SuperCharged-Claude-Code-Up…/public/claude-ide/preview-manager.js
uroma 0dd2083556 Initial commit: Obsidian Web Interface for Claude Code
- 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>
2026-01-19 16:29:44 +00:00

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;
}