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 = `
No games found.
Add custom game
`
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 `
${game.cover_url ? `
` : `
No Cover
`}
${escapeHtml(game.name)}
${escapeHtml(game.platform)}${game.year ? ` • ${game.year}` : ''}
${game.genres && game.genres.length > 0 ? `
${game.genres.slice(0, 3).map(genre => `
${escapeHtml(genre)}
`).join('')}
` : ''}
${Math.round(game.confidence)}% match
`
}).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 = `
Genres auto-set: ${gameData.genres.join(', ')}
`
}
badge.innerHTML = `
IGDB Match (${Math.round(gameData.confidence)}% confidence)
${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()
}
}
}