Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com>
This commit is contained in:
Robbie Davis 2024-08-26 10:52:46 -04:00 committed by GitHub
parent fc644985be
commit 62383042b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 741 additions and 399 deletions

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 {AsyncPipe, CommonModule, NgTemplateOutlet} from "@angular/common";
import {AsyncPipe, NgTemplateOutlet} from "@angular/common";
import {TranslocoDirective} from "@jsverse/transloco";
import {DynamicListPipe} from "./_pipes/dynamic-list.pipe";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";

View file

@ -0,0 +1,31 @@
<ng-container *transloco="let t; read: 'series-detail'">
@if(mobileSeriesImgBackground === 'true') {
<app-image [styles]="{'background': 'none'}" [imageUrl]="coverImage"></app-image>
} @else {
<app-image [styles]="{'object-fit': 'contain', 'background': 'none', 'max-height': '400px'}" [imageUrl]="coverImage"></app-image>
}
<div class="card-overlay"></div>
<div class="overlay-information">
<div class="overlay-information--centered">
<span class="card-title library mx-auto" style="width: auto;" (click)="read.emit()">
<!-- Card Image -->
<div>
<i class="fa-solid fa-book text-center" aria-hidden="true"></i>
</div>
</span>
</div>
</div>
@if (entity.pagesRead < entity.pages && entity.pagesRead > 0) {
<div class="progress-banner series" ngbTooltip="{{(entity.pagesRead / entity.pages) * 100 | number:'1.0-1'}}%">
<ngb-progressbar type="primary" [value]="entity.pagesRead" [max]="entity.pages" [showValue]="true"></ngb-progressbar>
</div>
@if (continueTitle !== '') {
<div class="under-image">
<div class="continue-from">
{{t('continue-from', {title: continueTitle})}}
</div>
</div>
}
}
</ng-container>

View file

@ -0,0 +1,139 @@
.overlay-information {
position: relative;
top: -364px;
height: 364px;
transition: all 0.2s;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
&:hover {
cursor: pointer;
background-color: var(--card-overlay-hover-bg-color) !important;
.overlay-information--centered {
visibility: visible;
}
}
.overlay-information--centered {
position: absolute;
border-radius: 15px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 50px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 115;
visibility: hidden;
&:hover {
background-color: var(--primary-color) !important;
cursor: pointer;
}
div {
width: 60px;
height: 60px;
i {
font-size: 1.6rem;
line-height: 60px;
width: 100%;
}
}
}
}
.overlay-information {
position: absolute;
top: 0;
left: 12px;
width: calc(100% - 24px);
height: 100%;
transition: all 0.2s;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
&:hover {
background-color: var(--card-overlay-hover-bg-color);
cursor: pointer;
}
.overlay-information--centered {
position: absolute;
border-radius: 15px;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 50px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 115;
&:hover {
background-color: var(--primary-color) !important;
cursor: pointer;
}
}
}
.series {
.overlay-information--centered {
div {
height: 32px;
width: 32px;
i {
font-size: 1.4rem;
line-height: 32px;
}
}
}
}
::ng-deep .image-container app-image img {
border-radius: 4px 4px 0 0;
}
.progress {
border-radius: 0;
}
.progress-banner.series {
position: relative;
}
::ng-deep .progress-banner.series span {
position: absolute;
left: 50%;
transform: translate(-50%, -50%);
color: white;
top: 50%;
}
.under-image {
position: relative;
.continue-from {
background-color: var(--breadcrumb-bg-color);
color: white;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
text-align: center;
position: absolute;
width: 100%;
}
}
@media screen and (max-width: 991px) {
.overlay-information {
visibility: hidden;
.overlay-information--centered {
visibility: hidden !important;
}
}
.progress-banner {
display: none;
}
.under-image {
display: none;
}
}

View file

