polyv-live-cli
Version:
CLI tool for managing PolyV live streaming services.
585 lines • 20 kB
JavaScript
"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