Change Detection: On Push aka UI Smoothness (#1369)
* Updated Series Info Cards to use OnPush and hooked in progress events when we do a mark as read/unread on entities. These events update progress bars but also will now trigger a re-calculation on Read Time Left. * Removed Library Card Component * Refactored manga reader title and subtitle calculation to the backend. * Coverted card actionables to onPush * Series Card on push cleanup * Updated edit collection tags for on push * Update cover image chooser for on push * Cleaned up carsouel reel * Updated cover image to allow for uploading gif and webp files * Bulk add to collection on push * Updated bulk operation to use on push. Updated bulk operation to have mark as unread and read buttons explicitly. Updated so add to collection is visible and delete. Fixed a bug where manage library component wasn't invoking the trackBy function * Updating entity title for on push * Removed file info component * Updated Mange Library for on push * Entity info cards on push * List item on push * Updated icon and title for on push and fixed some missing change detection on series detail * Restricted the typeahead interface to simplify the design * Edit Series Relation now shows a value in the dropdown for Parent relationships and disables the field. * Updated edit series relation to focus on new typeahead when adding a new relationship * Added some documentation and when Scanning a library, don't allow the user to enqueue the same job multiple times. * Applied the No-enqueue if already enqueued logic to other tasks * Library detail on push * Updated events widget to onpush * Card detail drawer on push. Card detail cover chooser now will show all chapter's covers for selection in cover chooser. * Chapter metadata detail on push * Removed Card Detail modal * All collections on push * Removed some comments * Updated bulk selection to use an observable rather than function calls so new on push strategy works * collection detail now uses on push and scroller is placed on correct element * Updated library recommended to on push. Ensure that when mark as read occurs, the appropriate streams are refreshed. * Updated library detail to on push * Update metadata fiter to onpush. Bugs found and reported to Project * person badge on push * Read more on push * Updated tag badge to on push * User login on push * When initing side nav, don't call an authenticated api until we are sure a user is logged in * Updated splash container to on push * Dashboard on push * Side nav slight refactor around some api calls * Cleaned up series card on push to use same cdRef naming convention * Updated Static Files to use caching * Added width and height to logo image * shortcuts modal on push * reading lists on push * Reading list detail on push * draggable ordered list on push * Refactored reading-list-detail to use a new item which drastically reduces renders on operations * series format on push * circular loader on push * Badge Expander on push * update notification modal on push * drawer on push * Edit Series Modal on push * reset password on push * review series modal on push * series metadata detail on push * theme manager on push * confirm reset password on push * register on push * confirm migration email on push * confirm email on push * add email to account migration on push * user preferences on push. Made global settings default open * edit series relation on push * Fixed an edge case bug for next chapter where if the current volume had a single chapter of 1 and the next volume had a chapter number of 0, it would say there are no more chapters. * Updated infinite scroller with on push support * Moved some animations over to typeahead, not integrated yet. * Manga reader is now on push * Reader settings on push * refactored how we close the book * Updated table of contents for on push * Updated book reader for on push. Fixed a bug where table of contents wasn't showing current page anchor due to a scroll calulation bug * Small code tweak * Icon and title on push * nav header on push * grouped typeahead on push * typeahead on push and added a new trackby identity function to allow even faster rendering of big lists * pdf reader on push * code cleanup
This commit is contained in:
parent
f5be0fac58
commit
4e49aa47ce
126 changed files with 1658 additions and 1674 deletions
|
|
@ -197,4 +197,35 @@ export class UtilityService {
|
|||
return [windowWidth, windowHeight];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data An array of objects
|
||||
* @param keySelector A method to fetch a string from the object, which is used to classify the JumpKey
|
||||
* @returns
|
||||
*/
|
||||
getJumpKeys(data :Array<any>, keySelector: (data: any) => string) {
|
||||
const keys: {[key: string]: number} = {};
|
||||
data.forEach(obj => {
|
||||
let ch = keySelector(obj).charAt(0);
|
||||
if (/\d|\#|!|%|@|\(|\)|\^|\*/g.test(ch)) {
|
||||
ch = '#';
|
||||
}
|
||||
if (!keys.hasOwnProperty(ch)) {
|
||||
keys[ch] = 0;
|
||||
}
|
||||
keys[ch] += 1;
|
||||
});
|
||||
return Object.keys(keys).map(k => {
|
||||
return {
|
||||
key: k,
|
||||
size: keys[k],
|
||||
title: k.toUpperCase()
|
||||
}
|
||||
}).sort((a, b) => {
|
||||
if (a.key < b.key) return -1;
|
||||
if (a.key > b.key) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-badge-expander',
|
||||
templateUrl: './badge-expander.component.html',
|
||||
styleUrls: ['./badge-expander.component.scss']
|
||||
styleUrls: ['./badge-expander.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class BadgeExpanderComponent implements OnInit {
|
||||
|
||||
|
|
@ -18,16 +19,18 @@ export class BadgeExpanderComponent implements OnInit {
|
|||
get itemsLeft() {
|
||||
return Math.max(this.items.length - this.itemsTillExpander, 0);
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
constructor(private readonly cdRef: ChangeDetectorRef) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.visibleItems = this.items.slice(0, this.itemsTillExpander);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
toggleVisible() {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
|
||||
this.visibleItems = this.items;
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,27 @@
|
|||
<ng-container *ngIf="currentValue > 0">
|
||||
|
||||
<div class="number">
|
||||
<i class="fa fa-angle-double-down" style="font-size: 36px;" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div style="width: 100px; height: 100px;">
|
||||
<circle-progress
|
||||
[percent]="currentValue"
|
||||
[radius]="100"
|
||||
[outerStrokeWidth]="15"
|
||||
[innerStrokeWidth]="0"
|
||||
[space] = "0"
|
||||
[backgroundPadding]="0"
|
||||
outerStrokeLinecap="butt"
|
||||
[outerStrokeColor]="'#4ac694'"
|
||||
[innerStrokeColor]="innerStrokeColor"
|
||||
titleFontSize= "24"
|
||||
unitsFontSize= "24"
|
||||
[showSubtitle] = "false"
|
||||
[animation]="animation"
|
||||
[animationDuration]="300"
|
||||
[startFromZero]="false"
|
||||
[responsive]="true"
|
||||
[backgroundOpacity]="0.5"
|
||||
[backgroundColor]="'#000'"
|
||||
></circle-progress>
|
||||
</div>
|
||||
|
||||
<div class="number">
|
||||
<i class="fa fa-angle-double-down" style="font-size: 36px;" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div style="width: 100px; height: 100px;">
|
||||
<circle-progress
|
||||
[percent]="currentValue"
|
||||
[radius]="100"
|
||||
[outerStrokeWidth]="15"
|
||||
[innerStrokeWidth]="0"
|
||||
[space] = "0"
|
||||
[backgroundPadding]="0"
|
||||
outerStrokeLinecap="butt"
|
||||
[outerStrokeColor]="'#4ac694'"
|
||||
[innerStrokeColor]="innerStrokeColor"
|
||||
titleFontSize= "24"
|
||||
unitsFontSize= "24"
|
||||
[showSubtitle] = "false"
|
||||
[animation]="animation"
|
||||
[animationDuration]="300"
|
||||
[startFromZero]="false"
|
||||
[responsive]="true"
|
||||
[backgroundOpacity]="0.5"
|
||||
[backgroundColor]="'#000'"
|
||||
></circle-progress>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-circular-loader',
|
||||
templateUrl: './circular-loader.component.html',
|
||||
styleUrls: ['./circular-loader.component.scss']
|
||||
styleUrls: ['./circular-loader.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class CircularLoaderComponent implements OnInit {
|
||||
export class CircularLoaderComponent {
|
||||
|
||||
@Input() currentValue: number = 0;
|
||||
@Input() maxValue: number = 0;
|
||||
@Input() animation: boolean = true;
|
||||
@Input() innerStrokeColor: string = 'transparent';
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
export class DrawerOptions {
|
||||
/**
|
||||
|
|
@ -11,7 +11,8 @@ export class DrawerOptions {
|
|||
selector: 'app-drawer',
|
||||
templateUrl: './drawer.component.html',
|
||||
styleUrls: ['./drawer.component.scss'],
|
||||
exportAs: "drawer"
|
||||
exportAs: "drawer",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DrawerComponent {
|
||||
@Input() isOpen = false;
|
||||
|
|
@ -24,10 +25,12 @@ export class DrawerComponent {
|
|||
@Output() drawerClosed = new EventEmitter();
|
||||
@Output() isOpenChange: EventEmitter<boolean> = new EventEmitter();
|
||||
|
||||
constructor(private readonly cdRef: ChangeDetectorRef) {}
|
||||
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
this.isOpenChange.emit(false);
|
||||
this.drawerClosed.emit(false);
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-icon-and-title',
|
||||
templateUrl: './icon-and-title.component.html',
|
||||
styleUrls: ['./icon-and-title.component.scss']
|
||||
styleUrls: ['./icon-and-title.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class IconAndTitleComponent implements OnInit {
|
||||
export class IconAndTitleComponent {
|
||||
/**
|
||||
* If the component is clickable and should emit click events
|
||||
*/
|
||||
|
|
@ -19,15 +20,9 @@ export class IconAndTitleComponent implements OnInit {
|
|||
|
||||
@Output() click: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
|
||||
|
||||
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
handleClick(event: MouseEvent) {
|
||||
if (this.clickable) this.click.emit(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { Person } from '../../_models/person';
|
||||
|
||||
@Component({
|
||||
selector: 'app-person-badge',
|
||||
templateUrl: './person-badge.component.html',
|
||||
styleUrls: ['./person-badge.component.scss']
|
||||
styleUrls: ['./person-badge.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PersonBadgeComponent implements OnInit {
|
||||
|
||||
@Input() person!: Person;
|
||||
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div>
|
||||
<span [innerHTML]="currentText | safeHtml" [ngClass]="{'blur-text': blur && isCollapsed}"></span>
|
||||
<a [class.hidden]="hideToggle" *ngIf="text && text.length > maxLength" class="read-more-link" (click)="toggleView()">
|
||||
<i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}}"></i> Read {{isCollapsed ? 'More' : 'Less'}}
|
||||
<i aria-hidden="true" class="fa" [ngClass]="{'fa-caret-down': isCollapsed, 'fa-caret-up': !isCollapsed}"></i> Read {{isCollapsed ? 'More' : 'Less'}}
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -1,43 +1,52 @@
|
|||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-read-more',
|
||||
templateUrl: './read-more.component.html',
|
||||
styleUrls: ['./read-more.component.scss']
|
||||
styleUrls: ['./read-more.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ReadMoreComponent implements OnChanges {
|
||||
|
||||
/**
|
||||
* String to apply readmore on
|
||||
*/
|
||||
@Input() text!: string;
|
||||
/**
|
||||
* Max length before apply read more. Defaults to 250 characters.
|
||||
*/
|
||||
@Input() maxLength: number = 250;
|
||||
/**
|
||||
* If the field is collapsed and blur true, text will not be readable
|
||||
*/
|
||||
@Input() blur: boolean = false;
|
||||
|
||||
currentText!: string;
|
||||
hideToggle: boolean = true;
|
||||
isCollapsed: boolean = true;
|
||||
|
||||
public isCollapsed: boolean = true;
|
||||
constructor(private readonly cdRef: ChangeDetectorRef) {}
|
||||
|
||||
toggleView() {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
this.determineView();
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
this.determineView();
|
||||
}
|
||||
determineView() {
|
||||
if (!this.text || this.text.length <= this.maxLength) {
|
||||
this.currentText = this.text;
|
||||
this.isCollapsed = false;
|
||||
this.hideToggle = true;
|
||||
return;
|
||||
}
|
||||
this.hideToggle = false;
|
||||
if (this.isCollapsed === true) {
|
||||
this.currentText = this.text.substring(0, this.maxLength);
|
||||
this.currentText = this.currentText.substr(0, Math.min(this.currentText.length, this.currentText.lastIndexOf(' ')));
|
||||
this.currentText = this.currentText + '…';
|
||||
} else if (this.isCollapsed === false) {
|
||||
this.currentText = this.text;
|
||||
}
|
||||
if (!this.text || this.text.length <= this.maxLength) {
|
||||
this.currentText = this.text;
|
||||
this.isCollapsed = false;
|
||||
this.hideToggle = true;
|
||||
return;
|
||||
}
|
||||
this.hideToggle = false;
|
||||
if (this.isCollapsed === true) {
|
||||
this.currentText = this.text.substring(0, this.maxLength);
|
||||
this.currentText = this.currentText.substr(0, Math.min(this.currentText.length, this.currentText.lastIndexOf(' ')));
|
||||
this.currentText = this.currentText + '…';
|
||||
} else if (this.isCollapsed === false) {
|
||||
this.currentText = this.text;
|
||||
}
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
ngOnChanges() {
|
||||
this.determineView();
|
||||
|
|
|
|||
|
|
@ -1,23 +1,17 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { MangaFormat } from 'src/app/_models/manga-format';
|
||||
import { UtilityService } from '../_services/utility.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-series-format',
|
||||
templateUrl: './series-format.component.html',
|
||||
styleUrls: ['./series-format.component.scss']
|
||||
styleUrls: ['./series-format.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SeriesFormatComponent implements OnInit {
|
||||
export class SeriesFormatComponent {
|
||||
|
||||
@Input() format: MangaFormat = MangaFormat.UNKNOWN;
|
||||
|
||||
get MangaFormat(): typeof MangaFormat {
|
||||
return MangaFormat;
|
||||
}
|
||||
|
||||
constructor(public utilityService: UtilityService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { AfterViewInit, Directive, ElementRef, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
|
||||
// TODO: Fix this code or remove it
|
||||
@Directive({
|
||||
selector: '[appShowIfScrollbar]'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<div class="tagbadge {{cursor}} {{fillStyle}}">
|
||||
<div class="tagbadge {{fillStyle}}" [ngClass]="{'selectable-cursor': selectionMode === TagBadgeCursor.Selectable,
|
||||
'not-allowed-cursor': selectionMode === TagBadgeCursor.NotAllowed, 'clickable-cursor': selectionMode === TagBadgeCursor.Clickable}">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
/**
|
||||
* What type of cursor to apply to the tag badge
|
||||
|
|
@ -24,29 +24,15 @@ export enum TagBadgeCursor {
|
|||
@Component({
|
||||
selector: 'app-tag-badge',
|
||||
templateUrl: './tag-badge.component.html',
|
||||
styleUrls: ['./tag-badge.component.scss']
|
||||
styleUrls: ['./tag-badge.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TagBadgeComponent implements OnInit {
|
||||
export class TagBadgeComponent {
|
||||
|
||||
@Input() selectionMode: TagBadgeCursor = TagBadgeCursor.Selectable;
|
||||
@Input() fillStyle: 'filled' | 'outline' = 'outline';
|
||||
|
||||
cursor: string = 'default';
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
switch (this.selectionMode) {
|
||||
case TagBadgeCursor.Selectable:
|
||||
this.cursor = 'selectable-cursor';
|
||||
break;
|
||||
case TagBadgeCursor.NotAllowed:
|
||||
this.cursor = 'not-allowed-cursor';
|
||||
break;
|
||||
case TagBadgeCursor.Clickable:
|
||||
this.cursor = 'clickable-cursor';
|
||||
break;
|
||||
}
|
||||
get TagBadgeCursor() {
|
||||
return TagBadgeCursor;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { UpdateVersionEvent } from 'src/app/_models/events/update-version-event';
|
||||
|
||||
|
|
@ -7,20 +7,16 @@ import { UpdateVersionEvent } from 'src/app/_models/events/update-version-event'
|
|||
@Component({
|
||||
selector: 'app-update-notification-modal',
|
||||
templateUrl: './update-notification-modal.component.html',
|
||||
styleUrls: ['./update-notification-modal.component.scss']
|
||||
styleUrls: ['./update-notification-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UpdateNotificationModalComponent implements OnInit {
|
||||
export class UpdateNotificationModalComponent {
|
||||
|
||||
@Input() updateData!: UpdateVersionEvent;
|
||||
|
||||
|
||||
constructor(public modal: NgbActiveModal) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
close() {
|
||||
this.modal.close({success: false, series: undefined});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue