298 lines
13 KiB
HTML
298 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Repertory</title>
|
||
<link rel="stylesheet" href="style.css" />
|
||
</head>
|
||
<body>
|
||
|
||
<header>
|
||
<h1>🪕 Repertory</h1>
|
||
<input type="search" id="filter-search" placeholder="Search…" />
|
||
<div id="header-actions">
|
||
<button id="btn-clear-filters" hidden>✕ Clear filters</button>
|
||
<button id="btn-call">Call</button>
|
||
<button id="btn-review">Review</button>
|
||
<button id="btn-learn">Learn</button>
|
||
<button id="btn-add-tune" hidden>+ Add</button>
|
||
<button id="btn-unlock">🔒 Unlock</button>
|
||
<button id="btn-bulk-edit" hidden>☰</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ── Tune table ───────────────────────────────────────────── -->
|
||
<main>
|
||
<table id="tune-table">
|
||
<thead>
|
||
<tr>
|
||
<th data-col="name">Name <span class="sort-indicator"></span></th>
|
||
<th data-filter="source">Source</th>
|
||
<th data-filter="key">Key</th>
|
||
<th data-filter="instrument">Instrument</th>
|
||
<th data-filter="tuning">Tuning</th>
|
||
<th data-filter="status">Status</th>
|
||
<th>Notes</th>
|
||
<th>Refs</th>
|
||
<th class="edit-col" hidden></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tune-tbody"></tbody>
|
||
</table>
|
||
<p id="empty-msg" hidden>No tunes match the current filters.</p>
|
||
</main>
|
||
|
||
<!-- ── Filter popover (shared, moved in DOM) ────────────────── -->
|
||
<div id="filter-popover" hidden>
|
||
<div id="filter-popover-inner"></div>
|
||
</div>
|
||
|
||
<!-- ── Bulk edit popover ─────────────────────────────────────── -->
|
||
<div id="bulk-edit-popover" hidden>
|
||
<button id="btn-open-bulk-refs">📎 Bulk Edit References</button>
|
||
<button id="btn-upload-csv">📂 Upload Tunes from CSV</button>
|
||
<button id="btn-upload-refs-csv">🔗 Upload References from CSV</button>
|
||
</div>
|
||
|
||
<!-- ── Bulk edit references modal ───────────────────────────── -->
|
||
<dialog id="bulk-refs-modal">
|
||
<h2>Bulk Edit References</h2>
|
||
<div id="bulk-refs-controls">
|
||
<input type="search" id="bulk-refs-search" placeholder="Filter by name…" />
|
||
<select id="bulk-refs-instrument">
|
||
<option value="">All instruments</option>
|
||
</select>
|
||
<select id="bulk-refs-source">
|
||
<option value="">All sources</option>
|
||
</select>
|
||
<select id="bulk-refs-has-refs">
|
||
<option value="">All tunes</option>
|
||
<option value="no">No refs yet</option>
|
||
<option value="yes">Has refs</option>
|
||
</select>
|
||
<select id="bulk-refs-sort">
|
||
<option value="newest">Newest first</option>
|
||
<option value="oldest">Oldest first</option>
|
||
<option value="name_asc">Name A→Z</option>
|
||
<option value="name_desc">Name Z→A</option>
|
||
</select>
|
||
</div>
|
||
<div id="bulk-refs-list"></div>
|
||
<div class="modal-actions">
|
||
<button id="btn-bulk-refs-close">Close</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<!-- ── Add / Edit tune modal ────────────────────────────────── -->
|
||
<dialog id="tune-modal">
|
||
<form id="tune-form" method="dialog">
|
||
<h2 id="modal-title">Add tune</h2>
|
||
|
||
<fieldset>
|
||
<legend>Tune</legend>
|
||
<label>Name <input name="name" type="text" /></label>
|
||
<label>Key <input name="key" type="text" placeholder="e.g. D" /></label>
|
||
<label>Modal <input name="modal" type="checkbox" /></label>
|
||
<label>Source
|
||
<span class="autocomplete-row">
|
||
<input name="source_name" id="source-input" type="text"
|
||
list="source-list" placeholder="— none —" autocomplete="off" />
|
||
<datalist id="source-list"></datalist>
|
||
<button type="button" class="quick-add-btn" id="btn-quick-add-source" title="Add new source">+</button>
|
||
</span>
|
||
</label>
|
||
</fieldset>
|
||
|
||
<fieldset id="fs-instrument">
|
||
<legend>Instrument entry</legend>
|
||
<label>Instrument
|
||
<span class="autocomplete-row">
|
||
<select name="instrument_id" id="instrument-select">
|
||
<option value="">— none —</option>
|
||
</select>
|
||
<button type="button" class="quick-add-btn" id="btn-quick-add-instrument" title="Add new instrument">+</button>
|
||
</span>
|
||
</label>
|
||
<label>Tuning
|
||
<span class="autocomplete-row">
|
||
<input name="inst_tuning" type="text"
|
||
list="tuning-list" placeholder="— none —" autocomplete="off" />
|
||
<datalist id="tuning-list"></datalist>
|
||
<button type="button" class="quick-add-btn" id="btn-quick-add-tuning" title="Add new tuning">+</button>
|
||
</span>
|
||
</label>
|
||
<label>Term
|
||
<span class="autocomplete-row">
|
||
<input name="inst_term" type="text"
|
||
list="term-list" placeholder="— none —" autocomplete="off" />
|
||
<datalist id="term-list"></datalist>
|
||
<button type="button" class="quick-add-btn" id="btn-quick-add-term" title="Add new term">+</button>
|
||
</span>
|
||
</label>
|
||
<label>Learned from
|
||
<span class="autocomplete-row">
|
||
<input name="inst_learned_from" type="text"
|
||
list="musician-list" placeholder="— none —" autocomplete="off" />
|
||
<datalist id="musician-list"></datalist>
|
||
<button type="button" class="quick-add-btn" id="btn-quick-add-musician" title="Add new musician">+</button>
|
||
</span>
|
||
</label>
|
||
<label>Date learned <input name="inst_date_learned" type="date" /></label>
|
||
<label>Status
|
||
<span class="status-radios">
|
||
<label class="radio-row"><input type="radio" name="inst_status" value="" /> None</label>
|
||
<label class="radio-row"><input type="radio" name="inst_status" value="callable" /> Callable</label>
|
||
<label class="radio-row"><input type="radio" name="inst_status" value="review" /> Needs Review</label>
|
||
<label class="radio-row"><input type="radio" name="inst_status" value="to_learn" /> To Learn</label>
|
||
</span>
|
||
</label>
|
||
<label>Difficulty
|
||
<select name="inst_difficulty">
|
||
<option value="">—</option>
|
||
<option value="easy">Easy</option>
|
||
<option value="hard">Hard</option>
|
||
</select>
|
||
</label>
|
||
</fieldset>
|
||
|
||
<div id="refs-section">
|
||
<h3>References</h3>
|
||
<div id="refs-list"></div>
|
||
<datalist id="ref-site-list"></datalist>
|
||
<button type="button" id="btn-add-ref">+ reference</button>
|
||
</div>
|
||
|
||
<p id="tune-error" class="modal-error" hidden></p>
|
||
<div class="modal-actions">
|
||
<button type="button" id="btn-delete-tune" class="btn-danger" hidden>Delete tune</button>
|
||
<button type="button" id="btn-edit-notes" hidden>📝 Notes</button>
|
||
<button type="button" id="btn-save-tune">Save</button>
|
||
<button type="button" id="btn-cancel">Cancel</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<!-- ── References display modal ─────────────────────────────── -->
|
||
<dialog id="refs-modal">
|
||
<h2 id="refs-modal-title">References</h2>
|
||
<div id="refs-modal-list"></div>
|
||
<div class="modal-actions">
|
||
<button id="btn-refs-modal-close">Close</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<!-- ── Notes modal ───────────────────────────────── -->
|
||
<dialog id="notes-modal">
|
||
<h2 id="notes-modal-title">Notes</h2>
|
||
<div id="tune-notes-section">
|
||
<h3>Tune notes</h3>
|
||
<div id="tune-notes-list"></div>
|
||
<button type="button" id="btn-add-tune-note" hidden>+ note</button>
|
||
</div>
|
||
<div id="inst-notes-section">
|
||
<h3 id="inst-notes-heading">Instrument notes</h3>
|
||
<div id="inst-notes-list"></div>
|
||
<button type="button" id="btn-add-inst-note" hidden>+ note</button>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button id="btn-notes-close">Close</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<!-- ── Note editor modal (fullscreen, separate from notes list) ─ -->
|
||
<dialog id="note-editor-modal">
|
||
<h2 id="note-editor-title">Add note</h2>
|
||
<textarea id="note-editor-ta" placeholder="Write your note… (Ctrl+↵ to save)"></textarea>
|
||
<div class="modal-actions">
|
||
<button id="btn-note-save">Save note</button>
|
||
<button id="btn-note-cancel">Cancel</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<!-- ── Call modal ─────────────────────────────────────────── -->
|
||
<dialog id="call-modal">
|
||
<div id="call-header">
|
||
<button id="call-back" hidden>← Back</button>
|
||
<h2 id="call-title"></h2>
|
||
<button id="call-close">✕</button>
|
||
</div>
|
||
<div id="call-options"></div>
|
||
</dialog>
|
||
|
||
<!-- ── CSV upload modal ─────────────────────────────────────── -->
|
||
<dialog id="upload-csv-modal">
|
||
<h2>Upload from CSV</h2>
|
||
<details id="csv-format-details" open>
|
||
<summary>Expected format</summary>
|
||
<p style="font-size:.85rem;margin:.5rem 0 .4rem">One tune per row. Headers (case-insensitive):</p>
|
||
<table class="csv-format-table">
|
||
<thead><tr><th>Column</th><th>Required</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>name</td><td>Yes</td><td>Tune name</td></tr>
|
||
<tr><td>key</td><td></td><td>e.g. D, G, Am</td></tr>
|
||
<tr><td>modal</td><td></td><td>true / false</td></tr>
|
||
<tr><td>source</td><td></td><td>Created if new</td></tr>
|
||
<tr><td>instrument</td><td></td><td>Created if new</td></tr>
|
||
<tr><td>tuning</td><td></td><td>Created if new</td></tr>
|
||
<tr><td>learned_from</td><td></td><td>Musician name — created if new</td></tr>
|
||
<tr><td>date_learned</td><td></td><td>YYYY-MM-DD</td></tr>
|
||
<tr><td>status</td><td></td><td>callable / review / to_learn</td></tr>
|
||
<tr><td>difficulty</td><td></td><td>easy / hard</td></tr>
|
||
<tr><td>term</td><td></td><td>Created if new</td></tr>
|
||
<tr><td>tune_note</td><td></td><td>One note on the tune</td></tr>
|
||
<tr><td>instrument_note</td><td></td><td>One note on the instrument entry</td></tr>
|
||
</tbody>
|
||
</table>
|
||
<p class="csv-format-note">If a tune with the same name already exists a new instrument entry is added to it. Sources, tunings, and musicians are created automatically if they don’t exist.</p>
|
||
</details>
|
||
<div id="csv-upload-area">
|
||
<input type="file" id="csv-file-input" accept=".csv,.tsv,text/csv" />
|
||
</div>
|
||
<div id="csv-results" hidden></div>
|
||
<div class="modal-actions">
|
||
<button id="btn-csv-import" disabled>Import</button>
|
||
<button id="btn-csv-cancel">Cancel</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<!-- ── References CSV upload modal ───────────────────────────── -->
|
||
<dialog id="upload-refs-csv-modal">
|
||
<h2>Upload References from CSV</h2>
|
||
<details id="refs-csv-format-details" open>
|
||
<summary>Expected format</summary>
|
||
<p style="font-size:.85rem;margin:.5rem 0 .4rem">One reference per row. Tune matched by name — must already exist.</p>
|
||
<table class="csv-format-table">
|
||
<thead><tr><th>Column</th><th>Required</th><th>Notes</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>tune_name</td><td>Yes</td><td>Must match an existing tune (case-insensitive)</td></tr>
|
||
<tr><td>link</td><td></td><td>URL</td></tr>
|
||
<tr><td>site</td><td></td><td>Site name — created if new</td></tr>
|
||
<tr><td>musician1</td><td></td><td>Musician name — created if new</td></tr>
|
||
<tr><td>musician2</td><td></td><td></td></tr>
|
||
<tr><td>musician3</td><td></td><td></td></tr>
|
||
<tr><td>musician4</td><td></td><td></td></tr>
|
||
</tbody>
|
||
</table>
|
||
<p class="csv-format-note">Rows where the tune name is not found are skipped with a warning in the results.</p>
|
||
</details>
|
||
<div id="refs-csv-upload-area">
|
||
<input type="file" id="refs-csv-file-input" accept=".csv,.tsv,text/csv" />
|
||
</div>
|
||
<div id="refs-csv-results" hidden></div>
|
||
<div class="modal-actions">
|
||
<button id="btn-refs-csv-import" disabled>Import</button>
|
||
<button id="btn-refs-csv-cancel">Cancel</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<!-- Tooltip for truncated mobile text -->
|
||
<div id="mob-tooltip" hidden></div>
|
||
|
||
<footer id="app-footer"><span id="status"></span></footer>
|
||
|
||
<!-- config.js is gitignored — copy config.example.js to config.js for local dev -->
|
||
<script src="config.js" onerror="void 0"></script>
|
||
<script src="app.js"></script>
|
||
</body>
|
||
</html>
|