From dd0a1b4a9ed1ddd9a969e68f2a6c0414edd8abae Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 00:33:00 -0500 Subject: [PATCH 01/34] Simplify re-scan. Re-scan should now apply the same channel type that was on the matched addon. --- .../addon-providers/curse-addon-provider.ts | 18 ++--- .../src/app/services/addons/addon.service.ts | 74 ++++++++----------- .../services/storage/addon-storage.service.ts | 4 +- .../src/environments/environment.ts | 6 +- 4 files changed, 44 insertions(+), 58 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index b5047aec..836f237d 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -78,11 +78,7 @@ export class CurseAddonProvider implements AddonProvider { } try { - const newAddon = this.getAddon( - clientType, - addonChannelType, - scanResult - ); + const newAddon = this.getAddon(clientType, scanResult); addonFolder.matchingAddon = newAddon; } catch (err) { @@ -491,7 +487,6 @@ export class CurseAddonProvider implements AddonProvider { private getAddon( clientType: WowClientType, - addonChannelType: AddonChannelType, scanResult: AppCurseScanResult ): Addon { const currentVersion = scanResult.exactMatch.file; @@ -506,15 +501,20 @@ export class CurseAddonProvider implements AddonProvider { clientType ); - let channelType = addonChannelType; - let latestVersion = latestFiles.find( - (lf) => this.getChannelType(lf.releaseType) <= addonChannelType + let channelType = this.getChannelType( + scanResult.exactMatch.file.releaseType ); + let latestVersion = latestFiles.find( + (lf) => this.getChannelType(lf.releaseType) <= channelType + ); + + console.log(scanResult.searchResult.name, channelType); // If there were no releases that met the channel type restrictions if (!latestVersion) { latestVersion = _.first(latestFiles); channelType = this.getWowUpChannel(latestVersion.releaseType); + console.warn("falling back to default channel"); } return { diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index d108fdb2..70102327 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -389,10 +389,13 @@ export class AddonService { rescan = false ): Promise { let addons = this._addonStorage.getAllForClientType(clientType); - if (rescan ) { + if (rescan) { const newAddons = await this.scanAddons(clientType); - console.log(newAddons) - this.updateAddons(addons, newAddons); + console.log(newAddons); + + this._addonStorage.removeAllForClientType(clientType); + addons = this.updateAddons(addons, newAddons); + this._addonStorage.saveAll(addons); } await this.syncAddons(clientType, addons); @@ -400,51 +403,34 @@ export class AddonService { return addons; } - private updateAddons(existingAddons: Addon[], newAddons: Addon[]): Addon[] { - const removedAddons = existingAddons.filter( - (existingAddon) => - !newAddons.some((newAddon) => this.addonsMatch(existingAddon, newAddon)) - ); - - const addedAddons = newAddons.filter( - (newAddon) => - !existingAddons.some((existingAddon) => - this.addonsMatch(existingAddon, newAddon) - ) - ); - - _.remove(existingAddons, (addon) => - removedAddons.some((removedAddon) => removedAddon.id === addon.id) - ); - - existingAddons.push(...addedAddons); - - for (let existingAddon of existingAddons) { - var matchingAddon = newAddons.find((newAddon) => - this.addonsMatch(newAddon, existingAddon) + private updateAddons(existingAddons: Addon[], newAddons: Addon[]) { + _.forEach(newAddons, (newAddon) => { + const existingAddon = _.find( + existingAddons, + (ea) => + ea.externalId == newAddon.externalId && + ea.providerName == newAddon.providerName ); - if (!matchingAddon) { - continue; + + if (!existingAddon) { + return; } - existingAddon.name = matchingAddon.name; - existingAddon.folderName = matchingAddon.folderName; - existingAddon.downloadUrl = matchingAddon.downloadUrl; - existingAddon.installedVersion = matchingAddon.installedVersion; - existingAddon.externalUrl = matchingAddon.externalUrl; - existingAddon.latestVersion = matchingAddon.latestVersion; - existingAddon.thumbnailUrl = matchingAddon.thumbnailUrl; - existingAddon.gameVersion = matchingAddon.gameVersion; - existingAddon.author = matchingAddon.author; - existingAddon.patreonFundingLink = matchingAddon.patreonFundingLink; - existingAddon.githubFundingLink = matchingAddon.githubFundingLink; - existingAddon.customFundingLink = matchingAddon.customFundingLink; - } + newAddon.name = existingAddon.name; + newAddon.folderName = existingAddon.folderName; + newAddon.downloadUrl = existingAddon.downloadUrl; + newAddon.installedVersion = existingAddon.installedVersion; + newAddon.externalUrl = existingAddon.externalUrl; + newAddon.latestVersion = existingAddon.latestVersion; + newAddon.thumbnailUrl = existingAddon.thumbnailUrl; + newAddon.gameVersion = existingAddon.gameVersion; + newAddon.author = existingAddon.author; + newAddon.patreonFundingLink = existingAddon.patreonFundingLink; + newAddon.githubFundingLink = existingAddon.githubFundingLink; + newAddon.customFundingLink = existingAddon.customFundingLink; + }); - this._addonStorage.removeAll(...removedAddons); - this._addonStorage.setAll(existingAddons); - - return existingAddons; + return newAddons; } private addonsMatch(addon1: Addon, addon2: Addon): boolean { diff --git a/wowup-electron/src/app/services/storage/addon-storage.service.ts b/wowup-electron/src/app/services/storage/addon-storage.service.ts index 5cce63bf..858c2b79 100644 --- a/wowup-electron/src/app/services/storage/addon-storage.service.ts +++ b/wowup-electron/src/app/services/storage/addon-storage.service.ts @@ -33,7 +33,7 @@ export class AddonStorageService { return addons; } - public setAll(addons: Addon[]) { + public saveAll(addons: Addon[]) { addons.forEach(addon => this.set(addon.id, addon)); } @@ -53,7 +53,7 @@ export class AddonStorageService { this._store.delete(addon.id); } - public removeForClientType(clientType: WowClientType) { + public removeAllForClientType(clientType: WowClientType) { const addons = this.getAllForClientType(clientType); addons.forEach(addon => this._store.delete(addon.id)); } diff --git a/wowup-electron/src/environments/environment.ts b/wowup-electron/src/environments/environment.ts index f3fcde6b..984e7d14 100644 --- a/wowup-electron/src/environments/environment.ts +++ b/wowup-electron/src/environments/environment.ts @@ -1,6 +1,6 @@ export const AppConfig = { production: false, - environment: 'LOCAL', - wowUpApiUrl: 'https://api.dev.wowup.io', - wowUpHubUrl: 'https://hub.dev.wowup.io' + environment: "LOCAL", + wowUpApiUrl: "https://api.dev.wowup.io", + wowUpHubUrl: "https://hub.dev.wowup.io", }; From 89b635786b9c6696f5591de267ffb2da354f8bac Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 01:14:08 -0500 Subject: [PATCH 02/34] More circuit breakers Add circuit breaker handling to the install from url box. --- .../addon-providers/curse-addon-provider.ts | 112 +++++++++---- .../addon-providers/tukui-addon-provider.ts | 158 +++++++++++------- .../wow-interface-addon-provider.ts | 109 ++++++++---- .../addon-providers/wowup-addon-provider.ts | 23 ++- .../install-from-url-dialog.component.ts | 93 ++++++----- 5 files changed, 328 insertions(+), 167 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 836f237d..011504b1 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -5,7 +5,7 @@ import { HttpClient } from "@angular/common/http"; import { map } from "rxjs/operators"; import * as _ from "lodash"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; -import { Observable, of } from "rxjs"; +import { from, Observable, of } from "rxjs"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; import { AddonChannelType } from "../models/wowup/addon-channel-type"; import { PotentialAddon } from "../models/wowup/potential-addon"; @@ -23,17 +23,44 @@ import { CurseSearchResult } from "../../common/curse/curse-search-result"; import { CurseFile } from "common/curse/curse-file"; import { CurseReleaseType } from "common/curse/curse-release-type"; import { CurseGetFeaturedResponse } from "app/models/curse/curse-get-featured-response"; +import * as CircuitBreaker from "opossum"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; export class CurseAddonProvider implements AddonProvider { + private readonly _circuitBreaker: CircuitBreaker< + [clientType: () => Promise], + any + >; + + private getCircuitBreaker() { + return this._circuitBreaker as CircuitBreaker< + [clientType: () => Promise], + T + >; + } + public readonly name = "Curse"; constructor( private _httpClient: HttpClient, private _cachingService: CachingService, private _electronService: ElectronService - ) {} + ) { + this._circuitBreaker = new CircuitBreaker( + (action) => this.sendRequest(action), + { + resetTimeout: 60000, + } + ); + + this._circuitBreaker.on("open", () => { + console.log(`${this.name} circuit breaker open`); + }); + this._circuitBreaker.on("close", () => { + console.log(`${this.name} circuit breaker close`); + }); + } async scan( clientType: WowClientType, @@ -58,7 +85,7 @@ export class CurseAddonProvider implements AddonProvider { ); const addonIds = _.uniq(matchedScanResultIds); - var addonResults = await this.getAllIds(addonIds).toPromise(); + var addonResults = await this.getAllIds(addonIds); for (let addonFolder of addonFolders) { var scanResult = scanResults.find( @@ -100,7 +127,7 @@ export class CurseAddonProvider implements AddonProvider { const fingerprintResponse = await this.getAddonsByFingerprints( scanResults.map((result) => result.fingerprint) - ).toPromise(); + ); console.log(fingerprintResponse); @@ -142,22 +169,36 @@ export class CurseAddonProvider implements AddonProvider { return gameVersionFlavor === this.getGameVersionFlavor(clientType); } - private getAddonsByFingerprints( + private async getAddonsByFingerprints( fingerprints: number[] - ): Observable { + ): Promise { const url = `${API_URL}/fingerprint`; - return this._httpClient.post(url, fingerprints); + return await this.getCircuitBreaker().fire( + async () => + await this._httpClient + .post(url, fingerprints) + .toPromise() + ); } - private getAllIds(addonIds: number[]): Observable { + private async getAllIds(addonIds: number[]): Promise { if (!addonIds?.length) { - return of([]); + return []; } const url = `${API_URL}/addon`; - return this._httpClient.post(url, addonIds); + return await this.getCircuitBreaker().fire( + async () => + await this._httpClient + .post(url, addonIds) + .toPromise() + ); + } + + private sendRequest(action: () => Promise): Promise { + return action.call(this); } private getScanResults = async ( @@ -206,7 +247,7 @@ export class CurseAddonProvider implements AddonProvider { const addonResults: AddonSearchResult[] = []; const searchResults = await this.getAllIds( addonIds.map((id) => parseInt(id, 10)) - ).toPromise(); + ); for (let result of searchResults) { const latestFiles = this.getLatestFiles(result, clientType); @@ -224,7 +265,7 @@ export class CurseAddonProvider implements AddonProvider { } getFeaturedAddons(clientType: WowClientType): Observable { - return this.getFeaturedAddonList().pipe( + return from(this.getFeaturedAddonList()).pipe( map((addons) => { return this.filterFeaturedAddons(addons, clientType); }), @@ -259,7 +300,7 @@ export class CurseAddonProvider implements AddonProvider { ): Promise { var searchResults: PotentialAddon[] = []; - var response = await this.getSearchResults(query).toPromise(); + var response = await this.getSearchResults(query); for (let result of response) { var latestFiles = this.getLatestFiles(result, clientType); if (!latestFiles.length) { @@ -288,12 +329,17 @@ export class CurseAddonProvider implements AddonProvider { throw new Error("Method not implemented."); } - private getSearchResults(query: string): Observable { + private async getSearchResults(query: string): Promise { const url = new URL(`${API_URL}/addon/search`); url.searchParams.set("gameId", "1"); url.searchParams.set("searchFilter", query); - return this._httpClient.get(url.toString()); + return await this.getCircuitBreaker().fire( + async () => + await this._httpClient + .get(url.toString()) + .toPromise() + ); } getById( @@ -302,7 +348,12 @@ export class CurseAddonProvider implements AddonProvider { ): Observable { const url = `${API_URL}/addon/${addonId}`; - return this._httpClient.get(url).pipe( + return from( + this.getCircuitBreaker().fire( + async () => + await this._httpClient.get(url).toPromise() + ) + ).pipe( map((result) => { if (!result) { return null; @@ -384,13 +435,13 @@ export class CurseAddonProvider implements AddonProvider { } } - private getFeaturedAddonList(): Observable { + private async getFeaturedAddonList(): Promise { const url = `${API_URL}/addon/featured`; const cachedResponse = this._cachingService.get( url ); if (cachedResponse) { - return of(cachedResponse.Popular); + return cachedResponse.Popular; } const body = { @@ -400,17 +451,22 @@ export class CurseAddonProvider implements AddonProvider { updatedCount: 0, }; - return this._httpClient.post(url, body).pipe( - map((result) => { - if (!result) { - return []; - } - - this._cachingService.set(url, result); - - return result.Popular; - }) + const result = await this.getCircuitBreaker< + CurseGetFeaturedResponse + >().fire( + async () => + await this._httpClient + .post(url, body) + .toPromise() ); + + if (!result) { + return []; + } + + this._cachingService.set(url, result); + + return result.Popular; } private getChannelType(releaseType: CurseReleaseType): AddonChannelType { diff --git a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts index 5eed34a9..085ad0af 100644 --- a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts @@ -11,45 +11,53 @@ import { ElectronService } from "app/services/electron/electron.service"; import { FileService } from "app/services/files/file.service"; import { from, Observable, of } from "rxjs"; import { AddonProvider } from "./addon-provider"; -import * as _ from 'lodash'; +import * as _ from "lodash"; import { AddonSearchResultFile } from "app/models/wowup/addon-search-result-file"; import { map } from "rxjs/operators"; -import { v4 as uuidv4 } from 'uuid'; -import * as CircuitBreaker from 'opossum'; +import { v4 as uuidv4 } from "uuid"; +import * as CircuitBreaker from "opossum"; const API_URL = "https://www.tukui.org/api.php"; const CLIENT_API_URL = "https://www.tukui.org/client-api.php"; const CACHE_TIME = 10 * 60 * 1000; export class TukUiAddonProvider implements AddonProvider { + private readonly _circuitBreaker: CircuitBreaker< + [clientType: WowClientType], + TukUiAddon[] + >; public readonly name = "TukUI"; - private readonly _circuitBreaker: CircuitBreaker<[clientType: WowClientType], TukUiAddon[]>; - constructor( private _httpClient: HttpClient, private _cachingService: CachingService, private _electronService: ElectronService, private _fileService: FileService ) { - this._circuitBreaker = new CircuitBreaker( - this.fetchApiResults, - { - resetTimeout: 60000 - }); + this._circuitBreaker = new CircuitBreaker(this.fetchApiResults, { + resetTimeout: 60000, + }); - this._circuitBreaker.on('open', () => { console.log(`${this.name} circuit breaker open`); }); - this._circuitBreaker.on('close', () => { console.log(`${this.name} circuit breaker close`); }); + this._circuitBreaker.on("open", () => { + console.log(`${this.name} circuit breaker open`); + }); + this._circuitBreaker.on("close", () => { + console.log(`${this.name} circuit breaker close`); + }); } - async getAll(clientType: WowClientType, addonIds: string[]): Promise { + async getAll( + clientType: WowClientType, + addonIds: string[] + ): Promise { let results: AddonSearchResult[] = []; try { const addons = await this.getAllAddons(clientType); - results = addons.filter(addon => _.some(addonIds, aid => aid === addon.id)) - .map(addon => this.toSearchResult(addon, '')); + results = addons + .filter((addon) => _.some(addonIds, (aid) => aid === addon.id)) + .map((addon) => this.toSearchResult(addon, "")); } catch (err) { // _analyticsService.Track(ex, "Failed to search TukUi"); } @@ -58,28 +66,41 @@ export class TukUiAddonProvider implements AddonProvider { } getFeaturedAddons(clientType: WowClientType): Observable { - return from(this.getAllAddons(clientType)) - .pipe( - map(tukUiAddons => { - return tukUiAddons.map(addon => this.toPotentialAddon(addon)); - }) - ); + return from(this.getAllAddons(clientType)).pipe( + map((tukUiAddons) => { + return tukUiAddons.map((addon) => this.toPotentialAddon(addon)); + }) + ); } - async searchByQuery(query: string, clientType: WowClientType): Promise { + async searchByQuery( + query: string, + clientType: WowClientType + ): Promise { const addons = await this.getAllAddons(clientType); const canonQuery = query.toLowerCase(); - let similarAddons = _.filter(addons, addon => addon.name.toLowerCase().indexOf(canonQuery) !== -1); - similarAddons = _.orderBy(similarAddons, ['downloads']); + let similarAddons = _.filter( + addons, + (addon) => addon.name.toLowerCase().indexOf(canonQuery) !== -1 + ); + similarAddons = _.orderBy(similarAddons, ["downloads"]); - return _.map(similarAddons, addon => this.toPotentialAddon(addon)); + return _.map(similarAddons, (addon) => this.toPotentialAddon(addon)); } - searchByUrl(addonUri: URL, clientType: WowClientType): Promise { + searchByUrl( + addonUri: URL, + clientType: WowClientType + ): Promise { throw new Error("Method not implemented."); } - async searchByName(addonName: string, folderName: string, clientType: WowClientType, nameOverride?: string): Promise { + async searchByName( + addonName: string, + folderName: string, + clientType: WowClientType, + nameOverride?: string + ): Promise { const results: AddonSearchResult[] = []; try { const addons = await this.searchAddons(addonName, clientType); @@ -94,31 +115,42 @@ export class TukUiAddonProvider implements AddonProvider { return results; } - getById(addonId: string, clientType: WowClientType): Observable { - return from(this.getAllAddons(clientType)) - .pipe( - map(addons => { - const match = _.find(addons, addon => addon.id === addonId); - return this.toSearchResult(match, ''); - }) - ) + getById( + addonId: string, + clientType: WowClientType + ): Observable { + return from(this.getAllAddons(clientType)).pipe( + map((addons) => { + const match = _.find(addons, (addon) => addon.id === addonId); + return this.toSearchResult(match, ""); + }) + ); } isValidAddonUri(addonUri: URL): boolean { return false; } - onPostInstall(addon: Addon): void { - } + onPostInstall(addon: Addon): void {} - async scan(clientType: WowClientType, addonChannelType: AddonChannelType, addonFolders: AddonFolder[]): Promise { + async scan( + clientType: WowClientType, + addonChannelType: AddonChannelType, + addonFolders: AddonFolder[] + ): Promise { const allAddons = await this.getAllAddons(clientType); for (let addonFolder of addonFolders) { let tukUiAddon: TukUiAddon; if (addonFolder.toc?.tukUiProjectId) { - tukUiAddon = _.find(allAddons, addon => addon.id.toString() === addonFolder.toc.tukUiProjectId); + tukUiAddon = _.find( + allAddons, + (addon) => addon.id.toString() === addonFolder.toc.tukUiProjectId + ); } else { - const results = await this.searchAddons(addonFolder.toc.title, clientType); + const results = await this.searchAddons( + addonFolder.toc.title, + clientType + ); tukUiAddon = _.first(results); } @@ -142,16 +174,17 @@ export class TukUiAddonProvider implements AddonProvider { latestVersion: tukUiAddon.version, providerName: this.name, thumbnailUrl: tukUiAddon.screenshot_url, - updatedAt: new Date() - } + updatedAt: new Date(), + }; } } } private async searchAddons(addonName: string, clientType: WowClientType) { var addons = await this.getAllAddons(clientType); - return addons - .filter(addon => addon.name.toLowerCase() === addonName.toLowerCase()); + return addons.filter( + (addon) => addon.name.toLowerCase() === addonName.toLowerCase() + ); } private toPotentialAddon(addon: TukUiAddon): PotentialAddon { @@ -163,11 +196,14 @@ export class TukUiAddonProvider implements AddonProvider { name: addon.name, providerName: this.name, thumbnailUrl: addon.screenshot_url, - summary: addon.small_desc + summary: addon.small_desc, }; } - private toSearchResult(addon: TukUiAddon, folderName: string): AddonSearchResult | undefined { + private toSearchResult( + addon: TukUiAddon, + folderName: string + ): AddonSearchResult | undefined { if (!addon) { return undefined; } @@ -178,7 +214,7 @@ export class TukUiAddonProvider implements AddonProvider { downloadUrl: addon.url, gameVersion: addon.patch, version: addon.version, - releaseDate: new Date(addon.lastUpdate) + releaseDate: new Date(addon.lastUpdate), }; return { @@ -188,11 +224,13 @@ export class TukUiAddonProvider implements AddonProvider { thumbnailUrl: addon.screenshot_url, externalUrl: addon.web_url, providerName: this.name, - files: [latestFile] + files: [latestFile], }; } - private getAllAddons = async (clientType: WowClientType): Promise => { + private getAllAddons = async ( + clientType: WowClientType + ): Promise => { if (clientType === WowClientType.None) { return []; } @@ -206,40 +244,42 @@ export class TukUiAddonProvider implements AddonProvider { try { const addons = await this._circuitBreaker.fire(clientType); - console.log('CACHED') + console.log("CACHED"); this._cachingService.set(cacheKey, addons, CACHE_TIME); return addons; } catch (err) { console.error(err); return []; } - } + }; private fetchApiResults = async (clientType: WowClientType) => { const query = this.getAddonsSuffix(clientType); const url = new URL(API_URL); - url.searchParams.append(query, 'all'); + url.searchParams.append(query, "all"); - const addons = await this._httpClient.get(url.toString()).toPromise(); + const addons = await this._httpClient + .get(url.toString()) + .toPromise(); if (this.isRetail(clientType)) { addons.push(await this.getTukUiRetailAddon().toPromise()); addons.push(await this.getElvUiRetailAddon().toPromise()); } return addons; - } + }; private getTukUiRetailAddon() { - return this.getClientApiAddon('tukui'); + return this.getClientApiAddon("tukui"); } private getElvUiRetailAddon() { - return this.getClientApiAddon('elvui'); + return this.getClientApiAddon("elvui"); } private getClientApiAddon(addonName: string): Observable { const url = new URL(CLIENT_API_URL); - url.searchParams.append('ui', addonName); + url.searchParams.append("ui", addonName); return this._httpClient.get(url.toString()); } @@ -265,7 +305,7 @@ export class TukUiAddonProvider implements AddonProvider { case WowClientType.Beta: return "addons"; default: - return ''; + return ""; } } @@ -279,7 +319,7 @@ export class TukUiAddonProvider implements AddonProvider { case WowClientType.Beta: return "tukui_addons"; default: - return ''; + return ""; } } } diff --git a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts index 82f356d8..151a248b 100644 --- a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts @@ -11,15 +11,21 @@ import { CachingService } from "app/services/caching/caching-service"; import { FileService } from "app/services/files/file.service"; import { from, Observable, of } from "rxjs"; import { AddonProvider } from "./addon-provider"; -import * as _ from 'lodash'; +import * as _ from "lodash"; import { AddonSearchResultFile } from "app/models/wowup/addon-search-result-file"; import { map } from "rxjs/operators"; -import { v4 as uuidv4 } from 'uuid'; +import { v4 as uuidv4 } from "uuid"; +import * as CircuitBreaker from "opossum"; const API_URL = "https://api.mmoui.com/v4/game/WOW"; const ADDON_URL = "https://www.wowinterface.com/downloads/info"; export class WowInterfaceAddonProvider implements AddonProvider { + private readonly _circuitBreaker: CircuitBreaker< + [addonId: string], + AddonDetailsResponse + >; + public readonly name = "WowInterface"; constructor( @@ -27,9 +33,23 @@ export class WowInterfaceAddonProvider implements AddonProvider { private _cachingService: CachingService, private _electronService: ElectronService, private _fileService: FileService - ) { } + ) { + this._circuitBreaker = new CircuitBreaker(this.getAddonDetails, { + resetTimeout: 60000, + }); - async getAll(clientType: WowClientType, addonIds: string[]): Promise { + this._circuitBreaker.on("open", () => { + console.log(`${this.name} circuit breaker open`); + }); + this._circuitBreaker.on("close", () => { + console.log(`${this.name} circuit breaker close`); + }); + } + + async getAll( + clientType: WowClientType, + addonIds: string[] + ): Promise { var searchResults: AddonSearchResult[] = []; for (let addonId of addonIds) { @@ -48,17 +68,23 @@ export class WowInterfaceAddonProvider implements AddonProvider { return of([]); } - async searchByQuery(query: string, clientType: WowClientType): Promise { + async searchByQuery( + query: string, + clientType: WowClientType + ): Promise { return []; } - async searchByUrl(addonUri: URL, clientType: WowClientType): Promise { + async searchByUrl( + addonUri: URL, + clientType: WowClientType + ): Promise { const addonId = this.getAddonId(addonUri); if (!addonId) { throw new Error(`Addon ID not found ${addonUri}`); } - var addon = await this.getAddonDetails(addonId).toPromise(); + var addon = await this._circuitBreaker.fire(addonId); if (addon == null) { throw new Error(`Bad addon api response ${addonUri}`); } @@ -66,33 +92,54 @@ export class WowInterfaceAddonProvider implements AddonProvider { return this.toPotentialAddon(addon); } - searchByName(addonName: string, folderName: string, clientType: WowClientType, nameOverride?: string): Promise { + searchByName( + addonName: string, + folderName: string, + clientType: WowClientType, + nameOverride?: string + ): Promise { throw new Error("Method not implemented."); } - getById(addonId: string, clientType: WowClientType): Observable { - return from(this.getAddonDetails(addonId)) - .pipe( - map(result => result ? this.toAddonSearchResult(result, '') : undefined) - ); + getById( + addonId: string, + clientType: WowClientType + ): Observable { + return from(this._circuitBreaker.fire(addonId)).pipe( + map((result) => + result ? this.toAddonSearchResult(result, "") : undefined + ) + ); } isValidAddonUri(addonUri: URL): boolean { - return addonUri.host && addonUri.host.endsWith('wowinterface.com'); + return addonUri.host && addonUri.host.endsWith("wowinterface.com"); } onPostInstall(addon: Addon): void { throw new Error("Method not implemented."); } - async scan(clientType: WowClientType, addonChannelType: AddonChannelType, addonFolders: AddonFolder[]): Promise { + async scan( + clientType: WowClientType, + addonChannelType: AddonChannelType, + addonFolders: AddonFolder[] + ): Promise { for (let addonFolder of addonFolders) { if (!addonFolder?.toc?.wowInterfaceId) { continue; } - const details = await this.getAddonDetails(addonFolder.toc.wowInterfaceId).toPromise(); - addonFolder.matchingAddon = this.toAddon(details, clientType, addonChannelType, addonFolder); + const details = await this._circuitBreaker.fire( + addonFolder.toc.wowInterfaceId + ); + + addonFolder.matchingAddon = this.toAddon( + details, + clientType, + addonChannelType, + addonFolder + ); } } @@ -103,14 +150,17 @@ export class WowInterfaceAddonProvider implements AddonProvider { return match[1]; } - private getAddonDetails(addonId: string): Observable { + private getAddonDetails = ( + addonId: string + ): Promise => { + console.debug('getAddonDetails'); + throw new Error('test') const url = new URL(`${API_URL}/filedetails/${addonId}.json`); return this._httpClient .get(url.toString()) - .pipe( - map(responses => _.first(responses)) - ); + .pipe(map((responses) => _.first(responses))) + .toPromise(); }; private getThumbnailUrl(response: AddonDetailsResponse) { @@ -137,7 +187,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { externalId: response.id.toString(), externalUrl: this.getAddonUrl(response), folderName: addonFolder.name, - gameVersion: '', + gameVersion: "", installedAt: new Date(), installedFolders: addonFolder.name, installedVersion: addonFolder.toc?.version, @@ -145,7 +195,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { latestVersion: response.version, name: response.title, providerName: this.name, - thumbnailUrl: this.getThumbnailUrl(response) + thumbnailUrl: this.getThumbnailUrl(response), }; } @@ -161,15 +211,18 @@ export class WowInterfaceAddonProvider implements AddonProvider { }; } - private toAddonSearchResult(response: AddonDetailsResponse, folderName: string): AddonSearchResult { + private toAddonSearchResult( + response: AddonDetailsResponse, + folderName: string + ): AddonSearchResult { try { var searchResultFile: AddonSearchResultFile = { channelType: AddonChannelType.Stable, version: response.version, downloadUrl: response.downloadUri, folders: [folderName], - gameVersion: '', - releaseDate: new Date() + gameVersion: "", + releaseDate: new Date(), }; return { @@ -179,10 +232,10 @@ export class WowInterfaceAddonProvider implements AddonProvider { thumbnailUrl: this.getThumbnailUrl(response), externalUrl: this.getAddonUrl(response), providerName: this.name, - files: [searchResultFile] + files: [searchResultFile], }; } catch (err) { - console.error('Failed to create addon search result', err); + console.error("Failed to create addon search result", err); return undefined; } } diff --git a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts index 2c74d530..3e3ac652 100644 --- a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts @@ -39,10 +39,12 @@ export class WowUpAddonProvider implements AddonProvider { .get(url.toString()) .toPromise(); - throw new Error("Method not implemented."); + // TODO + return []; } getFeaturedAddons(clientType: WowClientType): Observable { + // TODO return of([]); } @@ -50,34 +52,39 @@ export class WowUpAddonProvider implements AddonProvider { query: string, clientType: WowClientType ): Promise { + // TODO return []; } - searchByUrl( + async searchByUrl( addonUri: URL, clientType: WowClientType ): Promise { - throw new Error("Method not implemented."); + // TODO + return undefined; } - searchByName( + async searchByName( addonName: string, folderName: string, clientType: WowClientType, nameOverride?: string ): Promise { - throw new Error("Method not implemented."); + // TODO + return []; } getById( addonId: string, clientType: WowClientType ): Observable { - throw new Error("Method not implemented."); + // TODO + return of(undefined); } isValidAddonUri(addonUri: URL): boolean { - throw new Error("Method not implemented."); + // TODO + return false; } onPostInstall(addon: Addon): void { @@ -255,7 +262,7 @@ export class WowUpAddonProvider implements AddonProvider { thumbnailUrl: scanResult.exactMatch.image_url, patreonFundingLink: scanResult.exactMatch.patreon_funding_link, customFundingLink: scanResult.exactMatch.custom_funding_link, - githubFundingLink: scanResult.exactMatch.github_funding_link + githubFundingLink: scanResult.exactMatch.github_funding_link, }; } } diff --git a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts index ba43a7b2..ef2074a9 100644 --- a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts +++ b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts @@ -1,24 +1,23 @@ -import { HttpErrorResponse } from '@angular/common/http'; -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { PotentialAddon } from 'app/models/wowup/potential-addon'; -import { AddonService } from 'app/services/addons/addon.service'; -import { SessionService } from 'app/services/session/session.service'; -import { from, Subscription } from 'rxjs'; -import { AlertDialogComponent } from '../alert-dialog/alert-dialog.component'; +import { HttpErrorResponse } from "@angular/common/http"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { MatDialog, MatDialogRef } from "@angular/material/dialog"; +import { PotentialAddon } from "app/models/wowup/potential-addon"; +import { AddonService } from "app/services/addons/addon.service"; +import { SessionService } from "app/services/session/session.service"; +import { from, Subscription } from "rxjs"; +import { AlertDialogComponent } from "../alert-dialog/alert-dialog.component"; @Component({ - selector: 'app-install-from-url-dialog', - templateUrl: './install-from-url-dialog.component.html', - styleUrls: ['./install-from-url-dialog.component.scss'] + selector: "app-install-from-url-dialog", + templateUrl: "./install-from-url-dialog.component.html", + styleUrls: ["./install-from-url-dialog.component.scss"], }) export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { - public isBusy = false; public showInstallSpinner = false; public showInstallButton = false; public showInstallSuccess = false; - public query = ''; + public query = ""; public addon?: PotentialAddon; private _installSubscription?: Subscription; @@ -27,11 +26,10 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { private _addonService: AddonService, private _dialog: MatDialog, private _sessionService: SessionService, - public dialogRef: MatDialogRef, - ) { } + public dialogRef: MatDialogRef + ) {} - ngOnInit(): void { - } + ngOnInit(): void {} ngOnDestroy() { this._installSubscription?.unsubscribe(); @@ -42,7 +40,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { } onClearSearch() { - this.query = ''; + this.query = ""; this.onImportUrl(); } @@ -50,20 +48,23 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { this.showInstallButton = false; this.showInstallSpinner = true; - this._installSubscription = from(this._addonService - .installPotentialAddon(this.addon, this._sessionService.selectedClientType)) - .subscribe({ - next: () => { - this.showInstallSpinner = false; - this.showInstallSuccess = true; - }, - error: (err) => { - console.error(err); - this.showInstallSpinner = false; - this.showInstallButton = true; - this.showErrorMessage('Failed to install addon.'); - } - }); + this._installSubscription = from( + this._addonService.installPotentialAddon( + this.addon, + this._sessionService.selectedClientType + ) + ).subscribe({ + next: () => { + this.showInstallSpinner = false; + this.showInstallSuccess = true; + }, + error: (err) => { + console.error(err); + this.showInstallSpinner = false; + this.showInstallButton = true; + this.showErrorMessage("Failed to install addon."); + }, + }); } async onImportUrl() { @@ -81,12 +82,14 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { } try { - const importedAddon = await this._addonService - .getAddonByUrl(url, this._sessionService.selectedClientType); + const importedAddon = await this._addonService.getAddonByUrl( + url, + this._sessionService.selectedClientType + ); console.log(importedAddon); if (!importedAddon) { - throw new Error('Addon not found'); + throw new Error("Addon not found"); } this.addon = importedAddon; @@ -98,13 +101,14 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { } this.showInstallButton = true; - } - catch (err) { + } catch (err) { console.error(err); let message = err.message; if (err instanceof HttpErrorResponse) { message = `No addon was found.`; + } else if (err.code && err.code === "EOPENBREAKER") { // Provider circuit breaker is open + message = `Cannot connect to API, please wait a bit and try again.`; } this.showErrorMessage(message); @@ -112,16 +116,18 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { } private addonExists(externalId: string) { - return this._addonService.isInstalled(externalId, this._sessionService.selectedClientType); + return this._addonService.isInstalled( + externalId, + this._sessionService.selectedClientType + ); } private getUrlFromQuery(): URL | undefined { try { return new URL(this.query); - } - catch (err) { + } catch (err) { console.error(`Invalid url: ${this.query}`); - this.showErrorMessage('Invalid URL.'); + this.showErrorMessage("Invalid URL."); return undefined; } } @@ -131,10 +137,9 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { minWidth: 250, data: { title: `Error`, - message: errorMessage - } + message: errorMessage, + }, }); dialogRef.afterClosed().subscribe(); } - } From d89d4b6043de6016a694df73a89b8cc594d108d9 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 01:49:23 -0500 Subject: [PATCH 03/34] Changelog and about font change --- wowup-electron/src/app/pages/about/about.component.html | 2 +- wowup-electron/src/app/pages/about/about.component.scss | 4 ++-- wowup-electron/src/assets/changelog.json | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/wowup-electron/src/app/pages/about/about.component.html b/wowup-electron/src/app/pages/about/about.component.html index 378594e0..e413fb4a 100644 --- a/wowup-electron/src/app/pages/about/about.component.html +++ b/wowup-electron/src/app/pages/about/about.component.html @@ -17,7 +17,7 @@
  • -
    {{cl.Version}}
    +
    {{cl.Version}}
    {{cl.Description}}
diff --git a/wowup-electron/src/app/pages/about/about.component.scss b/wowup-electron/src/app/pages/about/about.component.scss index a254e815..4c138de2 100644 --- a/wowup-electron/src/app/pages/about/about.component.scss +++ b/wowup-electron/src/app/pages/about/about.component.scss @@ -52,8 +52,8 @@ background-color: $dark-4; .version { - font-size: 1em; - font-weight: bold; + // font-size: 1em; + // font-weight: bold; } .description { diff --git a/wowup-electron/src/assets/changelog.json b/wowup-electron/src/assets/changelog.json index a527e559..ad3de123 100644 --- a/wowup-electron/src/assets/changelog.json +++ b/wowup-electron/src/assets/changelog.json @@ -1,5 +1,13 @@ { "ChangeLogs": [ + { + "Version": "2.0.0-alpha.8", + "Description": "Localize most static text (by Pansa and Medok).\nImplemented addon detail view in my-addons (by Flippeey).\nWindow position/size/maximized state should now be restored when starting the app (by Chops).\nDouble clicking the titlebar on mac should perform the action set in your system settings (by Chops).\nAddon scanning is now much faster.\nWhen scanning addons, the matching channel type should be applied not the default.\nRe-scan is now less complicated and should prevent duplicates.\nAdd the 'Show Folder' button to the addon context menu.\nAdd http circuit breakers to the providers.\nFix issue with My Addons Provider column not sorting.\nPage context data on the footer.\nRemove the windows menu bar buttons for mac.\nAdd WowUp addon provider prototype" + }, + { + "Version": "2.0.0-alpha.7", + "Description": "Stuff?" + }, { "Version": "1.16.1", "Description": "Default addon channel can now be set separately per World of Warcraft client.\nUI updates.\nBug Fixes." From 807b4c82204b063d8201a9816aa5ed3354c59b6a Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 01:55:07 -0500 Subject: [PATCH 04/34] Tidy up --- wowup-electron/package.json | 1 + .../src/app/services/addons/addon.service.ts | 10 +--------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/wowup-electron/package.json b/wowup-electron/package.json index 50104bf4..15d1828d 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -69,6 +69,7 @@ "@types/node": "12.12.62", "@types/opossum": "4.1.1", "@types/rimraf": "3.0.0", + "@types/uuid": "8.3.0", "@typescript-eslint/eslint-plugin": "4.3.0", "@typescript-eslint/eslint-plugin-tslint": "4.3.0", "@typescript-eslint/parser": "4.3.0", diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 70102327..5f396b22 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -1,15 +1,13 @@ -import { Injectable, Injector } from "@angular/core"; +import { Injectable } from "@angular/core"; import { AddonStorageService } from "../storage/addon-storage.service"; import { Addon } from "../../entities/addon"; import { WarcraftService } from "../warcraft/warcraft.service"; import { AddonProvider } from "../../addon-providers/addon-provider"; import { CurseAddonProvider } from "../../addon-providers/curse-addon-provider"; -import { HttpClient } from "@angular/common/http"; import * as _ from "lodash"; import { v4 as uuidv4 } from "uuid"; import * as path from "path"; import * as fs from "fs"; -import { WowUpApiService } from "../wowup-api/wowup-api.service"; import { WowClientType } from "app/models/warcraft/wow-client-type"; import { PotentialAddon } from "app/models/wowup/potential-addon"; import { AddonFolder } from "app/models/wowup/addon-folder"; @@ -18,17 +16,12 @@ import { AddonSearchResult } from "app/models/wowup/addon-search-result"; import { AddonSearchResultFile } from "app/models/wowup/addon-search-result-file"; import { forkJoin, Observable, Subject } from "rxjs"; import { map } from "rxjs/operators"; -import { CachingService } from "../caching/caching-service"; import { AddonInstallState } from "app/models/wowup/addon-install-state"; import { DownloadSevice } from "../download/download.service"; import { WowUpService } from "../wowup/wowup.service"; import { FileService } from "../files/file.service"; import { TocService } from "../toc/toc.service"; -import { ElectronService } from "../electron/electron.service"; -import { TukUiAddonProvider } from "app/addon-providers/tukui-addon-provider"; import { AddonUpdateEvent } from "app/models/wowup/addon-update-event"; -import { WowInterfaceAddonProvider } from "app/addon-providers/wow-interface-addon-provider"; -import { GitHubAddonProvider } from "app/addon-providers/github-addon-provider"; import { AddonProviderFactory } from "./addon.provider.factory"; @Injectable({ @@ -52,7 +45,6 @@ export class AddonService { private _addonProviderFactory: AddonProviderFactory ) { this._addonProviders = [ - this._addonProviderFactory.createWowUpAddonProvider(), this._addonProviderFactory.createCurseAddonProvider(), this._addonProviderFactory.createTukUiAddonProvider(), this._addonProviderFactory.createWowInterfaceAddonProvider(), From 9a272836e416a1905ffd7690debbb70083001700 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 10:42:03 -0500 Subject: [PATCH 05/34] RU locale changes. Minor changes. Remove test error... Try disabling hardware acceleration. Remove all bold fonts as they are blurry. --- wowup-electron/main.ts | 2 + .../wow-interface-addon-provider.ts | 1 - .../addon-detail/addon-detail.component.scss | 2 +- .../my-addons-addon-cell.component.scss | 2 +- .../get-addons/get-addons.component.scss | 2 +- .../pages/my-addons/my-addons.component.scss | 2 +- wowup-electron/src/assets/i18n/ru.json | 74 +++++++++---------- wowup-electron/src/styles.scss | 2 +- 8 files changed, 44 insertions(+), 43 deletions(-) diff --git a/wowup-electron/main.ts b/wowup-electron/main.ts index 52ff67ea..05818d0c 100644 --- a/wowup-electron/main.ts +++ b/wowup-electron/main.ts @@ -104,6 +104,8 @@ const appMenuTemplate: Array = isMac const appMenu = Menu.buildFromTemplate(appMenuTemplate); Menu.setApplicationMenu(appMenu); +app.disableHardwareAcceleration(); // Try to improve font blur? + const LOG_PATH = path.join(app.getPath("userData"), "logs"); app.setAppLogsPath(LOG_PATH); log.transports.file.resolvePath = ( diff --git a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts index 151a248b..6a2dcc5d 100644 --- a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts @@ -154,7 +154,6 @@ export class WowInterfaceAddonProvider implements AddonProvider { addonId: string ): Promise => { console.debug('getAddonDetails'); - throw new Error('test') const url = new URL(`${API_URL}/filedetails/${addonId}.json`); return this._httpClient diff --git a/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss b/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss index 7a942863..83c647e0 100644 --- a/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss +++ b/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss @@ -16,7 +16,7 @@ } .addon-detail-summary { margin-bottom: 8px; - font-weight: bold; + // font-weight: bold; font-size: 14px; letter-spacing: 0.6px; } diff --git a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss index 7a2dadfa..4d1bf3c7 100644 --- a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss +++ b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss @@ -27,7 +27,7 @@ .channel { background: $dark-4; text-align: center; - font-weight: bold; + font-weight: 400; font-size: 0.8em; &.beta { diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss index 368a343a..1edef68e 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss @@ -64,7 +64,7 @@ } .addon-title { - font-weight: bold; + font-weight: 400; font-size: 1.1em; word-break: break-all; white-space: pre-wrap; diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss index 4b9c2499..1911ad0b 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss @@ -69,7 +69,7 @@ } .addon-title { - font-weight: bold; + font-weight: 400; font-size: 1.1em; word-break: break-all; white-space: pre-wrap; diff --git a/wowup-electron/src/assets/i18n/ru.json b/wowup-electron/src/assets/i18n/ru.json index 11d68759..c12cce69 100644 --- a/wowup-electron/src/assets/i18n/ru.json +++ b/wowup-electron/src/assets/i18n/ru.json @@ -3,55 +3,55 @@ "ABOUT": { "CHANGE_LOG_SECTION_LABEL": "Журнал изменений", "TITLE": "WowUp.io", - "WEBSITE_LINK_LABEL": "Посмотрите на сайт!" + "WEBSITE_LINK_LABEL": "Посетите наш сайт!" }, "GET_ADDONS": { "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", "REFRESH_BUTTON": "Обновить", - "INSTALL_FROM_URL_BUTTON": "Установить из URL", + "INSTALL_FROM_URL_BUTTON": "Установить по ссылке", "SEARCH_LABEL": "Искать", "TABLE": { - "ADDON_COLUMN_HEADER": "Addon", + "ADDON_COLUMN_HEADER": "Модификация", "AUTHOR_COLUMN_HEADER": "Автор", - "PROVIDER_COLUMN_HEADER": "Поставщик", + "PROVIDER_COLUMN_HEADER": "Источник", "STATUS_COLUMN_HEADER": "Статус" } }, "HOME": { "TITLE": "Приложение работает !", "GO_TO_DETAIL": "Детали", - "MY_ADDONS_TAB_TITLE": "Мои аддоны", - "GET_ADDONS_TAB_TITLE": "Получить аддоны", + "MY_ADDONS_TAB_TITLE": "Мои модификации", + "GET_ADDONS_TAB_TITLE": "Получить модификации", "ABOUT_TAB_TITLE": "О программе", - "OPTIONS_TAB_TITLE": "Варианты" + "OPTIONS_TAB_TITLE": "Настройки" }, "MY_ADDONS": { "CHECK_UPDATES_BUTTON": "Проверить обновления", - "CHECK_UPDATES_BUTTON_TOOLTIP": "Проверить наличие последних обновлений аддона", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Проверить наличие последних обновлений модификации", "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", - "RESCAN_FOLDERS_BUTTON": "Пересканировать папки", - "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Сканирование клиентской папки для установленных аддонов", - "UPDATE_ALL_BUTTON": "Обновить все", - "UPDATE_ALL_BUTTON_TOOLTIP": "Обновить все аддоны для этого клиента", + "FILTER_LABEL": "Фильтр", + "RESCAN_FOLDERS_BUTTON": "Сканировать папки", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Сканирование папки клиента на наличие установленных модификаций", + "UPDATE_ALL_BUTTON": "Обновить всё", + "UPDATE_ALL_BUTTON_TOOLTIP": "Обновить все модификации для этого клиента", "TABLE": { - "ADDON_COLUMN_HEADER": "Addon", + "ADDON_COLUMN_HEADER": "Модификация", "ADDON_INSTALL_BUTTON": "Установить", "ADDON_UPDATE_BUTTON": "Обновить", "AUTHOR_COLUMN_HEADER": "Автор", "AUTO_UPDATE_ICON_TOOLTIP": "Автообновление включено", "GAME_VERSION_COLUMN_HEADER": "Версия игры", "LATEST_VERSION_COLUMN_HEADER": "Последняя версия", - "PROVIDER_COLUMN_HEADER": "Поставщик", + "PROVIDER_COLUMN_HEADER": "Источник", "STATUS_COLUMN_HEADER": "Статус" }, "ADDON_CONTEXT_MENU": { - "IGNORE_ADDON_BUTTON": "Пропустить", + "IGNORE_ADDON_BUTTON": "Пропускать", "AUTO_UPDATE_ADDON_BUTTON": "Автообновление", - "CHANNEL_SUBMENT_TITLE": "Канал", - "SHOW_FOLDER": "SHOW_FOLDER", + "CHANNEL_SUBMENT_TITLE": "Тип выпуска", "REINSTALL_ADDON_BUTTON": "Переустановить", "REMOVE_ADDON_BUTTON": "Удалить", - "STABLE_ADDON_CHANNEL": "Конюшня", + "STABLE_ADDON_CHANNEL": "Стабильная", "BETA_ADDON_CHANNEL": "Бета", "ALPHA_ADDON_CHANNEL": "Альфа" }, @@ -59,35 +59,35 @@ "TITLE": "Показать колонки" }, "UPDATE_ALL_CONTEXT_MENU": { - "UPDATE_RETAIL_CLASSIC_BUTTON": "Обновить Retail/Classic", - "UPDATE_ALL_CLIENTS_BUTTON": "Обновить всех клиентов" + "UPDATE_RETAIL_CLASSIC_BUTTON": "Обновить Текущую/Classic", + "UPDATE_ALL_CLIENTS_BUTTON": "Обновить все клиенты" } }, "OPTIONS": { "APPLICATION": { - "MINIMIZE_ON_CLOSE_LABEL": "Свернуть при закрытии", - "MINIMIZE_ON_CLOSE_DESCRIPTION": "При закрытии окна WowUp сворачиваем в системный трей.", + "MINIMIZE_ON_CLOSE_LABEL": "Свернуть в трей при закрытии", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "При закрытии окна WowUp сворачивается в системный трей.", "TELEMETRY_DESCRIPTION": "Помогите улучшить WowUp, отправив анонимные данные об установке и/или ошибках.", "TELEMETRY_LABEL": "Телеметрия", "TITLE": "Приложение" }, "DEBUG": { "DEBUG_DATA_BUTTON": "Дамп отладочных данных", - "DEBUG_DATA_DESCRIPTION": "Записывать отладочные данные, чтобы помочь в диагностике потенциальных проблем. Это можно найти в последнем файле журнала для любопытства.", + "DEBUG_DATA_DESCRIPTION": "Записывать отладочные данные, чтобы помочь в диагностике потенциальных проблем. Его можно найти в последнем лог-файле, если необходимо.", "DEBUG_DATA_LABEL": "Отладка данных", "LOG_FILES_BUTTON": "Показать лог-файлы", - "LOG_FILES_DESCRIPTION": "Откройте папку, содержащую последние несколько лог файлов.", + "LOG_FILES_DESCRIPTION": "Открыть папку, содержащую последние несколько лог-файлов.", "LOG_FILES_LABEL": "Файлы логов", - "TITLE": "Debug" + "TITLE": "Отладка" }, "WOW": { - "AUTO_UPDATE_DESCRIPTION": "Новые установленные дополнения будут автоматически обновляться по умолчанию", + "AUTO_UPDATE_DESCRIPTION": "Новые установленные модификации будут автоматически обновляться по умолчанию", "AUTO_UPDATE_LABEL": "Автообновление", "TITLE": "World of Warcraft", - "DEFAULT_ADDON_CHANNEL_LABEL": "Канал аддона по умолчанию", - "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Канал дополнения", - "RESCAN_CLIENTS_BUTTON": "Пересканировать", - "RESCAN_CLIENTS_LABEL": "Пересканируйте установленные продукты World of Warcraft" + "DEFAULT_ADDON_CHANNEL_LABEL": "Тип выпуска модификации по умолчанию", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Тип выпуска модификации", + "RESCAN_CLIENTS_BUTTON": "Повторное сканирование", + "RESCAN_CLIENTS_LABEL": "Повторно найти установленные продукты World of Warcraft" } } }, @@ -103,21 +103,21 @@ "POSITIVE_BUTTON": "Да" }, "INSTALL_FROM_URL": { - "ADDON_URL_INPUT_LABEL": "Addon URL", - "ADDON_URL_INPUT_PLACEHOLDER": "Пример URL-адреса GitHub или WowInterface", + "ADDON_URL_INPUT_LABEL": "Ссылка на модификацию", + "ADDON_URL_INPUT_PLACEHOLDER": "Пример ссылки GitHub или WowInterface", "CLOSE_BUTTON": "Закрыть", "IMPORT_BUTTON": "Импорт", "INSTALL_BUTTON": "Установить", "INSTALL_SUCCESS_LABEL": "Установлено!", - "TITLE": "Install Addon URL", - "DESCRIPTION": "Если вы хотите установить аддон непосредственно с URL, вставьте его ниже, чтобы начать.", - "SUPPORTED_SOURCES": "Поддерживает WowInterface и GitHub*" + "TITLE": "Ссылка на установку модификации", + "DESCRIPTION": "Если вы хотите установить модификацию непосредственно по ссылке, вставьте её ниже, чтобы начать.", + "SUPPORTED_SOURCES": "Поддерживаются WowInterface и GitHub*" }, "TELEMETRY": { - "DESCRIPTION": "Помогите мне улучшить WowUp, отправив анонимные данные и/или ошибки в приложении?", + "DESCRIPTION": "Хотите помочь мне улучшить WowUp, анонимно отправляя данные об установке и ошибках?", "NEGATIVE_BUTTON": "Нет, спасибо", "POSITIVE_BUTTON": "Конечно!", - "TITLE": "WowUp Телеметрия" + "TITLE": "Телеметрия WowUp" } } } diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index c5ed9364..15c22de3 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -215,7 +215,7 @@ img { .addon-name { font-size: 1em; - font-weight: bold; + // font-weight: bold; } .addon-version { From c953a65332a8ae618482ad762a189c4608be81f0 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 10:42:28 -0500 Subject: [PATCH 06/34] Version bump --- wowup-electron/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wowup-electron/package.json b/wowup-electron/package.json index 15d1828d..beb5407c 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -1,7 +1,7 @@ { "name": "wowup", "productName": "WowUp", - "version": "2.0.0-alpha.8", + "version": "2.0.0-alpha.9", "description": "Word of Warcraft addon updater", "homepage": "https://github.com/maximegris/angular-electron", "author": { From 25ebc493c9ade5ee607c937905d3b36b255deac5 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 10:43:55 -0500 Subject: [PATCH 07/34] Changelog --- wowup-electron/src/assets/changelog.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wowup-electron/src/assets/changelog.json b/wowup-electron/src/assets/changelog.json index ad3de123..7fe0122d 100644 --- a/wowup-electron/src/assets/changelog.json +++ b/wowup-electron/src/assets/changelog.json @@ -1,5 +1,9 @@ { "ChangeLogs": [ + { + "Version": "2.0.0-alpha.9", + "Description": "Update Russian locale.\nTry to fix some font blurriness.\nRemove debug error in WowInterface provider." + }, { "Version": "2.0.0-alpha.8", "Description": "Localize most static text (by Pansa and Medok).\nImplemented addon detail view in my-addons (by Flippeey).\nWindow position/size/maximized state should now be restored when starting the app (by Chops).\nDouble clicking the titlebar on mac should perform the action set in your system settings (by Chops).\nAddon scanning is now much faster.\nWhen scanning addons, the matching channel type should be applied not the default.\nRe-scan is now less complicated and should prevent duplicates.\nAdd the 'Show Folder' button to the addon context menu.\nAdd http circuit breakers to the providers.\nFix issue with My Addons Provider column not sorting.\nPage context data on the footer.\nRemove the windows menu bar buttons for mac.\nAdd WowUp addon provider prototype" From e60c6626dadf000795f3f92d75ff4b41c2ded713 Mon Sep 17 00:00:00 2001 From: jliddev Date: Thu, 15 Oct 2020 14:38:55 -0500 Subject: [PATCH 08/34] Fix rescan wiping user settings. Add some logging --- .../app/addon-providers/curse-addon-provider.ts | 8 +++++++- .../src/app/services/addons/addon.service.ts | 14 ++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 011504b1..352fe074 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -125,11 +125,15 @@ export class CurseAddonProvider implements AddonProvider { return; } + scanResults.forEach(result => { + console.debug(result.folderName, result.fingerprint); + }); + const fingerprintResponse = await this.getAddonsByFingerprints( scanResults.map((result) => result.fingerprint) ); - console.log(fingerprintResponse); + console.log('fingerprintResponse', fingerprintResponse); for (let scanResult of scanResults) { // Curse can deliver the wrong result sometimes, ensure the result matches the client type @@ -174,6 +178,8 @@ export class CurseAddonProvider implements AddonProvider { ): Promise { const url = `${API_URL}/fingerprint`; + console.log(`Curse Fetching fingerprints`, JSON.stringify(fingerprints)); + return await this.getCircuitBreaker().fire( async () => await this._httpClient diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 5f396b22..294e685f 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -408,18 +408,8 @@ export class AddonService { return; } - newAddon.name = existingAddon.name; - newAddon.folderName = existingAddon.folderName; - newAddon.downloadUrl = existingAddon.downloadUrl; - newAddon.installedVersion = existingAddon.installedVersion; - newAddon.externalUrl = existingAddon.externalUrl; - newAddon.latestVersion = existingAddon.latestVersion; - newAddon.thumbnailUrl = existingAddon.thumbnailUrl; - newAddon.gameVersion = existingAddon.gameVersion; - newAddon.author = existingAddon.author; - newAddon.patreonFundingLink = existingAddon.patreonFundingLink; - newAddon.githubFundingLink = existingAddon.githubFundingLink; - newAddon.customFundingLink = existingAddon.customFundingLink; + newAddon.autoUpdateEnabled = existingAddon.autoUpdateEnabled; + newAddon.isIgnored = existingAddon.isIgnored; }); return newAddons; From e9f4634a9be1ef9bfeaf135596d70471f2c3dbde Mon Sep 17 00:00:00 2001 From: Oleksandr <72464426+Medoke@users.noreply.github.com> Date: Fri, 16 Oct 2020 00:17:00 +0300 Subject: [PATCH 09/34] Small updates Translated "Show folder", misc changes in wording. --- wowup-electron/src/assets/i18n/ru.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wowup-electron/src/assets/i18n/ru.json b/wowup-electron/src/assets/i18n/ru.json index c12cce69..8b659bf7 100644 --- a/wowup-electron/src/assets/i18n/ru.json +++ b/wowup-electron/src/assets/i18n/ru.json @@ -49,6 +49,7 @@ "IGNORE_ADDON_BUTTON": "Пропускать", "AUTO_UPDATE_ADDON_BUTTON": "Автообновление", "CHANNEL_SUBMENT_TITLE": "Тип выпуска", + "SHOW_FOLDER": "Показать папку", "REINSTALL_ADDON_BUTTON": "Переустановить", "REMOVE_ADDON_BUTTON": "Удалить", "STABLE_ADDON_CHANNEL": "Стабильная", @@ -93,7 +94,7 @@ }, "DIALOGS": { "ADDON_DETAILS": { - "VIEW_IN_BROWSER_BUTTON": "Просмотр в браузере" + "VIEW_IN_BROWSER_BUTTON": "Посмотреть в браузере" }, "ALERT": { "POSITIVE_BUTTON": "Окей" @@ -104,7 +105,7 @@ }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "Ссылка на модификацию", - "ADDON_URL_INPUT_PLACEHOLDER": "Пример ссылки GitHub или WowInterface", + "ADDON_URL_INPUT_PLACEHOLDER": "Например ссылки GitHub или WowInterface", "CLOSE_BUTTON": "Закрыть", "IMPORT_BUTTON": "Импорт", "INSTALL_BUTTON": "Установить", From 6311d653049aa0f3fbfb22e9755cc5c2c4af2c73 Mon Sep 17 00:00:00 2001 From: Dean Campbell Date: Thu, 15 Oct 2020 19:42:02 -0700 Subject: [PATCH 10/34] fix(electron): fix column text padding Add padding to prevent textual columns from colliding. --- .../src/app/pages/get-addons/get-addons.component.html | 2 +- .../src/app/pages/get-addons/get-addons.component.scss | 4 ++++ .../src/app/pages/my-addons/my-addons.component.html | 6 +++--- .../src/app/pages/my-addons/my-addons.component.scss | 4 ++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index a0722be0..e24f58a9 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -60,7 +60,7 @@ {{'PAGES.GET_ADDONS.TABLE.AUTHOR_COLUMN_HEADER' | translate}} - + {{element.author}} diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss index 1edef68e..af3cd838 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss @@ -69,6 +69,10 @@ word-break: break-all; white-space: pre-wrap; } + + .cell-padding { + padding-right: 15px; + } } .author-column { diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index 63cc69a3..fc35b83c 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -52,7 +52,7 @@ {{'PAGES.MY_ADDONS.TABLE.ADDON_COLUMN_HEADER' | translate}} - + @@ -88,7 +88,7 @@ {{'PAGES.MY_ADDONS.TABLE.LATEST_VERSION_COLUMN_HEADER' | translate}} - + {{element.addon.latestVersion}} @@ -106,7 +106,7 @@ {{'PAGES.MY_ADDONS.TABLE.PROVIDER_COLUMN_HEADER' | translate}} - +
{{element.addon.providerName}}
diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss index 1911ad0b..a86d9846 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss @@ -114,6 +114,10 @@ height: 15px; } } + + .cell-padding { + padding-right: 15px; + } } .author-column { From 3887851d4bca8fb6560f3e855c6b29d7ac13c31a Mon Sep 17 00:00:00 2001 From: Dean Campbell Date: Thu, 15 Oct 2020 22:03:25 -0700 Subject: [PATCH 11/34] feat(electron): add dl count and version Add download count as a column to the get addons page. Add addon version below the addon name. --- .../addon-providers/curse-addon-provider.ts | 21 +- .../addon-providers/tukui-addon-provider.ts | 1 + ...otential-addon-table-column.component.html | 2 +- .../src/app/models/wowup/potential-addon.ts | 1 + .../get-addons/get-addons.component.html | 14 +- .../pages/get-addons/get-addons.component.ts | 3 +- .../services/addons/addon.provider.factory.ts | 7 +- wowup-electron/src/assets/i18n/de.json | 3 +- wowup-electron/src/assets/i18n/en.json | 3 +- wowup-electron/src/assets/i18n/es.json | 3 +- wowup-electron/src/assets/i18n/fr.json | 3 +- wowup-electron/src/assets/i18n/it.json | 3 +- wowup-electron/src/assets/i18n/pt.json | 227 +++++++++--------- wowup-electron/src/assets/i18n/ru.json | 3 +- wowup-electron/src/assets/i18n/zh.json | 3 +- 15 files changed, 167 insertions(+), 130 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 352fe074..d2234c32 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -24,6 +24,7 @@ import { CurseFile } from "common/curse/curse-file"; import { CurseReleaseType } from "common/curse/curse-release-type"; import { CurseGetFeaturedResponse } from "app/models/curse/curse-get-featured-response"; import * as CircuitBreaker from "opossum"; +import { WowUpService } from "app/services/wowup/wowup.service"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; @@ -45,7 +46,8 @@ export class CurseAddonProvider implements AddonProvider { constructor( private _httpClient: HttpClient, private _cachingService: CachingService, - private _electronService: ElectronService + private _electronService: ElectronService, + private _wowUpService: WowUpService, ) { this._circuitBreaker = new CircuitBreaker( (action) => this.sendRequest(action), @@ -276,7 +278,7 @@ export class CurseAddonProvider implements AddonProvider { return this.filterFeaturedAddons(addons, clientType); }), map((filteredAddons) => { - return filteredAddons.map((addon) => this.getPotentialAddon(addon)); + return filteredAddons.map((addon) => this.getPotentialAddon(addon, clientType)); }) ); } @@ -313,7 +315,7 @@ export class CurseAddonProvider implements AddonProvider { continue; } - searchResults.push(this.getPotentialAddon(result)); + searchResults.push(this.getPotentialAddon(result, clientType)); } return searchResults; @@ -387,7 +389,17 @@ export class CurseAddonProvider implements AddonProvider { throw new Error("Method not implemented."); } - private getPotentialAddon(result: CurseSearchResult): PotentialAddon { + private getPotentialAddon(result: CurseSearchResult, clientType: WowClientType): PotentialAddon { + const clientTypeStr = this.getGameVersionFlavor(clientType); + let latestFile = _.orderBy(result.latestFiles, 'id', 'desc') + .find(file => + file.gameVersionFlavor === clientTypeStr && + this.getChannelType(file.releaseType) === this._wowUpService.getDefaultAddonChannel(clientType) + ); + if (!latestFile) { + latestFile = _.first(result.latestFiles); + } + return { author: this.getAuthor(result), downloadCount: result.downloadCount, @@ -398,6 +410,7 @@ export class CurseAddonProvider implements AddonProvider { thumbnailUrl: this.getThumbnailUrl(result), summary: result.summary, screenshotUrls: this.getScreenshotUrls(result), + version: latestFile.displayName }; } diff --git a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts index 085ad0af..4c615a2a 100644 --- a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts @@ -197,6 +197,7 @@ export class TukUiAddonProvider implements AddonProvider { providerName: this.name, thumbnailUrl: addon.screenshot_url, summary: addon.small_desc, + version: addon.version }; } diff --git a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html index a021d3db..65e2351a 100644 --- a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html +++ b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html @@ -3,6 +3,6 @@
{{addon.name}} -
{{addon.downloadCount | downloadCount}} downloads
+
{{addon?.version}}
diff --git a/wowup-electron/src/app/models/wowup/potential-addon.ts b/wowup-electron/src/app/models/wowup/potential-addon.ts index fa4f67a6..a2bea729 100644 --- a/wowup-electron/src/app/models/wowup/potential-addon.ts +++ b/wowup-electron/src/app/models/wowup/potential-addon.ts @@ -9,4 +9,5 @@ export interface PotentialAddon { downloadCount: number; summary?: string; screenshotUrls?: string[]; + version?: string; } diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index a0722be0..b586081f 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -36,7 +36,8 @@
- +
+ + + + diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index 2f4def4e..ca2e753d 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -37,8 +37,9 @@ export class GetAddonsComponent implements OnInit, OnDestroy { columns: ColumnState[] = [ { name: "name", display: "Addon", visible: true }, + { name: "downloadCount", display: "Downloads", visible: true }, { name: "author", display: "Author", visible: true }, - { name: "provider", display: "Provider", visible: true }, + { name: "providerName", display: "Provider", visible: true }, { name: "status", display: "Status", visible: true }, ]; diff --git a/wowup-electron/src/app/services/addons/addon.provider.factory.ts b/wowup-electron/src/app/services/addons/addon.provider.factory.ts index 55d5cbf7..f1bc2f49 100644 --- a/wowup-electron/src/app/services/addons/addon.provider.factory.ts +++ b/wowup-electron/src/app/services/addons/addon.provider.factory.ts @@ -11,6 +11,7 @@ import { CachingService } from "../caching/caching-service"; import { ElectronService } from "../electron/electron.service"; import { FileService } from "../files/file.service"; import { SessionService } from "../session/session.service"; +import { WowUpService } from "../wowup/wowup.service"; @Injectable({ providedIn: "root", @@ -21,7 +22,8 @@ export class AddonProviderFactory { private _electronService: ElectronService, private _httpClient: HttpClient, private _sessionService: SessionService, - private _fileService: FileService + private _fileService: FileService, + private _wowUpService: WowUpService, ) {} public getAddonProvider(providerType: T & AddonProvider) { @@ -39,7 +41,8 @@ export class AddonProviderFactory { return new CurseAddonProvider( this._httpClient, this._cachingService, - this._electronService + this._electronService, + this._wowUpService ); } diff --git a/wowup-electron/src/assets/i18n/de.json b/wowup-electron/src/assets/i18n/de.json index fb48d421..df02b4b3 100644 --- a/wowup-electron/src/assets/i18n/de.json +++ b/wowup-electron/src/assets/i18n/de.json @@ -14,7 +14,8 @@ "ADDON_COLUMN_HEADER": "Addon", "AUTHOR_COLUMN_HEADER": "Autor", "PROVIDER_COLUMN_HEADER": "Anbieter", - "STATUS_COLUMN_HEADER": "Status" + "STATUS_COLUMN_HEADER": "Status", + "DOWNLOAD_COUNT_COLUMN_HEADER": "TEXT_ELEMENT" } }, "HOME": { diff --git a/wowup-electron/src/assets/i18n/en.json b/wowup-electron/src/assets/i18n/en.json index 685d8b46..27aa0426 100644 --- a/wowup-electron/src/assets/i18n/en.json +++ b/wowup-electron/src/assets/i18n/en.json @@ -14,7 +14,8 @@ "ADDON_COLUMN_HEADER": "Addon", "AUTHOR_COLUMN_HEADER": "Author", "PROVIDER_COLUMN_HEADER": "Provider", - "STATUS_COLUMN_HEADER": "Status" + "STATUS_COLUMN_HEADER": "Status", + "DOWNLOAD_COUNT_COLUMN_HEADER": "Downloads" } }, "HOME": { diff --git a/wowup-electron/src/assets/i18n/es.json b/wowup-electron/src/assets/i18n/es.json index 47f03cfc..63e1adb5 100644 --- a/wowup-electron/src/assets/i18n/es.json +++ b/wowup-electron/src/assets/i18n/es.json @@ -14,7 +14,8 @@ "ADDON_COLUMN_HEADER": "Addon", "AUTHOR_COLUMN_HEADER": "Autor", "PROVIDER_COLUMN_HEADER": "Proveedor", - "STATUS_COLUMN_HEADER": "Estado" + "STATUS_COLUMN_HEADER": "Estado", + "DOWNLOAD_COUNT_COLUMN_HEADER": "TEXT_ELEMENT" } }, "HOME": { diff --git a/wowup-electron/src/assets/i18n/fr.json b/wowup-electron/src/assets/i18n/fr.json index 6c4b0a94..b7cf05f9 100644 --- a/wowup-electron/src/assets/i18n/fr.json +++ b/wowup-electron/src/assets/i18n/fr.json @@ -14,7 +14,8 @@ "ADDON_COLUMN_HEADER": "Addon", "AUTHOR_COLUMN_HEADER": "Auteur", "PROVIDER_COLUMN_HEADER": "Fournisseur", - "STATUS_COLUMN_HEADER": "Statut" + "STATUS_COLUMN_HEADER": "Statut", + "DOWNLOAD_COUNT_COLUMN_HEADER": "TEXT_ELEMENT" } }, "HOME": { diff --git a/wowup-electron/src/assets/i18n/it.json b/wowup-electron/src/assets/i18n/it.json index c525f452..412001f2 100644 --- a/wowup-electron/src/assets/i18n/it.json +++ b/wowup-electron/src/assets/i18n/it.json @@ -14,7 +14,8 @@ "ADDON_COLUMN_HEADER": "Addon", "AUTHOR_COLUMN_HEADER": "Autore", "PROVIDER_COLUMN_HEADER": "Provveditore", - "STATUS_COLUMN_HEADER": "Stato" + "STATUS_COLUMN_HEADER": "Stato", + "DOWNLOAD_COUNT_COLUMN_HEADER": "TEXT_ELEMENT" } }, "HOME": { diff --git a/wowup-electron/src/assets/i18n/pt.json b/wowup-electron/src/assets/i18n/pt.json index ef0a484c..af1df0c8 100644 --- a/wowup-electron/src/assets/i18n/pt.json +++ b/wowup-electron/src/assets/i18n/pt.json @@ -1,124 +1,125 @@ { - "PAGES": { - "ABOUT": { - "CHANGE_LOG_SECTION_LABEL": "Registro de Alterações", - "TITLE": "WowUp.io", - "WEBSITE_LINK_LABEL": "Conheça o nosso site!" - }, - "GET_ADDONS": { - "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", - "REFRESH_BUTTON": "Atualizar", - "INSTALL_FROM_URL_BUTTON": "Instalar pela URL", - "SEARCH_LABEL": "Procurar", - "TABLE": { - "ADDON_COLUMN_HEADER": "Addon", - "AUTHOR_COLUMN_HEADER": "Autor", - "PROVIDER_COLUMN_HEADER": "Provedor", - "STATUS_COLUMN_HEADER": "Estado" - } - }, - "HOME": { - "TITLE": "App funciona!", - "GO_TO_DETAIL": "Ir para Detalhes", - "MY_ADDONS_TAB_TITLE": "Meus Addons", - "GET_ADDONS_TAB_TITLE": "Obtenha Addons", - "ABOUT_TAB_TITLE": "Sobre", - "OPTIONS_TAB_TITLE": "Opções" - }, - "MY_ADDONS": { - "CHECK_UPDATES_BUTTON": "Verificar Atualizações", - "CHECK_UPDATES_BUTTON_TOOLTIP": "Verificar atualizações recentes", - "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", - "RESCAN_FOLDERS_BUTTON": "Re-escanear pastas", - "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Procura por Addons instalados", - "UPDATE_ALL_BUTTON": "Actualizar todos", - "UPDATE_ALL_BUTTON_TOOLTIP": "Atualizar todos os Addons", - "TABLE": { - "ADDON_COLUMN_HEADER": "Addon", - "ADDON_INSTALL_BUTTON": "Instalar", - "ADDON_UPDATE_BUTTON": "Atualizar", - "AUTHOR_COLUMN_HEADER": "Autor", - "AUTO_UPDATE_ICON_TOOLTIP": "Atualização automática habilitada", - "GAME_VERSION_COLUMN_HEADER": "Versão do Jogo", - "LATEST_VERSION_COLUMN_HEADER": "Ultima versão", - "PROVIDER_COLUMN_HEADER": "Provedor", - "STATUS_COLUMN_HEADER": "Estado" - }, - "ADDON_CONTEXT_MENU": { - "IGNORE_ADDON_BUTTON": "Ignorar", - "AUTO_UPDATE_ADDON_BUTTON": "Atualização Automática", - "CHANNEL_SUBMENT_TITLE": "Canal", - "SHOW_FOLDER": "SHOW_FOLDER", - "REINSTALL_ADDON_BUTTON": "Reinstalar", - "REMOVE_ADDON_BUTTON": "Remover", - "STABLE_ADDON_CHANNEL": "Estável", - "BETA_ADDON_CHANNEL": "Beta", - "ALPHA_ADDON_CHANNEL": "Alfa" - }, - "COLUMNS_CONTEXT_MENU": { - "TITLE": "Exibir Colunas" - }, - "UPDATE_ALL_CONTEXT_MENU": { - "UPDATE_RETAIL_CLASSIC_BUTTON": "Atualizar Retail/Clássico", - "UPDATE_ALL_CLIENTS_BUTTON": "Atualizar todos os clientes" - } - }, - "OPTIONS": { - "APPLICATION": { - "MINIMIZE_ON_CLOSE_LABEL": "Minimizar ao Fechar", - "MINIMIZE_ON_CLOSE_DESCRIPTION": "Ao fechar a janela do WowUp, minimize para a bandeja do sistema.", - "TELEMETRY_DESCRIPTION": "Ajude a melhorar o WowUp enviando dados e/ou erros de instalação anônimamente.", - "TELEMETRY_LABEL": "Telemetria", - "TITLE": "Aplicativo" - }, - "DEBUG": { - "DEBUG_DATA_BUTTON": "Esvaziar log de depuração de dados", - "DEBUG_DATA_DESCRIPTION": "Registra os dados de depuração e ajuda a diagnosticar problemas potenciais. Apenas por o curiosidade, isso pode ser encontrado em seu último arquivo de registro.", - "DEBUG_DATA_LABEL": "Depurar Dados", - "LOG_FILES_BUTTON": "Mostrar Arquivos de Registro", - "LOG_FILES_DESCRIPTION": "Abre a pasta que contém seus últimos arquivos de registro.", - "LOG_FILES_LABEL": "Arquivos de Registro", - "TITLE": "Depurar" - }, - "WOW": { - "AUTO_UPDATE_DESCRIPTION": "Addons recém-instalados serão definidos para atualizar automáticamente por padrão", - "AUTO_UPDATE_LABEL": "Atualização Automática", - "TITLE": "World of Warcraft", - "DEFAULT_ADDON_CHANNEL_LABEL": "Canal de Addon Padrão", - "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Canal de Addon", - "RESCAN_CLIENTS_BUTTON": "Re-escanear", - "RESCAN_CLIENTS_LABEL": "Reescanear World of Warcraft instalados" - } + "PAGES": { + "ABOUT": { + "CHANGE_LOG_SECTION_LABEL": "Registro de Alterações", + "TITLE": "WowUp.io", + "WEBSITE_LINK_LABEL": "Conheça o nosso site!" + }, + "GET_ADDONS": { + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "REFRESH_BUTTON": "Atualizar", + "INSTALL_FROM_URL_BUTTON": "Instalar pela URL", + "SEARCH_LABEL": "Procurar", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "AUTHOR_COLUMN_HEADER": "Autor", + "PROVIDER_COLUMN_HEADER": "Provedor", + "STATUS_COLUMN_HEADER": "Estado", + "DOWNLOAD_COUNT_COLUMN_HEADER": "TEXT_ELEMENT" } }, - "DIALOGS": { - "ADDON_DETAILS": { - "VIEW_IN_BROWSER_BUTTON": "Visualizar no navegador" + "HOME": { + "TITLE": "App funciona!", + "GO_TO_DETAIL": "Ir para Detalhes", + "MY_ADDONS_TAB_TITLE": "Meus Addons", + "GET_ADDONS_TAB_TITLE": "Obtenha Addons", + "ABOUT_TAB_TITLE": "Sobre", + "OPTIONS_TAB_TITLE": "Opções" + }, + "MY_ADDONS": { + "CHECK_UPDATES_BUTTON": "Verificar Atualizações", + "CHECK_UPDATES_BUTTON_TOOLTIP": "Verificar atualizações recentes", + "CLIENT_TYPE_SELECT_LABEL": "World of Warcraft", + "RESCAN_FOLDERS_BUTTON": "Re-escanear pastas", + "RESCAN_FOLDERS_BUTTON_TOOLTIP": "Procura por Addons instalados", + "UPDATE_ALL_BUTTON": "Actualizar todos", + "UPDATE_ALL_BUTTON_TOOLTIP": "Atualizar todos os Addons", + "TABLE": { + "ADDON_COLUMN_HEADER": "Addon", + "ADDON_INSTALL_BUTTON": "Instalar", + "ADDON_UPDATE_BUTTON": "Atualizar", + "AUTHOR_COLUMN_HEADER": "Autor", + "AUTO_UPDATE_ICON_TOOLTIP": "Atualização automática habilitada", + "GAME_VERSION_COLUMN_HEADER": "Versão do Jogo", + "LATEST_VERSION_COLUMN_HEADER": "Ultima versão", + "PROVIDER_COLUMN_HEADER": "Provedor", + "STATUS_COLUMN_HEADER": "Estado" }, - "ALERT": { - "POSITIVE_BUTTON": "Ok" + "ADDON_CONTEXT_MENU": { + "IGNORE_ADDON_BUTTON": "Ignorar", + "AUTO_UPDATE_ADDON_BUTTON": "Atualização Automática", + "CHANNEL_SUBMENT_TITLE": "Canal", + "SHOW_FOLDER": "SHOW_FOLDER", + "REINSTALL_ADDON_BUTTON": "Reinstalar", + "REMOVE_ADDON_BUTTON": "Remover", + "STABLE_ADDON_CHANNEL": "Estável", + "BETA_ADDON_CHANNEL": "Beta", + "ALPHA_ADDON_CHANNEL": "Alfa" }, - "CONFIRM": { - "NEGATIVE_BUTTON": "Não", - "POSITIVE_BUTTON": "Sim" + "COLUMNS_CONTEXT_MENU": { + "TITLE": "Exibir Colunas" }, - "INSTALL_FROM_URL": { - "ADDON_URL_INPUT_LABEL": "Addon URL", - "ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub ou WowInterface URL", - "CLOSE_BUTTON": "Fechar", - "IMPORT_BUTTON": "Importar", - "INSTALL_BUTTON": "Instalar", - "INSTALL_SUCCESS_LABEL": "Instalado!", - "TITLE": "Instalar Addon pela URL", - "DESCRIPTION": "Se você deseja instalar um addon diretamente de uma URL, cole-a abaixo para iniciar.", - "SUPPORTED_SOURCES": "Suporta WowInterface e GitHub*" + "UPDATE_ALL_CONTEXT_MENU": { + "UPDATE_RETAIL_CLASSIC_BUTTON": "Atualizar Retail/Clássico", + "UPDATE_ALL_CLIENTS_BUTTON": "Atualizar todos os clientes" + } + }, + "OPTIONS": { + "APPLICATION": { + "MINIMIZE_ON_CLOSE_LABEL": "Minimizar ao Fechar", + "MINIMIZE_ON_CLOSE_DESCRIPTION": "Ao fechar a janela do WowUp, minimize para a bandeja do sistema.", + "TELEMETRY_DESCRIPTION": "Ajude a melhorar o WowUp enviando dados e/ou erros de instalação anônimamente.", + "TELEMETRY_LABEL": "Telemetria", + "TITLE": "Aplicativo" }, - "TELEMETRY": { - "DESCRIPTION": "Ajude-nos a melhorar o WowUp enviando dados e/ou erros de instalação do aplicativo anônimamente?", - "NEGATIVE_BUTTON": "Não obrigado", - "POSITIVE_BUTTON": "Claro!", - "TITLE": "Telemetria do WowUp" + "DEBUG": { + "DEBUG_DATA_BUTTON": "Esvaziar log de depuração de dados", + "DEBUG_DATA_DESCRIPTION": "Registra os dados de depuração e ajuda a diagnosticar problemas potenciais. Apenas por o curiosidade, isso pode ser encontrado em seu último arquivo de registro.", + "DEBUG_DATA_LABEL": "Depurar Dados", + "LOG_FILES_BUTTON": "Mostrar Arquivos de Registro", + "LOG_FILES_DESCRIPTION": "Abre a pasta que contém seus últimos arquivos de registro.", + "LOG_FILES_LABEL": "Arquivos de Registro", + "TITLE": "Depurar" + }, + "WOW": { + "AUTO_UPDATE_DESCRIPTION": "Addons recém-instalados serão definidos para atualizar automáticamente por padrão", + "AUTO_UPDATE_LABEL": "Atualização Automática", + "TITLE": "World of Warcraft", + "DEFAULT_ADDON_CHANNEL_LABEL": "Canal de Addon Padrão", + "DEFAULT_ADDON_CHANNEL_SELECT_LABEL": "Canal de Addon", + "RESCAN_CLIENTS_BUTTON": "Re-escanear", + "RESCAN_CLIENTS_LABEL": "Reescanear World of Warcraft instalados" } } + }, + "DIALOGS": { + "ADDON_DETAILS": { + "VIEW_IN_BROWSER_BUTTON": "Visualizar no navegador" + }, + "ALERT": { + "POSITIVE_BUTTON": "Ok" + }, + "CONFIRM": { + "NEGATIVE_BUTTON": "Não", + "POSITIVE_BUTTON": "Sim" + }, + "INSTALL_FROM_URL": { + "ADDON_URL_INPUT_LABEL": "Addon URL", + "ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub ou WowInterface URL", + "CLOSE_BUTTON": "Fechar", + "IMPORT_BUTTON": "Importar", + "INSTALL_BUTTON": "Instalar", + "INSTALL_SUCCESS_LABEL": "Instalado!", + "TITLE": "Instalar Addon pela URL", + "DESCRIPTION": "Se você deseja instalar um addon diretamente de uma URL, cole-a abaixo para iniciar.", + "SUPPORTED_SOURCES": "Suporta WowInterface e GitHub*" + }, + "TELEMETRY": { + "DESCRIPTION": "Ajude-nos a melhorar o WowUp enviando dados e/ou erros de instalação do aplicativo anônimamente?", + "NEGATIVE_BUTTON": "Não obrigado", + "POSITIVE_BUTTON": "Claro!", + "TITLE": "Telemetria do WowUp" + } } +} \ No newline at end of file diff --git a/wowup-electron/src/assets/i18n/ru.json b/wowup-electron/src/assets/i18n/ru.json index c12cce69..61963814 100644 --- a/wowup-electron/src/assets/i18n/ru.json +++ b/wowup-electron/src/assets/i18n/ru.json @@ -14,7 +14,8 @@ "ADDON_COLUMN_HEADER": "Модификация", "AUTHOR_COLUMN_HEADER": "Автор", "PROVIDER_COLUMN_HEADER": "Источник", - "STATUS_COLUMN_HEADER": "Статус" + "STATUS_COLUMN_HEADER": "Статус", + "DOWNLOAD_COUNT_COLUMN_HEADER": "TEXT_ELEMENT" } }, "HOME": { diff --git a/wowup-electron/src/assets/i18n/zh.json b/wowup-electron/src/assets/i18n/zh.json index 55d11d02..b7b55d5d 100644 --- a/wowup-electron/src/assets/i18n/zh.json +++ b/wowup-electron/src/assets/i18n/zh.json @@ -14,7 +14,8 @@ "ADDON_COLUMN_HEADER": "Addon", "AUTHOR_COLUMN_HEADER": "作者", "PROVIDER_COLUMN_HEADER": "提供商", - "STATUS_COLUMN_HEADER": "状态" + "STATUS_COLUMN_HEADER": "状态", + "DOWNLOAD_COUNT_COLUMN_HEADER": "TEXT_ELEMENT" } }, "HOME": { From c3a5b5174612971a725ec612667c3011d4f201b0 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 13:17:48 -0500 Subject: [PATCH 12/34] Use the new fingerprint endpoint --- .../addon-providers/curse-addon-provider.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 352fe074..13b31b46 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -26,6 +26,7 @@ import { CurseGetFeaturedResponse } from "app/models/curse/curse-get-featured-re import * as CircuitBreaker from "opossum"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; +const HUB_API_URL = "https://hub.dev.wowup.io"; export class CurseAddonProvider implements AddonProvider { private readonly _circuitBreaker: CircuitBreaker< @@ -125,15 +126,15 @@ export class CurseAddonProvider implements AddonProvider { return; } - scanResults.forEach(result => { + scanResults.forEach((result) => { console.debug(result.folderName, result.fingerprint); }); - const fingerprintResponse = await this.getAddonsByFingerprints( + const fingerprintResponse = await this.getAddonsByFingerprintsW( scanResults.map((result) => result.fingerprint) ); - console.log('fingerprintResponse', fingerprintResponse); + console.log("fingerprintResponse", fingerprintResponse); for (let scanResult of scanResults) { // Curse can deliver the wrong result sometimes, ensure the result matches the client type @@ -146,7 +147,7 @@ export class CurseAddonProvider implements AddonProvider { ); // If the addon does not have an exact match, check the partial matches. - if (!scanResult.exactMatch) { + if (!scanResult.exactMatch && fingerprintResponse.partialMatches) { scanResult.exactMatch = fingerprintResponse.partialMatches.find( (partialMatch) => partialMatch.file?.modules?.some( @@ -173,6 +174,25 @@ export class CurseAddonProvider implements AddonProvider { return gameVersionFlavor === this.getGameVersionFlavor(clientType); } + private async getAddonsByFingerprintsW(fingerprints: number[]) { + const url = `${HUB_API_URL}/curseforge/addons/fingerprint`; + + console.log(`Wowup Fetching fingerprints`, JSON.stringify(fingerprints)); + + return await this._httpClient + .post(url, { + fingerprints, + }) + .toPromise(); + + return await this.getCircuitBreaker().fire( + async () => + await this._httpClient + .post(url, fingerprints) + .toPromise() + ); + } + private async getAddonsByFingerprints( fingerprints: number[] ): Promise { From 15709e3c2839f1b4a0f04770e9a242717d037142 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 13:22:18 -0500 Subject: [PATCH 13/34] Version bump --- wowup-electron/package.json | 2 +- wowup-electron/src/assets/changelog.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/wowup-electron/package.json b/wowup-electron/package.json index beb5407c..ca557672 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -1,7 +1,7 @@ { "name": "wowup", "productName": "WowUp", - "version": "2.0.0-alpha.9", + "version": "2.0.0-alpha.10", "description": "Word of Warcraft addon updater", "homepage": "https://github.com/maximegris/angular-electron", "author": { diff --git a/wowup-electron/src/assets/changelog.json b/wowup-electron/src/assets/changelog.json index 7fe0122d..5c28c804 100644 --- a/wowup-electron/src/assets/changelog.json +++ b/wowup-electron/src/assets/changelog.json @@ -1,5 +1,9 @@ { "ChangeLogs": [ + { + "Version": "2.0.0-alpha.10", + "Description": "Temporary CF fingerprint endpoint patch." + }, { "Version": "2.0.0-alpha.9", "Description": "Update Russian locale.\nTry to fix some font blurriness.\nRemove debug error in WowInterface provider." From 6e4fcada58d8b328e8985b82f1438cec70386215 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 15:40:14 -0500 Subject: [PATCH 14/34] Fix some GitHub related errors. --- .../src/app/addon-providers/github-addon-provider.ts | 7 +++---- wowup-electron/src/app/services/addons/addon.service.ts | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/github-addon-provider.ts b/wowup-electron/src/app/addon-providers/github-addon-provider.ts index c9cfd7f5..5e9dc5a0 100644 --- a/wowup-electron/src/app/addon-providers/github-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/github-addon-provider.ts @@ -31,7 +31,7 @@ export class GitHubAddonProvider implements AddonProvider { async getAll(clientType: WowClientType, addonIds: string[]): Promise { var searchResults: AddonSearchResult[] = [] - for (let addonId in addonIds) { + for (let addonId of addonIds) { var result = await this.getById(addonId, clientType).toPromise(); if (result == null) { continue; @@ -74,7 +74,7 @@ export class GitHubAddonProvider implements AddonProvider { author: author, downloadCount: asset.download_count, externalId: repoPath, - externalUrl: latestRelease.url, + externalUrl: repository.html_url, name: repository.name, providerName: this.name, thumbnailUrl: authorImageUrl @@ -124,7 +124,7 @@ export class GitHubAddonProvider implements AddonProvider { var searchResult: AddonSearchResult = { author: author, externalId: addonId, - externalUrl: asset.url, + externalUrl: repository.html_url, files: [searchResultFile], name: addonName, providerName: this.name, @@ -203,7 +203,6 @@ export class GitHubAddonProvider implements AddonProvider { private getReleases(repositoryPath: string): Observable { const url = `${API_URL}${repositoryPath}/releases`; - return this._httpClient.get(url.toString()); } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 294e685f..1ffbeb9c 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -93,6 +93,7 @@ export class AddonService { clientType ).toPromise(); this._addonStorage.set(addon.id, addon); + await this.installAddon(addon.id, onUpdate); } @@ -166,7 +167,6 @@ export class AddonService { let downloadedFilePath = ""; let unzippedDirectory = ""; - let downloadedThumbnail = ""; try { downloadedFilePath = await this._downloadService.downloadZipFile( addon.downloadUrl, @@ -174,6 +174,7 @@ export class AddonService { ); onUpdate?.call(this, AddonInstallState.Installing, 75); + this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Installing, From e69e55f774b77271f8fb263ea42690f0a9926587 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 16:07:45 -0500 Subject: [PATCH 15/34] Rework most of the IPC handlers --- wowup-electron/ipc-events.ts | 80 +++++- wowup-electron/main.ts | 235 ++++++------------ .../src/app/services/addons/addon.service.ts | 3 +- .../app/services/download/download.service.ts | 148 ++++++----- .../src/app/services/files/file.service.ts | 101 +++++--- .../common/models/copy-directory-request.ts | 10 +- .../src/common/models/copy-file-request.ts | 10 +- .../common/models/delete-directory-request.ts | 8 +- .../src/common/models/download-request.ts | 10 +- .../src/common/models/read-file-request.ts | 8 +- .../src/common/models/unzip-request.ts | 10 +- 11 files changed, 348 insertions(+), 275 deletions(-) diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index fa80a978..a261dfdc 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -1,8 +1,11 @@ import { ipcMain, shell } from "electron"; import * as fs from "fs"; import * as async from "async"; +import * as admZip from "adm-zip"; +import { ncp } from "ncp"; +import * as rimraf from "rimraf"; -import { readDirRecursive } from "./file.utils"; +import { readDirRecursive, readFile } from "./file.utils"; import { CURSE_HASH_FILE_CHANNEL, LIST_DIRECTORIES_CHANNEL, @@ -11,6 +14,12 @@ import { PATH_EXISTS_CHANNEL, CURSE_GET_SCAN_RESULTS, WOWUP_GET_SCAN_RESULTS, + UNZIP_FILE_CHANNEL, + COPY_FILE_CHANNEL, + COPY_DIRECTORY_CHANNEL, + DELETE_DIRECTORY_CHANNEL, + RENAME_DIRECTORY_CHANNEL, + READ_FILE_CHANNEL, } from "./src/common/constants"; import { CurseGetScanResultsRequest } from "./src/common/curse/curse-get-scan-results-request"; import { CurseGetScanResultsResponse } from "./src/common/curse/curse-get-scan-results-response"; @@ -28,6 +37,14 @@ import { WowUpGetScanResultsRequest } from "./src/common/wowup/wowup-get-scan-re import { WowUpGetScanResultsResponse } from "./src/common/wowup/wowup-get-scan-results-response"; import { WowUpFolderScanner } from "./src/common/wowup/wowup-folder-scanner"; import { WowUpScanResult } from "./src/common/wowup/wowup-scan-result"; +import { UnzipRequest } from "./src/common/models/unzip-request"; +import { UnzipStatus } from "./src/common/models/unzip-status"; +import { UnzipStatusType } from "./src/common/models/unzip-status-type"; +import { CopyFileRequest } from "./src/common/models/copy-file-request"; +import { CopyDirectoryRequest } from "./src/common/models/copy-directory-request"; +import { DeleteDirectoryRequest } from "./src/common/models/delete-directory-request"; +import { ReadFileRequest } from "./src/common/models/read-file-request"; +import { ReadFileResponse } from "./src/common/models/read-file-response"; const nativeAddon = require("./build/Release/addon.node"); @@ -84,7 +101,7 @@ ipcMain.on(LIST_FILES_CHANNEL, async (evt, arg: ListFilesRequest) => { response.error = err; } - evt.reply(arg.sourcePath, response); + evt.reply(arg.responseKey, response); }); ipcMain.on(LIST_DIRECTORIES_CHANNEL, (evt, arg: ValueRequest) => { @@ -175,3 +192,62 @@ ipcMain.on( evt.reply(arg.responseKey, response); } ); + +ipcMain.on(UNZIP_FILE_CHANNEL, (evt, arg: UnzipRequest) => { + const zipFilePath = arg.zipFilePath; + const outputFolder = arg.outputFolder; + + const zip = new admZip(zipFilePath); + zip.extractAllToAsync(outputFolder, true, (err) => { + const status: UnzipStatus = { + type: UnzipStatusType.Complete, + outputFolder, + }; + + if (err) { + status.type = UnzipStatusType.Error; + status.error = err; + } + + evt.reply(arg.responseKey, status); + }); +}); + +ipcMain.on(COPY_FILE_CHANNEL, (evt, arg: CopyFileRequest) => { + console.log("Copy File", arg); + fs.copyFile(arg.sourceFilePath, arg.destinationFilePath, (err) => { + evt.reply(arg.responseKey, { error: err }); + }); +}); + +ipcMain.on(COPY_DIRECTORY_CHANNEL, (evt, arg: CopyDirectoryRequest) => { + console.log("Copy Dir", arg); + ncp(arg.sourcePath, arg.destinationPath, (err) => { + evt.reply(arg.responseKey, err); + }); +}); + +ipcMain.on(DELETE_DIRECTORY_CHANNEL, (evt, arg: DeleteDirectoryRequest) => { + console.log("Delete Dir", arg); + rimraf(arg.sourcePath, (err) => { + evt.reply(arg.responseKey, err); + }); +}); + +ipcMain.on(RENAME_DIRECTORY_CHANNEL, (evt, arg: CopyDirectoryRequest) => { + console.log("Rename Dir", arg); + fs.rename(arg.sourcePath, arg.destinationPath, (err) => { + evt.reply(arg.responseKey, err); + }); +}); + +ipcMain.on(READ_FILE_CHANNEL, async (evt, arg: ReadFileRequest) => { + // console.log('Read File', arg); + const response: ReadFileResponse = { data: "" }; + try { + response.data = await readFile(arg.sourcePath); + } catch (err) { + response.error = err; + } + evt.reply(arg.responseKey, response); +}); diff --git a/wowup-electron/main.ts b/wowup-electron/main.ts index 05818d0c..998f1c6b 100644 --- a/wowup-electron/main.ts +++ b/wowup-electron/main.ts @@ -12,41 +12,19 @@ import { } from "electron"; import * as path from "path"; import * as url from "url"; -import * as fs from "fs"; import { release, arch } from "os"; import * as electronDl from "electron-dl"; -import * as admZip from "adm-zip"; import { DownloadRequest } from "./src/common/models/download-request"; import { DownloadStatus } from "./src/common/models/download-status"; import { DownloadStatusType } from "./src/common/models/download-status-type"; -import { UnzipStatus } from "./src/common/models/unzip-status"; -import { - DOWNLOAD_FILE_CHANNEL, - UNZIP_FILE_CHANNEL, - COPY_FILE_CHANNEL, - COPY_DIRECTORY_CHANNEL, - DELETE_DIRECTORY_CHANNEL, - RENAME_DIRECTORY_CHANNEL, - READ_FILE_CHANNEL, -} from "./src/common/constants"; -import { UnzipStatusType } from "./src/common/models/unzip-status-type"; -import { UnzipRequest } from "./src/common/models/unzip-request"; -import { CopyFileRequest } from "./src/common/models/copy-file-request"; -import { CopyDirectoryRequest } from "./src/common/models/copy-directory-request"; -import { DeleteDirectoryRequest } from "./src/common/models/delete-directory-request"; -import { ReadFileRequest } from "./src/common/models/read-file-request"; -import { ReadFileResponse } from "./src/common/models/read-file-response"; +import { DOWNLOAD_FILE_CHANNEL } from "./src/common/constants"; import "./ipc-events"; -import { ncp } from "ncp"; -import * as rimraf from "rimraf"; import * as log from "electron-log"; import { autoUpdater } from "electron-updater"; import * as Store from "electron-store"; -import { readFile } from "./file.utils"; -import { WindowState } from './src/common/models/window-state'; -import { isBetween } from './src/common/utils/number.utils'; -import { Subject } from 'rxjs'; -import { debounceTime } from 'rxjs/operators'; +import { WindowState } from "./src/common/models/window-state"; +import { Subject } from "rxjs"; +import { debounceTime } from "rxjs/operators"; const isMac = process.platform === "darwin"; const isWin = process.platform === "win32"; @@ -68,37 +46,37 @@ autoUpdater.on("update-downloaded", () => { const appMenuTemplate: Array = isMac ? [ - { - label: app.name, - submenu: [{ role: "quit" }], - }, - { - label: "Edit", - submenu: [ - { role: "undo" }, - { role: "redo" }, - { type: "separator" }, - { role: "cut" }, - { role: "copy" }, - { role: "paste" }, - { role: "selectAll" }, - ], - }, - { - label: "View", - submenu: [ - { role: "reload" }, - { role: "forceReload" }, - { role: "toggleDevTools" }, - { type: "separator" }, - { role: "resetZoom" }, - { role: "zoomIn" }, - { role: "zoomOut" }, - { type: "separator" }, - { role: "togglefullscreen" }, - ], - }, - ] + { + label: app.name, + submenu: [{ role: "quit" }], + }, + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + { role: "selectAll" }, + ], + }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + ] : []; const appMenu = Menu.buildFromTemplate(appMenuTemplate); @@ -161,43 +139,50 @@ function createTray() { tray.setContextMenu(contextMenu); } -function windowStateManager(windowName: string, { width, height }: { width: number, height: number }) { +function windowStateManager( + windowName: string, + { width, height }: { width: number; height: number } +) { let window: BrowserWindow; let windowState: WindowState; const saveState$ = new Subject(); function setState() { let setDefaults = false; - windowState = preferenceStore.get(`${windowName}-window-state`) as WindowState; + windowState = preferenceStore.get( + `${windowName}-window-state` + ) as WindowState; if (!windowState) { setDefaults = true; } else { - log.info('found window state:', windowState); + log.info("found window state:", windowState); - const valid = screen.getAllDisplays().some(display => { + const valid = screen.getAllDisplays().some((display) => { return ( windowState.x >= display.bounds.x && windowState.y >= display.bounds.y && - windowState.x + windowState.width <= display.bounds.x + display.bounds.width && - windowState.y + windowState.height <= display.bounds.y + display.bounds.height + windowState.x + windowState.width <= + display.bounds.x + display.bounds.width && + windowState.y + windowState.height <= + display.bounds.y + display.bounds.height ); - }) + }); if (!valid) { - log.info('reset window state, bounds are outside displays'); + log.info("reset window state, bounds are outside displays"); setDefaults = true; } } if (setDefaults) { - log.info('setting window defaults'); + log.info("setting window defaults"); windowState = { width, height }; } } function saveState() { - log.info('saving window state'); + log.info("saving window state"); if (!window.isMaximized() && !window.isFullScreen()) { windowState = { ...windowState, ...window.getBounds() }; } @@ -209,37 +194,38 @@ function windowStateManager(windowName: string, { width, height }: { width: numb function monitorState(win: BrowserWindow) { window = win; - win.on('close', saveState); - win.on('resize', () => saveState$.next()); - win.on('move', () => saveState$.next()); - win.on('closed', () => saveState$.unsubscribe()); + win.on("close", saveState); + win.on("resize", () => saveState$.next()); + win.on("move", () => saveState$.next()); + win.on("closed", () => saveState$.unsubscribe()); } - saveState$ - .pipe(debounceTime(500)) - .subscribe(() => saveState()); + saveState$.pipe(debounceTime(500)).subscribe(() => saveState()); setState(); - return ({ + return { ...windowState, monitorState, - }); + }; } function createWindow(): BrowserWindow { // Main object for managing window state // Initialize with a window name and default size - const mainWindowManager = windowStateManager('main', { width: 900, height: 600 }); + const mainWindowManager = windowStateManager("main", { + width: 900, + height: 600, + }); const windowOptions: BrowserWindowConstructorOptions = { width: mainWindowManager.width, height: mainWindowManager.height, x: mainWindowManager.x, y: mainWindowManager.y, - backgroundColor: '#444444', - title: 'WowUp', - titleBarStyle: 'hidden', + backgroundColor: "#444444", + title: "WowUp", + titleBarStyle: "hidden", webPreferences: { preload: path.join(__dirname, "preload.js"), nodeIntegration: true, @@ -264,21 +250,20 @@ function createWindow(): BrowserWindow { win.webContents.userAgent = USER_AGENT; - win.once('ready-to-show', () => { + win.once("ready-to-show", () => { win.show(); - autoUpdater.checkForUpdatesAndNotify() - .then((result) => { - console.log('UPDATE', result) - }) + autoUpdater.checkForUpdatesAndNotify().then((result) => { + console.log("UPDATE", result); + }); }); - win.once('show', () => { + win.once("show", () => { if (mainWindowManager.isFullScreen) { win.setFullScreen(true); } else if (mainWindowManager.isMaximized) { win.maximize(); } - }) + }); if (isMac) { win.on("close", (e) => { @@ -383,84 +368,26 @@ ipcMain.on(DOWNLOAD_FILE_CHANNEL, async (evt, arg: DownloadRequest) => { const download = await electronDl.download(win, arg.url, { directory: arg.outputFolder, onProgress: (progress) => { - win.webContents.send(arg.url, { + const progressStatus: DownloadStatus = { type: DownloadStatusType.Progress, progress: parseFloat((progress.percent * 100.0).toFixed(2)), - } as DownloadStatus); + }; + + win.webContents.send(arg.responseKey, progressStatus); }, }); - win.webContents.send(arg.url, { + const status: DownloadStatus = { type: DownloadStatusType.Complete, savePath: download.getSavePath(), - } as DownloadStatus); + }; + win.webContents.send(arg.responseKey, status); } catch (err) { console.error(err); - win.webContents.send(arg.url, { + const status: DownloadStatus = { type: DownloadStatusType.Error, error: err, - } as DownloadStatus); - } -}); - -ipcMain.on(UNZIP_FILE_CHANNEL, async (evt, arg: UnzipRequest) => { - const zipFilePath = arg.zipFilePath; - const outputFolder = arg.outputFolder; - - const zip = new admZip(zipFilePath); - zip.extractAllToAsync(outputFolder, true, (err) => { - const status: UnzipStatus = { - type: UnzipStatusType.Complete, - outputFolder, }; - - if (err) { - status.type = UnzipStatusType.Error; - status.error = err; - } - - win.webContents.send(zipFilePath, status); - }); -}); - -ipcMain.on(COPY_FILE_CHANNEL, async (evt, arg: CopyFileRequest) => { - console.log("Copy File", arg); - fs.copyFile(arg.sourceFilePath, arg.destinationFilePath, (err) => { - win.webContents.send(arg.destinationFilePath, { error: err }); - }); -}); - -ipcMain.on(COPY_DIRECTORY_CHANNEL, async (evt, arg: CopyDirectoryRequest) => { - console.log("Copy Dir", arg); - ncp(arg.sourcePath, arg.destinationPath, (err) => { - win.webContents.send(arg.destinationPath, err); - }); -}); - -ipcMain.on( - DELETE_DIRECTORY_CHANNEL, - async (evt, arg: DeleteDirectoryRequest) => { - console.log("Delete Dir", arg); - rimraf(arg.sourcePath, (err) => { - win.webContents.send(arg.sourcePath, err); - }); + win.webContents.send(arg.responseKey, status); } -); - -ipcMain.on(RENAME_DIRECTORY_CHANNEL, async (evt, arg: CopyDirectoryRequest) => { - console.log("Rename Dir", arg); - fs.rename(arg.sourcePath, arg.destinationPath, (err) => { - win.webContents.send(arg.destinationPath, err); - }); -}); - -ipcMain.on(READ_FILE_CHANNEL, async (evt, arg: ReadFileRequest) => { - // console.log('Read File', arg); - const response: ReadFileResponse = { data: "" }; - try { - response.data = await readFile(arg.sourcePath); - } catch (err) { - response.error = err; - } - win.webContents.send(arg.sourcePath, response); }); diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 1ffbeb9c..5ef96e21 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -174,7 +174,7 @@ export class AddonService { ); onUpdate?.call(this, AddonInstallState.Installing, 75); - + this._addonInstalledSrc.next({ addon, installState: AddonInstallState.Installing, @@ -185,6 +185,7 @@ export class AddonService { this._wowUpService.applicationDownloadsFolderPath, uuidv4() ); + unzippedDirectory = await this._downloadService.unzipFile( downloadedFilePath, unzipPath diff --git a/wowup-electron/src/app/services/download/download.service.ts b/wowup-electron/src/app/services/download/download.service.ts index e56df6c0..100c4844 100644 --- a/wowup-electron/src/app/services/download/download.service.ts +++ b/wowup-electron/src/app/services/download/download.service.ts @@ -1,5 +1,10 @@ import { Injectable } from "@angular/core"; -import { COPY_FILE_CHANNEL, DOWNLOAD_FILE_CHANNEL, UNZIP_FILE_CHANNEL } from "common/constants"; +import { + COPY_FILE_CHANNEL, + DOWNLOAD_FILE_CHANNEL, + UNZIP_FILE_CHANNEL, +} from "common/constants"; +import { v4 as uuidv4 } from "uuid"; import { DownloadRequest } from "common/models/download-request"; import { DownloadStatus } from "common/models/download-status"; import { DownloadStatusType } from "common/models/download-status-type"; @@ -10,78 +15,93 @@ import { ElectronService } from "../electron/electron.service"; import { CopyFileRequest } from "common/models/copy-file-request"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class DownloadSevice { + constructor(private _electronService: ElectronService) {} - constructor( - private _electronService: ElectronService - ) { } + public downloadZipFile( + url: string, + outputFolder: string, + onProgress?: (progress: number) => void + ): Promise { + return new Promise((resolve, reject) => { + const request: DownloadRequest = { + url, + outputFolder, + responseKey: uuidv4(), + }; - public downloadZipFile(url: string, outputFolder: string, onProgress?: (progress: number) => void): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: DownloadStatus) => { - switch (arg.type) { - case DownloadStatusType.Complete: - resolve(arg.savePath); - this._electronService.ipcRenderer.off(url, eventHandler); - break; - case DownloadStatusType.Error: - reject(arg.error); - this._electronService.ipcRenderer.off(url, eventHandler); - break; - case DownloadStatusType.Progress: - onProgress?.call(null, arg.progress); - break; - default: - break; - } - }; + const eventHandler = (_evt: any, arg: DownloadStatus) => { + switch (arg.type) { + case DownloadStatusType.Complete: + this._electronService.ipcRenderer.off( + request.responseKey, + eventHandler + ); + resolve(arg.savePath); + break; + case DownloadStatusType.Error: + this._electronService.ipcRenderer.off( + request.responseKey, + eventHandler + ); + reject(arg.error); + break; + case DownloadStatusType.Progress: + onProgress?.call(null, arg.progress); + break; + default: + break; + } + }; - this._electronService.ipcRenderer.on(url, eventHandler); - this._electronService.ipcRenderer.send(DOWNLOAD_FILE_CHANNEL, { url, outputFolder } as DownloadRequest); - }) - } + this._electronService.ipcRenderer.on(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(DOWNLOAD_FILE_CHANNEL, request); + }); + } - public unzipFile(zipFilePath: string, outputFolder: string): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: UnzipStatus) => { - this._electronService.ipcRenderer.off(zipFilePath, eventHandler); + public unzipFile(zipFilePath: string, outputFolder: string): Promise { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: UnzipStatus) => { + if (arg.type === UnzipStatusType.Error) { + return reject(arg.error); + } + resolve(arg.outputFolder); + }; - if (arg.type === UnzipStatusType.Error) { - return reject(arg.error); - } - resolve(arg.outputFolder); - } + const request: UnzipRequest = { + outputFolder, + zipFilePath, + responseKey: uuidv4(), + }; - const request: UnzipRequest = { - outputFolder, - zipFilePath - }; + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(UNZIP_FILE_CHANNEL, request); + }); + } - this._electronService.ipcRenderer.on(zipFilePath, eventHandler); - this._electronService.ipcRenderer.send(UNZIP_FILE_CHANNEL, request); - }); - } + public copyFile( + sourceFilePath: string, + destinationFilePath: string + ): Promise { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: { error?: Error }) => { + if (arg.error) { + return reject(arg.error); + } - public copyFile(sourceFilePath: string, destinationFilePath: string): Promise { - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: { error?: Error }) => { - this._electronService.ipcRenderer.off(destinationFilePath, eventHandler); - if (arg.error) { - return reject(arg.error); - } + resolve(destinationFilePath); + }; - resolve(destinationFilePath); - }; + const request: CopyFileRequest = { + destinationFilePath, + sourceFilePath, + responseKey: uuidv4(), + }; - const request: CopyFileRequest = { - destinationFilePath, - sourceFilePath - }; - - this._electronService.ipcRenderer.on(destinationFilePath, eventHandler); - this._electronService.ipcRenderer.send(COPY_FILE_CHANNEL, request); - }) - } -} \ No newline at end of file + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(COPY_FILE_CHANNEL, request); + }); + } +} diff --git a/wowup-electron/src/app/services/files/file.service.ts b/wowup-electron/src/app/services/files/file.service.ts index 2ca47e46..49ef0a68 100644 --- a/wowup-electron/src/app/services/files/file.service.ts +++ b/wowup-electron/src/app/services/files/file.service.ts @@ -1,27 +1,33 @@ import { Injectable } from "@angular/core"; -import { COPY_DIRECTORY_CHANNEL, DELETE_DIRECTORY_CHANNEL, LIST_DIRECTORIES_CHANNEL, LIST_FILES_CHANNEL, PATH_EXISTS_CHANNEL, READ_FILE_CHANNEL, RENAME_DIRECTORY_CHANNEL, SHOW_DIRECTORY } from "common/constants"; +import { + COPY_DIRECTORY_CHANNEL, + DELETE_DIRECTORY_CHANNEL, + LIST_DIRECTORIES_CHANNEL, + LIST_FILES_CHANNEL, + PATH_EXISTS_CHANNEL, + READ_FILE_CHANNEL, + RENAME_DIRECTORY_CHANNEL, + SHOW_DIRECTORY, +} from "common/constants"; import { CopyDirectoryRequest } from "common/models/copy-directory-request"; import { DeleteDirectoryRequest } from "common/models/delete-directory-request"; import { ElectronService } from "../electron/electron.service"; -import * as fs from 'fs'; -import * as globrex from 'globrex'; +import * as fs from "fs"; +import * as globrex from "globrex"; import { ReadFileResponse } from "common/models/read-file-response"; import { ReadFileRequest } from "common/models/read-file-request"; import { ListFilesResponse } from "common/models/list-files-response"; import { ListFilesRequest } from "common/models/list-files-request"; import { ShowDirectoryRequest } from "common/models/show-directory-request"; -import { v4 as uuidv4 } from 'uuid'; +import { v4 as uuidv4 } from "uuid"; import { ValueRequest } from "common/models/value-request"; import { ValueResponse } from "common/models/value-response"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class FileService { - - constructor( - private _electronService: ElectronService - ) { } + constructor(private _electronService: ElectronService) {} public showDirectory(sourceDir: string) { return new Promise((resolve, reject) => { @@ -29,11 +35,14 @@ export class FileService { resolve(arg); }; - const request: ShowDirectoryRequest = { sourceDir, responseKey: uuidv4() }; + const request: ShowDirectoryRequest = { + sourceDir, + responseKey: uuidv4(), + }; this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(SHOW_DIRECTORY, request); - }) + }); } public pathExists(sourcePath: string) { @@ -45,11 +54,14 @@ export class FileService { resolve(arg.value); }; - const request: ValueRequest = { value: sourcePath, responseKey: uuidv4() }; + const request: ValueRequest = { + value: sourcePath, + responseKey: uuidv4(), + }; this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(PATH_EXISTS_CHANNEL, request); - }) + }); } public deleteDirectory(sourcePath: string) { @@ -61,11 +73,14 @@ export class FileService { resolve(sourcePath); }; - const request: DeleteDirectoryRequest = { sourcePath }; + const request: DeleteDirectoryRequest = { + sourcePath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(sourcePath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(DELETE_DIRECTORY_CHANNEL, request); - }) + }); } public copyDirectory(sourcePath: string, destinationPath: string) { @@ -78,11 +93,15 @@ export class FileService { resolve(destinationPath); }; - const request: CopyDirectoryRequest = { sourcePath, destinationPath }; + const request: CopyDirectoryRequest = { + sourcePath, + destinationPath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(destinationPath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(COPY_DIRECTORY_CHANNEL, request); - }) + }); } public renameDirectory(sourcePath: string, destinationPath: string) { @@ -95,9 +114,13 @@ export class FileService { resolve(destinationPath); }; - const request: CopyDirectoryRequest = { sourcePath, destinationPath }; + const request: CopyDirectoryRequest = { + sourcePath, + destinationPath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(destinationPath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(RENAME_DIRECTORY_CHANNEL, request); }); } @@ -112,11 +135,14 @@ export class FileService { resolve(arg.data); }; - const request: ReadFileRequest = { sourcePath }; + const request: ReadFileRequest = { + sourcePath, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(sourcePath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(READ_FILE_CHANNEL, request); - }) + }); } public listDirectories(sourcePath: string): Promise { @@ -129,7 +155,10 @@ export class FileService { resolve(arg.value); }; - const request: ValueRequest = { value: sourcePath, responseKey: uuidv4() }; + const request: ValueRequest = { + value: sourcePath, + responseKey: uuidv4(), + }; this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(LIST_DIRECTORIES_CHANNEL, request); @@ -139,12 +168,16 @@ export class FileService { public listFiles(sourcePath: string, filter: string) { const globFilter = globrex(filter); - return fs.readdirSync(sourcePath, { withFileTypes: true }) - .filter(entry => !!globFilter.regex.test(entry.name)) - .map(entry => entry.name); + return fs + .readdirSync(sourcePath, { withFileTypes: true }) + .filter((entry) => !!globFilter.regex.test(entry.name)) + .map((entry) => entry.name); } - public listAllFiles(sourcePath: string, recursive: boolean = true): Promise { + public listAllFiles( + sourcePath: string, + recursive: boolean = true + ): Promise { return new Promise((resolve, reject) => { const eventHandler = (_evt: any, arg: ListFilesResponse) => { if (arg.error) { @@ -154,10 +187,14 @@ export class FileService { resolve(arg.files); }; - const request: ListFilesRequest = { sourcePath, recursive, responseKey: uuidv4() }; + const request: ListFilesRequest = { + sourcePath, + recursive, + responseKey: uuidv4(), + }; - this._electronService.ipcRenderer.once(sourcePath, eventHandler); + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); this._electronService.ipcRenderer.send(LIST_FILES_CHANNEL, request); }); } -} \ No newline at end of file +} diff --git a/wowup-electron/src/common/models/copy-directory-request.ts b/wowup-electron/src/common/models/copy-directory-request.ts index 1daebc25..58af48a5 100644 --- a/wowup-electron/src/common/models/copy-directory-request.ts +++ b/wowup-electron/src/common/models/copy-directory-request.ts @@ -1,4 +1,6 @@ -export interface CopyDirectoryRequest { - sourcePath: string; - destinationPath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface CopyDirectoryRequest extends IpcRequest { + sourcePath: string; + destinationPath: string; +} diff --git a/wowup-electron/src/common/models/copy-file-request.ts b/wowup-electron/src/common/models/copy-file-request.ts index ba372693..3b2392d8 100644 --- a/wowup-electron/src/common/models/copy-file-request.ts +++ b/wowup-electron/src/common/models/copy-file-request.ts @@ -1,4 +1,6 @@ -export interface CopyFileRequest { - sourceFilePath: string; - destinationFilePath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface CopyFileRequest extends IpcRequest { + sourceFilePath: string; + destinationFilePath: string; +} diff --git a/wowup-electron/src/common/models/delete-directory-request.ts b/wowup-electron/src/common/models/delete-directory-request.ts index 27fcc9b1..809fadec 100644 --- a/wowup-electron/src/common/models/delete-directory-request.ts +++ b/wowup-electron/src/common/models/delete-directory-request.ts @@ -1,3 +1,5 @@ -export interface DeleteDirectoryRequest { - sourcePath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface DeleteDirectoryRequest extends IpcRequest { + sourcePath: string; +} diff --git a/wowup-electron/src/common/models/download-request.ts b/wowup-electron/src/common/models/download-request.ts index 7998414a..d44ef73e 100644 --- a/wowup-electron/src/common/models/download-request.ts +++ b/wowup-electron/src/common/models/download-request.ts @@ -1,4 +1,6 @@ -export interface DownloadRequest { - url: string; - outputFolder: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface DownloadRequest extends IpcRequest { + url: string; + outputFolder: string; +} diff --git a/wowup-electron/src/common/models/read-file-request.ts b/wowup-electron/src/common/models/read-file-request.ts index d12eb617..bdfd2e94 100644 --- a/wowup-electron/src/common/models/read-file-request.ts +++ b/wowup-electron/src/common/models/read-file-request.ts @@ -1,3 +1,5 @@ -export interface ReadFileRequest { - sourcePath: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface ReadFileRequest extends IpcRequest { + sourcePath: string; +} diff --git a/wowup-electron/src/common/models/unzip-request.ts b/wowup-electron/src/common/models/unzip-request.ts index dc04a261..8bb375f6 100644 --- a/wowup-electron/src/common/models/unzip-request.ts +++ b/wowup-electron/src/common/models/unzip-request.ts @@ -1,4 +1,6 @@ -export interface UnzipRequest { - zipFilePath: string; - outputFolder: string; -} \ No newline at end of file +import { IpcRequest } from "./ipc-request"; + +export interface UnzipRequest extends IpcRequest { + zipFilePath: string; + outputFolder: string; +} From 5c17e64bb8a6e07915d685076136dd3b1808f0b7 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 16:10:09 -0500 Subject: [PATCH 16/34] Fix bug with uninstalling addons with no installed folders --- wowup-electron/src/app/services/addons/addon.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 5ef96e21..b152cbee 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -185,7 +185,7 @@ export class AddonService { this._wowUpService.applicationDownloadsFolderPath, uuidv4() ); - + unzippedDirectory = await this._downloadService.unzipFile( downloadedFilePath, unzipPath @@ -364,7 +364,7 @@ export class AddonService { } public async removeAddon(addon: Addon) { - const installedDirectories = addon.installedFolders.split(","); + const installedDirectories = addon.installedFolders?.split(",") ?? []; const addonFolderPath = this._warcraftService.getAddonFolderPath( addon.clientType From e9c0031d3323a298b9347ad317191256db30dcc0 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 16:44:43 -0500 Subject: [PATCH 17/34] Text color ;) --- .../potential-addon-table-column.component.html | 2 +- .../potential-addon-table-column.component.scss | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html index 65e2351a..f12920d0 100644 --- a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html +++ b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html @@ -3,6 +3,6 @@
{{addon.name}} -
{{addon?.version}}
+
{{addon?.version}}
diff --git a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.scss b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.scss index e8e1d927..b8eed7c9 100644 --- a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.scss +++ b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.scss @@ -33,4 +33,8 @@ color: $white-2; } } + + .addon-version { + color: $white-2; + } } From 6e408fc014d3b743185ba1a534c8055f7582b231 Mon Sep 17 00:00:00 2001 From: Dean Campbell Date: Fri, 16 Oct 2020 16:02:28 -0700 Subject: [PATCH 18/34] Remove addon provider dependency on wowup service --- .../src/app/addon-providers/addon-provider.ts | 4 ++-- .../addon-providers/curse-addon-provider.ts | 18 ++++++++++-------- .../pages/get-addons/get-addons.component.ts | 7 +++++-- .../services/addons/addon.provider.factory.ts | 7 ++----- .../src/app/services/addons/addon.service.ts | 10 ++++++---- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/addon-provider.ts b/wowup-electron/src/app/addon-providers/addon-provider.ts index da3d649f..dbd962d0 100644 --- a/wowup-electron/src/app/addon-providers/addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/addon-provider.ts @@ -12,9 +12,9 @@ export interface AddonProvider { getAll(clientType: WowClientType, addonIds: string[]): Promise; - getFeaturedAddons(clientType: WowClientType): Observable; + getFeaturedAddons(clientType: WowClientType, channelType?: AddonChannelType): Observable; - searchByQuery(query: string, clientType: WowClientType): Promise; + searchByQuery(query: string, clientType: WowClientType, channelType?: AddonChannelType): Promise; searchByUrl(addonUri: URL, clientType: WowClientType): Promise; diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index d2234c32..37cd5506 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -46,8 +46,7 @@ export class CurseAddonProvider implements AddonProvider { constructor( private _httpClient: HttpClient, private _cachingService: CachingService, - private _electronService: ElectronService, - private _wowUpService: WowUpService, + private _electronService: ElectronService ) { this._circuitBreaker = new CircuitBreaker( (action) => this.sendRequest(action), @@ -272,13 +271,14 @@ export class CurseAddonProvider implements AddonProvider { return addonResults; } - getFeaturedAddons(clientType: WowClientType): Observable { + getFeaturedAddons(clientType: WowClientType, channelType?: AddonChannelType): Observable { + channelType = typeof channelType === 'undefined' ? AddonChannelType.Stable : channelType; return from(this.getFeaturedAddonList()).pipe( map((addons) => { return this.filterFeaturedAddons(addons, clientType); }), map((filteredAddons) => { - return filteredAddons.map((addon) => this.getPotentialAddon(addon, clientType)); + return filteredAddons.map((addon) => this.getPotentialAddon(addon, clientType, channelType)); }) ); } @@ -304,8 +304,10 @@ export class CurseAddonProvider implements AddonProvider { async searchByQuery( query: string, - clientType: WowClientType + clientType: WowClientType, + channelType?: AddonChannelType ): Promise { + channelType = typeof channelType === 'undefined' ? AddonChannelType.Stable : channelType; var searchResults: PotentialAddon[] = []; var response = await this.getSearchResults(query); @@ -315,7 +317,7 @@ export class CurseAddonProvider implements AddonProvider { continue; } - searchResults.push(this.getPotentialAddon(result, clientType)); + searchResults.push(this.getPotentialAddon(result, clientType, channelType)); } return searchResults; @@ -389,12 +391,12 @@ export class CurseAddonProvider implements AddonProvider { throw new Error("Method not implemented."); } - private getPotentialAddon(result: CurseSearchResult, clientType: WowClientType): PotentialAddon { + private getPotentialAddon(result: CurseSearchResult, clientType: WowClientType, channelType: AddonChannelType): PotentialAddon { const clientTypeStr = this.getGameVersionFlavor(clientType); let latestFile = _.orderBy(result.latestFiles, 'id', 'desc') .find(file => file.gameVersionFlavor === clientTypeStr && - this.getChannelType(file.releaseType) === this._wowUpService.getDefaultAddonChannel(clientType) + this.getChannelType(file.releaseType) === channelType ); if (!latestFile) { latestFile = _.first(result.latestFiles); diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index ca2e753d..c6556834 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -15,6 +15,7 @@ import { map } from "rxjs/operators"; import { MatTableDataSource } from "@angular/material/table"; import { MatSort } from "@angular/material/sort"; import * as _ from "lodash"; +import { WowUpService } from "app/services/wowup/wowup.service"; @Component({ selector: "app-get-addons", @@ -55,6 +56,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { private _addonService: AddonService, private _sessionService: SessionService, private _dialog: MatDialog, + private _wowUpService: WowUpService, public electronService: ElectronService, public warcraftService: WarcraftService ) { @@ -134,7 +136,8 @@ export class GetAddonsComponent implements OnInit, OnDestroy { let searchResults = await this._addonService.search( this.query, - this.selectedClient + this.selectedClient, + this._wowUpService.getDefaultAddonChannel(this.selectedClient) ); searchResults = this.filterInstalledAddons(searchResults); @@ -159,7 +162,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { this.isBusy = true; - this._addonService.getFeaturedAddons(clientType).subscribe({ + this._addonService.getFeaturedAddons(clientType, this._wowUpService.getDefaultAddonChannel(clientType)).subscribe({ next: (addons) => { addons = this.filterInstalledAddons(addons); this.formatAddons(addons); diff --git a/wowup-electron/src/app/services/addons/addon.provider.factory.ts b/wowup-electron/src/app/services/addons/addon.provider.factory.ts index f1bc2f49..55d5cbf7 100644 --- a/wowup-electron/src/app/services/addons/addon.provider.factory.ts +++ b/wowup-electron/src/app/services/addons/addon.provider.factory.ts @@ -11,7 +11,6 @@ import { CachingService } from "../caching/caching-service"; import { ElectronService } from "../electron/electron.service"; import { FileService } from "../files/file.service"; import { SessionService } from "../session/session.service"; -import { WowUpService } from "../wowup/wowup.service"; @Injectable({ providedIn: "root", @@ -22,8 +21,7 @@ export class AddonProviderFactory { private _electronService: ElectronService, private _httpClient: HttpClient, private _sessionService: SessionService, - private _fileService: FileService, - private _wowUpService: WowUpService, + private _fileService: FileService ) {} public getAddonProvider(providerType: T & AddonProvider) { @@ -41,8 +39,7 @@ export class AddonProviderFactory { return new CurseAddonProvider( this._httpClient, this._cachingService, - this._electronService, - this._wowUpService + this._electronService ); } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 294e685f..158aa00e 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -58,10 +58,11 @@ export class AddonService { public async search( query: string, - clientType: WowClientType + clientType: WowClientType, + channelType: AddonChannelType ): Promise { var searchTasks = this._addonProviders.map((p) => - p.searchByQuery(query, clientType) + p.searchByQuery(query, clientType, channelType) ); var searchResults = await Promise.all(searchTasks); @@ -528,10 +529,11 @@ export class AddonService { } public getFeaturedAddons( - clientType: WowClientType + clientType: WowClientType, + channelType: AddonChannelType, ): Observable { return forkJoin( - this._addonProviders.map((p) => p.getFeaturedAddons(clientType)) + this._addonProviders.map((p) => p.getFeaturedAddons(clientType, channelType)) ).pipe( map((results) => { return _.orderBy(results.flat(1), ["downloadCount"]).reverse(); From 67dea624ceff0f97a9484cf955c29be6f0aac54f Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 20:34:23 -0500 Subject: [PATCH 19/34] Start analytics --- .../services/analytics/analytics.service.ts | 121 +++++++++++++----- .../src/environments/environment.dev.ts | 6 +- .../src/environments/environment.prod.ts | 2 + .../src/environments/environment.ts | 2 + .../src/environments/environment.web.ts | 4 +- 5 files changed, 100 insertions(+), 35 deletions(-) diff --git a/wowup-electron/src/app/services/analytics/analytics.service.ts b/wowup-electron/src/app/services/analytics/analytics.service.ts index 1b07674f..4cb6df67 100644 --- a/wowup-electron/src/app/services/analytics/analytics.service.ts +++ b/wowup-electron/src/app/services/analytics/analytics.service.ts @@ -1,47 +1,104 @@ import { ErrorHandler, Injectable } from "@angular/core"; -import * as Rollbar from 'rollbar'; +import * as Rollbar from "rollbar"; import { PreferenceStorageService } from "../storage/preference-storage.service"; import { telemetryEnabledKey } from "../../../constants"; +import { AppConfig } from "environments/environment"; +import { HttpClient, HttpParams } from "@angular/common/http"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class AnalyticsService implements ErrorHandler { - private rollbarConfig = { - accessToken: 'd01c11314a064572b11acee18d880650', - captureUncaught: true, - captureUnhandledRejections: true, - }; + private readonly analyticsUrl = "https://www.google-analytics.com"; + private readonly installIdPreferenceKey = "install_id"; + private readonly telemetryPromptUsedKey = "telemetry_prompt_sent"; + private readonly telemetryEnabledKey = "telemetry_enabled"; - private _rollbar: Rollbar; - private get rollbar() { - if (!this.telemetryEnabled) { - return undefined; - } + private readonly rollbarConfig = { + accessToken: AppConfig.rollbarAccessKey, + captureUncaught: true, + captureUnhandledRejections: true, + }; - if (!this._rollbar) { - this._rollbar = new Rollbar(this.rollbarConfig); - } - return this._rollbar; + private _rollbar: Rollbar; + private get rollbar() { + if (!this.telemetryEnabled) { + return undefined; } - private get telemetryEnabled() { - return this._preferenceStorageService.get(telemetryEnabledKey) === true.toString(); + if (!this._rollbar) { + this._rollbar = new Rollbar(this.rollbarConfig); + } + return this._rollbar; + } + + private get telemetryEnabled() { + return ( + this._preferenceStorageService.get(telemetryEnabledKey) === + true.toString() + ); + } + + public get shouldPromptTelemetry() { + return ( + this._preferenceStorageService.get(telemetryEnabledKey) === undefined + ); + } + + constructor( + private _preferenceStorageService: PreferenceStorageService, + private _httpClient: HttpClient + ) {} + + public async TrackStartup() { + await Track((request) => { + request.SetQueryParam("t", "pageview").SetQueryParam("dp", "app/startup"); + }); + + await TrackAppCenter("AppStartup"); + } + + // ErrorHandler + handleError(error: any): void { + console.error("Caught error", error); + + this.rollbar?.error(error.originalError || error); + } + + private async track(action: (params: HttpParams) => void = undefined) { + if (!this.telemetryEnabled) { + return; } + var url = `${this.analyticsUrl}/collect`; - public get shouldPromptTelemetry() { - return this._preferenceStorageService.get(telemetryEnabledKey) === undefined; + try { + let params = new HttpParams(); + params.set('v', '1'); + params.set('tid', AppConfig.googleAnalyticsId); + params.set('cid', AppConfig.googleAnalyticsId); + + action?.call(params); + + const response = await this._httpClient + .post( + url, + {}, + { + params, + } + ) + .toPromise(); + var response = await request + .SetQueryParam("v", "1") + .SetQueryParam("tid", "") + .SetQueryParam("cid", InstallId) + .SetQueryParam("ua", HttpUtilities.UserAgent) + .SetQueryParam("an", "WowUp Client") + .SetQueryParam("av", AppUtilities.CurrentVersionString) + .PostJsonAsync(new {}()); + } catch (Exception) { + // eat } - - constructor( - private _preferenceStorageService: PreferenceStorageService - ) { } - - // ErrorHandler - handleError(error: any): void { - console.error('Caught error', error); - - this.rollbar?.error(error.originalError || error); - } -} \ No newline at end of file + } +} diff --git a/wowup-electron/src/environments/environment.dev.ts b/wowup-electron/src/environments/environment.dev.ts index 42fd2388..b0d3deaa 100644 --- a/wowup-electron/src/environments/environment.dev.ts +++ b/wowup-electron/src/environments/environment.dev.ts @@ -5,7 +5,9 @@ export const AppConfig = { production: false, - environment: 'DEV', - wowUpApiUrl: 'https://api.dev.wowup.io', + environment: "DEV", + wowUpApiUrl: "https://api.dev.wowup.io", wowUpHubUrl: "https://hub.dev.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; diff --git a/wowup-electron/src/environments/environment.prod.ts b/wowup-electron/src/environments/environment.prod.ts index a8e45d57..cd8a50b7 100644 --- a/wowup-electron/src/environments/environment.prod.ts +++ b/wowup-electron/src/environments/environment.prod.ts @@ -3,4 +3,6 @@ export const AppConfig = { environment: "PROD", wowUpApiUrl: "https://api.wowup.io", wowUpHubUrl: "https://hub.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; diff --git a/wowup-electron/src/environments/environment.ts b/wowup-electron/src/environments/environment.ts index 984e7d14..6e4ad5a7 100644 --- a/wowup-electron/src/environments/environment.ts +++ b/wowup-electron/src/environments/environment.ts @@ -3,4 +3,6 @@ export const AppConfig = { environment: "LOCAL", wowUpApiUrl: "https://api.dev.wowup.io", wowUpHubUrl: "https://hub.dev.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; diff --git a/wowup-electron/src/environments/environment.web.ts b/wowup-electron/src/environments/environment.web.ts index 5936cb06..b0d3deaa 100644 --- a/wowup-electron/src/environments/environment.web.ts +++ b/wowup-electron/src/environments/environment.web.ts @@ -5,7 +5,9 @@ export const AppConfig = { production: false, - environment: 'DEV', + environment: "DEV", wowUpApiUrl: "https://api.dev.wowup.io", wowUpHubUrl: "https://hub.dev.wowup.io", + rollbarAccessKey: "d01c11314a064572b11acee18d880650", + googleAnalyticsId: "UA-92563227-4", }; From e3013163d3f72d0edb50756f2c48a603d98cb7b7 Mon Sep 17 00:00:00 2001 From: john liddell Date: Fri, 16 Oct 2020 22:11:35 -0500 Subject: [PATCH 20/34] Fix issues related to error handler add some button tracking --- wowup-electron/src/app/app.component.ts | 7 +- wowup-electron/src/app/app.module.ts | 8 +- .../wow-client-options.component.html | 15 ++- wowup-electron/src/app/directive.module.ts | 13 +- .../user-action-tracker.directive.spec.ts | 8 ++ .../user-action-tracker.directive.ts | 20 +++ .../interceptors/error-handler-intercepter.ts | 36 +++++ .../app/pages/options/options.component.html | 14 +- .../app/pages/options/options.component.ts | 14 +- .../services/analytics/analytics.service.ts | 126 ++++++++++-------- .../src/app/services/wowup/wowup.service.ts | 12 -- wowup-electron/src/constants.ts | 2 - 12 files changed, 179 insertions(+), 96 deletions(-) create mode 100644 wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts create mode 100644 wowup-electron/src/app/directives/user-action-tracker.directive.ts create mode 100644 wowup-electron/src/app/interceptors/error-handler-intercepter.ts diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index 5a4860a8..89ca3649 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -37,7 +37,7 @@ export class AppComponent implements AfterViewInit { if (this._analyticsService.shouldPromptTelemetry) { this.openDialog(); } else { - // TODO track startup + this._analyticsService.trackStartup(); } this.onAutoUpdateInterval(); @@ -53,7 +53,10 @@ export class AppComponent implements AfterViewInit { }); dialogRef.afterClosed().subscribe((result) => { - this._wowUpService.telemetryEnabled = result; + this._analyticsService.telemetryEnabled = result; + if (result) { + this._analyticsService.trackStartup(); + } }); } diff --git a/wowup-electron/src/app/app.module.ts b/wowup-electron/src/app/app.module.ts index 357a2806..8b62e433 100644 --- a/wowup-electron/src/app/app.module.ts +++ b/wowup-electron/src/app/app.module.ts @@ -10,7 +10,7 @@ import { HTTP_INTERCEPTORS, } from "@angular/common/http"; import { SharedModule } from "./shared/shared.module"; - +import { ErrorHandlerIntercepter } from './interceptors/error-handler-intercepter'; import { AppRoutingModule } from "./app-routing.module"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @@ -28,6 +28,8 @@ import { AnalyticsService } from "./services/analytics/analytics.service"; import { DirectiveModule } from "./directive.module"; import { MatModule } from "./mat-module"; import { MatProgressButtonsModule } from "mat-progress-buttons"; +import { ElectronService } from "./services"; +import { PreferenceStorageService } from "./services/storage/preference-storage.service"; // AoT requires an exported function for factories export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { @@ -61,8 +63,8 @@ export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader { useClass: DefaultHeadersInterceptor, multi: true, }, - { provide: ErrorHandler, useClass: AnalyticsService }, + { provide: ErrorHandler, useClass: ErrorHandlerIntercepter, deps: [AnalyticsService] }, ], bootstrap: [AppComponent], }) -export class AppModule {} +export class AppModule { } diff --git a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html index e275fada..da781d35 100644 --- a/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html +++ b/wowup-electron/src/app/components/wow-client-options/wow-client-options.component.html @@ -4,9 +4,11 @@ {{clientTypeName}} Path - The folder that contains the {{clientTypeName | lowercase}} client folder "{{clientFolderName}}" + The folder that contains the {{clientTypeName | lowercase}} client folder "{{clientFolderName}}" + - +
@@ -14,7 +16,9 @@
{{'PAGES.OPTIONS.WOW.DEFAULT_ADDON_CHANNEL_SELECT_LABEL' | translate}} - + {{channel.name}} @@ -24,6 +28,7 @@

{{'PAGES.OPTIONS.WOW.AUTO_UPDATE_LABEL' | translate}}

{{'PAGES.OPTIONS.WOW.AUTO_UPDATE_DESCRIPTION' | translate}}
- + - \ No newline at end of file + diff --git a/wowup-electron/src/app/directive.module.ts b/wowup-electron/src/app/directive.module.ts index 035632b9..828962f5 100644 --- a/wowup-electron/src/app/directive.module.ts +++ b/wowup-electron/src/app/directive.module.ts @@ -1,8 +1,15 @@ import { NgModule } from "@angular/core"; import { ExternalLinkDirective } from "./directives/external-link.directive"; +import { UserActionTrackerDirective } from "./directives/user-action-tracker.directive"; @NgModule({ - declarations: [ExternalLinkDirective], - exports: [ExternalLinkDirective], + declarations: [ + ExternalLinkDirective, + UserActionTrackerDirective + ], + exports: [ + ExternalLinkDirective, + UserActionTrackerDirective + ], }) -export class DirectiveModule {} +export class DirectiveModule { } diff --git a/wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts b/wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts new file mode 100644 index 00000000..40f2ea87 --- /dev/null +++ b/wowup-electron/src/app/directives/user-action-tracker.directive.spec.ts @@ -0,0 +1,8 @@ +import { UserActionTrackerDirective } from './user-action-tracker.directive'; + +describe('UserActionTrackerDirective', () => { + it('should create an instance', () => { + const directive = new UserActionTrackerDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/wowup-electron/src/app/directives/user-action-tracker.directive.ts b/wowup-electron/src/app/directives/user-action-tracker.directive.ts new file mode 100644 index 00000000..4100daaf --- /dev/null +++ b/wowup-electron/src/app/directives/user-action-tracker.directive.ts @@ -0,0 +1,20 @@ +import { Directive, HostListener, Input } from '@angular/core'; +import { AnalyticsService } from '../services/analytics/analytics.service'; + +@Directive({ + selector: '[appUserActionTracker]' +}) +export class UserActionTrackerDirective { + + @Input() appUserActionTracker: string; + @Input() category: string; + @Input() action: string; + @Input() label: string; + + @HostListener('click', ['$event']) onClick($event) { + this._analyticsService.trackUserAction(this.category, this.action, this.label); + } + + constructor(private _analyticsService: AnalyticsService) { } + +} diff --git a/wowup-electron/src/app/interceptors/error-handler-intercepter.ts b/wowup-electron/src/app/interceptors/error-handler-intercepter.ts new file mode 100644 index 00000000..7268e0a6 --- /dev/null +++ b/wowup-electron/src/app/interceptors/error-handler-intercepter.ts @@ -0,0 +1,36 @@ +import { ErrorHandler } from "@angular/core"; +import { AnalyticsService } from "app/services/analytics/analytics.service"; +import { AppConfig } from "environments/environment"; +import * as Rollbar from "rollbar"; + +export class ErrorHandlerIntercepter implements ErrorHandler { + + private readonly rollbarConfig = { + accessToken: AppConfig.rollbarAccessKey, + captureUncaught: true, + captureUnhandledRejections: true, + }; + + private _rollbar: Rollbar; + private get rollbar() { + if (!this._analytics.telemetryEnabled) { + return undefined; + } + + if (!this._rollbar) { + this._rollbar = new Rollbar(this.rollbarConfig); + } + return this._rollbar; + } + + constructor(private _analytics: AnalyticsService) { + + } + + // ErrorHandler + handleError(error: any): void { + console.error("Caught error", error); + + this.rollbar?.error(error.originalError || error); + } +} \ No newline at end of file diff --git a/wowup-electron/src/app/pages/options/options.component.html b/wowup-electron/src/app/pages/options/options.component.html index a4b3b3c1..075b81f4 100644 --- a/wowup-electron/src/app/pages/options/options.component.html +++ b/wowup-electron/src/app/pages/options/options.component.html @@ -7,7 +7,8 @@
{{'PAGES.OPTIONS.WOW.RESCAN_CLIENTS_LABEL' | translate}}
-
@@ -45,7 +46,8 @@

{{'PAGES.OPTIONS.APPLICATION.MINIMIZE_ON_CLOSE_LABEL' | translate}}

{{'PAGES.OPTIONS.APPLICATION.MINIMIZE_ON_CLOSE_DESCRIPTION' | translate}} - +
@@ -60,7 +62,8 @@

{{'PAGES.OPTIONS.DEBUG.LOG_FILES_LABEL' | translate}}

{{'PAGES.OPTIONS.DEBUG.LOG_FILES_DESCRIPTION' | translate}} - @@ -69,11 +72,12 @@

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_LABEL' | translate}}

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_DESCRIPTION' | translate}} - - \ No newline at end of file + diff --git a/wowup-electron/src/app/pages/options/options.component.ts b/wowup-electron/src/app/pages/options/options.component.ts index 92c24aca..3ba10916 100644 --- a/wowup-electron/src/app/pages/options/options.component.ts +++ b/wowup-electron/src/app/pages/options/options.component.ts @@ -4,7 +4,6 @@ import { WowClientType } from 'app/models/warcraft/wow-client-type'; import { ElectronService } from 'app/services'; import { WarcraftService } from 'app/services/warcraft/warcraft.service'; import { WowUpService } from 'app/services/wowup/wowup.service'; -import { telemetryEnabledKey } from '../../../constants'; import { filter, map } from 'rxjs/operators'; import * as _ from 'lodash'; import * as path from 'path'; @@ -13,6 +12,7 @@ import { AlertDialogComponent } from 'app/components/alert-dialog/alert-dialog.c import { getEnumList, getEnumName } from 'app/utils/enum.utils'; import { WowUpReleaseChannelType } from 'app/models/wowup/wowup-release-channel-type'; import { MatSelectChange } from '@angular/material/select'; +import { AnalyticsService } from 'app/services/analytics/analytics.service'; @Component({ selector: 'app-options', @@ -36,6 +36,7 @@ export class OptionsComponent implements OnInit, OnChanges { .map((type: WowUpReleaseChannelType) => ({ type, name: getEnumName(WowUpReleaseChannelType, type) })); constructor( + private _analyticsService: AnalyticsService, private warcraft: WarcraftService, private _electronService: ElectronService, private _warcraftService: WarcraftService, @@ -44,10 +45,9 @@ export class OptionsComponent implements OnInit, OnChanges { private zone: NgZone, public electronService: ElectronService ) { - _wowUpService.preferenceChange$ - .pipe(filter(change => change.key === telemetryEnabledKey)) - .subscribe(change => { - this.telemetryEnabled = change.value === true.toString() + _analyticsService.telemetryEnabled$ + .subscribe(enabled => { + this.telemetryEnabled = enabled; }) } @@ -71,7 +71,7 @@ export class OptionsComponent implements OnInit, OnChanges { } onTelemetryChange = (evt: MatSlideToggleChange) => { - this._wowUpService.telemetryEnabled = evt.checked; + this._analyticsService.telemetryEnabled = evt.checked; } onCollapseChange = (evt: MatSlideToggleChange) => { @@ -155,7 +155,7 @@ export class OptionsComponent implements OnInit, OnChanges { private loadData() { this.zone.run(() => { - this.telemetryEnabled = this._wowUpService.telemetryEnabled; + this.telemetryEnabled = this._analyticsService.telemetryEnabled; this.collapseToTray = this._wowUpService.collapseToTray; this.retailLocation = this.warcraft.getClientLocation(WowClientType.Retail); this.classicLocation = this.warcraft.getClientLocation(WowClientType.Classic); diff --git a/wowup-electron/src/app/services/analytics/analytics.service.ts b/wowup-electron/src/app/services/analytics/analytics.service.ts index 4cb6df67..5bd14e45 100644 --- a/wowup-electron/src/app/services/analytics/analytics.service.ts +++ b/wowup-electron/src/app/services/analytics/analytics.service.ts @@ -1,68 +1,70 @@ import { ErrorHandler, Injectable } from "@angular/core"; -import * as Rollbar from "rollbar"; +import { v4 as uuidv4 } from "uuid"; import { PreferenceStorageService } from "../storage/preference-storage.service"; -import { telemetryEnabledKey } from "../../../constants"; import { AppConfig } from "environments/environment"; import { HttpClient, HttpParams } from "@angular/common/http"; +import { ElectronService } from "../electron/electron.service"; +import { BehaviorSubject } from "rxjs"; @Injectable({ providedIn: "root", }) -export class AnalyticsService implements ErrorHandler { +export class AnalyticsService { private readonly analyticsUrl = "https://www.google-analytics.com"; private readonly installIdPreferenceKey = "install_id"; - private readonly telemetryPromptUsedKey = "telemetry_prompt_sent"; - private readonly telemetryEnabledKey = "telemetry_enabled"; + private readonly _installId: string; + private readonly _appVersion: string; + private readonly _telemetryEnabledSrc = new BehaviorSubject(false); - private readonly rollbarConfig = { - accessToken: AppConfig.rollbarAccessKey, - captureUncaught: true, - captureUnhandledRejections: true, - }; + public readonly telemetryPromptUsedKey = "telemetry_prompt_sent"; + public readonly telemetryEnabledKey = "telemetry_enabled"; + public readonly telemetryEnabled$ = this._telemetryEnabledSrc.asObservable(); - private _rollbar: Rollbar; - private get rollbar() { - if (!this.telemetryEnabled) { - return undefined; - } - - if (!this._rollbar) { - this._rollbar = new Rollbar(this.rollbarConfig); - } - return this._rollbar; - } - - private get telemetryEnabled() { - return ( - this._preferenceStorageService.get(telemetryEnabledKey) === - true.toString() - ); + private get installId() { + return this._installId; } public get shouldPromptTelemetry() { return ( - this._preferenceStorageService.get(telemetryEnabledKey) === undefined + this._preferenceStorageService.get(this.telemetryEnabledKey) === undefined ); } - constructor( - private _preferenceStorageService: PreferenceStorageService, - private _httpClient: HttpClient - ) {} - - public async TrackStartup() { - await Track((request) => { - request.SetQueryParam("t", "pageview").SetQueryParam("dp", "app/startup"); - }); - - await TrackAppCenter("AppStartup"); + public get telemetryEnabled() { + const preference = this._preferenceStorageService.findByKey(this.telemetryEnabledKey); + return preference === true.toString(); } - // ErrorHandler - handleError(error: any): void { - console.error("Caught error", error); + public set telemetryEnabled(value: boolean) { + this._preferenceStorageService.set(this.telemetryEnabledKey, value); + this._telemetryEnabledSrc.next(value); + } - this.rollbar?.error(error.originalError || error); + constructor( + private _electronService: ElectronService, + private _httpClient: HttpClient, + private _preferenceStorageService: PreferenceStorageService + ) { + this._appVersion = _electronService.remote.app.getVersion(); + this._installId = this.loadInstallId(); + this._telemetryEnabledSrc.next(this.telemetryEnabled); + console.log('installId', this._installId); + } + + public async trackStartup() { + await this.track((params) => { + params.set("t", "pageview"); + params.set("dp", "app/startup"); + }); + } + + public async trackUserAction(category: string, action: string, label: string = null) { + await this.track(params => { + params.set("t", "event"); + params.set("ec", category); + params.set("ea", action); + params.set("el", label); + }); } private async track(action: (params: HttpParams) => void = undefined) { @@ -73,32 +75,42 @@ export class AnalyticsService implements ErrorHandler { var url = `${this.analyticsUrl}/collect`; try { - let params = new HttpParams(); + let params = new URLSearchParams(); params.set('v', '1'); params.set('tid', AppConfig.googleAnalyticsId); - params.set('cid', AppConfig.googleAnalyticsId); + params.set('cid', this._installId); + params.set('ua', window.navigator.userAgent); + params.set('an', "WowUp Client"); + params.set('av', this._appVersion); - action?.call(params); + action?.call(this, params); + + const fullUrl = `${url}?${params}`; const response = await this._httpClient .post( - url, + fullUrl, {}, { - params, + responseType: 'text' } ) .toPromise(); - var response = await request - .SetQueryParam("v", "1") - .SetQueryParam("tid", "") - .SetQueryParam("cid", InstallId) - .SetQueryParam("ua", HttpUtilities.UserAgent) - .SetQueryParam("an", "WowUp Client") - .SetQueryParam("av", AppUtilities.CurrentVersionString) - .PostJsonAsync(new {}()); - } catch (Exception) { + } catch (e) { // eat + console.error(e) } } + + private loadInstallId() { + let installId = this._preferenceStorageService.findByKey(this.installIdPreferenceKey); + if (installId) { + return installId; + } + + installId = uuidv4(); + this._preferenceStorageService.set(this.installIdPreferenceKey, installId); + + return installId; + } } diff --git a/wowup-electron/src/app/services/wowup/wowup.service.ts b/wowup-electron/src/app/services/wowup/wowup.service.ts index 02092ba9..058185f3 100644 --- a/wowup-electron/src/app/services/wowup/wowup.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup.service.ts @@ -24,7 +24,6 @@ import { defaultAutoUpdateKeySuffix, defaultChannelKeySuffix, lastSelectedWowClientTypeKey, - telemetryEnabledKey, wowupReleaseChannelKey } from "../../../constants"; @@ -75,17 +74,6 @@ export class WowUpService { this._preferenceChangeSrc.next({ key, value: value.toString() }) } - public get telemetryEnabled() { - const preference = this._preferenceStorageService.findByKey(telemetryEnabledKey); - return preference === 'true'; - } - - public set telemetryEnabled(value: boolean) { - const key = telemetryEnabledKey; - this._preferenceStorageService.set(key, value); - this._preferenceChangeSrc.next({ key, value: value.toString() }) - } - public get wowUpReleaseChannel() { const preference = this._preferenceStorageService.findByKey(wowupReleaseChannelKey); return parseInt(preference, 10) as WowUpReleaseChannelType; diff --git a/wowup-electron/src/constants.ts b/wowup-electron/src/constants.ts index ed7086d2..48e46a11 100644 --- a/wowup-electron/src/constants.ts +++ b/wowup-electron/src/constants.ts @@ -2,6 +2,4 @@ export const collapseToTrayKey = 'collapse_to_tray'; export const wowupReleaseChannelKey = 'wowup_release_channel'; export const defaultChannelKeySuffix = '_default_addon_channel'; export const defaultAutoUpdateKeySuffix = '_default_auto_update'; -export const telemetryEnabledKey = 'telemetry_enabled'; -export const telemetryPromptSentKey = 'telemetry_prompt_sent'; export const lastSelectedWowClientTypeKey = 'last_selected_client_type'; \ No newline at end of file From d63cb1fa8b5e22de2674b48b5d7e5a41281ecc31 Mon Sep 17 00:00:00 2001 From: john liddell Date: Fri, 16 Oct 2020 22:37:18 -0500 Subject: [PATCH 21/34] More analytics Fix all the cursor pointer issues i could find on mac. --- .../components/footer/footer.component.html | 27 ++++++++++--------- .../components/footer/footer.component.scss | 2 ++ .../titlebar/titlebar.component.html | 4 +-- .../src/app/pages/about/about.component.html | 4 +-- .../get-addons/get-addons.component.html | 12 ++++++--- .../pages/my-addons/my-addons.component.html | 19 ++++++++----- wowup-electron/src/styles.scss | 24 ++++++++++++++--- 7 files changed, 60 insertions(+), 32 deletions(-) diff --git a/wowup-electron/src/app/components/footer/footer.component.html b/wowup-electron/src/app/components/footer/footer.component.html index 1a4f8342..3cebbe4a 100644 --- a/wowup-electron/src/app/components/footer/footer.component.html +++ b/wowup-electron/src/app/components/footer/footer.component.html @@ -1,14 +1,15 @@
- - - - - - -

{{sessionService.statusText$ | async}}

-
-

{{sessionService.pageContextText$ | async}}

-

v{{wowUpService.applicationVersion}}

-
-
\ No newline at end of file + + + + + + +

{{sessionService.statusText$ | async}}

+
+

{{sessionService.pageContextText$ | async}}

+

v{{wowUpService.applicationVersion}}

+
+ diff --git a/wowup-electron/src/app/components/footer/footer.component.scss b/wowup-electron/src/app/components/footer/footer.component.scss index 59325c1c..b5fd3c15 100644 --- a/wowup-electron/src/app/components/footer/footer.component.scss +++ b/wowup-electron/src/app/components/footer/footer.component.scss @@ -37,6 +37,8 @@ footer { } .discord-link { + padding: 0 0.25em; + &:hover { background-color: $dark-3; } diff --git a/wowup-electron/src/app/components/titlebar/titlebar.component.html b/wowup-electron/src/app/components/titlebar/titlebar.component.html index 56f58fb7..58aea898 100644 --- a/wowup-electron/src/app/components/titlebar/titlebar.component.html +++ b/wowup-electron/src/app/components/titlebar/titlebar.component.html @@ -6,8 +6,8 @@
WowUp.io
-
- bug_report +
+ bug_report
bug_report diff --git a/wowup-electron/src/app/pages/about/about.component.html b/wowup-electron/src/app/pages/about/about.component.html index e413fb4a..07eea492 100644 --- a/wowup-electron/src/app/pages/about/about.component.html +++ b/wowup-electron/src/app/pages/about/about.component.html @@ -6,7 +6,7 @@

{{'PAGES.ABOUT.TITLE' | translate}}

v{{version}}
@@ -23,4 +23,4 @@
- \ No newline at end of file + diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index a0722be0..7c698421 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -23,10 +23,12 @@
- -
@@ -70,7 +72,9 @@ {{'PAGES.GET_ADDONS.TABLE.STATUS_COLUMN_HEADER' | translate}}
@@ -78,4 +82,4 @@
{{'PAGES.GET_ADDONS.TABLE.ADDON_COLUMN_HEADER' | translate}} @@ -47,7 +48,16 @@ - + + + {{'PAGES.GET_ADDONS.TABLE.DOWNLOAD_COUNT_COLUMN_HEADER' | translate}} + + {{element.downloadCount | downloadCount}} + {{'PAGES.GET_ADDONS.TABLE.PROVIDER_COLUMN_HEADER' | translate}} - + +
- \ No newline at end of file + diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index 63cc69a3..b5848f48 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -4,7 +4,7 @@
{{'PAGES.MY_ADDONS.CLIENT_TYPE_SELECT_LABEL' | translate}} - {{warcraftService.getClientDisplayName(clientType)}} @@ -25,17 +25,20 @@
@@ -62,11 +65,13 @@ {{'PAGES.MY_ADDONS.TABLE.STATUS_COLUMN_HEADER' | translate}}
-
@@ -219,4 +224,4 @@ {{'PAGES.MY_ADDONS.UPDATE_ALL_CONTEXT_MENU.UPDATE_ALL_CLIENTS_BUTTON' | translate}} - \ No newline at end of file + diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index 15c22de3..abc40ec0 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -24,8 +24,7 @@ img:not([draggable="true"]) { a[href^="http://"], a[href^="https://"], -a[href^="ftp://"] -{ +a[href^="ftp://"] { -webkit-user-drag: auto; user-drag: auto; /* Technically not supported in Electron yet */ @@ -59,9 +58,11 @@ img { .mr-1 { margin-right: .25em !important; } + .mr-2 { margin-right: .5em !important; } + .mr-3 { margin-right: 1em !important; } @@ -70,14 +71,29 @@ img { -webkit-app-region: no-drag; } -.pointer:hover { - cursor: pointer; +.pointer { + pointer-events: all !important; + + &:hover { + cursor: pointer !important; + } +} + +.mat-tab-label, +.mat-tab-label-content, +.mat-select-value, +.mat-form-field-infix, +.mat-button-wrapper span { + &:hover { + cursor: pointer !important; + } } .mat-slide-toggle-thumb, .mat-slide-toggle-bar, .mat-button-wrapper { -webkit-app-region: no-drag; + &:hover { cursor: pointer; } From d9eb69c3e1c4e63bacd59418fa408cc127bdb690 Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 22:57:56 -0500 Subject: [PATCH 22/34] Last of the tracking --- .../install-from-url-dialog.component.html | 3 +- .../pages/my-addons/my-addons.component.html | 32 +++++++++++------ .../pages/my-addons/my-addons.component.ts | 1 - .../src/app/services/addons/addon.service.ts | 18 +++++++--- .../services/analytics/analytics.service.ts | 34 ++++++++++++------- 5 files changed, 58 insertions(+), 30 deletions(-) diff --git a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html index 23110f5c..f85a63c8 100644 --- a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html +++ b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.html @@ -40,7 +40,8 @@ -
\ No newline at end of file diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index b5848f48..d4dd5111 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -157,36 +157,44 @@
+ (change)="onClickIgnoreAddon($event, listItem)" appUserActionTracker category="MyAddons" action="IgnoreAddon" + [label]="listItem.addon.name"> {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.IGNORE_ADDON_BUTTON' | translate}} + [checked]="listItem.addon.autoUpdateEnabled" (change)="onClickAutoUpdateAddon($event, listItem.addon)" + appUserActionTracker category="MyAddons" action="AutoUpdateAddon" [label]="listItem.addon.name"> {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.AUTO_UPDATE_ADDON_BUTTON' | translate}} - - - - + {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.STABLE_ADDON_CHANNEL' | translate}} - + {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.BETA_ADDON_CHANNEL' | translate}} - + {{'PAGES.MY_ADDONS.ADDON_CONTEXT_MENU.ALPHA_ADDON_CHANNEL' | translate}} @@ -217,11 +225,13 @@ - - - + \ No newline at end of file diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index df89531a..86e7db71 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -111,7 +111,6 @@ export class MyAddonsComponent implements OnInit, OnDestroy { ) { _sessionService.selectedHomeTab$.subscribe((tabIndex) => { this.isSelectedTab = tabIndex === this.tabIndex; - console.log("TAB CHANGE", tabIndex, this.tabIndex); if (this.isSelectedTab) { this.setPageContextText(); } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index b152cbee..edee475d 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -23,6 +23,7 @@ import { FileService } from "../files/file.service"; import { TocService } from "../toc/toc.service"; import { AddonUpdateEvent } from "app/models/wowup/addon-update-event"; import { AddonProviderFactory } from "./addon.provider.factory"; +import { AnalyticsService } from "../analytics/analytics.service"; @Injectable({ providedIn: "root", @@ -37,6 +38,7 @@ export class AddonService { constructor( private _addonStorage: AddonStorageService, + private _analyticsService: AnalyticsService, private _warcraftService: WarcraftService, private _wowUpService: WowUpService, private _downloadService: DownloadSevice, @@ -65,7 +67,12 @@ export class AddonService { ); var searchResults = await Promise.all(searchTasks); - // await _analyticsService.TrackUserAction("Addons", "Search", $"{clientType}|{query}"); + await this._analyticsService.trackUserAction( + "Addons", + "Search", + `${clientType}|${query}` + ); + const flatResults = searchResults.flat(1); return _.orderBy(flatResults, "downloadCount").reverse(); @@ -107,7 +114,6 @@ export class AddonService { for (let clientTypeStr in clientTypeGroups) { const clientType: WowClientType = parseInt(clientTypeStr, 10); - // console.log('clientType', clientType, clientTypeGroups[clientType]); const synced = await this.syncAddons( clientType, @@ -126,7 +132,7 @@ export class AddonService { await this.installAddon(addon.id); updateCt += 1; } catch (err) { - // _analyticsService.Track(ex, "Failed to install addon"); + console.error(err); } } } @@ -209,7 +215,11 @@ export class AddonService { this._addonStorage.set(addon.id, addon); - // await _analyticsService.TrackUserAction("Addons", "InstallById", $"{addon.ClientType}|{addon.Name}"); + await this._analyticsService.trackUserAction( + "Addons", + "InstallById", + `${addon.clientType}|${addon.name}` + ); } catch (err) { console.error(err); diff --git a/wowup-electron/src/app/services/analytics/analytics.service.ts b/wowup-electron/src/app/services/analytics/analytics.service.ts index 5bd14e45..29747f23 100644 --- a/wowup-electron/src/app/services/analytics/analytics.service.ts +++ b/wowup-electron/src/app/services/analytics/analytics.service.ts @@ -31,7 +31,9 @@ export class AnalyticsService { } public get telemetryEnabled() { - const preference = this._preferenceStorageService.findByKey(this.telemetryEnabledKey); + const preference = this._preferenceStorageService.findByKey( + this.telemetryEnabledKey + ); return preference === true.toString(); } @@ -48,7 +50,7 @@ export class AnalyticsService { this._appVersion = _electronService.remote.app.getVersion(); this._installId = this.loadInstallId(); this._telemetryEnabledSrc.next(this.telemetryEnabled); - console.log('installId', this._installId); + console.log("installId", this._installId); } public async trackStartup() { @@ -58,8 +60,12 @@ export class AnalyticsService { }); } - public async trackUserAction(category: string, action: string, label: string = null) { - await this.track(params => { + public async trackUserAction( + category: string, + action: string, + label: string = null + ) { + await this.track((params) => { params.set("t", "event"); params.set("ec", category); params.set("ea", action); @@ -76,12 +82,12 @@ export class AnalyticsService { try { let params = new URLSearchParams(); - params.set('v', '1'); - params.set('tid', AppConfig.googleAnalyticsId); - params.set('cid', this._installId); - params.set('ua', window.navigator.userAgent); - params.set('an', "WowUp Client"); - params.set('av', this._appVersion); + params.set("v", "1"); + params.set("tid", AppConfig.googleAnalyticsId); + params.set("cid", this._installId); + params.set("ua", window.navigator.userAgent); + params.set("an", "WowUp Client"); + params.set("av", this._appVersion); action?.call(this, params); @@ -92,18 +98,20 @@ export class AnalyticsService { fullUrl, {}, { - responseType: 'text' + responseType: "text", } ) .toPromise(); } catch (e) { // eat - console.error(e) + console.error(e); } } private loadInstallId() { - let installId = this._preferenceStorageService.findByKey(this.installIdPreferenceKey); + let installId = this._preferenceStorageService.findByKey( + this.installIdPreferenceKey + ); if (installId) { return installId; } From 6c81b8237618121b93f4836b6e49c24a4289be9c Mon Sep 17 00:00:00 2001 From: jliddev Date: Fri, 16 Oct 2020 23:24:38 -0500 Subject: [PATCH 23/34] Fix some author column crowding. Debug button does the logging we need now. --- .../addon-providers/curse-addon-provider.ts | 74 ++++---- .../get-addons/get-addons.component.html | 2 +- .../get-addons/get-addons.component.scss | 1 + .../pages/my-addons/my-addons.component.html | 2 +- .../app/pages/options/options.component.html | 2 +- .../app/pages/options/options.component.ts | 164 +++++++++++------- .../src/app/services/addons/addon.service.ts | 18 ++ 7 files changed, 163 insertions(+), 100 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 13b31b46..d53f477e 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -63,7 +63,7 @@ export class CurseAddonProvider implements AddonProvider { }); } - async scan( + public async scan( clientType: WowClientType, addonChannelType: AddonChannelType, addonFolders: AddonFolder[] @@ -112,12 +112,45 @@ export class CurseAddonProvider implements AddonProvider { } catch (err) { console.error(scanResult); console.error(err); - // TODO - // _analyticsService.Track(ex, $"Failed to create addon for result {scanResult.FolderScanner.Fingerprint}"); } } } + public getScanResults = async ( + addonFolders: AddonFolder[] + ): Promise => { + const t1 = Date.now(); + + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: CurseGetScanResultsResponse) => { + if (arg.error) { + return reject(arg.error); + } + + const appScanResults: AppCurseScanResult[] = arg.scanResults.map( + (scanResult) => { + const addonFolder = addonFolders.find( + (af) => af.path === scanResult.directory + ); + + return Object.assign({}, scanResult, { addonFolder }); + } + ); + + console.log("scan delta", Date.now() - t1); + resolve(appScanResults); + }; + + const request: CurseGetScanResultsRequest = { + filePaths: addonFolders.map((addonFolder) => addonFolder.path), + responseKey: uuidv4(), + }; + + this._electronService.ipcRenderer.once(request.responseKey, eventHandler); + this._electronService.ipcRenderer.send(CURSE_GET_SCAN_RESULTS, request); + }); + }; + private async mapAddonFolders( scanResults: AppCurseScanResult[], clientType: WowClientType @@ -227,41 +260,6 @@ export class CurseAddonProvider implements AddonProvider { return action.call(this); } - private getScanResults = async ( - addonFolders: AddonFolder[] - ): Promise => { - const t1 = Date.now(); - - return new Promise((resolve, reject) => { - const eventHandler = (_evt: any, arg: CurseGetScanResultsResponse) => { - if (arg.error) { - return reject(arg.error); - } - - const appScanResults: AppCurseScanResult[] = arg.scanResults.map( - (scanResult) => { - const addonFolder = addonFolders.find( - (af) => af.path === scanResult.directory - ); - - return Object.assign({}, scanResult, { addonFolder }); - } - ); - - console.log("scan delta", Date.now() - t1); - resolve(appScanResults); - }; - - const request: CurseGetScanResultsRequest = { - filePaths: addonFolders.map((addonFolder) => addonFolder.path), - responseKey: uuidv4(), - }; - - this._electronService.ipcRenderer.once(request.responseKey, eventHandler); - this._electronService.ipcRenderer.send(CURSE_GET_SCAN_RESULTS, request); - }); - }; - async getAll( clientType: WowClientType, addonIds: string[] diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index 7c698421..f05ea7b3 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -62,7 +62,7 @@ {{'PAGES.GET_ADDONS.TABLE.AUTHOR_COLUMN_HEADER' | translate}} - + {{element.author}} diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss index 1edef68e..d541a785 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss @@ -73,6 +73,7 @@ .author-column { width: 100px; + padding-right: 1em; } .provider-column { min-width: 60px; diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index d4dd5111..9dcbc26a 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -127,7 +127,7 @@ {{'PAGES.MY_ADDONS.TABLE.AUTHOR_COLUMN_HEADER' | translate}} - + {{element.addon.author}} diff --git a/wowup-electron/src/app/pages/options/options.component.html b/wowup-electron/src/app/pages/options/options.component.html index 075b81f4..71b0de33 100644 --- a/wowup-electron/src/app/pages/options/options.component.html +++ b/wowup-electron/src/app/pages/options/options.component.html @@ -72,7 +72,7 @@

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_LABEL' | translate}}

{{'PAGES.OPTIONS.DEBUG.DEBUG_DATA_DESCRIPTION' | translate}} - diff --git a/wowup-electron/src/app/pages/options/options.component.ts b/wowup-electron/src/app/pages/options/options.component.ts index 3ba10916..689c87ae 100644 --- a/wowup-electron/src/app/pages/options/options.component.ts +++ b/wowup-electron/src/app/pages/options/options.component.ts @@ -1,41 +1,58 @@ -import { Component, OnInit, NgZone, OnChanges, SimpleChanges, Input } from '@angular/core'; -import { MatSlideToggleChange } from '@angular/material/slide-toggle'; -import { WowClientType } from 'app/models/warcraft/wow-client-type'; -import { ElectronService } from 'app/services'; -import { WarcraftService } from 'app/services/warcraft/warcraft.service'; -import { WowUpService } from 'app/services/wowup/wowup.service'; -import { filter, map } from 'rxjs/operators'; -import * as _ from 'lodash'; -import * as path from 'path'; -import { MatDialog } from '@angular/material/dialog'; -import { AlertDialogComponent } from 'app/components/alert-dialog/alert-dialog.component'; -import { getEnumList, getEnumName } from 'app/utils/enum.utils'; -import { WowUpReleaseChannelType } from 'app/models/wowup/wowup-release-channel-type'; -import { MatSelectChange } from '@angular/material/select'; -import { AnalyticsService } from 'app/services/analytics/analytics.service'; +import { + Component, + OnInit, + NgZone, + OnChanges, + SimpleChanges, + Input, +} from "@angular/core"; +import { MatSlideToggleChange } from "@angular/material/slide-toggle"; +import { WowClientType } from "app/models/warcraft/wow-client-type"; +import { ElectronService } from "app/services"; +import { WarcraftService } from "app/services/warcraft/warcraft.service"; +import { WowUpService } from "app/services/wowup/wowup.service"; +import { filter, map } from "rxjs/operators"; +import * as _ from "lodash"; +import * as path from "path"; +import { MatDialog } from "@angular/material/dialog"; +import { AlertDialogComponent } from "app/components/alert-dialog/alert-dialog.component"; +import { getEnumList, getEnumName } from "app/utils/enum.utils"; +import { WowUpReleaseChannelType } from "app/models/wowup/wowup-release-channel-type"; +import { MatSelectChange } from "@angular/material/select"; +import { AnalyticsService } from "app/services/analytics/analytics.service"; +import { AddonService } from "app/services/addons/addon.service"; @Component({ - selector: 'app-options', - templateUrl: './options.component.html', - styleUrls: ['./options.component.scss'] + selector: "app-options", + templateUrl: "./options.component.html", + styleUrls: ["./options.component.scss"], }) export class OptionsComponent implements OnInit, OnChanges { + @Input("tabIndex") tabIndex: number; - @Input('tabIndex') tabIndex: number; - - public retailLocation = ''; - public classicLocation = ''; - public retailPtrLocation = ''; - public classicPtrLocation = ''; - public betaLocation = ''; + public retailLocation = ""; + public classicLocation = ""; + public retailPtrLocation = ""; + public classicPtrLocation = ""; + public betaLocation = ""; public collapseToTray = false; public telemetryEnabled = false; - public wowClientTypes: WowClientType[] = getEnumList(WowClientType).filter(clientType => clientType !== WowClientType.None) as WowClientType[]; + public wowClientTypes: WowClientType[] = getEnumList(WowClientType).filter( + (clientType) => clientType !== WowClientType.None + ) as WowClientType[]; public wowUpReleaseChannel: WowUpReleaseChannelType; - public wowUpReleaseChannels: { type: WowUpReleaseChannelType, name: string }[] = getEnumList(WowUpReleaseChannelType) - .map((type: WowUpReleaseChannelType) => ({ type, name: getEnumName(WowUpReleaseChannelType, type) })); + public wowUpReleaseChannels: { + type: WowUpReleaseChannelType; + name: string; + }[] = getEnumList( + WowUpReleaseChannelType + ).map((type: WowUpReleaseChannelType) => ({ + type, + name: getEnumName(WowUpReleaseChannelType, type), + })); constructor( + private _addonService: AddonService, private _analyticsService: AnalyticsService, private warcraft: WarcraftService, private _electronService: ElectronService, @@ -45,14 +62,13 @@ export class OptionsComponent implements OnInit, OnChanges { private zone: NgZone, public electronService: ElectronService ) { - _analyticsService.telemetryEnabled$ - .subscribe(enabled => { - this.telemetryEnabled = enabled; - }) + _analyticsService.telemetryEnabled$.subscribe((enabled) => { + this.telemetryEnabled = enabled; + }); } ngOnChanges(changes: SimpleChanges): void { - console.log(changes) + console.log(changes); } ngOnInit(): void { @@ -63,25 +79,29 @@ export class OptionsComponent implements OnInit, OnChanges { onShowLogs = () => { this._wowUpService.showLogsFolder(); - } + }; onReScan = () => { this.warcraft.scanProducts(); this.loadData(); - } + }; onTelemetryChange = (evt: MatSlideToggleChange) => { this._analyticsService.telemetryEnabled = evt.checked; - } + }; onCollapseChange = (evt: MatSlideToggleChange) => { this._wowUpService.collapseToTray = evt.checked; - } + }; onWowUpChannelChange(evt: MatSelectChange) { this._wowUpService.wowUpReleaseChannel = evt.value; } + async onLogDebugData() { + await this._addonService.logDebugData(); + } + async onSelectRetailClientPath() { const selectedPath = await this.selectWowClientPath(WowClientType.Retail); if (selectedPath) { @@ -90,7 +110,9 @@ export class OptionsComponent implements OnInit, OnChanges { } async onSelectRetailPtrClientPath() { - const selectedPath = await this.selectWowClientPath(WowClientType.RetailPtr); + const selectedPath = await this.selectWowClientPath( + WowClientType.RetailPtr + ); if (selectedPath) { this.retailPtrLocation = selectedPath; } @@ -104,7 +126,9 @@ export class OptionsComponent implements OnInit, OnChanges { } async onSelectClassicPtrClientPath() { - const selectedPath = await this.selectWowClientPath(WowClientType.ClassicPtr); + const selectedPath = await this.selectWowClientPath( + WowClientType.ClassicPtr + ); if (selectedPath) { this.classicPtrLocation = selectedPath; } @@ -117,52 +141,74 @@ export class OptionsComponent implements OnInit, OnChanges { } } - private async selectWowClientPath(clientType: WowClientType): Promise { - const dialogResult = await this._electronService.remote.dialog.showOpenDialog({ - properties: ['openDirectory'] - }); + private async selectWowClientPath( + clientType: WowClientType + ): Promise { + const dialogResult = await this._electronService.remote.dialog.showOpenDialog( + { + properties: ["openDirectory"], + } + ); if (dialogResult.canceled) { - return ''; + return ""; } const selectedPath = _.first(dialogResult.filePaths); if (!selectedPath) { - console.warn('No path selected') - return ''; + console.warn("No path selected"); + return ""; } - console.log('dialogResult', selectedPath); + console.log("dialogResult", selectedPath); if (this._warcraftService.setWowFolderPath(clientType, selectedPath)) { return selectedPath; } - const clientFolderName = this._warcraftService.getClientFolderName(clientType); - const clientExecutableName = this._warcraftService.getExecutableName(clientType); - const clientExecutablePath = path.join(selectedPath, clientFolderName, clientExecutableName); + const clientFolderName = this._warcraftService.getClientFolderName( + clientType + ); + const clientExecutableName = this._warcraftService.getExecutableName( + clientType + ); + const clientExecutablePath = path.join( + selectedPath, + clientFolderName, + clientExecutableName + ); const dialogRef = this._dialog.open(AlertDialogComponent, { data: { title: `Alert`, - message: `Unable to set "${selectedPath}" as your ${getEnumName(WowClientType, clientType)} folder.\nPath not found: "${clientExecutablePath}".` - } + message: `Unable to set "${selectedPath}" as your ${getEnumName( + WowClientType, + clientType + )} folder.\nPath not found: "${clientExecutablePath}".`, + }, }); await dialogRef.afterClosed().toPromise(); - return ''; + return ""; } private loadData() { this.zone.run(() => { this.telemetryEnabled = this._analyticsService.telemetryEnabled; this.collapseToTray = this._wowUpService.collapseToTray; - this.retailLocation = this.warcraft.getClientLocation(WowClientType.Retail); - this.classicLocation = this.warcraft.getClientLocation(WowClientType.Classic); - this.retailPtrLocation = this.warcraft.getClientLocation(WowClientType.RetailPtr); - this.classicPtrLocation = this.warcraft.getClientLocation(WowClientType.ClassicPtr); + this.retailLocation = this.warcraft.getClientLocation( + WowClientType.Retail + ); + this.classicLocation = this.warcraft.getClientLocation( + WowClientType.Classic + ); + this.retailPtrLocation = this.warcraft.getClientLocation( + WowClientType.RetailPtr + ); + this.classicPtrLocation = this.warcraft.getClientLocation( + WowClientType.ClassicPtr + ); this.betaLocation = this.warcraft.getClientLocation(WowClientType.Beta); - }) + }); } - } diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index edee475d..d2bea129 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -242,6 +242,24 @@ export class AddonService { }); } + public async logDebugData() { + const curseProvider = this._addonProviders.find( + (p) => p.name === "Curse" + ) as CurseAddonProvider; + + const clientTypes = await this._warcraftService.getWowClientTypes(); + for (let clientType of clientTypes) { + const addonFolders = await this._warcraftService.listAddons(clientType); + const scanResults = await curseProvider.getScanResults(addonFolders); + const map = {}; + + scanResults.forEach((sr) => (map[sr.folderName] = sr.fingerprint)); + + console.log(`clientType ${this._warcraftService.getClientDisplayName(clientType)} addon fingerprints`); + console.log(map); + } + } + private async getLatestGameVersion( baseDir: string, installedFolders: string[] From 7b092b819d8649249cbf61c1d5a449deadebe543 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 00:06:07 -0500 Subject: [PATCH 24/34] Add a simple message passing method. Add the auto update notification popup. --- wowup-electron/ipc-events.ts | 9 +++ wowup-electron/src/app/app.component.ts | 19 +++++- .../app/pages/options/options.component.ts | 13 ++-- .../app/services/electron/electron.service.ts | 64 ++++++++++++++----- .../src/app/services/files/file.service.ts | 8 +++ wowup-electron/src/common/constants.ts | 1 + 6 files changed, 89 insertions(+), 25 deletions(-) diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index a261dfdc..6aea993c 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -1,6 +1,7 @@ import { ipcMain, shell } from "electron"; import * as fs from "fs"; import * as async from "async"; +import * as path from "path"; import * as admZip from "adm-zip"; import { ncp } from "ncp"; import * as rimraf from "rimraf"; @@ -20,6 +21,7 @@ import { DELETE_DIRECTORY_CHANNEL, RENAME_DIRECTORY_CHANNEL, READ_FILE_CHANNEL, + GET_ASSET_FILE_PATH, } from "./src/common/constants"; import { CurseGetScanResultsRequest } from "./src/common/curse/curse-get-scan-results-request"; import { CurseGetScanResultsResponse } from "./src/common/curse/curse-get-scan-results-response"; @@ -53,6 +55,13 @@ ipcMain.on(SHOW_DIRECTORY, async (evt, arg: ShowDirectoryRequest) => { evt.reply(arg.responseKey, true); }); +ipcMain.on(GET_ASSET_FILE_PATH, async (evt, arg: ValueRequest) => { + const response: ValueResponse = { + value: path.join(__dirname, "assets", arg.value), + }; + evt.reply(arg.responseKey, response); +}); + ipcMain.on(CURSE_HASH_FILE_CHANNEL, async (evt, arg: CurseHashFileRequest) => { // console.log(CURSE_HASH_FILE_CHANNEL, arg); diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index 89ca3649..573d8e01 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -6,6 +6,7 @@ import { TelemetryDialogComponent } from "./components/telemetry-dialog/telemetr import { ElectronService } from "./services"; import { AddonService } from "./services/addons/addon.service"; import { AnalyticsService } from "./services/analytics/analytics.service"; +import { FileService } from "./services/files/file.service"; import { WarcraftService } from "./services/warcraft/warcraft.service"; import { WowUpService } from "./services/wowup/wowup.service"; @@ -21,7 +22,8 @@ export class AppComponent implements AfterViewInit { constructor( private _analyticsService: AnalyticsService, - private electronService: ElectronService, + private _electronService: ElectronService, + private _fileService: FileService, private translate: TranslateService, private warcraft: WarcraftService, private _wowUpService: WowUpService, @@ -30,7 +32,7 @@ export class AppComponent implements AfterViewInit { ) { this.translate.setDefaultLang("en"); - this.translate.use(this.electronService.locale); + this.translate.use(this._electronService.locale); } ngAfterViewInit(): void { @@ -63,5 +65,18 @@ export class AppComponent implements AfterViewInit { private onAutoUpdateInterval = async () => { console.log("Auto update"); const updateCount = await this._addonService.processAutoUpdates(); + + if (updateCount === 0) { + return; + } + + const iconPath = await this._fileService.getAssetFilePath( + "wowup_logo_512np.png" + ); + + this._electronService.showNotification("Auto Updates", { + body: `Automatically updated ${updateCount} addons.`, + icon: iconPath, + }); }; } diff --git a/wowup-electron/src/app/pages/options/options.component.ts b/wowup-electron/src/app/pages/options/options.component.ts index 689c87ae..fb0396fe 100644 --- a/wowup-electron/src/app/pages/options/options.component.ts +++ b/wowup-electron/src/app/pages/options/options.component.ts @@ -21,6 +21,7 @@ import { WowUpReleaseChannelType } from "app/models/wowup/wowup-release-channel- import { MatSelectChange } from "@angular/material/select"; import { AnalyticsService } from "app/services/analytics/analytics.service"; import { AddonService } from "app/services/addons/addon.service"; +import { GET_ASSET_FILE_PATH } from "common/constants"; @Component({ selector: "app-options", @@ -44,12 +45,12 @@ export class OptionsComponent implements OnInit, OnChanges { public wowUpReleaseChannels: { type: WowUpReleaseChannelType; name: string; - }[] = getEnumList( - WowUpReleaseChannelType - ).map((type: WowUpReleaseChannelType) => ({ - type, - name: getEnumName(WowUpReleaseChannelType, type), - })); + }[] = getEnumList(WowUpReleaseChannelType).map( + (type: WowUpReleaseChannelType) => ({ + type, + name: getEnumName(WowUpReleaseChannelType, type), + }) + ); constructor( private _addonService: AddonService, diff --git a/wowup-electron/src/app/services/electron/electron.service.ts b/wowup-electron/src/app/services/electron/electron.service.ts index 74c8e828..ceb2b5b0 100644 --- a/wowup-electron/src/app/services/electron/electron.service.ts +++ b/wowup-electron/src/app/services/electron/electron.service.ts @@ -1,14 +1,17 @@ -import { Injectable } from '@angular/core'; +import { Injectable } from "@angular/core"; +import { v4 as uuidv4 } from "uuid"; // If you import a module but never use any of the imported values other than as TypeScript types, // the resulting javascript file will look as if you never imported the module at all. -import { ipcRenderer, webFrame, remote, shell } from 'electron'; -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import { BehaviorSubject } from 'rxjs'; +import { ipcRenderer, webFrame, remote, shell } from "electron"; +import * as childProcess from "child_process"; +import * as fs from "fs"; +import { BehaviorSubject } from "rxjs"; +import { ValueResponse } from "common/models/value-response"; +import { ValueRequest } from "common/models/value-request"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class ElectronService { private readonly _windowMaximizedSrc = new BehaviorSubject(false); @@ -31,7 +34,7 @@ export class ElectronService { } get locale(): string { - return this.remote.app.getLocale().split('-')[0]; + return this.remote.app.getLocale().split("-")[0]; } constructor() { @@ -39,27 +42,27 @@ export class ElectronService { if (!this.isElectron) { return; } - this.ipcRenderer = window.require('electron').ipcRenderer; - this.webFrame = window.require('electron').webFrame; - this.remote = window.require('electron').remote; - this.shell = window.require('electron').shell; + this.ipcRenderer = window.require("electron").ipcRenderer; + this.webFrame = window.require("electron").webFrame; + this.remote = window.require("electron").remote; + this.shell = window.require("electron").shell; - this.childProcess = window.require('child_process'); - this.fs = window.require('fs'); + this.childProcess = window.require("child_process"); + this.fs = window.require("fs"); - this.remote.getCurrentWindow().on('minimize', () => { + this.remote.getCurrentWindow().on("minimize", () => { this._windowMinimizedSrc.next(true); }); - this.remote.getCurrentWindow().on('restore', () => { + this.remote.getCurrentWindow().on("restore", () => { this._windowMinimizedSrc.next(false); }); - this.remote.getCurrentWindow().on('maximize', () => { + this.remote.getCurrentWindow().on("maximize", () => { this._windowMaximizedSrc.next(true); }); - this.remote.getCurrentWindow().on('unmaximize', () => { + this.remote.getCurrentWindow().on("unmaximize", () => { this._windowMaximizedSrc.next(false); }); @@ -86,4 +89,31 @@ export class ElectronService { this.remote.getCurrentWindow().close(); this.remote.app.quit(); } + + public showNotification(title: string, options?: NotificationOptions) { + const myNotification = new Notification(title, options); + } + + public sendIpcValueMessage( + channel: string, + value: TIN + ): Promise { + return new Promise((resolve, reject) => { + const eventHandler = (_evt: any, arg: ValueResponse) => { + if (arg.error) { + return reject(arg.error); + } + + resolve(arg.value); + }; + + const request: ValueRequest = { + value, + responseKey: uuidv4(), + }; + + this.ipcRenderer.once(request.responseKey, eventHandler); + this.ipcRenderer.send(channel, request); + }); + } } diff --git a/wowup-electron/src/app/services/files/file.service.ts b/wowup-electron/src/app/services/files/file.service.ts index 49ef0a68..909a696d 100644 --- a/wowup-electron/src/app/services/files/file.service.ts +++ b/wowup-electron/src/app/services/files/file.service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@angular/core"; import { COPY_DIRECTORY_CHANNEL, DELETE_DIRECTORY_CHANNEL, + GET_ASSET_FILE_PATH, LIST_DIRECTORIES_CHANNEL, LIST_FILES_CHANNEL, PATH_EXISTS_CHANNEL, @@ -29,6 +30,13 @@ import { ValueResponse } from "common/models/value-response"; export class FileService { constructor(private _electronService: ElectronService) {} + public async getAssetFilePath(fileName: string) { + return await this._electronService.sendIpcValueMessage( + GET_ASSET_FILE_PATH, + fileName + ); + } + public showDirectory(sourceDir: string) { return new Promise((resolve, reject) => { const eventHandler = (_evt: any, arg: boolean) => { diff --git a/wowup-electron/src/common/constants.ts b/wowup-electron/src/common/constants.ts index fc0daaa2..b98d2094 100644 --- a/wowup-electron/src/common/constants.ts +++ b/wowup-electron/src/common/constants.ts @@ -13,3 +13,4 @@ export const CURSE_HASH_FILE_CHANNEL = "curse-hash-file"; export const SHOW_DIRECTORY = "show-directory"; export const CURSE_GET_SCAN_RESULTS = "curse-get-scan-results"; export const WOWUP_GET_SCAN_RESULTS = "wowup-get-scan-results"; +export const GET_ASSET_FILE_PATH = "get-asset-file-path"; From 36dfd80e97347159c629b7b09ad3faa846b66b30 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 00:18:08 -0500 Subject: [PATCH 25/34] Use em --- .../src/app/pages/get-addons/get-addons.component.scss | 2 +- wowup-electron/src/app/pages/my-addons/my-addons.component.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss index 81e226dd..b81027fd 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.scss +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.scss @@ -71,7 +71,7 @@ } .cell-padding { - padding-right: 15px; + padding-right: 1em; } } diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss index a86d9846..e9371732 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.scss +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.scss @@ -116,7 +116,7 @@ } .cell-padding { - padding-right: 15px; + padding-right: 1em; } } From bec3bbca4b6b399eb43a3d745b4586de0157ef9e Mon Sep 17 00:00:00 2001 From: Dean Campbell Date: Fri, 16 Oct 2020 23:23:49 -0700 Subject: [PATCH 26/34] Refresh get addons on channel type change --- .../addon-providers/curse-addon-provider.ts | 1 - .../pages/get-addons/get-addons.component.ts | 21 ++++++++++++++++--- .../src/app/services/wowup/wowup.service.ts | 1 + 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index bff7c9b8..87e9686f 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -24,7 +24,6 @@ import { CurseFile } from "common/curse/curse-file"; import { CurseReleaseType } from "common/curse/curse-release-type"; import { CurseGetFeaturedResponse } from "app/models/curse/curse-get-featured-response"; import * as CircuitBreaker from "opossum"; -import { WowUpService } from "app/services/wowup/wowup.service"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; const HUB_API_URL = "https://hub.dev.wowup.io"; diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index c6556834..08959037 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -11,11 +11,14 @@ 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, Subject, Subscription } from "rxjs"; -import { map } from "rxjs/operators"; +import { filter, map } from "rxjs/operators"; import { MatTableDataSource } from "@angular/material/table"; import { MatSort } from "@angular/material/sort"; import * as _ from "lodash"; import { WowUpService } from "app/services/wowup/wowup.service"; +import { defaultChannelKeySuffix } from "../../../constants"; +import { getEnumName } from "app/utils/enum.utils"; +import { AddonChannelType } from "app/models/wowup/addon-channel-type"; @Component({ selector: "app-get-addons", @@ -33,6 +36,8 @@ export class GetAddonsComponent implements OnInit, OnDestroy { private readonly _destroyed$ = new Subject(); private subscriptions: Subscription[] = []; private isSelectedTab: boolean = false; + private channelType: AddonChannelType = AddonChannelType.Stable; + private channelTypeKey: string = ''; public dataSource = new MatTableDataSource([]); @@ -73,6 +78,8 @@ export class GetAddonsComponent implements OnInit, OnDestroy { .pipe( map((clientType) => { this.selectedClient = clientType; + this.channelType = this._wowUpService.getDefaultAddonChannel(this.selectedClient); + this.channelTypeKey = `${getEnumName(WowClientType, this.selectedClient)}${defaultChannelKeySuffix}`.toLowerCase(); this.loadPopularAddons(this.selectedClient); }) ) @@ -94,10 +101,18 @@ export class GetAddonsComponent implements OnInit, OnDestroy { } ); + const channelTypeSubscription = this._wowUpService.preferenceChange$ + .pipe(filter(change => change.key === this.channelTypeKey)) + .subscribe(change => { + this.channelType = parseInt(change.value, 10) as AddonChannelType; + this.onSearch(); + }); + this.subscriptions = [ selectedClientSubscription, addonRemovedSubscription, displayAddonSubscription, + channelTypeSubscription ]; } @@ -137,7 +152,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { let searchResults = await this._addonService.search( this.query, this.selectedClient, - this._wowUpService.getDefaultAddonChannel(this.selectedClient) + this.channelType ); searchResults = this.filterInstalledAddons(searchResults); @@ -162,7 +177,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { this.isBusy = true; - this._addonService.getFeaturedAddons(clientType, this._wowUpService.getDefaultAddonChannel(clientType)).subscribe({ + this._addonService.getFeaturedAddons(clientType, this.channelType).subscribe({ next: (addons) => { addons = this.filterInstalledAddons(addons); this.formatAddons(addons); diff --git a/wowup-electron/src/app/services/wowup/wowup.service.ts b/wowup-electron/src/app/services/wowup/wowup.service.ts index 02092ba9..5072b653 100644 --- a/wowup-electron/src/app/services/wowup/wowup.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup.service.ts @@ -116,6 +116,7 @@ export class WowUpService { public setDefaultAddonChannel(clientType: WowClientType, channelType: AddonChannelType) { const key = this.getClientDefaultAddonChannelKey(clientType); this._preferenceStorageService.set(key, channelType); + this._preferenceChangeSrc.next({ key, value: channelType.toString() }) } public getDefaultAutoUpdate(clientType: WowClientType): boolean { From d9e4c94619478977c1b8955f064d7b0d7497c964 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 15:37:57 -0500 Subject: [PATCH 27/34] pull in most of PR --- .../src/app/addon-providers/addon-provider.ts | 44 ++- .../addon-providers/curse-addon-provider.ts | 47 +-- .../addon-providers/github-addon-provider.ts | 373 +++++++++--------- .../addon-providers/tukui-addon-provider.ts | 39 +- .../wow-interface-addon-provider.ts | 33 +- .../addon-providers/wowup-addon-provider.ts | 11 +- .../business-objects/get-addon-list-item.ts | 29 ++ .../business-objects/my-addon-list-item.ts | 89 +++++ .../business-objects/my-addons-list-item.ts | 85 ---- .../addon-detail/addon-detail.component.html | 38 +- .../addon-detail/addon-detail.component.scss | 17 +- .../addon-detail/addon-detail.component.ts | 28 +- .../addon-install-button.component.html | 6 +- .../addon-install-button.component.ts | 196 ++++++--- .../get-addon-status-column.component.html | 2 + .../get-addon-status-column.component.scss | 0 .../get-addon-status-column.component.spec.ts | 25 ++ .../get-addon-status-column.component.ts | 110 ++++++ .../install-from-url-dialog.component.ts | 4 +- .../my-addon-status-column.component.html | 2 + .../my-addon-status-column.component.scss | 0 .../my-addon-status-column.component.spec.ts | 25 ++ .../my-addon-status-column.component.ts | 130 ++++++ .../my-addons-addon-cell.component.html | 3 +- .../my-addons-addon-cell.component.ts | 23 +- .../potential-addon-table-column.component.ts | 11 +- wowup-electron/src/app/entities/addon.ts | 3 + .../app/models/wowup/addon-install-state.ts | 13 +- .../app/models/wowup/addon-search-result.ts | 12 +- .../src/app/models/wowup/potential-addon.ts | 12 - .../get-addons/get-addons.component.html | 4 +- .../pages/get-addons/get-addons.component.ts | 30 +- .../src/app/pages/home/home.module.ts | 4 + .../pages/my-addons/my-addons.component.html | 13 +- .../pages/my-addons/my-addons.component.ts | 45 ++- .../src/app/services/addons/addon.service.ts | 7 +- wowup-electron/src/assets/i18n/de.json | 16 + wowup-electron/src/assets/i18n/en.json | 18 + wowup-electron/src/assets/i18n/es.json | 16 + wowup-electron/src/assets/i18n/fr.json | 16 + wowup-electron/src/assets/i18n/it.json | 16 + wowup-electron/src/assets/i18n/pt.json | 16 + wowup-electron/src/assets/i18n/ru.json | 16 + wowup-electron/src/assets/i18n/zh.json | 16 + wowup-electron/src/styles.scss | 2 +- 45 files changed, 1120 insertions(+), 525 deletions(-) create mode 100644 wowup-electron/src/app/business-objects/get-addon-list-item.ts create mode 100644 wowup-electron/src/app/business-objects/my-addon-list-item.ts delete mode 100644 wowup-electron/src/app/business-objects/my-addons-list-item.ts create mode 100644 wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.html create mode 100644 wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.scss create mode 100644 wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.spec.ts create mode 100644 wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.ts create mode 100644 wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html create mode 100644 wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.scss create mode 100644 wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.spec.ts create mode 100644 wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts delete mode 100644 wowup-electron/src/app/models/wowup/potential-addon.ts diff --git a/wowup-electron/src/app/addon-providers/addon-provider.ts b/wowup-electron/src/app/addon-providers/addon-provider.ts index da3d649f..80d3791d 100644 --- a/wowup-electron/src/app/addon-providers/addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/addon-provider.ts @@ -1,32 +1,56 @@ import { WowClientType } from "../models/warcraft/wow-client-type"; import { Addon } from "../entities/addon"; -import { PotentialAddon } from "../models/wowup/potential-addon"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { Observable } from "rxjs"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { AddonChannelType } from "app/models/wowup/addon-channel-type"; export interface AddonProvider { - name: AddonProviderType; - getAll(clientType: WowClientType, addonIds: string[]): Promise; + getAll( + clientType: WowClientType, + addonIds: string[] + ): Promise; - getFeaturedAddons(clientType: WowClientType): Observable; + getFeaturedAddons(clientType: WowClientType): Promise; - searchByQuery(query: string, clientType: WowClientType): Promise; + searchByQuery( + query: string, + clientType: WowClientType + ): Promise; - searchByUrl(addonUri: URL, clientType: WowClientType): Promise; + searchByUrl( + addonUri: URL, + clientType: WowClientType + ): Promise; - searchByName(addonName: string, folderName: string, clientType: WowClientType, nameOverride?: string): Promise; + searchByName( + addonName: string, + folderName: string, + clientType: WowClientType, + nameOverride?: string + ): Promise; - getById(addonId: string, clientType: WowClientType): Observable; + getById( + addonId: string, + clientType: WowClientType + ): Observable; isValidAddonUri(addonUri: URL): boolean; onPostInstall(addon: Addon): void; - scan(clientType: WowClientType, addonChannelType: AddonChannelType, addonFolders: AddonFolder[]): Promise; + scan( + clientType: WowClientType, + addonChannelType: AddonChannelType, + addonFolders: AddonFolder[] + ): Promise; } -export type AddonProviderType = 'Curse' | 'GitHub' | 'TukUI' | 'WowInterface' | 'WowUp'; +export type AddonProviderType = + | "Curse" + | "GitHub" + | "TukUI" + | "WowInterface" + | "WowUp"; diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index d53f477e..39b9aedd 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -8,7 +8,6 @@ import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { from, Observable, of } from "rxjs"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; import { AddonChannelType } from "../models/wowup/addon-channel-type"; -import { PotentialAddon } from "../models/wowup/potential-addon"; import { CachingService } from "app/services/caching/caching-service"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { ElectronService } from "app/services"; @@ -288,15 +287,12 @@ export class CurseAddonProvider implements AddonProvider { return addonResults; } - getFeaturedAddons(clientType: WowClientType): Observable { - return from(this.getFeaturedAddonList()).pipe( - map((addons) => { - return this.filterFeaturedAddons(addons, clientType); - }), - map((filteredAddons) => { - return filteredAddons.map((addon) => this.getPotentialAddon(addon)); - }) - ); + public async getFeaturedAddons( + clientType: WowClientType + ): Promise { + const addons = await this.getFeaturedAddonList(); + const filteredAddons = this.filterFeaturedAddons(addons, clientType); + return filteredAddons.map((addon) => this.getAddonSearchResult(addon)); } private filterFeaturedAddons( @@ -321,8 +317,8 @@ export class CurseAddonProvider implements AddonProvider { async searchByQuery( query: string, clientType: WowClientType - ): Promise { - var searchResults: PotentialAddon[] = []; + ): Promise { + var searchResults: AddonSearchResult[] = []; var response = await this.getSearchResults(query); for (let result of response) { @@ -331,7 +327,7 @@ export class CurseAddonProvider implements AddonProvider { continue; } - searchResults.push(this.getPotentialAddon(result)); + searchResults.push(this.getAddonSearchResult(result)); } return searchResults; @@ -340,7 +336,7 @@ export class CurseAddonProvider implements AddonProvider { searchByUrl( addonUri: URL, clientType: WowClientType - ): Promise { + ): Promise { throw new Error("Method not implemented."); } @@ -405,23 +401,9 @@ export class CurseAddonProvider implements AddonProvider { throw new Error("Method not implemented."); } - private getPotentialAddon(result: CurseSearchResult): PotentialAddon { - return { - author: this.getAuthor(result), - downloadCount: result.downloadCount, - externalId: result.id.toString(), - externalUrl: result.websiteUrl, - name: result.name, - providerName: this.name, - thumbnailUrl: this.getThumbnailUrl(result), - summary: result.summary, - screenshotUrls: this.getScreenshotUrls(result), - }; - } - private getAddonSearchResult( result: CurseSearchResult, - latestFiles: CurseFile[] + latestFiles: CurseFile[] = [] ): AddonSearchResult { try { const thumbnailUrl = this.getThumbnailUrl(result); @@ -450,6 +432,7 @@ export class CurseAddonProvider implements AddonProvider { externalUrl: result.websiteUrl, providerName: this.name, files: searchResultFiles, + downloadCount: result.downloadCount, }; return searchResult; @@ -570,12 +553,15 @@ export class CurseAddonProvider implements AddonProvider { scanResult: AppCurseScanResult ): Addon { const currentVersion = scanResult.exactMatch.file; + const authors = scanResult.searchResult.authors .map((author) => author.name) .join(", "); + const folderList = scanResult.exactMatch.file.modules .map((module) => module.foldername) .join(","); + const latestFiles = this.getLatestFiles( scanResult.searchResult, clientType @@ -616,6 +602,9 @@ export class CurseAddonProvider implements AddonProvider { latestVersion: latestVersion.displayName, providerName: this.name, thumbnailUrl: this.getThumbnailUrl(scanResult.searchResult), + screenshotUrls: this.getScreenshotUrls(scanResult.searchResult), + downloadCount: scanResult.searchResult.downloadCount, + summary: scanResult.searchResult.summary, }; } } diff --git a/wowup-electron/src/app/addon-providers/github-addon-provider.ts b/wowup-electron/src/app/addon-providers/github-addon-provider.ts index 5e9dc5a0..c2afbf83 100644 --- a/wowup-electron/src/app/addon-providers/github-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/github-addon-provider.ts @@ -5,210 +5,231 @@ import { WowClientType } from "app/models/warcraft/wow-client-type"; import { AddonChannelType } from "app/models/wowup/addon-channel-type"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { AddonSearchResult } from "app/models/wowup/addon-search-result"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; import { forkJoin, Observable, of } from "rxjs"; import { map } from "rxjs/operators"; import { AddonProvider } from "./addon-provider"; -import * as _ from 'lodash'; -import { extname } from 'path'; +import * as _ from "lodash"; +import { extname } from "path"; import { GitHubAsset } from "app/models/github/github-asset"; import { GitHubRepository } from "app/models/github/github-repository"; import { AddonSearchResultFile } from "app/models/wowup/addon-search-result-file"; const API_URL = "https://api.github.com/repos"; const RELEASE_CONTENT_TYPES = [ - "application/x-zip-compressed", - "application/zip" + "application/x-zip-compressed", + "application/zip", ]; export class GitHubAddonProvider implements AddonProvider { - public readonly name = "GitHub"; + public readonly name = "GitHub"; - constructor( - private _httpClient: HttpClient, - ) { } + constructor(private _httpClient: HttpClient) {} - async getAll(clientType: WowClientType, addonIds: string[]): Promise { - var searchResults: AddonSearchResult[] = [] + async getAll( + clientType: WowClientType, + addonIds: string[] + ): Promise { + var searchResults: AddonSearchResult[] = []; - for (let addonId of addonIds) { - var result = await this.getById(addonId, clientType).toPromise(); - if (result == null) { - continue; - } + for (let addonId of addonIds) { + var result = await this.getById(addonId, clientType).toPromise(); + if (result == null) { + continue; + } - searchResults.push(result); + searchResults.push(result); + } + + return searchResults; + } + + async getFeaturedAddons( + clientType: WowClientType + ): Promise { + return []; + } + + async searchByQuery( + query: string, + clientType: WowClientType + ): Promise { + return []; + } + + async searchByUrl( + addonUri: URL, + clientType: WowClientType + ): Promise { + const repoPath = addonUri.pathname; + const repoExtension = extname(repoPath); // if the repo has the git extension it wont work? + if (!repoPath || repoExtension) { + throw new Error(`Invlaid URL: ${addonUri}`); + } + + const results = await this.getReleases(repoPath).toPromise(); + const latestRelease = this.getLatestRelease(results); + const asset = this.getValidAsset(latestRelease, clientType); + + if (asset == null) { + throw new Error(`No release found: ${addonUri}`); + } + + var repository = await this.getRepository(repoPath).toPromise(); + var author = repository.owner.login; + var authorImageUrl = repository.owner.avatar_url; + + var potentialAddon: AddonSearchResult = { + author: author, + downloadCount: asset.download_count, + externalId: repoPath, + externalUrl: repository.html_url, + name: repository.name, + providerName: this.name, + thumbnailUrl: authorImageUrl, + }; + + return potentialAddon; + } + + async searchByName( + addonName: string, + folderName: string, + clientType: WowClientType, + nameOverride?: string + ): Promise { + return []; + } + + getById( + addonId: string, + clientType: WowClientType + ): Observable { + return forkJoin([ + this.getReleases(addonId), + this.getRepository(addonId), + ]).pipe( + map(([releases, repository]) => { + if (!releases?.length) { + return undefined; } - return searchResults; - } - - getFeaturedAddons(clientType: WowClientType): Observable { - return of([]); - } - - async searchByQuery(query: string, clientType: WowClientType): Promise { - return []; - } - - async searchByUrl(addonUri: URL, clientType: WowClientType): Promise { - const repoPath = addonUri.pathname; - const repoExtension = extname(repoPath); // if the repo has the git extension it wont work? - if (!repoPath || repoExtension) { - throw new Error(`Invlaid URL: ${addonUri}`); + const latestRelease = this.getLatestRelease(releases); + if (!latestRelease) { + return undefined; } - const results = await this.getReleases(repoPath).toPromise(); - const latestRelease = this.getLatestRelease(results); const asset = this.getValidAsset(latestRelease, clientType); - - if (asset == null) { - throw new Error(`No release found: ${addonUri}`); + if (!asset) { + return undefined; } - var repository = await this.getRepository(repoPath).toPromise(); - var author = repository.owner.login; - var authorImageUrl = repository.owner.avatar_url; + const author = repository.owner.login; + const authorImageUrl = repository.owner.avatar_url; + const addonName = this.getAddonName(addonId); - var potentialAddon: PotentialAddon = { - author: author, - downloadCount: asset.download_count, - externalId: repoPath, - externalUrl: repository.html_url, - name: repository.name, - providerName: this.name, - thumbnailUrl: authorImageUrl + var searchResultFile: AddonSearchResultFile = { + channelType: AddonChannelType.Stable, + downloadUrl: asset.browser_download_url, + folders: [addonName], + gameVersion: "", + version: asset.name, + releaseDate: new Date(asset.created_at), }; - return potentialAddon; + var searchResult: AddonSearchResult = { + author: author, + externalId: addonId, + externalUrl: repository.html_url, + files: [searchResultFile], + name: addonName, + providerName: this.name, + thumbnailUrl: authorImageUrl, + }; + + return searchResult; + }) + ); + } + + isValidAddonUri(addonUri: URL): boolean { + return addonUri.host && addonUri.host.endsWith("github.com"); + } + + onPostInstall(addon: Addon): void {} + + async scan( + clientType: WowClientType, + addonChannelType: AddonChannelType, + addonFolders: AddonFolder[] + ): Promise {} + + private getLatestRelease(releases: GitHubRelease[]): GitHubRelease { + let sortedReleases = _.filter(releases, (r) => !r.draft); + sortedReleases = _.sortBy( + sortedReleases, + (release) => new Date(release.published_at) + ).reverse(); + + return _.first(sortedReleases); + } + + private getValidAsset( + release: GitHubRelease, + clientType: WowClientType + ): GitHubAsset { + const sortedAssets = _.filter( + release.assets, + (asset) => + this.isNotNoLib(asset) && + this.isValidContentType(asset) && + this.isValidClientType(clientType, asset) + ); + + return _.first(sortedAssets); + } + + private isNotNoLib(asset: GitHubAsset): boolean { + return asset.name.toLowerCase().indexOf("-nolib") === -1; + } + + private isValidContentType(asset: GitHubAsset): boolean { + return RELEASE_CONTENT_TYPES.some((ct) => ct == asset.content_type); + } + + private isValidClientType( + clientType: WowClientType, + asset: GitHubAsset + ): boolean { + const isClassic = this.isClassicAsset(asset); + + switch (clientType) { + case WowClientType.Retail: + case WowClientType.RetailPtr: + case WowClientType.Beta: + return !isClassic; + case WowClientType.Classic: + case WowClientType.ClassicPtr: + return isClassic; + default: + return false; } + } - async searchByName(addonName: string, folderName: string, clientType: WowClientType, nameOverride?: string): Promise { - return []; - } + private isClassicAsset(asset: GitHubAsset): boolean { + return asset.name.toLowerCase().endsWith("-classic.zip"); + } - getById(addonId: string, clientType: WowClientType): Observable { - return forkJoin([ - this.getReleases(addonId), - this.getRepository(addonId) - ]) - .pipe( - map(([releases, repository]) => { - if (!releases?.length) { - return undefined; - } + private getAddonName(addonId: string): string { + return addonId.split("/").filter((str) => !!str)[1]; + } - const latestRelease = this.getLatestRelease(releases); - if (!latestRelease) { - return undefined; - } + private getReleases(repositoryPath: string): Observable { + const url = `${API_URL}${repositoryPath}/releases`; + return this._httpClient.get(url.toString()); + } - const asset = this.getValidAsset(latestRelease, clientType); - if (!asset) { - return undefined; - } - - const author = repository.owner.login; - const authorImageUrl = repository.owner.avatar_url; - const addonName = this.getAddonName(addonId); - - var searchResultFile: AddonSearchResultFile = { - channelType: AddonChannelType.Stable, - downloadUrl: asset.browser_download_url, - folders: [addonName], - gameVersion: '', - version: asset.name, - releaseDate: new Date(asset.created_at) - }; - - var searchResult: AddonSearchResult = { - author: author, - externalId: addonId, - externalUrl: repository.html_url, - files: [searchResultFile], - name: addonName, - providerName: this.name, - thumbnailUrl: authorImageUrl - }; - - return searchResult; - }) - ); - } - - isValidAddonUri(addonUri: URL): boolean { - return addonUri.host && - addonUri.host.endsWith("github.com"); - } - - onPostInstall(addon: Addon): void { - } - - async scan( - clientType: WowClientType, - addonChannelType: AddonChannelType, - addonFolders: AddonFolder[] - ): Promise { - } - - private getLatestRelease(releases: GitHubRelease[]): GitHubRelease { - let sortedReleases = _.filter(releases, r => !r.draft); - sortedReleases = _.sortBy(sortedReleases, release => new Date(release.published_at)) - .reverse(); - - return _.first(sortedReleases); - } - - private getValidAsset(release: GitHubRelease, clientType: WowClientType): GitHubAsset { - const sortedAssets = _.filter( - release.assets, - asset => this.isNotNoLib(asset) && - this.isValidContentType(asset) && - this.isValidClientType(clientType, asset)); - - return _.first(sortedAssets); - } - - private isNotNoLib(asset: GitHubAsset): boolean { - return asset.name.toLowerCase().indexOf("-nolib") === -1; - } - - private isValidContentType(asset: GitHubAsset): boolean { - return RELEASE_CONTENT_TYPES.some(ct => ct == asset.content_type); - } - - private isValidClientType(clientType: WowClientType, asset: GitHubAsset): boolean { - const isClassic = this.isClassicAsset(asset); - - switch (clientType) { - case WowClientType.Retail: - case WowClientType.RetailPtr: - case WowClientType.Beta: - return !isClassic; - case WowClientType.Classic: - case WowClientType.ClassicPtr: - return isClassic; - default: - return false; - } - } - - private isClassicAsset(asset: GitHubAsset): boolean { - return asset.name.toLowerCase().endsWith("-classic.zip"); - } - - private getAddonName(addonId: string): string { - return addonId.split("/").filter(str => !!str)[1]; - } - - private getReleases(repositoryPath: string): Observable { - const url = `${API_URL}${repositoryPath}/releases`; - return this._httpClient.get(url.toString()); - } - - private getRepository(repositoryPath: string): Observable { - const url = `${API_URL}${repositoryPath}`; - return this._httpClient.get(url.toString()); - } - -} \ No newline at end of file + private getRepository(repositoryPath: string): Observable { + const url = `${API_URL}${repositoryPath}`; + return this._httpClient.get(url.toString()); + } +} diff --git a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts index 085ad0af..a752a0cd 100644 --- a/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/tukui-addon-provider.ts @@ -5,7 +5,6 @@ import { WowClientType } from "app/models/warcraft/wow-client-type"; import { AddonChannelType } from "app/models/wowup/addon-channel-type"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { AddonSearchResult } from "app/models/wowup/addon-search-result"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; import { CachingService } from "app/services/caching/caching-service"; import { ElectronService } from "app/services/electron/electron.service"; import { FileService } from "app/services/files/file.service"; @@ -65,18 +64,17 @@ export class TukUiAddonProvider implements AddonProvider { return results; } - getFeaturedAddons(clientType: WowClientType): Observable { - return from(this.getAllAddons(clientType)).pipe( - map((tukUiAddons) => { - return tukUiAddons.map((addon) => this.toPotentialAddon(addon)); - }) - ); + public async getFeaturedAddons( + clientType: WowClientType + ): Promise { + const tukUiAddons = await this.getAllAddons(clientType); + return tukUiAddons.map((addon) => this.toSearchResult(addon)); } async searchByQuery( query: string, clientType: WowClientType - ): Promise { + ): Promise { const addons = await this.getAllAddons(clientType); const canonQuery = query.toLowerCase(); let similarAddons = _.filter( @@ -85,13 +83,13 @@ export class TukUiAddonProvider implements AddonProvider { ); similarAddons = _.orderBy(similarAddons, ["downloads"]); - return _.map(similarAddons, (addon) => this.toPotentialAddon(addon)); + return _.map(similarAddons, (addon) => this.toSearchResult(addon)); } searchByUrl( addonUri: URL, clientType: WowClientType - ): Promise { + ): Promise { throw new Error("Method not implemented."); } @@ -175,6 +173,9 @@ export class TukUiAddonProvider implements AddonProvider { providerName: this.name, thumbnailUrl: tukUiAddon.screenshot_url, updatedAt: new Date(), + summary: tukUiAddon.small_desc, + downloadCount: Number.parseFloat(tukUiAddon.downloads), + screenshotUrls: [tukUiAddon.screenshot_url], }; } } @@ -187,22 +188,9 @@ export class TukUiAddonProvider implements AddonProvider { ); } - private toPotentialAddon(addon: TukUiAddon): PotentialAddon { - return { - author: addon.author, - downloadCount: parseInt(addon.downloads, 10), - externalId: addon.id, - externalUrl: addon.web_url, - name: addon.name, - providerName: this.name, - thumbnailUrl: addon.screenshot_url, - summary: addon.small_desc, - }; - } - private toSearchResult( addon: TukUiAddon, - folderName: string + folderName?: string ): AddonSearchResult | undefined { if (!addon) { return undefined; @@ -210,7 +198,7 @@ export class TukUiAddonProvider implements AddonProvider { var latestFile: AddonSearchResultFile = { channelType: AddonChannelType.Stable, - folders: [folderName], + folders: folderName ? [folderName] : [], downloadUrl: addon.url, gameVersion: addon.patch, version: addon.version, @@ -224,6 +212,7 @@ export class TukUiAddonProvider implements AddonProvider { thumbnailUrl: addon.screenshot_url, externalUrl: addon.web_url, providerName: this.name, + downloadCount: parseInt(addon.downloads, 10), files: [latestFile], }; } diff --git a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts index 6a2dcc5d..441b4b8b 100644 --- a/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wow-interface-addon-provider.ts @@ -5,7 +5,6 @@ import { AddonDetailsResponse } from "app/models/wow-interface/addon-details-res import { AddonChannelType } from "app/models/wowup/addon-channel-type"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { AddonSearchResult } from "app/models/wowup/addon-search-result"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; import { ElectronService } from "app/services"; import { CachingService } from "app/services/caching/caching-service"; import { FileService } from "app/services/files/file.service"; @@ -64,21 +63,23 @@ export class WowInterfaceAddonProvider implements AddonProvider { return searchResults; } - getFeaturedAddons(clientType: WowClientType): Observable { - return of([]); + public async getFeaturedAddons( + clientType: WowClientType + ): Promise { + return []; } async searchByQuery( query: string, clientType: WowClientType - ): Promise { + ): Promise { return []; } async searchByUrl( addonUri: URL, clientType: WowClientType - ): Promise { + ): Promise { const addonId = this.getAddonId(addonUri); if (!addonId) { throw new Error(`Addon ID not found ${addonUri}`); @@ -89,7 +90,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { throw new Error(`Bad addon api response ${addonUri}`); } - return this.toPotentialAddon(addon); + return this.toAddonSearchResult(addon); } searchByName( @@ -153,7 +154,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { private getAddonDetails = ( addonId: string ): Promise => { - console.debug('getAddonDetails'); + console.debug("getAddonDetails"); const url = new URL(`${API_URL}/filedetails/${addonId}.json`); return this._httpClient @@ -195,31 +196,22 @@ export class WowInterfaceAddonProvider implements AddonProvider { name: response.title, providerName: this.name, thumbnailUrl: this.getThumbnailUrl(response), - }; - } - - private toPotentialAddon(response: AddonDetailsResponse): PotentialAddon { - return { - providerName: this.name, - name: response.title, + summary: response.description, + screenshotUrls: response.images?.map((img) => img.imageUrl), downloadCount: response.downloads, - thumbnailUrl: this.getThumbnailUrl(response), - externalId: response.id.toString(), - externalUrl: this.getAddonUrl(response), - author: response.author, }; } private toAddonSearchResult( response: AddonDetailsResponse, - folderName: string + folderName?: string ): AddonSearchResult { try { var searchResultFile: AddonSearchResultFile = { channelType: AddonChannelType.Stable, version: response.version, downloadUrl: response.downloadUri, - folders: [folderName], + folders: folderName ? [folderName] : [], gameVersion: "", releaseDate: new Date(), }; @@ -231,6 +223,7 @@ export class WowInterfaceAddonProvider implements AddonProvider { thumbnailUrl: this.getThumbnailUrl(response), externalUrl: this.getAddonUrl(response), providerName: this.name, + downloadCount: response.downloads, files: [searchResultFile], }; } catch (err) { diff --git a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts index 3e3ac652..4c5a1d41 100644 --- a/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/wowup-addon-provider.ts @@ -5,7 +5,6 @@ import { v4 as uuidv4 } from "uuid"; import { Addon } from "../entities/addon"; import { WowClientType } from "../models/warcraft/wow-client-type"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; -import { PotentialAddon } from "../models/wowup/potential-addon"; import { AddonProvider, AddonProviderType } from "./addon-provider"; import { WowUpAddonRepresentation } from "../models/wowup-api/wowup-addon.representation"; import { AddonFolder } from "app/models/wowup/addon-folder"; @@ -43,15 +42,17 @@ export class WowUpAddonProvider implements AddonProvider { return []; } - getFeaturedAddons(clientType: WowClientType): Observable { + public async getFeaturedAddons( + clientType: WowClientType + ): Promise { // TODO - return of([]); + return []; } async searchByQuery( query: string, clientType: WowClientType - ): Promise { + ): Promise { // TODO return []; } @@ -59,7 +60,7 @@ export class WowUpAddonProvider implements AddonProvider { async searchByUrl( addonUri: URL, clientType: WowClientType - ): Promise { + ): Promise { // TODO return undefined; } diff --git a/wowup-electron/src/app/business-objects/get-addon-list-item.ts b/wowup-electron/src/app/business-objects/get-addon-list-item.ts new file mode 100644 index 00000000..e5fb298f --- /dev/null +++ b/wowup-electron/src/app/business-objects/get-addon-list-item.ts @@ -0,0 +1,29 @@ +import { AddonSearchResult } from "app/models/wowup/addon-search-result"; + +export class GetAddonListItem { + public readonly searchResult: AddonSearchResult; + + get downloadCount() { + return this.searchResult.downloadCount || 0; + } + + get name() { + return this.searchResult.name; + } + + get thumbnailUrl() { + return this.searchResult.thumbnailUrl; + } + + get author() { + return this.searchResult.author; + } + + get providerName() { + return this.searchResult.providerName; + } + + constructor(searchResult: AddonSearchResult) { + this.searchResult = searchResult; + } +} diff --git a/wowup-electron/src/app/business-objects/my-addon-list-item.ts b/wowup-electron/src/app/business-objects/my-addon-list-item.ts new file mode 100644 index 00000000..7959efd8 --- /dev/null +++ b/wowup-electron/src/app/business-objects/my-addon-list-item.ts @@ -0,0 +1,89 @@ +import { Addon } from "app/entities/addon"; +import { AddonChannelType } from "app/models/wowup/addon-channel-type"; +import { AddonDisplayState } from "../models/wowup/addon-display-state"; + +export class AddonViewModel { + public readonly addon: Addon; + + isInstalling: boolean = false; + installProgress: number = 0; + statusText: string = ""; + selected: boolean = false; + + get needsInstall() { + return ( + !this.isInstalling && this.displayState === AddonDisplayState.Install + ); + } + + get needsUpdate() { + return !this.isInstalling && this.displayState === AddonDisplayState.Update; + } + + get isUpToDate() { + return ( + !this.isInstalling && this.displayState === AddonDisplayState.UpToDate + ); + } + + get isIgnored() { + return this.displayState === AddonDisplayState.Ignored; + } + + get isStableChannel() { + return this.addon.channelType === AddonChannelType.Stable; + } + + get isBetaChannel() { + return this.addon.channelType === AddonChannelType.Beta; + } + + get isAlphaChannel() { + return this.addon.channelType === AddonChannelType.Alpha; + } + + get displayState(): AddonDisplayState { + if (this.addon.isIgnored) { + return AddonDisplayState.Ignored; + } + + if (!this.addon.installedVersion) { + return AddonDisplayState.Install; + } + + if (this.addon.installedVersion !== this.addon.latestVersion) { + return AddonDisplayState.Update; + } + + if (this.addon.installedVersion === this.addon.latestVersion) { + return AddonDisplayState.UpToDate; + } + + return AddonDisplayState.Unknown; + } + + constructor(addon?: Addon) { + this.addon = addon; + this.statusText = this.getStateText(); + } + + public onClicked() { + this.selected = !this.selected; + } + + public getStateText() { + switch (this.displayState) { + case AddonDisplayState.UpToDate: + return "Up to Date"; + case AddonDisplayState.Ignored: + return "Ignored"; + case AddonDisplayState.Update: + case AddonDisplayState.Install: + return "Install"; + case AddonDisplayState.Unknown: + default: + console.log("Unhandled display state", this.displayState); + return ""; + } + } +} diff --git a/wowup-electron/src/app/business-objects/my-addons-list-item.ts b/wowup-electron/src/app/business-objects/my-addons-list-item.ts deleted file mode 100644 index 439e64e7..00000000 --- a/wowup-electron/src/app/business-objects/my-addons-list-item.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Addon } from "app/entities/addon"; -import { WowClientType } from "app/models/warcraft/wow-client-type"; -import { AddonChannelType } from "app/models/wowup/addon-channel-type"; -import { AddonDisplayState } from "../models/wowup/addon-display-state"; - -export class MyAddonsListItem { - addon: Addon; - - isInstalling: boolean = false; - installProgress: number = 0; - statusText: string = ''; - selected: boolean = false; - - get needsInstall() { - return !this.isInstalling && this.displayState === AddonDisplayState.Install; - } - - get needsUpdate() { - return !this.isInstalling && this.displayState === AddonDisplayState.Update; - } - - get isUpToDate() { - return !this.isInstalling && this.displayState === AddonDisplayState.UpToDate; - } - - get isIgnored() { - return this.displayState === AddonDisplayState.Ignored; - } - - get isStableChannel() { - return this.addon.channelType === AddonChannelType.Stable; - } - - get isBetaChannel() { - return this.addon.channelType === AddonChannelType.Beta; - } - - get isAlphaChannel() { - return this.addon.channelType === AddonChannelType.Alpha; - } - - get displayState(): AddonDisplayState { - if (this.addon.isIgnored) { - return AddonDisplayState.Ignored; - } - - if (!this.addon.installedVersion) { - return AddonDisplayState.Install; - } - - if (this.addon.installedVersion !== this.addon.latestVersion) { - return AddonDisplayState.Update; - } - - if (this.addon.installedVersion === this.addon.latestVersion) { - return AddonDisplayState.UpToDate; - } - - return AddonDisplayState.Unknown; - } - - constructor(addon?: Addon) { - this.addon = addon; - this.statusText = this.getStateText(); - } - - public onClicked() { - this.selected = !this.selected; - } - - public getStateText() { - switch (this.displayState) { - case AddonDisplayState.UpToDate: - return "Up to Date"; - case AddonDisplayState.Ignored: - return "Ignored"; - case AddonDisplayState.Update: - case AddonDisplayState.Install: - case AddonDisplayState.Unknown: - default: - console.log('Unhandled display state', this.displayState) - return ''; - } - } -} \ No newline at end of file diff --git a/wowup-electron/src/app/components/addon-detail/addon-detail.component.html b/wowup-electron/src/app/components/addon-detail/addon-detail.component.html index a864a802..59be5b70 100644 --- a/wowup-electron/src/app/components/addon-detail/addon-detail.component.html +++ b/wowup-electron/src/app/components/addon-detail/addon-detail.component.html @@ -1,30 +1,40 @@
- -

{{ addonDetail.name }}

+ +

{{ addon.name }}

-
By {{ addonDetail.author }}
- +
By {{ addon.author }}
+
- {{ addonDetail.summary }} + {{ addon.summary }} +
+
+ Addon Picture
-

- {{ addonDetail.description }} -

- Addon Picture
+ {{ "DIALOGS.ADDON_DETAILS.VIEW_IN_BROWSER_BUTTON" | translate }} -
\ No newline at end of file + diff --git a/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss b/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss index 83c647e0..23707778 100644 --- a/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss +++ b/wowup-electron/src/app/components/addon-detail/addon-detail.component.scss @@ -1,12 +1,18 @@ .addon-detail-view { - width: 450px; + width: 35vw; + max-width: 500px; margin-top: 8px; .addon-detail-title, .addon-detail-subtitle { display: flex; justify-content: space-between; + + h2 { + padding: 0 0.5rem; + } } + .mat-icon-button .mat-button-wrapper, .close-icon { cursor: pointer !important; @@ -16,15 +22,20 @@ } .addon-detail-summary { margin-bottom: 8px; - // font-weight: bold; + font-weight: 400; font-size: 14px; letter-spacing: 0.6px; } .dialog-buttons { - display:flex; + display: flex; .btn-wrapper { margin-left: auto; } } + .screenshot-container { + img { + max-height: 200px; + } + } } diff --git a/wowup-electron/src/app/components/addon-detail/addon-detail.component.ts b/wowup-electron/src/app/components/addon-detail/addon-detail.component.ts index b3acf225..ed2110bb 100644 --- a/wowup-electron/src/app/components/addon-detail/addon-detail.component.ts +++ b/wowup-electron/src/app/components/addon-detail/addon-detail.component.ts @@ -1,6 +1,16 @@ import { Component, Inject, OnInit } from "@angular/core"; import { MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { AddonDetailModel } from "app/models/wowup/addon-detail.model"; +import { Addon } from "app/entities/addon"; + +export interface AddonDetailModel { + author?: string; + externalUrl?: string; + name: string; + providerName?: string; + screenshotUrls?: string[]; + summary?: string; + thumbnailUrl?: string; +} @Component({ selector: "app-addon-detail", @@ -8,23 +18,19 @@ import { AddonDetailModel } from "app/models/wowup/addon-detail.model"; styleUrls: ["./addon-detail.component.scss"], }) export class AddonDetailComponent implements OnInit { - addonDetail: AddonDetailModel; + public addon: AddonDetailModel; constructor(@Inject(MAT_DIALOG_DATA) public data: AddonDetailModel) { - this.addonDetail = this.data; + this.addon = this.data; } get defaultImageUrl(): string { - return this.addonDetail?.screenshotUrls && this.addonDetail?.screenshotUrls[0] - ? this.addonDetail?.screenshotUrls[0] - : this.addonDetail?.thumbnailUrl - ? this.addonDetail?.thumbnailUrl + return this.addon?.screenshotUrls && this.addon?.screenshotUrls[0] + ? this.addon?.screenshotUrls[0] + : this.addon?.thumbnailUrl + ? this.addon?.thumbnailUrl : ""; } ngOnInit(): void {} - - installAddon() {} - - openInBrowser() {} } diff --git a/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.html b/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.html index 3e87f0b3..f2aaaa72 100644 --- a/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.html +++ b/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.html @@ -1 +1,5 @@ - + + + + \ No newline at end of file diff --git a/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.ts b/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.ts index ba1b07b0..59a41b41 100644 --- a/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.ts +++ b/wowup-electron/src/app/components/addon-install-button/addon-install-button.component.ts @@ -1,13 +1,18 @@ import { Component, Input, OnDestroy, OnInit } from "@angular/core"; -import { AddonDetailModel } from "app/models/wowup/addon-detail.model"; -import { AddonInstallState } from "app/models/wowup/addon-install-state"; +import { MatDialog } from "@angular/material/dialog"; +import { TranslateService } from "@ngx-translate/core"; +import { AddonViewModel } from "app/business-objects/my-addon-list-item"; +import { Addon } from "app/entities/addon"; +import { AddonDisplayState } from "app/models/wowup/addon-display-state"; +import { AddonSearchResult } from "app/models/wowup/addon-search-result"; import { AddonUpdateEvent } from "app/models/wowup/addon-update-event"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; import { AddonService } from "app/services/addons/addon.service"; import { SessionService } from "app/services/session/session.service"; +import { getEnumName } from "app/utils/enum.utils"; import { MatProgressButtonOptions } from "mat-progress-buttons"; import { Subscription } from "rxjs"; import { filter, map } from "rxjs/operators"; +import { ConfirmDialogComponent } from "../confirm-dialog/confirm-dialog.component"; @Component({ selector: "app-addon-install-button", @@ -15,37 +20,46 @@ import { filter, map } from "rxjs/operators"; styleUrls: ["./addon-install-button.component.scss"], }) export class AddonInstallButtonComponent implements OnInit, OnDestroy { - @Input("addon") addon: PotentialAddon | AddonDetailModel; + @Input() addon: Addon; + @Input() hideUninstall = false; - isInstalled: boolean; - canUninstall: boolean; - hasUpdate: boolean; - buttonOptions: MatProgressButtonOptions; + addonModel: AddonViewModel; + + isInstalled = false; + btnUninstallOptions: MatProgressButtonOptions; + btnInstallOptions: MatProgressButtonOptions; private _subscriptions: Subscription[]; + get canUninstall(): boolean { + return this.isInstalled && !this.hideUninstall; + } + + get shouldDisableInstallButton(): boolean { + return ( + (!this.addonModel.needsInstall && !this.addonModel.needsUpdate) || + this.addonModel.isInstalling + ); + } + constructor( private _addonService: AddonService, - private _sessionService: SessionService + private _sessionService: SessionService, + private _dialog: MatDialog, + private _translate: TranslateService ) {} ngOnInit(): void { + this.addonModel = new AddonViewModel(this.addon); this.isInstalled = this._addonService.isInstalled( this.addon.externalId, this._sessionService.selectedClientType ); this.setButtonOptions(); + const addonUpdateSubscription = this._addonService.addonInstalled$ - .pipe( - filter((x) => x.addon.externalId === this.addon.externalId), - map((event: AddonUpdateEvent) => { - if (event.installState === AddonInstallState.Complete) { - this.isInstalled = true; - this.setButtonOptions(); - } - }) - ) - .subscribe(); + .pipe(filter((x) => x.addon.externalId === this.addon.externalId)) + .subscribe((event) => this.onAddonUpdate(event)); this._subscriptions = [addonUpdateSubscription]; } @@ -53,70 +67,130 @@ export class AddonInstallButtonComponent implements OnInit, OnDestroy { this._subscriptions.forEach((x) => x.unsubscribe()); } - setButtonOptions(): void { - if (!this.isInstalled) { - this.buttonOptions = this.getBaseBtnOptions(); - } else if (this.isInstalled && !this.canUninstall) { - this.buttonOptions = this.getInstalledBtnOptions(); - } else if (this.isInstalled && this.canUninstall) { - this.buttonOptions = this.getUninstallBtnOptions(); - } else if (this.isInstalled && this.hasUpdate) { - this.buttonOptions = this.getUpdateBtnOptions(); + onAddonUpdate(event: AddonUpdateEvent): void { + const addonModel = new AddonViewModel(event.addon); + addonModel.installProgress = event.progress; + // addonModel.updateInstallState(event.installState); + // addonModel.setStatusText(event.installState); + this.addonModel = addonModel; + + if (event.installState === 4) { + this.setButtonOptions(); + } else { + this.updateButtonOptions(); } } - onButtonClick(): void { - this.buttonOptions.active = true; - this.buttonOptions.text = "Installing..."; + setButtonOptions(): void { + this.btnInstallOptions = this.getBaseBtnOptions(); + this.btnUninstallOptions = this.getUninstallBtnOptions(); + + if (this.shouldDisableInstallButton) { + this.btnInstallOptions.disabled = true; + } + if (!this.canUninstall) { + this.btnUninstallOptions.disabled = true; + this.btnUninstallOptions.active = false; + } + } + + updateButtonOptions(): void { + this.btnInstallOptions.active = this.addonModel.isInstalling; + this.btnInstallOptions.value = this.addonModel.installProgress; + this.btnInstallOptions.text = this.addonModel.isInstalling + ? this.getTranslatedStatusText() + : this.getTranslatedStateText(); + } + + onInstallUpdateClick(): void { + this.btnInstallOptions.active = true; + if (this.addonModel.needsUpdate) { + this.updateAddon(); + } else if (this.addonModel.needsInstall) { + this.installAddon(); + } + } + + onUninstallClick(): void { + this.confirmRemoveAddon(); + } + + private installAddon() { this._addonService.installPotentialAddon( - this.addon as PotentialAddon, - this._sessionService.selectedClientType, - (state, progress) => { - if (state === AddonInstallState.Complete) { - this.isInstalled = true; - this.setButtonOptions(); - } - this.buttonOptions.value = progress; + this.addonModel.addon as AddonSearchResult, + this._sessionService.selectedClientType + ); + } + + private updateAddon() { + this._addonService.installAddon(this.addonModel.addon.id); + } + + private confirmRemoveAddon() { + const dialogRef = this._dialog.open(ConfirmDialogComponent, { + data: { + title: this._translate.instant("DIALOGS.REMOVE_ADDON.TITLE"), + message: this._translate.instant("DIALOGS.REMOVE_ADDON.MESSAGE", { + addon: this.addon.name, + }), + }, + }); + + dialogRef.afterClosed().subscribe((result) => { + if (!result) { + return; } + this.btnUninstallOptions.active = true; + this.btnUninstallOptions.text = this._translate.instant( + "COMMON.ADDON_STATUS.UNINSTALLING" + ); + this._addonService.removeAddon(this.addonModel.addon); + // Parent component should listen to addon removed event and make changes. + }); + } + + private getTranslatedStatusText(): string { + const status = this.addonModel.statusText; + return this._translate.instant( + `COMMON.ADDON_STATUS.${status.toUpperCase()}` + ); + } + + private getTranslatedStateText(): string { + const state = this.addonModel.displayState; + return this._translate.instant( + `COMMON.ADDON_STATE.${getEnumName( + AddonDisplayState, + state + ).toUpperCase()}` ); } private getBaseBtnOptions(): MatProgressButtonOptions { return { - active: false, - text: "Install", + active: this.addonModel.isInstalling, + disabled: this.shouldDisableInstallButton, + value: this.addonModel.installProgress, + text: this.addonModel.isInstalling + ? this.getTranslatedStatusText() + : this.getTranslatedStateText(), + mode: "determinate", buttonColor: "primary", barColor: "accent", customClass: "install-button", raised: true, stroked: false, - mode: "determinate", - value: 0, - disabled: false, fullWidth: false, }; } - private getInstalledBtnOptions(): MatProgressButtonOptions { - return { - ...this.getBaseBtnOptions(), - disabled: true, - active: false, - text: "Installed", - }; - } - private getUninstallBtnOptions(): MatProgressButtonOptions { return { ...this.getBaseBtnOptions(), - text: "Uninstall", - }; - } - - private getUpdateBtnOptions(): MatProgressButtonOptions { - return { - ...this.getBaseBtnOptions(), - text: "Update", + text: this._translate.instant("COMMON.ADDON_STATE.UNINSTALL"), + mode: "indeterminate", + buttonColor: "warn", + barColor: "primary", }; } } diff --git a/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.html b/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.html new file mode 100644 index 00000000..35c5a94b --- /dev/null +++ b/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.scss b/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.spec.ts b/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.spec.ts new file mode 100644 index 00000000..d540da6c --- /dev/null +++ b/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GetAddonStatusColumnComponent } from './get-addon-status-column.component'; + +describe('GetAddonStatusColumnComponent', () => { + let component: GetAddonStatusColumnComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GetAddonStatusColumnComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(GetAddonStatusColumnComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.ts b/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.ts new file mode 100644 index 00000000..2e04924c --- /dev/null +++ b/wowup-electron/src/app/components/get-addon-status-column/get-addon-status-column.component.ts @@ -0,0 +1,110 @@ +import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { TranslateService } from "@ngx-translate/core"; +import { GetAddonListItem } from "app/business-objects/get-addon-list-item"; +import { AddonInstallState } from "app/models/wowup/addon-install-state"; +import { AddonService } from "app/services/addons/addon.service"; +import { SessionService } from "app/services/session/session.service"; +import { MatProgressButtonOptions } from "mat-progress-buttons"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; + +@Component({ + selector: "app-get-addon-status-column", + templateUrl: "./get-addon-status-column.component.html", + styleUrls: ["./get-addon-status-column.component.scss"], +}) +export class GetAddonStatusColumnComponent implements OnInit, OnDestroy { + @Input() listItem: GetAddonListItem; + + private readonly _buttonOptionsSrc: BehaviorSubject; + private installState: AddonInstallState = AddonInstallState.Unknown; + private installProgress: number = 0; + + public readonly buttonOptions$: Observable; + + public get buttonText() { + if (this.installState !== AddonInstallState.Unknown) { + return this.getInstallStateText(this.installState); + } + + return this._translate.instant("COMMON.ADDON_STATE.INSTALL"); + } + + public getInstallStateText(installState: AddonInstallState) { + switch (installState) { + case AddonInstallState.BackingUp: + return this._translate.instant("COMMON.ADDON_STATUS.BACKINGUP"); + case AddonInstallState.Complete: + return this._translate.instant("COMMON.ADDON_STATUS.COMPLETE"); + case AddonInstallState.Downloading: + return this._translate.instant("COMMON.ADDON_STATUS.DOWNLOADING"); + case AddonInstallState.Installing: + return this._translate.instant("COMMON.ADDON_STATUS.INSTALLING"); + case AddonInstallState.Pending: + return this._translate.instant("COMMON.ADDON_STATUS.PENDING"); + default: + return ""; + } + } + + public get isButtonActive() { + return ( + this.installState !== AddonInstallState.Unknown && + this.installState !== AddonInstallState.Complete + ); + } + + public get isButtonDisabled() { + return this.installState === AddonInstallState.Complete; + } + + public getButtonOptions(): MatProgressButtonOptions { + return { + active: this.isButtonActive, + disabled: this.isButtonDisabled, + value: this.installProgress, + text: this.buttonText, + mode: "determinate", + buttonColor: "primary", + barColor: "accent", + customClass: "install-button", + raised: false, + flat: true, + stroked: false, + fullWidth: false, + }; + } + + constructor( + private _addonService: AddonService, + private _sessionService: SessionService, + private _translate: TranslateService + ) { + this._buttonOptionsSrc = new BehaviorSubject( + this.getButtonOptions() + ); + this.buttonOptions$ = this._buttonOptionsSrc.asObservable(); + } + + ngOnInit(): void {} + + ngOnDestroy(): void { + this._buttonOptionsSrc.complete(); + } + + onInstallUpdateClick() { + this._addonService.installPotentialAddon( + this.listItem.searchResult, + this._sessionService.selectedClientType, + this.onInstallUpdate + ); + } + + private onInstallUpdate = ( + installState: AddonInstallState, + progress: number + ) => { + this.installState = installState; + this.installProgress = progress; + this._buttonOptionsSrc.next(this.getButtonOptions()); + }; +} diff --git a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts index ef2074a9..6cc392b9 100644 --- a/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts +++ b/wowup-electron/src/app/components/install-from-url-dialog/install-from-url-dialog.component.ts @@ -1,7 +1,7 @@ import { HttpErrorResponse } from "@angular/common/http"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { MatDialog, MatDialogRef } from "@angular/material/dialog"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; +import { AddonSearchResult } from "app/models/wowup/addon-search-result"; import { AddonService } from "app/services/addons/addon.service"; import { SessionService } from "app/services/session/session.service"; import { from, Subscription } from "rxjs"; @@ -18,7 +18,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy { public showInstallButton = false; public showInstallSuccess = false; public query = ""; - public addon?: PotentialAddon; + public addon?: AddonSearchResult; private _installSubscription?: Subscription; diff --git a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html new file mode 100644 index 00000000..35c5a94b --- /dev/null +++ b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.scss b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.spec.ts b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.spec.ts new file mode 100644 index 00000000..e3dd595b --- /dev/null +++ b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MyAddonStatusColumnComponent } from './my-addon-status-column.component'; + +describe('MyAddonStatusColumnComponent', () => { + let component: MyAddonStatusColumnComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MyAddonStatusColumnComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MyAddonStatusColumnComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts new file mode 100644 index 00000000..e30baf06 --- /dev/null +++ b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts @@ -0,0 +1,130 @@ +import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { MatProgressButtonOptions } from "mat-progress-buttons"; +import { BehaviorSubject, Observable } from "rxjs"; +import { AddonInstallState } from "app/models/wowup/addon-install-state"; +import { AddonViewModel } from "app/business-objects/my-addon-list-item"; +import { AddonService } from "app/services/addons/addon.service"; +import { TranslateService } from "@ngx-translate/core"; + +@Component({ + selector: "app-my-addon-status-column", + templateUrl: "./my-addon-status-column.component.html", + styleUrls: ["./my-addon-status-column.component.scss"], +}) +export class MyAddonStatusColumnComponent implements OnInit, OnDestroy { + @Input() listItem: AddonViewModel; + + private readonly _buttonOptionsSrc: BehaviorSubject; + private installState: AddonInstallState = AddonInstallState.Unknown; + private installProgress: number = 0; + + public readonly buttonOptions$: Observable; + + public get buttonText() { + if (this.installState !== AddonInstallState.Unknown) { + return this.getInstallStateText(this.installState); + } + + return this.getStatusText(); + } + + public get isButtonActive() { + return ( + this.installState !== AddonInstallState.Unknown && + this.installState !== AddonInstallState.Complete + ); + } + + public get isButtonDisabled() { + return ( + this.listItem?.isUpToDate || + this.installState === AddonInstallState.Complete + ); + } + + constructor( + private _addonService: AddonService, + private _translate: TranslateService + ) { + this._buttonOptionsSrc = new BehaviorSubject( + this.getButtonOptions() + ); + this.buttonOptions$ = this._buttonOptionsSrc.asObservable(); + } + + ngOnInit(): void { + this._buttonOptionsSrc.next(this.getButtonOptions()); + } + + ngOnDestroy(): void { + this._buttonOptionsSrc.complete(); + } + + public getStatusText() { + if (this.listItem?.needsInstall) { + return this._translate.instant( + "PAGES.MY_ADDONS.TABLE.ADDON_INSTALL_BUTTON" + ); + } + + if (this.listItem?.needsUpdate) { + return this._translate.instant( + "PAGES.MY_ADDONS.TABLE.ADDON_UPDATE_BUTTON" + ); + } + + return this.listItem?.statusText; + } + + public onInstallUpdateClick() { + this._addonService.installAddon( + this.listItem.addon.id, + this.onInstallUpdate + ); + } + + private onInstallUpdate = ( + installState: AddonInstallState, + progress: number + ) => { + this.installState = installState; + this.installProgress = progress; + + console.log(this.getButtonOptions()); + this._buttonOptionsSrc.next(this.getButtonOptions()); + }; + + private getInstallStateText(installState: AddonInstallState) { + switch (installState) { + case AddonInstallState.BackingUp: + return this._translate.instant("COMMON.ADDON_STATUS.BACKINGUP"); + case AddonInstallState.Complete: + return this._translate.instant("COMMON.ADDON_STATE.UPTODATE"); + case AddonInstallState.Downloading: + return this._translate.instant("COMMON.ADDON_STATUS.DOWNLOADING"); + case AddonInstallState.Installing: + return this._translate.instant("COMMON.ADDON_STATUS.INSTALLING"); + case AddonInstallState.Pending: + return this._translate.instant("COMMON.ADDON_STATUS.PENDING"); + default: + return ""; + } + } + + private getButtonOptions(): MatProgressButtonOptions { + return { + active: this.isButtonActive, + disabled: this.isButtonDisabled, + value: this.installProgress, + text: this.buttonText, + mode: "determinate", + buttonColor: "primary", + barColor: "accent", + customClass: "install-button", + raised: false, + flat: true, + stroked: false, + fullWidth: false, + }; + } +} diff --git a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html index fdb4274f..90d6fad9 100644 --- a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html +++ b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html @@ -7,8 +7,7 @@ {{listItem.isAlphaChannel ? 'Alpha': 'Beta'}}
- {{listItem.addon.name}} + {{listItem.addon.name}}
diff --git a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts index 33fdda9d..9d0d9cd1 100644 --- a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts +++ b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts @@ -1,19 +1,22 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { Addon } from 'app/entities/addon'; -import { MyAddonsListItem } from 'app/business-objects/my-addons-list-item'; +import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { Addon } from "app/entities/addon"; +import { AddonViewModel } from "app/business-objects/my-addon-list-item"; @Component({ - selector: 'app-my-addons-addon-cell', - templateUrl: './my-addons-addon-cell.component.html', - styleUrls: ['./my-addons-addon-cell.component.scss'] + selector: "app-my-addons-addon-cell", + templateUrl: "./my-addons-addon-cell.component.html", + styleUrls: ["./my-addons-addon-cell.component.scss"], }) export class MyAddonsAddonCellComponent implements OnInit { + @Input("addon") listItem: AddonViewModel; - @Input('addon') listItem: MyAddonsListItem; + @Output() onViewDetails: EventEmitter = new EventEmitter(); - constructor() { } + constructor() {} - ngOnInit(): void { + ngOnInit(): void {} + + viewDetails() { + this.onViewDetails.emit(this.listItem.addon); } - } diff --git a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts index 5959ad63..92c040c7 100644 --- a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts +++ b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts @@ -1,5 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; +import { GetAddonListItem } from "app/business-objects/get-addon-list-item"; +import { AddonSearchResult } from "app/models/wowup/addon-search-result"; @Component({ selector: "app-potential-addon-table-column", @@ -7,15 +8,15 @@ import { PotentialAddon } from "app/models/wowup/potential-addon"; styleUrls: ["./potential-addon-table-column.component.scss"], }) export class PotentialAddonTableColumnComponent implements OnInit { - @Input("addon") addon: PotentialAddon; + @Input("addon") addon: GetAddonListItem; - @Output() onViewDetails: EventEmitter = new EventEmitter(); + @Output() onViewDetails: EventEmitter = new EventEmitter(); constructor() {} - ngOnInit(): void { } + ngOnInit(): void {} viewDetails() { - this.onViewDetails.emit(this.addon); + this.onViewDetails.emit(this.addon.searchResult); } } diff --git a/wowup-electron/src/app/entities/addon.ts b/wowup-electron/src/app/entities/addon.ts index 5e0c5fae..8e45648f 100644 --- a/wowup-electron/src/app/entities/addon.ts +++ b/wowup-electron/src/app/entities/addon.ts @@ -25,4 +25,7 @@ export interface Addon { patreonFundingLink?: string; githubFundingLink?: string; customFundingLink?: string; + downloadCount?: number; + summary?: string; + screenshotUrls?: string[]; } \ No newline at end of file diff --git a/wowup-electron/src/app/models/wowup/addon-install-state.ts b/wowup-electron/src/app/models/wowup/addon-install-state.ts index 3030eb5c..e76281ba 100644 --- a/wowup-electron/src/app/models/wowup/addon-install-state.ts +++ b/wowup-electron/src/app/models/wowup/addon-install-state.ts @@ -1,7 +1,8 @@ export enum AddonInstallState { - Pending, - Downloading, - BackingUp, - Installing, - Complete -} \ No newline at end of file + Pending, + Downloading, + BackingUp, + Installing, + Complete, + Unknown, +} diff --git a/wowup-electron/src/app/models/wowup/addon-search-result.ts b/wowup-electron/src/app/models/wowup/addon-search-result.ts index 191b466d..8c46b0d8 100644 --- a/wowup-electron/src/app/models/wowup/addon-search-result.ts +++ b/wowup-electron/src/app/models/wowup/addon-search-result.ts @@ -1,11 +1,15 @@ import { AddonSearchResultFile } from "./addon-search-result-file"; export interface AddonSearchResult { - name: string; author: string; - thumbnailUrl: string; + downloadCount?: number; externalId: string; externalUrl: string; + files?: AddonSearchResultFile[]; + name: string; providerName: string; - files: AddonSearchResultFile[]; -} \ No newline at end of file + screenshotUrl?: string; + screenshotUrls?: string[]; + summary?: string; + thumbnailUrl: string; +} diff --git a/wowup-electron/src/app/models/wowup/potential-addon.ts b/wowup-electron/src/app/models/wowup/potential-addon.ts deleted file mode 100644 index fa4f67a6..00000000 --- a/wowup-electron/src/app/models/wowup/potential-addon.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface PotentialAddon { - name: string; - providerName: string; - thumbnailUrl: string; - screenshotUrl?: string; - externalId: string; - externalUrl: string; - author: string; - downloadCount: number; - summary?: string; - screenshotUrls?: string[]; -} diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index 772efd93..2794c71e 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -72,9 +72,7 @@ {{'PAGES.GET_ADDONS.TABLE.STATUS_COLUMN_HEADER' | translate}} - - + diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index 2f4def4e..61d2ef6b 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -5,7 +5,6 @@ import { InstallFromUrlDialogComponent } from "app/components/install-from-url-d import { WowClientType } from "app/models/warcraft/wow-client-type"; import { AddonDetailModel } from "app/models/wowup/addon-detail.model"; import { ColumnState } from "app/models/wowup/column-state"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; import { ElectronService } from "app/services"; import { AddonService } from "app/services/addons/addon.service"; import { SessionService } from "app/services/session/session.service"; @@ -15,6 +14,8 @@ import { map } from "rxjs/operators"; import { MatTableDataSource } from "@angular/material/table"; import { MatSort } from "@angular/material/sort"; import * as _ from "lodash"; +import { GetAddonListItem } from "app/business-objects/get-addon-list-item"; +import { AddonSearchResult } from "app/models/wowup/addon-search-result"; @Component({ selector: "app-get-addons", @@ -26,14 +27,14 @@ export class GetAddonsComponent implements OnInit, OnDestroy { @ViewChild(MatSort) sort: MatSort; - private readonly _displayAddonsSrc = new BehaviorSubject( + private readonly _displayAddonsSrc = new BehaviorSubject( [] ); private readonly _destroyed$ = new Subject(); private subscriptions: Subscription[] = []; private isSelectedTab: boolean = false; - public dataSource = new MatTableDataSource([]); + public dataSource = new MatTableDataSource([]); columns: ColumnState[] = [ { name: "name", display: "Addon", visible: true }, @@ -84,7 +85,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { .subscribe(); const displayAddonSubscription = this._displayAddonsSrc.subscribe( - (items: PotentialAddon[]) => { + (items: GetAddonListItem[]) => { this.dataSource.data = items; this.dataSource.sortingDataAccessor = _.get; this.dataSource.sort = this.sort; @@ -136,16 +137,16 @@ export class GetAddonsComponent implements OnInit, OnDestroy { this.selectedClient ); - searchResults = this.filterInstalledAddons(searchResults); - this.formatAddons(searchResults); - this._displayAddonsSrc.next(searchResults); + this._displayAddonsSrc.next( + this.formatAddons(this.filterInstalledAddons(searchResults)) + ); this.isBusy = false; this.setPageContextText(); } - openDetailDialog(addon: PotentialAddon) { + openDetailDialog(addon: AddonSearchResult) { const dialogRef = this._dialog.open(AddonDetailComponent, { - data: new AddonDetailModel(addon), + data: addon, }); dialogRef.afterClosed().subscribe(); @@ -160,9 +161,8 @@ export class GetAddonsComponent implements OnInit, OnDestroy { this._addonService.getFeaturedAddons(clientType).subscribe({ next: (addons) => { - addons = this.filterInstalledAddons(addons); - this.formatAddons(addons); - this._displayAddonsSrc.next(addons); + const listItems = this.formatAddons(this.filterInstalledAddons(addons)); + this._displayAddonsSrc.next(listItems); this.isBusy = false; }, error: (err) => { @@ -171,7 +171,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { }); } - private filterInstalledAddons(addons: PotentialAddon[]) { + private filterInstalledAddons(addons: AddonSearchResult[]) { return addons.filter( (addon) => !this._addonService.isInstalled( @@ -181,12 +181,14 @@ export class GetAddonsComponent implements OnInit, OnDestroy { ); } - private formatAddons(addons: PotentialAddon[]) { + private formatAddons(addons: AddonSearchResult[]): GetAddonListItem[] { addons.forEach((addon) => { if (!addon.thumbnailUrl) { addon.thumbnailUrl = "assets/wowup_logo_512np.png"; } }); + + return addons.map((addon) => new GetAddonListItem(addon)); } private setPageContextText() { diff --git a/wowup-electron/src/app/pages/home/home.module.ts b/wowup-electron/src/app/pages/home/home.module.ts index a0dd5ede..49c18e78 100644 --- a/wowup-electron/src/app/pages/home/home.module.ts +++ b/wowup-electron/src/app/pages/home/home.module.ts @@ -24,6 +24,8 @@ import { AddonDetailComponent } from "app/components/addon-detail/addon-detail.c import { AddonProviderBadgeComponent } from "app/components/addon-provider-badge/addon-provider-badge.component"; import { MatProgressButtonsModule } from "mat-progress-buttons"; import { AddonInstallButtonComponent } from "app/components/addon-install-button/addon-install-button.component"; +import { GetAddonStatusColumnComponent } from "app/components/get-addon-status-column/get-addon-status-column.component"; +import { MyAddonStatusColumnComponent } from "app/components/my-addon-status-column/my-addon-status-column.component"; @NgModule({ declarations: [ @@ -44,6 +46,8 @@ import { AddonInstallButtonComponent } from "app/components/addon-install-button AddonDetailComponent, AddonProviderBadgeComponent, AddonInstallButtonComponent, + GetAddonStatusColumnComponent, + MyAddonStatusColumnComponent, ], imports: [ CommonModule, diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index dd9ccdc1..a0c87579 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -56,15 +56,18 @@ {{'PAGES.MY_ADDONS.TABLE.ADDON_COLUMN_HEADER' | translate}} - + + {{'PAGES.MY_ADDONS.TABLE.STATUS_COLUMN_HEADER' | translate}} - -
+ + + + @@ -137,7 +140,7 @@ + (dblclick)="openDetailDialog(row)" (contextmenu)="onCellContext($event, row)">
diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index 86e7db71..dbb8d853 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -17,7 +17,7 @@ import { SessionService } from "app/services/session/session.service"; import { Overlay, OverlayRef } from "@angular/cdk/overlay"; import { ColumnState } from "app/models/wowup/column-state"; import { MatCheckboxChange } from "@angular/material/checkbox"; -import { MyAddonsListItem } from "app/business-objects/my-addons-list-item"; +import { AddonViewModel } from "app/business-objects/my-addon-list-item"; import * as _ from "lodash"; import { ElectronService } from "app/services"; import { AddonDisplayState } from "app/models/wowup/addon-display-state"; @@ -30,6 +30,8 @@ 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"; +import { GetAddonListItem } from "app/business-objects/get-addon-list-item"; +import { AddonDetailComponent } from "app/components/addon-detail/addon-detail.component"; @Component({ selector: "app-my-addons", @@ -45,7 +47,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { updateAllContextMenu: MatMenuTrigger; @ViewChild(MatSort) sort: MatSort; - private readonly _displayAddonsSrc = new BehaviorSubject( + private readonly _displayAddonsSrc = new BehaviorSubject( [] ); private readonly _destroyed$ = new Subject(); @@ -57,7 +59,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { contextMenuPosition = { x: "0px", y: "0px" }; - public dataSource = new MatTableDataSource([]); + public dataSource = new MatTableDataSource([]); public filter = ""; columns: ColumnState[] = [ @@ -118,7 +120,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { const addonInstalledSubscription = this.addonService.addonInstalled$.subscribe( (evt) => { - let listItems: MyAddonsListItem[] = [].concat( + let listItems: AddonViewModel[] = [].concat( this._displayAddonsSrc.value ); @@ -136,7 +138,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy { if (listItemIdx === -1) { listItems.push(listItem); } else { - listItems[listItemIdx] = listItem; + return; + // listItems[listItemIdx] = listItem; } listItems = this.sortListItems(listItems); @@ -149,7 +152,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { const addonRemovedSubscription = this.addonService.addonRemoved$.subscribe( (addonId) => { - const addons: MyAddonsListItem[] = [].concat( + const addons: AddonViewModel[] = [].concat( this._displayAddonsSrc.value ); const listItemIdx = addons.findIndex((li) => li.addon.id === addonId); @@ -162,11 +165,11 @@ export class MyAddonsComponent implements OnInit, OnDestroy { ); const displayAddonSubscription = this._displayAddonsSrc.subscribe( - (items: MyAddonsListItem[]) => { + (items: AddonViewModel[]) => { this.dataSource.data = items; this.dataSource.sortingDataAccessor = _.get; this.dataSource.filterPredicate = ( - item: MyAddonsListItem, + item: AddonViewModel, filter: string ) => { if ( @@ -212,7 +215,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { this.loadAddons(this.selectedClient); } - onRowClicked(event: MouseEvent, row: MyAddonsListItem, index: number) { + onRowClicked(event: MouseEvent, row: AddonViewModel, index: number) { console.log(row.displayState); console.log("index clicked: " + index); @@ -221,7 +224,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { return; } - let listItems: MyAddonsListItem[] = [].concat(this._displayAddonsSrc.value); + let listItems: AddonViewModel[] = [].concat(this._displayAddonsSrc.value); if (event.shiftKey) { const startIdx = listItems.findIndex((item) => item.selected); @@ -247,6 +250,14 @@ export class MyAddonsComponent implements OnInit, OnDestroy { }); } + openDetailDialog(addon: AddonViewModel) { + const dialogRef = this._dialog.open(AddonDetailComponent, { + data: addon, + }); + + dialogRef.afterClosed().subscribe(); + } + filterAddons(): void { this.dataSource.filter = this.filter.trim().toLowerCase(); } @@ -304,7 +315,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { this.columnContextMenu.openMenu(); } - onCellContext(event: MouseEvent, listItem: MyAddonsListItem) { + onCellContext(event: MouseEvent, listItem: AddonViewModel) { event.preventDefault(); this.updateContextMenuPosition(event); this.contextMenu.menuData = { listItem: listItem }; @@ -335,7 +346,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { } } - onUpdateAddon(listItem: MyAddonsListItem) { + onUpdateAddon(listItem: AddonViewModel) { listItem.isInstalling = true; this.addonService.installAddon(listItem.addon.id); @@ -388,7 +399,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { onInstall() {} - onClickIgnoreAddon(evt: MatCheckboxChange, listItem: MyAddonsListItem) { + onClickIgnoreAddon(evt: MatCheckboxChange, listItem: AddonViewModel) { listItem.addon.isIgnored = evt.checked; listItem.statusText = listItem.getStateText(); this.addonService.saveAddon(listItem.addon); @@ -418,7 +429,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy { // Only care about the ones that need to be updated/installed addons = addons - .map((addon) => new MyAddonsListItem(addon)) + .map((addon) => new AddonViewModel(addon)) .filter((listItem) => listItem.needsUpdate || listItem.needsInstall) .map((listItem) => listItem.addon); @@ -468,18 +479,18 @@ export class MyAddonsComponent implements OnInit, OnDestroy { }); } - private formatAddons(addons: Addon[]): MyAddonsListItem[] { + private formatAddons(addons: Addon[]): AddonViewModel[] { const listItems = addons.map((addon) => this.createAddonListItem(addon)); return this.sortListItems(listItems); } - private sortListItems(listItems: MyAddonsListItem[]) { + private sortListItems(listItems: AddonViewModel[]) { return _.orderBy(listItems, ["displayState", "addon.name"]); } private createAddonListItem(addon: Addon) { - const listItem = new MyAddonsListItem(addon); + const listItem = new AddonViewModel(addon); if (!listItem.addon.thumbnailUrl) { listItem.addon.thumbnailUrl = "assets/wowup_logo_512np.png"; diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index d2bea129..d0da138f 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -9,7 +9,6 @@ import { v4 as uuidv4 } from "uuid"; import * as path from "path"; import * as fs from "fs"; import { WowClientType } from "app/models/warcraft/wow-client-type"; -import { PotentialAddon } from "app/models/wowup/potential-addon"; import { AddonFolder } from "app/models/wowup/addon-folder"; import { AddonChannelType } from "app/models/wowup/addon-channel-type"; import { AddonSearchResult } from "app/models/wowup/addon-search-result"; @@ -61,7 +60,7 @@ export class AddonService { public async search( query: string, clientType: WowClientType - ): Promise { + ): Promise { var searchTasks = this._addonProviders.map((p) => p.searchByQuery(query, clientType) ); @@ -79,7 +78,7 @@ export class AddonService { } public async installPotentialAddon( - potentialAddon: PotentialAddon, + potentialAddon: AddonSearchResult, clientType: WowClientType, onUpdate: ( installState: AddonInstallState, @@ -559,7 +558,7 @@ export class AddonService { public getFeaturedAddons( clientType: WowClientType - ): Observable { + ): Observable { return forkJoin( this._addonProviders.map((p) => p.getFeaturedAddons(clientType)) ).pipe( diff --git a/wowup-electron/src/assets/i18n/de.json b/wowup-electron/src/assets/i18n/de.json index fb48d421..95238aff 100644 --- a/wowup-electron/src/assets/i18n/de.json +++ b/wowup-electron/src/assets/i18n/de.json @@ -119,5 +119,21 @@ "POSITIVE_BUTTON": "Sicher!", "TITLE": "WowUp Telemetrie" } + }, + "COMMON": { + "ADDON_STATUS": { + "COMPLETE": "COMPLETE", + "DOWNLOADING": "DOWNLOADING", + "INSTALLING": "INSTALLING", + "UNINSTALLING": "UNINSTALLING", + "UPDATING": "UPDATING" + }, + "ADDON_STATE": { + "UNINSTALL": "UNINSTALL", + "IGNORED": "IGNORED", + "UPDATE": "UPDATE", + "INSTALL": "INSTALL", + "UPTODATE": "UPTODATE" + } } } diff --git a/wowup-electron/src/assets/i18n/en.json b/wowup-electron/src/assets/i18n/en.json index 685d8b46..07aa2983 100644 --- a/wowup-electron/src/assets/i18n/en.json +++ b/wowup-electron/src/assets/i18n/en.json @@ -120,5 +120,23 @@ "POSITIVE_BUTTON": "Sure!", "TITLE": "WowUp Telemetry" } + }, + "COMMON": { + "ADDON_STATUS": { + "BACKINGUP": "Backing Up", + "COMPLETE": "Installed", + "DOWNLOADING": "Downloading", + "INSTALLING": "Installing", + "PENDING": "Pending", + "UNINSTALLING": "Uninstalling", + "UPDATING": "Updating..." + }, + "ADDON_STATE": { + "UNINSTALL": "Uninstall", + "IGNORED": "Ignored", + "UPDATE": "Update", + "INSTALL": "Install", + "UPTODATE": "Up to date" + } } } diff --git a/wowup-electron/src/assets/i18n/es.json b/wowup-electron/src/assets/i18n/es.json index 47f03cfc..e9b1a029 100644 --- a/wowup-electron/src/assets/i18n/es.json +++ b/wowup-electron/src/assets/i18n/es.json @@ -119,5 +119,21 @@ "POSITIVE_BUTTON": "¡Seguro!", "TITLE": "Telemetría WowUp" } + }, + "COMMON": { + "ADDON_STATUS": { + "COMPLETE": "COMPLETE", + "DOWNLOADING": "DOWNLOADING", + "INSTALLING": "INSTALLING", + "UNINSTALLING": "UNINSTALLING", + "UPDATING": "UPDATING" + }, + "ADDON_STATE": { + "UNINSTALL": "UNINSTALL", + "IGNORED": "IGNORED", + "UPDATE": "UPDATE", + "INSTALL": "INSTALL", + "UPTODATE": "UPTODATE" + } } } diff --git a/wowup-electron/src/assets/i18n/fr.json b/wowup-electron/src/assets/i18n/fr.json index 6c4b0a94..d62cc16d 100644 --- a/wowup-electron/src/assets/i18n/fr.json +++ b/wowup-electron/src/assets/i18n/fr.json @@ -119,5 +119,21 @@ "POSITIVE_BUTTON": "Bien sûr!", "TITLE": "Télémétrie WowUp" } + }, + "COMMON": { + "ADDON_STATUS": { + "COMPLETE": "COMPLETE", + "DOWNLOADING": "DOWNLOADING", + "INSTALLING": "INSTALLING", + "UNINSTALLING": "UNINSTALLING", + "UPDATING": "UPDATING" + }, + "ADDON_STATE": { + "UNINSTALL": "UNINSTALL", + "IGNORED": "IGNORED", + "UPDATE": "UPDATE", + "INSTALL": "INSTALL", + "UPTODATE": "UPTODATE" + } } } diff --git a/wowup-electron/src/assets/i18n/it.json b/wowup-electron/src/assets/i18n/it.json index c525f452..b4a1eece 100644 --- a/wowup-electron/src/assets/i18n/it.json +++ b/wowup-electron/src/assets/i18n/it.json @@ -119,5 +119,21 @@ "POSITIVE_BUTTON": "Certo!", "TITLE": "Telemetria WowUp" } + }, + "COMMON": { + "ADDON_STATUS": { + "COMPLETE": "COMPLETE", + "DOWNLOADING": "DOWNLOADING", + "INSTALLING": "INSTALLING", + "UNINSTALLING": "UNINSTALLING", + "UPDATING": "UPDATING" + }, + "ADDON_STATE": { + "UNINSTALL": "UNINSTALL", + "IGNORED": "IGNORED", + "UPDATE": "UPDATE", + "INSTALL": "INSTALL", + "UPTODATE": "UPTODATE" + } } } diff --git a/wowup-electron/src/assets/i18n/pt.json b/wowup-electron/src/assets/i18n/pt.json index ef0a484c..ecce56ed 100644 --- a/wowup-electron/src/assets/i18n/pt.json +++ b/wowup-electron/src/assets/i18n/pt.json @@ -119,6 +119,22 @@ "POSITIVE_BUTTON": "Claro!", "TITLE": "Telemetria do WowUp" } + }, + "COMMON": { + "ADDON_STATUS": { + "COMPLETE": "COMPLETE", + "DOWNLOADING": "DOWNLOADING", + "INSTALLING": "INSTALLING", + "UNINSTALLING": "UNINSTALLING", + "UPDATING": "UPDATING" + }, + "ADDON_STATE": { + "UNINSTALL": "UNINSTALL", + "IGNORED": "IGNORED", + "UPDATE": "UPDATE", + "INSTALL": "INSTALL", + "UPTODATE": "UPTODATE" + } } } \ No newline at end of file diff --git a/wowup-electron/src/assets/i18n/ru.json b/wowup-electron/src/assets/i18n/ru.json index 8b659bf7..6379d584 100644 --- a/wowup-electron/src/assets/i18n/ru.json +++ b/wowup-electron/src/assets/i18n/ru.json @@ -120,5 +120,21 @@ "POSITIVE_BUTTON": "Конечно!", "TITLE": "Телеметрия WowUp" } + }, + "COMMON": { + "ADDON_STATUS": { + "COMPLETE": "COMPLETE", + "DOWNLOADING": "DOWNLOADING", + "INSTALLING": "INSTALLING", + "UNINSTALLING": "UNINSTALLING", + "UPDATING": "UPDATING" + }, + "ADDON_STATE": { + "UNINSTALL": "UNINSTALL", + "IGNORED": "IGNORED", + "UPDATE": "UPDATE", + "INSTALL": "INSTALL", + "UPTODATE": "UPTODATE" + } } } diff --git a/wowup-electron/src/assets/i18n/zh.json b/wowup-electron/src/assets/i18n/zh.json index 55d11d02..82858303 100644 --- a/wowup-electron/src/assets/i18n/zh.json +++ b/wowup-electron/src/assets/i18n/zh.json @@ -119,5 +119,21 @@ "POSITIVE_BUTTON": "当然!", "TITLE": "WowUp遥测" } + }, + "COMMON": { + "ADDON_STATUS": { + "COMPLETE": "COMPLETE", + "DOWNLOADING": "DOWNLOADING", + "INSTALLING": "INSTALLING", + "UNINSTALLING": "UNINSTALLING", + "UPDATING": "UPDATING" + }, + "ADDON_STATE": { + "UNINSTALL": "UNINSTALL", + "IGNORED": "IGNORED", + "UPDATE": "UPDATE", + "INSTALL": "INSTALL", + "UPTODATE": "UPTODATE" + } } } diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index abc40ec0..b6f9e137 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -307,5 +307,5 @@ img { } .install-button { - min-width: 110px !important; + min-width: 120px !important; } From 9d4768c87eec9b55dc63247ec46448fcd32bc70c Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 16:12:19 -0500 Subject: [PATCH 28/34] stuff? --- .../addon-providers/curse-addon-provider.ts | 24 ------------------- .../business-objects/get-addon-list-item.ts | 7 ++++++ ...otential-addon-table-column.component.html | 2 +- .../potential-addon-table-column.component.ts | 16 ++++++++++++- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index 762d797f..a9c27542 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -406,30 +406,6 @@ export class CurseAddonProvider implements AddonProvider { throw new Error("Method not implemented."); } - // private getPotentialAddon(result: CurseSearchResult, clientType: WowClientType, channelType: AddonChannelType): PotentialAddon { - // const clientTypeStr = this.getGameVersionFlavor(clientType); - // let latestFile = _.orderBy(result.latestFiles, 'id', 'desc') - // .find(file => - // file.gameVersionFlavor === clientTypeStr && - // this.getChannelType(file.releaseType) === channelType - // ); - // if (!latestFile) { - // latestFile = _.first(result.latestFiles); - // } - - // return { - // author: this.getAuthor(result), - // downloadCount: result.downloadCount, - // externalId: result.id.toString(), - // externalUrl: result.websiteUrl, - // name: result.name, - // providerName: this.name, - // thumbnailUrl: this.getThumbnailUrl(result), - // summary: result.summary, - // screenshotUrls: this.getScreenshotUrls(result), - // }; - // } - private getAddonSearchResult( result: CurseSearchResult, latestFiles: CurseFile[] = [] diff --git a/wowup-electron/src/app/business-objects/get-addon-list-item.ts b/wowup-electron/src/app/business-objects/get-addon-list-item.ts index e5fb298f..c4d92f98 100644 --- a/wowup-electron/src/app/business-objects/get-addon-list-item.ts +++ b/wowup-electron/src/app/business-objects/get-addon-list-item.ts @@ -1,4 +1,7 @@ +import { AddonChannelType } from "app/models/wowup/addon-channel-type"; import { AddonSearchResult } from "app/models/wowup/addon-search-result"; +import { AddonSearchResultFile } from "app/models/wowup/addon-search-result-file"; +import * as _ from "lodash"; export class GetAddonListItem { public readonly searchResult: AddonSearchResult; @@ -26,4 +29,8 @@ export class GetAddonListItem { constructor(searchResult: AddonSearchResult) { this.searchResult = searchResult; } + + public getLatestFile(channel: AddonChannelType): AddonSearchResultFile { + return _.find(this.searchResult.files, (f) => f.channelType <= channel); + } } diff --git a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html index f12920d0..22bf1424 100644 --- a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html +++ b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.html @@ -3,6 +3,6 @@
{{addon.name}} -
{{addon?.version}}
+
{{addonVersion}}
diff --git a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts index 92c040c7..5bfee5a0 100644 --- a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts +++ b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts @@ -1,6 +1,9 @@ import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { GetAddonListItem } from "app/business-objects/get-addon-list-item"; +import { AddonChannelType } from "app/models/wowup/addon-channel-type"; import { AddonSearchResult } from "app/models/wowup/addon-search-result"; +import { SessionService } from "app/services/session/session.service"; +import { WowUpService } from "app/services/wowup/wowup.service"; @Component({ selector: "app-potential-addon-table-column", @@ -12,7 +15,18 @@ export class PotentialAddonTableColumnComponent implements OnInit { @Output() onViewDetails: EventEmitter = new EventEmitter(); - constructor() {} + public get addonVersion() { + const defaultChannel = this._wowupService.getDefaultAddonChannel( + this._sessionService.selectedClientType + ); + const latestFile = this.addon.getLatestFile(defaultChannel); + return latestFile?.version; + } + + constructor( + private _sessionService: SessionService, + private _wowupService: WowUpService + ) {} ngOnInit(): void {} From e4214f31e9f9ee1b580f48d79d29877845ad051b Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 16:40:35 -0500 Subject: [PATCH 29/34] Less loops --- .../potential-addon-table-column.component.ts | 34 ++- .../get-addons/get-addons.component.html | 2 +- .../pages/get-addons/get-addons.component.ts | 16 +- .../src/app/pages/home/home.component.html | 2 +- .../src/app/services/wowup/wowup.service.ts | 222 +++++++++++------- 5 files changed, 180 insertions(+), 96 deletions(-) diff --git a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts index 5bfee5a0..f60bc75c 100644 --- a/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts +++ b/wowup-electron/src/app/components/potential-addon-table-column/potential-addon-table-column.component.ts @@ -1,4 +1,12 @@ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; import { GetAddonListItem } from "app/business-objects/get-addon-list-item"; import { AddonChannelType } from "app/models/wowup/addon-channel-type"; import { AddonSearchResult } from "app/models/wowup/addon-search-result"; @@ -10,25 +18,31 @@ import { WowUpService } from "app/services/wowup/wowup.service"; templateUrl: "./potential-addon-table-column.component.html", styleUrls: ["./potential-addon-table-column.component.scss"], }) -export class PotentialAddonTableColumnComponent implements OnInit { +export class PotentialAddonTableColumnComponent implements OnInit, OnChanges { @Input("addon") addon: GetAddonListItem; + @Input() channel: AddonChannelType; @Output() onViewDetails: EventEmitter = new EventEmitter(); - public get addonVersion() { - const defaultChannel = this._wowupService.getDefaultAddonChannel( - this._sessionService.selectedClientType - ); - const latestFile = this.addon.getLatestFile(defaultChannel); - return latestFile?.version; - } + public addonVersion: string = ""; constructor( private _sessionService: SessionService, private _wowupService: WowUpService ) {} - ngOnInit(): void {} + ngOnChanges(changes: SimpleChanges): void { + if (changes.channel) { + const latestFile = this.addon.getLatestFile(this.channel); + this.addonVersion = latestFile?.version; + } + } + + ngOnInit(): void { + // this._defaultChannel = this._wowupService.getDefaultAddonChannel( + // this._sessionService.selectedClientType + // ); + } viewDetails() { this.onViewDetails.emit(this.addon.searchResult); diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.html b/wowup-electron/src/app/pages/get-addons/get-addons.component.html index cfb6bb39..3a432240 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.html +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.html @@ -45,7 +45,7 @@ {{'PAGES.GET_ADDONS.TABLE.ADDON_COLUMN_HEADER' | translate}} - + diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index 8acc77dc..7be86c29 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -51,6 +51,18 @@ export class GetAddonsComponent implements OnInit, OnDestroy { return this.columns.filter((col) => col.visible).map((col) => col.name); } + public get defaultAddonChannelKey() { + return this._wowUpService.getClientDefaultAddonChannelKey( + this._sessionService.selectedClientType + ); + } + + public get defaultAddonChannel() { + return this._wowUpService.getDefaultAddonChannel( + this._sessionService.selectedClientType + ); + } + public query = ""; public isBusy = false; public selectedClient = WowClientType.None; @@ -98,7 +110,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { ); const channelTypeSubscription = this._wowUpService.preferenceChange$ - .pipe(filter((change) => change.key === this.channelTypeKey)) + .pipe(filter((change) => change.key === this.defaultAddonChannelKey)) .subscribe((change) => { this.onSearch(); }); @@ -173,7 +185,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy { this._addonService.getFeaturedAddons(clientType).subscribe({ next: (addons) => { - console.log('FEAT', addons) + console.log("FEAT", addons); const listItems = this.formatAddons(this.filterInstalledAddons(addons)); this._displayAddonsSrc.next(listItems); this.isBusy = false; diff --git a/wowup-electron/src/app/pages/home/home.component.html b/wowup-electron/src/app/pages/home/home.component.html index 898307f4..c824000e 100644 --- a/wowup-electron/src/app/pages/home/home.component.html +++ b/wowup-electron/src/app/pages/home/home.component.html @@ -1,6 +1,6 @@
- diff --git a/wowup-electron/src/app/services/wowup/wowup.service.ts b/wowup-electron/src/app/services/wowup/wowup.service.ts index 57a6e2b5..790363e6 100644 --- a/wowup-electron/src/app/services/wowup/wowup.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup.service.ts @@ -1,9 +1,9 @@ import { Injectable } from "@angular/core"; import { CachingService } from "../caching/caching-service"; import { remote } from "electron"; -import { join } from 'path'; -import { existsSync } from 'fs'; -import { v4 as uuidv4 } from 'uuid'; +import { join } from "path"; +import { existsSync } from "fs"; +import { v4 as uuidv4 } from "uuid"; import { PreferenceStorageService } from "../storage/preference-storage.service"; import { WowUpReleaseChannelType } from "app/models/wowup/wowup-release-channel-type"; import { getEnumList, getEnumName } from "app/utils/enum.utils"; @@ -15,7 +15,7 @@ import { from, Observable, of, Subject } from "rxjs"; import { LatestVersionResponse } from "app/models/wowup-api/latest-version-response"; import { map, switchMap } from "rxjs/operators"; import { LatestVersion } from "app/models/wowup-api/latest-version"; -import * as compareVersions from 'compare-versions'; +import * as compareVersions from "compare-versions"; import { DownloadSevice } from "../download/download.service"; import { PreferenceChange } from "app/models/wowup/preference-change"; import { FileService } from "../files/file.service"; @@ -24,23 +24,32 @@ import { defaultAutoUpdateKeySuffix, defaultChannelKeySuffix, lastSelectedWowClientTypeKey, - wowupReleaseChannelKey + wowupReleaseChannelKey, } from "../../../constants"; -const LATEST_VERSION_CACHE_KEY = 'latest-version-response'; +const LATEST_VERSION_CACHE_KEY = "latest-version-response"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class WowUpService { - private readonly _preferenceChangeSrc = new Subject(); - public readonly updaterName = 'WowUpUpdater.exe'; - public readonly applicationFolderPath: string = remote.app.getPath('userData'); - public readonly applicationLogsFolderPath: string = remote.app.getPath('logs'); - public readonly applicationDownloadsFolderPath: string = join(this.applicationFolderPath, 'downloads'); - public readonly applicationUpdaterPath: string = join(this.applicationFolderPath, this.updaterName); + public readonly updaterName = "WowUpUpdater.exe"; + public readonly applicationFolderPath: string = remote.app.getPath( + "userData" + ); + public readonly applicationLogsFolderPath: string = remote.app.getPath( + "logs" + ); + public readonly applicationDownloadsFolderPath: string = join( + this.applicationFolderPath, + "downloads" + ); + public readonly applicationUpdaterPath: string = join( + this.applicationFolderPath, + this.updaterName + ); public readonly applicationVersion: string; public readonly isBetaBuild: boolean; public readonly preferenceChange$ = this._preferenceChangeSrc.asObservable(); @@ -56,7 +65,8 @@ export class WowUpService { this.setDefaultPreferences(); this.applicationVersion = _electronService.remote.app.getVersion(); - this.isBetaBuild = this.applicationVersion.toLowerCase().indexOf('beta') != -1; + this.isBetaBuild = + this.applicationVersion.toLowerCase().indexOf("beta") != -1; } public get updaterExists() { @@ -64,18 +74,22 @@ export class WowUpService { } public get collapseToTray() { - const preference = this._preferenceStorageService.findByKey(collapseToTrayKey); - return preference === 'true'; + const preference = this._preferenceStorageService.findByKey( + collapseToTrayKey + ); + return preference === "true"; } public set collapseToTray(value: boolean) { const key = collapseToTrayKey; this._preferenceStorageService.set(key, value); - this._preferenceChangeSrc.next({ key, value: value.toString() }) + this._preferenceChangeSrc.next({ key, value: value.toString() }); } public get wowUpReleaseChannel() { - const preference = this._preferenceStorageService.findByKey(wowupReleaseChannelKey); + const preference = this._preferenceStorageService.findByKey( + wowupReleaseChannelKey + ); return parseInt(preference, 10) as WowUpReleaseChannelType; } @@ -84,15 +98,23 @@ export class WowUpService { } public get lastSelectedClientType(): WowClientType { - const preference = this._preferenceStorageService.findByKey(lastSelectedWowClientTypeKey); + const preference = this._preferenceStorageService.findByKey( + lastSelectedWowClientTypeKey + ); const value = parseInt(preference, 10); - return isNaN(value) - ? WowClientType.None - : value as WowClientType; + return isNaN(value) ? WowClientType.None : (value as WowClientType); } public set lastSelectedClientType(clientType: WowClientType) { - this._preferenceStorageService.set(lastSelectedWowClientTypeKey, clientType); + this._preferenceStorageService.set( + lastSelectedWowClientTypeKey, + clientType + ); + } + + public getClientDefaultAddonChannelKey(clientType: WowClientType) { + const typeName = getEnumName(WowClientType, clientType); + return `${typeName}${defaultChannelKeySuffix}`.toLowerCase(); } public getDefaultAddonChannel(clientType: WowClientType): AddonChannelType { @@ -101,10 +123,13 @@ export class WowUpService { return parseInt(preference, 10) as AddonChannelType; } - public setDefaultAddonChannel(clientType: WowClientType, channelType: AddonChannelType) { + public setDefaultAddonChannel( + clientType: WowClientType, + channelType: AddonChannelType + ) { const key = this.getClientDefaultAddonChannelKey(clientType); this._preferenceStorageService.set(key, channelType); - this._preferenceChangeSrc.next({ key, value: channelType.toString() }) + this._preferenceChangeSrc.next({ key, value: channelType.toString() }); } public getDefaultAutoUpdate(clientType: WowClientType): boolean { @@ -125,51 +150,68 @@ export class WowUpService { public isUpdateAvailable(): Observable { const releaseChannel = this.wowUpReleaseChannel; - return this.getLatestWowUpVersion(releaseChannel) - .pipe( - map(response => { - if (!response?.version) { - console.error("Got empty WowUp version"); - return false; - } + return this.getLatestWowUpVersion(releaseChannel).pipe( + map((response) => { + if (!response?.version) { + console.error("Got empty WowUp version"); + return false; + } - if (this.isBetaBuild && releaseChannel != WowUpReleaseChannelType.Beta) { - return true; - } + if ( + this.isBetaBuild && + releaseChannel != WowUpReleaseChannelType.Beta + ) { + return true; + } - return compareVersions(response.version, this._electronService.remote.app.getVersion()) > 0; - }) - ); + return ( + compareVersions( + response.version, + this._electronService.remote.app.getVersion() + ) > 0 + ); + }) + ); } - public getLatestWowUpVersion(channel: WowUpReleaseChannelType): Observable { - const cachedResponse = this._cacheService.get(LATEST_VERSION_CACHE_KEY); + public getLatestWowUpVersion( + channel: WowUpReleaseChannelType + ): Observable { + const cachedResponse = this._cacheService.get( + LATEST_VERSION_CACHE_KEY + ); if (cachedResponse) { - return of(channel === WowUpReleaseChannelType.Beta ? cachedResponse.beta : cachedResponse.stable); - } - return this._wowUpApiService.getLatestVersion() - .pipe( - map(response => { - this._cacheService.set(LATEST_VERSION_CACHE_KEY, response); - return channel === WowUpReleaseChannelType.Beta ? response.beta : response.stable; - }) + return of( + channel === WowUpReleaseChannelType.Beta + ? cachedResponse.beta + : cachedResponse.stable ); + } + return this._wowUpApiService.getLatestVersion().pipe( + map((response) => { + this._cacheService.set(LATEST_VERSION_CACHE_KEY, response); + return channel === WowUpReleaseChannelType.Beta + ? response.beta + : response.stable; + }) + ); } public getLatestUpdaterVersion() { - return this._wowUpApiService.getLatestVersion() - .pipe( - map(response => { - return response.updater; - }) - ); + return this._wowUpApiService.getLatestVersion().pipe( + map((response) => { + return response.updater; + }) + ); } public installUpdate() { // TODO } - public checkUpdaterApp(onProgress?: (progress: number) => void): Observable { + public checkUpdaterApp( + onProgress?: (progress: number) => void + ): Observable { if (this.updaterExists) { return of(undefined); } else { @@ -177,23 +219,37 @@ export class WowUpService { } } - private installUpdater(onProgress?: (progress: number) => void): Observable { - return this.getLatestUpdaterVersion() - .pipe( - switchMap(response => from(this._downloadService.downloadZipFile(response.url, this.applicationDownloadsFolderPath, onProgress))), - switchMap(downloadedPath => { - const unzipPath = join(this.applicationDownloadsFolderPath, uuidv4()); - return from(this._downloadService.unzipFile(downloadedPath, unzipPath)); - }), - switchMap(unzippedDir => { - console.log(unzippedDir); - const newUpdaterPath = join(unzippedDir, this.updaterName); - return from(this._downloadService.copyFile(newUpdaterPath, this.applicationUpdaterPath)); - }), - map(() => { - console.log('DOWNLOAD COMPLETE') - }) - ) + private installUpdater( + onProgress?: (progress: number) => void + ): Observable { + return this.getLatestUpdaterVersion().pipe( + switchMap((response) => + from( + this._downloadService.downloadZipFile( + response.url, + this.applicationDownloadsFolderPath, + onProgress + ) + ) + ), + switchMap((downloadedPath) => { + const unzipPath = join(this.applicationDownloadsFolderPath, uuidv4()); + return from(this._downloadService.unzipFile(downloadedPath, unzipPath)); + }), + switchMap((unzippedDir) => { + console.log(unzippedDir); + const newUpdaterPath = join(unzippedDir, this.updaterName); + return from( + this._downloadService.copyFile( + newUpdaterPath, + this.applicationUpdaterPath + ) + ); + }), + map(() => { + console.log("DOWNLOAD COMPLETE"); + }) + ); } private setDefaultPreference(key: string, defaultValue: any) { @@ -203,11 +259,6 @@ export class WowUpService { } } - private getClientDefaultAddonChannelKey(clientType: WowClientType) { - const typeName = getEnumName(WowClientType, clientType); - return `${typeName}${defaultChannelKeySuffix}`.toLowerCase(); - } - private getClientDefaultAutoUpdateKey(clientType: WowClientType): string { const typeName = getEnumName(WowClientType, clientType); return `${typeName}${defaultAutoUpdateKeySuffix}`.toLowerCase(); @@ -215,13 +266,18 @@ export class WowUpService { private setDefaultPreferences() { this.setDefaultPreference(collapseToTrayKey, true); - this.setDefaultPreference(wowupReleaseChannelKey, this.getDefaultReleaseChannel()); + this.setDefaultPreference( + wowupReleaseChannelKey, + this.getDefaultReleaseChannel() + ); this.setDefaultClientPreferences(); } private setDefaultClientPreferences() { - const keys = getEnumList(WowClientType).filter(key => key !== WowClientType.None); - keys.forEach(key => { + const keys = getEnumList(WowClientType).filter( + (key) => key !== WowClientType.None + ); + keys.forEach((key) => { const preferenceKey = this.getClientDefaultAddonChannelKey(key); this.setDefaultPreference(preferenceKey, AddonChannelType.Stable); @@ -231,6 +287,8 @@ export class WowUpService { } private getDefaultReleaseChannel() { - return this.isBetaBuild ? WowUpReleaseChannelType.Beta : WowUpReleaseChannelType.Stable; + return this.isBetaBuild + ? WowUpReleaseChannelType.Beta + : WowUpReleaseChannelType.Stable; } -} \ No newline at end of file +} From 49d580666541bed3c6666abe762897b28c670fb9 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 16:59:44 -0500 Subject: [PATCH 30/34] remove log --- wowup-electron/src/app/pages/get-addons/get-addons.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts index 7be86c29..ee4efbee 100644 --- a/wowup-electron/src/app/pages/get-addons/get-addons.component.ts +++ b/wowup-electron/src/app/pages/get-addons/get-addons.component.ts @@ -185,7 +185,6 @@ export class GetAddonsComponent implements OnInit, OnDestroy { this._addonService.getFeaturedAddons(clientType).subscribe({ next: (addons) => { - console.log("FEAT", addons); const listItems = this.formatAddons(this.filterInstalledAddons(addons)); this._displayAddonsSrc.next(listItems); this.isBusy = false; From c69f804c41145a9a7dc8edba7b902445043ba2f0 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 17:14:57 -0500 Subject: [PATCH 31/34] fix detail popup issue --- .../my-addons-addon-cell/my-addons-addon-cell.component.ts | 4 ++-- wowup-electron/src/app/pages/my-addons/my-addons.component.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts index 9d0d9cd1..159fe577 100644 --- a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts +++ b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.ts @@ -10,13 +10,13 @@ import { AddonViewModel } from "app/business-objects/my-addon-list-item"; export class MyAddonsAddonCellComponent implements OnInit { @Input("addon") listItem: AddonViewModel; - @Output() onViewDetails: EventEmitter = new EventEmitter(); + @Output() onViewDetails: EventEmitter = new EventEmitter(); constructor() {} ngOnInit(): void {} viewDetails() { - this.onViewDetails.emit(this.listItem.addon); + this.onViewDetails.emit(this.listItem); } } diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts index dbb8d853..bee98b61 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.ts +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.ts @@ -250,9 +250,9 @@ export class MyAddonsComponent implements OnInit, OnDestroy { }); } - openDetailDialog(addon: AddonViewModel) { + openDetailDialog(listItem: AddonViewModel) { const dialogRef = this._dialog.open(AddonDetailComponent, { - data: addon, + data: listItem.addon, }); dialogRef.afterClosed().subscribe(); From 2eb5e855554ed398ee98599d8eb1922a2b8edf6f Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 18:08:14 -0500 Subject: [PATCH 32/34] Update curse-addon-provider.ts --- wowup-electron/src/app/addon-providers/curse-addon-provider.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts index a9c27542..428881f0 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -579,8 +579,6 @@ export class CurseAddonProvider implements AddonProvider { (lf) => this.getChannelType(lf.releaseType) <= channelType ); - console.log(scanResult.searchResult.name, channelType); - // If there were no releases that met the channel type restrictions if (!latestVersion) { latestVersion = _.first(latestFiles); From ab4634e8e6403998212c22b5381bf12906230501 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 18:14:57 -0500 Subject: [PATCH 33/34] Prep for build --- wowup-electron/package.json | 2 +- wowup-electron/src/assets/changelog.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/wowup-electron/package.json b/wowup-electron/package.json index ca557672..8f7a6e3d 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -1,7 +1,7 @@ { "name": "wowup", "productName": "WowUp", - "version": "2.0.0-alpha.10", + "version": "2.0.0-alpha.11", "description": "Word of Warcraft addon updater", "homepage": "https://github.com/maximegris/angular-electron", "author": { diff --git a/wowup-electron/src/assets/changelog.json b/wowup-electron/src/assets/changelog.json index 5c28c804..56b1adce 100644 --- a/wowup-electron/src/assets/changelog.json +++ b/wowup-electron/src/assets/changelog.json @@ -1,5 +1,9 @@ { "ChangeLogs": [ + { + "Version": "2.0.0-alpha.11", + "Description": "Add version label to the 'Get Addons' page.\nUpdates to the addon detail page (by Flippey).\nRussian local updates (by Medok).\nAdd the system notification for auto updates.\nFix some column spacing.\nTelemetry for user actions added.\nFix a bug not allowing users to uninstall an addon with no 'installed folders'.\nHopefully less issues with installing multiple addons at once.\nFix some errors related to addons imported from GitHub." + }, { "Version": "2.0.0-alpha.10", "Description": "Temporary CF fingerprint endpoint patch." From 5c6608fd38d7a3fca5a0eca3fc64daabfcfdd0b1 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sat, 17 Oct 2020 22:18:32 -0500 Subject: [PATCH 34/34] Fix ignore and auto update display --- .../business-objects/my-addon-list-item.ts | 4 +++ .../my-addon-status-column.component.html | 15 +++++++++-- .../my-addon-status-column.component.scss | 8 ++++++ .../my-addon-status-column.component.ts | 4 +++ .../my-addons-addon-cell.component.html | 5 ++-- .../my-addons-addon-cell.component.scss | 4 +++ .../pages/my-addons/my-addons.component.html | 25 +------------------ 7 files changed, 37 insertions(+), 28 deletions(-) diff --git a/wowup-electron/src/app/business-objects/my-addon-list-item.ts b/wowup-electron/src/app/business-objects/my-addon-list-item.ts index 7959efd8..e8344f41 100644 --- a/wowup-electron/src/app/business-objects/my-addon-list-item.ts +++ b/wowup-electron/src/app/business-objects/my-addon-list-item.ts @@ -20,6 +20,10 @@ export class AddonViewModel { return !this.isInstalling && this.displayState === AddonDisplayState.Update; } + get isAutoUpdate() { + return this.addon.autoUpdateEnabled; + } + get isUpToDate() { return ( !this.isInstalling && this.displayState === AddonDisplayState.UpToDate diff --git a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html index 35c5a94b..b8a790ef 100644 --- a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html +++ b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.html @@ -1,2 +1,13 @@ - - \ No newline at end of file + + + +
+
+ {{getStatusText()}} +
+ + update + +
\ No newline at end of file diff --git a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.scss b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.scss index e69de29b..7b288be2 100644 --- a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.scss +++ b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.scss @@ -0,0 +1,8 @@ +@import "../../../variables.scss"; + +.auto-update-icon { + margin-left: 0.5em; +} +.ignored { + color: $white-4; +} \ No newline at end of file diff --git a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts index e30baf06..30e2a8d5 100644 --- a/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts +++ b/wowup-electron/src/app/components/my-addon-status-column/my-addon-status-column.component.ts @@ -20,6 +20,10 @@ export class MyAddonStatusColumnComponent implements OnInit, OnDestroy { public readonly buttonOptions$: Observable; + public get showStatusText() { + return this.listItem?.isUpToDate || this.listItem?.isIgnored; + } + public get buttonText() { if (this.installState !== AddonInstallState.Unknown) { return this.getInstallStateText(this.installState); diff --git a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html index 90d6fad9..6f79aab7 100644 --- a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html +++ b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.html @@ -7,7 +7,8 @@ {{listItem.isAlphaChannel ? 'Alpha': 'Beta'}}
- {{listItem.addon.name}} + {{listItem.addon.name}} -
{{listItem.addon.installedVersion}}
+
{{listItem.addon.installedVersion}}
\ No newline at end of file diff --git a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss index 4d1bf3c7..71b46657 100644 --- a/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss +++ b/wowup-electron/src/app/components/my-addons-addon-cell/my-addons-addon-cell.component.scss @@ -51,6 +51,10 @@ text-decoration: underline; color: $white-2; } + + &.ignored { + color: $white-4; + } } .addon-version { diff --git a/wowup-electron/src/app/pages/my-addons/my-addons.component.html b/wowup-electron/src/app/pages/my-addons/my-addons.component.html index a0c87579..fb7da49e 100644 --- a/wowup-electron/src/app/pages/my-addons/my-addons.component.html +++ b/wowup-electron/src/app/pages/my-addons/my-addons.component.html @@ -64,31 +64,8 @@ {{'PAGES.MY_ADDONS.TABLE.STATUS_COLUMN_HEADER' | translate}} - + - -