changed app name, added new journal entry from map and timeline
This commit is contained in:
@@ -1,39 +1,73 @@
|
||||
<script>
|
||||
import { get } from 'svelte/store';
|
||||
import { journals, updateJournal } from '../stores/journalStore.js';
|
||||
import { journals, addJournal, updateJournal } from '../stores/journalStore.js';
|
||||
import { countryNames } from '../shared/countries.js';
|
||||
import SearchInput from '../shared/SearchInput.svelte';
|
||||
import PhotoEditor from './PhotoEditor.svelte';
|
||||
|
||||
/** @type {{ entry: import('../stores/journalStore.js').JournalEntry, onBack: () => void }} */
|
||||
let { entry, onBack } = $props();
|
||||
/**
|
||||
* entry = null → "new entry" mode
|
||||
* entry = {...} → "edit" mode
|
||||
* @type {{ entry?: import('../stores/journalStore.js').JournalEntry | null, initialCountry?: string, onBack: () => void }}
|
||||
*/
|
||||
let { entry = null, initialCountry = '', onBack } = $props();
|
||||
|
||||
let city = $state(entry.location.city);
|
||||
let country = $state(entry.location.country);
|
||||
let date = $state(entry.date);
|
||||
let days = $state(String(entry.days));
|
||||
let tripType = $state(entry.tripType);
|
||||
let photos = $state([...entry.photos]);
|
||||
let memo = $state(entry.memo);
|
||||
let songTitle = $state(entry.song.title);
|
||||
let songArtist = $state(entry.song.artist);
|
||||
let isNew = !entry;
|
||||
|
||||
let city = $state(entry?.location.city ?? '');
|
||||
let country = $state(entry?.location.country ?? initialCountry);
|
||||
let date = $state(entry?.date ?? new Date().toISOString().slice(0, 10));
|
||||
let days = $state(String(entry?.days ?? 1));
|
||||
let tripType = $state(entry?.tripType ?? 'solo');
|
||||
let photos = $state([...(entry?.photos ?? [])]);
|
||||
let memo = $state(entry?.memo ?? '');
|
||||
let songTitle = $state(entry?.song.title ?? '');
|
||||
let songArtist = $state(entry?.song.artist ?? '');
|
||||
|
||||
const MEMO_MAX = 100;
|
||||
let wordCount = $derived(memo.trim() === '' ? 0 : memo.trim().split(/\s+/).length);
|
||||
let memoOverLimit = $derived(wordCount > MEMO_MAX);
|
||||
|
||||
function onMemoInput(e) {
|
||||
const raw = e.currentTarget.value;
|
||||
const words = raw.trim() === '' ? [] : raw.trim().split(/\s+/);
|
||||
if (words.length > MEMO_MAX) {
|
||||
// keep first 100 words, preserve trailing space if user is mid-word
|
||||
memo = words.slice(0, MEMO_MAX).join(' ');
|
||||
e.currentTarget.value = memo;
|
||||
} else {
|
||||
memo = raw;
|
||||
}
|
||||
}
|
||||
|
||||
// City suggestions from existing entries (deduplicated)
|
||||
let cityOptions = $derived(
|
||||
[...new Set(get(journals).map(e => e.location.city))].sort()
|
||||
);
|
||||
|
||||
function save() {
|
||||
updateJournal({
|
||||
...entry,
|
||||
date,
|
||||
days: Number(days),
|
||||
tripType,
|
||||
memo,
|
||||
photos,
|
||||
location: { city, country },
|
||||
song: { title: songTitle, artist: songArtist },
|
||||
});
|
||||
if (isNew) {
|
||||
addJournal({
|
||||
title: `${city}, ${country}`,
|
||||
date,
|
||||
days: Number(days),
|
||||
tripType,
|
||||
memo,
|
||||
photos,
|
||||
location: { city, country },
|
||||
song: { title: songTitle, artist: songArtist },
|
||||
});
|
||||
} else {
|
||||
updateJournal({
|
||||
...entry,
|
||||
date,
|
||||
days: Number(days),
|
||||
tripType,
|
||||
memo,
|
||||
photos,
|
||||
location: { city, country },
|
||||
song: { title: songTitle, artist: songArtist },
|
||||
});
|
||||
}
|
||||
onBack();
|
||||
}
|
||||
</script>
|
||||
@@ -49,7 +83,7 @@
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
<span class="topbar-title">Edit</span>
|
||||
<span class="topbar-title">{isNew ? 'New entry' : 'Edit'}</span>
|
||||
<div class="topbar-right">
|
||||
<button class="topbar-btn topbar-btn--save" onclick={save}>Save changes</button>
|
||||
</div>
|
||||
@@ -59,14 +93,14 @@
|
||||
<form class="form" onsubmit={(e) => { e.preventDefault(); save(); }}>
|
||||
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label class="label" for="edit-city">City <span class="req">*</span></label>
|
||||
<SearchInput id="edit-city" bind:value={city} options={cityOptions} required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="edit-country">Country <span class="req">*</span></label>
|
||||
<SearchInput id="edit-country" bind:value={country} options={countryNames} required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="edit-city">City <span class="req">*</span></label>
|
||||
<SearchInput id="edit-city" bind:value={city} options={cityOptions} required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -95,8 +129,11 @@
|
||||
<PhotoEditor {photos} onchange={(p) => (photos = p)} />
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="edit-memo">How was it?</label>
|
||||
<textarea id="edit-memo" class="input textarea" rows="4" bind:value={memo}></textarea>
|
||||
<div class="label-row">
|
||||
<label class="label" for="edit-memo">How was it?</label>
|
||||
<span class="char-count" class:over={memoOverLimit}>{wordCount} / {MEMO_MAX} words</span>
|
||||
</div>
|
||||
<textarea id="edit-memo" class="input textarea" class:input-over={memoOverLimit} rows="4" value={memo} oninput={onMemoInput}></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -206,6 +243,12 @@
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.label-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
@@ -214,6 +257,15 @@
|
||||
color: var(--text-sub);
|
||||
}
|
||||
|
||||
.char-count {
|
||||
font-size: 11px;
|
||||
font-weight: 300;
|
||||
color: var(--text-sub);
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.char-count.over { color: #dc2626; }
|
||||
.input-over { border-color: #fca5a5; }
|
||||
|
||||
.req {
|
||||
color: var(--accent);
|
||||
font-size: 11px;
|
||||
|
||||
Reference in New Issue
Block a user