diff --git a/src/lib/JournalDetail.svelte b/src/lib/JournalDetail.svelte index a00c0b0..1cf348c 100644 --- a/src/lib/JournalDetail.svelte +++ b/src/lib/JournalDetail.svelte @@ -1,5 +1,4 @@
@@ -28,7 +46,7 @@ Back - {#if entry.photos.length > 0} + {#if entry.photos?.length > 0}
Soundtrack - {entry.song.title} - {entry.song.artist} + {entry.song?.title || ''} + {entry.song?.artist || ''}
@@ -223,6 +244,11 @@ .trip-badge--solo { background: rgba(245,158,11,0.12); color: #b45309; } .trip-badge--friends { background: rgba(59,130,246,0.12); color: #1d4ed8; } + .transport-badge { + background: rgba(16,185,129,0.12); + color: #059669; + } + .detail-title { font-size: 28px; font-weight: 700; diff --git a/src/lib/stores/entriesStore.svelte.js b/src/lib/stores/entriesStore.svelte.js new file mode 100644 index 0000000..9337777 --- /dev/null +++ b/src/lib/stores/entriesStore.svelte.js @@ -0,0 +1,45 @@ +import { db } from '../firebase.js'; +import { collection, doc, onSnapshot, query, orderBy, addDoc, updateDoc, deleteDoc, serverTimestamp } from 'firebase/firestore'; + +let entries = $state([]); +let _uid = null; +let _unsubscribe = null; + +export function getEntries() { + return entries; +} + +export function initEntriesListener(uid) { + if (_unsubscribe) _unsubscribe(); + _uid = uid; + const q = query( + collection(db, 'users', uid, 'entries'), + orderBy('createdAt', 'desc') + ); + _unsubscribe = onSnapshot(q, (snap) => { + entries = snap.docs.map((d) => ({ id: d.id, ...d.data() })); + }); +} + +export async function addEntry(data) { + if (!_uid) return null; + const ref = await addDoc(collection(db, 'users', _uid, 'entries'), { + ...data, + createdAt: serverTimestamp(), + updatedAt: serverTimestamp(), + }); + return ref.id; +} + +export async function updateEntry(id, data) { + if (!_uid) return; + await updateDoc(doc(db, 'users', _uid, 'entries', id), { + ...data, + updatedAt: serverTimestamp(), + }); +} + +export async function removeEntry(id) { + if (!_uid) return; + await deleteDoc(doc(db, 'users', _uid, 'entries', id)); +} diff --git a/src/lib/world-map/WorldMap.svelte b/src/lib/world-map/WorldMap.svelte index cca1ed5..ee705f8 100644 --- a/src/lib/world-map/WorldMap.svelte +++ b/src/lib/world-map/WorldMap.svelte @@ -50,6 +50,8 @@ } let frameEl; + let _paths = null; + let _g = null; function fitProjection(proj, w, h) { proj.fitSize([w, h], { type: 'Sphere' }); @@ -57,6 +59,15 @@ proj.scale(s).translate([w / 2, h * 0.70]); } + function updateAllFills() { + if (!_paths || !_g) return; + const sel = getSelected(); + _paths.attr('fill', d => sel.has(effId(d)) ? '#22c55e' : '#ffffff'); + _g.selectAll('.micro-state').attr('fill', d => sel.has(effId(d)) ? '#22c55e' : '#ffffff'); + } + + $effect(updateAllFills); + onMount(() => { const width = frameEl.clientWidth; const height = frameEl.clientHeight; @@ -81,7 +92,7 @@ .attr('width', width) .attr('height', height); - const g = svg.append('g'); + _g = svg.append('g'); const tooltip = d3.select(frameEl) .append('div') @@ -90,7 +101,7 @@ function updateFill(sel) { sel.attr('fill', d => getSelected().has(effId(d)) ? '#22c55e' : '#ffffff'); - g.selectAll('.micro-state').attr('fill', d => getSelected().has(effId(d)) ? '#22c55e' : '#ffffff'); + _g.selectAll('.micro-state').attr('fill', d => getSelected().has(effId(d)) ? '#22c55e' : '#ffffff'); } function attachEvents(sel) { @@ -113,24 +124,24 @@ }); } - const paths = g.selectAll('path') + _paths = _g.selectAll('path') .data(countries) .join('path') .attr('d', path) .attr('fill', '#ffffff') .attr('stroke', '#d4d4d4') .attr('stroke-width', 0.5); - attachEvents(paths); + attachEvents(_paths); function renderMicrostates() { - g.selectAll('.micro-state').remove(); + _g.selectAll('.micro-state').remove(); const threshold = Math.max(4, 16 / d3.zoomTransform(svg.node()).k); - paths.each(function (d) { + _paths.each(function (d) { if (effId(d) !== d.id) return; const { width, height } = this.getBBox(); if (width < threshold && height < threshold) { const [cx, cy] = path.centroid(d); - const c = g.append('circle') + const c = _g.append('circle') .attr('class', 'micro-state') .datum(d) .attr('cx', cx) @@ -149,7 +160,7 @@ const zoom = d3.zoom() .scaleExtent([1, 32]) .on('zoom', (event) => { - g.attr('transform', event.transform); + _g.attr('transform', event.transform); renderMicrostates(); }); @@ -166,7 +177,7 @@ const { width, height } = entry.contentRect; svg.attr('width', width).attr('height', height); fitProjection(projection, width, height); - const countryPaths = g.selectAll('path'); + const countryPaths = _g.selectAll('path'); countryPaths.attr('d', path); updateFill(countryPaths); renderMicrostates();