Last batch of bugfixes (#1262)

* Refactored code to show action bar instead of drawer in immersive mode

* Card grid

* adding margin for pagination gap

* Fixed a rare routing case that wouldn't redirect

* Fixed a bug where series detail would show blank filtering

* Fixing image scaling and library card spacing

* Refactored some methods to be static

* Adding card grid to series detail

* Fixed a bug with webtoon going to non-webtoon mode, resulting in black screen.

* Ensure emails are trimmed when trying to invite.

* Don't show More In if there is only 1 item in there on library recommended tab

* Fixed some bugs around locking metadata fields where the correct param wasn't being sent to backend.

* Added some UI error messaging when the email doesn't match the confirm-email (or rather any email in the system).

* Fixed some pages where actions weren't working (library detail) and removed some actionable buttons where they didn't make sense

* Refactored the series detail to use Robbie's new grid system.

* some styling fixes

* Styling fixes

- Removing select border gap
- fixing switches on lite theme
- fixing search result text-light

* better css var naming

* changing search lite text color override

* fixing as per feedback

* Removing boolean from being visible in bookreader

* Fixed some bugs in bulk operations not being visible on light/eink screens. Added --bulk-selection-highlight-text-color and --bulk-selection-text-color.

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
Joseph Milazzo 2022-05-18 19:31:49 -05:00 committed by GitHub
parent 6f23a3bc6d
commit 1961b41268
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 195 additions and 98 deletions

View File

@ -770,7 +770,7 @@ public class SeriesServiceTests
{ {
SeriesId = 1, SeriesId = 1,
Publishers = new List<PersonDto>() {new () {Id = 0, Name = "Existing Person", Role = PersonRole.Publisher}}, Publishers = new List<PersonDto>() {new () {Id = 0, Name = "Existing Person", Role = PersonRole.Publisher}},
PublisherLocked = true PublishersLocked = true
}, },
CollectionTags = new List<CollectionTagDto>() CollectionTags = new List<CollectionTagDto>()
}); });

View File

