UX Overhaul Part 1 (#3047)
Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com>
This commit is contained in:
parent
5934d516f3
commit
ff79710ac6
324 changed files with 11589 additions and 4598 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.profile-image {
|
||||
font-size: 2rem;
|
||||
font-size: 1.2rem;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue