Upload progress spinner + duplicate game detection
Build & Deploy / build-and-deploy (push) Successful in 1m20s
Build & Deploy / build-and-deploy (push) Successful in 1m20s
Two features:
1. Progress indicator: when upload starts, the dialog switches from
IGDB results to a centered spinner with 'Uploading and processing'
text + filename. The Cancel button stays visible but disabled.
2. Duplicate game blocking: before upload, checks if a game with the
same title (case-insensitive) already exists in the library.
Frontend checks against the loaded games list; backend also
checks as a second line of defense. Shows ⚠️ message in dialog.
Existing unique-ID suffix logic (foo-2, foo-3) is still in place
but now only reached when titles actually differ.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
let searchError = $state("");
|
||||
let igdbResults = $state(null); // null=loading, []=no results, [{...}]=matches
|
||||
let manualSearched = $state(false); // true after user clicked Search manually
|
||||
let duplicateError = $state(""); // title of duplicate game if blocked
|
||||
|
||||
let fileInput;
|
||||
|
||||
@@ -80,12 +81,25 @@
|
||||
igdbResults = null;
|
||||
searchError = "";
|
||||
manualSearched = false;
|
||||
duplicateError = "";
|
||||
}
|
||||
|
||||
async function doUpload(title, igdbId) {
|
||||
if (!pendingFile) return;
|
||||
|
||||
// Block duplicates: case-insensitive title match
|
||||
const finalTitle = title || pendingFileName;
|
||||
if (finalTitle) {
|
||||
const dup = games.find(g => g.title?.toLowerCase() === finalTitle.toLowerCase());
|
||||
if (dup) {
|
||||
duplicateError = `"${dup.title}" is already in your library`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uploading = true;
|
||||
uploadError = "";
|
||||
duplicateError = "";
|
||||
try {
|
||||
await uploadGame(pendingFile, title || undefined, igdbId);
|
||||
cancelUpload();
|
||||
@@ -134,7 +148,18 @@
|
||||
<p class="upload-file-name">{pendingFileName}.zip</p>
|
||||
|
||||
<div class="upload-dialog-body">
|
||||
{#if searching}
|
||||
{#if uploading}
|
||||
<div class="upload-progress">
|
||||
<div class="upload-progress-spinner"></div>
|
||||
<p class="upload-progress-text">Uploading and processing</p>
|
||||
<p class="upload-progress-sub">{pendingFileName}.zip</p>
|
||||
<p class="upload-progress-note">This may take a while for large games</p>
|
||||
</div>
|
||||
{:else if duplicateError}
|
||||
<div class="status-msg error-msg">
|
||||
⚠️ {duplicateError}
|
||||
</div>
|
||||
{:else if searching}
|
||||
<div class="status-msg">Searching IGDB for "{pendingFileName}"...</div>
|
||||
{:else if igdbResults !== null && igdbResults.length > 0}
|
||||
<p class="match-label">Select a match or enter a custom title:</p>
|
||||
@@ -490,4 +515,40 @@
|
||||
background: #332211;
|
||||
border-color: #ffaa44;
|
||||
}
|
||||
|
||||
/* ─── Upload Progress ──────────────────────────── */
|
||||
.upload-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.upload-progress-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border);
|
||||
border-top: 3px solid var(--phosphor);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.upload-progress-text {
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.upload-progress-sub {
|
||||
color: var(--text-dim);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.upload-progress-note {
|
||||
color: var(--text-dim);
|
||||
font-size: 0.75rem;
|
||||
margin-top: 8px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user