Fix specified issues in PR

Create utility function for string filtering.
Move filter and search inputs to left side of buttons.
Implement sorting for get addons page.
Include i18 translation for new text.
Cleanup unused imports/variables in my addons and get addons components.
This commit is contained in:
Dean Campbell
2020-10-06 08:55:43 -07:00
parent 340b5df627
commit 03fcab040e
8 changed files with 140 additions and 97 deletions

View File

@@ -11,30 +11,34 @@
</mat-select>
</mat-form-field>
</div>
<div class="button-container">
<button mat-flat-button color="primary" (click)="onRefresh()">
{{'PAGES.GET_ADDONS.REFRESH_BUTTON' | translate}}
</button>
<button mat-flat-button color="primary" (click)="onInstallFromUrl()">
{{'PAGES.GET_ADDONS.INSTALL_FROM_URL_BUTTON' | translate}}
</button>
<mat-form-field class="example-form-field">
<mat-label>{{'PAGES.GET_ADDONS.SEARCH_LABEL' | translate}}</mat-label>
<input matInput type="text" [(ngModel)]="query" (keyup.enter)="onSearch()">
<button mat-button color="accent" *ngIf="query" matSuffix mat-icon-button aria-label="Clear"
(click)="onClearSearch()">
<mat-icon>close</mat-icon>
<div class="right-container">
<div class="search-container">
<mat-form-field>
<mat-label>{{'PAGES.GET_ADDONS.SEARCH_LABEL' | translate}}</mat-label>
<input matInput type="text" [(ngModel)]="query" (keyup.enter)="onSearch()">
<button mat-button color="accent" *ngIf="query" matSuffix mat-icon-button aria-label="Clear"
(click)="onClearSearch()">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
</div>
<div class="button-container">
<button mat-flat-button color="primary" (click)="onRefresh()">
{{'PAGES.GET_ADDONS.REFRESH_BUTTON' | translate}}
</button>
</mat-form-field>
<button mat-flat-button color="primary" (click)="onInstallFromUrl()">
{{'PAGES.GET_ADDONS.INSTALL_FROM_URL_BUTTON' | translate}}
</button>
</div>
</div>
</div>
<app-progress-spinner *ngIf="isBusy === true"></app-progress-spinner>
<div class="table-container flex-grow-1" *ngIf="isBusy === false">
<table mat-table [dataSource]="displayAddons$ | async" class="mat-elevation-z8">
<ng-container matColumnDef="addon">
<th mat-header-cell *matHeaderCellDef>
<div class="table-container flex-grow-1" [hidden]="isBusy === true">
<table mat-table matSort [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container matColumnDef="name">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
{{'PAGES.GET_ADDONS.TABLE.ADDON_COLUMN_HEADER' | translate}}
</th>
<td mat-cell *matCellDef="let element">
@@ -44,7 +48,7 @@
</ng-container>
<ng-container matColumnDef="provider">
<th mat-header-cell *matHeaderCellDef class="provider-column">
<th mat-header-cell mat-sort-header *matHeaderCellDef class="provider-column">
{{'PAGES.GET_ADDONS.TABLE.PROVIDER_COLUMN_HEADER' | translate}}
</th>
<td mat-cell *matCellDef="let element">
@@ -53,7 +57,7 @@
</ng-container>
<ng-container matColumnDef="author">
<th mat-header-cell *matHeaderCellDef class="author-column">
<th mat-header-cell mat-sort-header *matHeaderCellDef class="author-column">
{{'PAGES.GET_ADDONS.TABLE.AUTHOR_COLUMN_HEADER' | translate}}
</th>
<td mat-cell *matCellDef="let element">

View File

@@ -10,14 +10,24 @@
flex-grow: 1;
}
.button-container {
.right-container {
display: flex;
flex-direction: row;
align-items: center;
button {
&:not(:last-child) {
margin-right: 0.5em;
.search-container {
padding-top: 1em;
}
.button-container {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 1em;
button {
&:not(:last-child) {
margin-right: 0.5em;
}
}
}
}

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { MatDialog } from "@angular/material/dialog";
import { AddonDetailComponent } from "app/components/addon-detail/addon-detail.component";
import { InstallFromUrlDialogComponent } from "app/components/install-from-url-dialog/install-from-url-dialog.component";
@@ -11,8 +11,11 @@ import { ElectronService } from "app/services";
import { AddonService } from "app/services/addons/addon.service";
import { SessionService } from "app/services/session/session.service";
import { WarcraftService } from "app/services/warcraft/warcraft.service";
import { BehaviorSubject, Subscription } from "rxjs";
import { map, tap } from "rxjs/operators";
import { BehaviorSubject, Subject, Subscription } from "rxjs";
import { map } from "rxjs/operators";
import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import * as _ from 'lodash';
@Component({
selector: "app-get-addons",
@@ -20,13 +23,17 @@ import { map, tap } from "rxjs/operators";
styleUrls: ["./get-addons.component.scss"],
})
export class GetAddonsComponent implements OnInit, OnDestroy {
private readonly _displayAddonsSrc = new BehaviorSubject<PotentialAddon[]>(
[]
);
@ViewChild(MatSort) sort: MatSort;
private readonly _displayAddonsSrc = new BehaviorSubject<PotentialAddon[]>([]);
private readonly _destroyed$ = new Subject<void>();
private subscriptions: Subscription[] = [];
public dataSource = new MatTableDataSource<PotentialAddon>([]);
columns: ColumnState[] = [
{ name: "addon", display: "Addon", visible: true },
{ name: "name", display: "Addon", visible: true },
{ name: "author", display: "Author", visible: true },
{ name: "provider", display: "Provider", visible: true },
{ name: "status", display: "Status", visible: true },
@@ -37,8 +44,6 @@ export class GetAddonsComponent implements OnInit, OnDestroy {
}
public query = "";
public displayAddons$ = this._displayAddonsSrc.asObservable();
public isBusy = false;
public selectedClient = WowClientType.None;
@@ -64,9 +69,17 @@ export class GetAddonsComponent implements OnInit, OnDestroy {
})
).subscribe();
const displayAddonSubscription = this._displayAddonsSrc
.subscribe((items: PotentialAddon[]) => {
this.dataSource.data = items;
this.dataSource.sortingDataAccessor = _.get;
this.dataSource.sort = this.sort;
});
this.subscriptions = [
selectedClientSubscription,
addonRemovedSubscription
addonRemovedSubscription,
displayAddonSubscription
];
}

View File

@@ -1,38 +1,42 @@
<div class="tab-container d-flex flex-col"
[ngClass]="{'mac': electronService.isMac, 'windows': electronService.isWin }">
<div class="control-container">
<div class="left-container">
<div class="select-container ">
<mat-form-field>
<mat-label>{{'PAGES.MY_ADDONS.CLIENT_TYPE_SELECT_LABEL' | translate}}</mat-label>
<mat-select class="select" [(value)]="selectedClient" (selectionChange)="onClientChange()"
[disabled]="enableControls === false">
<mat-option [value]="clientType" *ngFor="let clientType of warcraftService.installedClientTypes$ | async">
{{warcraftService.getClientDisplayName(clientType)}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="select-container ">
<mat-form-field>
<mat-label>{{'PAGES.MY_ADDONS.CLIENT_TYPE_SELECT_LABEL' | translate}}</mat-label>
<mat-select class="select" [(value)]="selectedClient" (selectionChange)="onClientChange()"
[disabled]="enableControls === false">
<mat-option [value]="clientType" *ngFor="let clientType of warcraftService.installedClientTypes$ | async">
{{warcraftService.getClientDisplayName(clientType)}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="right-container">
<div class="filter-container" *ngIf="selectedClient !== wowClientType.None">
<mat-form-field>
<mat-label>Filter</mat-label>
<input matInput (keyup)="filterAddons($event)">
<mat-label>{{'PAGES.MY_ADDONS.FILTER_LABEL' | translate}}</mat-label>
<input matInput (keyup)="filterAddons()" [(ngModel)]="filter">
<button mat-button color="accent" *ngIf="filter" matSuffix mat-icon-button aria-label="Clear"
(click)="onClearFilter()">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
</div>
</div>
<div class="button-container">
<button mat-flat-button color="primary" [matTooltip]="'PAGES.MY_ADDONS.UPDATE_ALL_BUTTON_TOOLTIP' | translate"
[disabled]="enableControls === false" (click)="onUpdateAll()" (contextmenu)="onUpdateAllContext($event)">
{{'PAGES.MY_ADDONS.UPDATE_ALL_BUTTON' | translate}}
</button>
<button mat-flat-button color="primary" [matTooltip]="'PAGES.MY_ADDONS.CHECK_UPDATES_BUTTON_TOOLTIP' | translate"
[disabled]="enableControls === false" (click)="onRefresh()">
{{'PAGES.MY_ADDONS.CHECK_UPDATES_BUTTON' | translate}}
</button>
<button mat-flat-button color="primary" [matTooltip]="'PAGES.MY_ADDONS.RESCAN_FOLDERS_BUTTON_TOOLTIP' | translate"
[disabled]="enableControls === false" (click)="onReScan()">
{{'PAGES.MY_ADDONS.RESCAN_FOLDERS_BUTTON' | translate}}
</button>
<div class="button-container">
<button mat-flat-button color="primary" [matTooltip]="'PAGES.MY_ADDONS.UPDATE_ALL_BUTTON_TOOLTIP' | translate"
[disabled]="enableControls === false" (click)="onUpdateAll()" (contextmenu)="onUpdateAllContext($event)">
{{'PAGES.MY_ADDONS.UPDATE_ALL_BUTTON' | translate}}
</button>
<button mat-flat-button color="primary" [matTooltip]="'PAGES.MY_ADDONS.CHECK_UPDATES_BUTTON_TOOLTIP' | translate"
[disabled]="enableControls === false" (click)="onRefresh()">
{{'PAGES.MY_ADDONS.CHECK_UPDATES_BUTTON' | translate}}
</button>
<button mat-flat-button color="primary" [matTooltip]="'PAGES.MY_ADDONS.RESCAN_FOLDERS_BUTTON_TOOLTIP' | translate"
[disabled]="enableControls === false" (click)="onReScan()">
{{'PAGES.MY_ADDONS.RESCAN_FOLDERS_BUTTON' | translate}}
</button>
</div>
</div>
</div>

View File

@@ -5,29 +5,29 @@
flex-direction: row;
padding: 0 1em 0 1em;
.left-container {
.select-container {
padding-top: 1em;
flex-grow: 1;
}
.right-container {
display: flex;
flex-direction: row;
flex-grow: 1;
.select-container {
padding-top: 1em;
margin-right: 1em;
}
.filter-container {
padding-top: 1em;
}
}
.button-container {
display: flex;
flex-direction: row;
align-items: center;
button {
&:not(:last-child) {
margin-right: 0.5em;
.button-container {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 1em;
button {
&:not(:last-child) {
margin-right: 0.5em;
}
}
}
}

View File

@@ -1,7 +1,7 @@
import { Component, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { WowClientType } from '../../models/warcraft/wow-client-type';
import { debounceTime, filter, first, map, take, takeUntil, tap } from 'rxjs/operators';
import { from, BehaviorSubject, Observable, fromEvent, Subscription, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { from, BehaviorSubject, Subscription, Subject } from 'rxjs';
import { Addon } from 'app/entities/addon';
import { WarcraftService } from 'app/services/warcraft/warcraft.service';
import { AddonService } from 'app/services/addons/addon.service';
@@ -21,6 +21,7 @@ import { ConfirmDialogComponent } from 'app/components/confirm-dialog/confirm-di
import { getEnumName } from 'app/utils/enum.utils';
import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { stringIncludes } from 'app/utils/string.utils';
@Component({
selector: 'app-my-addons',
@@ -38,13 +39,13 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
private readonly _destroyed$ = new Subject<void>();
private subscriptions: Subscription[] = [];
private sub: Subscription;
public spinnerMessage = 'Loading...';
contextMenuPosition = { x: '0px', y: '0px' };
public dataSource = new MatTableDataSource<MyAddonsListItem>([]);
public filter = '';
columns: ColumnState[] = [
{ name: 'addon.name', display: 'Addon', visible: true },
@@ -76,7 +77,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
private _dialog: MatDialog
) {
this.addonService.addonInstalled$.subscribe((evt) => {
const addonInstalledSubscription = this.addonService.addonInstalled$.subscribe((evt) => {
console.log('UPDATE')
let listItems: MyAddonsListItem[] = [].concat(this._displayAddonsSrc.value);
const listItemIdx = listItems.findIndex(li => li.addon.id === evt.addon.id);
@@ -98,7 +99,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
});
});
this.addonService.addonRemoved$
const addonRemovedSubscription = this.addonService.addonRemoved$
.subscribe((addonId) => {
const addons: MyAddonsListItem[] = [].concat(this._displayAddonsSrc.value);
const listItemIdx = addons.findIndex(li => li.addon.id === addonId);
@@ -109,23 +110,24 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
});
})
this._displayAddonsSrc
.pipe(takeUntil(this._destroyed$))
const displayAddonSubscription = this._displayAddonsSrc
.subscribe((items: MyAddonsListItem[]) => {
this.dataSource.data = items;
this.dataSource.sortingDataAccessor = _.get;
this.dataSource.filterPredicate = (item: MyAddonsListItem, filter: string) => {
if (item.addon.name.trim().toLowerCase().indexOf(filter) >= 0) return true;
if (item.addon.latestVersion && item.addon.latestVersion.trim().toLowerCase().indexOf(filter) >= 0) return true;
if (item.addon.author && item.addon.author.trim().toLowerCase().indexOf(filter) >= 0) return true;
if (stringIncludes(item.addon.name, filter) || stringIncludes(item.addon.latestVersion, filter) || stringIncludes(item.addon.author, filter)) {
return true;
}
return false;
}
this.dataSource.sort = this.sort;
});
this.subscriptions.concat(...[addonInstalledSubscription, addonRemovedSubscription, displayAddonSubscription]);
}
ngOnInit(): void {
this._sessionService.selectedClientType$
const selectedClientSubscription = this._sessionService.selectedClientType$
.pipe(
map(clientType => {
this.selectedClient = clientType;
@@ -133,12 +135,12 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
})
)
.subscribe();
this.subscriptions.push(selectedClientSubscription);
}
ngOnDestroy(): void {
this.subscriptions.forEach(sub => sub.unsubscribe());
this._destroyed$.next();
this._destroyed$.complete();
}
onRefresh() {
@@ -180,10 +182,13 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
});
}
filterAddons(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
console.log('filterValue:', filterValue);
this.dataSource.filter = filterValue.trim().toLowerCase();
filterAddons(): void {
this.dataSource.filter = this.filter.trim().toLowerCase();
}
onClearFilter(): void {
this.filter = '';
this.filterAddons();
}
async onUpdateAll() {

View File

@@ -0,0 +1,6 @@
export function stringIncludes(value: string, search: string) {
if (value == null) {
return false;
}
return value.trim().toLowerCase().indexOf(search.trim().toLowerCase()) >= 0;
}

View File

@@ -29,6 +29,7 @@
"CHECK_UPDATES_BUTTON": "Check Updates",
"CHECK_UPDATES_BUTTON_TOOLTIP": "Check for latest addon updates",
"CLIENT_TYPE_SELECT_LABEL": "World of Warcraft",
"FILTER_LABEL": "Filter",
"RESCAN_FOLDERS_BUTTON": "Re-Scan Folders",
"RESCAN_FOLDERS_BUTTON_TOOLTIP": "Scan your client folder for installed addons",
"UPDATE_ALL_BUTTON": "Update All",