feat: streamer search/bulk-remove + cutter drag-drop + per-streamer scroll

Three Phase-10 wins.

1. Streamer-list filter + bulk-remove. Above 6 streamers (the magic
   number where the list starts to feel cluttered) a search input
   appears below the section title and a small bulk-remove "x"
   button next to it. Filter is title-substring, case-insensitive.
   Bulk-remove honours the active filter — when the input is empty
   it confirms removing the entire list, when filled it confirms
   removing only the matching subset. Used a confirm() dialog with
   the matching count interpolated into the locale string.

2. Cutter drag-and-drop. Dragging a video file from Explorer onto
   the cutter tab now loads it directly — no separate Browse click.
   Uses Electron's File.path extension on the dropped File object
   (works through contextIsolation:true). selectCutterVideo was
   refactored into loadCutterFromPath + a thin wrapper so the drop
   handler reuses the same loading logic. dragenter/dragleave count
   adds visual outline on #cutterPreview while a Files drag is over
   the tab. Falls back gracefully if the dropped file lacks .path.

3. Per-streamer VOD scroll position. Switching streamers used to
   reset scroll-to-top, painful when cycling between archives.
   vodScrollPositions Record<streamer, scrollY> persisted to
   localStorage, capped to 32 entries to bound storage. Save fires
   on a 250ms scroll-debounce timer + on every selectStreamer
   transition. Restore happens 80ms after renderVODs paints (lets
   the first chunk settle) so scrollTop has somewhere to land.

Plus: bounded the persistence table at 32 entries, locale strings
DE/EN, all wired through applyLanguageToStaticUI for live language
switch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE
2026-05-10 16:03:47 +02:00
co-authored by Claude Opus 4.7
parent e5decfd851
commit b959a930af
7 changed files with 219 additions and 7 deletions
+5 -1
View File
@@ -208,7 +208,11 @@
</div>
</nav>
<div class="section-title">Streamer</div>
<div class="section-title" id="streamerSectionTitle" style="display:flex; align-items:center; gap:6px; justify-content:space-between;">
<span id="streamerSectionTitleText">Streamer</span>
<button id="btnStreamerBulkRemove" type="button" onclick="bulkRemoveStreamers()" title="Bulk remove" style="display:none; background:transparent; border:1px solid var(--border-soft); border-radius:4px; padding:2px 8px; color:var(--text-secondary); font-size:11px; cursor:pointer;">x</button>
</div>
<input type="text" id="streamerListFilter" placeholder="Filter..." oninput="onStreamerListFilterChange()" style="display:none; width:calc(100% - 16px); margin:0 8px 8px; background:var(--bg-card); border:1px solid var(--border-soft); border-radius:4px; padding:4px 8px; color:var(--text); font-size:12px;">
<div class="streamers" id="streamerList"></div>
<div class="queue-section">