UX Overhaul Part 1 (#3047)

Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com>
This commit is contained in:
Robbie Davis 2024-08-09 13:55:31 -04:00 committed by GitHub
parent 5934d516f3
commit ff79710ac6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
324 changed files with 11589 additions and 4598 deletions

View file

@ -1,5 +1,5 @@
<ng-container *transloco="let t; read: 'actionable'">
<ng-container *ngIf="actions.length > 0">
@if (actions.length > 0) {
<div ngbDropdown container="body" class="d-inline-block">
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle
(click)="preventEvent($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
@ -8,33 +8,33 @@
</div>
</div>
<ng-template #submenu let-list="list">
<ng-container *ngFor="let action of list">
@for(action of list; track action.id) {
<!-- Non Submenu items -->
<ng-container *ngIf="action.children === undefined || action?.children?.length === 0 || action.dynamicList !== undefined ; else submenuDropdown">
<ng-container *ngIf="action.dynamicList !== undefined && (action.dynamicList | async | dynamicList) as dList; else justItem">
<ng-container *ngFor="let dynamicItem of dList">
@if (action.children === undefined || action?.children?.length === 0 || action.dynamicList !== undefined) {
@if (action.dynamicList !== undefined && (action.dynamicList | async | dynamicList); as dList) {
@for(dynamicItem of dList; track dynamicItem.title) {
<button ngbDropdownItem (click)="performDynamicClick($event, action, dynamicItem)">{{dynamicItem.title}}</button>
</ng-container>
</ng-container>
<ng-template #justItem>
<button ngbDropdownItem *ngIf="willRenderAction(action)" (click)="performAction($event, action)" (mouseover)="closeAllSubmenus()">{{t(action.title)}}</button>
</ng-template>
</ng-container>
<ng-template #submenuDropdown>
<!-- Submenu items -->
<ng-container *ngIf="shouldRenderSubMenu(action, action.children?.[0].dynamicList | async)">
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right left" (click)="preventEvent($event); openSubmenu(action.title, subMenuHover)" (mouseover)="preventEvent($event); openSubmenu(action.title, subMenuHover)" (mouseleave)="preventEvent($event)">
<button *ngIf="willRenderAction(action)" id="actions-{{action.title}}" class="submenu-toggle" ngbDropdownToggle>{{t(action.title)}} <i class="fa-solid fa-angle-right submenu-icon"></i></button>
}
} @else if (willRenderAction(action)) {
<button ngbDropdownItem (click)="performAction($event, action)" (mouseover)="closeAllSubmenus()">{{t(action.title)}}</button>
}
} @else {
@if (shouldRenderSubMenu(action, action.children?.[0].dynamicList | async)) {
<!-- Submenu items -->
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right-top"
(click)="preventEvent($event); openSubmenu(action.title, subMenuHover)"
(mouseover)="preventEvent($event); openSubmenu(action.title, subMenuHover)"
(mouseleave)="preventEvent($event)">
@if (willRenderAction(action)) {
<button id="actions-{{action.title}}" class="submenu-toggle" ngbDropdownToggle>{{t(action.title)}} <i class="fa-solid fa-angle-right submenu-icon"></i></button>
}
<div ngbDropdownMenu attr.aria-labelledby="actions-{{action.title}}">
<ng-container *ngTemplateOutlet="submenu; context: { list: action.children }"></ng-container>
</div>
</div>
</ng-container>
</ng-template>
</ng-container>
}
}
}
</ng-template>
</ng-container>
}
</ng-container>

View file

@ -26,3 +26,9 @@
float: right;
padding: var(--bs-dropdown-item-padding-y) 0;
}
// Robbie added this but it broke most of the uses
//.dropdown-toggle {
// padding-top: 0;
// padding-bottom: 0;
//}

View file

