Initial JPEG photo metadata editor v0.1

This commit is contained in:
dmo committed 2026-07-23 01:10:49 +02:00
1 parent cdc206680c
commit 8738b8c814
12 files changed
+1222

No files matched your search

+205
View File
@@ -0,0 +1,205 @@
<!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="/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>Local folder · in-place JPEG metadata</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 Chromebook 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"><div id="progress-bar" class="progress-bar"></div></div>
<div class="status-legend">
<span><i class="dot saved"></i>Saved</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>
<input id="filter-input" class="input filter" type="search" placeholder="Filter filenames…" disabled>
<div id="file-list" class="file-list" aria-live="polite">
<div class="empty-list">Choose a folder containing JPG or JPEG photos.</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>
<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 preview left">↶</button>
<button id="rotate-right" class="icon-button" title="Rotate preview 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</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 class="tab active" data-tab="date" role="tab">Date &amp; time</button>
<button class="tab" data-tab="details" role="tab">Details</button>
<button class="tab" data-tab="keywords" role="tab">Keywords</button>
</div>
<form id="metadata-form" autocomplete="off">
<section class="tab-panel active" data-panel="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 class="tab-panel" data-panel="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 class="tab-panel" data-panel="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>
</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 without moving</dd></div>
<div><dt>← / →</dt><dd>Previous or next photo</dd></div>
<div><dt>S</dt><dd>Skip to next photo</dd></div>
<div><dt>C</dt><dd>Copy previous photo's values</dd></div>
</dl>
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="/app.js" type="module"></script>
</body>
</html>