Add empty-state designs for empty playlists and history
Replaces bare status text with visual empty-state components: - History: clock icon, 'Nothing watched yet' title, description, 'Search videos' CTA - Playlist: music icon, 'This playlist is empty' title, description with tip, 'Browse videos' CTA - Search: unchanged (hero landing handles this case) - CTA buttons navigate to search view for quick action
This commit is contained in:
39
BACKLOG.md
Normal file
39
BACKLOG.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# YT Player — Improvement Backlog
|
||||
|
||||
The `/loop` works through this top-to-bottom, **one item per iteration**. Tick an item
|
||||
(`[x]`) only after it builds, verifies, and is committed + pushed. Add new ideas to the
|
||||
bottom as they come up.
|
||||
|
||||
## Build & verify protocol (every iteration)
|
||||
1. Edit the **canonical source** in WSL (`~/development/personal/ytplayer`).
|
||||
2. Sync changed files **individually** into `C:\ytbuild\ytplayer` — never
|
||||
`Copy-Item -Recurse` onto an existing dir (it nests as `frontend\frontend`).
|
||||
3. `npx tauri build` in `C:\ytbuild\ytplayer`.
|
||||
4. Launch with `--remote-debugging-port=922x` and verify via CDP/screenshot
|
||||
(retry the launch a few times — Smart App Control is intermittent).
|
||||
5. **Only if it builds and verifies:** refresh `releases/`, commit (no AI attribution),
|
||||
`git push origin main`, then tick the item here.
|
||||
|
||||
## UX / polish
|
||||
- [x] Empty-state designs for empty playlists and empty history (match the new hero).
|
||||
- [ ] Loading skeletons for search results instead of a bare "Searching…".
|
||||
- [ ] Keyboard-shortcut help overlay (press `?`), listing space / arrows / f / m.
|
||||
- [ ] Download/preload progress indicator on cards (percent or a bar, not just a pulse).
|
||||
- [ ] Remember and restore the last playback position per video.
|
||||
- [ ] Toast queue (stack multiple toasts instead of replacing).
|
||||
|
||||
## Features
|
||||
- [ ] Drag-to-reorder videos within a playlist.
|
||||
- [ ] "Up next" queue panel showing the autoplay queue.
|
||||
- [ ] Persistent mini now-playing bar when browsing other views.
|
||||
- [ ] Cache size cap in Settings (auto-evict oldest past a limit).
|
||||
- [ ] Export / import playlists as JSON.
|
||||
- [ ] More / trending quick-search chips on the hero, rotated.
|
||||
|
||||
## Robustness
|
||||
- [ ] Sanitize `video_id` in `cache_download` (reject path separators).
|
||||
- [ ] Gate the embedded yt-dlp behind a build flag so debug builds compile faster.
|
||||
- [ ] Surface yt-dlp extraction failures with a retry button.
|
||||
|
||||
## Done
|
||||
<!-- move completed items here with the commit hash -->
|
||||
@@ -596,11 +596,44 @@ function renderList() {
|
||||
}
|
||||
|
||||
if (!list.length) {
|
||||
els.status.classList.remove('hidden');
|
||||
els.status.textContent =
|
||||
view.type === 'search' ? 'Search for something to begin.'
|
||||
: view.type === 'history' ? 'Nothing watched yet.'
|
||||
: 'This playlist is empty. Add videos from search.';
|
||||
els.status.classList.add('hidden');
|
||||
els.cards.innerHTML = '';
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'empty-state';
|
||||
|
||||
if (view.type === 'search') {
|
||||
// Hero landing is already in the player pane — list pane stays bare.
|
||||
els.status.classList.remove('hidden');
|
||||
els.status.textContent = 'Search for something to begin.';
|
||||
return;
|
||||
}
|
||||
|
||||
if (view.type === 'history') {
|
||||
empty.innerHTML = `
|
||||
<div class="empty-icon">🕘</div>
|
||||
<h3 class="empty-title">Nothing watched yet</h3>
|
||||
<p class="empty-desc">Your viewing history shows up here once you start playing videos.</p>
|
||||
`;
|
||||
const cta = document.createElement('button');
|
||||
cta.className = 'empty-cta';
|
||||
cta.textContent = '🔍 Search videos';
|
||||
cta.addEventListener('click', () => { view = { type: 'search' }; render(); });
|
||||
empty.appendChild(cta);
|
||||
} else {
|
||||
// Playlist view — empty
|
||||
empty.innerHTML = `
|
||||
<div class="empty-icon">🎵</div>
|
||||
<h3 class="empty-title">This playlist is empty</h3>
|
||||
<p class="empty-desc">Add videos from search results or use the <strong>+ Playlist</strong> button while playing.</p>
|
||||
`;
|
||||
const cta = document.createElement('button');
|
||||
cta.className = 'empty-cta';
|
||||
cta.textContent = '🔍 Browse videos';
|
||||
cta.addEventListener('click', () => { view = { type: 'search' }; render(); });
|
||||
empty.appendChild(cta);
|
||||
}
|
||||
|
||||
els.cards.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
els.status.classList.add('hidden');
|
||||
|
||||
@@ -557,6 +557,66 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
|
||||
.list-actions button:hover { color: var(--text); border-color: var(--accent); background: var(--bg-3); }
|
||||
|
||||
.status { padding: 16px 20px; color: var(--text-dim); font-size: 13px; }
|
||||
|
||||
/* ===================== Empty state ===================== */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 40px 32px 60px;
|
||||
gap: 8px;
|
||||
animation: heroIn 0.5s var(--ease) both;
|
||||
}
|
||||
.empty-icon {
|
||||
font-size: 42px;
|
||||
width: 80px; height: 80px;
|
||||
display: grid; place-items: center;
|
||||
border-radius: 24px;
|
||||
background: var(--bg-2);
|
||||
border: 1px solid var(--line);
|
||||
margin-bottom: 10px;
|
||||
transition: transform 0.3s var(--ease), box-shadow 0.3s;
|
||||
}
|
||||
.empty-state:hover .empty-icon {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 32px -12px var(--accent-glow);
|
||||
}
|
||||
.empty-title {
|
||||
font-family: var(--display);
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
.empty-desc {
|
||||
margin: 4px 0 0;
|
||||
color: var(--text-dim);
|
||||
font-size: 13.5px;
|
||||
line-height: 1.5;
|
||||
max-width: 300px;
|
||||
}
|
||||
.empty-desc strong { color: var(--text-2); font-weight: 600; }
|
||||
.empty-cta {
|
||||
margin-top: 16px;
|
||||
background: linear-gradient(145deg, var(--accent-bright), var(--accent-deep));
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 22px;
|
||||
border-radius: 11px;
|
||||
font-family: var(--ui);
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.01em;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 22px -8px var(--accent-glow);
|
||||
transition: transform 0.16s var(--ease), box-shadow 0.16s;
|
||||
}
|
||||
.empty-cta:hover { transform: translateY(-2px); box-shadow: 0 12px 28px -8px var(--accent-glow); }
|
||||
.empty-cta:active { transform: translateY(0); }
|
||||
|
||||
.cards { flex: 1; overflow-y: auto; padding: 6px 14px 28px; display: flex; flex-direction: column; gap: 5px; }
|
||||
|
||||
.card {
|
||||
|
||||
BIN
releases/YT Player_1.0.0_x64-setup.exe
Executable file
BIN
releases/YT Player_1.0.0_x64-setup.exe
Executable file
Binary file not shown.
BIN
releases/YT Player_1.0.0_x64_en-US.msi
Executable file
BIN
releases/YT Player_1.0.0_x64_en-US.msi
Executable file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user