@ -11,7 +11,7 @@ import {
import {NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle} from '@ng-bootstrap/ng-bootstrap';
import { AccountService } from 'src/app/_services/account.service';
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
import {CommonModule} from "@angular/common";
import {AsyncPipe, CommonModule, NgTemplateOutlet} from "@angular/common";
import {TranslocoDirective} from "@ngneat/transloco";
import {DynamicListPipe} from "./_pipes/dynamic-list.pipe";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@ -19,7 +19,7 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({
selector: 'app-card-actionables',
standalone: true,
imports: [CommonModule, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, DynamicListPipe, TranslocoDirective],
imports: [NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, DynamicListPipe, TranslocoDirective, AsyncPipe, NgTemplateOutlet],
templateUrl: './card-actionables.component.html',
styleUrls: ['./card-actionables.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush

View file

@ -1,5 +1,5 @@
.profile-image {
font-size: 2rem;
font-size: 1.2rem;
padding: 20px;
}

View file

@ -1,28 +0,0 @@
import {inject, Pipe, PipeTransform} from '@angular/core';
import {ScrobbleEventType} from "../_models/scrobbling/scrobble-event";
import {TranslocoService} from "@ngneat/transloco";
@Pipe({
name: 'scrobbleEventType',
standalone: true
})
export class ScrobbleEventTypePipe implements PipeTransform {
translocoService = inject(TranslocoService);
transform(value: ScrobbleEventType): string {
switch (value) {
case ScrobbleEventType.ChapterRead:
return this.translocoService.translate('scrobble-event-type-pipe.chapter-read');
case ScrobbleEventType.ScoreUpdated:
return this.translocoService.translate('scrobble-event-type-pipe.score-updated');
case ScrobbleEventType.AddWantToRead:
return this.translocoService.translate('scrobble-event-type-pipe.want-to-read-add');
case ScrobbleEventType.RemoveWantToRead:
return this.translocoService.translate('scrobble-event-type-pipe.want-to-read-remove');
case ScrobbleEventType.Review:
return this.translocoService.translate('scrobble-event-type-pipe.review');
}
}
}

View file

@ -21,12 +21,9 @@
}
</div>
</div>
<table class="table table-striped table-hover table-sm scrollable">
<table class="table table-striped table-sm scrollable">
<thead>
<tr>
<th scope="col" sortable="createdUtc" (sort)="updateSort($event)">
{{t('created-header')}}
</th>
<th scope="col" sortable="lastModifiedUtc" (sort)="updateSort($event)" direction="desc">
{{t('last-modified-header')}}
</th>
@ -45,66 +42,62 @@
</tr>
</thead>
<tbody>
@if (events.length === 0) {
<tr>
<td colspan="6">{{t('no-data')}}</td>
</tr>
}
<tr *ngFor="let item of events; let idx = index;">
<td>
{{item.createdUtc | utcToLocalTime | defaultValue}}
</td>
<td>
{{item.lastModifiedUtc | utcToLocalTime | defaultValue }}
</td>
<td>
{{item.scrobbleEventType | scrobbleEventType}}
</td>
<td id="scrobble-history--{{idx}}">
<a href="/library/{{item.libraryId}}/series/{{item.seriesId}}" target="_blank">{{item.seriesName}}</a>
</td>
<td>
@switch (item.scrobbleEventType) {
@case (ScrobbleEventType.ChapterRead) {
@if(item.volumeNumber === LooseLeafOrDefaultNumber) {
@if (item.chapterNumber === LooseLeafOrDefaultNumber) {
{{t('special')}}
} @else {
{{t('chapter-num', {num: item.chapterNumber})}}
@for(item of events; track item; let idx = $index) {
<tr>
<td>
{{item.lastModifiedUtc | utcToLocalTime | defaultValue }}
</td>
<td>
{{item.scrobbleEventType | scrobbleEventType}}
</td>
<td id="scrobble-history--{{idx}}">
<a href="/library/{{item.libraryId}}/series/{{item.seriesId}}" target="_blank">{{item.seriesName}}</a>
</td>
<td>
@switch (item.scrobbleEventType) {
@case (ScrobbleEventType.ChapterRead) {
@if(item.volumeNumber === LooseLeafOrDefaultNumber) {
@if (item.chapterNumber === LooseLeafOrDefaultNumber) {
{{t('special')}}
} @else {
{{t('chapter-num', {num: item.chapterNumber})}}
}
}
@else if (item.chapterNumber === LooseLeafOrDefaultNumber) {
{{t('volume-num', {num: item.volumeNumber})}}
}
@else if (item.chapterNumber === LooseLeafOrDefaultNumber && item.volumeNumber === SpecialVolumeNumber) {
Special
}
@else {
{{t('volume-and-chapter-num', {v: item.volumeNumber, n: item.chapterNumber})}}
}
}
@case (ScrobbleEventType.ScoreUpdated) {
{{t('rating', {r: item.rating})}}
}
@default {
{{t('not-applicable')}}
}
}
@else if (item.chapterNumber === LooseLeafOrDefaultNumber) {
{{t('volume-num', {num: item.volumeNumber})}}
</td>
<td>
@if(item.isProcessed) {
<i class="fa-solid fa-check-circle icon" aria-hidden="true"></i>
} @else if (item.isErrored) {
<i class="fa-solid fa-circle-exclamation icon error" aria-hidden="true" [ngbTooltip]="item.errorDetails"></i>
} @else {
<i class="fa-regular fa-circle icon" aria-hidden="true"></i>
}
@else if (item.chapterNumber === LooseLeafOrDefaultNumber && item.volumeNumber === SpecialVolumeNumber) {
Special
}
@else {
{{t('volume-and-chapter-num', {v: item.volumeNumber, n: item.chapterNumber})}}
}
}
@case (ScrobbleEventType.ScoreUpdated) {
{{t('rating', {r: item.rating})}}
}
@default {
{{t('not-applicable')}}
}
}
</td>
<td>
@if(item.isProcessed) {
<i class="fa-solid fa-check-circle icon" aria-hidden="true"></i>
} @else if (item.isErrored) {
<i class="fa-solid fa-circle-exclamation icon error" aria-hidden="true" [ngbTooltip]="item.errorDetails"></i>
} @else {
<i class="fa-regular fa-circle icon" aria-hidden="true"></i>
}
<span class="visually-hidden" attr.aria-labelledby="scrobble-history--{{idx}}">
{{item.isProcessed ? t('processed') : t('not-processed')}}
</span>
</td>
</tr>
<span class="visually-hidden" attr.aria-labelledby="scrobble-history--{{idx}}">
{{item.isProcessed ? t('processed') : t('not-processed')}}
</span>
</td>
</tr>
} @empty {
<tr><td colspan="6" style="text-align: center;">{{t('no-data')}}</td></tr>
}
</tbody>
</table>
</ng-container>

View file

@ -4,7 +4,7 @@ import {CommonModule} from '@angular/common';
import {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.service";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {ScrobbleEvent, ScrobbleEventType} from "../../_models/scrobbling/scrobble-event";
import {ScrobbleEventTypePipe} from "../scrobble-event-type.pipe";
import {ScrobbleEventTypePipe} from "../../_pipes/scrobble-event-type.pipe";
import {NgbPagination, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {ScrobbleEventSortField} from "../../_models/scrobbling/scrobble-event-filter";
import {debounceTime, take} from "rxjs/operators";