Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .kiro/specs/flight-map-generator/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,19 @@ This plan implements batch flight map generation with AWS CDK deployment for sta
- **Property 10: HTML Link Correctness**
- **Validates: Requirements 4.3**

- [ ] 4. Create HTML template
- [x] 4. Create HTML template
- Create `src/connections/templates/index.html.j2`
- Implement responsive grid layout
- Add thumbnail previews with clickable links
- Display map titles
- _Requirements: 4.2, 4.3, 4.4_

- [ ] 5. Add index generation CLI command or script
- [x] 5. Add index generation CLI command or script
- Add command to generate index.html from output directory
- Can be integrated into batch command or separate script
- _Requirements: 4.1, 4.5_

- [ ] 6. Checkpoint - Ensure all tests pass
- [x] 6. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.

- [ ] 7. Create CDK infrastructure
Expand Down
23 changes: 23 additions & 0 deletions src/connections/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
import logging
from pathlib import Path

import click

from connections.batch import BatchProcessor
from connections.index import IndexGenerator
from connections.map import FlightMap
from connections.model import Flight, Flights

Expand Down Expand Up @@ -46,5 +48,26 @@ def batch(input_directory: str, output_directory: str) -> None:
click.echo("No flight maps were generated.")


@cli.command()
@click.option("-d", "--directory", type=click.Path(exists=True), required=True)
@click.option("-o", "--output", type=str, default=None)
def index(directory: str, output: str) -> None:
"""Generate index.html from PNG files in a directory"""
generator = IndexGenerator()
maps = generator.scan_output_directory(directory)

if not maps:
click.echo("No PNG files found in directory.")
return

# Default output path is index.html in the same directory
if output is None:
output = str(Path(directory) / "index.html")

generator.generate(maps=maps, output_path=output)
click.echo(f"Generated index page at {output}")
click.echo(f"Found {len(maps)} flight map(s)")


if __name__ == "__main__":
cli()
142 changes: 108 additions & 34 deletions src/connections/templates/index.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flight Maps</title>
<title>Flight Connection Maps</title>
<style>
* {
margin: 0;
Expand All @@ -13,54 +13,74 @@

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: #f5f5f5;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
line-height: 1.6;
}

.container {
max-width: 1400px;
margin: 0 auto;
}

h1 {
color: #333;
margin-bottom: 2rem;
header {
text-align: center;
color: white;
margin-bottom: 3rem;
}

h1 {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}

.subtitle {
font-size: 1.1rem;
opacity: 0.9;
}

.maps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 2rem;
margin-top: 2rem;
margin-bottom: 2rem;
}

.map-card {
background: white;
border-radius: 8px;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.map-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
transform: translateY(-5px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
}

.map-card a {
text-decoration: none;
color: inherit;
display: block;
.map-thumbnail {
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 aspect ratio */
overflow: hidden;
background: #f0f0f0;
}

.map-thumbnail {
.map-thumbnail img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 250px;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.3s ease;
}

.map-card:hover .map-thumbnail img {
transform: scale(1.05);
}

.map-info {
Expand All @@ -72,18 +92,44 @@
font-weight: 600;
color: #333;
margin-bottom: 0.5rem;
word-wrap: break-word;
}

.map-link {
display: inline-block;
margin-top: 0.75rem;
padding: 0.5rem 1rem;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 6px;
font-size: 0.9rem;
font-weight: 500;
transition: background 0.3s ease;
}

.map-link:hover {
background: #5568d3;
}

.empty-state {
text-align: center;
color: white;
padding: 4rem 2rem;
}

.map-filename {
font-size: 0.875rem;
color: #666;
.empty-state h2 {
font-size: 1.5rem;
margin-bottom: 1rem;
}

.no-maps {
footer {
text-align: center;
color: #666;
font-size: 1.125rem;
margin-top: 4rem;
color: white;
margin-top: 3rem;
padding-top: 2rem;
border-top: 1px solid rgba(255, 255, 255, 0.2);
opacity: 0.8;
}

@media (max-width: 768px) {
Expand All @@ -100,29 +146,57 @@
gap: 1.5rem;
}
}

@media (max-width: 480px) {
h1 {
font-size: 1.5rem;
}

.subtitle {
font-size: 1rem;
}

.map-info {
padding: 1rem;
}

.map-title {
font-size: 1.1rem;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Flight Maps</h1>
<header>
<h1>✈️ Flight Connection Maps</h1>
<p class="subtitle">Explore your flight routes around the world</p>
</header>

{% if maps %}
<div class="maps-grid">
{% for map in maps %}
<div class="map-card">
<a href="{{ map.relative_path }}" target="_blank">
<img src="{{ map.relative_path }}" alt="{{ map.title }}" class="map-thumbnail">
<div class="map-info">
<div class="map-title">{{ map.title }}</div>
<div class="map-filename">{{ map.filename }}</div>
</div>
<a href="{{ map.relative_path }}" class="map-thumbnail">
<img src="{{ map.relative_path }}" alt="{{ map.title }}" loading="lazy">
</a>
<div class="map-info">
<h2 class="map-title">{{ map.title }}</h2>
<a href="{{ map.relative_path }}" class="map-link" target="_blank">View Full Size</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p class="no-maps">No flight maps found.</p>
<div class="empty-state">
<h2>No flight maps found</h2>
<p>Add some flight data to generate your first map!</p>
</div>
{% endif %}

<footer>
<p>Generated with Connections Flight Map Generator</p>
</footer>
</div>
</body>
</html>
Loading