Merge pull request #1149 from WowUp/milestone/2.7

Milestone/2.7
This commit is contained in:
jliddev
2022-03-12 07:28:12 -08:00
committed by GitHub
21 changed files with 1292 additions and 996 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@
"electron:publish": "npm run lint && npm run build:prod && electron-builder build --publish always",
"electron:publish:linux": "docker-compose -f linux-compose.yml up",
"electron:publish:never": "npm run electron:build && electron-builder --publish never",
"electron:publish:never:local": "npx electron-builder -c electron-builder-local.json --publish never ",
"electron:publish:never:local": "npm run build:dev && npx electron-builder -c electron-builder-local.json --publish never ",
"test": "ng test --watch=false",
"test:watch": "ng test",
"test:locales": "ng test --watch=false --include='src/locales.spec.ts'",
@@ -55,13 +55,13 @@
},
"devDependencies": {
"@angular-builders/custom-webpack": "13.1.0",
"@angular-devkit/build-angular": "13.2.4",
"@angular-devkit/build-angular": "13.2.6",
"@angular-eslint/builder": "13.1.0",
"@angular-eslint/eslint-plugin": "13.1.0",
"@angular-eslint/eslint-plugin-template": "13.1.0",
"@angular-eslint/schematics": "13.1.0",
"@angular-eslint/template-parser": "13.1.0",
"@angular/cli": "13.2.4",
"@angular/cli": "13.2.6",
"@ngx-translate/core": "14.0.0",
"@ngx-translate/http-loader": "7.0.0",
"@types/adm-zip": "0.4.34",
@@ -70,7 +70,7 @@
"@types/globrex": "0.1.1",
"@types/jasmine": "3.10.3",
"@types/jasminewd2": "2.0.10",
"@types/lodash": "4.14.178",
"@types/lodash": "4.14.179",
"@types/markdown-it": "12.2.3",
"@types/mocha": "9.1.0",
"@types/node": "16.11.10",
@@ -79,22 +79,22 @@
"@types/slug": "5.0.3",
"@types/string-similarity": "4.0.0",
"@types/uuid": "8.3.4",
"@typescript-eslint/eslint-plugin": "5.12.1",
"@typescript-eslint/eslint-plugin-tslint": "5.12.1",
"@typescript-eslint/parser": "5.12.1",
"@typescript-eslint/eslint-plugin": "5.14.0",
"@typescript-eslint/eslint-plugin-tslint": "5.14.0",
"@typescript-eslint/parser": "5.14.0",
"chai": "4.3.6",
"core-js": "3.21.1",
"cross-env": "7.0.3",
"del": "6.0.0",
"dotenv": "16.0.0",
"electron": "17.1.0",
"electron-builder": "22.14.5",
"electron": "17.1.2",
"electron-builder": "22.14.13",
"electron-notarize": "1.1.1",
"electron-reload": "2.0.0-alpha.1",
"eslint": "8.9.0",
"eslint-config-prettier": "8.4.0",
"eslint": "8.10.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jsdoc": "37.9.4",
"eslint-plugin-jsdoc": "38.0.0",
"eslint-plugin-prefer-arrow": "1.2.3",
"flat": "5.0.2",
"gulp": "4.0.2",
@@ -109,8 +109,8 @@
"karma-jasmine-html-reporter": "1.7.0",
"mocha": "9.2.1",
"node-addon-api": "4.3.0",
"node-fetch": "3.2.0",
"node-gyp": "8.4.1",
"node-fetch": "3.2.2",
"node-gyp": "9.0.0",
"npm-run-all": "4.1.5",
"prettier": "2.5.1",
"spectron": "19.0.0",
@@ -123,17 +123,17 @@
"node": ">=14.0.0"
},
"dependencies": {
"@angular/animations": "13.2.3",
"@angular/cdk": "13.2.3",
"@angular/common": "13.2.3",
"@angular/compiler": "13.2.3",
"@angular/compiler-cli": "13.2.3",
"@angular/core": "13.2.3",
"@angular/forms": "13.2.3",
"@angular/material": "13.2.3",
"@angular/platform-browser": "13.2.3",
"@angular/platform-browser-dynamic": "13.2.3",
"@angular/router": "13.2.3",
"@angular/animations": "13.2.6",
"@angular/cdk": "13.2.6",
"@angular/common": "13.2.6",
"@angular/compiler": "13.2.6",
"@angular/compiler-cli": "13.2.6",
"@angular/core": "13.2.6",
"@angular/forms": "13.2.6",
"@angular/material": "13.2.6",
"@angular/platform-browser": "13.2.6",
"@angular/platform-browser-dynamic": "13.2.6",
"@angular/router": "13.2.6",
"@bbob/core": "2.8.0",
"@bbob/html": "2.8.0",
"@bbob/preset-html5": "2.8.0",
@@ -143,7 +143,7 @@
"@fortawesome/free-brands-svg-icons": "6.0.0",
"@fortawesome/free-regular-svg-icons": "6.0.0",
"@fortawesome/free-solid-svg-icons": "6.0.0",
"@microsoft/applicationinsights-web": "2.7.3",
"@microsoft/applicationinsights-web": "2.7.4",
"adm-zip": "0.5.9",
"ag-grid-angular": "27.0.0",
"ag-grid-community": "27.0.1",
@@ -160,7 +160,7 @@
"messageformat": "2.3.0",
"minimist": "1.2.5",
"nanoid": "3.3.1",
"ng-gallery": "6.0.0",
"ng-gallery": "6.0.1",
"ngx-translate-messageformat-compiler": "5.0.1",
"node-cache": "5.1.2",
"node-disk-info": "1.3.0",
@@ -169,12 +169,12 @@
"protobufjs": "6.11.2",
"pushy-electron": "1.0.8",
"rxjs": "7.5.4",
"slug": "5.2.0",
"slug": "5.3.0",
"string-similarity": "4.0.4",
"ts-custom-error": "3.2.0",
"tslib": "2.3.1",
"uuid": "8.3.2",
"win-ca": "3.4.5",
"win-ca": "3.5.0",
"yauzl": "2.10.0",
"zone.js": "0.11.4"
},

