diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24eefb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Lession_material +*.DS_Store +.aider* diff --git a/css/map.css b/css/map.css index e69de29..ba46085 100644 --- a/css/map.css +++ b/css/map.css @@ -0,0 +1,528 @@ +/* Map Page Specific Styles */ + +/* App Layout */ +.app-container { + display: flex; + height: 100vh; + overflow: hidden; + font-family: 'Poppins', sans-serif; +} + +/* Sidebar Styles */ +.sidebar { + width: 350px; + background-color: #2c3e50; + color: #ecf0f1; + display: flex; + flex-direction: column; + box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; + z-index: 10; +} + +.sidebar-header { + padding: 20px; + border-bottom: 1px solid #34495e; +} + +.sidebar-header h1 { + margin: 0; + font-size: 1.5rem; + display: flex; + align-items: center; + gap: 10px; +} + +.tagline { + margin: 5px 0 0; + font-size: 0.85rem; + color: #bdc3c7; + font-family: 'Roboto', sans-serif; +} + +/* Mode Selector */ +.mode-selector { + display: flex; + padding: 15px; + gap: 10px; + border-bottom: 1px solid #34495e; +} + +.mode-btn { + flex: 1; + padding: 10px; + background-color: #34495e; + border: none; + color: #ecf0f1; + border-radius: 5px; + cursor: pointer; + font-family: 'Poppins', sans-serif; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.2s ease; +} + +.mode-btn:hover { + background-color: #3d566e; +} + +.mode-btn.active { + background-color: #3498db; + box-shadow: 0 2px 5px rgba(52, 152, 219, 0.3); +} + +/* Panels */ +.panel { + padding: 20px; + border-bottom: 1px solid #34495e; + display: none; +} + +.panel.active-panel { + display: block; +} + +.panel h3 { + margin-top: 0; + display: flex; + align-items: center; + gap: 10px; + font-size: 1.2rem; +} + +/* Form Styles */ +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + display: flex; + align-items: center; + gap: 5px; +} + +.form-group input, +.form-group textarea, +.form-group select { + width: 100%; + padding: 10px; + border-radius: 5px; + border: 1px solid #34495e; + background-color: #34495e; + color: #ecf0f1; + font-family: 'Roboto', sans-serif; +} + +.form-group textarea { + min-height: 80px; + resize: vertical; +} + +/* Instructions */ +.instructions { + background-color: rgba(52, 73, 94, 0.5); + border-radius: 5px; + padding: 15px; + margin: 15px 0; +} + +.instructions h4 { + margin-top: 0; + display: flex; + align-items: center; + gap: 8px; +} + +.instructions ol { + margin: 10px 0 0; + padding-left: 20px; +} + +.instructions li { + margin-bottom: 5px; + font-size: 0.9rem; +} + +/* Buttons */ +.button-group { + display: flex; + gap: 10px; + margin-top: 15px; +} + +.btn { + padding: 10px 15px; + border: none; + border-radius: 5px; + cursor: pointer; + font-family: 'Poppins', sans-serif; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: all 0.2s ease; +} + +.btn-primary { + background-color: #3498db; + color: white; +} + +.btn-primary:hover { + background-color: #2980b9; +} + +.btn-secondary { + background-color: #7f8c8d; + color: white; +} + +.btn-secondary:hover { + background-color: #6c7b7d; +} + +.btn-danger { + background-color: #e74c3c; + color: white; +} + +.btn-danger:hover { + background-color: #c0392b; +} + +.btn-small { + padding: 5px 10px; + font-size: 0.85rem; +} + +/* Filter Options */ +.filter-options { + margin: 15px 0; +} + +.checkbox-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.checkbox-group label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +/* Journey Info */ +.journey-info { + background-color: rgba(52, 73, 94, 0.5); + border-radius: 5px; + padding: 15px; + margin-top: 15px; +} + +.info-content p { + margin: 8px 0; +} + +/* Markers List */ +.markers-list { + flex: 1; + padding: 20px; + overflow-y: auto; +} + +.markers-list h3 { + display: flex; + align-items: center; + gap: 10px; + margin-top: 0; +} + +#markers-container { + margin-top: 15px; +} + +.empty-message { + text-align: center; + color: #7f8c8d; + font-style: italic; + padding: 20px; +} + +/* Footer */ +.sidebar-footer { + padding: 15px 20px; + border-top: 1px solid #34495e; + background-color: #253342; +} + +.navigation { + display: flex; + justify-content: space-around; + margin-bottom: 10px; +} + +.nav-link { + color: #3498db; + text-decoration: none; + display: flex; + align-items: center; + gap: 5px; + font-size: 0.9rem; +} + +.nav-link:hover { + text-decoration: underline; +} + +.footer-text { + text-align: center; + font-size: 0.8rem; + color: #7f8c8d; + margin: 0; +} + +/* Map Area */ +.map-area { + flex: 1; + position: relative; +} + +#map { + width: 100%; + height: 100%; +} + +/* Map Controls */ +.map-controls { + position: absolute; + top: 20px; + right: 20px; + display: flex; + flex-direction: column; + gap: 10px; + z-index: 1; +} + +.control-btn { + width: 40px; + height: 40px; + border-radius: 5px; + background-color: white; + border: none; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + color: #2c3e50; + transition: all 0.2s ease; +} + +.control-btn:hover { + background-color: #f8f9fa; + transform: translateY(-2px); +} + +/* Mode Indicator */ +.mode-indicator { + position: absolute; + top: 20px; + left: 20px; + background-color: rgba(255, 255, 255, 0.9); + padding: 10px 15px; + border-radius: 5px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + gap: 10px; + z-index: 1; +} + +.indicator-text { + font-weight: 500; + color: #2c3e50; +} + +.indicator-dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.indicator-dot.creating { + background-color: #3498db; +} + +.indicator-dot.viewing { + background-color: #2ecc71; +} + +/* Modal */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 100; + align-items: center; + justify-content: center; +} + +.modal.active { + display: flex; +} + +.modal-content { + background-color: white; + border-radius: 10px; + width: 90%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2); +} + +.modal-header { + padding: 20px; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h3 { + margin: 0; + display: flex; + align-items: center; + gap: 10px; +} + +.close-btn { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #7f8c8d; +} + +.close-btn:hover { + color: #2c3e50; +} + +.modal-body { + padding: 20px; +} + +/* Image Upload */ +.image-upload-area { + border: 2px dashed #ddd; + border-radius: 5px; + padding: 15px; + margin-top: 5px; +} + +.image-preview { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 15px; + min-height: 60px; +} + +.upload-actions { + display: flex; + gap: 10px; +} + +.help-text { + display: block; + margin-top: 5px; + color: #7f8c8d; + font-size: 0.85rem; +} + +.coordinates { + padding: 10px; + background-color: #f8f9fa; + border-radius: 5px; + margin-top: 15px; +} + +.modal-footer { + padding: 15px 20px; + border-top: 1px solid #eee; + display: flex; + gap: 10px; + justify-content: flex-end; +} + +/* Toast */ +.toast { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #2ecc71; + color: white; + padding: 15px 20px; + border-radius: 5px; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); + display: none; + z-index: 100; + animation: slideIn 0.3s ease; +} + +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* Responsive */ +@media (max-width: 768px) { + .sidebar { + position: fixed; + top: 0; + left: 0; + height: 100%; + transform: translateX(-100%); + } + + .sidebar.active { + transform: translateX(0); + } + + .app-container { + flex-direction: column; + } + + .map-controls { + top: auto; + bottom: 20px; + right: 20px; + flex-direction: row; + } + + .mode-indicator { + top: 10px; + left: 10px; + font-size: 0.9rem; + } +} \ No newline at end of file diff --git a/css/style.css b/css/style.css index b941227..de33381 100644 --- a/css/style.css +++ b/css/style.css @@ -9,4 +9,39 @@ .sidebar { width: 350px; } -} \ No newline at end of file +} + +/* Journey timeline styles */ +.journey-timeline { + padding: 20px; + max-width: 800px; + margin: 0 auto; +} + +.timeline-event { + border-left: 3px solid #3498db; + padding: 15px 20px; + margin: 20px 0; + background-color: #f9f9f9; +} + +.timeline-event .date { + color: #7f8c8d; + font-style: italic; +} + +.timeline-event .location { + font-size: 0.9em; + color: #2c3e50; +} + +.images-container { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.images-container img { + max-width: 100px; + height: auto; +} diff --git a/journey.html b/journey.html index e69de29..826cb5f 100644 --- a/journey.html +++ b/journey.html @@ -0,0 +1,17 @@ + + + + + + Journey Timeline + + + + +
+

+
+
+ + + diff --git a/js/journey-post.js b/js/journey-post.js index e69de29..876e62d 100644 --- a/js/journey-post.js +++ b/js/journey-post.js @@ -0,0 +1,37 @@ +function displayJourney(journey) { + document.getElementById('journey-title').textContent = journey.name; + + const container = document.getElementById('timeline-container'); + container.innerHTML = ''; + + journey.markers.forEach(marker => { + const markerEl = document.createElement('div'); + markerEl.className = 'timeline-event'; + markerEl.innerHTML = ` +

${marker.content.title || 'Untitled Event'}

+

${marker.content.date}

+

${marker.lngLat.lng.toFixed(4)}, ${marker.lngLat.lat.toFixed(4)}

+

${marker.content.text}

+ ${marker.content.videoUrl ? `` : ''} +
+ ${marker.content.images.map(img => `Event image`).join('')} +
+ `; + container.appendChild(markerEl); + }); +} + +// Get journey ID from URL +const urlParams = new URLSearchParams(window.location.search); +const journeyId = urlParams.get('id'); + +// Load journey data from localStorage +if (journeyId) { + loadJourneysFromLocalStorage(); + const journey = journeys.find(j => j.id === parseInt(journeyId)); + if (journey) { + displayJourney(journey); + } else { + document.getElementById('journey-title').textContent = 'Journey not found'; + } +} diff --git a/js/main.js b/js/main.js index e69de29..dbe0ff7 100644 --- a/js/main.js +++ b/js/main.js @@ -0,0 +1,71 @@ +document.addEventListener('DOMContentLoaded', function() { + // Mode switching + const modeCreateBtn = document.getElementById('mode-create'); + const modeViewBtn = document.getElementById('mode-view'); + const createPanel = document.getElementById('create-panel'); + const viewPanel = document.getElementById('view-panel'); + const markersContainer = document.getElementById('markers-container'); + const emptyMarkers = document.getElementById('empty-markers'); + + function switchMode(mode) { + if (mode === 'create') { +倉-> modeCreateBtn.classList.add('active'); + modeViewBtn.classList.remove('active'); + createPanel.classList.add('active-panel'); + viewPanel.classList.remove('active-panel'); + //ҧ Enable marker adding + window.isCreatingJourney = true; + } else { // view mode + modeCreateBtn.classList.remove('active'); + modeViewBtn.classList.add('active'); + createPanel.classList.remove('active-panel'); + viewPanel.classList.add('active-panel'); + // Disable marker adding + window.isCreatingJourney = false; + } + } + + modeCreateBtn.addEventListener('click', () => switchMode('create')); + modeViewBtn.addEventListener('click', () => switchMode('view')); + + // Journey save handler + document.getElementById('save-journey').addEventListener('click', function() { + const title = document.getElementById('journey-title').value; + const description = document.getElementById('journey-description').value; + + if (!title.trim()) { + alert('Journey title cannot be empty'); + return; + } + + window.currentJourney.name = title; + window.currentJourney.description = description; + window.saveJourneyToLocalStorage(); + + // Show notification + document.getElementById('toast-message').textContent = 'Journey saved successfully!'; + document.getElementById('toast').classList.add('show'); + setTimeout(() => { + document.getElementById('toast').classList.remove('show'); + }, 3000); + }); + + // Clear markers + document.getElementById('clear-markers').addEventListener('click', function() { + if (window.currentJourney.markers.length > 0 && confirm('Are you sure you want to clear all markers?')) { + window.currentJourney.markers.forEach(marker => marker.remove()); + window.currentJourney.markers = []; + markersContainer.innerHTML = ''; + emptyMarkers.style.display = 'block'; + window.updateJourneyPath(); + } + }); + + // Toggle sidebar + document.getElementById('toggle-sidebar').addEventListener('click', function() { + document.querySelector('.sidebar').classList.toggle('collapsed'); + }); + + // Initialize in create mode + switchMode('create'); +}); diff --git a/js/map.js b/js/map.js index 190b295..08d66f4 100644 --- a/js/map.js +++ b/js/map.js @@ -1,11 +1,36 @@ let map; +let journeys = []; let currentJourney = { + id: Date.now(), + name: "Untitled Journey", + description: "", markers: [], - path: nullJustin // This will be the line feature (GeoJSON) + path: null }; let currentMarkerBeingEdited = null; let isCreatingJourney = true; +function saveJourneyToLocalStorage() { + journeys.push({ + id: currentJourney.id, + name: currentJourney.name, + description: currentJourney.description, + markers: currentJourney.markers.map(marker => ({ + id: marker.id, + lngLat: marker.getLngLat(), + content: marker.content + })) + }); + localStorage.setItem('journeys', JSON.stringify(journeys)); +} + +function loadJourneysFromLocalStorage() { + const stored = localStorage.getItem('journeyMapper_journeys'); + if (stored) { + journeys = JSON.parse(stored); + } +} + // Function to create a marker at a lngLat and add to the map function createMarker(lngLat) { const markerElement = document.createElement('div'); @@ -40,14 +65,24 @@ function createMarker(lngLat) { function openMarkerEditor(marker) { currentMarkerBeingEdited = marker; - document.getElementById('marker-title').value = marker.content.title; - document.getElementById('marker-date').value = marker.content.date; - document.getElementById('marker-text').value = marker.content.text; - document.getElementById('video-url').value = marker.content.videoUrl; - document.getElementById('marker-coords').textContent = + document.getElementById('marker-title').value = marker.content.title || ''; + document.getElementById('marker-date').value = marker.content.date || ''; + document.getElementById('marker-text').value = marker.content.text || ''; + document.getElementById('video-url').value = marker.content.videoUrl || ''; + document.getElementById('marker-coords').textContent = `${marker.getLngLat().lng.toFixed(4)}, ${marker.getLngLat().lat.toFixed(4)}`; - // Show the modal + // Update imagine review + const imagePreview = document.getElementById('image-preview'); + imagePreview.innerHTML = ''; + 学生学习 if (marker.content.images && marker.content.images.length > 0) { + marker.content.images.forEach(img => { + const imgEl = document.createElement('img'); + imgEl.src = img; + imagePreview.appendChild(imgEl); + }); + } + document.getElementById('marker-modal').style.display = 'block'; } @@ -64,6 +99,35 @@ document.addEventListener('DOMContentLoaded', function() { zoom: 2 }); + // Add journey path layer + map.on('load', function() { + map.addSource('journey-path', { + type: 'geojson', + data: { + type: 'Feature', + properties: {}, + geometry: { + type: 'LineString', + coordinates: [] + } + } + }); + + map.addLayer({ + id: 'journey-path', + type: 'line', + source: 'journey-path', + layout: { + 'line-join': 'round', + 'line-cap': 'round' + }, + paint: { + 'line-color': '#3887be', + 'line-width': 5 + } + }); + }); + // Close editor events document.getElementById('close-modal').addEventListener('click', closeMarkerEditor); document.getElementById('cancel-marker').addEventListener('click', closeMarkerEditor); @@ -80,7 +144,7 @@ document.addEventListener('DOMContentLoaded', function() { // Update the popup currentMarkerBeingEdited.getPopup().setHTML( - `${currentMarkerBeingEdited.content.title || '袭刊Untitled'}` + `${currentMarkerBeingEdited.content.title || 'Untitled'}` ); closeMarkerEditor(); @@ -107,7 +171,18 @@ document.addEventListener('DOMContentLoaded', function() { if (isCreatingJourney) { const marker = createMarker(e.lngLat); currentJourney.markers.push(marker); - // TODO: update the path line + updateJourneyPath(); } }); -}); \ No newline at end of file + + // Save journey button + document.getElementById('save-journey-btn').addEventListener('click', function() { + currentJourney.name = document.getElementById('journey-name').value; + currentJourney.description = document.getElementById('journey-description').value; + saveJourneyToLocalStorage(); + alert('Journey saved!'); + }); + + // Load journeys on start + loadJourneysFromLocalStorage(); +}); diff --git a/map-page.html b/map-page.html index f28d94c..5fb8c6c 100644 --- a/map-page.html +++ b/map-page.html @@ -260,16 +260,4 @@ - - - - - \ No newline at end of file +