connect viewpage to other pages

This commit is contained in:
adeliptr 2025-06-10 04:04:32 +09:00
parent 1f4d571197
commit 0570bcffe9
7 changed files with 247 additions and 118 deletions

View File

@ -1,70 +1,78 @@
<script> <script lang="ts">
export let destination = ''; import { goto } from '$app/navigation';
export let startDate = ''; export let destination = '';
export let endDate = ''; export let startDate = '';
export let image = ''; export let endDate = '';
export let image = '';
export let tid = '';
export let memoryId = '';
function handleClick() {
goto(`/viewimage/${tid}/${memoryId}`);
}
</script> </script>
<div class="memory-card"> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="image" style="background-image: url({image})"> <div
<!-- Image placeholder if no image provided --> class="memory-card"
{#if !image} role="button"
<div class="placeholder"> tabindex="0"
<i class="fa-solid fa-image" style="color: var(--gray-800)"></i> onclick={handleClick}
</div> >
{/if} <div class="image" style="background-image: url({image || ''})">
</div> {#if !image}
<div class="info"> <div class="placeholder">
<h3>{destination}</h3> <i class="fa-solid fa-image" style="color: var(--gray-400)"></i>
<p class="date">{startDate} - {endDate}</p> </div>
</div> {/if}
</div>
<div class="info">
<h3>{destination}</h3>
<p class="date">{startDate} - {endDate}</p>
</div>
</div> </div>
<style> <style>
.memory-card { .memory-card {
background: var(--black); background: var(--gray-900);
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
cursor: pointer; cursor: pointer;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
color: var(--white); position: relative;
} }
.memory-card:hover {
.memory-card:hover { transform: translateY(-2px);
transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 8px rgba(255, 255, 255, 0.1); }
} .image {
height: 160px;
.image { background-size: cover;
height: 160px; background-position: center;
background-size: cover; background-color: var(--gray-100);
background-position: center; position: relative;
background-color: var(--gray-900); }
} .placeholder {
height: 100%;
.placeholder { display: flex;
height: 100%; align-items: center;
display: flex; justify-content: center;
align-items: center; font-size: 2rem;
justify-content: center; }
font-size: 2rem; .info {
} padding: 1rem;
}
.info { .info h3 {
padding: 1rem; margin: 0;
} font-size: 1.2rem;
font-weight: 600;
.info h3 { color: var(--white);
margin: 0; }
font-size: 1.2rem; .date {
font-weight: 600; margin: 0.25rem 0 0 0;
} font-size: 0.8rem;
color: var(--gray-200);
.date { }
margin: 0.25rem 0 0 0;
font-size: 0.8rem;
color: var(--gray-400);
}
</style> </style>

View File

@ -233,6 +233,8 @@
} }
let isUploading = false; let isUploading = false;
const isDark = document.documentElement.classList.contains('dark');
</script> </script>
{#if showPopup} {#if showPopup}

View File

@ -7,6 +7,7 @@
import { ref, get } from 'firebase/database'; import { ref, get } from 'firebase/database';
import { db } from '../../firebase'; import { db } from '../../firebase';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import NewMemoryPopup from '$lib/components/NewMemoryPopup.svelte';
let mapContainer: HTMLDivElement; let mapContainer: HTMLDivElement;
@ -65,6 +66,8 @@
} }
let cleanup: (() => void) | undefined; let cleanup: (() => void) | undefined;
let showNewMemoryPopup = false;
let selectedTripIdForMemory = '';
onMount(() => { onMount(() => {
let mounted = true; let mounted = true;
@ -176,8 +179,32 @@
.attr('class', 'trip-marker') .attr('class', 'trip-marker')
.attr('data-has-memory', trip.hasMemory ? 'true' : 'false') .attr('data-has-memory', trip.hasMemory ? 'true' : 'false')
.style('cursor', 'pointer') .style('cursor', 'pointer')
.on('click', function () { .on('click', async function () {
goto(`/itinerary/${trip.tid}`); // Use document.documentElement for dark mode detection
const isDark = document.documentElement.classList.contains('dark');
const hasMemory = trip.hasMemory;
if (isDark) {
if (hasMemory) {
// Fetch the topmost memoryId for this trip
const tripRef = ref(db, `trips/${trip.tid}/memories`);
const snapshot = await get(tripRef);
if (snapshot.exists()) {
const memoryIds = Object.keys(snapshot.val());
if (memoryIds.length > 0) {
const topMemoryId = memoryIds[0];
goto(`/viewimage/${trip.tid}/${topMemoryId}`);
}
}
} else {
// Show NewMemoryPopup for this trip
selectedTripIdForMemory = trip.tid;
showNewMemoryPopup = true;
}
} else {
// Default: go to itinerary
goto(`/itinerary/${trip.tid}`);
}
}); });
markerGroup.append('text') markerGroup.append('text')
@ -187,8 +214,32 @@
.attr('class', 'trip-label') .attr('class', 'trip-label')
.text(`${formatDate(trip.startDate)} - ${formatDate(trip.endDate)}`) .text(`${formatDate(trip.startDate)} - ${formatDate(trip.endDate)}`)
.style('cursor', 'pointer') .style('cursor', 'pointer')
.on('click', function () { .on('click', async function () {
goto(`/itinerary/${trip.tid}`); // Use document.documentElement for dark mode detection
const isDark = document.documentElement.classList.contains('dark');
const hasMemory = trip.hasMemory;
if (isDark) {
if (hasMemory) {
// Fetch the topmost memoryId for this trip
const tripRef = ref(db, `trips/${trip.tid}/memories`);
const snapshot = await get(tripRef);
if (snapshot.exists()) {
const memoryIds = Object.keys(snapshot.val());
if (memoryIds.length > 0) {
const topMemoryId = memoryIds[0];
goto(`/viewimage/${trip.tid}/${topMemoryId}`);
}
}
} else {
// Show NewMemoryPopup for this trip
selectedTripIdForMemory = trip.tid;
showNewMemoryPopup = true;
}
} else {
// Default: go to itinerary
goto(`/itinerary/${trip.tid}`);
}
}); });
markerGroup markerGroup
@ -235,6 +286,10 @@
<div bind:this={mapContainer} class="map-wrapper"></div> <div bind:this={mapContainer} class="map-wrapper"></div>
{#if showNewMemoryPopup}
<NewMemoryPopup bind:showPopup={showNewMemoryPopup} onCancel={() => showNewMemoryPopup = false} />
{/if}
<style> <style>
.map-wrapper { .map-wrapper {
width: 100%; width: 100%;

View File

@ -10,6 +10,7 @@ export interface Trip {
lng: number; lng: number;
} }
}; };
_cardImage?: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
tripmates: string[]; tripmates: string[];

View File

@ -4,28 +4,59 @@
import Button from '$lib/components/Button.svelte'; import Button from '$lib/components/Button.svelte';
import NewMemoryPopup from '$lib/components/NewMemoryPopup.svelte'; import NewMemoryPopup from '$lib/components/NewMemoryPopup.svelte';
import Nav from '$lib/components/Nav.svelte'; import Nav from '$lib/components/Nav.svelte';
import { onMount } from 'svelte';
interface Trip { import { ref, onValue } from 'firebase/database';
destination: string; import { db } from '../../firebase';
startDate: string;
endDate: string;
imageUrl: string;
}
let showNewMemoryPopup = false; let showNewMemoryPopup = false;
let contentContainer: HTMLElement; let contentContainer: HTMLElement;
// Sample data, replace with actual data later interface MemoryCardData {
const sample_memories = { tid: string;
destination: "Taiwan", memoryId: string;
startDate: "04.27.2025", destination: string;
endDate: "04.30.2025", startDate: string;
imageUrl: "" endDate: string;
image: string;
} }
let pastMemories = Array(3).fill(sample_memories);
let pastMemories: MemoryCardData[] = [];
onMount(() => {
const tripsRef = ref(db, 'trips');
onValue(tripsRef, (snapshot) => {
const memories: MemoryCardData[] = [];
snapshot.forEach((childSnapshot) => {
const trip = childSnapshot.val();
const tid = childSnapshot.key;
if (trip.memories && typeof trip.memories === 'object') {
const memoryEntries = Object.entries(trip.memories);
if (memoryEntries.length > 0) {
const [memoryId, memory] = memoryEntries[0];
const mem = memory as { images: string[] };
if (mem.images && mem.images.length > 0) {
const format = (d: string) => {
const date = new Date(d);
return `${String(date.getDate()).padStart(2, '0')}/${String(date.getMonth() + 1).padStart(2, '0')}/${date.getFullYear()}`;
};
memories.push({
tid,
memoryId,
destination: trip.destination?.name || '',
startDate: format(trip.startDate),
endDate: format(trip.endDate),
image: mem.images[0]
});
}
}
}
});
pastMemories = memories;
});
});
function handleNewMemory() { function handleNewMemory() {
showNewMemoryPopup = true; showNewMemoryPopup = true;
} }
</script> </script>
@ -40,7 +71,7 @@
<div class="memories-container"> <div class="memories-container">
{#if pastMemories.length === 0} {#if pastMemories.length === 0}
<div class="empty-state"> <div class="empty-state">
<p>There is no past trip</p> <p>There is no memory yet</p>
</div> </div>
{:else} {:else}
<div class="memories-grid"> <div class="memories-grid">

View File

@ -21,13 +21,27 @@
const tripsRef = ref(db, 'trips'); const tripsRef = ref(db, 'trips');
// listen for changes in the trips data // listen for changes in the trips data
onValue(tripsRef, (snapshot) => { onValue(tripsRef, async (snapshot) => {
const trips: Trip[] = []; const trips: Trip[] = [];
const tripImageMap: Record<string, string> = {};
const promises: Promise<void>[] = [];
snapshot.forEach((childSnapshot) => { snapshot.forEach((childSnapshot) => {
trips.push({ const tripData = {
tid: childSnapshot.key, tid: childSnapshot.key,
...childSnapshot.val() ...childSnapshot.val()
}); };
trips.push(tripData);
// Check for memories and get the first image if exists
if (tripData.memories && typeof tripData.memories === 'object') {
const memoryIds = Object.keys(tripData.memories);
if (memoryIds.length > 0) {
const firstMemory = tripData.memories[memoryIds[0]];
if (firstMemory.images && firstMemory.images.length > 0) {
tripImageMap[tripData.tid] = firstMemory.images[0];
}
}
}
}); });
// get today's date at midnight for comparison // get today's date at midnight for comparison
@ -35,7 +49,6 @@
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
// filter trips based on end date // filter trips based on end date
// end date > today = pastTrips
ongoingTrips = trips.filter(trip => { ongoingTrips = trips.filter(trip => {
const endDate = new Date(trip.endDate); const endDate = new Date(trip.endDate);
return endDate >= today; return endDate >= today;
@ -45,6 +58,10 @@
const endDate = new Date(trip.endDate); const endDate = new Date(trip.endDate);
return endDate < today; return endDate < today;
}).sort((a, b) => new Date(b.endDate).getTime() - new Date(a.endDate).getTime()); // sort past trips by most recent first }).sort((a, b) => new Date(b.endDate).getTime() - new Date(a.endDate).getTime()); // sort past trips by most recent first
// Attach the image override map to each trip for rendering
ongoingTrips = ongoingTrips.map(trip => ({ ...trip, _cardImage: tripImageMap[trip.tid] || trip.destination.photo }));
pastTrips = pastTrips.map(trip => ({ ...trip, _cardImage: tripImageMap[trip.tid] || trip.destination.photo }));
}); });
}); });
@ -95,7 +112,7 @@
destination={trip.destination.name} destination={trip.destination.name}
startDate={new Date(trip.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })} startDate={new Date(trip.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
endDate={new Date(trip.endDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })} endDate={new Date(trip.endDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
image={trip.destination.photo} image={trip._cardImage}
tid={trip.tid} tid={trip.tid}
/> />
{/each} {/each}
@ -113,7 +130,7 @@
destination={trip.destination.name} destination={trip.destination.name}
startDate={new Date(trip.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })} startDate={new Date(trip.startDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
endDate={new Date(trip.endDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })} endDate={new Date(trip.endDate).toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}
image={trip.destination.photo} image={trip._cardImage}
tid={trip.tid} tid={trip.tid}
/> />
{/each} {/each}

View File

@ -1,38 +1,38 @@
<script> <script lang="ts">
import '../../../../app.css'; import '../../../../app.css';
import Nav from '$lib/components/Nav.svelte'; import Nav from '$lib/components/Nav.svelte';
import Button from '$lib/components/Button.svelte'; import Button from '$lib/components/Button.svelte';
import { page } from '$app/stores'; import { page } from '$app/state';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { ref, get } from 'firebase/database'; import { ref, get } from 'firebase/database';
import { db } from '../../../../firebase'; import { db } from '../../../../firebase';
let tripId, memoryId; let tripId, memoryId;
let memory = null; let memory: any = null;
let tripOptions = []; let tripOptions: any[] = [];
let currentImageIndex = 0; let currentImageIndex = 0;
let gradientLayers = []; let gradientLayers: any[] = [];
let columnGroups = []; let columnGroups: any[] = [];
let rotationAngle = 0; let rotationAngle = 0;
let droppedTripId = null; let droppedTripId: any = null;
let droppedMemory = null; let droppedMemory: any = null;
let droppedImageIndex = 0; let droppedImageIndex = 0;
let droppedColumnGroups = []; let droppedColumnGroups: any[] = [];
let droppedGradientLayers = []; let droppedGradientLayers: any[] = [];
let droppedWheelStyle = {}; let droppedWheelStyle = {};
let droppedCurrentImage = null; let droppedCurrentImage: any = null;
let isDroppedVisible = true; let isDroppedVisible = true;
$: tripId = $page.params.tripId; $: tripId = page.params.tripId;
$: memoryId = $page.params.memoryId; $: memoryId = page.params.memoryId;
$: currentImage = memory?.images?.[currentImageIndex]; $: currentImage = memory?.images?.[currentImageIndex];
$: if (tripId && memoryId) { $: if (tripId && memoryId) {
loadMemoryAndTrip(tripId, memoryId); loadMemoryAndTrip(tripId, memoryId);
} }
async function loadMemoryAndTrip(tripId, memoryId) { async function loadMemoryAndTrip(tripId: string, memoryId: string) {
try { try {
const memorySnap = await get(ref(db, `trips/${tripId}/memories/${memoryId}`)); const memorySnap = await get(ref(db, `trips/${tripId}/memories/${memoryId}`));
const tripSnap = await get(ref(db, `trips/${tripId}`)); const tripSnap = await get(ref(db, `trips/${tripId}`));
@ -79,7 +79,7 @@
} }
} }
async function loadDroppedTrip(tripId, memoryId) { async function loadDroppedTrip(tripId: any, memoryId: any) {
const memorySnap = await get(ref(db, `trips/${tripId}/memories/${memoryId}`)); const memorySnap = await get(ref(db, `trips/${tripId}/memories/${memoryId}`));
if (memorySnap.exists()) { if (memorySnap.exists()) {
droppedTripId = tripId; droppedTripId = tripId;
@ -104,7 +104,7 @@
transformOrigin: 'center center' transformOrigin: 'center center'
}; };
async function getImageData(url) { async function getImageData(url: string) {
const img = new Image(); const img = new Image();
img.crossOrigin = 'Anonymous'; img.crossOrigin = 'Anonymous';
img.src = url; img.src = url;
@ -141,7 +141,7 @@
return [h, s * 100, v * 255]; return [h, s * 100, v * 255];
} }
function getColumnColors(imageData, columnCount = 5) { function getColumnColors(imageData: ImageData, columnCount = 5) {
const { data, width, height } = imageData; const { data, width, height } = imageData;
const columnWidth = Math.floor(width / columnCount); const columnWidth = Math.floor(width / columnCount);
const columns = Array.from({ length: columnCount }, () => []); const columns = Array.from({ length: columnCount }, () => []);
@ -167,7 +167,7 @@
}); });
} }
async function extractColumnwiseColors(imageUrls, reverseColumns = true) { async function extractColumnwiseColors(imageUrls: any, reverseColumns = true) {
const columnColorGroups = [[], [], [], [], []]; const columnColorGroups = [[], [], [], [], []];
for (const url of imageUrls) { for (const url of imageUrls) {
@ -225,7 +225,7 @@
gradients.push('radial-gradient(circle, var(--black) 100%)'); gradients.push('radial-gradient(circle, var(--black) 100%)');
} }
droppedGradientLayers = gradients.map((bg, i) => { droppedGradientLayers = gradients.map((bg: any, i: number) => {
const scale = 1 - i * 0.1; const scale = 1 - i * 0.1;
return `background: ${bg}; return `background: ${bg};
width: ${scale * 100}%; width: ${scale * 100}%;
@ -268,7 +268,7 @@
} }
} }
function handleDrop(event) { function handleDrop(event: { preventDefault: () => void; dataTransfer: { getData: (arg0: string) => any; }; }) {
event.preventDefault(); event.preventDefault();
if (droppedMemory && isDroppedVisible) return; if (droppedMemory && isDroppedVisible) return;
@ -277,7 +277,7 @@
loadDroppedTrip(droppedTripId, droppedMemoryId); loadDroppedTrip(droppedTripId, droppedMemoryId);
} }
function allowDrop(event) { function allowDrop(event: { preventDefault: () => void; }) {
if (!droppedMemory || !isDroppedVisible) { if (!droppedMemory || !isDroppedVisible) {
event.preventDefault(); event.preventDefault();
} }
@ -330,20 +330,28 @@
<img class="preview-img" src={currentImage} alt="Current Image" /> <img class="preview-img" src={currentImage} alt="Current Image" />
{/if} {/if}
<!-- svelte-ignore a11y_consider_explicit_label -->
<div class="arrow-controls"> <div class="arrow-controls">
<!-- svelte-ignore a11y_consider_explicit_label -->
<button on:click={prevImage}> <button on:click={prevImage}>
<img src="/lucide_chevron-up.png" alt="Up" width="24" height="24" /> <i class="fa-solid fa-chevron-up"></i>
</button> </button>
<button on:click={nextImage}> <button on:click={nextImage}>
<img src="/lucide_chevron-down.png" alt="Down" width="24" height="24" /> <i class="fa-solid fa-chevron-down"></i>
</button> </button>
</div> </div>
</div> </div>
<!-- rightside dropped view --> <!-- rightside dropped view -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="wheel-section drop-zone" on:drop={handleDrop} on:dragover={allowDrop}> <div class="wheel-section drop-zone" on:drop={handleDrop} on:dragover={allowDrop}>
{#if droppedMemory && isDroppedVisible} {#if droppedMemory && isDroppedVisible}
<img src="/lucide_x.png" alt="Close" class="close-button" on:click={closeDroppedWheel} /> <!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- <img src="/lucide_x.png" alt="Close" class="close-button" on:click={closeDroppedWheel} /> -->
<button class="close-button" on:click={closeDroppedWheel} aria-label="Close">
<i class="fa-solid fa-xmark"></i>
</button>
<div class="dropped-mask"> <div class="dropped-mask">
<div class="dropped-wheel" style={droppedWheelStyle}> <div class="dropped-wheel" style={droppedWheelStyle}>
@ -354,15 +362,18 @@
</div> </div>
{#if droppedCurrentImage} {#if droppedCurrentImage}
<!-- svelte-ignore a11y_img_redundant_alt -->
<img class="dropped-img" src={droppedCurrentImage} alt="Dropped Image" /> <img class="dropped-img" src={droppedCurrentImage} alt="Dropped Image" />
{/if} {/if}
<div class="dropped-controls"> <div class="dropped-controls">
<!-- svelte-ignore a11y_consider_explicit_label -->
<button on:click={prevDroppedImage}> <button on:click={prevDroppedImage}>
<img src="/lucide_chevron-up.png" alt="Up" width="24" height="24" /> <i class="fa-solid fa-chevron-up"></i>
</button> </button>
<!-- svelte-ignore a11y_consider_explicit_label -->
<button on:click={nextDroppedImage}> <button on:click={nextDroppedImage}>
<img src="/lucide_chevron-down.png" alt="Down" width="24" height="24" /> <i class="fa-solid fa-chevron-down"></i>
</button> </button>
</div> </div>
{/if} {/if}
@ -388,6 +399,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100vw; width: 100vw;
font-family: 'Inter', sans-serif;
} }
.header { .header {
@ -583,10 +595,13 @@
.close-button { .close-button {
position: absolute; position: absolute;
background: none;
border: none;
font-size: 1.4rem;
color: white;
top: -48px; top: -48px;
right: 2rem; right: 2rem;
width: 24px; width: 24px;
height: 24px;
cursor: pointer; cursor: pointer;
z-index: 5; z-index: 5;
} }