Files
reitti/docs/tools/gpx-generator/index.html

1134 lines
38 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPX Test Data Generator - Create and Edit GPS Tracks</title>
<meta name="description" content="Interactive GPX track generator and editor. Create realistic GPS test data with customizable speed, elevation, and timing. Perfect for testing location-based applications.">
<meta name="keywords" content="GPX, GPS, track generator, test data, location testing, GPS simulation, route planning">
<meta name="author" content="Reitti Project">
<meta property="og:title" content="GPX Test Data Generator">
<meta property="og:description" content="Interactive tool to create and edit GPX tracks for testing location-based applications">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="GPX Test Data Generator">
<meta name="twitter:description" content="Interactive tool to create and edit GPX tracks for testing location-based applications">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-tertiary: #f1f5f9;
--bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--text-primary: #2d3748;
--text-secondary: #4a5568;
--text-muted: #718096;
--border-color: #e2e8f0;
--border-color-light: rgba(226, 232, 240, 0.8);
--shadow-light: rgba(0, 0, 0, 0.06);
--shadow-medium: rgba(0, 0, 0, 0.1);
--shadow-heavy: rgba(0, 0, 0, 0.15);
}
[data-theme="dark"] {
--bg-primary: #1a202c;
--bg-secondary: #2d3748;
--bg-tertiary: #4a5568;
--bg-gradient: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
--text-primary: #f7fafc;
--text-secondary: #e2e8f0;
--text-muted: #a0aec0;
--border-color: #4a5568;
--border-color-light: rgba(74, 85, 104, 0.8);
--shadow-light: rgba(0, 0, 0, 0.2);
--shadow-medium: rgba(0, 0, 0, 0.3);
--shadow-heavy: rgba(0, 0, 0, 0.4);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--bg-gradient);
min-height: 100vh;
color: var(--text-primary);
line-height: 1.5;
transition: all 0.3s ease;
}
.container {
max-width: 100%;
margin: 0;
padding: 0;
}
.main-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
box-shadow: 0 25px 50px var(--shadow-heavy);
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.3);
position: relative;
}
.edit-drawer {
position: fixed;
left: -400px;
top: 0;
width: 380px;
height: 100vh;
background: var(--bg-primary);
border-right: 1px solid var(--border-color);
box-shadow: 4px 0 20px var(--shadow-medium);
transition: left 0.3s ease;
z-index: 2000;
overflow-y: auto;
}
.edit-drawer.open {
left: 0;
}
.edit-drawer-header {
padding: 16px 20px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.edit-drawer-close {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: var(--text-secondary);
padding: 4px;
border-radius: 4px;
}
.edit-drawer-close:hover {
background: var(--bg-tertiary);
}
.edit-drawer-content {
padding: 20px;
}
.drawer-section {
margin-bottom: 24px;
}
.drawer-section-title {
font-size: 14px;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}
.mode-toggle {
background: var(--bg-primary);
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 8px;
}
.mode-toggle.edit-mode {
border-color: #48bb78;
background: #48bb78;
color: white;
}
.mode-toggle:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--shadow-medium);
}
.about-dialog {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: none;
justify-content: center;
align-items: center;
z-index: 2000;
}
.about-dialog.open {
display: flex;
}
.about-content {
background: var(--bg-primary);
border-radius: 12px;
padding: 32px;
max-width: 600px;
margin: 20px;
box-shadow: 0 20px 60px var(--shadow-heavy);
border: 1px solid var(--border-color);
}
.about-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.about-title {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
}
.about-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: var(--text-secondary);
padding: 4px;
border-radius: 4px;
}
.about-close:hover {
background: var(--bg-tertiary);
}
.about-text {
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 20px;
}
.about-link {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.about-link:hover {
text-decoration: underline;
}
.header-section {
display: flex;
align-items: center;
gap: 12px;
}
.header-divider {
width: 1px;
height: 24px;
background: var(--border-color);
margin: 0 8px;
}
[data-theme="dark"] .main-container {
background: rgba(26, 32, 44, 0.95);
border: 1px solid rgba(74, 85, 104, 0.3);
}
.main-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.8), transparent);
}
.header {
background: var(--bg-secondary);
color: var(--text-secondary);
padding: 8px 16px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
height: 56px;
}
.header-content {
flex: 1;
}
.header-controls {
display: flex;
gap: 12px;
align-items: center;
}
.theme-toggle {
background: var(--bg-primary);
border: 2px solid var(--border-color);
border-radius: 6px;
padding: 6px 12px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
color: var(--text-secondary);
}
.theme-toggle:hover {
border-color: #667eea;
background: #667eea;
color: white;
}
.layer-toggle {
background: var(--bg-primary);
border: 2px solid var(--border-color);
border-radius: 6px;
padding: 6px 12px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
color: var(--text-secondary);
}
.layer-toggle:hover {
border-color: #48bb78;
background: #48bb78;
color: white;
}
.header h1 {
font-size: 16px;
font-weight: 500;
margin-bottom: 2px;
}
.header p {
opacity: 0.8;
font-size: 12px;
}
.controls {
padding: 12px 16px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color-light);
backdrop-filter: blur(10px);
}
.control-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
margin-bottom: 12px;
}
.control-group {
display: flex;
flex-direction: column;
}
.control-label {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 6px;
}
.control-input {
padding: 8px 12px;
border: 2px solid var(--border-color);
border-radius: 8px;
font-size: 13px;
transition: all 0.3s ease;
background: var(--bg-primary);
color: var(--text-primary);
font-family: inherit;
box-shadow: inset 0 2px 4px var(--shadow-light);
}
.control-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1), inset 0 2px 4px rgba(0, 0, 0, 0.06);
background: linear-gradient(145deg, #ffffff, #f1f5f9);
}
[data-theme="dark"] .control-input {
background: var(--bg-secondary);
color: var(--text-primary);
border-color: var(--border-color);
}
[data-theme="dark"] .control-input:focus {
background: var(--bg-tertiary);
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2), inset 0 2px 4px var(--shadow-light);
}
.control-button {
padding: 8px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
background: linear-gradient(145deg, #ffffff, #f8fafc);
color: #4a5568;
font-family: inherit;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.control-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
transition: left 0.5s;
}
.control-button:hover {
border-color: #667eea;
background: linear-gradient(145deg, #667eea, #5a67d8);
color: white;
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.control-button:hover::before {
left: 100%;
}
.control-button.active {
background: #667eea;
border-color: #667eea;
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.control-button.success {
background: #48bb78;
border-color: #48bb78;
color: white;
}
.control-button.success:hover {
background: #38a169;
border-color: #38a169;
}
.control-button.danger {
background: #f56565;
border-color: #f56565;
color: white;
}
.control-button.danger:hover {
background: #e53e3e;
border-color: #e53e3e;
}
.paint-mode-info {
font-size: 12px;
color: #718096;
margin-top: 6px;
font-style: italic;
}
.button-group {
display: flex;
gap: 12px;
}
.main-content {
display: flex;
height: 91vh;
min-height: 650px;
}
.map-container {
flex: 3;
position: relative;
}
#map {
height: 100%;
width: 100%;
}
.points-panel {
flex: 1;
min-width: 350px;
border-left: 1px solid rgba(226, 232, 240, 0.8);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
}
.speed-legend {
padding: 12px 20px;
background: rgba(247, 250, 252, 0.9);
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
font-size: 12px;
}
.speed-legend-title {
font-weight: 600;
margin-bottom: 8px;
color: #4a5568;
}
.speed-legend-items {
display: flex;
flex-direction: column;
gap: 4px;
}
.speed-legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.speed-color-indicator {
width: 12px;
height: 12px;
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.points-header {
padding: 20px;
background: linear-gradient(135deg, #4a5568, #2d3748);
color: white;
font-weight: 600;
font-size: 16px;
}
.points-summary {
padding: 16px 20px;
background: rgba(247, 250, 252, 0.9);
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
font-size: 13px;
color: #4a5568;
}
.points-list {
flex: 1;
overflow-y: auto;
}
.point-item {
padding: 6px 20px;
border-bottom: 1px solid rgba(237, 242, 247, 0.8);
cursor: pointer;
transition: all 0.3s ease;
font-size: 10px;
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 28px;
border-left: 4px solid #e2e8f0;
}
.point-item:hover {
background: rgba(102, 126, 234, 0.05);
transform: translateX(4px);
}
.point-item.selected {
background: rgba(102, 126, 234, 0.1);
border-left: 4px solid #667eea !important;
}
.point-content {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
font-family: 'Monaco', 'Menlo', monospace;
}
.point-coords {
color: #4a5568;
font-weight: 500;
min-width: 120px;
}
.point-time {
color: #718096;
min-width: 80px;
}
.point-speed {
font-weight: 500;
min-width: 45px;
text-align: right;
}
.point-delete {
background: #f56565;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
transition: all 0.3s ease;
opacity: 0;
}
.point-item:hover .point-delete {
opacity: 1;
}
.point-delete:hover {
background: #e53e3e;
transform: scale(1.1);
}
.status {
padding: 20px;
background: rgba(247, 250, 252, 0.9);
border-top: 1px solid rgba(226, 232, 240, 0.8);
font-size: 14px;
color: #4a5568;
display: flex;
justify-content: space-between;
align-items: center;
}
.speed-legend-inline {
display: flex;
gap: 12px;
align-items: center;
font-size: 12px;
}
.speed-legend-inline .speed-legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.speed-legend-inline .speed-color-indicator {
width: 10px;
height: 10px;
border-radius: 2px;
border: 1px solid rgba(0, 0, 0, 0.1);
}
.hover-tooltip {
position: absolute;
background: rgba(26, 32, 44, 0.95);
color: white;
padding: 12px 16px;
border-radius: 12px;
font-size: 13px;
pointer-events: none;
z-index: 1000;
white-space: nowrap;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.speed-indicator {
font-size: 12px;
margin-top: 4px;
font-weight: 500;
}
.speed-ok { color: #48bb78; }
.speed-fast { color: #ed8936; }
.speed-unrealistic { color: #f56565; }
.track-header {
padding: 12px 20px;
background: #2d3748;
color: white;
cursor: pointer;
border-bottom: 1px solid rgba(226, 232, 240, 0.8);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
transition: all 0.3s ease;
}
.track-header:hover {
background: #4a5568;
}
.track-header.active {
background: #667eea;
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3);
}
.track-header.active:hover {
background: #5a67d8;
}
.track-color {
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 8px;
border: 2px solid white;
}
.track-info {
display: flex;
align-items: center;
}
.track-controls {
display: flex;
gap: 8px;
}
.track-export-btn {
padding: 4px 8px;
background: #48bb78;
color: white;
border: none;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
}
.track-export-btn:hover {
background: #38a169;
}
.track-points {
display: block;
}
.track-points.collapsed {
display: none;
}
.collapse-icon {
transition: transform 0.3s ease;
}
.collapsed .collapse-icon {
transform: rotate(-90deg);
}
.range-input {
-webkit-appearance: none;
appearance: none;
height: 6px;
border-radius: 3px;
background: #e2e8f0;
outline: none;
transition: all 0.3s ease;
}
.range-input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
}
.range-input::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.5);
}
.paint-active {
background: #48bb78 !important;
border-color: #48bb78 !important;
color: white !important;
animation: pulse 2s infinite;
}
.paint-ready {
background: #ed8936 !important;
border-color: #ed8936 !important;
color: white !important;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(72, 187, 120, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(72, 187, 120, 0); }
100% { box-shadow: 0 0 0 0 rgba(72, 187, 120, 0); }
}
/* Scrollbar styling */
.points-list::-webkit-scrollbar {
width: 6px;
}
.points-list::-webkit-scrollbar-track {
background: rgba(237, 242, 247, 0.5);
}
.points-list::-webkit-scrollbar-thumb {
background: rgba(102, 126, 234, 0.3);
border-radius: 3px;
}
.points-list::-webkit-scrollbar-thumb:hover {
background: rgba(102, 126, 234, 0.5);
}
/* Date Picker Styles */
.date-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 8px;
margin: 20px 0;
max-height: 300px;
overflow-y: auto;
padding: 4px;
}
.date-item {
padding: 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
text-align: center;
font-size: 12px;
cursor: pointer;
background: var(--bg-primary);
transition: all 0.2s;
user-select: none;
}
.date-item:hover {
background: var(--bg-tertiary);
}
.date-item.selected {
background: #667eea;
color: white;
border-color: #667eea;
}
.date-item.in-range {
background: rgba(102, 126, 234, 0.2);
border-color: #667eea;
}
</style>
</head>
<body>
<div class="container">
<div class="main-container">
<div class="header">
<div class="header-content">
<h1>GPX Test Data Generator</h1>
<p id="headerDescription">Interactive tool for creating and editing GPS tracks</p>
</div>
<div class="header-controls">
<div class="header-section">
<button id="modeToggle" class="mode-toggle" onclick="toggleEditMode()">
<span>📍</span> View Mode
</button>
</div>
<div class="header-divider"></div>
<div class="header-section">
<button onclick="document.getElementById('gpxFileInput').click()" class="control-button">📁 Import</button>
<button onclick="exportAllGPX()" class="control-button success">💾 Export</button>
<button onclick="clearAll()" class="control-button danger">🗑️ Clear</button>
</div>
<div class="header-divider"></div>
<div class="header-section">
<button onclick="openAboutDialog()" class="control-button"> About</button>
<button id="layerToggle" class="layer-toggle" onclick="toggleMapLayer()">🛰️ Satellite</button>
<button id="themeToggle" class="theme-toggle" onclick="toggleTheme()">🌙</button>
</div>
</div>
</div>
<!-- Edit Drawer -->
<div class="edit-drawer" id="editDrawer">
<div class="edit-drawer-header">
<span>Edit Controls</span>
<button class="edit-drawer-close" onclick="toggleEditDrawer()">×</button>
</div>
<div class="edit-drawer-content">
<div class="drawer-section">
<div class="drawer-section-title">Track Settings</div>
<div class="control-group">
<label class="control-label" for="startDateTime">Start Date & Time</label>
<input type="datetime-local" id="startDateTime" step="1" class="control-input">
</div>
<div class="control-group">
<label class="control-label" for="timeInterval">Interval (seconds)</label>
<input type="number" id="timeInterval" value="30" min="1" max="3600" class="control-input">
</div>
<div class="control-group">
<label class="control-label" for="maxSpeed">Max Speed (km/h)</label>
<input type="number" id="maxSpeed" value="25" min="1" max="300" class="control-input">
</div>
</div>
<div class="drawer-section">
<div class="drawer-section-title">Elevation & GPS</div>
<div class="control-group">
<label class="control-label" for="elevation">Elevation (m)</label>
<input type="number" id="elevation" value="10" step="0.1" class="control-input">
</div>
<div class="control-group">
<label class="control-label" for="elevationVariation">±Variation (m)</label>
<input type="number" id="elevationVariation" value="0" min="0" step="0.1" class="control-input">
</div>
<div class="control-group">
<label class="control-label" for="accuracySlider">GPS Accuracy: <span id="accuracyValue">5</span>m</label>
<input type="range" id="accuracySlider" min="0" max="100" value="5" class="range-input">
</div>
</div>
<div class="drawer-section">
<div class="drawer-section-title">Options</div>
<div class="control-group">
<label class="control-label">
<input type="checkbox" id="autoNewTrack" checked style="margin-right: 8px;">
Auto new track on day change
</label>
</div>
<div class="control-group">
<label class="control-label">
<input type="checkbox" id="autoStops" style="margin-right: 8px;">
Add realistic stops
</label>
</div>
</div>
<div class="drawer-section">
<div class="drawer-section-title">Drawing Tools</div>
<div class="control-group">
<button id="paintModeToggle" class="control-button" onclick="togglePaintMode()">🎨 Paint Mode: OFF</button>
<div class="paint-mode-info">Click map to start/stop painting</div>
</div>
<div class="control-group">
<button onclick="newTrack()" class="control-button"> New Track</button>
</div>
</div>
<div class="drawer-section">
<div class="drawer-section-title">Time Adjustment</div>
<div class="control-group">
<label class="control-label">Shift Current Track</label>
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
<button onclick="shiftTrackTime(-1, 'hour')" class="control-button" style="flex: 1; font-size: 11px;">-1h</button>
<button onclick="shiftTrackTime(1, 'hour')" class="control-button" style="flex: 1; font-size: 11px;">+1h</button>
</div>
<div style="display: flex; gap: 8px;">
<button onclick="shiftTrackTime(-1, 'day')" class="control-button" style="flex: 1; font-size: 11px;">-1d</button>
<button onclick="shiftTrackTime(1, 'day')" class="control-button" style="flex: 1; font-size: 11px;">+1d</button>
</div>
</div>
</div>
</div>
</div>
<input type="file" id="gpxFileInput" accept=".gpx,.json" multiple style="display: none;" onchange="handleGPXFiles(event)">
<div class="main-content">
<div class="map-container">
<div id="map"></div>
</div>
<div class="points-panel">
<div class="points-header">
Points (<span id="pointCount">0</span>)
</div>
<div class="points-summary" id="pointsSummary">
Click on the map to add points
</div>
<div class="points-list" id="pointsList">
</div>
</div>
</div>
<div class="status">
<span id="statusText">Ready to create GPX track</span>
<div class="speed-legend-inline" id="speedLegendInline">
<!-- Speed legend will be populated by JavaScript -->
</div>
</div>
</div>
<!-- About Dialog -->
<div class="about-dialog" id="aboutDialog">
<div class="about-content">
<div class="about-header">
<h2 class="about-title">About GPX Generator</h2>
<button class="about-close" onclick="closeAboutDialog()">×</button>
</div>
<div class="about-text">
<p>This interactive GPX generator is designed for creating realistic GPS test data for location-based applications. It's particularly useful for testing and development of GPS tracking systems, route planning applications, and location analytics tools.</p>
<p><strong>Features:</strong></p>
<ul style="margin: 12px 0; padding-left: 20px;">
<li>Interactive map-based point creation</li>
<li>Realistic speed and elevation simulation</li>
<li>Customizable GPS accuracy and timing</li>
<li>Multiple track support with export capabilities</li>
<li>Paint mode for continuous track drawing</li>
</ul>
<p><strong>Use Cases:</strong></p>
<ul style="margin: 12px 0; padding-left: 20px;">
<li>Testing location-based mobile applications</li>
<li>Creating sample data for GPS analytics</li>
<li>Simulating user movement patterns</li>
<li>Generating test routes for navigation systems</li>
</ul>
<p>This tool is part of the <a href="https://github.com/dedicatedcode/reitti" target="_blank" rel="noopener" class="about-link">Reitti project</a> - an open-source location tracking and analytics platform.</p>
</div>
</div>
</div>
<!-- JSON Date Picker Dialog -->
<div class="about-dialog" id="jsonDatePicker">
<div class="about-content">
<div class="about-header">
<h2 class="about-title">Select Dates to Import</h2>
<button class="about-close" onclick="closeJsonPicker()">×</button>
</div>
<div class="about-text">
<p>Select a range of dates from the Google Records file. Click a date to select it, or use Shift+Click to select a range.</p>
<div id="dateGrid" class="date-grid"></div>
<div style="display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px;">
<button class="control-button" onclick="closeJsonPicker()">Cancel</button>
<button class="control-button success" onclick="importSelectedJsonDates()">Import Selected</button>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="gpx-generator.js"></script>
<script>
// Initialize edit mode state
let isEditMode = false;
let isEditDrawerOpen = false;
function toggleEditMode() {
isEditMode = !isEditMode;
const modeToggle = document.getElementById('modeToggle');
const headerDescription = document.getElementById('headerDescription');
if (isEditMode) {
modeToggle.innerHTML = '<span>✏️</span> Edit Mode';
modeToggle.classList.add('edit-mode');
headerDescription.textContent = 'Click on the map to add points. Right-click points to remove them.';
// Open the edit drawer automatically
if (!isEditDrawerOpen) {
toggleEditDrawer();
}
// Enable point addition (this will be handled in gpx-generator.js)
if (window.enableEditMode) {
window.enableEditMode();
}
} else {
modeToggle.innerHTML = '<span>📍</span> View Mode';
modeToggle.classList.remove('edit-mode');
headerDescription.textContent = 'Interactive tool for creating and editing GPS tracks';
// Close edit drawer if open
if (isEditDrawerOpen) {
toggleEditDrawer();
}
// Disable point addition (this will be handled in gpx-generator.js)
if (window.disableEditMode) {
window.disableEditMode();
}
}
}
function toggleEditDrawer() {
isEditDrawerOpen = !isEditDrawerOpen;
const drawer = document.getElementById('editDrawer');
if (isEditDrawerOpen) {
drawer.classList.add('open');
} else {
drawer.classList.remove('open');
}
}
function openAboutDialog() {
document.getElementById('aboutDialog').classList.add('open');
}
function closeAboutDialog() {
document.getElementById('aboutDialog').classList.remove('open');
}
// Close about dialog when clicking outside
document.getElementById('aboutDialog').addEventListener('click', function(e) {
if (e.target === this) {
closeAboutDialog();
}
});
// Initialize the page in view mode
document.addEventListener('DOMContentLoaded', function() {
// Ensure we start in view mode
isEditMode = false;
isEditDrawerOpen = false;
// Set up initial UI state
const modeToggle = document.getElementById('modeToggle');
const headerDescription = document.getElementById('headerDescription');
modeToggle.innerHTML = '<span>📍</span> View Mode';
modeToggle.classList.remove('edit-mode');
headerDescription.textContent = 'Interactive tool for creating and editing GPS tracks';
// Ensure drawer is closed
const drawer = document.getElementById('editDrawer');
drawer.classList.remove('open');
// Disable edit mode in the map (this will be handled in gpx-generator.js)
setTimeout(() => {
if (window.disableEditMode) {
window.disableEditMode();
}
}, 100);
});
</script>
</body>
</html>