@ -0,0 +1,36 @@
import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core';
import {DecimalPipe, NgClass} from "@angular/common";
import {TranslocoDirective} from "@jsverse/transloco";
import {ImageComponent} from "../../shared/image/image.component";
import {NgbProgressbar, NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
import {IHasProgress} from "../../_models/common/i-has-progress";
/**
* Used for the Series/Volume/Chapter Detail pages
*/
@Component({
selector: 'app-cover-image',
standalone: true,
imports: [
NgClass,
TranslocoDirective,
ImageComponent,
NgbProgressbar,
DecimalPipe,
NgbTooltip
],
templateUrl: './cover-image.component.html',
styleUrl: './cover-image.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CoverImageComponent {
@Input({required: true}) coverImage!: string;
@Input({required: true}) entity!: IHasProgress;
@Input() continueTitle: string = '';
@Output() read = new EventEmitter();
mobileSeriesImgBackground = getComputedStyle(document.documentElement)
.getPropertyValue('--mobile-series-img-background').trim();
}

View file

@ -1,23 +1,21 @@
<ng-container *transloco="let t; read: 'details-tab'">
<div class="details pb-3">
<div class="mb-3">
<app-carousel-reel [items]="genres" [title]="t('genres-title')">
<ng-template #carouselItem let-item>
<app-tag-badge (click)="openGeneric(FilterField.Genres, item.id)" [selectionMode]="TagBadgeCursor.Clickable">
{{item.title}}
</app-tag-badge>
<h4 class="header">{{t('genres-title')}}</h4>
<app-badge-expander [includeComma]="true" [items]="genres">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Genres, item.id)">{{item.title}}</a>
</ng-template>
</app-carousel-reel>
</app-badge-expander>
</div>
<div class="mb-3">
<app-carousel-reel [items]="tags" [title]="t('tags-title')">
<ng-template #carouselItem let-item>
<app-tag-badge (click)="openGeneric(FilterField.Tags, item.id)" [selectionMode]="TagBadgeCursor.Clickable">
{{item.title}}
</app-tag-badge>
<h4 class="header">{{t('tags-title')}}</h4>
<app-badge-expander [includeComma]="true" [items]="tags">
<ng-template #badgeExpanderItem let-item let-position="idx" let-last="last">
<a href="javascript:void(0)" class="dark-exempt btn-icon" (click)="openGeneric(FilterField.Tags, item.id)">{{item.title}}</a>
</ng-template>
</app-carousel-reel>
</app-badge-expander>
</div>
<div class="mb-3">

View file

@ -14,18 +14,20 @@ import {TagBadgeComponent, TagBadgeCursor} from "../../shared/tag-badge/tag-badg
import {ImageComponent} from "../../shared/image/image.component";
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {ImageService} from "../../_services/image.service";
import {BadgeExpanderComponent} from "../../shared/badge-expander/badge-expander.component";
@Component({
selector: 'app-details-tab',
standalone: true,
imports: [
CarouselReelComponent,
PersonBadgeComponent,
TranslocoDirective,
TagBadgeComponent,
ImageComponent,
SafeHtmlPipe
],
imports: [
CarouselReelComponent,
PersonBadgeComponent,
TranslocoDirective,
TagBadgeComponent,
ImageComponent,
SafeHtmlPipe,
BadgeExpanderComponent
],
templateUrl: './details-tab.component.html',
styleUrl: './details-tab.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush

View file

@ -553,15 +553,17 @@
</div>
}
<app-setting-item [title]="t('files-label')" [toggleOnViewClick]="false" [showEdit]="false">
<ng-template #view>
@for (file of chapter.files; track file.id) {
<div>
<span>{{file.filePath}}</span><span class="ms-2 me-2"></span><span>{{file.bytes | bytes}}</span>
</div>
}
</ng-template>
</app-setting-item>
@if (accountService.isAdmin$ | async) {
<app-setting-item [title]="t('files-label')" [toggleOnViewClick]="false" [showEdit]="false">
<ng-template #view>
@for (file of chapter.files; track file.id) {
<div>
<span>{{file.filePath}}</span><span class="ms-2 me-2"></span><span>{{file.bytes | bytes}}</span>
</div>
}
</ng-template>
</app-setting-item>
}
</ng-template>
</li>

View file

@ -28,7 +28,6 @@ import {Language} from "../../_models/metadata/language";
import {Person, PersonRole} from "../../_models/metadata/person";
import {Genre} from "../../_models/metadata/genre";
import {AgeRatingDto} from "../../_models/metadata/age-rating-dto";
import {SeriesService} from "../../_services/series.service";
import {ImageService} from "../../_services/image.service";
import {UploadService} from "../../_services/upload.service";
import {MetadataService} from "../../_services/metadata.service";

View file

@ -88,16 +88,17 @@
</div>
</div>
<app-setting-item [title]="t('files-label')" [toggleOnViewClick]="false" [showEdit]="false">
<ng-template #view>
@for (file of files; track file.id) {
<div>
<span>{{file.filePath}}</span><span class="ms-2 me-2"></span><span>{{file.bytes | bytes}}</span>
</div>
}
</ng-template>
</app-setting-item>
@if (accountService.isAdmin$ | async) {
<app-setting-item [title]="t('files-label')" [toggleOnViewClick]="false" [showEdit]="false">
<ng-template #view>
@for (file of files; track file.id) {
<div>
<span>{{file.filePath}}</span><span class="ms-2 me-2"></span><span>{{file.bytes | bytes}}</span>
</div>
}
</ng-template>
</app-setting-item>
}
</ng-template>
</li>

View file

@ -7,7 +7,7 @@ import {
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import {CommonModule, DOCUMENT, NgOptimizedImage} from '@angular/common';
import {DOCUMENT, NgOptimizedImage} from '@angular/common';
import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
import {ReactiveFormsModule} from "@angular/forms";
import {UserReview} from "../review-card/user-review";
@ -20,7 +20,7 @@ import {ProviderImagePipe} from "../../_pipes/provider-image.pipe";
@Component({
selector: 'app-review-card-modal',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, SpoilerComponent, SafeHtmlPipe, TranslocoDirective, DefaultValuePipe, NgOptimizedImage, ProviderImagePipe],
imports: [ReactiveFormsModule, SpoilerComponent, SafeHtmlPipe, TranslocoDirective, DefaultValuePipe, NgOptimizedImage, ProviderImagePipe],
templateUrl: './review-card-modal.component.html',
styleUrls: ['./review-card-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,

View file

@ -8,13 +8,12 @@ import {
OnInit,
Output
} from '@angular/core';
import {CommonModule, NgOptimizedImage} from '@angular/common';
import {NgOptimizedImage} from '@angular/common';
import {UserReview} from "./user-review";
import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
import {ReviewCardModalComponent} from "../review-card-modal/review-card-modal.component";
import {AccountService} from "../../_services/account.service";
import {
ReviewSeriesModalCloseAction,
ReviewSeriesModalCloseEvent,
ReviewSeriesModalComponent
} from "../review-series-modal/review-series-modal.component";

View file

@ -13,14 +13,16 @@
<textarea id="review" class="form-control" formControlName="reviewBody" rows="3" [minlength]="minLength"
[class.is-invalid]="reviewGroup.get('reviewBody')?.invalid && reviewGroup.get('reviewBody')?.touched" aria-describedby="body-validations"
></textarea>
<div id="body-validations" class="invalid-feedback" *ngIf="reviewGroup.dirty || reviewGroup.touched">
@if (reviewGroup.get('reviewBody')?.errors?.required) {
<div>{{t('required')}}</div>
}
@if (reviewGroup.get('reviewBody')?.errors?.minlength) {
<div>{{t('min-length', {count: minLength})}}</div>
}
</div>
@if (reviewGroup.dirty || reviewGroup.touched) {
<div id="body-validations" class="invalid-feedback">
@if (reviewGroup.get('reviewBody')?.errors?.required) {
<div>{{t('required')}}</div>
}
@if (reviewGroup.get('reviewBody')?.errors?.minlength) {
<div>{{t('min-length', {count: minLength})}}</div>
}
</div>
}
</div>
</form>

View file

@ -2,7 +2,6 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
inject,
Input,
OnInit
@ -11,7 +10,6 @@ import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/
import {NgbActiveModal, NgbRating} from '@ng-bootstrap/ng-bootstrap';
import { SeriesService } from 'src/app/_services/series.service';
import {UserReview} from "../review-card/user-review";
import {CommonModule} from "@angular/common";
import {translate, TranslocoDirective} from "@jsverse/transloco";
import {ConfirmService} from "../../shared/confirm.service";
import {ToastrService} from "ngx-toastr";
@ -31,7 +29,7 @@ export interface ReviewSeriesModalCloseEvent {
@Component({
selector: 'app-review-series-modal',
standalone: true,
imports: [CommonModule, NgbRating, ReactiveFormsModule, TranslocoDirective],
imports: [NgbRating, ReactiveFormsModule, TranslocoDirective],
templateUrl: './review-series-modal.component.html',
styleUrls: ['./review-series-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush

View file

@ -22,6 +22,6 @@ a.read-more-link {
}
.offcanvas-body {
mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%);
-webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 95%, transparent 100%);
mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%);
-webkit-mask-image: linear-gradient(to bottom, transparent, black 0%, black 97%, transparent 100%);
}

View file

@ -1,8 +1,9 @@
<ng-container *transloco="let t; read:'spoiler'">
<div (click)="toggle()" [attr.aria-expanded]="!isCollapsed" class="btn spoiler" tabindex="0">
<span *ngIf="isCollapsed; else show">{{t('click-to-show')}}</span>
<ng-template #show>
@if (isCollapsed) {
<span>{{t('click-to-show')}}</span>
} @else {
<div [innerHTML]="html | safeHtml"></div>
</ng-template>
}
</div>
</ng-container>

View file

@ -7,14 +7,13 @@ import {
OnInit,
ViewEncapsulation
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {SafeHtmlPipe} from "../../_pipes/safe-html.pipe";
import {TranslocoDirective} from "@jsverse/transloco";
@Component({
selector: 'app-spoiler',
standalone: true,
imports: [CommonModule, SafeHtmlPipe, TranslocoDirective],
imports: [SafeHtmlPipe, TranslocoDirective],
templateUrl: './spoiler.component.html',
styleUrls: ['./spoiler.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,

View file

@ -1,5 +1,4 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.service";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@ -21,7 +20,7 @@ import {LooseLeafOrDefaultNumber, SpecialVolumeNumber} from "../../_models/chapt
@Component({
selector: 'app-user-scrobble-history',
standalone: true,
imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule,
imports: [ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule,
DefaultValuePipe, TranslocoLocaleModule, UtcToLocalTimePipe, NgbTooltip],
templateUrl: './user-scrobble-history.component.html',
styleUrls: ['./user-scrobble-history.component.scss'],