faster smaller
This commit is contained in:
@@ -46,6 +46,8 @@ class QuixoticApp {
|
||||
private welcomePlaceholder!: HTMLElement;
|
||||
private searchTimeout?: NodeJS.Timeout;
|
||||
private currentVideos: VideoResult[] = [];
|
||||
private loadingStartTime: number = 0;
|
||||
private minLoadingDuration: number = 400; // Минимальное время показа спиннера (ms)
|
||||
|
||||
constructor() {
|
||||
this.tg = (window as WindowWithTelegram).Telegram?.WebApp;
|
||||
@@ -91,10 +93,13 @@ class QuixoticApp {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set new timeout for search (500ms delay)
|
||||
// Show loading spinner immediately for better UX feedback
|
||||
this.showLoading();
|
||||
|
||||
// Set new timeout for search (300ms delay - best practice for instant search)
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.search();
|
||||
}, 500);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Still handle Enter key for immediate search
|
||||
@@ -148,14 +153,35 @@ class QuixoticApp {
|
||||
}
|
||||
|
||||
const data: SearchResponse = await response.json();
|
||||
|
||||
// Ensure minimum loading time for better UX (prevents flashing)
|
||||
const elapsedTime = Date.now() - this.loadingStartTime;
|
||||
const remainingTime = Math.max(0, this.minLoadingDuration - elapsedTime);
|
||||
|
||||
if (remainingTime > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
|
||||
this.displayResults(data.videos);
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
|
||||
// Ensure minimum loading time even for errors
|
||||
const elapsedTime = Date.now() - this.loadingStartTime;
|
||||
const remainingTime = Math.max(0, this.minLoadingDuration - elapsedTime);
|
||||
|
||||
if (remainingTime > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
|
||||
this.showNoResults();
|
||||
}
|
||||
}
|
||||
|
||||
private showLoading(): void {
|
||||
// Record loading start time
|
||||
this.loadingStartTime = Date.now();
|
||||
|
||||
// Clear any existing status messages
|
||||
const existingMessage = document.querySelector('.tg-status-message');
|
||||
if (existingMessage) {
|
||||
@@ -201,8 +227,15 @@ class QuixoticApp {
|
||||
this.noResults.classList.add('tg-hidden');
|
||||
this.noResults.style.display = 'none';
|
||||
|
||||
this.results.innerHTML = videos.map(video => `
|
||||
<div class='tg-list-item' onclick='app.convertVideo("${video.id}", "${this.escapeHtml(video.title)}", "${this.escapeHtml(video.url)}")'>
|
||||
// Use DocumentFragment for better performance
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
videos.forEach(video => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'tg-list-item';
|
||||
item.dataset.videoId = video.id;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class='tg-list-item__content'>
|
||||
<div class='tg-list-item__media'>
|
||||
<img class='tg-list-item__thumbnail'
|
||||
@@ -217,29 +250,35 @@ class QuixoticApp {
|
||||
<div class='tg-list-item__subtitle'>${this.escapeHtml(video.channel)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Add touch event listeners to prevent sticky states
|
||||
this.results.querySelectorAll('.tg-list-item').forEach(item => {
|
||||
const element = item as HTMLElement;
|
||||
`;
|
||||
|
||||
// Use event listener instead of inline onclick
|
||||
item.addEventListener('click', () => {
|
||||
this.convertVideo(video.id, video.title, video.url);
|
||||
});
|
||||
|
||||
// Reset visual state on touch end
|
||||
element.addEventListener('touchend', () => {
|
||||
item.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
element.blur();
|
||||
element.style.background = '';
|
||||
element.style.transform = '';
|
||||
item.blur();
|
||||
item.style.background = '';
|
||||
item.style.transform = '';
|
||||
}, 100);
|
||||
});
|
||||
}, { passive: true });
|
||||
|
||||
// Also handle mouse leave for desktop
|
||||
element.addEventListener('mouseleave', () => {
|
||||
element.style.background = '';
|
||||
element.style.transform = '';
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.background = '';
|
||||
item.style.transform = '';
|
||||
});
|
||||
|
||||
fragment.appendChild(item);
|
||||
});
|
||||
|
||||
// Clear and append once
|
||||
this.results.innerHTML = '';
|
||||
this.results.appendChild(fragment);
|
||||
|
||||
// Lazy load images with IntersectionObserver
|
||||
const imageObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
@@ -281,8 +320,8 @@ class QuixoticApp {
|
||||
const performer = video?.channel || 'Unknown Artist';
|
||||
const thumbnail = video?.thumbnail || '';
|
||||
|
||||
// Find the clicked element by looking for the one that contains this videoId
|
||||
const videoElement = document.querySelector(`[onclick*="${videoId}"]`) as HTMLElement;
|
||||
// Find the clicked element using data attribute
|
||||
const videoElement = document.querySelector(`[data-video-id="${videoId}"]`) as HTMLElement;
|
||||
if (videoElement) {
|
||||
// Force blur to reset any active/focus states
|
||||
videoElement.blur();
|
||||
|
||||
Reference in New Issue
Block a user