@ -353,6 +353,7 @@ namespace API.Controllers
_logger.LogInformation("{User} is inviting {Email} to the server", adminUser.UserName, dto.Email); _logger.LogInformation("{User} is inviting {Email} to the server", adminUser.UserName, dto.Email);
// Check if there is an existing invite // Check if there is an existing invite
dto.Email = dto.Email.Trim();
var emailValidationErrors = await _accountService.ValidateEmail(dto.Email); var emailValidationErrors = await _accountService.ValidateEmail(dto.Email);
if (emailValidationErrors.Any()) if (emailValidationErrors.Any())
{ {
@ -454,6 +455,11 @@ namespace API.Controllers
{ {
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email); var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (user == null)
{
return BadRequest("The email does not match the registered email");
}
// Validate Password and Username // Validate Password and Username
var validationErrors = new List<ApiException>(); var validationErrors = new List<ApiException>();
validationErrors.AddRange(await _accountService.ValidateUsername(dto.Username)); validationErrors.AddRange(await _accountService.ValidateUsername(dto.Username));

View File

@ -6,7 +6,7 @@ namespace API.DTOs.Account;
public class InviteUserDto public class InviteUserDto
{ {
[Required] [Required]
public string Email { get; init; } public string Email { get; set; }
/// <summary> /// <summary>
/// List of Roles to assign to user. If admin not present, Pleb will be applied. /// List of Roles to assign to user. If admin not present, Pleb will be applied.
/// If admin present, all libraries will be granted access and will ignore those from DTO. /// If admin present, all libraries will be granted access and will ignore those from DTO.

View File

@ -69,16 +69,16 @@ namespace API.DTOs
public bool PublicationStatusLocked { get; set; } public bool PublicationStatusLocked { get; set; }
public bool GenresLocked { get; set; } public bool GenresLocked { get; set; }
public bool TagsLocked { get; set; } public bool TagsLocked { get; set; }
public bool WriterLocked { get; set; } public bool WritersLocked { get; set; }
public bool CharacterLocked { get; set; } public bool CharactersLocked { get; set; }
public bool ColoristLocked { get; set; } public bool ColoristsLocked { get; set; }
public bool EditorLocked { get; set; } public bool EditorsLocked { get; set; }
public bool InkerLocked { get; set; } public bool InkersLocked { get; set; }
public bool LettererLocked { get; set; } public bool LetterersLocked { get; set; }
public bool PencillerLocked { get; set; } public bool PencillersLocked { get; set; }
public bool PublisherLocked { get; set; } public bool PublishersLocked { get; set; }
public bool TranslatorLocked { get; set; } public bool TranslatorsLocked { get; set; }
public bool CoverArtistLocked { get; set; } public bool CoverArtistsLocked { get; set; }
public int SeriesId { get; set; } public int SeriesId { get; set; }

View File

@ -81,7 +81,7 @@ namespace API.Data
} }
void OnEntityTracked(object sender, EntityTrackedEventArgs e) static void OnEntityTracked(object sender, EntityTrackedEventArgs e)
{ {
if (!e.FromQuery && e.Entry.State == EntityState.Added && e.Entry.Entity is IEntityDate entity) if (!e.FromQuery && e.Entry.State == EntityState.Added && e.Entry.Entity is IEntityDate entity)
{ {
@ -91,7 +91,7 @@ namespace API.Data
} }
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e) static void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{ {
if (e.NewState == EntityState.Modified && e.Entry.Entity is IEntityDate entity) if (e.NewState == EntityState.Modified && e.Entry.Entity is IEntityDate entity)
entity.LastModified = DateTime.Now; entity.LastModified = DateTime.Now;

View File

@ -156,16 +156,16 @@ public class SeriesService : ISeriesService
series.Metadata.LanguageLocked = updateSeriesMetadataDto.SeriesMetadata.LanguageLocked; series.Metadata.LanguageLocked = updateSeriesMetadataDto.SeriesMetadata.LanguageLocked;
series.Metadata.GenresLocked = updateSeriesMetadataDto.SeriesMetadata.GenresLocked; series.Metadata.GenresLocked = updateSeriesMetadataDto.SeriesMetadata.GenresLocked;
series.Metadata.TagsLocked = updateSeriesMetadataDto.SeriesMetadata.TagsLocked; series.Metadata.TagsLocked = updateSeriesMetadataDto.SeriesMetadata.TagsLocked;
series.Metadata.CharacterLocked = updateSeriesMetadataDto.SeriesMetadata.CharacterLocked; series.Metadata.CharacterLocked = updateSeriesMetadataDto.SeriesMetadata.CharactersLocked;
series.Metadata.ColoristLocked = updateSeriesMetadataDto.SeriesMetadata.ColoristLocked; series.Metadata.ColoristLocked = updateSeriesMetadataDto.SeriesMetadata.ColoristsLocked;
series.Metadata.EditorLocked = updateSeriesMetadataDto.SeriesMetadata.EditorLocked; series.Metadata.EditorLocked = updateSeriesMetadataDto.SeriesMetadata.EditorsLocked;
series.Metadata.InkerLocked = updateSeriesMetadataDto.SeriesMetadata.InkerLocked; series.Metadata.InkerLocked = updateSeriesMetadataDto.SeriesMetadata.InkersLocked;
series.Metadata.LettererLocked = updateSeriesMetadataDto.SeriesMetadata.LettererLocked; series.Metadata.LettererLocked = updateSeriesMetadataDto.SeriesMetadata.LetterersLocked;
series.Metadata.PencillerLocked = updateSeriesMetadataDto.SeriesMetadata.PencillerLocked; series.Metadata.PencillerLocked = updateSeriesMetadataDto.SeriesMetadata.PencillersLocked;
series.Metadata.PublisherLocked = updateSeriesMetadataDto.SeriesMetadata.PublisherLocked; series.Metadata.PublisherLocked = updateSeriesMetadataDto.SeriesMetadata.PublishersLocked;
series.Metadata.TranslatorLocked = updateSeriesMetadataDto.SeriesMetadata.TranslatorLocked; series.Metadata.TranslatorLocked = updateSeriesMetadataDto.SeriesMetadata.TranslatorsLocked;
series.Metadata.CoverArtistLocked = updateSeriesMetadataDto.SeriesMetadata.CoverArtistLocked; series.Metadata.CoverArtistLocked = updateSeriesMetadataDto.SeriesMetadata.CoverArtistsLocked;
series.Metadata.WriterLocked = updateSeriesMetadataDto.SeriesMetadata.WriterLocked; series.Metadata.WriterLocked = updateSeriesMetadataDto.SeriesMetadata.WritersLocked;
series.Metadata.SummaryLocked = updateSeriesMetadataDto.SeriesMetadata.SummaryLocked; series.Metadata.SummaryLocked = updateSeriesMetadataDto.SeriesMetadata.SummaryLocked;
if (!_unitOfWork.HasChanges()) if (!_unitOfWork.HasChanges())

View File

@ -35,7 +35,7 @@ export interface SeriesMetadata {
tagsLocked: boolean; tagsLocked: boolean;
writersLocked: boolean; writersLocked: boolean;
coverArtistsLocked: boolean; coverArtistsLocked: boolean;
publisherLocked: boolean; publishersLocked: boolean;
charactersLocked: boolean; charactersLocked: boolean;
pencillersLocked: boolean; pencillersLocked: boolean;
inkersLocked: boolean; inkersLocked: boolean;

View File

@ -42,7 +42,7 @@ export class InviteUserComponent implements OnInit {
invite() { invite() {
this.isSending = true; this.isSending = true;
const email = this.inviteForm.get('email')?.value; const email = this.inviteForm.get('email')?.value.trim();
this.accountService.inviteUser({ this.accountService.inviteUser({
email, email,
libraries: this.selectedLibraries, libraries: this.selectedLibraries,

View File

@ -78,6 +78,7 @@ const routes: Routes = [
}, },
{path: 'login', loadChildren: () => import('../app/registration/registration.module').then(m => m.RegistrationModule)}, {path: 'login', loadChildren: () => import('../app/registration/registration.module').then(m => m.RegistrationModule)},
{path: '**', pathMatch: 'full', redirectTo: 'libraries'}, {path: '**', pathMatch: 'full', redirectTo: 'libraries'},
{path: '', pathMatch: 'full', redirectTo: 'libraries'},
]; ];
@NgModule({ @NgModule({

View File

@ -54,7 +54,7 @@
(fullscreen)="toggleFullscreen()" (fullscreen)="toggleFullscreen()"
(layoutModeUpdate)="updateLayoutMode($event)" (layoutModeUpdate)="updateLayoutMode($event)"
(readingDirection)="updateReadingDirection($event)" (readingDirection)="updateReadingDirection($event)"
(immersiveMode)="immersiveMode = $event" (immersiveMode)="updateImmersiveMode($event)"
></app-reader-settings> ></app-reader-settings>
</ng-template> </ng-template>
</li> </li>
@ -96,7 +96,7 @@
</div> </div>
<ng-template #actionBar> <ng-template #actionBar>
<div class="action-bar row g-0 justify-content-between" *ngIf="!immersiveMode || drawerOpen"> <div class="action-bar row g-0 justify-content-between" *ngIf="!immersiveMode || drawerOpen || actionBarVisible">
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)" <button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsPrevDisabled : IsNextDisabled" [disabled]="readingDirection === ReadingDirection.LeftToRight ? IsPrevDisabled : IsNextDisabled"
title="{{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}} Page"> title="{{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}} Page">

View File

@ -124,6 +124,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* Belongs to drawer component * Belongs to drawer component
*/ */
drawerOpen = false; drawerOpen = false;
/**
* If the action bar is visible
*/
actionBarVisible = true;
/** /**
* Book reader setting that hides the menuing system * Book reader setting that hides the menuing system
*/ */
@ -1105,6 +1109,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
toggleDrawer() { toggleDrawer() {
this.drawerOpen = !this.drawerOpen; this.drawerOpen = !this.drawerOpen;
if (this.immersiveMode) {
this.actionBarVisible = false;
}
} }
scrollTo(partSelector: string) { scrollTo(partSelector: string) {
@ -1197,6 +1205,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateImagesWithHeight(); this.updateImagesWithHeight();
// Calulate if bottom actionbar is needed. On a timeout to get accurate heights // Calulate if bottom actionbar is needed. On a timeout to get accurate heights
if (this.readingHtml == null) {
setTimeout(() => this.updateLayoutMode(this.layoutMode), 10);
return;
}
setTimeout(() => {this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.reader.nativeElement.clientHeight;}); setTimeout(() => {this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.reader.nativeElement.clientHeight;});
} }
@ -1204,6 +1216,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.readingDirection = readingDirection; this.readingDirection = readingDirection;
} }
updateImmersiveMode(immersiveMode: boolean) {
this.immersiveMode = immersiveMode;
if (this.immersiveMode && !this.drawerOpen) {
this.actionBarVisible = false;
}
}
// Table of Contents // Table of Contents
cleanIdSelector(id: string) { cleanIdSelector(id: string) {
const tokens = id.split('/'); const tokens = id.split('/');
@ -1299,7 +1318,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
Math.abs(this.mousePosition.x - event.screenX) <= mouseOffset && Math.abs(this.mousePosition.x - event.screenX) <= mouseOffset &&
Math.abs(this.mousePosition.y - event.screenY) <= mouseOffset Math.abs(this.mousePosition.y - event.screenY) <= mouseOffset
) { ) {
this.drawerOpen = true; this.actionBarVisible = !this.actionBarVisible;
} }
} }

View File

@ -216,6 +216,7 @@ export class ReaderSettingsComponent implements OnInit, OnDestroy {
this.readingDirection.emit(this.readingDirectionModel); this.readingDirection.emit(this.readingDirectionModel);
this.clickToPaginateChanged.emit(this.user.preferences.bookReaderTapToPaginate); this.clickToPaginateChanged.emit(this.user.preferences.bookReaderTapToPaginate);
this.layoutModeUpdate.emit(this.user.preferences.bookReaderLayoutMode); this.layoutModeUpdate.emit(this.user.preferences.bookReaderLayoutMode);
this.immersiveMode.emit(this.user.preferences.bookReaderImmersiveMode);
this.resetSettings(); this.resetSettings();
} else { } else {

View File

@ -192,8 +192,8 @@
<div class="mb-3"> <div class="mb-3">
<label for="publisher" class="form-label">Publisher</label> <label for="publisher" class="form-label">Publisher</label>
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)" <app-typeahead (selectedData)="updatePerson($event, PersonRole.Publisher)" [settings]="getPersonsSettings(PersonRole.Publisher)"
[(locked)]="metadata.publisherLocked" (onUnlock)="metadata.publisherLocked = false" [(locked)]="metadata.publishersLocked" (onUnlock)="metadata.publishersLocked = false"
(newItemAdded)="metadata.publisherLocked = true" (selectedData)="metadata.publisherLocked = true"> (newItemAdded)="metadata.publishersLocked = true" (selectedData)="metadata.publishersLocked = true">
<ng-template #badgeItem let-item let-position="idx"> <ng-template #badgeItem let-item let-position="idx">
{{item.name}} {{item.name}}
</ng-template> </ng-template>

View File

@ -1,9 +1,17 @@
.bulk-select { .bulk-select {
background-color: var(--navbar-bg-color); background-color: var(--navbar-bg-color);
border-bottom: 2px solid var(--primary-color); border-bottom: 2px solid var(--primary-color);
color: var(--navbar-text-color); color: var(--bulk-selection-text-color) !important;
.btn-icon {
color: var(--bulk-selection-text-color);
}
} }
.highlight { .highlight {
color: var(--primary-color) !important; color: var(--bulk-selection-highlight-text-color) !important;
}
::ng-deep button i.fa {
color: var(--bulk-selection-text-color);
} }

View File

@ -35,8 +35,8 @@
<ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'bottom' }"></ng-container> <ng-container [ngTemplateOutlet]="paginationTemplate" [ngTemplateOutletContext]="{ id: 'bottom' }"></ng-container>
<ng-template #cardTemplate> <ng-template #cardTemplate>
<div class="row justify-content-evenly justify-content-sm-start g-0 mb-3"> <div class="card-container row g-0 mt-3 mb-3">
<div class="col-auto ps-1 pe-1 mt-1 mb-1" *ngFor="let item of items; trackBy:trackByIdentity; index as i"> <div class="card col-auto mt-2 mb-2" *ngFor="let item of items; trackBy:trackByIdentity; index as i">
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container> <ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, idx: i }"></ng-container>
</div> </div>

View File

@ -0,0 +1,6 @@
.card-container {
display: grid;
grid-template-columns: repeat(auto-fill, 158px);
grid-gap: 0.5rem;
justify-content: space-around;
}

View File

@ -4,7 +4,7 @@ import { FilterSettings } from 'src/app/metadata-filter/filter-settings';
import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service'; import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service';
import { Library } from 'src/app/_models/library'; import { Library } from 'src/app/_models/library';
import { Pagination } from 'src/app/_models/pagination'; import { Pagination } from 'src/app/_models/pagination';
import { FilterEvent, FilterItem, SeriesFilter, SortField } from 'src/app/_models/series-filter'; import { FilterEvent, FilterItem, SeriesFilter } from 'src/app/_models/series-filter';
import { ActionItem } from 'src/app/_services/action-factory.service'; import { ActionItem } from 'src/app/_services/action-factory.service';
import { SeriesService } from 'src/app/_services/series.service'; import { SeriesService } from 'src/app/_services/series.service';

View File

@ -25,9 +25,8 @@ $image-width: 160px;
padding-right: 0px; padding-right: 0px;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
background-color: var(--card-bg-color);
color: var(--card-text-color); color: var(--card-text-color);
border-color: var(--card-border-color); border: 1px var(--card-border-color);
} }
@ -150,6 +149,11 @@ $image-width: 160px;
.card-body { .card-body {
padding: 5px !important; padding: 5px !important;
background-color: var(--card-bg-color);
border-width: var(--card-border-width);
border-style: var(--card-border-style);
border-color: var(--card-border-color);
border-radius: 0.25em;
} }
.library { .library {

View File

@ -1,9 +1,8 @@
<app-side-nav-companion-bar [hasFilter]="false" (filterOpen)="filterOpen.emit($event)"> <app-side-nav-companion-bar [hasFilter]="false" (filterOpen)="filterOpen.emit($event)">
<h2 title> <h2 title>
<app-card-actionables [actions]="collectionTagActions"></app-card-actionables>
Collections Collections
</h2> </h2>
<h6 subtitle style="margin-left:40px;">{{collections.length}} Items</h6> <h6 subtitle>{{collections.length}} Items</h6>
</app-side-nav-companion-bar> </app-side-nav-companion-bar>
<app-card-detail-layout <app-card-detail-layout
[isLoading]="isLoading" [isLoading]="isLoading"

View File

@ -1,6 +1,6 @@
<app-side-nav-companion-bar [hasFilter]="true" [filterOpenByDefault]="filterSettings.openByDefault" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive"> <app-side-nav-companion-bar [hasFilter]="true" [filterOpenByDefault]="filterSettings.openByDefault" (filterOpen)="filterOpen.emit($event)" [filterActive]="filterActive">
<h2 title> <h2 title>
<app-card-actionables [actions]="actions"></app-card-actionables> <app-card-actionables [actions]="actions" (actionHandler)="performAction($event)"></app-card-actionables>
{{libraryName}} {{libraryName}}
</h2> </h2>
<h6 subtitle style="margin-left:40px;" *ngIf="active.fragment === ''">{{pagination?.totalItems}} Series</h6> <h6 subtitle style="margin-left:40px;" *ngIf="active.fragment === ''">{{pagination?.totalItems}} Series</h6>

View File

@ -3,7 +3,6 @@
width: 200; width: 200;
} }
.viewport { .viewport {
width: 600px; width: 600px;
height: 100%; height: 100%;

View File

@ -146,8 +146,10 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
if (library === undefined) { if (library === undefined) {
lib = {id: this.libraryId, name: this.libraryName}; lib = {id: this.libraryId, name: this.libraryName};
} }
console.log('lib: ', lib);
switch (action) { switch (action) {
case(Action.ScanLibrary): case(Action.ScanLibrary):
console.log('action handler');
this.actionService.scanLibrary(lib); this.actionService.scanLibrary(lib);
break; break;
case(Action.RefreshMetadata): case(Action.RefreshMetadata):
@ -158,6 +160,12 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
} }
} }
performAction(action: ActionItem<any>) {
if (typeof action.callback === 'function') {
action.callback(action.action, undefined);
}
}
updateFilter(data: FilterEvent) { updateFilter(data: FilterEvent) {
this.filter = data.filter; this.filter = data.filter;

View File

@ -38,10 +38,12 @@
<ng-container *ngIf="genre$ | async as genre"> <ng-container *ngIf="genre$ | async as genre">
<ng-container *ngIf="moreIn$ | async as moreIn"> <ng-container *ngIf="moreIn$ | async as moreIn">
<ng-container *ngIf="moreIn.length > 1">
<app-carousel-reel [items]="moreIn" title="More In {{genre.title}}"> <app-carousel-reel [items]="moreIn" title="More In {{genre.title}}">
<ng-template #carouselItem let-item let-position="idx"> <ng-template #carouselItem let-item let-position="idx">
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card> <app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card>
</ng-template> </ng-template>
</app-carousel-reel> </app-carousel-reel>
</ng-container> </ng-container>
</ng-container>
</ng-container> </ng-container>

View File

@ -41,13 +41,13 @@
<div class="pagination-area"> <div class="pagination-area">
<!-- Pagination controls and screen hints--> <!-- Pagination controls and screen hints-->
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? WindowHeight: 25 + '%')}"> <div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, 'left')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: 25 + '%')}">
<div *ngIf="showClickOverlay"> <div *ngIf="showClickOverlay">
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}" <i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
title="Previous Page" aria-hidden="true"></i> title="Previous Page" aria-hidden="true"></i>
</div> </div>
</div> </div>
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? WindowHeight: 25 + '%')}"> <div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, 'right')" [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? ImageHeight: 25 + '%')}">
<div *ngIf="showClickOverlay"> <div *ngIf="showClickOverlay">
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}" <i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
title="Next Page" aria-hidden="true"></i> title="Next Page" aria-hidden="true"></i>
@ -60,7 +60,7 @@
'fit-to-height-double-offset': this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.HEIGHT && ShouldRenderDoublePage, 'fit-to-height-double-offset': this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.HEIGHT && ShouldRenderDoublePage,
'original-double-offset' : this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage, 'original-double-offset' : this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.ORIGINAL && ShouldRenderDoublePage,
'reverse': ShouldRenderReverseDouble}"> 'reverse': ShouldRenderReverseDouble}">
<img [src]="canvasImage.src" id="image-1" <img #image [src]="canvasImage.src" id="image-1"
class="{{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}"> class="{{getFittingOptionClass()}} {{readerMode === ReaderMode.LeftRight || readerMode === ReaderMode.UpDown ? '' : 'd-none'}} {{showClickOverlay ? 'blur' : ''}}">
<ng-container *ngIf="ShouldRenderDoublePage && (this.pageNum + 1 <= maxPages - 1 && this.pageNum > 0)"> <ng-container *ngIf="ShouldRenderDoublePage && (this.pageNum + 1 <= maxPages - 1 && this.pageNum > 0)">

