Files
MusicFrontend/src/components/items/SearchModal.vue
T

188 lines
4.8 KiB
Vue

<template>
<transition name="modalAni">
<div v-if="show" class="modal" @click.self="closeModal">
<div class="modalContainer">
<div class="modalWrapper">
<span class="closeModalImg" @click="closeModal">&times;</span>
<h1>Search Soundtracks</h1>
<div class="searchContainer">
<input
type="text"
v-model="searchTerm"
@input="debouncedSearch"
placeholder="Search for soundtracks..."
class="searchInput"
/>
<div v-if="searchTerm.length > 0" class="searchResults">
<div v-if="loading" class="loading">Searching...</div>
<div v-else-if="results.length > 0" class="resultsList">
<div v-for="soundtrack in results" :key="soundtrack" class="resultItem">
{{ soundtrack }}
</div>
</div>
<div v-else class="noResults">No results found</div>
</div>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
data() {
return {
show: false,
searchTerm: "",
results: [],
loading: false,
searchTimeout: null,
};
},
methods: {
openModal() {
this.show = true;
this.searchTerm = "";
this.results = [];
},
closeModal() {
this.show = false;
this.searchTerm = "";
this.results = [];
this.loading = false;
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
this.searchTimeout = null;
}
},
debouncedSearch() {
// Clear existing timeout
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
// Set new timeout
this.searchTimeout = setTimeout(() => {
this.performSearch();
}, 250);
},
async performSearch() {
if (this.searchTerm.length === 0) {
this.results = [];
return;
}
this.loading = true;
try {
const response = await this.axios({
method: "post",
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/find`,
data: new URLSearchParams({ search_term: this.searchTerm }),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
// Parse soundtracks from HTML response
this.results = this.parseSoundtracksFromResponse(response.data);
} catch (error) {
console.error("Error searching soundtracks:", error);
this.results = [];
} finally {
this.loading = false;
}
},
parseSoundtracksFromResponse(htmlData) {
// The server returns HTML from FoundSoundtracks templ component
// It generates divs containing soundtrack names in <p> tags
const soundtracks = [];
const parser = new DOMParser();
const doc = parser.parseFromString(htmlData, "text/html");
// Find all p tags inside divs (the templ generates <div><p>{soundtrack}</p></div>)
const pTags = doc.querySelectorAll("div p");
for (const p of pTags) {
const soundtrackName = p.textContent.trim();
if (soundtrackName) {
soundtracks.push(soundtrackName);
}
}
return soundtracks;
},
},
};
</script>
<style scoped>
.searchContainer {
width: 100%;
margin-top: 10px;
}
.searchInput {
width: 100%;
padding: 12px;
font-size: 1.1rem;
border: 1px solid #777;
background-color: rgb(66, 66, 66);
color: white;
border-radius: 5px;
box-sizing: border-box;
}
.searchInput:focus {
outline: none;
border-color: #ff9c00;
box-shadow: 0 0 5px rgba(255, 156, 0, 0.3);
}
.searchResults {
margin-top: 15px;
max-height: 400px;
overflow-y: auto;
}
.loading {
color: #bbb;
font-style: italic;
text-align: center;
padding: 20px;
}
.resultsList {
width: 100%;
}
.resultItem {
padding: 12px;
margin-bottom: 8px;
background-color: rgb(66, 66, 66);
border: 1px solid #555;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
}
.resultItem:hover {
background-color: #555;
}
.noResults {
color: #bbb;
text-align: center;
padding: 20px;
font-style: italic;
}
@media only screen and (max-width: 1000px) {
.searchInput {
font-size: 1rem;
padding: 10px;
}
.resultItem {
padding: 10px;
font-size: 0.95rem;
}
}
</style>