- Removed inline CSS from index.html and linked to external stylesheet. - Moved JavaScript code to a separate app.js file for better organization. - Added a health-check endpoint to the Flask application for monitoring purposes. - Implemented a static file serving route to handle requests for static assets.
148 lines
4.9 KiB
HTML
148 lines
4.9 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>TeslaCam Video Player</title>
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||
</head>
|
||
<body>
|
||
<!-- Header -->
|
||
<header>
|
||
<div class="brand">
|
||
<span class="logo" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24">
|
||
<path
|
||
d="M12 3C7 3 3.2 4.4 3.2 4.4l.9 1.7s3.1-1 5-.9l.7 1.3C7.9 7.7 6 9 6 9s.6 2.4 3 4.6c2.5-2.2 3-4.6 3-4.6s-1.9-1.3-3.7-1.5l.6-1.3c1.9-.1 5 .9 5 .9l.9-1.7S17 3 12 3z"
|
||
/>
|
||
</svg>
|
||
</span>
|
||
<div>
|
||
TeslaCam Player
|
||
<small>Dashcam & Sentry Archive</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="search-wrap">
|
||
<span class="search-icon" aria-hidden="true">
|
||
<svg
|
||
width="18"
|
||
height="18"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2"
|
||
>
|
||
<circle cx="11" cy="11" r="7" />
|
||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||
</svg>
|
||
</span>
|
||
<input
|
||
type="text"
|
||
id="searchBox"
|
||
placeholder="Search by date, timestamp or filename…"
|
||
autocomplete="off"
|
||
aria-label="Search clips"
|
||
/>
|
||
<button class="clear-btn" id="clearSearch" aria-label="Clear search">
|
||
×
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Layout -->
|
||
<div class="layout">
|
||
<!-- Left: Video Player Panel -->
|
||
<section class="player-panel">
|
||
<div class="video-stage">
|
||
<video id="player" controls preload="metadata" playsinline></video>
|
||
<div class="video-badge" id="videoBadge">
|
||
<span class="dot"></span><span id="badgeText">Front</span>
|
||
</div>
|
||
<div class="video-placeholder" id="videoPlaceholder">
|
||
<svg
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="1.5"
|
||
>
|
||
<rect x="2" y="6" width="14" height="12" rx="2" />
|
||
<path d="M16 10l6-3v10l-6-3" />
|
||
</svg>
|
||
<div>Select a clip from the list to begin playback</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Synchronized Camera Switching Bar -->
|
||
<div class="cam-switch" id="camSwitch">
|
||
<span class="cs-label">Camera Angles — Same Timestamp</span>
|
||
<div
|
||
id="camSwitchButtons"
|
||
style="display: flex; flex-wrap: wrap; gap: 8px; width: 100%"
|
||
>
|
||
<span
|
||
style="color: var(--text-muted); font-size: 12.5px; padding: 4px"
|
||
>Select a video to unlock synchronized camera switching.</span
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="now-playing" id="nowPlaying" hidden>
|
||
<span>Now Playing: <strong id="npName">—</strong></span>
|
||
<span class="ts" id="npTime">—</span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Right: Sidebar & Clip Library -->
|
||
<aside class="sidebar">
|
||
<div class="cam-tabs" id="camTabs">
|
||
<button class="tab active" data-cam="all">
|
||
All Views <span class="count" id="countAll">0</span>
|
||
</button>
|
||
{% for name, data in categorized_videos.items() %}
|
||
<button class="tab" data-cam="{{ data.raw_key }}">
|
||
{{ name }} <span class="count">{{ data.videos | length }}</span>
|
||
</button>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<div class="list-toolbar">
|
||
<span id="resultCount">0 clips</span>
|
||
<select id="sortSelect" aria-label="Sort clips">
|
||
<option value="oldest">Oldest first</option>
|
||
<option value="name">Filename A–Z</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="clip-list" id="clipList">
|
||
<!-- Dynamically populated -->
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
|
||
<!-- Embedded Data Payload from Flask -->
|
||
<script type="application/json" id="videoData">
|
||
{
|
||
{% for name, data in categorized_videos.items() %}
|
||
"{{ data.raw_key }}": {
|
||
"display": {{ name | tojson }},
|
||
"raw_key": {{ data.raw_key | tojson }},
|
||
"videos": [
|
||
{% for v in data.videos %}
|
||
{
|
||
"filename": {{ v.filename | tojson }},
|
||
"timestamp": {{ v.timestamp | tojson }},
|
||
"rel_path": {{ v.rel_path | tojson }},
|
||
"url": {{ v.url | tojson }}
|
||
}{% if not loop.last %},{% endif %}
|
||
{% endfor %}
|
||
]
|
||
}{% if not loop.last %},{% endif %}
|
||
{% endfor %}
|
||
}
|
||
</script>
|
||
|
||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||
</body>
|
||
</html>
|