added manual matching on books
This commit is contained in:
171
desktop/src/scripts/utils/match-modal.js
Normal file
171
desktop/src/scripts/utils/match-modal.js
Normal file
@@ -0,0 +1,171 @@
|
||||
const MatchModal = (function() {
|
||||
let _config = {
|
||||
onSearch: async (query, provider) => [], // Debe devolver Array de objetos
|
||||
onSelect: (item, provider) => {},
|
||||
provider: 'generic'
|
||||
};
|
||||
|
||||
let elements = {};
|
||||
let searchTimeout = null;
|
||||
|
||||
function init() {
|
||||
if (document.getElementById('waifu-match-modal')) return;
|
||||
|
||||
// Inyectar HTML
|
||||
const modalHTML = `
|
||||
<div class="match-modal-overlay" id="waifu-match-modal">
|
||||
<div class="match-modal-content">
|
||||
<div class="match-header">
|
||||
<h3 class="match-title">Manual Match <span id="match-provider-badge" style="opacity:0.6; font-size:0.8em; margin-left:8px;"></span></h3>
|
||||
<button class="match-close-btn" id="match-close-btn">✕</button>
|
||||
</div>
|
||||
|
||||
<div class="match-search-container">
|
||||
<input type="text" id="match-input" class="match-input" placeholder="Search title..." autocomplete="off">
|
||||
<button id="match-btn-action" class="match-search-btn">Search</button>
|
||||
</div>
|
||||
|
||||
<div class="match-results-body" id="match-results-container">
|
||||
<div class="match-msg">Type to start searching...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||||
|
||||
// Cachear elementos
|
||||
elements = {
|
||||
overlay: document.getElementById('waifu-match-modal'),
|
||||
input: document.getElementById('match-input'),
|
||||
results: document.getElementById('match-results-container'),
|
||||
badge: document.getElementById('match-provider-badge'),
|
||||
closeBtn: document.getElementById('match-close-btn'),
|
||||
searchBtn: document.getElementById('match-btn-action')
|
||||
};
|
||||
|
||||
// Event Listeners
|
||||
elements.closeBtn.onclick = close;
|
||||
elements.overlay.onclick = (e) => { if(e.target === elements.overlay) close(); };
|
||||
|
||||
// Búsqueda al hacer clic
|
||||
elements.searchBtn.onclick = () => performSearch(elements.input.value);
|
||||
|
||||
// Búsqueda al escribir (Debounce)
|
||||
elements.input.addEventListener('input', (e) => {
|
||||
clearTimeout(searchTimeout);
|
||||
if(e.target.value.trim().length === 0) return;
|
||||
searchTimeout = setTimeout(() => performSearch(e.target.value), 600);
|
||||
});
|
||||
|
||||
// Enter key
|
||||
elements.input.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') performSearch(elements.input.value);
|
||||
});
|
||||
}
|
||||
|
||||
function open(options) {
|
||||
init(); // Asegurar que el DOM existe
|
||||
|
||||
_config = { ..._config, ...options };
|
||||
|
||||
// Resetear UI
|
||||
elements.input.value = options.initialQuery || '';
|
||||
elements.results.innerHTML = '<div class="match-msg">Search above to find matches...</div>';
|
||||
elements.badge.innerText = options.provider ? `(${options.provider})` : '';
|
||||
|
||||
// Mostrar Modal
|
||||
elements.overlay.classList.add('active');
|
||||
|
||||
// Auto-search si hay query inicial
|
||||
if (options.initialQuery) {
|
||||
performSearch(options.initialQuery);
|
||||
}
|
||||
|
||||
setTimeout(() => elements.input.focus(), 100);
|
||||
}
|
||||
|
||||
function close() {
|
||||
if(elements.overlay) elements.overlay.classList.remove('active');
|
||||
}
|
||||
|
||||
async function performSearch(query) {
|
||||
if (!query || query.trim().length < 2) return;
|
||||
|
||||
elements.results.innerHTML = '<div class="match-spinner"></div>';
|
||||
|
||||
try {
|
||||
// Ejecutar la función de búsqueda pasada en la config
|
||||
const results = await _config.onSearch(query, _config.provider);
|
||||
renderResults(results);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
elements.results.innerHTML = '<div class="match-msg error">Error searching provider.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderResults(results) {
|
||||
elements.results.innerHTML = '';
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
elements.results.innerHTML = '<div class="match-msg">No matches found.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'match-list-grid';
|
||||
|
||||
results.forEach(item => {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'match-item';
|
||||
|
||||
// Normalización de datos para asegurar compatibilidad con Anime/Libros
|
||||
const img = item.coverImage?.large || item.coverImage || item.image || '/public/assets/no-image.png';
|
||||
const title = item.title?.english || item.title?.romaji || item.title || 'Unknown Title';
|
||||
const meta = item.releaseDate || item.year || item.startDate?.year || '';
|
||||
const url = item.url || item.externalUrl || null;
|
||||
|
||||
el.innerHTML = `
|
||||
<img src="${img}" class="match-poster" loading="lazy">
|
||||
<div class="match-info">
|
||||
<div class="match-item-title">${title}</div>
|
||||
<div class="match-item-meta">${meta}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Botón de enlace externo (si existe URL)
|
||||
if (url) {
|
||||
const linkBtn = document.createElement('a');
|
||||
linkBtn.href = url;
|
||||
linkBtn.target = "_blank";
|
||||
linkBtn.className = "match-link-btn";
|
||||
linkBtn.title = "View Source";
|
||||
linkBtn.innerHTML = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
</svg>
|
||||
`;
|
||||
// Evitar que el click en el enlace dispare el select
|
||||
linkBtn.onclick = (e) => e.stopPropagation();
|
||||
el.appendChild(linkBtn);
|
||||
}
|
||||
|
||||
// Click en la tarjeta selecciona
|
||||
el.onclick = () => {
|
||||
_config.onSelect(item);
|
||||
close();
|
||||
};
|
||||
|
||||
grid.appendChild(el);
|
||||
});
|
||||
|
||||
elements.results.appendChild(grid);
|
||||
}
|
||||
|
||||
return {
|
||||
open,
|
||||
close
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user