import * as plugins from '../plugins.js'; import { apiService } from '../services/index.js'; const { html, css, cssManager, customElement, property, state, DeesElement } = plugins; @customElement('tsview-mongo-document') export class TsviewMongoDocument extends DeesElement { @property({ type: String }) public accessor databaseName: string = ''; @property({ type: String }) public accessor collectionName: string = ''; @property({ type: String }) public accessor documentId: string = ''; @state() private accessor document: Record | null = null; @state() private accessor loading: boolean = false; @state() private accessor editing: boolean = false; @state() private accessor editContent: string = ''; @state() private accessor error: string = ''; public static styles = [ cssManager.defaultStyles, css` :host { display: block; height: 100%; } .document-container { display: flex; flex-direction: column; height: 100%; } .header { padding: 12px; border-bottom: 1px solid #333; display: flex; align-items: center; justify-content: space-between; } .header-title { font-size: 14px; font-weight: 500; } .header-actions { display: flex; gap: 8px; } .action-btn { padding: 6px 12px; background: transparent; border: 1px solid #444; color: #888; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.15s; } .action-btn:hover { border-color: #666; color: #aaa; } .action-btn.primary { background: rgba(99, 102, 241, 0.2); border-color: #6366f1; color: #818cf8; } .action-btn.primary:hover { background: rgba(99, 102, 241, 0.3); } .action-btn.danger { background: rgba(239, 68, 68, 0.2); border-color: #ef4444; color: #f87171; } .action-btn.danger:hover { background: rgba(239, 68, 68, 0.3); } .content { flex: 1; overflow: auto; padding: 12px; } .json-view { font-family: 'Monaco', 'Menlo', monospace; font-size: 12px; line-height: 1.6; white-space: pre-wrap; word-break: break-all; color: #ccc; } .json-key { color: #818cf8; } .json-string { color: #a5d6a7; } .json-number { color: #fbbf24; } .json-boolean { color: #f87171; } .json-null { color: #888; } .edit-area { width: 100%; height: 100%; background: rgba(0, 0, 0, 0.3); border: 1px solid #444; border-radius: 6px; color: #fff; font-family: 'Monaco', 'Menlo', monospace; font-size: 12px; line-height: 1.6; padding: 12px; resize: none; } .edit-area:focus { outline: none; border-color: #6366f1; } .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #666; text-align: center; padding: 24px; } .empty-state svg { width: 48px; height: 48px; margin-bottom: 12px; opacity: 0.5; } .loading-state { display: flex; align-items: center; justify-content: center; height: 100%; color: #888; } .error-state { padding: 16px; color: #f87171; text-align: center; } `, ]; updated(changedProperties: Map) { if (changedProperties.has('documentId')) { this.editing = false; if (this.documentId) { this.loadDocument(); } else { this.document = null; } } } private async loadDocument() { if (!this.documentId || !this.databaseName || !this.collectionName) return; this.loading = true; this.error = ''; try { this.document = await apiService.getDocument( this.databaseName, this.collectionName, this.documentId ); } catch (err) { console.error('Error loading document:', err); this.error = 'Failed to load document'; } this.loading = false; } private startEditing() { this.editContent = JSON.stringify(this.document, null, 2); this.editing = true; } private cancelEditing() { this.editing = false; this.editContent = ''; } private async saveDocument() { try { const updatedDoc = JSON.parse(this.editContent); // Remove _id from update (can't update _id) const { _id, ...updateFields } = updatedDoc; await apiService.updateDocument( this.databaseName, this.collectionName, this.documentId, updateFields ); this.editing = false; await this.loadDocument(); this.dispatchEvent( new CustomEvent('document-updated', { detail: { documentId: this.documentId }, bubbles: true, composed: true, }) ); } catch (err) { console.error('Error saving document:', err); this.error = 'Invalid JSON or save failed'; } } private async deleteDocument() { if (!confirm('Delete this document?')) return; try { await apiService.deleteDocument( this.databaseName, this.collectionName, this.documentId ); this.dispatchEvent( new CustomEvent('document-deleted', { detail: { documentId: this.documentId }, bubbles: true, composed: true, }) ); this.document = null; } catch (err) { console.error('Error deleting document:', err); this.error = 'Delete failed'; } } private formatJson(obj: unknown): string { return JSON.stringify(obj, null, 2); } private syntaxHighlight(json: string): string { // Basic syntax highlighting return json .replace(/"([^"]+)":/g, '"$1":') .replace(/: "([^"]*)"/g, ': "$1"') .replace(/: (\d+\.?\d*)/g, ': $1') .replace(/: (true|false)/g, ': $1') .replace(/: (null)/g, ': $1'); } render() { if (!this.documentId) { return html`

Select a document to view

`; } if (this.loading) { return html`
Loading...
`; } if (this.error && !this.document) { return html`
${this.error}
`; } return html`
Document
${this.editing ? html` ` : html` `}
${this.editing ? html` ` : html`
`}
`; } }