fix(pwa): progress bar setup loading, PWA install detection, Miei filter chip & UI cleanup

This commit is contained in:
David Frassi
2026-08-08 16:17:20 +02:00
parent 0069c1a55f
commit a15a6c2139
21 changed files with 2378 additions and 559 deletions
+311 -159
View File
@@ -6,12 +6,15 @@
<div class="header-text-group">
<span class="app-name">{{ appName }}</span>
<span class="version-badge" style="cursor: default; display: inline-flex; align-items: center; gap: 4px;">
v{{ version }}<span *ngIf="settingsService.userName()"> - {{ settingsService.userName() }}</span>
v{{ version }}
</span>
</div>
</div>
</ion-title>
<ion-buttons slot="end">
<ion-button routerLink="/propose-canto" class="add-btn" title="Crea un nuovo canto">
<ion-icon slot="icon-only" name="add-outline"></ion-icon>
</ion-button>
<ng-container *ngIf="hasUpdateAvailable(); else showQrBtn">
<ion-button (click)="checkForAppUpdate($event)" class="add-btn" title="Aggiornamento disponibile">
<ion-icon slot="icon-only" name="refresh-outline" color="secondary"></ion-icon>
@@ -35,117 +38,328 @@
placeholder="Cerca un canto..."
[value]="searchQuery()"
(ionInput)="onSearch($event)"
[disabled]="isAdvancedSearchOpen()"
class="custom-searchbar">
</ion-searchbar>
<ion-button fill="clear" (click)="toggleVoiceSearch()" class="voice-search-btn">
<ion-icon slot="icon-only" [name]="audioEngine.isSearching() ? 'mic' : 'mic-outline'" [color]="audioEngine.isSearching() ? 'danger' : 'secondary'"></ion-icon>
<ion-button fill="clear" (click)="toggleAdvancedSearch()" class="adv-search-btn" [class.active]="isAdvancedSearchOpen()" title="Ricerca avanzata">
<ion-icon slot="icon-only" name="options-outline" [color]="isAdvancedSearchOpen() ? 'secondary' : 'medium'"></ion-icon>
</ion-button>
<ion-button fill="clear" (click)="toggleVoiceSearch('global')" class="voice-search-btn" [disabled]="isAdvancedSearchOpen()">
<ion-icon slot="icon-only" [name]="(audioEngine.isSearching() && activeVoiceField() === 'global') ? 'mic' : 'mic-outline'" [color]="(audioEngine.isSearching() && activeVoiceField() === 'global') ? 'danger' : 'secondary'"></ion-icon>
</ion-button>
</div>
</div>
<div class="filter-actions-row">
<div class="song-count-card outfit-font">
{{ filteredCanti().length }}
</div>
<div *ngIf="playlistService.activeListName() !== null && totalPlaylistDuration()" class="song-count-card outfit-font">
<ion-icon name="time-outline" style="margin-right: 4px; font-size: 1rem; color: var(--ion-color-secondary);"></ion-icon>
<span>{{ totalPlaylistDuration() }}</span>
</div>
<div class="filter-buttons">
<ion-button
[fill]="activeFilterType() === 'playlist' || playlistService.activeListName() !== null ? 'solid' : 'outline'"
size="small"
(click)="toggleFilterType('playlist')"
class="filter-chip">
{{ playlistService.activeListName() !== null ? 'Playlist: ' + playlistService.activeListName() : 'Playlist' }}
<span *ngIf="isRemotePlaylist() && playlistService.hasRemotePlaylistUpdate()" (click)="refreshActiveRemotePlaylist(); $event.stopPropagation();" style="display: inline-flex; align-items: center; justify-content: center; padding: 2px; margin-left: 6px; background: rgba(var(--ion-color-secondary-rgb), 0.25); border-radius: 50%; border: 1px solid var(--ion-color-secondary); width: 20px; height: 20px; box-shadow: 0 0 8px rgba(var(--ion-color-secondary-rgb), 0.3); vertical-align: middle;">
<ion-icon name="refresh-outline" style="font-size: 0.95rem; color: var(--ion-color-secondary); font-weight: bold;"></ion-icon>
</span>
<ion-icon slot="end" name="chevron-down-outline" *ngIf="playlistService.activeListName() === null"></ion-icon>
<span class="close-icon-wrapper" *ngIf="playlistService.activeListName() !== null" (click)="clearSpecialList($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
<div class="comunita-filter-wrapper" style="position: relative; display: inline-flex; align-items: center; margin-right: 8px; z-index: 999;" *ngIf="settingsService.comunitaEnabled()">
<ion-button
[fill]="comunitaService.isFilterActive() ? 'solid' : 'outline'"
size="small"
(click)="toggleComunitaFilter()"
[color]="comunitaService.isFilterActive() ? 'secondary' : 'medium'"
class="filter-chip"
style="margin: 0;">
<ion-icon slot="start" name="people-outline" style="font-size: 1.1rem; margin-right: 4px;"></ion-icon>
{{ comunitaService.comunitaCode() ? comunitaService.comunitaNome() : 'Comunità' }}
</ion-button>
<!-- Advanced Search & Filters Toggle Row -->
<div class="advanced-search-toggle-row">
<div class="toggle-row-left">
<div class="song-count-card outfit-font">
{{ filteredCanti().length }}
</div>
<button type="button" class="advanced-search-link outfit-font" (click)="toggleAdvancedSearch()">
<ion-icon [name]="isAdvancedSearchOpen() ? 'chevron-up-outline' : 'options-outline'"></ion-icon>
<span>Ricerche</span>
<span class="inline-clear-btn" *ngIf="hasAdvancedSearchParams()" (click)="clearAdvancedSearch($event)" title="Reset ricerche">
<ion-icon name="close-circle"></ion-icon>
</span>
</button>
<button type="button" class="advanced-search-link outfit-font" (click)="toggleFilterCard()">
<ion-icon [name]="isFilterCardOpen() ? 'chevron-up-outline' : 'filter-outline'"></ion-icon>
<span>Filtri</span>
<span class="inline-clear-btn" *ngIf="hasFilterCardParams()" (click)="clearFilterCard($event)" title="Reset filtri">
<ion-icon name="close-circle"></ion-icon>
</span>
</button>
<button type="button" class="advanced-search-link outfit-font" (click)="togglePlaylistCard()">
<ion-icon [name]="isPlaylistCardOpen() ? 'chevron-up-outline' : 'list-outline'"></ion-icon>
<span>Liste</span>
<span class="inline-clear-btn" *ngIf="hasPlaylistCardParams()" (click)="clearPlaylistCard($event)" title="Reset liste">
<ion-icon name="close-circle"></ion-icon>
</span>
</button>
</div>
</div>
<!-- Advanced Search Panel -->
<div class="advanced-search-panel glass outfit-font"
*ngIf="isAdvancedSearchOpen()"
(touchstart)="onPanelTouchStart($event)"
(touchend)="onPanelTouchEnd($event, 'advancedSearch')">
<div class="adv-search-grid">
<div class="adv-input-group">
<label class="adv-label">Ricerca per titolo</label>
<div class="adv-input-wrapper">
<ion-icon name="text-outline" class="adv-input-icon"></ion-icon>
<input
type="text"
placeholder="Titolo canto..."
[value]="searchTitle()"
(input)="onSearchTitleInput($event)"
class="adv-input" />
<ion-icon name="close-circle" *ngIf="searchTitle()" (click)="searchTitle.set('')" class="adv-clear-icon"></ion-icon>
<ion-button fill="clear" size="small" (click)="toggleVoiceSearch('title')" class="adv-mic-btn">
<ion-icon slot="icon-only" [name]="(audioEngine.isSearching() && activeVoiceField() === 'title') ? 'mic' : 'mic-outline'" [color]="(audioEngine.isSearching() && activeVoiceField() === 'title') ? 'danger' : 'secondary'"></ion-icon>
</ion-button>
</div>
</div>
<ion-button
[fill]="activeFilterType() === 'lista_completa' || showValidati() || showNonValidati() ? 'solid' : 'outline'"
size="small"
(click)="toggleFilterType('lista_completa')"
class="filter-chip">
{{ showValidati() ? 'Lista: Validati' : (showNonValidati() ? 'Lista: Non Validati' : 'Lista completa') }}
<ion-icon slot="end" name="chevron-down-outline" *ngIf="!showValidati() && !showNonValidati()"></ion-icon>
<span class="close-icon-wrapper" *ngIf="showValidati() || showNonValidati()" (click)="clearListaCompleta($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
<ion-button
[fill]="activeFilterType() === 'liturgico' || selectedLiturgico() !== null ? 'solid' : 'outline'"
size="small"
(click)="toggleFilterType('liturgico')"
class="filter-chip">
{{ selectedLiturgico() !== null ? 'Liturgia: ' + getSelectedLiturgicoLabel() : 'Liturgia' }}
<ion-icon slot="end" name="chevron-down-outline" *ngIf="selectedLiturgico() === null"></ion-icon>
<span class="close-icon-wrapper" *ngIf="selectedLiturgico() !== null" (click)="clearLiturgico($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
<ion-button
[fill]="activeFilterType() === 'tematico' || selectedTematico() !== null ? 'solid' : 'outline'"
size="small"
(click)="toggleFilterType('tematico')"
class="filter-chip">
{{ selectedTematico() !== null ? 'Periodo: ' + getSelectedTematicoLabel() : 'Periodo' }}
<ion-icon slot="end" name="chevron-down-outline" *ngIf="selectedTematico() === null"></ion-icon>
<span class="close-icon-wrapper" *ngIf="selectedTematico() !== null" (click)="clearTematico($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
<ion-button
[fill]="showSuggeriti() ? 'solid' : 'outline'"
size="small"
(click)="toggleSuggeriti()"
class="filter-chip">
Suggeriti
<span class="close-icon-wrapper" *ngIf="showSuggeriti()" (click)="clearSuggeriti($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
<div class="adv-input-group">
<label class="adv-label">Ricerca per autore</label>
<div class="adv-input-wrapper">
<ion-icon name="person-outline" class="adv-input-icon"></ion-icon>
<input
type="text"
placeholder="Nome autore..."
[value]="searchAuthor()"
(input)="onSearchAuthorInput($event)"
class="adv-input" />
<ion-icon name="close-circle" *ngIf="searchAuthor()" (click)="searchAuthor.set('')" class="adv-clear-icon"></ion-icon>
<ion-button fill="clear" size="small" (click)="toggleVoiceSearch('author')" class="adv-mic-btn">
<ion-icon slot="icon-only" [name]="(audioEngine.isSearching() && activeVoiceField() === 'author') ? 'mic' : 'mic-outline'" [color]="(audioEngine.isSearching() && activeVoiceField() === 'author') ? 'danger' : 'secondary'"></ion-icon>
</ion-button>
</div>
</div>
<ion-button
[fill]="showTopTen() ? 'solid' : 'outline'"
size="small"
(click)="toggleTopTen()"
class="filter-chip">
Top Ten
<span class="close-icon-wrapper" *ngIf="showTopTen()" (click)="clearTopTen($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
<div class="adv-input-group">
<label class="adv-label">Ricerca per testo</label>
<div class="adv-input-wrapper">
<ion-icon name="document-text-outline" class="adv-input-icon"></ion-icon>
<input
type="text"
placeholder="Parole nel testo..."
[value]="searchText()"
(input)="onSearchTextInput($event)"
class="adv-input" />
<ion-icon name="close-circle" *ngIf="searchText()" (click)="searchText.set('')" class="adv-clear-icon"></ion-icon>
<ion-button fill="clear" size="small" (click)="toggleVoiceSearch('text')" class="adv-mic-btn">
<ion-icon slot="icon-only" [name]="(audioEngine.isSearching() && activeVoiceField() === 'text') ? 'mic' : 'mic-outline'" [color]="(audioEngine.isSearching() && activeVoiceField() === 'text') ? 'danger' : 'secondary'"></ion-icon>
</ion-button>
</div>
</div>
<div class="adv-input-group">
<label class="adv-label">Ricerca per nr canto</label>
<div class="adv-input-wrapper">
<ion-icon name="pricetag-outline" class="adv-input-icon"></ion-icon>
<input
type="text"
placeholder="Numero canto..."
[value]="searchNumber()"
(input)="onSearchNumberInput($event)"
class="adv-input" />
<ion-icon name="close-circle" *ngIf="searchNumber()" (click)="searchNumber.set('')" class="adv-clear-icon"></ion-icon>
<ion-button fill="clear" size="small" (click)="toggleVoiceSearch('number')" class="adv-mic-btn">
<ion-icon slot="icon-only" [name]="(audioEngine.isSearching() && activeVoiceField() === 'number') ? 'mic' : 'mic-outline'" [color]="(audioEngine.isSearching() && activeVoiceField() === 'number') ? 'danger' : 'secondary'"></ion-icon>
</ion-button>
</div>
</div>
</div>
</div>
<!-- Playlist Card (default closed) -->
<div class="filter-card glass outfit-font"
*ngIf="isPlaylistCardOpen()"
(touchstart)="onPanelTouchStart($event)"
(touchend)="onPanelTouchEnd($event, 'playlistCard')">
<div class="filter-card-row">
<span class="filter-row-label">Playlist</span>
<div class="filter-row-items">
<!-- Comunità filter button if enabled -->
<div class="comunita-filter-wrapper" style="position: relative; display: inline-flex; align-items: center; z-index: 999;" *ngIf="settingsService.comunitaEnabled()">
<ion-button
[fill]="comunitaService.isFilterActive() ? 'solid' : 'outline'"
size="small"
(click)="toggleComunitaFilter()"
[color]="comunitaService.isFilterActive() ? 'secondary' : 'medium'"
class="filter-chip"
style="margin: 0;">
<ion-icon slot="start" name="people-outline" style="font-size: 1.1rem; margin-right: 4px;"></ion-icon>
{{ comunitaService.comunitaCode() ? comunitaService.comunitaNome() : 'Comunità' }}
</ion-button>
</div>
<!-- Playlist Chips -->
<div
*ngFor="let item of playlistService.allPlaylists()"
class="momento-chip glass"
[class.active]="playlistService.activePlaylistId() === item.id"
[class.comunita-chip]="item.isComunita"
(click)="playlistService.activePlaylistId() === item.id ? clearSpecialList($event) : selectPlaylist(item)">
<ion-icon *ngIf="item.isComunita" name="people-outline" style="font-size: 0.85rem; margin-right: 4px; vertical-align: middle;"></ion-icon>
{{ item.name }}
<span *ngIf="playlistService.activePlaylistId() === item.id && isRemotePlaylist() && playlistService.hasRemotePlaylistUpdate()" (click)="refreshActiveRemotePlaylist(); $event.stopPropagation();" style="display: inline-flex; align-items: center; justify-content: center; padding: 2px; margin-left: 6px; background: rgba(var(--ion-color-secondary-rgb), 0.25); border-radius: 50%; border: 1px solid var(--ion-color-secondary); width: 18px; height: 18px; vertical-align: middle;">
<ion-icon name="refresh-outline" style="font-size: 0.85rem; color: var(--ion-color-secondary); font-weight: bold;"></ion-icon>
</span>
<span class="close-icon-wrapper" *ngIf="playlistService.activePlaylistId() === item.id" (click)="clearSpecialList($event)">
<ion-icon name="close-circle"></ion-icon>
</span>
</div>
<span *ngIf="playlistService.allPlaylists().length === 0 && !playlistService.selectionMode()" class="empty-playlist-text">
Nessuna playlist creata
</span>
</ion-button>
</div>
</div>
<div class="filter-card-footer" *ngIf="hasPlaylistCardParams()">
<div></div>
<button type="button" class="clear-advanced-link outfit-font" (click)="clearPlaylistCard()">
<ion-icon name="close-circle-outline"></ion-icon>
<span>Reset liste</span>
</button>
</div>
</div>
<!-- Filter Card (default closed) -->
<div class="filter-card glass outfit-font"
*ngIf="isFilterCardOpen()"
(touchstart)="onPanelTouchStart($event)"
(touchend)="onPanelTouchEnd($event, 'filterCard')">
<!-- Row 2: Tipologia -->
<div class="filter-card-row">
<span class="filter-row-label">Tipologia</span>
<div class="filter-row-items">
<div
class="momento-chip glass"
[class.active]="showValidati()"
(click)="toggleValidati()">
Validati
<span class="close-icon-wrapper" *ngIf="showValidati()" (click)="clearListaCompleta($event)">
<ion-icon name="close-circle"></ion-icon>
</span>
</div>
<div
class="momento-chip glass"
[class.active]="showNonValidati()"
(click)="toggleNonValidati()"
style="display: inline-flex; align-items: center; gap: 6px;">
<span>Non Validati</span>
<ion-icon
*ngIf="showNonValidati() && !playlistService.selectionMode() && settingsService.showEditor()"
name="add-outline"
(click)="$event.stopPropagation()"
routerLink="/propose-canto"
class="non-validati-add-icon">
</ion-icon>
<span class="close-icon-wrapper" *ngIf="showNonValidati()" (click)="clearListaCompleta($event)">
<ion-icon name="close-circle"></ion-icon>
</span>
</div>
</div>
</div>
<!-- Row 3: Liturgia -->
<div class="filter-card-row">
<span class="filter-row-label">Liturgia</span>
<div class="filter-row-items">
<div
*ngFor="let item of cantiService.indiceLiturgico()"
class="momento-chip glass"
[class.active]="selectedLiturgico() === item.id"
(click)="toggleIndex(item.id, 'liturgico')">
{{ item.tag_name }}
<span class="close-icon-wrapper" *ngIf="selectedLiturgico() === item.id" (click)="clearLiturgico($event)">
<ion-icon name="close-circle"></ion-icon>
</span>
</div>
</div>
</div>
<!-- Row 4: Periodo -->
<div class="filter-card-row">
<span class="filter-row-label">Periodo</span>
<div class="filter-row-items">
<div
*ngFor="let item of cantiService.indiceTematico()"
class="momento-chip glass"
[class.active]="selectedTematico() === item.id"
(click)="toggleIndex(item.id, 'tematico')">
{{ item.tag_name }}
<span class="close-icon-wrapper" *ngIf="selectedTematico() === item.id" (click)="clearTematico($event)">
<ion-icon name="close-circle"></ion-icon>
</span>
</div>
</div>
</div>
<!-- Row 5 (Footer): Suggeriti, Top Ten & Reset filtri -->
<div class="filter-card-footer">
<div class="footer-actions">
<ion-button
[fill]="showSuggeriti() ? 'solid' : 'outline'"
size="small"
(click)="toggleSuggeriti()"
class="filter-chip">
Suggeriti
<span class="close-icon-wrapper" *ngIf="showSuggeriti()" (click)="clearSuggeriti($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
<ion-button
[fill]="showTopTen() ? 'solid' : 'outline'"
size="small"
(click)="toggleTopTen()"
class="filter-chip">
Top Ten
<span class="close-icon-wrapper" *ngIf="showTopTen()" (click)="clearTopTen($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
<ion-button
[fill]="showOnlyMine() ? 'solid' : 'outline'"
size="small"
(click)="toggleOnlyMine()"
class="filter-chip">
Miei
<span class="close-icon-wrapper" *ngIf="showOnlyMine()" (click)="clearOnlyMine($event)">
<ion-icon slot="end" name="close-circle"></ion-icon>
</span>
</ion-button>
</div>
</div>
</div>
</div>
</ion-toolbar>
<!-- Fixed Actions Bar in header for active playlist or selection mode -->
<ion-toolbar class="bg-gradient playlist-toolbar" *ngIf="(playlistService.activeListName() !== null && !playlistService.selectionMode()) || (playlistService.selectionMode() && reorderList().length > 0)">
<div class="playlist-actions-bar">
<!-- Duration Badge on the left -->
<div class="playlist-duration-pill outfit-font" *ngIf="totalPlaylistDuration()">
<ion-icon name="time-outline"></ion-icon>
<span>{{ totalPlaylistDuration() }}</span>
</div>
<!-- Playlist Name in middle -->
<div class="playlist-title-badge outfit-font" *ngIf="playlistService.activeListName() && !playlistService.selectionMode()">
<span class="playlist-title-text">{{ playlistService.activeListName() }}</span>
</div>
<!-- Active Playlist Actions -->
<div class="selection-pill glass" *ngIf="playlistService.activeListName() !== null && !playlistService.selectionMode()">
<ion-button fill="clear" color="secondary" (click)="refreshActiveRemotePlaylist()" class="mini-action-btn" title="Aggiorna Playlist" *ngIf="isRemotePlaylist()">
<ion-icon slot="icon-only" name="refresh-outline" [style.color]="playlistService.hasRemotePlaylistUpdate() ? 'var(--ion-color-warning)' : 'inherit'"></ion-icon>
</ion-button>
<ion-button fill="clear" color="secondary" (click)="editPlaylist()" class="mini-action-btn" *ngIf="!isComunitaPlaylist() && isActivePlaylistSaved()">
<ion-icon slot="icon-only" name="create-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" color="secondary" (click)="cloneActivePlaylist()" class="mini-action-btn" title="Clona Playlist">
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" color="secondary" (click)="printActivePlaylist()" class="mini-action-btn" title="Esporta PDF">
<ion-icon slot="icon-only" name="print-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" color="secondary" (click)="shareActivePlaylist()" class="mini-action-btn">
<ion-icon slot="icon-only" name="share-social-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" color="danger" (click)="deleteActivePlaylist()" class="mini-action-btn" *ngIf="playlistService.activeListName() !== null && !isComunitaPlaylist()">
<ion-icon name="trash-outline" slot="icon-only"></ion-icon>
</ion-button>
</div>
<!-- Category Scroll (Dropdown style) -->
<ion-toolbar class="bg-gradient momentos-toolbar" *ngIf="shouldShowSubSectionToolbar()">
<div class="momento-scroll" *ngIf="activeFilterType() !== 'playlist' || playlistService.allPlaylists().length > 0 || playlistService.selectionMode()">
<!-- Selection Mode Actions -->
<div class="selection-pill glass" *ngIf="playlistService.selectionMode() && reorderList().length > 0">
<span class="selection-count">{{ reorderList().length }}</span>
@@ -166,68 +380,6 @@
<ion-icon slot="icon-only" name="close-circle-outline"></ion-icon>
</ion-button>
</div>
<!-- Active Playlist Actions -->
<div class="selection-pill glass" *ngIf="activeFilterType() === 'playlist' && playlistService.activePlaylistId() && !playlistService.selectionMode()">
<ion-button fill="clear" color="secondary" (click)="refreshActiveRemotePlaylist()" class="mini-action-btn" title="Aggiorna Playlist" *ngIf="isRemotePlaylist()">
<ion-icon slot="icon-only" name="refresh-outline" [style.color]="playlistService.hasRemotePlaylistUpdate() ? 'var(--ion-color-warning)' : 'inherit'"></ion-icon>
</ion-button>
<ion-button fill="clear" color="secondary" (click)="editPlaylist()" class="mini-action-btn" *ngIf="!isComunitaPlaylist() && isActivePlaylistSaved()">
<ion-icon slot="icon-only" name="create-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" color="secondary" (click)="cloneActivePlaylist()" class="mini-action-btn" title="Clona Playlist">
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" color="secondary" (click)="shareActivePlaylist()" class="mini-action-btn">
<ion-icon slot="icon-only" name="share-social-outline"></ion-icon>
</ion-button>
<ion-button fill="clear" color="danger" (click)="deleteActivePlaylist()" class="mini-action-btn" *ngIf="playlistService.activePlaylistId() && !isComunitaPlaylist()">
<ion-icon name="trash-outline" slot="icon-only"></ion-icon>
</ion-button>
</div>
<!-- Filter chips -->
<ng-container *ngIf="activeFilterType() === 'lista_completa'">
<div
class="momento-chip glass"
[class.active]="showValidati()"
(click)="toggleValidati()">
Validati
</div>
<div
class="momento-chip glass"
[class.active]="showNonValidati()"
(click)="toggleNonValidati()"
style="display: inline-flex; align-items: center; gap: 6px;">
<span>Non Validati</span>
<ion-icon
*ngIf="showNonValidati() && !playlistService.selectionMode() && settingsService.showEditor()"
name="add-outline"
(click)="$event.stopPropagation()"
routerLink="/propose-canto"
class="non-validati-add-icon">
</ion-icon>
</div>
</ng-container>
<ng-container *ngIf="activeFilterType() !== 'lista_completa'">
<div
*ngFor="let item of (activeFilterType() === 'playlist' ? playlistService.allPlaylists() : (activeFilterType() === 'liturgico' ? cantiService.indiceLiturgico() : (activeFilterType() === 'tematico' ? cantiService.indiceTematico() : [])))"
class="momento-chip glass"
[class.active]="activeFilterType() === 'playlist' ? playlistService.activePlaylistId() === item.id : isIndexSelected(item.id)"
[class.comunita-chip]="activeFilterType() === 'playlist' && item.isComunita"
(click)="activeFilterType() === 'playlist' ? selectPlaylist(item) : toggleIndex(item.id, activeFilterType()!)">
<ion-icon *ngIf="activeFilterType() === 'playlist' && item.isComunita" name="people-outline" style="font-size: 0.85rem; margin-right: 4px; vertical-align: middle;"></ion-icon>
{{ activeFilterType() === 'playlist' ? item.name : item.tag_name }}
</div>
</ng-container>
</div>
<!-- Instructions when playlist is empty -->
<div class="empty-playlist-container" *ngIf="activeFilterType() === 'playlist' && playlistService.allPlaylists().length === 0 && !playlistService.selectionMode()">
<p class="empty-playlist-instruction outfit-font">
Per creare una playlist, seleziona il nr del canto che vuoi inserire nella playlist, riordinali e salvala con nome
</p>
</div>
</ion-toolbar>
</ion-header>
@@ -244,6 +396,7 @@
<div class="loading-wrapper">
<ion-spinner name="crescent" color="secondary"></ion-spinner>
<div class="percentage-label outfit-font">{{ cantiService.progress() }}%</div>
<ion-progress-bar [value]="cantiService.progress() / 100" color="secondary" style="width: 200px; border-radius: 10px; height: 8px; margin-top: 8px;"></ion-progress-bar>
<p class="ion-margin-top" style="color: var(--ion-color-secondary)">Caricamento canti...</p>
</div>
</div>
@@ -473,7 +626,6 @@
<div class="black-screen-overlay" *ngIf="isBlackOverlayActive()" (click)="isBlackOverlayDismissed.set(true)">
<div class="black-screen-content" (click)="$event.stopPropagation()">
<div (click)="isBlackOverlayDismissed.set(true)" style="display: flex; flex-direction: column; align-items: center; gap: 8px; cursor: pointer; width: 100%;">
<ion-icon name="car-outline" class="car-mode-icon"></ion-icon>
<p class="car-mode-text outfit-font">Schermo nero attivo</p>
</div>