diff --git a/.claude/launch.json b/.claude/launch.json index f3c9817..974415a 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -3,6 +3,7 @@ "configurations": [ { "name": "Map-Jurnal", + "cwd": "/Users/haerikim/Desktop/Map-Jurnal", "runtimeExecutable": "npm", "runtimeArgs": ["run", "dev"], "port": 5173, diff --git a/.env b/.env new file mode 100644 index 0000000..2deacb9 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +VITE_FIREBASE_API_KEY=AIzaSyC_hZf9TpIIb4H7y7umUeYtFKD-guN_iR0 +VITE_FIREBASE_AUTH_DOMAIN=map-jurnal.firebaseapp.com +VITE_FIREBASE_PROJECT_ID=map-jurnal +VITE_FIREBASE_STORAGE_BUCKET=map-jurnal.firebasestorage.app +VITE_FIREBASE_MESSAGING_SENDER_ID=922587077950 +VITE_FIREBASE_APP_ID=1:922587077950:web:9f140f84468e306152606f diff --git a/index.html b/index.html index b971d68..4a7c108 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Map Journal + Journi
diff --git a/package-lock.json b/package-lock.json index 691083e..0703013 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "d3": "^7.9.0", "firebase": "^12.14.0", "flag-icons": "^7.5.0", + "html-to-image": "^1.11.13", "topojson-client": "^3.1.0", "world-atlas": "^2.0.2" }, @@ -747,14 +748,14 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.2" + "@tybys/wasm-util": "^0.10.1" }, "funding": { "type": "github", @@ -814,12 +815,6 @@ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", - "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "license": "BSD-3-Clause" - }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", @@ -931,9 +926,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -951,9 +943,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -971,9 +960,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -991,9 +977,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1011,9 +994,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1031,9 +1011,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1865,6 +1842,12 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/html-to-image": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz", + "integrity": "sha512-cuOPoI7WApyhBElTTb9oqsawRvZ0rHhaHwghRLlTuffoD1B2aDemlCruLeZrUIIdvG7gs9xeELEPm6PhuASqrg==", + "license": "MIT" + }, "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", @@ -2060,9 +2043,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2084,9 +2064,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2108,9 +2085,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2132,9 +2106,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2299,9 +2270,9 @@ } }, "node_modules/protobufjs": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz", - "integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz", + "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -2311,7 +2282,6 @@ "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", diff --git a/package.json b/package.json index 7b3829b..ff96360 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "d3": "^7.9.0", "firebase": "^12.14.0", "flag-icons": "^7.5.0", + "html-to-image": "^1.11.13", "topojson-client": "^3.1.0", "world-atlas": "^2.0.2" } diff --git a/src/App.svelte b/src/App.svelte index 3a4665a..0ad9f93 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -8,9 +8,10 @@ import StatsPanel from './lib/world-map/StatsPanel.svelte'; import TimelineView from './lib/timeline/TimelineView.svelte'; - let screen = $state('worldmap'); let journeyActive = $state(false); let journeyProgress = $state(null); + let inDetail = $state(false); + let pendingCountry = $state(''); function onNavigate(s) { screen = s; @@ -30,6 +31,11 @@ journeyProgress = p; } + function handleCountryClick(name) { + pendingCountry = name; + screen = 'timeline'; + } + $effect(() => { initAuth(); }); @@ -44,21 +50,25 @@ Loading... {:else} - + {#if screen === 'worldmap'}
{#if journeyActive} {:else} - + {/if}
{:else} - + (inDetail = v)} + {pendingCountry} + onNewEntryClear={() => (pendingCountry = '')} + /> {/if}
@@ -67,6 +77,7 @@ {:else if needsCountry} {/if} + {/if} {/if} diff --git a/src/lib/shared/countries.js b/src/lib/shared/countries.js new file mode 100644 index 0000000..a6401c4 --- /dev/null +++ b/src/lib/shared/countries.js @@ -0,0 +1,25 @@ +export const countryCodeMap = { + 'Argentina': 'AR', 'Australia': 'AU', 'Austria': 'AT', + 'Belgium': 'BE', 'Brazil': 'BR', + 'Canada': 'CA', 'Chile': 'CL', 'China': 'CN', 'Croatia': 'HR', + 'Czech Republic': 'CZ', 'Denmark': 'DK', 'Egypt': 'EG', + 'Finland': 'FI', 'France': 'FR', 'Germany': 'DE', 'Greece': 'GR', + 'Hungary': 'HU', 'India': 'IN', 'Indonesia': 'ID', 'Italy': 'IT', + 'Japan': 'JP', 'Kenya': 'KE', + 'Malaysia': 'MY', 'Mexico': 'MX', 'Morocco': 'MA', + 'Netherlands': 'NL', 'New Zealand': 'NZ', 'Norway': 'NO', + 'Peru': 'PE', 'Poland': 'PL', 'Portugal': 'PT', + 'Singapore': 'SG', 'South Africa': 'ZA', 'South Korea': 'KR', + 'Spain': 'ES', 'Sweden': 'SE', 'Switzerland': 'CH', + 'Taiwan': 'TW', 'Thailand': 'TH', 'Turkey': 'TR', + 'UK': 'GB', 'USA': 'US', 'Vietnam': 'VN', +}; + +export const countryNames = Object.keys(countryCodeMap).sort(); + +/** @param {string} country */ +export function flagEmoji(country) { + const code = countryCodeMap[country]; + if (!code) return ''; + return [...code].map(c => String.fromCodePoint(0x1F1E6 - 65 + c.charCodeAt(0))).join(''); +} diff --git a/src/lib/stores/journalStore.js b/src/lib/stores/journalStore.js index 1ef37c9..57b29e6 100644 --- a/src/lib/stores/journalStore.js +++ b/src/lib/stores/journalStore.js @@ -7,7 +7,7 @@ import { writable } from 'svelte/store'; * date: string, * location: { country: string, city: string }, * photos: string[], - * song: { title: string, artist: string }, + * transport: 'flight' | 'train' | 'bus' | 'car' | 'ship' | 'walk', * tripType: 'solo' | 'friends', * days: number, * memo: string @@ -26,7 +26,7 @@ const mockEntries = [ 'https://images.unsplash.com/photo-1513407030348-c983a97b98d8?w=600&q=80', 'https://images.unsplash.com/photo-1490806843957-31f4c9a91c65?w=600&q=80', ], - song: { title: 'Tokyo', artist: 'Imagine Dragons' }, + transport: 'flight', tripType: 'solo', days: 5, memo: 'Got completely lost in Shinjuku — stumbled into a tiny ramen shop with no English menu. The chashu just melted. Worth every wrong turn.', @@ -40,7 +40,7 @@ const mockEntries = [ 'https://images.unsplash.com/photo-1528360983277-13d401cdc186?w=600&q=80', 'https://images.unsplash.com/photo-1545569341-9eb8b30979d9?w=600&q=80', ], - song: { title: 'Spirited Away Suite', artist: 'Joe Hisaishi' }, + transport: 'train', tripType: 'friends', days: 3, memo: 'Arrived at 6am before the crowds. Just me and the wind moving through the bamboo. One of those moments you keep coming back to.', @@ -55,7 +55,7 @@ const mockEntries = [ 'https://images.unsplash.com/photo-1499856871958-5b9627545d1a?w=600&q=80', 'https://images.unsplash.com/photo-1511739001486-6bfe10ce785f?w=600&q=80', ], - song: { title: 'La Vie en Rose', artist: 'Édith Piaf' }, + transport: 'flight', tripType: 'solo', days: 7, memo: 'Watched the whole city turn orange from the steps of Sacré-Cœur. A street musician was playing La Vie en Rose. Cliché, perfect.', @@ -69,7 +69,7 @@ const mockEntries = [ 'https://images.unsplash.com/photo-1523531294919-4bcd7c65e216?w=600&q=80', 'https://images.unsplash.com/photo-1583422409516-2895a77efded?w=600&q=80', ], - song: { title: 'Spain', artist: 'Chick Corea' }, + transport: 'flight', tripType: 'friends', days: 4, memo: 'Nothing prepares you for the light inside. The stained glass turns the whole nave into a kaleidoscope. Gaudí was building a forest.', @@ -84,7 +84,7 @@ const mockEntries = [ 'https://images.unsplash.com/photo-1485871981521-5b1fd3805345?w=600&q=80', 'https://images.unsplash.com/photo-1522083165195-3424ed129620?w=600&q=80', ], - song: { title: 'New York, New York', artist: 'Frank Sinatra' }, + transport: 'car', tripType: 'friends', days: 6, memo: 'Peak foliage. Joggers, picnics, a guy playing saxophone near Bethesda Fountain. Hard to believe a city this big wraps around this much quiet.', @@ -98,7 +98,7 @@ const mockEntries = [ 'https://images.unsplash.com/photo-1563492065599-3520f775eeed?w=600&q=80', 'https://images.unsplash.com/photo-1552465011-b4e21bf6e79a?w=600&q=80', ], - song: { title: 'Elephant', artist: 'Tame Impala' }, + transport: 'ship', tripType: 'solo', days: 2, memo: 'Stood in front of the 45m golden Buddha for a long time. The mother-of-pearl inlay on the soles of the feet is impossibly detailed.', @@ -116,3 +116,8 @@ export function addJournal(entry) { export function removeJournal(id) { journals.update((entries) => entries.filter((e) => e.id !== id)); } + +/** @param {JournalEntry} updated */ +export function updateJournal(updated) { + journals.update((entries) => entries.map((e) => e.id === updated.id ? updated : e)); +} diff --git a/src/lib/timeline/DeleteConfirm.svelte b/src/lib/timeline/DeleteConfirm.svelte new file mode 100644 index 0000000..b602869 --- /dev/null +++ b/src/lib/timeline/DeleteConfirm.svelte @@ -0,0 +1,88 @@ + + + + + diff --git a/src/lib/timeline/EditForm.svelte b/src/lib/timeline/EditForm.svelte new file mode 100644 index 0000000..c4d942a --- /dev/null +++ b/src/lib/timeline/EditForm.svelte @@ -0,0 +1,358 @@ + + +
+ +
+
+ +
+ {isNew ? 'New entry' : 'Edit'} +
+ +
+
+ +
+
{ e.preventDefault(); save(); }}> + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+
+ +
+ +
+ {#each transportOptions as opt} + + {/each} +
+
+ + (photos = p)} /> + +
+
+ + {wordCount} / {MEMO_MAX} words +
+ +
+ + + +
+
+ + diff --git a/src/lib/timeline/JournalDetail.svelte b/src/lib/timeline/JournalDetail.svelte index fe43a0f..e8dcc58 100644 --- a/src/lib/timeline/JournalDetail.svelte +++ b/src/lib/timeline/JournalDetail.svelte @@ -1,19 +1,16 @@ +{#if showDeleteConfirm} + showDeleteConfirm = false} /> +{/if} + {#if lightboxSrc}