Skip to content

Commit 16e51a2

Browse files
authored
Merge pull request #26 from hothsys/perf/cleanup
Performance: Cleanup/Refactor
2 parents 8ee4a04 + 21f1090 commit 16e51a2

11 files changed

Lines changed: 161 additions & 267 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@
6666

6767
## Documentation
6868

69+
- [Architecture](docs/architecture.md)
6970
- [Data persistence & migration](docs/persistence.md)
7071
- [Data storage internals](docs/data-storage.md)
7172
- [Tile caching architecture](docs/tile-caching.md)
72-
- [Synology NAS + Tailscale setup](docs/synology-setup.md)
73-
- [Keyboard shortcuts](docs/keyboard-shortcuts.md)
7473
- [Tips & features](docs/tips.md)
75-
- [Architecture](docs/architecture.md)
76-
- [Testing](docs/testing.md)
74+
- [Keyboard shortcuts](docs/keyboard-shortcuts.md)
75+
- [Synology NAS + Tailscale setup](docs/synology-setup.md)
7776
- [Demo modes](docs/demo-mode.md)
77+
- [Testing](docs/testing.md)
7878
- [GitHub workflows](docs/github-workflows.md)
7979

8080
---

css/styles.css

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
--accent2:#e8774a;--green:#4ec98a;
88
--text:#e4e7f2;--muted:#7a829c;--muted2:#555d78;
99
--radius:12px;--shadow:0 8px 40px rgba(0,0,0,.55);
10-
--font:'Josefin Sans',sans-serif;--font-serif:'Josefin Sans',sans-serif;
10+
--font:'Josefin Sans',sans-serif;
1111
}
1212
html{font-size:19px}
1313
html,body{height:100%;overflow:hidden;font-family:var(--font);background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased}
@@ -26,7 +26,7 @@ input,textarea,select{font-family:var(--font)}
2626
.sidebar-header{padding:14px 18px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;flex-shrink:0}
2727
.logo-mark{width:56px;height:56px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
2828
.logo-mark svg{width:100%;height:100%;fill:var(--accent)}
29-
.logo-text{font-family:var(--font-serif);font-size:1.15rem;font-weight:600;letter-spacing:-.02em}
29+
.logo-text{font-family:var(--font);font-size:1.15rem;font-weight:600;letter-spacing:-.02em}
3030
.logo-text span{color:var(--accent)}
3131

3232
/* 3-tab sidebar */
@@ -360,13 +360,12 @@ textarea.fi{resize:vertical;min-height:60px;line-height:1.5}
360360
#export-overlay{display:none;position:fixed;inset:0;z-index:9000;background:rgba(0,0,0,.75);backdrop-filter:blur(6px);align-items:center;justify-content:center}
361361
#export-overlay.open{display:flex}
362362
.export-overlay-box{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:32px 40px;text-align:center;box-shadow:0 12px 60px rgba(0,0,0,.6);min-width:320px}
363-
.export-overlay-title{font-family:var(--font-serif);font-size:1.2rem;font-weight:600;color:var(--text);margin-bottom:8px}
363+
.export-overlay-title{font-family:var(--font);font-size:1.2rem;font-weight:600;color:var(--text);margin-bottom:8px}
364364
.export-overlay-progress{font-size:.78rem;color:var(--muted);margin-bottom:16px}
365365
.export-overlay-bar-wrap{height:6px;background:var(--surface3);border-radius:3px;overflow:hidden}
366366
.export-overlay-bar{height:100%;width:0%;background:var(--accent);border-radius:3px;transition:width .3s ease}
367367
.export-cancel-btn{margin-top:16px;background:none;border:1px solid var(--border2);color:var(--muted);padding:6px 20px;border-radius:8px;font-size:.75rem;cursor:pointer;transition:color .15s,border-color .15s}
368368
.export-cancel-btn:hover{color:var(--text);border-color:var(--text)}
369-
#tb-export-video.recording{color:var(--accent2)}
370369

371370
/* TOAST */
372371
#toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%) translateY(60px);background:var(--surface);border:1px solid var(--border);padding:8px 16px;border-radius:28px;font-size:.74rem;box-shadow:var(--shadow);z-index:999;transition:transform .28s ease;pointer-events:none}

js/data.js

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -598,93 +598,9 @@ async function init() {
598598
}, 500);
599599
// Proactive tile caching — start after a short delay so it doesn't compete with initial load
600600
setTimeout(() => cacheMapTiles(), 10000);
601-
// One-time migration: convert PNG thumbnails to JPEG (remove once both machines have run this)
602-
setTimeout(() => _migrateThumbsToWebP(), 2000);
603-
// One-time migration: move full-size base64 images from IndexedDB to disk
604-
// After this, IndexedDB holds only metadata + thumbnails (~50KB per photo vs ~4MB)
605-
setTimeout(() => _migrateImagesToDisk(), 3000);
606601
}
607602

608603