View File

@@ -49,6 +49,7 @@ import { strictFilter } from "../utils/array.utils";
import { TocService } from "../services/toc/toc.service";
import { WarcraftService } from "../services/warcraft/warcraft.service";
import { SensitiveStorageService } from "../services/storage/sensitive-storage.service";
import { getWowClientGroup } from "../../common/warcraft";
interface ProtocolData {
addonId: number;
@@ -812,7 +813,9 @@ export class CurseAddonV2Provider extends AddonProvider {
}
private getLatestFiles(result: CF2Addon, clientType: WowClientType): CF2File[] {
const filtered = result.latestFiles.filter((latestFile) => this.isClientType(latestFile, clientType));
const filtered = result.latestFiles.filter(
(latestFile) => latestFile.exposeAsAlternative !== true && this.isClientType(latestFile, clientType)
);
return _.sortBy(filtered, (latestFile) => latestFile.id).reverse();
}
@@ -1021,7 +1024,7 @@ export class CurseAddonV2Provider extends AddonProvider {
}
private getCFGameVersionType(clientType: WowClientType): CF2WowGameVersionType {
const clientGroup = this._warcraftService.getClientGroup(clientType);
const clientGroup = getWowClientGroup(clientType);
switch (clientGroup) {
case WowClientGroup.BurningCrusade:

View File

@@ -26,6 +26,7 @@ import { WowInstallation } from "../../common/warcraft/wow-installation";
import { convertMarkdown } from "../utils/markdown.utlils";
import { strictFilterBy } from "../utils/array.utils";
import { WarcraftService } from "../services/warcraft/warcraft.service";
import { getWowClientGroup } from "../../common/warcraft";
type MetadataFlavor = "bcc" | "classic" | "mainline";
@@ -126,7 +127,7 @@ export class GitHubAddonProvider extends AddonProvider {
searchResult: undefined,
};
const clientGroup = this._warcraftService.getClientGroup(installation.clientType);
const clientGroup = getWowClientGroup(installation.clientType);
try {
const results = await this.getReleases(repoPath);

View File

@@ -174,11 +174,11 @@ export class TukUiAddonProvider extends AddonProvider {
matches.push({ ...tukUiAddon });
const installedFolders = targetToc.tukUiProjectFolders ? targetToc.tukUiProjectFolders : tukUiAddon.name;
const installedFolders = targetToc.tukUiProjectFolders ? targetToc.tukUiProjectFolders : addonFolder.name;
const installedFolderList = targetToc.tukUiProjectFolders
? targetToc.tukUiProjectFolders.split(",").map((f) => f.trim())
: [tukUiAddon.name];
: [addonFolder.name];
addonFolder.matchingAddon = {
autoUpdateEnabled: false,

View File

@@ -24,6 +24,7 @@ import { getEnumName } from "../utils/enum.utils";
import { convertMarkdown } from "../utils/markdown.utlils";
import { AddonProvider, GetAllResult } from "./addon-provider";
import { SourceRemovedAddonError } from "../errors";
import { getWowClientGroup } from "../../common/warcraft";
declare type WagoGameVersion = "retail" | "classic" | "bc";
declare type WagoStability = "stable" | "beta" | "alpha";
@@ -643,7 +644,7 @@ export class WagoAddonProvider extends AddonProvider {
// The wago name for the client type
private getGameVersion(clientType: WowClientType): WagoGameVersion {
const clientGroup = this._warcraftService.getClientGroup(clientType);
const clientGroup = getWowClientGroup(clientType);
switch (clientGroup) {
case WowClientGroup.BurningCrusade:
return "bc";

View File

@@ -3,13 +3,17 @@ import { from, Observable } from "rxjs";
import { map } from "rxjs/operators";
import { v4 as uuidv4 } from "uuid";
import { ADDON_PROVIDER_HUB, IPC_WOWUP_GET_SCAN_RESULTS } from "../../common/constants";
import { ADDON_PROVIDER_HUB, APP_PROTOCOL_NAME, IPC_WOWUP_GET_SCAN_RESULTS } from "../../common/constants";
import { Addon } from "../../common/entities/addon";
import { WowClientType } from "../../common/warcraft/wow-client-type";
import { WowClientGroup, WowClientType } from "../../common/warcraft/wow-client-type";
import { AddonCategory, AddonChannelType, WowUpScanResult } from "../../common/wowup/models";
import { AppConfig } from "../../environments/environment";
import { SourceRemovedAddonError } from "../errors";
import { WowUpAddonReleaseRepresentation, WowUpAddonRepresentation } from "../models/wowup-api/addon-representations";
import {
AddonReleaseGameVersion,
WowUpAddonReleaseRepresentation,
WowUpAddonRepresentation,
} from "../models/wowup-api/addon-representations";
import {
GetFeaturedAddonsResponse,
WowUpGetAddonReleaseResponse,
@@ -30,6 +34,12 @@ import { CircuitBreakerWrapper, NetworkService } from "../services/network/netwo
import { getGameVersion } from "../utils/addon.utils";
import { getEnumName } from "../utils/enum.utils";
import { AddonProvider, GetAllBatchResult, GetAllResult } from "./addon-provider";
import { ProtocolSearchResult } from "../models/wowup/protocol-search-result";
interface ProtocolData {
addonId: string;
releaseId: string;
}
const API_URL = AppConfig.wowUpHubUrl;
const FEATURED_ADDONS_CACHE_TTL_SEC = AppConfig.featuredAddonsCacheTimeSec;
@@ -79,6 +89,55 @@ export class WowUpAddonProvider extends AddonProvider {
return !addon.installedExternalReleaseId;
}
public isValidProtocol(protocol: string): boolean {
return protocol.toLowerCase().startsWith(`${APP_PROTOCOL_NAME}://`);
}
public async searchProtocol(protocol: string): Promise<ProtocolSearchResult | undefined> {
const protocolData = this.parseProtocol(protocol);
if (!protocolData.addonId || !protocolData.releaseId) {
throw new Error("Invalid protocol data");
}
const addonResult = await this.getAddonById(protocolData.addonId);
if (!addonResult) {
throw new Error(`Failed to get addon data: ${protocolData.addonId}`);
}
console.debug("addonResult", addonResult);
const addonFileResponse = await this.getReleaseById(protocolData.addonId, protocolData.releaseId);
console.debug("targetFile", addonFileResponse);
if (!addonFileResponse) {
throw new Error("Failed to get target file");
}
const addonSearchResult = this.getSearchResultWithReleases(addonResult.addon, [addonFileResponse.release]);
if (!addonSearchResult) {
throw new Error("Addon search result not created");
}
const searchResult: ProtocolSearchResult = {
protocol,
protocolAddonId: protocolData.addonId.toString(),
protocolReleaseId: protocolData.releaseId.toString(),
validClientGroups: _.map(addonFileResponse.release.game_versions, (gv) => this.getWowClientGroup(gv.game_type)),
...addonSearchResult,
};
console.debug("searchResult", searchResult);
return searchResult;
}
private parseProtocol(protocol: string): ProtocolData {
const url = new URL(protocol);
return {
addonId: url.searchParams.get("addonId") || "",
releaseId: url.searchParams.get("releaseId") || "",
};
}
public async getAllBatch(installations: WowInstallation[], addonIds: string[]): Promise<GetAllBatchResult> {
const batchResult: GetAllBatchResult = {
errors: {},
@@ -373,6 +432,13 @@ export class WowUpAddonProvider extends AddonProvider {
return undefined;
}
return this.getSearchResultFileWithVersion(release, matchingVersion);
}
private getSearchResultFileWithVersion(
release: WowUpAddonReleaseRepresentation,
matchingVersion: AddonReleaseGameVersion
): AddonSearchResultFile | undefined {
const version = matchingVersion?.version || release.tag_name || "";
return {
@@ -428,6 +494,37 @@ export class WowUpAddonProvider extends AddonProvider {
};
}
private getSearchResultWithReleases(
representation: WowUpAddonRepresentation,
releases: WowUpAddonReleaseRepresentation[]
): AddonSearchResult | undefined {
const searchResultFiles: AddonSearchResultFile[] = _.flatMap(releases, (release) =>
_.map(release.game_versions, (gv) => this.getSearchResultFileWithVersion(release, gv))
).filter((sr) => sr !== undefined);
if (searchResultFiles.length === 0) {
return undefined;
}
const name = _.first(searchResultFiles)?.title ?? representation.repository_name;
const authors = _.first(searchResultFiles)?.authors ?? representation.owner_name ?? "";
return {
author: authors,
externalId: representation.id.toString(),
externalUrl: `${AppConfig.wowUpWebsiteUrl}/addons/${representation.id}`,
name,
providerName: this.name,
thumbnailUrl: representation.image_url || representation.owner_image_url || "",
downloadCount: representation.total_download_count,
files: searchResultFiles,
releasedAt: new Date(),
summary: representation.description,
fundingLinks: [...(representation?.funding_links ?? [])],
screenshotUrls: this.getScreenshotUrls(releases),
};
}
// Currently we only support images, so we filter for those
private getScreenshotUrls(releases: WowUpAddonReleaseRepresentation[]): string[] {
const urls = _.flatten(
@@ -527,4 +624,15 @@ export class WowUpAddonProvider extends AddonProvider {
return WowGameType.Retail;
}
}
private getWowClientGroup(gameType: WowGameType): WowClientGroup {
switch (gameType) {
case WowGameType.BurningCrusade:
return WowClientGroup.BurningCrusade;
case WowGameType.Classic:
return WowClientGroup.Classic;
case WowGameType.Retail:
return WowClientGroup.Retail;
}
}
}

View File

@@ -58,6 +58,7 @@ import {
ConsentDialogComponent,
ConsentDialogResult,
} from "./components/common/consent-dialog/consent-dialog.component";
import { WowUpProtocolService } from "./services/wowup/wowup-protocol.service";
@Component({
selector: "app-root",
@@ -101,6 +102,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
private _warcraftInstallationService: WarcraftInstallationService,
private _wowupAddonService: WowUpAddonService,
private _zoomService: ZoomService,
private _wowUpProtocolService: WowUpProtocolService,
public electronService: ElectronService,
public overlayContainer: OverlayContainer,
public sessionService: SessionService,
@@ -140,6 +142,8 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
});
}
});
this._wowUpProtocolService.initialize();
}
public ngOnInit(): void {

View File

@@ -133,9 +133,18 @@ export class InstallFromProtocolDialogComponent implements OnInit, AfterViewInit
this.addon = searchResult;
this.validWowInstallations = await this._warcraftInstallationService.getWowInstallationsByClientTypes(
searchResult.validClientTypes
);
if (Array.isArray(searchResult.validClientGroups)) {
this.validWowInstallations = await this._warcraftInstallationService.getWowInstallationsByClientGroups(
searchResult.validClientGroups
);
} else if (Array.isArray(searchResult.validClientTypes)) {
this.validWowInstallations = await this._warcraftInstallationService.getWowInstallationsByClientTypes(
searchResult.validClientTypes
);
} else {
throw new Error("No valid clients found");
}
if (this.validWowInstallations.length === 0) {
this.error = ERROR_NO_VALID_WOW_INSTALLATIONS;
return;

View File

@@ -1,9 +1,10 @@
import { WowClientType } from "../../../common/warcraft/wow-client-type";
import { WowClientGroup, WowClientType } from "../../../common/warcraft/wow-client-type";
import { AddonSearchResult } from "./addon-search-result";
export interface ProtocolSearchResult extends AddonSearchResult {
protocol: string;
protocolAddonId?: string;
protocolReleaseId?: string;
validClientTypes: WowClientType[];
validClientTypes?: WowClientType[];
validClientGroups?: WowClientGroup[];
}

View File

@@ -1,12 +1,11 @@
import { from, Subscription } from "rxjs";
import { filter, first, map, switchMap } from "rxjs/operators";
import { from, of, Subscription } from "rxjs";
import { catchError, filter, first, map, switchMap, tap } from "rxjs/operators";
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from "@angular/core";
import { MatSnackBar } from "@angular/material/snack-bar";
import { TranslateService } from "@ngx-translate/core";
import {
APP_PROTOCOL_NAME,
CURSE_PROTOCOL_NAME,
IPC_POWER_MONITOR_RESUME,
IPC_POWER_MONITOR_UNLOCK,
@@ -31,6 +30,7 @@ import { WarcraftInstallationService } from "../../services/warcraft/warcraft-in
import { WowUpService } from "../../services/wowup/wowup.service";
import { getProtocol } from "../../utils/string.utils";
import { WowInstallation } from "../../../common/warcraft/wow-installation";
import { WowUpProtocolService } from "../../services/wowup/wowup-protocol.service";
@Component({
selector: "app-home",
@@ -62,7 +62,8 @@ export class HomeComponent implements AfterViewInit, OnDestroy {
private _snackBarService: SnackbarService,
private _cdRef: ChangeDetectorRef,
private _warcraftInstallationService: WarcraftInstallationService,
private _dialogFactory: DialogFactory
private _dialogFactory: DialogFactory,
private _wowUpProtocolService: WowUpProtocolService
) {
const wowInstalledSub = this._warcraftInstallationService.wowInstallations$.subscribe((installations) => {
this.hasWowClient = installations.length > 0;
@@ -70,8 +71,12 @@ export class HomeComponent implements AfterViewInit, OnDestroy {
const customProtocolSub = this.electronService.customProtocol$
.pipe(
filter((protocol) => !!protocol),
switchMap((protocol) => from(this.handleCustomProtocol(protocol)))
filter((protocol) => getProtocol(protocol) === CURSE_PROTOCOL_NAME),
tap((protocol) => this.handleAddonInstallProtocol(protocol)),
catchError((e) => {
console.error(e);
return of(undefined);
})
)
.subscribe();
@@ -85,25 +90,7 @@ export class HomeComponent implements AfterViewInit, OnDestroy {
this._subscriptions.push(customProtocolSub, wowInstalledSub, scanErrorSub, scanUpdateSub, addonInstallErrorSub);
}
private handleCustomProtocol = async (protocol: string): Promise<void> => {
const protocolName = getProtocol(protocol);
try {
switch (protocolName) {
case APP_PROTOCOL_NAME:
break;
case CURSE_PROTOCOL_NAME:
await this.handleAddonInstallProtocol(protocol);
break;
default:
console.warn(`Unknown protocol: ${protocol}`);
return;
}
} catch (e) {
console.error(`Failed to handle protocol`, e);
}
};
private async handleAddonInstallProtocol(protocol: string) {
private handleAddonInstallProtocol(protocol: string) {
const dialog = this._dialogFactory.getDialog(InstallFromProtocolDialogComponent, {
disableClose: true,
data: {
@@ -111,7 +98,7 @@ export class HomeComponent implements AfterViewInit, OnDestroy {
},
});
await dialog.afterClosed().toPromise();
return dialog.afterClosed().pipe(first());
}
public ngAfterViewInit(): void {

View File

@@ -108,7 +108,10 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
map((wowInstall) => wowInstall !== undefined)
);
public readonly isBusy$ = this._isBusySrc.asObservable();
public readonly isBusy$ = combineLatest([this._isBusySrc, this._addonService.syncing$]).pipe(
map((vals) => _.some(vals))
);
public readonly filterInput$ = this._filterInputSrc.asObservable();
public readonly rowData$ = combineLatest([this._baseRowDataSrc, this.filterInput$]).pipe(

View File

@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
import { Addon } from "../../../common/entities/addon";
import { WowClientType } from "../../../common/warcraft/wow-client-type";
import { getWowClientGroup } from "../../../common/warcraft";
import { WowInstallation } from "../../../common/warcraft/wow-installation";
import { getEnumName } from "../../utils/enum.utils";
import { AddonStorageService } from "../storage/addon-storage.service";
@@ -232,8 +233,8 @@ export class AddonBrokerService {
}
private isSameClient(srcClient: WowClientType, targetClient: string) {
const srcGroup = this._warcraftService.getClientGroup(srcClient);
const targetGroup = this._warcraftService.getClientGroup(targetClient);
const srcGroup = getWowClientGroup(srcClient);
const targetGroup = getWowClientGroup(targetClient);
return srcGroup === targetGroup;
}

View File

@@ -118,6 +118,7 @@ export class AddonService {
private readonly _installQueue = new Subject<InstallQueueItem>();
private readonly _anyUpdatesAvailableSrc = new BehaviorSubject<boolean>(false);
private readonly _addonProviderChangeSrc = new Subject<AddonProvider>();
private readonly _syncingSrc = new BehaviorSubject<boolean>(false);
private _activeInstalls: AddonUpdateEvent[] = [];
private _subscriptions: Subscription[] = [];
@@ -132,6 +133,7 @@ export class AddonService {
public readonly searchError$ = this._searchErrorSrc.asObservable();
public readonly anyUpdatesAvailable$ = this._anyUpdatesAvailableSrc.asObservable();
public readonly addonProviderChange$ = this._addonProviderChangeSrc.asObservable();
public readonly syncing$ = this._syncingSrc.asObservable();
public constructor(
private _addonStorage: AddonStorageService,
@@ -1114,6 +1116,8 @@ export class AddonService {
/** Iterate over all the installed WoW clients and attempt to check for addon updates */
public async syncAllClients(): Promise<void> {
console.debug("syncAllClients");
this._syncingSrc.next(true);
const installations = await this._warcraftInstallationService.getWowInstallationsAsync();
try {
@@ -1122,6 +1126,7 @@ export class AddonService {
} catch (e) {
console.error(e);
} finally {
this._syncingSrc.next(false);
this._addonActionSrc.next({ type: "sync" });
}
}

View File

@@ -133,7 +133,6 @@ export class ElectronService {
});
this.onRendererEvent(IPC_CUSTOM_PROTOCOL_RECEIVED, (evt, protocol: string) => {
console.debug(IPC_CUSTOM_PROTOCOL_RECEIVED, protocol);
this._customProtocolSrc.next(protocol);
});

View File

@@ -1,5 +1,5 @@
import * as _ from "lodash";
import { BehaviorSubject, from, Subject } from "rxjs";
import { BehaviorSubject, combineLatest, from, Subject } from "rxjs";
import { Injectable } from "@angular/core";
@@ -49,7 +49,9 @@ export class SessionService {
public readonly wowUpAccountPushEnabled$ = this._wowUpAccountService.accountPushSrc.asObservable();
public readonly myAddonsCompactVersion$ = this._myAddonsCompactVersionSrc.asObservable();
public readonly adSpace$ = this._adSpaceSrc.asObservable(); // TODO this should be driven by the enabled providers
public readonly enableControls$ = this._enableControlsSrc.asObservable();
public readonly enableControls$ = combineLatest([this._enableControlsSrc, this._addonService.syncing$]).pipe(
map(([enable, syncing]) => enable && !syncing)
);
public readonly debugAdFrame$ = new Subject<boolean>();
public readonly currentTheme$ = this._currentThemeSrc.asObservable();

View File

@@ -12,7 +12,7 @@ import {
DEFAULT_CHANNEL_PREFERENCE_KEY_SUFFIX,
WOW_INSTALLATIONS_KEY,
} from "../../../common/constants";
import { WowClientType } from "../../../common/warcraft/wow-client-type";
import { WowClientGroup, WowClientType } from "../../../common/warcraft/wow-client-type";
import { AddonChannelType } from "../../../common/wowup/models";
import { WowInstallation } from "../../../common/warcraft/wow-installation";
import { getEnumName } from "../../utils/enum.utils";
@@ -20,6 +20,7 @@ import { ElectronService } from "../electron/electron.service";
import { FileService } from "../files/file.service";
import { PreferenceStorageService } from "../storage/preference-storage.service";
import { WarcraftService } from "./warcraft.service";
import { getWowClientFolderName, getWowClientGroup } from "../../../common/warcraft";
@Injectable({
providedIn: "root",
@@ -108,6 +109,14 @@ export class WarcraftInstallationService {
return _.filter(installations, (installation) => clientTypes.includes(installation.clientType));
}
public async getWowInstallationsByClientGroups(clientGroups: WowClientGroup[]): Promise<WowInstallation[]> {
const installations = await this.getWowInstallationsAsync();
return _.filter(installations, (installation) => {
const clientGroup = getWowClientGroup(installation.clientType);
return clientGroups.includes(clientGroup);
});
}
public async setWowInstallations(wowInstallations: WowInstallation[]): Promise<void> {
console.log(`Setting wow installations: ${wowInstallations.length}`);
await this._preferenceStorageService.setAsync(WOW_INSTALLATIONS_KEY, wowInstallations);
@@ -343,7 +352,7 @@ export class WarcraftInstallationService {
}
private getFullProductPath(location: string, clientType: WowClientType): string {
const clientFolderName = this._warcraftService.getClientFolderName(clientType);
const clientFolderName = getWowClientFolderName(clientType);
const executableName = this._warcraftService.getExecutableName(clientType);
return path.join(location, clientFolderName, executableName);
}

View File

@@ -6,7 +6,7 @@ import { Injectable } from "@angular/core";
import { ElectronService } from "../electron/electron.service";
import * as constants from "../../../common/constants";
import { WowClientGroup, WowClientType } from "../../../common/warcraft/wow-client-type";
import { WowClientType } from "../../../common/warcraft/wow-client-type";
import { InstalledProduct } from "../../models/warcraft/installed-product";
import { AddonFolder } from "../../models/wowup/addon-folder";
import { SelectItem } from "../../models/wowup/select-item";
@@ -193,48 +193,6 @@ export class WarcraftService {
return this._impl.getClientType(binaryPath);
}
public getClientFolderName(clientType: WowClientType): string {
switch (clientType) {
case WowClientType.Retail:
return constants.WOW_RETAIL_FOLDER;
case WowClientType.ClassicEra:
return constants.WOW_CLASSIC_ERA_FOLDER;
case WowClientType.Classic:
return constants.WOW_CLASSIC_FOLDER;
case WowClientType.RetailPtr:
return constants.WOW_RETAIL_PTR_FOLDER;
case WowClientType.ClassicPtr:
return constants.WOW_CLASSIC_PTR_FOLDER;
case WowClientType.Beta:
return constants.WOW_BETA_FOLDER;
case WowClientType.ClassicBeta:
return constants.WOW_CLASSIC_BETA_FOLDER;
case WowClientType.ClassicEraPtr:
return constants.WOW_CLASSIC_ERA_PTR_FOLDER;
default:
return "";
}
}
public getClientGroup(clientType: string | WowClientType): WowClientGroup {
const enumVal: WowClientType = typeof clientType === "string" ? WowClientType[clientType] : clientType;
switch (enumVal) {
case WowClientType.Beta:
case WowClientType.Retail:
case WowClientType.RetailPtr:
return WowClientGroup.Retail;
case WowClientType.ClassicEra:
case WowClientType.ClassicEraPtr:
return WowClientGroup.Classic;
case WowClientType.Classic:
case WowClientType.ClassicBeta:
case WowClientType.ClassicPtr:
return WowClientGroup.BurningCrusade;
default:
throw new Error(`unsupported client type: ${clientType}`);
}
}
/**
* Get the old style preference key for a WoW client type
* @deprecated

View File

@@ -0,0 +1,45 @@
import { Injectable } from "@angular/core";
import { catchError, filter, first, of, switchMap, tap } from "rxjs";
import { APP_PROTOCOL_NAME } from "../../../common/constants";
import { InstallFromProtocolDialogComponent } from "../../components/addons/install-from-protocol-dialog/install-from-protocol-dialog.component";
import { getProtocol, getProtocolParts } from "../../utils/string.utils";
import { DialogFactory } from "../dialog/dialog.factory";
import { ElectronService } from "../electron/electron.service";
@Injectable({
providedIn: "root",
})
export class WowUpProtocolService {
public constructor(private _dialogFactory: DialogFactory, private _electronService: ElectronService) {}
public initialize() {
this._electronService.customProtocol$
.pipe(
tap((prt) => console.log("WowUpProtocolService", prt)),
filter((prt) => getProtocol(prt) === APP_PROTOCOL_NAME && this.isInstallAction(prt)),
switchMap((prt) => this.onInstallProtocol(prt)),
catchError((e) => {
console.error(e);
return of(undefined);
})
)
.subscribe();
}
public isInstallAction(protocol: string) {
return getProtocolParts(protocol)[0] === "install";
}
public onInstallProtocol(protocol: string) {
console.log("onInstallProtocol", protocol);
const dialog = this._dialogFactory.getDialog(InstallFromProtocolDialogComponent, {
disableClose: true,
data: {
protocol,
},
});
return dialog.afterClosed().pipe(first());
}
}

View File

@@ -42,6 +42,13 @@ export function getProtocol(arg: string): string | null {
return match !== null && match.length > 1 ? match[1] : null;
}
export function getProtocolParts(protocol: string) {
return new URL(protocol).pathname
.split("/")
.filter((part) => !!part)
.map((part) => part.toLowerCase());
}
export function getRelativeDateFormat(value: string): [string, object | undefined] {
if (!value) {
return ["", undefined];

View File

@@ -0,0 +1,44 @@
import * as constants from "../constants";
import { WowClientGroup, WowClientType } from "./wow-client-type";
export function getWowClientFolderName(clientType: WowClientType): string {
switch (clientType) {
case WowClientType.Retail:
return constants.WOW_RETAIL_FOLDER;
case WowClientType.ClassicEra:
return constants.WOW_CLASSIC_ERA_FOLDER;
case WowClientType.Classic:
return constants.WOW_CLASSIC_FOLDER;
case WowClientType.RetailPtr:
return constants.WOW_RETAIL_PTR_FOLDER;
case WowClientType.ClassicPtr:
return constants.WOW_CLASSIC_PTR_FOLDER;
case WowClientType.Beta:
return constants.WOW_BETA_FOLDER;
case WowClientType.ClassicBeta:
return constants.WOW_CLASSIC_BETA_FOLDER;
case WowClientType.ClassicEraPtr:
return constants.WOW_CLASSIC_ERA_PTR_FOLDER;
default:
return "";
}
}
export function getWowClientGroup(clientType: string | WowClientType): WowClientGroup {
const enumVal: WowClientType = typeof clientType === "string" ? WowClientType[clientType] : clientType;
switch (enumVal) {
case WowClientType.Beta:
case WowClientType.Retail:
case WowClientType.RetailPtr:
return WowClientGroup.Retail;
case WowClientType.ClassicEra:
case WowClientType.ClassicEraPtr:
return WowClientGroup.Classic;
case WowClientType.Classic:
case WowClientType.ClassicBeta:
case WowClientType.ClassicPtr:
return WowClientGroup.BurningCrusade;
default:
throw new Error(`unsupported client type: ${clientType}`);
}
}