View File

@ -118,6 +118,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('reader') reader!: ElementRef; @ViewChild('reader') reader!: ElementRef;
@ViewChild('readingArea') readingArea!: ElementRef; @ViewChild('readingArea') readingArea!: ElementRef;
@ViewChild('content') canvas: ElementRef | undefined; @ViewChild('content') canvas: ElementRef | undefined;
@ViewChild('image') image!: ElementRef;
private ctx!: CanvasRenderingContext2D; private ctx!: CanvasRenderingContext2D;
/** /**
* Used to render a page on the canvas or in the image tag. This Image element is prefetched by the cachedImages buffer * Used to render a page on the canvas or in the image tag. This Image element is prefetched by the cachedImages buffer
@ -286,6 +287,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return this.readingArea?.nativeElement.scrollHeight + 'px'; return this.readingArea?.nativeElement.scrollHeight + 'px';
} }
get ImageHeight() {
return this.image?.nativeElement.height + 'px';
}
get splitIconClass() { get splitIconClass() {
if (this.isSplitLeftToRight()) { if (this.isSplitLeftToRight()) {
@ -438,6 +442,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
this.ctx = this.canvas.nativeElement.getContext('2d', { alpha: false }); this.ctx = this.canvas.nativeElement.getContext('2d', { alpha: false });
this.canvasImage.onload = () => this.renderPage(); this.canvasImage.onload = () => this.renderPage();
this.getWindowDimensions();
} }
ngOnDestroy() { ngOnDestroy() {
@ -1050,6 +1055,8 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|| document.documentElement.clientHeight || document.documentElement.clientHeight
|| document.body.clientHeight; || document.body.clientHeight;
console.log(windowHeight);
const needsSplitting = this.isCoverImage(); const needsSplitting = this.isCoverImage();
let newScale = this.generalSettingsForm.get('fittingOption')?.value; let newScale = this.generalSettingsForm.get('fittingOption')?.value;
const widthRatio = windowWidth / (this.canvasImage.width / (needsSplitting ? 2 : 1)); const widthRatio = windowWidth / (this.canvasImage.width / (needsSplitting ? 2 : 1));
@ -1106,8 +1113,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
loadPage() { loadPage() {
if (!this.canvas || !this.ctx) { return; }
this.isLoading = true; this.isLoading = true;
this.canvasImage = this.cachedImages.current(); this.canvasImage = this.cachedImages.current();
@ -1128,6 +1133,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.renderPage(); this.renderPage();
} }
this.prefetch(); this.prefetch();
this.isLoading = false;
} }
setReadingDirection() { setReadingDirection() {
@ -1264,6 +1270,12 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
break; break;
} }
// We must set this here because loadPage from render doesn't call if we aren't page splitting
if (this.readerMode !== ReaderMode.Webtoon) {
this.canvasImage = this.cachedImages.current();
this.isLoading = true;
}
this.updateForm(); this.updateForm();
this.render(); this.render();

View File

@ -85,3 +85,7 @@
.scroll-to-top:hover { .scroll-to-top:hover {
animation: MoveUpDown 1s linear infinite; animation: MoveUpDown 1s linear infinite;
} }
.text-light {
color: var(--search-result-text-lite-color) !important;
}

View File

@ -1,6 +1,5 @@
<app-side-nav-companion-bar pageHeader="Home"> <app-side-nav-companion-bar pageHeader="Home">
<h2 title> <h2 title>
<app-card-actionables [actions]="actions"></app-card-actionables>
Reading Lists Reading Lists
</h2> </h2>
<h6 subtitle>{{pagination?.totalItems}} Items</h6> <h6 subtitle>{{pagination?.totalItems}} Items</h6>

View File

@ -72,14 +72,14 @@
<li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (volumes.length > 0 || chapters.length > 0)"> <li [ngbNavItem]="TabID.Storyline" *ngIf="libraryType !== LibraryType.Book && (volumes.length > 0 || chapters.length > 0)">
<a ngbNavLink>Storyline</a> <a ngbNavLink>Storyline</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<div class="row g-0"> <div class="card-container row g-0">
<ng-container *ngFor="let volume of volumes; let idx = index; trackBy: trackByVolumeIdentity"> <ng-container *ngFor="let volume of volumes; let idx = index; trackBy: trackByVolumeIdentity">
<app-card-item class="col-auto p-2" *ngIf="volume.number != 0" [entity]="volume" [title]="volume.name" (click)="openVolume(volume)" <app-card-item class="col-auto mt-2 mb-2" *ngIf="volume.number != 0" [entity]="volume" [title]="volume.name" (click)="openVolume(volume)"
[imageUrl]="imageService.getVolumeCoverImage(volume.id) + '&offset=' + coverImageOffset" [imageUrl]="imageService.getVolumeCoverImage(volume.id) + '&offset=' + coverImageOffset"
[read]="volume.pagesRead" [total]="volume.pages" [actions]="volumeActions" (selection)="bulkSelectionService.handleCardSelection('volume', idx, volumes.length, $event)" [selected]="bulkSelectionService.isCardSelected('volume', idx)" [allowSelection]="true"></app-card-item> [read]="volume.pagesRead" [total]="volume.pages" [actions]="volumeActions" (selection)="bulkSelectionService.handleCardSelection('volume', idx, volumes.length, $event)" [selected]="bulkSelectionService.isCardSelected('volume', idx)" [allowSelection]="true"></app-card-item>
</ng-container> </ng-container>
<ng-container *ngFor="let chapter of storyChapters; let idx = index; trackBy: trackByChapterIdentity"> <ng-container *ngFor="let chapter of storyChapters; let idx = index; trackBy: trackByChapterIdentity">
<app-card-item class="col-auto p-2" *ngIf="!chapter.isSpecial" [entity]="chapter" [title]="chapter.title" (click)="openChapter(chapter)" <app-card-item class="col-auto mt-2 mb-2" *ngIf="!chapter.isSpecial" [entity]="chapter" [title]="chapter.title" (click)="openChapter(chapter)"
[imageUrl]="imageService.getChapterCoverImage(chapter.id) + '&offset=' + coverImageOffset" [imageUrl]="imageService.getChapterCoverImage(chapter.id) + '&offset=' + coverImageOffset"
[read]="chapter.pagesRead" [total]="chapter.pages" [actions]="chapterActions" (selection)="bulkSelectionService.handleCardSelection('chapter', idx, storyChapters.length, $event)" [selected]="bulkSelectionService.isCardSelected('chapter', idx)" [allowSelection]="true"></app-card-item> [read]="chapter.pagesRead" [total]="chapter.pages" [actions]="chapterActions" (selection)="bulkSelectionService.handleCardSelection('chapter', idx, storyChapters.length, $event)" [selected]="bulkSelectionService.isCardSelected('chapter', idx)" [allowSelection]="true"></app-card-item>
</ng-container> </ng-container>
@ -89,51 +89,44 @@
<li [ngbNavItem]="TabID.Volumes" *ngIf="volumes.length > 0"> <li [ngbNavItem]="TabID.Volumes" *ngIf="volumes.length > 0">
<a ngbNavLink>{{libraryType === LibraryType.Book ? 'Books': 'Volumes'}}</a> <a ngbNavLink>{{libraryType === LibraryType.Book ? 'Books': 'Volumes'}}</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<div class="card-container row g-0">
<app-card-detail-layout <ng-container *ngFor="let item of volumes; let idx = index; trackBy: trackByVolumeIdentity">
[isLoading]="isLoading" <app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.name" (click)="openVolume(item)"
[items]="volumes">
<ng-template #cardItem let-item let-position="idx">
<app-card-item class="col-auto" [entity]="item" [title]="item.name" (click)="openVolume(item)"
[imageUrl]="imageService.getVolumeCoverImage(item.id) + '&offset=' + coverImageOffset" [imageUrl]="imageService.getVolumeCoverImage(item.id) + '&offset=' + coverImageOffset"
[read]="item.pagesRead" [total]="item.pages" [actions]="volumeActions" [read]="item.pagesRead" [total]="item.pages" [actions]="volumeActions"
(selection)="bulkSelectionService.handleCardSelection('volume', position, volumes.length, $event)" (selection)="bulkSelectionService.handleCardSelection('volume', idx, volumes.length, $event)"
[selected]="bulkSelectionService.isCardSelected('volume', position)" [allowSelection]="true"> [selected]="bulkSelectionService.isCardSelected('volume', idx)" [allowSelection]="true">
</app-card-item> </app-card-item>
</ng-template> </ng-container>
</app-card-detail-layout> </div>
</ng-template> </ng-template>
</li> </li>
<li [ngbNavItem]="TabID.Chapters" *ngIf="chapters.length > 0"> <li [ngbNavItem]="TabID.Chapters" *ngIf="chapters.length > 0">
<a ngbNavLink>{{utilityService.formatChapterName(libraryType) + 's'}}</a> <a ngbNavLink>{{utilityService.formatChapterName(libraryType) + 's'}}</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<app-card-detail-layout <div class="card-container row g-0">
[isLoading]="isLoading" <ng-container *ngFor="let item of chapters; let idx = index; trackBy: trackByChapterIdentity">
[items]="chapters"> <app-card-item class="col-auto mt-2 mb-2" *ngIf="!item.isSpecial" [entity]="item" [title]="item.title" (click)="openChapter(item)"
<ng-template #cardItem let-item let-position="idx">
<app-card-item class="col-auto" *ngIf="!item.isSpecial" [entity]="item" [title]="item.title" (click)="openChapter(item)"
[imageUrl]="imageService.getChapterCoverImage(item.id) + '&offset=' + coverImageOffset" [imageUrl]="imageService.getChapterCoverImage(item.id) + '&offset=' + coverImageOffset"
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions" [read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
(selection)="bulkSelectionService.handleCardSelection('chapter', position, chapters.length, $event)" (selection)="bulkSelectionService.handleCardSelection('chapter', idx, chapters.length, $event)"
[selected]="bulkSelectionService.isCardSelected('chapter', position)" [allowSelection]="true"></app-card-item> [selected]="bulkSelectionService.isCardSelected('chapter', idx)" [allowSelection]="true"></app-card-item>
</ng-template> </ng-container>
</app-card-detail-layout> </div>
</ng-template> </ng-template>
</li> </li>
<li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials"> <li [ngbNavItem]="TabID.Specials" *ngIf="hasSpecials">
<a ngbNavLink>Specials</a> <a ngbNavLink>Specials</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<app-card-detail-layout <div class="card-container row g-0">
[isLoading]="isLoading" <ng-container *ngFor="let item of specials; let idx = index; trackBy: trackByChapterIdentity">
[items]="specials"> <app-card-item class="col-auto mt-2 mb-2" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
<ng-template #cardItem let-item let-position="idx">
<app-card-item class="col-auto" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
[imageUrl]="imageService.getChapterCoverImage(item.id)" [imageUrl]="imageService.getChapterCoverImage(item.id)"
[read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions" [read]="item.pagesRead" [total]="item.pages" [actions]="chapterActions"
(selection)="bulkSelectionService.handleCardSelection('special', position, chapters.length, $event)" (selection)="bulkSelectionService.handleCardSelection('special', idx, chapters.length, $event)"
[selected]="bulkSelectionService.isCardSelected('special', position)" [allowSelection]="true"></app-card-item> [selected]="bulkSelectionService.isCardSelected('special', idx)" [allowSelection]="true"></app-card-item>
</ng-template> </ng-container>
</app-card-detail-layout> </div>
</ng-template> </ng-template>
</li> </li>
<li [ngbNavItem]="TabID.Related" *ngIf="hasRelations"> <li [ngbNavItem]="TabID.Related" *ngIf="hasRelations">

View File

@ -6,3 +6,10 @@
margin-top: 2px; margin-top: 2px;
font-size: 1.5rem; font-size: 1.5rem;
} }
.card-container{
display: grid;
grid-template-columns: repeat(auto-fill, 158px);
grid-gap: 0.5rem;
justify-content: space-around;
}

View File

@ -1,7 +1,7 @@
<form [formGroup]="typeaheadForm"> <form [formGroup]="typeaheadForm">
<div class="input-group {{hasFocus ? 'open': ''}} {{locked ? 'lock-active' : ''}}"> <div class="input-group {{hasFocus ? 'open': ''}} {{locked ? 'lock-active' : ''}}">
<ng-container *ngIf="settings.showLocked"> <ng-container *ngIf="settings.showLocked">
<span class="input-group-text clickable" (click)="unlock($event)"><i class="fa fa-lock" aria-hidden="true"></i> <span class="input-group-text clickable" (click)="toggleLock($event)"><i class="fa fa-lock" aria-hidden="true"></i>
<span class="visually-hidden">Field is locked</span> <span class="visually-hidden">Field is locked</span>
</span> </span>
</ng-container> </ng-container>

View File

@ -464,11 +464,14 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
} }
} }
unlock(event: any) { toggleLock(event: any) {
if (this.disabled) return; if (this.disabled) return;
this.locked = !this.locked; this.locked = !this.locked;
this.onUnlock.emit();
this.lockedChange.emit(this.locked); this.lockedChange.emit(this.locked);
if (!this.locked) {
this.onUnlock.emit();
}
} }
} }

View File

@ -36,7 +36,6 @@
@import './theme/components/progress'; @import './theme/components/progress';
@import './theme/components/sidenav'; @import './theme/components/sidenav';
@import './theme/components/carousel'; @import './theme/components/carousel';
@import './theme/components/progress';
@import './theme/utilities/utilities'; @import './theme/utilities/utilities';

View File

@ -3,6 +3,7 @@
color: var(--card-text-color); color: var(--card-text-color);
border-color: var(--card-border-color); border-color: var(--card-border-color);
position: relative; position: relative;
border: var(--card-border);
.card-overlay { .card-overlay {
background-color: var(--card-overlay-bg-color); background-color: var(--card-overlay-bg-color);

View File

@ -184,7 +184,9 @@
/* Card */ /* Card */
--card-bg-color: rgba(22,27,34,0.5); --card-bg-color: rgba(22,27,34,0.5);
--card-text-color: var(--body-text-color); --card-text-color: var(--body-text-color);
--card-border-color: rgba(239, 239, 239, 0.125); --card-border-width: 0 1px 1px 1px;
--card-border-style: solid;
--card-border-color: transparent;
--card-progress-bar-color: var(--primary-color); --card-progress-bar-color: var(--primary-color);
--card-overlay-bg-color: rgba(0, 0, 0, 0); --card-overlay-bg-color: rgba(0, 0, 0, 0);
--card-overlay-hover-bg-color: rgba(0, 0, 0, 0.2); --card-overlay-hover-bg-color: rgba(0, 0, 0, 0.2);
@ -223,4 +225,12 @@
--event-widget-text-color: var(--body-text-color); --event-widget-text-color: var(--body-text-color);
--event-widget-item-border-color: rgba(53, 53, 53, 0.5); --event-widget-item-border-color: rgba(53, 53, 53, 0.5);
--event-widget-border-color: rgba(1, 4, 9, 0.5); --event-widget-border-color: rgba(1, 4, 9, 0.5);
/* Search */
--search-result-text-lite-color: initial;
/* Bulk Selection */
--bulk-selection-text-color: var(--navbar-text-color);
--bulk-selection-highlight-text-color: var(--primary-color);
} }

