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>
This commit is contained in:
230
public/claude-ide/preview-manager.js
Normal file
230
public/claude-ide/preview-manager.js
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user