Files
iptvnator/libs/ui/styles/_detail-view-actions.scss
T
genrichh93-ui 188f5c4b56 feat(downloads): pause and resume support for the download manager (#1147)
Adds a paused state to the Electron download manager with a full partial-file lifecycle:

- Pause keeps the .part and byte progress; cancel discards them; every lifecycle stage (queued, active, mid-transfer) is pausable.
- Resume continues via HTTP Range with If-Range entity validation (strong ETag / Last-Modified persisted in the new resume_validator column, idempotent migration incl. legacy-table rebuild). Non-206 answers restart from zero over the same .part; the 206 Content-Range offset is verified; responses that end before the advertised size are retained for a Range retry instead of being committed as completed.
- Crash recovery converts interrupted transfers to paused, keeps queued-with-partial rows resumable, and commits finalizations that crashed before the DB update.
- Destination collisions are non-destructive (retained partials finalize to the next numbered name); locked .part files never lose their DB owner across cancel/remove/restart; resume claims rows atomically and the queue dedupes ids.
- Stored request headers are re-filtered through the User-Agent/Origin/Referer allowlist on read, URL-derived extensions are sanitized, resume appends never follow symlinks, and transfer errors are logged by message only.
- UI: pause/resume/cancel/retry/remove surface failures in a snackbar; paused items show an active Resume button in VOD/episode detail views; translations for all 18 locales.
- Runtime split into download-runtime/transfer/finalize/broadcast modules; +30 unit tests and an Electron E2E covering pause -> retained .part -> Range/If-Range resume -> byte-exact assembly.

Co-authored-by: genrichh93-ui <genrichh93@users.noreply.github.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-24 18:40:30 +02:00

139 lines
3.1 KiB
SCSS

@mixin play-button-copy($meta-color: currentColor) {
&__copy {
display: inline-flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
min-width: 0;
line-height: 1.1;
text-align: left;
}
&__label {
display: block;
min-width: 0;
line-height: 1.15;
}
&__meta {
display: block;
max-width: min(42vw, 360px);
overflow: hidden;
color: $meta-color;
font-size: 0.78rem;
font-weight: 500;
line-height: 1.2;
opacity: 0.72;
text-overflow: ellipsis;
white-space: nowrap;
}
}
// Secondary detail-view actions (Favorite, Download). Per UX audit: only Play
// should read as the primary CTA, so these stay ghost/neutral. State accents
// (favorite=red destructive, download=green completed) keep their semantic
// meaning but ride on transparent fills so they never out-shout Play.
@mixin secondary-action-button {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 64px;
padding: 0 24px;
font-size: 1rem;
font-weight: 600;
background: rgba(255, 255, 255, 0.08);
color: var(--text-primary, #fff);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: rgba(255, 255, 255, 0.14);
border-color: rgba(255, 255, 255, 0.2);
}
mat-icon {
font-size: 24px;
width: 24px;
height: 24px;
}
}
@mixin favorite-button-state {
&--active {
background: rgba(255, 91, 110, 0.16);
border-color: rgba(255, 91, 110, 0.4);
color: #ff8f8f;
&:hover {
background: rgba(255, 91, 110, 0.24);
}
}
}
@mixin download-button-state {
// Idle download stays neutral — extends secondary-action-button.
&--completed {
background: rgba(74, 222, 128, 0.16);
border-color: rgba(74, 222, 128, 0.4);
color: #4ade80;
&:hover {
background: rgba(74, 222, 128, 0.24);
}
}
&--downloading {
background: rgba(255, 255, 255, 0.04);
border-color: rgba(255, 255, 255, 0.08);
cursor: not-allowed;
opacity: 0.6;
&:hover {
background: rgba(255, 255, 255, 0.04);
}
}
&--paused {
background: rgba(251, 191, 36, 0.14);
border-color: rgba(251, 191, 36, 0.4);
color: #fbbf24;
&:hover {
background: rgba(251, 191, 36, 0.22);
}
}
}
@mixin mobile-action-buttons {
.play-btn,
.favorite-btn,
.download-btn {
flex: 1;
justify-content: center;
min-height: 58px;
padding: 10px 16px;
}
.play-btn__meta {
max-width: 100%;
}
}
@mixin projected-mobile-action-buttons {
::ng-deep .play-btn,
::ng-deep .favorite-btn,
::ng-deep .download-btn {
flex: 1;
justify-content: center;
min-height: 58px;
padding: 10px 16px;
}
::ng-deep .play-btn__meta {
max-width: 100%;
}
}