mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:26:43 -08:00
refactor(external-playback): update styles
Entire-Checkpoint: c6e522b4276c
This commit is contained in:
1 parent
ce7ae1a251
commit
4bc733d9c2
6 files changed
+122
-48
No files matched your search
+26
-17
@@ -3,7 +3,18 @@
|
||||
[class.external-playback-dock--compact]="compact()"
|
||||
[class.external-playback-dock--error]="session().status === 'error'"
|
||||
>
|
||||
<div class="external-playback-dock__artwork">
|
||||
<button
|
||||
type="button"
|
||||
class="external-playback-dock__artwork"
|
||||
[class.external-playback-dock__artwork--interactive]="
|
||||
artworkInteractive()
|
||||
"
|
||||
[disabled]="!artworkInteractive()"
|
||||
[attr.aria-label]="
|
||||
artworkInteractive() ? 'Open in playlist' : null
|
||||
"
|
||||
(click)="onArtworkClick()"
|
||||
>
|
||||
@if (showArtwork()) {
|
||||
<img
|
||||
[src]="artworkUrl()"
|
||||
@@ -16,29 +27,27 @@
|
||||
<mat-icon>{{ artworkPlaceholderIcon() }}</mat-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="external-playback-dock__meta">
|
||||
<div class="external-playback-dock__topline">
|
||||
<span class="external-playback-dock__chip">{{
|
||||
playerLabel()
|
||||
}}</span>
|
||||
@if (!compact()) {
|
||||
<span class="external-playback-dock__caption">
|
||||
External player
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="external-playback-dock__title">{{ session().title }}</div>
|
||||
<div class="external-playback-dock__status">
|
||||
<span class="external-playback-dock__chip">{{ playerLabel() }}</span>
|
||||
<span
|
||||
class="external-playback-dock__status-separator"
|
||||
aria-hidden="true"
|
||||
>·</span
|
||||
>
|
||||
@if (showSpinner()) {
|
||||
<mat-spinner diameter="14" strokeWidth="3"></mat-spinner>
|
||||
} @else {
|
||||
<mat-icon>{{
|
||||
session().status === 'error' ? 'error' : 'play_arrow'
|
||||
<mat-spinner diameter="12" strokeWidth="2"></mat-spinner>
|
||||
} @else if (showStatusIcon()) {
|
||||
<mat-icon class="external-playback-dock__status-icon">{{
|
||||
statusIcon()
|
||||
}}</mat-icon>
|
||||
}
|
||||
<span>{{ statusLabel() }}</span>
|
||||
<span class="external-playback-dock__status-text">{{
|
||||
statusLabel()
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+52
-25
@@ -8,7 +8,7 @@
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-height: 72px;
|
||||
padding: 8px 14px;
|
||||
padding: 2px 14px;
|
||||
border: 1px solid color-mix(in srgb, var(--mat-sys-outline-variant) 60%, transparent);
|
||||
border-radius: 15px 15px 0 0;
|
||||
background:
|
||||
@@ -59,10 +59,17 @@
|
||||
.external-playback-dock__artwork {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
background: color-mix(in srgb, var(--mat-sys-surface-container-highest) 92%, transparent);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
|
||||
color: inherit;
|
||||
cursor: default;
|
||||
transition:
|
||||
transform 0.18s ease,
|
||||
box-shadow 0.18s ease;
|
||||
|
||||
img,
|
||||
.external-playback-dock__placeholder {
|
||||
@@ -74,6 +81,22 @@
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&--interactive {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.04);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.32),
|
||||
0 6px 16px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--mat-sys-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.external-playback-dock__placeholder {
|
||||
@@ -86,30 +109,21 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.external-playback-dock__topline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.external-playback-dock__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
padding: 0 8px;
|
||||
height: 18px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--mat-sys-primary-container) 82%, transparent);
|
||||
color: var(--mat-sys-on-primary-container);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.external-playback-dock__caption {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--mat-sys-surface-container-highest) 80%,
|
||||
transparent
|
||||
);
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--mat-sys-outline-variant) 70%, transparent);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
@@ -120,20 +134,33 @@
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: var(--mat-sys-on-surface);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.external-playback-dock__status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
color: var(--mat-sys-on-surface-variant);
|
||||
font-size: 0.78rem;
|
||||
min-width: 0;
|
||||
|
||||
mat-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
.external-playback-dock__status-separator {
|
||||
opacity: 0.55;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.external-playback-dock__status-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.external-playback-dock__status-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
|
||||
+22
-6
@@ -30,6 +30,7 @@ export class ExternalPlaybackDockComponent {
|
||||
readonly compact = input(false);
|
||||
|
||||
readonly closeClicked = output<void>();
|
||||
readonly artworkClicked = output<void>();
|
||||
private readonly artworkFailed = signal(false);
|
||||
|
||||
readonly playerLabel = computed(() => this.session().player.toUpperCase());
|
||||
@@ -38,25 +39,32 @@ export class ExternalPlaybackDockComponent {
|
||||
);
|
||||
readonly statusLabel = computed(() => {
|
||||
const session = this.session();
|
||||
const player = this.playerLabel();
|
||||
|
||||
switch (session.status) {
|
||||
case 'launching':
|
||||
return `Opening in ${player}...`;
|
||||
return 'Launching…';
|
||||
case 'opened':
|
||||
return `Opened in ${player}`;
|
||||
case 'playing':
|
||||
return `Playing in ${player}`;
|
||||
return 'Opened';
|
||||
case 'error':
|
||||
return session.error || `${player} failed to launch`;
|
||||
return session.error || 'Playback failed';
|
||||
default:
|
||||
return `${player} closed`;
|
||||
return 'Closed';
|
||||
}
|
||||
});
|
||||
|
||||
readonly statusIcon = computed(() => {
|
||||
const status = this.session().status;
|
||||
if (status === 'error') return 'error_outline';
|
||||
return 'open_in_new';
|
||||
});
|
||||
|
||||
readonly showSpinner = computed(
|
||||
() => this.session().status === 'launching'
|
||||
);
|
||||
readonly showStatusIcon = computed(
|
||||
() => this.session().status !== 'launching'
|
||||
);
|
||||
readonly showArtwork = computed(
|
||||
() => !!this.artworkUrl() && !this.artworkFailed()
|
||||
);
|
||||
@@ -72,6 +80,9 @@ export class ExternalPlaybackDockComponent {
|
||||
return 'live_tv';
|
||||
}
|
||||
});
|
||||
readonly artworkInteractive = computed(
|
||||
() => !!this.session().contentInfo?.playlistId
|
||||
);
|
||||
readonly showCloseButton = computed(
|
||||
() => this.session().canClose && this.session().status !== 'error'
|
||||
);
|
||||
@@ -86,4 +97,9 @@ export class ExternalPlaybackDockComponent {
|
||||
onArtworkError(): void {
|
||||
this.artworkFailed.set(true);
|
||||
}
|
||||
|
||||
onArtworkClick(): void {
|
||||
if (!this.artworkInteractive()) return;
|
||||
this.artworkClicked.emit();
|
||||
}
|
||||
}
|
||||
+17
@@ -720,6 +720,23 @@ export class WorkspaceShellFacade {
|
||||
);
|
||||
}
|
||||
|
||||
openActiveExternalSessionTarget(): void {
|
||||
const playlistId = this.externalPlaybackSession()?.contentInfo
|
||||
?.playlistId;
|
||||
if (!playlistId) return;
|
||||
|
||||
const playlist = this.playlists().find((p) => p._id === playlistId);
|
||||
if (!playlist) return;
|
||||
|
||||
if (playlist.serverUrl) {
|
||||
void this.router.navigate(['/workspace', 'xtreams', playlistId]);
|
||||
} else if (playlist.macAddress) {
|
||||
void this.router.navigate(['/workspace', 'stalker', playlistId]);
|
||||
} else {
|
||||
void this.router.navigate(['/workspace', 'playlists', playlistId]);
|
||||
}
|
||||
}
|
||||
|
||||
onSearchInput(value: string): void {
|
||||
this.searchQuery.set(value);
|
||||
this.scheduleSearchApply(value);
|
||||
|
||||
@@ -135,6 +135,9 @@
|
||||
<app-external-playback-dock
|
||||
[session]="session"
|
||||
(closeClicked)="facade.closeActiveExternalSession()"
|
||||
(artworkClicked)="
|
||||
facade.openActiveExternalSessionTarget()
|
||||
"
|
||||
/>
|
||||
</footer>
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ app-workspace-shell-context-sidebar {
|
||||
.workspace-now-playing {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
padding: 0;
|
||||
background: var(--app-content-bg, var(--mat-sys-surface));
|
||||
}
|
||||
|
||||
.workspace-content {
|
||||
|
||||
Reference in new issue
Block a user