View File

@ -19,7 +19,7 @@
/* Inputs */ /* Inputs */
--input-bg-color: #fff; --input-bg-color: #fff;
--input-focused-border-color: #ccc; --input-focused-border-color: #ccc;
--input-bg-readonly-color: unset; --input-bg-readonly-color: rgba(0,0,0,0.2);
--input-placeholder-color: #aeaeae; --input-placeholder-color: #aeaeae;
--input-border-color: #ccc; --input-border-color: #ccc;
--input-range-color: var(--primary-color); --input-range-color: var(--primary-color);
@ -113,6 +113,8 @@
/* Card */ /* Card */
--card-text-color: #000; --card-text-color: #000;
--card-border-width: 0 1px 1px 1px;
--card-border-style: solid;
--card-border-color: #ccc; --card-border-color: #ccc;
--card-progress-bar-color: var(--primary-color); --card-progress-bar-color: var(--primary-color);
--card-overlay-hover-bg-color: rgba(0, 0, 0, 0.2); --card-overlay-hover-bg-color: rgba(0, 0, 0, 0.2);
@ -178,4 +180,10 @@
--popover-bg-color: lightgrey; --popover-bg-color: lightgrey;
--popover-border-color: lightgrey; --popover-border-color: lightgrey;
/* Search */
--search-result-text-lite-color: rgba(0,0,0,1);
/* Bulk Selection */
--bulk-selection-text-color: white;
--bulk-selection-highlight-text-color: white;
} }

