mirror of
https://github.com/ryankazokas/turbovault-app.git
synced 2026-04-17 05:22:51 +00:00
Moving to github
This commit is contained in:
282
app/javascript/controllers/igdb_search_controller.js
Normal file
282
app/javascript/controllers/igdb_search_controller.js
Normal file
@@ -0,0 +1,282 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [
|
||||
"query",
|
||||
"results",
|
||||
"igdbId",
|
||||
"title",
|
||||
"summary",
|
||||
"platformSelect",
|
||||
"customGameToggle",
|
||||
"igdbSection",
|
||||
"manualSection"
|
||||
]
|
||||
|
||||
static values = {
|
||||
url: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
console.log("IGDB Search controller connected!")
|
||||
console.log("Search URL:", this.urlValue)
|
||||
console.log("Query target exists:", this.hasQueryTarget)
|
||||
console.log("Results target exists:", this.hasResultsTarget)
|
||||
console.log("Title target exists:", this.hasTitleTarget)
|
||||
this.timeout = null
|
||||
this.selectedGame = null
|
||||
this.searchResults = []
|
||||
}
|
||||
|
||||
search() {
|
||||
console.log("Search triggered")
|
||||
clearTimeout(this.timeout)
|
||||
|
||||
const query = this.queryTarget.value.trim()
|
||||
console.log("Query:", query)
|
||||
|
||||
if (query.length < 2) {
|
||||
console.log("Query too short, hiding results")
|
||||
this.hideResults()
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Starting search timeout...")
|
||||
this.timeout = setTimeout(() => {
|
||||
this.performSearch(query)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
async performSearch(query) {
|
||||
const platformId = this.hasPlatformSelectTarget ? this.platformSelectTarget.value : ""
|
||||
const url = `${this.urlValue}?q=${encodeURIComponent(query)}&platform_id=${platformId}`
|
||||
|
||||
console.log("Fetching:", url)
|
||||
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
console.log("Response status:", response.status)
|
||||
const results = await response.json()
|
||||
console.log("Results:", results)
|
||||
this.displayResults(results)
|
||||
} catch (error) {
|
||||
console.error("IGDB search error:", error)
|
||||
}
|
||||
}
|
||||
|
||||
displayResults(results) {
|
||||
if (results.length === 0) {
|
||||
this.resultsTarget.innerHTML = `
|
||||
<div class="p-4 text-sm text-gray-500 text-center border-t">
|
||||
No games found. <a href="#" data-action="click->igdb-search#showManualEntry" class="text-indigo-600 hover:text-indigo-800">Add custom game</a>
|
||||
</div>
|
||||
`
|
||||
this.resultsTarget.classList.remove("hidden")
|
||||
return
|
||||
}
|
||||
|
||||
// Store results in memory and use indices instead of embedding JSON
|
||||
this.searchResults = results
|
||||
|
||||
const html = results.map((game, index) => {
|
||||
// HTML escape function
|
||||
const escapeHtml = (text) => {
|
||||
const div = document.createElement('div')
|
||||
div.textContent = text
|
||||
return div.innerHTML
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="p-3 hover:bg-gray-50 cursor-pointer border-b last:border-b-0 flex gap-3"
|
||||
data-action="click->igdb-search#selectGame"
|
||||
data-index="${index}">
|
||||
${game.cover_url ? `
|
||||
<img src="https://images.igdb.com/igdb/image/upload/t_thumb/${game.cover_url}.jpg"
|
||||
class="w-12 h-16 object-cover rounded"
|
||||
alt="${escapeHtml(game.name)}">
|
||||
` : `
|
||||
<div class="w-12 h-16 bg-gray-200 rounded flex items-center justify-center">
|
||||
<span class="text-gray-400 text-xs">No<br>Cover</span>
|
||||
</div>
|
||||
`}
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-semibold text-gray-900 truncate">${escapeHtml(game.name)}</div>
|
||||
<div class="text-sm text-gray-600">${escapeHtml(game.platform)}${game.year ? ` • ${game.year}` : ''}</div>
|
||||
${game.genres && game.genres.length > 0 ? `
|
||||
<div class="flex gap-1 mt-1 flex-wrap">
|
||||
${game.genres.slice(0, 3).map(genre => `
|
||||
<span class="px-1.5 py-0.5 bg-indigo-100 text-indigo-700 text-xs rounded">${escapeHtml(genre)}</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="text-xs text-gray-500">${Math.round(game.confidence)}% match</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}).join('')
|
||||
|
||||
this.resultsTarget.innerHTML = html
|
||||
this.resultsTarget.classList.remove("hidden")
|
||||
}
|
||||
|
||||
selectGame(event) {
|
||||
const index = parseInt(event.currentTarget.dataset.index)
|
||||
const gameData = this.searchResults[index]
|
||||
this.selectedGame = gameData
|
||||
|
||||
console.log("Selected game:", gameData)
|
||||
|
||||
// Fill in the form fields
|
||||
if (this.hasTitleTarget) {
|
||||
this.titleTarget.value = gameData.name
|
||||
console.log("Set title to:", gameData.name)
|
||||
} else {
|
||||
console.warn("Title target not found")
|
||||
}
|
||||
|
||||
if (this.hasIgdbIdTarget) {
|
||||
this.igdbIdTarget.value = gameData.igdb_id
|
||||
console.log("Set IGDB ID to:", gameData.igdb_id)
|
||||
} else {
|
||||
console.warn("IGDB ID target not found")
|
||||
}
|
||||
|
||||
if (this.hasSummaryTarget && gameData.summary) {
|
||||
this.summaryTarget.value = gameData.summary
|
||||
console.log("Set summary")
|
||||
}
|
||||
|
||||
// Set genres
|
||||
if (gameData.genre_ids && gameData.genre_ids.length > 0) {
|
||||
this.setGenres(gameData.genre_ids)
|
||||
}
|
||||
|
||||
// Update the query field to show selected game
|
||||
this.queryTarget.value = gameData.name
|
||||
|
||||
// Hide results
|
||||
this.hideResults()
|
||||
|
||||
// Show IGDB badge/info
|
||||
this.showIgdbInfo(gameData)
|
||||
}
|
||||
|
||||
setGenres(genreIds) {
|
||||
console.log("Setting genres:", genreIds)
|
||||
|
||||
// Find all genre checkboxes
|
||||
const genreCheckboxes = document.querySelectorAll('input[name="game[genre_ids][]"]')
|
||||
|
||||
// Uncheck all first
|
||||
genreCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = false
|
||||
})
|
||||
|
||||
// Check the matched genres
|
||||
genreIds.forEach(genreId => {
|
||||
const checkbox = document.querySelector(`input[name="game[genre_ids][]"][value="${genreId}"]`)
|
||||
if (checkbox) {
|
||||
checkbox.checked = true
|
||||
console.log("Checked genre:", genreId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
showIgdbInfo(gameData) {
|
||||
// Add a visual indicator that this is from IGDB
|
||||
const badge = document.createElement('div')
|
||||
badge.className = 'mt-2 space-y-2'
|
||||
|
||||
let genreText = ''
|
||||
if (gameData.genres && gameData.genres.length > 0) {
|
||||
genreText = `
|
||||
<div class="text-xs text-green-700">
|
||||
<strong>Genres auto-set:</strong> ${gameData.genres.join(', ')}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
badge.innerHTML = `
|
||||
<div class="inline-flex items-center px-2 py-1 bg-green-100 text-green-800 text-xs rounded">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"/>
|
||||
</svg>
|
||||
IGDB Match (${Math.round(gameData.confidence)}% confidence)
|
||||
</div>
|
||||
${genreText}
|
||||
`
|
||||
|
||||
// Insert after query field
|
||||
const existingBadge = this.queryTarget.parentElement.querySelector('.igdb-match-badge')
|
||||
if (existingBadge) {
|
||||
existingBadge.remove()
|
||||
}
|
||||
badge.classList.add('igdb-match-badge')
|
||||
this.queryTarget.parentElement.appendChild(badge)
|
||||
}
|
||||
|
||||
showManualEntry(event) {
|
||||
event.preventDefault()
|
||||
|
||||
// Clear IGDB fields
|
||||
if (this.hasIgdbIdTarget) {
|
||||
this.igdbIdTarget.value = ""
|
||||
}
|
||||
|
||||
// Focus on title field
|
||||
if (this.hasTitleTarget) {
|
||||
this.titleTarget.focus()
|
||||
}
|
||||
|
||||
this.hideResults()
|
||||
|
||||
// Remove IGDB badge
|
||||
const badge = this.queryTarget.parentElement.querySelector('.igdb-match-badge')
|
||||
if (badge) {
|
||||
badge.remove()
|
||||
}
|
||||
}
|
||||
|
||||
hideResults() {
|
||||
if (this.hasResultsTarget) {
|
||||
this.resultsTarget.classList.add("hidden")
|
||||
}
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
this.queryTarget.value = ""
|
||||
this.hideResults()
|
||||
|
||||
if (this.hasTitleTarget) {
|
||||
this.titleTarget.value = ""
|
||||
}
|
||||
|
||||
if (this.hasIgdbIdTarget) {
|
||||
this.igdbIdTarget.value = ""
|
||||
}
|
||||
|
||||
if (this.hasSummaryTarget) {
|
||||
this.summaryTarget.value = ""
|
||||
}
|
||||
|
||||
// Uncheck all genres
|
||||
const genreCheckboxes = document.querySelectorAll('input[name="game[genre_ids][]"]')
|
||||
genreCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = false
|
||||
})
|
||||
|
||||
const badge = this.queryTarget.parentElement.querySelector('.igdb-match-badge')
|
||||
if (badge) {
|
||||
badge.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// Hide results when clicking outside
|
||||
clickOutside(event) {
|
||||
if (!this.element.contains(event.target)) {
|
||||
this.hideResults()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user