UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

585 lines 20 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SearchPanel = void 0; const blessed_1 = __importDefault(require("blessed")); class SearchPanel { constructor(screen, eventBus, config = {}) { this.isVisible = false; this.searchHistory = []; this.currentQuery = ''; this.currentResults = []; this.selectedIndex = 0; this.dataSource = []; this.filterCriteria = {}; this.screen = screen; this.eventBus = eventBus; this.config = { placeholder: 'Search...', caseSensitive: false, mode: 'fuzzy', searchFields: ['name', 'id', 'status'], maxHistory: 10, showSuggestions: true, minChars: 1, debounceDelay: 300, ...config, }; } show() { if (this.isVisible) { this.hide(); } this.createSearchInterface(); if (this.searchBox && this.resultsBox && this.statusBox) { this.isVisible = true; this.eventBus.emit('search:shown', { timestamp: new Date(), }); } } hide() { if (!this.isVisible) return; try { if (this.searchBox) { this.screen.remove(this.searchBox); this.searchBox = null; } if (this.resultsBox) { this.screen.remove(this.resultsBox); this.resultsBox = null; } if (this.statusBox) { this.screen.remove(this.statusBox); this.statusBox = null; } this.isVisible = false; this.currentQuery = ''; this.currentResults = []; this.selectedIndex = 0; if (this.searchTimeout) { clearTimeout(this.searchTimeout); this.searchTimeout = undefined; } this.screen.render(); this.eventBus.emit('search:hidden', { timestamp: new Date(), }); } catch (error) { console.error('Failed to hide search panel:', error); this.isVisible = false; } } setDataSource(data) { this.dataSource = data; if (this.currentQuery) { this.performSearch(this.currentQuery); } } setFilterCriteria(criteria) { this.filterCriteria = criteria; if (this.currentQuery) { this.performSearch(this.currentQuery); } } getResults() { return this.currentResults; } getSelectedResult() { if (this.selectedIndex >= 0 && this.selectedIndex < this.currentResults.length) { return this.currentResults[this.selectedIndex] || null; } return null; } isSearchVisible() { return this.isVisible; } clearSearch() { this.currentQuery = ''; this.currentResults = []; this.selectedIndex = 0; if (this.searchBox) { this.searchBox.clearValue(); } this.updateResultsDisplay(); this.updateStatusDisplay(); } navigateUp() { if (this.currentResults.length === 0) return; this.selectedIndex = this.selectedIndex === 0 ? this.currentResults.length - 1 : this.selectedIndex - 1; this.updateResultsDisplay(); } navigateDown() { if (this.currentResults.length === 0) return; this.selectedIndex = (this.selectedIndex + 1) % this.currentResults.length; this.updateResultsDisplay(); } selectResult() { const selected = this.getSelectedResult(); if (selected) { this.eventBus.emit('search:result:selected', { result: selected, query: this.currentQuery, timestamp: new Date(), }); this.addToHistory(this.currentQuery); } } createSearchInterface() { try { const screenWidth = this.screen.width || 80; const screenHeight = this.screen.height || 24; this.searchBox = blessed_1.default.textbox({ top: 2, left: 'center', width: Math.min(60, screenWidth - 4), height: 3, label: ' Search ', border: { type: 'line', }, style: { fg: 'white', bg: 'black', border: { fg: 'cyan', }, focus: { border: { fg: 'yellow', }, }, }, inputOnFocus: true, keys: true, mouse: true, }); this.resultsBox = blessed_1.default.box({ top: 6, left: 'center', width: Math.min(60, screenWidth - 4), height: Math.min(15, screenHeight - 12), label: ' Results ', border: { type: 'line', }, style: { fg: 'white', bg: 'black', border: { fg: 'gray', }, }, scrollable: true, alwaysScroll: true, keys: true, mouse: true, tags: true, }); this.statusBox = blessed_1.default.box({ top: screenHeight - 3, left: 'center', width: Math.min(60, screenWidth - 4), height: 3, content: 'Press Ctrl+F to search, Esc to close', border: { type: 'line', }, style: { fg: 'gray', bg: 'black', border: { fg: 'gray', }, }, tags: true, }); this.setupSearchEvents(); this.screen.append(this.searchBox); this.screen.append(this.resultsBox); this.screen.append(this.statusBox); this.searchBox.focus(); this.screen.render(); } catch (error) { console.error('Failed to create search interface:', error); this.isVisible = false; this.searchBox = null; this.resultsBox = null; this.statusBox = null; } } setupSearchEvents() { if (!this.searchBox || !this.resultsBox) return; this.searchBox.on('submit', () => { this.selectResult(); }); this.searchBox.key(['escape'], () => { this.hide(); }); this.searchBox.key(['up'], () => { this.navigateUp(); }); this.searchBox.key(['down'], () => { this.navigateDown(); }); this.searchBox.key(['enter'], () => { this.selectResult(); }); this.searchBox.on('keypress', (_ch, key) => { if (key && (key.name === 'escape' || key.name === 'enter' || key.name === 'up' || key.name === 'down')) { return; } setTimeout(() => { const query = this.searchBox.getValue(); if (query !== this.currentQuery) { this.handleSearchInput(query); } }, 10); }); this.resultsBox.key(['escape'], () => { this.hide(); }); this.resultsBox.key(['enter'], () => { this.selectResult(); }); this.resultsBox.on('click', () => { this.selectResult(); }); } handleSearchInput(query) { this.currentQuery = query; if (this.searchTimeout) { clearTimeout(this.searchTimeout); } this.searchTimeout = setTimeout(() => { this.performSearch(query); }, this.config.debounceDelay); } performSearch(query) { try { if (!query || query.length < (this.config.minChars || 1)) { this.currentResults = []; this.selectedIndex = 0; this.updateResultsDisplay(); this.updateStatusDisplay(); return; } const filteredData = this.applyFilters(this.dataSource); let results; switch (this.config.mode) { case 'fuzzy': results = this.fuzzySearch(query, filteredData); break; case 'contains': results = this.containsSearch(query, filteredData); break; case 'exact': default: results = this.exactSearch(query, filteredData); break; } this.currentResults = results; this.selectedIndex = 0; this.updateResultsDisplay(); this.updateStatusDisplay(); this.eventBus.emit('search:results:updated', { query, results: this.currentResults, timestamp: new Date(), }); } catch (error) { console.error('Search failed:', error); this.currentResults = []; this.updateResultsDisplay(); this.updateStatusDisplay(); } } applyFilters(data) { return data.filter(item => { if (this.filterCriteria.status && this.filterCriteria.status.length > 0) { if (!this.filterCriteria.status.includes(item.status)) { return false; } } if (this.filterCriteria.type && this.filterCriteria.type.length > 0) { if (!this.filterCriteria.type.includes(item.type)) { return false; } } if (this.filterCriteria.dateRange) { const itemDate = new Date(item.createdAt || item.updatedAt || Date.now()); if (this.filterCriteria.dateRange.start && itemDate < this.filterCriteria.dateRange.start) { return false; } if (this.filterCriteria.dateRange.end && itemDate > this.filterCriteria.dateRange.end) { return false; } } if (this.filterCriteria.custom && !this.filterCriteria.custom(item)) { return false; } return true; }); } fuzzySearch(query, data) { const searchQuery = this.config.caseSensitive ? query : query.toLowerCase(); const results = []; data.forEach((item, index) => { const matches = []; let totalScore = 0; for (const field of this.config.searchFields || []) { const fieldValue = this.getFieldValue(item, field); if (!fieldValue) continue; const searchValue = this.config.caseSensitive ? fieldValue : fieldValue.toLowerCase(); const match = this.calculateFuzzyMatch(searchQuery, searchValue, field); if (match.score > 0) { matches.push(match); totalScore += match.score; } } if (matches.length > 0) { results.push({ item, score: totalScore / matches.length, matches, index, }); } }); return results.sort((a, b) => b.score - a.score); } exactSearch(query, data) { const searchQuery = this.config.caseSensitive ? query : query.toLowerCase(); const results = []; data.forEach((item, index) => { const matches = []; for (const field of this.config.searchFields || []) { const fieldValue = this.getFieldValue(item, field); if (!fieldValue) continue; const searchValue = this.config.caseSensitive ? fieldValue : fieldValue.toLowerCase(); if (searchValue.includes(searchQuery)) { const match = this.calculateExactMatch(searchQuery, searchValue, fieldValue, field); matches.push(match); } } if (matches.length > 0) { results.push({ item, score: 1.0, matches, index, }); } }); return results; } containsSearch(query, data) { const searchQuery = this.config.caseSensitive ? query : query.toLowerCase(); const results = []; data.forEach((item, index) => { const matches = []; for (const field of this.config.searchFields || []) { const fieldValue = this.getFieldValue(item, field); if (!fieldValue) continue; const searchValue = this.config.caseSensitive ? fieldValue : fieldValue.toLowerCase(); if (searchValue.includes(searchQuery)) { const match = this.calculateExactMatch(searchQuery, searchValue, fieldValue, field); matches.push(match); } } if (matches.length > 0) { results.push({ item, score: 0.8, matches, index, }); } }); return results; } calculateFuzzyMatch(query, value, field) { const positions = []; let score = 0; let queryIndex = 0; for (let i = 0; i < value.length && queryIndex < query.length; i++) { if (value[i] === query[queryIndex]) { positions.push(i); queryIndex++; score += 1; } } if (queryIndex === query.length) { score = score / query.length * (query.length / value.length); } else { score = 0; } const highlighted = this.highlightMatches(value, positions); return { field, value, highlighted, positions, score, }; } calculateExactMatch(query, searchValue, originalValue, field) { const positions = []; let startIndex = 0; while (true) { const index = searchValue.indexOf(query, startIndex); if (index === -1) break; for (let i = 0; i < query.length; i++) { positions.push(index + i); } startIndex = index + 1; } const highlighted = this.highlightMatches(originalValue, positions); return { field, value: originalValue, highlighted, positions, score: 1.0, }; } highlightMatches(text, positions) { if (positions.length === 0) return text; let highlighted = ''; let inHighlight = false; for (let i = 0; i < text.length; i++) { const shouldHighlight = positions.includes(i); if (shouldHighlight && !inHighlight) { highlighted += '{yellow-fg}'; inHighlight = true; } else if (!shouldHighlight && inHighlight) { highlighted += '{/yellow-fg}'; inHighlight = false; } highlighted += text[i]; } if (inHighlight) { highlighted += '{/yellow-fg}'; } return highlighted; } getFieldValue(item, field) { const keys = field.split('.'); let value = item; for (const key of keys) { if (value && typeof value === 'object' && key in value) { value = value[key]; } else { return ''; } } return value ? String(value) : ''; } updateResultsDisplay() { if (!this.resultsBox) return; try { if (this.currentResults.length === 0) { this.resultsBox.setContent(this.currentQuery ? 'No results found' : 'Start typing to search...'); } else { const lines = []; this.currentResults.forEach((result, index) => { const isSelected = index === this.selectedIndex; const prefix = isSelected ? '▶ ' : ' '; const primaryMatch = result.matches[0]; if (!primaryMatch) return; const displayText = `${prefix}${primaryMatch.highlighted}`; if (isSelected) { lines.push(`{cyan-fg}${displayText}{/cyan-fg}`); } else { lines.push(displayText); } if (result.matches.length > 1) { result.matches.slice(1).forEach(match => { lines.push(` ${match.field}: ${match.highlighted}`); }); } }); this.resultsBox.setContent(lines.join('\n')); } this.screen.render(); } catch (error) { console.error('Failed to update results display:', error); } } updateStatusDisplay() { if (!this.statusBox) return; try { let status = ''; if (this.currentQuery) { status = `Found ${this.currentResults.length} results for "${this.currentQuery}"`; if (this.currentResults.length > 0) { status += ` | ${this.selectedIndex + 1}/${this.currentResults.length}`; } } else { status = 'Type to search • ↑↓ Navigate • Enter Select • Esc Close'; } this.statusBox.setContent(status); this.screen.render(); } catch (error) { console.error('Failed to update status display:', error); } } addToHistory(query) { if (!query || query.trim() === '' || this.searchHistory.includes(query)) return; this.searchHistory.unshift(query); if (this.searchHistory.length > (this.config.maxHistory || 10)) { this.searchHistory = this.searchHistory.slice(0, this.config.maxHistory); } this.eventBus.emit('search:history:updated', { history: this.searchHistory, timestamp: new Date(), }); } getSearchHistory() { return [...this.searchHistory]; } clearHistory() { this.searchHistory = []; this.eventBus.emit('search:history:cleared', { timestamp: new Date(), }); } destroy() { this.hide(); if (this.searchTimeout) { clearTimeout(this.searchTimeout); this.searchTimeout = undefined; } } } exports.SearchPanel = SearchPanel; //# sourceMappingURL=search-panel.js.map