- Replace all 'game' references with 'soundtrack' throughout frontend (112 occurrences) - Add radio buttons for fuzzy/exact search in SearchModal - Fuzzy search is default and uses /findfuzzy endpoint - Exact search uses existing /find endpoint - Fix syntax errors in SyncProgressModal from previous refactoring Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
358 lines
11 KiB
Vue
358 lines
11 KiB
Vue
<template>
|
|
<div class="modal" v-if="isOpen">
|
|
<div class="modalContainer syncProgressContainer">
|
|
<span class="closeModalImg" @click="closeModal">×</span>
|
|
<h1>Sync in Progress</h1>
|
|
|
|
<div v-if="!syncComplete" class="progressSection">
|
|
<div class="progressBarContainer">
|
|
<div class="progressBar" :style="{ width: progress + '%' }"></div>
|
|
</div>
|
|
<p class="progressText">{{ progress }}% complete</p>
|
|
<p class="timeText">Time spent: {{ timeSpent }}</p>
|
|
</div>
|
|
|
|
<div v-else class="resultsSection">
|
|
<h2>Sync Complete!</h2>
|
|
<p>Total time: {{ syncData.total_time }}</p>
|
|
|
|
<div v-if="syncData.soundtracks_added && syncData.soundtracks_added.length > 0" class="resultItem">
|
|
<h3>Soundtracks Added: {{ syncData.soundtracks_added.length }}</h3>
|
|
<ul class="soundtrackList">
|
|
<li v-for="soundtrack in syncData.soundtracks_added" :key="soundtrack">{{ soundtrack }}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div v-if="syncData.soundtracks_re_added && syncData.soundtracks_re_added.length > 0" class="resultItem">
|
|
<h3>Soundtracks Re-added: {{ syncData.soundtracks_re_added.length }}</h3>
|
|
<ul class="soundtrackList">
|
|
<li v-for="soundtrack in syncData.soundtracks_re_added" :key="soundtrack">{{ soundtrack }}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div v-if="syncData.soundtracks_removed && syncData.soundtracks_removed.length > 0" class="resultItem">
|
|
<h3>Soundtracks Removed: {{ syncData.soundtracks_removed.length }}</h3>
|
|
<ul class="soundtrackList">
|
|
<li v-for="soundtrack in syncData.soundtracks_removed" :key="soundtrack">{{ soundtrack }}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div v-if="Object.keys(syncData.soundtracks_changed_title || {}).length > 0" class="resultItem">
|
|
<h3>Soundtracks with Changed Title: {{ Object.keys(syncData.soundtracks_changed_title || {}).length }}</h3>
|
|
<ul class="soundtrackList">
|
|
<li v-for="(newTitle, oldTitle) in syncData.soundtracks_changed_title" :key="oldTitle">{{ oldTitle }} → {{ newTitle }}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div v-if="syncData.soundtracks_changed_content && syncData.soundtracks_changed_content.length > 0" class="resultItem">
|
|
<h3>Soundtracks with Changed Content: {{ syncData.soundtracks_changed_content.length }}</h3>
|
|
<ul class="soundtrackList">
|
|
<li v-for="soundtrack in syncData.soundtracks_changed_content" :key="soundtrack">{{ soundtrack }}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div v-if="syncData.catched_errors && syncData.catched_errors.length > 0" class="resultItem errors">
|
|
<h3>Errors: {{ syncData.catched_errors.length }}</h3>
|
|
<ul class="soundtrackList">
|
|
<li v-for="error in syncData.catched_errors" :key="error">{{ error }}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<p v-if="noChanges" class="noChanges">No changes detected.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: {
|
|
isOpen: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
syncInProgress: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
progress: 0,
|
|
timeSpent: "00:00:00",
|
|
syncComplete: false,
|
|
syncData: {
|
|
soundtracks_added: [],
|
|
soundtracks_re_added: [],
|
|
soundtracks_changed_title: {},
|
|
soundtracks_changed_content: [],
|
|
soundtracks_removed: [],
|
|
catched_errors: [],
|
|
total_time: "",
|
|
},
|
|
pollInterval: null,
|
|
hasFetchedInitialData: false,
|
|
};
|
|
},
|
|
computed: {
|
|
noChanges() {
|
|
return (
|
|
this.syncData.soundtracks_added.length === 0 &&
|
|
this.syncData.soundtracks_re_added.length === 0 &&
|
|
Object.keys(this.syncData.soundtracks_changed_title).length === 0 &&
|
|
this.syncData.soundtracks_changed_content.length === 0 &&
|
|
this.syncData.soundtracks_removed.length === 0 &&
|
|
this.syncData.catched_errors.length === 0
|
|
);
|
|
},
|
|
},
|
|
methods: {
|
|
closeModal() {
|
|
this.$emit("close");
|
|
},
|
|
startPolling() {
|
|
if (this.pollInterval) {
|
|
clearInterval(this.pollInterval);
|
|
}
|
|
this.pollInterval = setInterval(() => {
|
|
this.fetchProgress();
|
|
}, 1000);
|
|
},
|
|
stopPolling() {
|
|
if (this.pollInterval) {
|
|
clearInterval(this.pollInterval);
|
|
this.pollInterval = null;
|
|
}
|
|
},
|
|
async fetchProgress() {
|
|
try {
|
|
const response = await this.axios({
|
|
method: "get",
|
|
url: `${window.__RUNTIME_CONFIG__.API_HOSTNAME}/sync/progress`,
|
|
});
|
|
|
|
const data = response.data;
|
|
|
|
// Check if we got a progress response or a result response
|
|
if (data.progress !== undefined) {
|
|
// Still syncing - update progress
|
|
this.progress = parseInt(data.progress) || 0;
|
|
this.timeSpent = data.time_spent || this.timeSpent;
|
|
this.syncComplete = false;
|
|
// If we're not actively polling but sync is in progress, start polling
|
|
if (this.syncInProgress && !this.pollInterval) {
|
|
this.startPolling();
|
|
}
|
|
} else {
|
|
// Sync complete - show results
|
|
this.syncComplete = true;
|
|
this.syncData = {
|
|
soundtracks_added: data.soundtracks_added || [],
|
|
soundtracks_re_added: data.soundtracks_re_added || [],
|
|
soundtracks_changed_title: data.soundtracks_changed_title || {},
|
|
soundtracks_changed_content: data.soundtracks_changed_content || [],
|
|
soundtracks_removed: data.soundtracks_removed || [],
|
|
catched_errors: data.catched_errors || [],
|
|
total_time: data.total_time || "",
|
|
};
|
|
this.progress = 100;
|
|
this.stopPolling();
|
|
}
|
|
this.hasFetchedInitialData = true;
|
|
} catch (error) {
|
|
console.error("Error fetching sync progress:", error);
|
|
}
|
|
},
|
|
async fetchInitialData() {
|
|
// Fetch once to check current state
|
|
await this.fetchProgress();
|
|
// If we got progress data, start polling
|
|
if (!this.syncComplete && !this.pollInterval) {
|
|
this.startPolling();
|
|
}
|
|
},
|
|
},
|
|
watch: {
|
|
isOpen(newVal) {
|
|
if (newVal) {
|
|
this.hasFetchedInitialData = false;
|
|
if (this.syncInProgress) {
|
|
// New sync in progress - start polling
|
|
this.progress = 0;
|
|
this.timeSpent = "00:00:00";
|
|
this.syncComplete = false;
|
|
this.syncData = {
|
|
soundtracks_added: [],
|
|
soundtracks_re_added: [],
|
|
soundtracks_changed_title: {},
|
|
soundtracks_changed_content: [],
|
|
soundtracks_removed: [],
|
|
catched_errors: [],
|
|
total_time: "",
|
|
};
|
|
// Small delay to ensure sync has started before first poll
|
|
setTimeout(() => {
|
|
this.startPolling();
|
|
}, 200);
|
|
} else {
|
|
// Check current state - might be in progress or have results
|
|
this.fetchInitialData();
|
|
}
|
|
} else {
|
|
this.stopPolling();
|
|
}
|
|
},
|
|
},
|
|
beforeUnmount() {
|
|
this.stopPolling();
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.syncProgressContainer {
|
|
background-color: #eeeeee;
|
|
margin: 10% auto;
|
|
border: 1px solid #888;
|
|
width: 70%;
|
|
max-width: 800px;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.closeModalImg {
|
|
position: absolute;
|
|
right: 8px;
|
|
top: 8px;
|
|
cursor: pointer;
|
|
opacity: 70%;
|
|
font-size: 2rem;
|
|
color: #555;
|
|
}
|
|
|
|
.closeModalImg:hover {
|
|
opacity: 100%;
|
|
}
|
|
|
|
.syncProgressContainer h1 {
|
|
color: black;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
padding-top: 10px;
|
|
font-size: 1.9rem;
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.syncProgressContainer h2 {
|
|
color: black;
|
|
text-align: center;
|
|
font-size: 1.5rem;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.syncProgressContainer p {
|
|
color: black;
|
|
text-align: center;
|
|
}
|
|
|
|
.progressSection {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.progressBarContainer {
|
|
width: 100%;
|
|
height: 30px;
|
|
background-color: #ddd;
|
|
border-radius: 15px;
|
|
overflow: hidden;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.progressBar {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #4CAF50, #8BC34A);
|
|
border-radius: 15px;
|
|
transition: width 0.3s ease;
|
|
text-align: center;
|
|
line-height: 30px;
|
|
color: white;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.progressText {
|
|
font-size: 1.3rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.timeText {
|
|
font-size: 1rem;
|
|
color: #666;
|
|
}
|
|
|
|
.resultsSection {
|
|
max-height: 60vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.resultItem {
|
|
margin-bottom: 20px;
|
|
padding: 10px;
|
|
background-color: #f8f8f8;
|
|
border-radius: 8px;
|
|
border-left: 4px solid #4CAF50;
|
|
}
|
|
|
|
.resultItem.errors {
|
|
border-left-color: #f44336;
|
|
}
|
|
|
|
.resultItem h3 {
|
|
color: #333;
|
|
font-size: 1.1rem;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.soundtrackList {
|
|
list-style-type: none;
|
|
padding: 0;
|
|
margin: 5px 0 0 0;
|
|
}
|
|
|
|
.soundtrackList li {
|
|
padding: 3px 0;
|
|
color: #555;
|
|
}
|
|
|
|
.noChanges {
|
|
text-align: center;
|
|
color: #666;
|
|
font-style: italic;
|
|
padding: 20px;
|
|
}
|
|
|
|
@media only screen and (max-width: 1000px) {
|
|
.syncProgressContainer {
|
|
width: 93%;
|
|
margin: 5% auto;
|
|
padding: 15px;
|
|
}
|
|
|
|
.syncProgressContainer h1 {
|
|
font-size: 1.6rem;
|
|
padding-top: 5px;
|
|
}
|
|
|
|
.progressBarContainer {
|
|
height: 25px;
|
|
}
|
|
|
|
.progressText {
|
|
font-size: 1.1rem;
|
|
}
|
|
}
|
|
</style>
|