View File

@ -19,7 +19,7 @@
/* Inputs */ /* Inputs */
--input-bg-color: #fff; --input-bg-color: #fff;
--input-focused-border-color: #ccc; --input-focused-border-color: #ccc;
--input-bg-readonly-color: unset; --input-bg-readonly-color: rgba(0,0,0,0.2);
--input-placeholder-color: #aeaeae; --input-placeholder-color: #aeaeae;
--input-border-color: #ccc; --input-border-color: #ccc;
--input-range-color: var(--primary-color); --input-range-color: var(--primary-color);
@ -114,6 +114,8 @@
/* Card */ /* Card */
--card-text-color: #000; --card-text-color: #000;
--card-border-width: 0 1px 1px 1px;
--card-border-style: solid;
--card-border-color: #ccc; --card-border-color: #ccc;
--card-progress-bar-color: var(--primary-color); --card-progress-bar-color: var(--primary-color);
--card-overlay-bg-color: rgba(0, 0, 0, 0); --card-overlay-bg-color: rgba(0, 0, 0, 0);
@ -122,7 +124,7 @@
/* List items */ /* List items */
--list-group-item-text-color: var(--body-text-color); --list-group-item-text-color: var(--body-text-color);
--list-group-item-bg-color: white; --list-group-item-bg-color: white;
--list-group-hover-text-color: inherit; --list-group-hover-text-color: var(--body-text-color);
--list-group-hover-bg-color: #eaeaea; --list-group-hover-bg-color: #eaeaea;
--list-group-item-border-color: rgba(239, 239, 239, 0.125); --list-group-item-border-color: rgba(239, 239, 239, 0.125);
--list-group-active-border-color: none; --list-group-active-border-color: none;
@ -192,4 +194,10 @@
--accordion-button-focus-border-color: rgba(74, 198, 148, 0.9); --accordion-button-focus-border-color: rgba(74, 198, 148, 0.9);
--accordion-button-focus-box-shadow: unset; --accordion-button-focus-box-shadow: unset;
/* Search */
--search-result-text-lite-color: rgba(0,0,0,1);
/* Bulk Selection */
--bulk-selection-text-color: var(--navbar-text-color);
--bulk-selection-highlight-text-color: var(--primary-color);
} }