mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-31 04:04:19 -04:00
Misc Cleanup (#1242)
* Updated the wording for Read in incognito, as 'in' was redundant * Added icons to the middle tabs for a mobile compaitible view * Fixed up the code for side nav to make the display much cleaner * Added icons to tabs * Styling polishing - Making pagination spacing uniform - Fixing onresize event - Making cards center justification on mobile only - fixing vertical alignment for companion bar icons - Fixing Issue where drawer buttons would sometimes not be visible. - Fixed vertical alignment issue with filter button * Fixing orientation change event * added fixed position to drawer - fixes styling issues * added total pages to series modal * Downgraded ExCSS package to a version that doesn't die on @page query selectors. * Cleaned up some code and wrote some bug markers in typeahead * Removed some padding top on companion bar * Aligned the top margin for card detail layout and series detail * Use a temp close button on book reader until new code is ready. Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
This commit is contained in:
parent
f9299a3e03
commit
7f456770cb
@ -39,7 +39,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
||||||
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.2" />
|
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.2" />
|
||||||
<PackageReference Include="ExCSS" Version="4.1.3" />
|
<PackageReference Include="ExCSS" Version="4.1.0" />
|
||||||
<PackageReference Include="Flurl" Version="3.0.5" />
|
<PackageReference Include="Flurl" Version="3.0.5" />
|
||||||
<PackageReference Include="Flurl.Http" Version="3.2.3" />
|
<PackageReference Include="Flurl.Http" Version="3.2.3" />
|
||||||
<PackageReference Include="Hangfire" Version="1.7.28" />
|
<PackageReference Include="Hangfire" Version="1.7.28" />
|
||||||
|
@ -250,7 +250,7 @@ export class ActionFactoryService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.IncognitoRead,
|
action: Action.IncognitoRead,
|
||||||
title: 'Read in Incognito',
|
title: 'Read Incognito',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false
|
||||||
},
|
},
|
||||||
@ -277,7 +277,7 @@ export class ActionFactoryService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.IncognitoRead,
|
action: Action.IncognitoRead,
|
||||||
title: 'Read in Incognito',
|
title: 'Read Incognito',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false
|
||||||
},
|
},
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<a id="content"></a>
|
<a id="content"></a>
|
||||||
<app-side-nav *ngIf="navService.sideNavVisibility$ | async"></app-side-nav>
|
<app-side-nav *ngIf="navService.sideNavVisibility$ | async"></app-side-nav>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div style="padding-top: 10px; padding-bottom: 65px;" *ngIf="navService.sideNavVisibility$ | async else noSideNav">
|
<div style="padding: 20px 0;" *ngIf="navService.sideNavVisibility$ | async else noSideNav">
|
||||||
<div class="companion-bar" [ngClass]="{'companion-bar-content': !(navService?.sideNavCollapsed$ | async)}">
|
<div class="companion-bar" [ngClass]="{'companion-bar-content': !(navService?.sideNavCollapsed$ | async)}">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,12 +35,12 @@ export class AppComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('resize')
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize() {
|
onResize(){
|
||||||
this.setDocHeight();
|
this.setDocHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('orientationchange')
|
@HostListener('window:orientationchange', ['$event'])
|
||||||
onOrientationChange() {
|
onOrientationChange() {
|
||||||
this.setDocHeight();
|
this.setDocHeight();
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,10 @@ import { NavModule } from './nav/nav.module';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
<app-drawer #commentDrawer="drawer" [isOpen]="drawerOpen" [style.--drawer-width]="'300px'" [options]="{topOffset: topOffset}" [style.--drawer-background-color]="drawerBackgroundColor" (drawerClosed)="closeDrawer()">
|
<app-drawer #commentDrawer="drawer" [isOpen]="drawerOpen" [style.--drawer-width]="'300px'" [options]="{topOffset: topOffset}" [style.--drawer-background-color]="drawerBackgroundColor" (drawerClosed)="closeDrawer()">
|
||||||
<div header>
|
<div header>
|
||||||
<h2 style="margin-top: 0.5rem">Book Settings
|
<h2 style="margin-top: 0.5rem">Book Settings
|
||||||
<button type="button" class="btn-close" aria-label="Close" (click)="commentDrawer.close()"></button>
|
<!-- Temp use times rather than btn-close due to some styling issue -->
|
||||||
|
<button type="button" class="btn btn-icon" style="font-size: 2rem" aria-label="Close" (click)="commentDrawer.close()">×</button>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div body class="drawer-body">
|
<div body class="drawer-body">
|
||||||
|
@ -352,6 +352,8 @@
|
|||||||
<div class="col-md-6">Max Items: {{metadata.maxCount}}</div>
|
<div class="col-md-6">Max Items: {{metadata.maxCount}}</div>
|
||||||
<div class="col-md-6">Total Items: {{metadata.totalCount}}</div>
|
<div class="col-md-6">Total Items: {{metadata.totalCount}}</div>
|
||||||
<div class="col-md-6">Publication Status: {{metadata.publicationStatus | publicationStatus}}</div>
|
<div class="col-md-6">Publication Status: {{metadata.publicationStatus | publicationStatus}}</div>
|
||||||
|
<div class="col-md-6">Total Pages: {{series.pages}}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<h4>Volumes</h4>
|
<h4>Volumes</h4>
|
||||||
<div class="spinner-border text-secondary" role="status" *ngIf="isLoadingVolumes">
|
<div class="spinner-border text-secondary" role="status" *ngIf="isLoadingVolumes">
|
||||||
|
@ -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 g-0 mt-2 mb-2">
|
<div class="row justify-content-evenly justify-content-sm-start g-0 mb-3">
|
||||||
<div class="col-auto ps-1 pe-1 mt-2 mb-2" *ngFor="let item of items; trackBy:trackByIdentity; index as i">
|
<div class="col-auto ps-1 pe-1 mt-1 mb-1" *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>
|
||||||
|
|
||||||
|
@ -7,7 +7,12 @@
|
|||||||
<div main>
|
<div main>
|
||||||
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav nav-pills" style="flex-wrap: nowrap;">
|
<ul ngbNav #nav="ngbNav" [(activeId)]="active" class="nav nav-pills" style="flex-wrap: nowrap;">
|
||||||
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
<li *ngFor="let tab of tabs" [ngbNavItem]="tab">
|
||||||
<a ngbNavLink>{{tab.title | sentenceCase}}</a>
|
<a ngbNavLink>
|
||||||
|
<span class="d-none d-sm-flex align-items-center"><i class="fa {{tab.icon}} me-1" style="padding-right: 5px;" aria-hidden="true"></i> {{tab.title | sentenceCase}}</span>
|
||||||
|
<span class="d-flex d-sm-none">
|
||||||
|
<i class="fa {{tab.icon}}" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<ng-container *ngIf="tab.title === 'Recommended'">
|
<ng-container *ngIf="tab.title === 'Recommended'">
|
||||||
<app-library-recommended [libraryId]="libraryId"></app-library-recommended>
|
<app-library-recommended [libraryId]="libraryId"></app-library-recommended>
|
||||||
|
@ -8,4 +8,4 @@
|
|||||||
width: 600px;
|
width: 600px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
@ -39,9 +39,9 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
|||||||
filterActive: boolean = false;
|
filterActive: boolean = false;
|
||||||
filterActiveCheck!: SeriesFilter;
|
filterActiveCheck!: SeriesFilter;
|
||||||
|
|
||||||
tabs: Array<{title: string, fragment: string}> = [
|
tabs: Array<{title: string, fragment: string, icon: string}> = [
|
||||||
{title: 'Library', fragment: ''},
|
{title: 'Library', fragment: '', icon: 'fa-landmark'},
|
||||||
{title: 'Recommended', fragment: 'recomended'},
|
{title: 'Recommended', fragment: 'recomended', icon: 'fa-award'},
|
||||||
];
|
];
|
||||||
active = this.tabs[0];
|
active = this.tabs[0];
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[items]="volumes">
|
[items]="volumes">
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
<app-card-item class="col-auto p-2" [entity]="item" [title]="item.name" (click)="openVolume(item)"
|
<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', position, volumes.length, $event)"
|
||||||
@ -111,7 +111,7 @@
|
|||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[items]="chapters">
|
[items]="chapters">
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
<app-card-item class="col-auto p-2" *ngIf="!item.isSpecial" [entity]="item" [title]="item.title" (click)="openChapter(item)"
|
<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', position, chapters.length, $event)"
|
||||||
@ -127,7 +127,7 @@
|
|||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[items]="specials">
|
[items]="specials">
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
<app-card-item class="col-auto p-2" [entity]="item" [title]="item.title || item.range" (click)="openChapter(item)"
|
<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', position, chapters.length, $event)"
|
||||||
@ -141,7 +141,7 @@
|
|||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div class="row g-0">
|
<div class="row g-0">
|
||||||
<ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy">
|
<ng-container *ngFor="let item of relations; let idx = index; trackBy: trackByRelatedSeriesIdentiy">
|
||||||
<app-series-card class="col-auto p-2" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
|
<app-series-card class="col-auto" [data]="item.series" [libraryId]="item.series.libraryId" [relation]="item.relation"></app-series-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div
|
<div
|
||||||
class="drawer-container"
|
class="drawer-container"
|
||||||
[ngStyle]="{'top': options.topOffset + 'px', 'padding-bottom': options.topOffset + 'px'}"
|
[ngStyle]="{'position': 'fixed','top': options.topOffset + 'px','height': 'calc(100% - ' + options.topOffset + 'px)'}"
|
||||||
[class.is-open]="isOpen"
|
[class.is-open]="isOpen"
|
||||||
[class.position-right]="position === 'right'"
|
[class.position-right]="position === 'right'"
|
||||||
[class.position-left]="position === 'left'"
|
[class.position-left]="position === 'left'"
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
<div class="mt-0">
|
<div class="mt-0 d-flex justify-content-between align-items-center">
|
||||||
<div class="row g-0">
|
<div>
|
||||||
<div class="col mr-auto">
|
<ng-content select="[title]"></ng-content>
|
||||||
<ng-content select="[title]"></ng-content>
|
<ng-content select="[subtitle]"></ng-content>
|
||||||
<ng-content select="[subtitle]"></ng-content>
|
|
||||||
</div>
|
|
||||||
<div class="col mr-auto d-none d-sm-flex hide-if-empty">
|
|
||||||
<ng-content select="[main]"></ng-content>
|
|
||||||
</div>
|
|
||||||
<div class="col" *ngIf="hasFilter">
|
|
||||||
<div class="row justify-content-end">
|
|
||||||
<div class="col-auto align-self-end">
|
|
||||||
<button *ngIf="hasFilter" class="btn btn-{{filterActive ? 'primary' : 'secondary'}} btn-small" (click)="toggleFilter()" [attr.aria-expanded]="filterOpen" placement="left" ngbTooltip="{{filterOpen ? 'Open' : 'Close'}} Filtering and Sorting" attr.aria-label="{{filterOpen ? 'Open' : 'Close'}} Filtering and Sorting">
|
|
||||||
<i class="fa fa-filter" aria-hidden="true"></i>
|
|
||||||
<span class="visually-hidden">Sort / Filter</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<ng-content select="[main]"></ng-content>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button *ngIf="hasFilter" class="btn btn-{{filterActive ? 'primary' : 'secondary'}} btn-small" (click)="toggleFilter()" [attr.aria-expanded]="filterOpen" placement="left" ngbTooltip="{{filterOpen ? 'Open' : 'Close'}} Filtering and Sorting" attr.aria-label="{{filterOpen ? 'Open' : 'Close'}} Filtering and Sorting">
|
||||||
|
<i class="fa fa-filter" aria-hidden="true"></i>
|
||||||
|
<span class="visually-hidden">Sort / Filter</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
@ -7,7 +7,7 @@ import { KEY_CODES } from '../shared/_services/utility.service';
|
|||||||
import { SelectionCompareFn, TypeaheadSettings } from './typeahead-settings';
|
import { SelectionCompareFn, TypeaheadSettings } from './typeahead-settings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SelectionModel<T> is used for keeping track of multiple selections. Simple interface with ability to toggle.
|
* SelectionModel<T> is used for keeping track of multiple selections. Simple interface with ability to toggle.
|
||||||
* @param selectedState Optional state to set selectedOptions to. If not passed, defaults to false.
|
* @param selectedState Optional state to set selectedOptions to. If not passed, defaults to false.
|
||||||
* @param selectedOptions Optional data elements to inform the SelectionModel of. If not passed, as toggle() occur, items are tracked.
|
* @param selectedOptions Optional data elements to inform the SelectionModel of. If not passed, as toggle() occur, items are tracked.
|
||||||
* @param propAccessor Optional string that points to a unique field within the T type. Used for quickly looking up.
|
* @param propAccessor Optional string that points to a unique field within the T type. Used for quickly looking up.
|
||||||
@ -39,7 +39,7 @@ export class SelectionModel<T> {
|
|||||||
if (compareFn != undefined || compareFn != null) {
|
if (compareFn != undefined || compareFn != null) {
|
||||||
lookupMethod = compareFn;
|
lookupMethod = compareFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataItem = this._data.filter(d => lookupMethod(d.value, data));
|
const dataItem = this._data.filter(d => lookupMethod(d.value, data));
|
||||||
if (dataItem.length > 0) {
|
if (dataItem.length > 0) {
|
||||||
if (selectedState != undefined) {
|
if (selectedState != undefined) {
|
||||||
@ -66,9 +66,9 @@ export class SelectionModel<T> {
|
|||||||
if (compareFn != undefined || compareFn != null) {
|
if (compareFn != undefined || compareFn != null) {
|
||||||
lookupMethod = compareFn;
|
lookupMethod = compareFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
dataItem = this._data.filter(d => lookupMethod(d.value, data));
|
dataItem = this._data.filter(d => lookupMethod(d.value, data));
|
||||||
|
|
||||||
if (dataItem.length > 0) {
|
if (dataItem.length > 0) {
|
||||||
return dataItem[0].selected;
|
return dataItem[0].selected;
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ export class SelectionModel<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns If some of the items are selected, but not all
|
* @returns If some of the items are selected, but not all
|
||||||
*/
|
*/
|
||||||
hasSomeSelected(): boolean {
|
hasSomeSelected(): boolean {
|
||||||
@ -85,7 +85,7 @@ export class SelectionModel<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns All Selected items
|
* @returns All Selected items
|
||||||
*/
|
*/
|
||||||
selected(): Array<T> {
|
selected(): Array<T> {
|
||||||
@ -93,7 +93,7 @@ export class SelectionModel<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns All Non-Selected items
|
* @returns All Non-Selected items
|
||||||
*/
|
*/
|
||||||
unselected(): Array<T> {
|
unselected(): Array<T> {
|
||||||
@ -101,7 +101,7 @@ export class SelectionModel<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns Last element added/tracked or undefined if nothing is tracked
|
* @returns Last element added/tracked or undefined if nothing is tracked
|
||||||
*/
|
*/
|
||||||
peek(): T | undefined {
|
peek(): T | undefined {
|
||||||
@ -115,17 +115,17 @@ export class SelectionModel<T> {
|
|||||||
shallowEqual(object1: T, object2: T) {
|
shallowEqual(object1: T, object2: T) {
|
||||||
const keys1 = Object.keys(object1);
|
const keys1 = Object.keys(object1);
|
||||||
const keys2 = Object.keys(object2);
|
const keys2 = Object.keys(object2);
|
||||||
|
|
||||||
if (keys1.length !== keys2.length) {
|
if (keys1.length !== keys2.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let key of keys1) {
|
for (let key of keys1) {
|
||||||
if ((object1 as any)[key] !== (object2 as any)[key]) {
|
if ((object1 as any)[key] !== (object2 as any)[key]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
@Output() newItemAdded = new EventEmitter<any[] | any>();
|
@Output() newItemAdded = new EventEmitter<any[] | any>();
|
||||||
@Output() onUnlock = new EventEmitter<void>();
|
@Output() onUnlock = new EventEmitter<void>();
|
||||||
@Output() lockedChange = new EventEmitter<boolean>();
|
@Output() lockedChange = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
|
||||||
@ViewChild('input') inputElem!: ElementRef<HTMLInputElement>;
|
@ViewChild('input') inputElem!: ElementRef<HTMLInputElement>;
|
||||||
@ContentChild('optionItem') optionTemplate!: TemplateRef<any>;
|
@ContentChild('optionItem') optionTemplate!: TemplateRef<any>;
|
||||||
@ -171,7 +171,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
isLoadingOptions: boolean = false;
|
isLoadingOptions: boolean = false;
|
||||||
typeaheadControl!: FormControl;
|
typeaheadControl!: FormControl;
|
||||||
typeaheadForm!: FormGroup;
|
typeaheadForm!: FormGroup;
|
||||||
|
|
||||||
private readonly onDestroy = new Subject<void>();
|
private readonly onDestroy = new Subject<void>();
|
||||||
|
|
||||||
constructor(private renderer2: Renderer2, @Inject(DOCUMENT) private document: Document) { }
|
constructor(private renderer2: Renderer2, @Inject(DOCUMENT) private document: Document) { }
|
||||||
@ -206,7 +206,6 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
'typeahead': this.typeaheadControl
|
'typeahead': this.typeaheadControl
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.filteredOptions = this.typeaheadForm.get('typeahead')!.valueChanges
|
this.filteredOptions = this.typeaheadForm.get('typeahead')!.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
// Adjust input box to grow
|
// Adjust input box to grow
|
||||||
@ -218,7 +217,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
map(val => val.trim()),
|
map(val => val.trim()),
|
||||||
auditTime(this.settings.debounce),
|
auditTime(this.settings.debounce),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(), // ?!: BUG Doesn't trigger the search to run when filtered array changes
|
||||||
filter(val => {
|
filter(val => {
|
||||||
// If minimum filter characters not met, do not filter
|
// If minimum filter characters not met, do not filter
|
||||||
if (this.settings.minCharacters === 0) return true;
|
if (this.settings.minCharacters === 0) return true;
|
||||||
@ -229,7 +228,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
switchMap(val => {
|
switchMap(val => {
|
||||||
this.isLoadingOptions = true;
|
this.isLoadingOptions = true;
|
||||||
let results: Observable<any[]>;
|
let results: Observable<any[]>;
|
||||||
@ -243,14 +242,14 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
return results;
|
return results;
|
||||||
}),
|
}),
|
||||||
tap((filteredOptions) => {
|
tap((filteredOptions) => {
|
||||||
this.isLoadingOptions = false;
|
this.isLoadingOptions = false;
|
||||||
this.focusedIndex = 0;
|
this.focusedIndex = 0;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.updateShowAddItem(filteredOptions);
|
this.updateShowAddItem(filteredOptions);
|
||||||
this.updateHighlight();
|
this.updateHighlight();
|
||||||
}, 10);
|
}, 10);
|
||||||
setTimeout(() => this.updateHighlight(), 20);
|
setTimeout(() => this.updateHighlight(), 20);
|
||||||
|
|
||||||
}),
|
}),
|
||||||
shareReplay(),
|
shareReplay(),
|
||||||
takeUntil(this.onDestroy)
|
takeUntil(this.onDestroy)
|
||||||
@ -259,7 +258,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
if (this.settings.savedData) {
|
if (this.settings.savedData) {
|
||||||
if (this.settings.multiple) {
|
if (this.settings.multiple) {
|
||||||
this.optionSelection = new SelectionModel<any>(true, this.settings.savedData);
|
this.optionSelection = new SelectionModel<any>(true, this.settings.savedData);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const isArray = this.settings.savedData.hasOwnProperty('length');
|
const isArray = this.settings.savedData.hasOwnProperty('length');
|
||||||
@ -281,7 +280,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:keydown', ['$event'])
|
@HostListener('window:keydown', ['$event'])
|
||||||
handleKeyPress(event: KeyboardEvent) {
|
handleKeyPress(event: KeyboardEvent) {
|
||||||
if (!this.hasFocus) { return; }
|
if (!this.hasFocus) { return; }
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
|
|
||||||
@ -304,22 +303,13 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
{
|
{
|
||||||
this.document.querySelectorAll('.list-group-item').forEach((item, index) => {
|
this.document.querySelectorAll('.list-group-item').forEach((item, index) => {
|
||||||
if (item.classList.contains('active')) {
|
if (item.classList.contains('active')) {
|
||||||
this.filteredOptions.pipe(take(1)).subscribe((opts: any[]) => {
|
this.filteredOptions.pipe(take(1)).subscribe((opts: any[]) => {
|
||||||
// This isn't giving back the filtered array, but everything
|
// This isn't giving back the filtered array, but everything
|
||||||
|
event.preventDefault();
|
||||||
if (this.settings.addIfNonExisting && item.classList.contains('add-item')) {
|
event.stopPropagation();
|
||||||
this.addNewItem(this.typeaheadControl.value);
|
|
||||||
this.resetField();
|
|
||||||
this.focusedIndex = 0;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(item as HTMLElement).click();
|
(item as HTMLElement).click();
|
||||||
this.focusedIndex = 0;
|
this.focusedIndex = 0;
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -369,7 +359,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
} else {
|
} else {
|
||||||
this.optionSelection.selected().forEach(item => this.optionSelection.toggle(item, false));
|
this.optionSelection.selected().forEach(item => this.optionSelection.toggle(item, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedData.emit(this.optionSelection.selected());
|
this.selectedData.emit(this.optionSelection.selected());
|
||||||
this.resetField();
|
this.resetField();
|
||||||
}
|
}
|
||||||
@ -388,7 +378,7 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addNewItem(title: string) {
|
addNewItem(title: string) {
|
||||||
if (this.settings.addTransformFn == undefined) {
|
if (this.settings.addTransformFn == undefined || !this.settings.addIfNonExisting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newItem = this.settings.addTransformFn(title);
|
const newItem = this.settings.addTransformFn(title);
|
||||||
@ -400,8 +390,8 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param item
|
* @param item
|
||||||
* @returns True if the item is NOT selected already
|
* @returns True if the item is NOT selected already
|
||||||
*/
|
*/
|
||||||
filterSelected(item: any) {
|
filterSelected(item: any) {
|
||||||
@ -432,18 +422,19 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
if (this.inputElem) {
|
if (this.inputElem) {
|
||||||
// hack: To prevent multiple typeaheads from being open at once, click document then trigger the focus
|
// hack: To prevent multiple typeaheads from being open at once, click document then trigger the focus
|
||||||
this.document.body.click();
|
this.document.body.click();
|
||||||
|
|
||||||
this.inputElem.nativeElement.focus();
|
this.inputElem.nativeElement.focus();
|
||||||
this.hasFocus = true;
|
this.hasFocus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.openDropdown();
|
this.openDropdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
resetField() {
|
resetField() {
|
||||||
if (this.inputElem && this.inputElem.nativeElement) {
|
if (this.inputElem && this.inputElem.nativeElement) {
|
||||||
this.renderer2.setStyle(this.inputElem.nativeElement, 'width', 4, RendererStyleFlags2.Important);
|
this.renderer2.setStyle(this.inputElem.nativeElement, 'width', 4, RendererStyleFlags2.Important);
|
||||||
}
|
}
|
||||||
this.typeaheadControl.setValue('');
|
this.typeaheadControl.setValue('');
|
||||||
this.focusedIndex = 0;
|
this.focusedIndex = 0;
|
||||||
@ -463,11 +454,11 @@ export class TypeaheadComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateShowAddItem(options: any[]) {
|
updateShowAddItem(options: any[]) {
|
||||||
this.showAddItem = this.settings.addIfNonExisting && this.typeaheadControl.value.trim()
|
this.showAddItem = this.settings.addIfNonExisting && this.typeaheadControl.value.trim()
|
||||||
&& this.typeaheadControl.value.trim().length >= Math.max(this.settings.minCharacters, 1)
|
&& this.typeaheadControl.value.trim().length >= Math.max(this.settings.minCharacters, 1)
|
||||||
&& this.typeaheadControl.dirty
|
&& this.typeaheadControl.dirty
|
||||||
&& (typeof this.settings.compareFn == 'function' && this.settings.compareFn(options, this.typeaheadControl.value.trim()).length === 0);
|
&& (typeof this.settings.compareFn == 'function' && this.settings.compareFn(options, this.typeaheadControl.value.trim()).length === 0);
|
||||||
|
|
||||||
if (this.showAddItem) {
|
if (this.showAddItem) {
|
||||||
this.hasFocus = true;
|
this.hasFocus = true;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,9 @@ label, select, .clickable {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: calc(var(--vh)*100 - 56px);
|
||||||
|
}
|
||||||
|
|
||||||
// Needed for fullscreen
|
// Needed for fullscreen
|
||||||
app-root {
|
app-root {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user