Files
mExifEditor/app/static/index.html
T

289 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
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">
<meta name="theme-color" content="#121820">
<title>Photo Date Editor</title>
<link rel="manifest" href="/manifest.webmanifest">
<link rel="stylesheet" href="/vendor/leaflet/leaflet.css">
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="app-shell">
<header class="topbar">
<div class="brand">
<div class="brand-mark" aria-hidden="true"></div>
<div>
<h1 id="app-name">Photo Date Editor</h1>
<p id="app-subtitle">Loading enabled photo formats…</p>
</div>
</div>
<div class="top-actions">
<button id="shortcuts-button" class="button ghost">⌨ Keyboard shortcuts</button>
<button id="open-folder-top" class="button primary">Open photo folder</button>
</div>
</header>
<main class="workspace">
<aside class="sidebar left-panel">
<section class="panel-section folder-section">
<div class="section-label">Folder</div>
<div class="folder-row">
<div>
<strong id="folder-name">No folder selected</strong>
<span id="folder-access">Choose a local folder</span>
</div>
<button id="open-folder" class="button compact">Change</button>
</div>
</section>
<section class="panel-section progress-section">
<div class="section-heading">
<span>Progress</span>
<span id="progress-count">0 / 0</span>
</div>
<div class="progress-track" role="progressbar" aria-label="Photo processing progress" aria-valuemin="0" aria-valuemax="100">
<div id="progress-saved" class="progress-segment saved" title="Saved"></div>
<div id="progress-skipped" class="progress-segment skipped" title="Skipped"></div>
<div id="progress-failed" class="progress-segment failed" title="Failed"></div>
</div>
<div class="status-legend">
<span><i class="dot saved"></i>Saved</span>
<span><i class="dot skipped"></i>Skipped</span>
<span><i class="dot pending"></i>Pending</span>
<span><i class="dot failed"></i>Failed</span>
</div>
</section>
<section class="panel-section file-section">
<div class="section-heading"><span>Photos</span><span id="photo-count">0</span></div>
<div class="file-controls">
<input id="filter-input" class="input filter filename-filter" type="search" placeholder="Filter filenames…" aria-label="Filter photos by filename" disabled>
<select id="status-filter" class="input filter" aria-label="Filter photos by status" disabled>
<option value="all">All statuses</option>
<option value="pending">Pending</option>
<option value="saved">Saved</option>
<option value="skipped">Skipped</option>
<option value="failed">Failed</option>
</select>
<select id="sort-select" class="input filter" aria-label="Sort photos" disabled>
<option value="filename">Sort: Filename</option>
<option value="status">Sort: Status</option>
<option value="format">Sort: Format</option>
</select>
</div>
<div id="file-list" class="file-list" aria-live="polite">
<div class="empty-list">Choose a folder containing an enabled photo format.</div>
</div>
</section>
<section class="sidebar-footer">
<button id="rescan-folder" class="button wide" disabled>↻ Rescan folder</button>
</section>
</aside>
<section class="viewer-panel">
<div id="viewer-empty" class="viewer-empty">
<div class="empty-icon"></div>
<h2>Choose your scanned-photo folder</h2>
<p>The site will request read/write access. Photos stay on this device.</p>
<button id="open-folder-center" class="button primary large">Open photo folder</button>
</div>
<div id="viewer-content" class="viewer-content hidden">
<div class="image-stage">
<img id="photo-preview" alt="Current scanned photograph">
<div id="rotation-status" class="rotation-status hidden" role="status"></div>
</div>
<div class="viewer-tools">
<div class="tool-group">
<button id="zoom-out" class="icon-button" title="Zoom out"></button>
<button id="zoom-in" class="icon-button" title="Zoom in">+</button>
<button id="fit-image" class="button compact">Fit</button>
</div>
<div class="filename-block">
<strong id="current-filename"></strong>
<span id="current-filesize"></span>
</div>
<div class="tool-group">
<button id="rotate-left" class="icon-button" title="Rotate left"></button>
<button id="rotate-right" class="icon-button" title="Rotate right"></button>
<button id="reset-view" class="icon-button" title="Reset preview"></button>
</div>
</div>
<div class="navigation-bar">
<button id="previous-button" class="button">← Previous</button>
<div id="viewer-position" class="position">0 / 0</div>
<div class="navigation-actions">
<button id="skip-button" class="button ghost">Skip</button>
<button id="save-button" class="button">Save current</button>
<button id="save-next-button" class="button primary">Save &amp; Next →</button>
</div>
</div>
</div>
</section>
<aside class="sidebar right-panel">
<div class="tabs" role="tablist">
<button id="tab-date" class="tab active" data-tab="date" role="tab" aria-selected="true" aria-controls="panel-date">Date &amp; time</button>
<button id="tab-details" class="tab" data-tab="details" role="tab" aria-selected="false" aria-controls="panel-details" tabindex="-1">Details</button>
<button id="tab-keywords" class="tab" data-tab="keywords" role="tab" aria-selected="false" aria-controls="panel-keywords" tabindex="-1">Keywords</button>
<button id="tab-location" class="tab" data-tab="location" role="tab" aria-selected="false" aria-controls="panel-location" tabindex="-1">Location</button>
</div>
<form id="metadata-form" autocomplete="off">
<section id="panel-date" class="tab-panel active" data-panel="date" role="tabpanel" aria-labelledby="tab-date">
<fieldset>
<legend>Date precision</legend>
<label class="radio-row"><input type="radio" name="precision" value="exact" checked> <span>Exact date</span></label>
<label class="radio-row"><input type="radio" name="precision" value="month"> <span>Month and year</span></label>
<label class="radio-row"><input type="radio" name="precision" value="year"> <span>Year only</span></label>
<label class="radio-row"><input type="radio" name="precision" value="approximate"> <span>Approximate year</span></label>
</fieldset>
<div class="field-grid date-grid">
<label class="field">
<span>Day</span>
<input id="day-input" class="input" type="number" min="1" max="31" value="1">
</label>
<label class="field month-field">
<span>Month</span>
<select id="month-input" class="input">
<option value="1">January</option><option value="2">February</option>
<option value="3">March</option><option value="4">April</option>
<option value="5">May</option><option value="6">June</option>
<option value="7">July</option><option value="8">August</option>
<option value="9">September</option><option value="10">October</option>
<option value="11">November</option><option value="12">December</option>
</select>
</label>
<label class="field">
<span>Year</span>
<input id="year-input" class="input" type="number" min="1800" max="2200" placeholder="1987" required>
</label>
</div>
<label class="field">
<span>Time</span>
<select id="time-mode" class="input">
<option value="unknown">Unknown — use 12:00:00</option>
<option value="known">Enter a time</option>
</select>
</label>
<label id="time-field" class="field hidden">
<span>Time value</span>
<input id="time-input" class="input" type="time" step="1" value="12:00:00">
</label>
<div class="info-card">
<strong>Partial dates</strong>
<p>EXIF requires a full timestamp. Month/year uses day 1; year-only uses January 1. XMP also stores the reduced precision.</p>
</div>
</section>
<section id="panel-details" class="tab-panel" data-panel="details" role="tabpanel" aria-labelledby="tab-details">
<label class="field">
<span>Description</span>
<textarea id="description-input" class="input textarea" rows="7" maxlength="2000" placeholder="Who, where, occasion, or other context…"></textarea>
</label>
<div class="helper-row"><span>Written to EXIF, XMP and IPTC</span><span id="description-count">0 / 2000</span></div>
</section>
<section id="panel-keywords" class="tab-panel" data-panel="keywords" role="tabpanel" aria-labelledby="tab-keywords">
<label class="field">
<span>People and keywords</span>
<textarea id="keywords-input" class="input textarea" rows="7" placeholder="Daniel, family, birthday, Bergen"></textarea>
</label>
<p class="field-help">Separate entries with commas or new lines. Written as XMP Subject and IPTC Keywords.</p>
</section>
<section id="panel-location" class="tab-panel" data-panel="location" role="tabpanel" aria-labelledby="tab-location">
<div class="favorite-locations">
<div class="section-heading-row">
<div>
<strong>Favorite locations</strong>
<span>Shared by everyone using this Docker stack</span>
</div>
</div>
<div class="favorite-controls">
<select id="favorite-location-select" class="input" aria-label="Favorite locations">
<option value="">No favorites saved</option>
</select>
<button id="use-favorite-location" class="button" type="button" disabled>Use</button>
</div>
<div class="favorite-actions">
<button id="save-favorite-location" class="button ghost" type="button">★ Add current location</button>
<button id="delete-favorite-location" class="button ghost danger-text" type="button" disabled>Delete selected</button>
</div>
</div>
<div class="location-search">
<label class="field location-search-field">
<span>Search for a place or address</span>
<input id="address-search" class="input" type="search" placeholder="Cabin address, town, landmark…">
</label>
<button id="address-search-button" class="button" type="button">Search</button>
</div>
<p class="field-help search-privacy">Search queries are sent to the configured geocoder. You can place a pin manually instead.</p>
<div id="address-results" class="address-results" aria-live="polite"></div>
<div id="location-map" class="location-map" aria-label="Map for selecting photo location"></div>
<p id="map-status" class="field-help map-status" role="status">Open this tab to load the map.</p>
<p class="field-help map-help">Click the map to place a pin, or drag the pin to fine-tune the position.</p>
<div class="field-grid coordinate-grid">
<label class="field">
<span>Latitude</span>
<input id="latitude-input" class="input" type="number" min="-90" max="90" step="0.000001" placeholder="60.391263">
</label>
<label class="field">
<span>Longitude</span>
<input id="longitude-input" class="input" type="number" min="-180" max="180" step="0.000001" placeholder="5.322054">
</label>
</div>
<label class="field">
<span>Location label</span>
<input id="location-name-input" class="input" type="text" maxlength="500" placeholder="Cabin, Voss, Norway">
</label>
<div class="location-actions">
<button id="clear-location" class="button ghost" type="button">Clear location</button>
</div>
<div class="info-card location-info">
<strong>What gets written</strong>
<p>Coordinates are written to EXIF and XMP GPS fields. The optional label is written to XMP Location.</p>
</div>
</section>
</form>
<section class="right-footer">
<div id="save-status" class="save-status idle">
<span class="status-icon"></span>
<div><strong>No photo selected</strong><span>Open a folder to begin.</span></div>
</div>
<button id="copy-previous" class="button wide" disabled>Copy previous values</button>
</section>
</aside>
</main>
</div>
<dialog id="shortcuts-dialog">
<div class="dialog-heading"><h2>Keyboard shortcuts</h2><button id="close-shortcuts" class="icon-button">×</button></div>
<dl class="shortcut-list">
<div><dt>Enter / Ctrl+Enter</dt><dd>Save and open next photo</dd></div>
<div><dt>Ctrl+S</dt><dd>Save current photo and stay on it</dd></div>
<div><dt>← / →</dt><dd>Previous or next photo</dd></div>
<div><dt>S</dt><dd>Mark skipped and open next photo</dd></div>
<div><dt>C</dt><dd>Copy previous photo's values</dd></div>
<div><dt>Shift+Tab</dt><dd>Open the previous metadata tab and focus its first field</dd></div>
</dl>
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="/vendor/leaflet/leaflet.js"></script>
<script src="/app.js" type="module"></script>
</body>
</html>