upload: add overwrite option to replace existing activities
When 'Overwrite existing activities' is checked, duplicate activities are
re-extracted and replaced instead of silently skipped:
- Deletes {id}.json, .geojson, .timeseries.json from activities/ and _merged/
- Removes the stale index summary and dedup cache entry
- Ingests the new file fresh via ingest_parsed
- Reports 'overwritten' (↺) status in the SSE stream vs 'imported' (↓)
- done event includes 'overwritten' count; UI shows it alongside 'added'
This commit is contained in:
@@ -317,6 +317,17 @@ try {
|
||||
<span class="text-zinc-600 block mt-0.5">Lets you reprocess if the format changes. See the <a href={`${baseUrl}about/`} class="underline hover:text-zinc-400">About page</a> for details.</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="flex items-start gap-2 mt-2 cursor-pointer group">
|
||||
<input
|
||||
id="upload-overwrite"
|
||||
type="checkbox"
|
||||
class="mt-0.5 accent-amber-500 shrink-0"
|
||||
/>
|
||||
<span class="text-xs text-zinc-500 group-hover:text-zinc-300 transition-colors leading-snug">
|
||||
Overwrite existing activities
|
||||
<span class="text-zinc-600 block mt-0.5">Re-extract and replace any duplicate found on the server. Use to fix a corrupted or mis-parsed activity.</span>
|
||||
</span>
|
||||
</label>
|
||||
<p id="upload-status" class="mt-3 text-xs text-center" style="color: var(--text-5); min-height: 1.25rem"></p>
|
||||
</div>
|
||||
|
||||
@@ -554,6 +565,7 @@ try {
|
||||
const input = document.getElementById('upload-input');
|
||||
const label = document.getElementById('upload-label');
|
||||
const keepOriginalChk = document.getElementById('upload-keep-original');
|
||||
const overwriteChk = document.getElementById('upload-overwrite');
|
||||
const fileStatus = document.getElementById('upload-status');
|
||||
const stravaStatus = document.getElementById('strava-status');
|
||||
const stravaConnect = document.getElementById('strava-connect-area');
|
||||
@@ -629,6 +641,7 @@ try {
|
||||
const fd = new FormData();
|
||||
for (const f of files) fd.append('files', f);
|
||||
fd.append('store_original', keepOriginalChk?.checked ? 'true' : 'false');
|
||||
fd.append('overwrite', overwriteChk?.checked ? 'true' : 'false');
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', `${editUrl}/api/upload`);
|
||||
@@ -636,7 +649,7 @@ try {
|
||||
xhr.setRequestHeader('Accept', 'text/event-stream');
|
||||
|
||||
let buf = '';
|
||||
let added = 0, dupes = 0, errors = 0, csvUpdates = 0;
|
||||
let added = 0, overwrittenCount = 0, dupes = 0, errors = 0, csvUpdates = 0;
|
||||
|
||||
xhr.onprogress = () => {
|
||||
const newText = xhr.responseText.slice(buf.length);
|
||||
@@ -647,23 +660,25 @@ try {
|
||||
const ev = JSON.parse(line.slice(6));
|
||||
if (ev.type === 'progress') {
|
||||
const pct = Math.round((ev.n / ev.total) * 100);
|
||||
const icon = ev.status === 'imported' ? '↓' : ev.status === 'duplicate' ? '·' : '✗';
|
||||
const icon = ev.status === 'imported' ? '↓' : ev.status === 'overwritten' ? '↺' : ev.status === 'duplicate' ? '·' : '✗';
|
||||
fileStatus.textContent = `${icon} ${ev.n}/${ev.total} (${pct}%) — ${ev.name}`;
|
||||
if (ev.status === 'imported') added++;
|
||||
else if (ev.status === 'overwritten') overwrittenCount++;
|
||||
else if (ev.status === 'duplicate') dupes++;
|
||||
else errors++;
|
||||
} else if (ev.type === 'csv') {
|
||||
csvUpdates = ev.updates;
|
||||
} else if (ev.type === 'done') {
|
||||
added = ev.added; dupes = ev.duplicates; errors = ev.errors; csvUpdates = ev.csv_updates;
|
||||
added = ev.added; overwrittenCount = ev.overwritten ?? 0; dupes = ev.duplicates; errors = ev.errors; csvUpdates = ev.csv_updates;
|
||||
const parts = [];
|
||||
if (added > 0) parts.push(`${added} added`);
|
||||
if (overwrittenCount > 0) parts.push(`${overwrittenCount} overwritten`);
|
||||
if (csvUpdates > 0) parts.push(`${csvUpdates} updated from CSV`);
|
||||
if (dupes) parts.push(`${dupes} duplicate${dupes > 1 ? 's' : ''}`);
|
||||
if (errors) parts.push(`${errors} failed`);
|
||||
if (parts.length === 0) parts.push('nothing to add');
|
||||
fileStatus.textContent = parts.join(', ');
|
||||
const anyGood = added > 0 || csvUpdates > 0;
|
||||
const anyGood = added > 0 || overwrittenCount > 0 || csvUpdates > 0;
|
||||
fileStatus.style.color = anyGood ? '#4ade80' : '#a1a1aa';
|
||||
if (anyGood) setTimeout(() => window.location.reload(), 1200);
|
||||
else drop.style.pointerEvents = '';
|
||||
|
||||
Reference in New Issue
Block a user