609-
// ═══════════════════════════════════════
610-
// ONE-TIME MIGRATION: PNG → JPEG thumbnails
611-
// Remove this function and its setTimeout call in init() once both machines have run it.
612-
// ═══════════════════════════════════════
613-
async function _migrateThumbsToWebP() {
614-
let migrated = 0;
615-
for (const p of photos) {
616-
if (!p.thumbUrl || p.thumbUrl.startsWith('data:image/jpeg')) continue;
617-
try {
618-
const img = new Image();
619-
await new Promise((res, rej) => { img.onload = res; img.onerror = rej; img.src = p.thumbUrl; });
620-
const canvas = document.createElement('canvas');
621-
canvas.width = img.naturalWidth;
622-
canvas.height = img.naturalHeight;
623-
canvas.getContext('2d').drawImage(img, 0, 0);
624-
p.thumbUrl = canvas.toDataURL('image/jpeg', 0.7);
625-
await dbPut('photos', p);
626-
// Upload the new WebP thumbnail to disk (bypasses _savedPhotoDisk guard)
627-
if (_autoSaveAvailable) {
628-
await fetch(`/api/photos/${p.id}/thumb`, {
629-
method: 'POST',
630-
headers: { 'Content-Type': 'application/json' },
631-
body: JSON.stringify({ dataUrl: p.thumbUrl })
632-
});
633-
}
634-
migrated++;
635-
} catch (_) { /* skip failures */ }
636-
}
637-
if (migrated) {
638-
rebuildPhotoList();
639-
buildTimeline();
640-
scheduleAutoSave();
641-
}
642-
}
643-
644-
// ═══════════════════════════════════════
645-
// ONE-TIME MIGRATION: Move base64 images from IndexedDB to disk
646-
// After this migration, IndexedDB holds metadata + thumbnails only (~50KB/photo vs ~4MB).
647-
// Full-size images live in matrix-photos/ and are loaded on-demand by the lightbox.
648-
// ═══════════════════════════════════════
649-
async function _migrateImagesToDisk() {
650-
if (!_autoSaveAvailable) return; // serve.py must be running to save files
651-
// Find photos still storing base64 full-size images in IndexedDB
652-
const needMigration = photos.filter(p => p.dataUrl && p.dataUrl.startsWith('data:'));
653-
if (!needMigration.length) return;
654-
655-
showToast(`Migrating ${needMigration.length} photo${needMigration.length !== 1 ? 's' : ''} to disk…`, 'info');
656-
let migrated = 0;
657-
658-
for (const p of needMigration) {
659-
try {
660-
const ext = (p.dataUrl.match(/data:image\/(\w+)/) || [])[1] === 'png' ? 'png' : 'jpg';
661-
const filePath = `matrix-photos/${p.id}.${ext}`;
662-
// Check if the image already exists on disk (from prior auto-save)
663-
const exists = await fetch(`/${filePath}`, { method: 'HEAD' }).then(r => r.ok).catch(() => false);
664-
if (!exists) {
665-
// Image not on disk yet — save it now from the base64 in IndexedDB
666-
await fetch(`/api/photos/${p.id}`, {
667-
method: 'POST',
668-
headers: { 'Content-Type': 'application/json' },
669-
body: JSON.stringify({ dataUrl: p.dataUrl })
670-
});
671-
}
672-
// Replace base64 with the file path reference in memory + IndexedDB
673-
p.dataUrl = filePath;
674-
await dbPut('photos', p);
675-
migrated++;
676-
} catch (err) {
677-
// Skip failures — photo retains its base64 and can retry next load
678-
console.warn(`Migration failed for ${p.id}:`, err.message);
679-
}
680-
}
681-
682-
if (migrated) {
683-
scheduleAutoSave();
684-
showToast(`Migrated ${migrated} photo${migrated !== 1 ? 's' : ''} to disk ✓`, 'success');
685-
}
686-
}
687-
688604
// ═══════════════════════════════════════
689605
// OFFLINE SUPPORT
690606
// ═══════════════════════════════════════

js/export.js

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -101,27 +101,13 @@ async function exportVideo() {
101101
if (_exporting || _playbackActive) return;
102102
if (_mapStyle === 'globe') { showToast('Export Video is not available in Globe mode', 'error'); return; }
103103

104-
// Build stops (same logic as startPlayback)
105-
const dated = photos.filter(p => p.lat !== null && p.date)
106-
.sort((a, b) => photoSortKey(a) < photoSortKey(b) ? -1 : 1);
104+
const stops = buildPlaybackStops();
107105

108-
if (dated.length < 2) {
106+
if (stops.length < 2) {
109107
showToast('Need at least 2 dated pinned photos to export', 'error');
110108
return;
111109
}
112110

113-
const stops = [];
114-
const seen = new Set();
115-
for (const p of dated) {
116-
const k = locKey(p);
117-
if (seen.has(k)) {
118-
stops.find(s => s.key === k).photoIds.push(p.id);
119-
} else {
120-
seen.add(k);
121-
stops.push({ key: k, lat: p.lat, lng: p.lng, photoIds: [p.id] });
122-
}
123-
}
124-
125111
// Show overlay
126112
_exporting = true;
127113
document.getElementById('export-overlay').classList.add('open');
@@ -366,7 +352,7 @@ async function exportVideo() {
366352

367353
// Cleanup
368354
exportCleanup();
369-
showToast(`Video exported (${(blob.size / 1024 / 1024).toFixed(1)} MB)`, 'success');
355+
showToast('Video exported', 'success');
370356
}
371357

372358
function exportCleanup() {

0 commit comments

Comments
 (0)