feat(xtream): enhance portal status display with dynamic styling and actions

This commit is contained in:
4gray committed 2024-12-26 13:33:59 +01:00
1 parent 96f61bc859
commit d36b808026
3 files changed
+229 -16

No files matched your search

@@ -1,5 +1,48 @@
<div class="portal-status-container" [class]="getStatusColor()">
<div class="portal-status-wrapper">
<div class="portal-header">
<div class="portal-icon" [class]="getStatusColor()">
<mat-icon>{{ getStatusIcon() }}</mat-icon>
</div>
</div>
<div class="portal-info">
<span class="portal-name">{{
currentPlaylist()?.title || 'Unknown Portal'
}}</span>
<span class="portal-label">{{ xtreamStore.portalStatus() }}</span>
</div>
<div class="portal-actions">
<button
mat-icon-button
class="action-button"
routerLink="/"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
matTooltip="Home"
>
<mat-icon>home</mat-icon>
</button>
<button
mat-icon-button
class="action-button"
(click)="openAccountInfo()"
matTooltip="Account Info"
>
<mat-icon>person</mat-icon>
</button>
<button
mat-icon-button
class="action-button"
(click)="openPlaylistInfo()"
matTooltip="Playlist Info"
>
<mat-icon>playlist_play</mat-icon>
</button>
</div>
</div>
</div>
<mat-nav-list>
<a
<!-- <a
mat-list-item
routerLink="/"
routerLinkActive="active"
@@ -7,7 +50,7 @@
>
<mat-icon matListItemIcon>home</mat-icon>
<span matListItemTitle>Home</span>
</a>
</a> -->
<a
mat-list-item
@@ -73,16 +116,6 @@
</mat-nav-list>
<mat-nav-list class="navigation-bottom">
<a mat-list-item (click)="openAccountInfo()">
<mat-icon matListItemIcon>account_circle</mat-icon>
<span matListItemTitle>Account Info</span>
</a>
<a mat-list-item (click)="openPlaylistInfo()">
<mat-icon matListItemIcon>info</mat-icon>
<span matListItemTitle>Playlist Info</span>
</a>
<a mat-list-item (click)="openSettings()">
<mat-icon matListItemIcon>settings</mat-icon>
<span matListItemTitle>{{ 'MENU.SETTINGS' | translate }}</span>
@@ -31,4 +31,148 @@ mat-divider {
mat-icon {
margin-right: 8px;
}
.portal-status-container {
margin: -10px -10px 0px;
padding: 16px;
position: relative;
overflow: hidden;
background: linear-gradient(to bottom, rgba(18, 18, 18, 0.8) 0%, transparent 100%);
backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
min-height: 120px;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
opacity: 0.8;
transition: background 0.3s ease;
}
&.status-active::before {
background: linear-gradient(135deg, rgba(76, 175, 80, 0.3) 0%, transparent 100%);
}
&.status-inactive::before, &.status-unavailable::before {
background: linear-gradient(135deg, rgba(244, 67, 54, 0.3) 0%, transparent 100%);
}
&.status-expired::before {
background: linear-gradient(135deg, rgba(255, 152, 0, 0.3) 0%, transparent 100%);
}
.portal-status-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
height: 100%;
min-width: 0;
width: 100%;
}
.portal-header {
display: flex;
justify-content: center;
margin-bottom: 4px;
}
.portal-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
mat-icon {
font-size: 28px;
width: 28px;
height: 28px;
margin: 0;
}
&.status-active { color: #4caf50; }
&.status-inactive, &.status-unavailable { color: #f44336; }
&.status-expired { color: #ff9800; }
}
.portal-info {
text-align: center;
.portal-name {
font-size: 16px;
font-weight: 600;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 4px;
}
.portal-label {
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
color: rgba(255, 255, 255, 0.7);
}
}
.portal-actions {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 4px;
.action-button {
width: 36px;
height: 36px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
mat-icon {
font-size: 20px;
width: 20px;
height: 20px;
margin: 0;
color: rgba(255, 255, 255, 0.7);
transition: all 0.2s ease;
}
&:hover {
background: rgba(255, 255, 255, 0.15);
mat-icon {
color: rgba(255, 255, 255, 0.95);
transform: scale(1.1);
}
}
}
}
.info-button {
opacity: 0.7;
margin-left: 8px;
&:hover {
opacity: 1;
}
}
}
.navigation-bottom {
padding-bottom: 16px;
}
@@ -1,7 +1,9 @@
import { Component, inject } from '@angular/core';
import { MatIconButton } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatIcon } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
import { MatTooltipModule } from '@angular/material/tooltip';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { Store } from '@ngrx/store';
import { TranslateModule } from '@ngx-translate/core';
@@ -16,10 +18,12 @@ import { XtreamStore } from '../xtream.store';
standalone: true,
imports: [
MatListModule,
MatIconModule,
MatIcon,
MatIconButton,
RouterLink,
RouterLinkActive,
TranslateModule,
MatTooltipModule,
],
templateUrl: './navigation.component.html',
styleUrl: './navigation.component.scss',
@@ -29,8 +33,40 @@ export class NavigationComponent {
private readonly store = inject(Store);
readonly xtreamStore = inject(XtreamStore);
private readonly currentPlaylist =
this.store.selectSignal(selectActivePlaylist);
// Make currentPlaylist public so template can access it
readonly currentPlaylist = this.store.selectSignal(selectActivePlaylist);
ngOnInit() {
this.xtreamStore.checkPortalStatus();
}
getStatusColor(): string {
const status = this.xtreamStore.portalStatus();
switch (status) {
case 'active':
return 'status-active';
case 'inactive':
return 'status-inactive';
case 'expired':
return 'status-expired';
default:
return 'status-unavailable';
}
}
getStatusIcon(): string {
const status = this.xtreamStore.portalStatus();
switch (status) {
case 'active':
return 'check_circle';
case 'inactive':
return 'cancel';
case 'expired':
return 'warning';
default:
return 'error';
}
}
openAccountInfo() {
this.dialog.open(AccountInfoComponent, {