From 286dc355e38f586bc10698910e488fcb0e54dda3 Mon Sep 17 00:00:00 2001 From: jliddev Date: Sun, 21 Mar 2021 10:36:46 -0500 Subject: [PATCH] Rework protocol handling for IPC messages Create new UI for installing protocol addons Update addon service to allow installing a specific addon version Update addon providers to allow searching via protocol Standardize the thumbnail component --- wowup-electron/ipc-events.ts | 15 +- wowup-electron/main.ts | 59 +++--- wowup-electron/package.json | 1 + wowup-electron/preload.ts | 16 -- .../src/app/addon-providers/addon-provider.ts | 9 + .../addon-providers/curse-addon-provider.ts | 77 +++++++- wowup-electron/src/app/app.component.ts | 14 +- .../addon-thumbnail.component.html | 9 + .../addon-thumbnail.component.scss | 26 +++ .../addon-thumbnail.component.spec.ts | 25 +++ .../addon-thumbnail.component.ts | 24 +++ ...nstall-from-protocol-dialog.component.html | 53 ++++++ ...nstall-from-protocol-dialog.component.scss | 22 +++ ...all-from-protocol-dialog.component.spec.ts | 25 +++ .../install-from-protocol-dialog.component.ts | 169 ++++++++++++++++++ .../my-addons-addon-cell.component.html | 21 +-- .../my-addons-addon-cell.component.ts | 4 + .../options-app-section.component.html | 57 ++++-- .../options-app-section.component.scss | 12 +- .../options-app-section.component.ts | 30 +++- ...otential-addon-table-column.component.html | 10 +- .../src/app/models/curse/curse-api.ts | 3 + .../models/wowup/protocol-search-result.ts | 9 + .../src/app/pages/home/home.component.ts | 73 ++++++-- .../src/app/pages/home/home.module.ts | 4 + .../pages/my-addons/my-addons.component.ts | 43 ++--- .../src/app/services/addons/addon.service.ts | 34 +++- .../src/app/services/dialog/dialog.factory.ts | 7 +- .../app/services/electron/electron.service.ts | 92 +++++----- .../src/app/services/icons/icon.service.ts | 2 + .../app/services/session/session.service.ts | 16 +- .../warcraft/warcraft-installation.service.ts | 4 + .../src/app/services/wowup/wowup.service.ts | 21 --- wowup-electron/src/app/utils/string.utils.ts | 9 + wowup-electron/src/assets/i18n/cs.json | 14 ++ wowup-electron/src/assets/i18n/de.json | 16 +- wowup-electron/src/assets/i18n/en.json | 18 +- wowup-electron/src/assets/i18n/es.json | 14 ++ wowup-electron/src/assets/i18n/fr.json | 14 ++ wowup-electron/src/assets/i18n/it.json | 14 ++ wowup-electron/src/assets/i18n/ko.json | 14 ++ wowup-electron/src/assets/i18n/nb.json | 14 ++ wowup-electron/src/assets/i18n/pt.json | 14 ++ wowup-electron/src/assets/i18n/ru.json | 14 ++ wowup-electron/src/assets/i18n/zh-TW.json | 14 ++ wowup-electron/src/assets/i18n/zh.json | 14 ++ wowup-electron/src/common/constants.ts | 6 + wowup-electron/src/common/wowup.d.ts | 7 +- wowup-electron/src/common/wowup/models.ts | 1 - wowup-electron/src/styles.scss | 25 +++ 50 files changed, 974 insertions(+), 234 deletions(-) create mode 100644 wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.html create mode 100644 wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.scss create mode 100644 wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.spec.ts create mode 100644 wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.ts create mode 100644 wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.html create mode 100644 wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.scss create mode 100644 wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.spec.ts create mode 100644 wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.ts create mode 100644 wowup-electron/src/app/models/wowup/protocol-search-result.ts diff --git a/wowup-electron/ipc-events.ts b/wowup-electron/ipc-events.ts index 97e72d09..1906d786 100644 --- a/wowup-electron/ipc-events.ts +++ b/wowup-electron/ipc-events.ts @@ -51,6 +51,7 @@ import { IPC_WOWUP_GET_SCAN_RESULTS, IPC_WRITE_FILE_CHANNEL, IPC_FOCUS_WINDOW, + IPC_IS_DEFAULT_PROTOCOL_CLIENT, } from "./src/common/constants"; import { CurseFolderScanner } from "./src/common/curse/curse-folder-scanner"; import { CurseFolderScanResult } from "./src/common/curse/curse-folder-scan-result"; @@ -170,13 +171,17 @@ export function initializeIpcHandlers(window: BrowserWindow): void { } ); - handle(IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT, () => { - app.setAsDefaultProtocolClient(APP_PROTOCOL_NAME); + handle(IPC_IS_DEFAULT_PROTOCOL_CLIENT, (evt, protocol: string) => { + return app.isDefaultProtocolClient(protocol); }); - handle(IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT, () => { - app.removeAsDefaultProtocolClient(APP_PROTOCOL_NAME); - }) + handle(IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT, (evt, protocol: string) => { + return app.setAsDefaultProtocolClient(protocol); + }); + + handle(IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT, (evt, protocol: string) => { + return app.removeAsDefaultProtocolClient(protocol); + }); handle(IPC_LIST_DIRECTORIES_CHANNEL, async (evt, filePath: string, scanSymlinks: boolean) => { const files = await fs.readdir(filePath, { withFileTypes: true }); diff --git a/wowup-electron/main.ts b/wowup-electron/main.ts index 18f346ac..f13f7536 100644 --- a/wowup-electron/main.ts +++ b/wowup-electron/main.ts @@ -1,18 +1,23 @@ import { app, BrowserWindow, BrowserWindowConstructorOptions, powerMonitor } from "electron"; import * as log from "electron-log"; -import { type as osType, release as osRelease, arch as osArch } from "os"; +import { find } from "lodash"; +import * as minimist from "minimist"; +import { arch as osArch, release as osRelease, type as osType } from "os"; import { join } from "path"; import { format as urlFormat } from "url"; import { inspect } from "util"; -import * as platform from "./platform"; -import * as minimist from "minimist"; + +import { createAppMenu } from "./app-menu"; import { initializeAppUpdateIpcHandlers, initializeAppUpdater } from "./app-updater"; import { initializeIpcHandlers } from "./ipc-events"; +import * as platform from "./platform"; import { + APP_USER_MODEL_ID, COLLAPSE_TO_TRAY_PREFERENCE_KEY, CURRENT_THEME_KEY, DEFAULT_BG_COLOR, DEFAULT_LIGHT_BG_COLOR, + IPC_CUSTOM_PROTOCOL_RECEIVED, IPC_POWER_MONITOR_LOCK, IPC_POWER_MONITOR_RESUME, IPC_POWER_MONITOR_SUSPEND, @@ -27,15 +32,12 @@ import { WINDOW_DEFAULT_WIDTH, WINDOW_MIN_HEIGHT, WINDOW_MIN_WIDTH, - IPC_REQUEST_INSTALL_FROM_URL, - APP_PROTOCOL_NAME, WOWUP_LOGO_FILENAME, } from "./src/common/constants"; -import { AppOptions } from "./src/common/wowup/models"; -import { windowStateManager } from "./window-state"; -import { createAppMenu } from "./app-menu"; import { MainChannels } from "./src/common/wowup"; +import { AppOptions } from "./src/common/wowup/models"; import { preferenceStore } from "./stores"; +import { windowStateManager } from "./window-state"; // LOGGING SETUP // Override the default log path so they aren't a pain to find on Mac @@ -46,6 +48,9 @@ log.transports.file.resolvePath = (variables: log.PathVariables) => { }; log.info("Main starting"); log.info(`Electron: ${process.versions.electron}`); +log.info(`BinaryPath: ${app.getPath("exe")}`); +log.info("ExecPath", process.execPath); +log.info("Args", process.argv); // ERROR HANDLING SETUP process.on("uncaughtException", (error) => { @@ -59,7 +64,7 @@ process.on("unhandledRejection", (error) => { // VARIABLES const startedAt = Date.now(); const argv = minimist(process.argv.slice(1), { - boolean: ["serve", "hidden"] + boolean: ["serve", "hidden"], }) as AppOptions; const isPortable = !!process.env.PORTABLE_EXECUTABLE_DIR; const USER_AGENT = getUserAgent(); @@ -72,7 +77,7 @@ let win: BrowserWindow = null; createAppMenu(win); // Set the app ID so that our notifications work correctly on Windows -app.setAppUserModelId("io.wowup.jliddev"); +app.setAppUserModelId(APP_USER_MODEL_ID); // HARDWARE ACCELERATION SETUP if (preferenceStore.get(USE_HARDWARE_ACCELERATION_PREFERENCE_KEY) === "false") { @@ -94,6 +99,7 @@ if (!singleInstanceLock) { app.quit(); } else { app.on("second-instance", (evt, args) => { + log.info(`Second instance detected`, args); // Someone tried to run a second instance, we should focus our window. if (!win) { log.warn("Second instance launched, but no window found"); @@ -107,25 +113,28 @@ if (!singleInstanceLock) { } win.focus(); - - args.slice(1).forEach(arg => { - try { - const url = new URL(arg); - if (url && url.protocol == APP_PROTOCOL_NAME + ":") { - win.webContents.send(IPC_REQUEST_INSTALL_FROM_URL, url.searchParams.get("install")); - return; - } - } catch { log.info("Failed to load as URI: " + arg); } - }); - const argv = minimist(args.slice(1), { - string: ["install"] - }) as AppOptions; - - win.webContents.send(IPC_REQUEST_INSTALL_FROM_URL, argv.install); + // Find the first protocol arg if any exist + const customProtocol = find(args, (arg) => isProtocol(arg)); + if (customProtocol) { + log.info(`Custom protocol detected: ${customProtocol}`); + // If we did get a custom protocol notify the app + win.webContents.send(IPC_CUSTOM_PROTOCOL_RECEIVED, customProtocol); + } else { + log.info(`No custom protocol detected`); + } }); } +function isProtocol(arg: string) { + return getProtocol(arg) != null; +} + +function getProtocol(arg: string) { + const match = /^([a-z][a-z0-9+\-.]*):/.exec(arg); + return match !== null && match.length > 1 ? match[1] : null; +} + // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. diff --git a/wowup-electron/package.json b/wowup-electron/package.json index e72ef403..473ec1de 100644 --- a/wowup-electron/package.json +++ b/wowup-electron/package.json @@ -40,6 +40,7 @@ "test": "ng test --watch=false", "test:watch": "ng test", "test:locales": "ng test --watch=false --include='src/locales.spec.ts'", + "test:customprotocol:": "npx electron . \"curseforge://install?addonId=3358^^^&fileId=3240590\"", "e2e": "npm run build:prod && cross-env TS_NODE_PROJECT='e2e/tsconfig.e2e.json' mocha --timeout 300000 --require ts-node/register e2e/**/*.e2e.ts", "version": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md", "lint": "ng lint", diff --git a/wowup-electron/preload.ts b/wowup-electron/preload.ts index e206c5f0..9d2fd646 100644 --- a/wowup-electron/preload.ts +++ b/wowup-electron/preload.ts @@ -39,18 +39,6 @@ function rendererOn(channel: string, listener: (event: IpcRendererEvent, ...args ipcRenderer.on(channel, listener); } -function isDefaultProtocolClient(protocol: string, path?: string, args?: string[]) { - return remote.app.isDefaultProtocolClient(protocol, path, args); -} - -function setAsDefaultProtocolClient(protocol: string, path?: string, args?: string[]) { - return remote.app.setAsDefaultProtocolClient(protocol, path, args); -} - -function removeAsDefaultProtocolClient(protocol: string, path?: string, args?: string[]) { - return remote.app.removeAsDefaultProtocolClient(protocol, path, args); -} - function openExternal(url: string, options?: OpenExternalOptions): Promise { return shell.openExternal(url, options); } @@ -71,7 +59,6 @@ function showOpenDialog(options: OpenDialogOptions): Promise { + return Promise.resolve(undefined); + } + public searchByName( addonName: string, folderName: string, @@ -75,6 +80,10 @@ export abstract class AddonProvider { return false; } + public isValidProtocol(protocol: string): boolean { + return false; + } + public async scan( installation: WowInstallation, addonChannelType: AddonChannelType, 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 153ba15f..906fb367 100644 --- a/wowup-electron/src/app/addon-providers/curse-addon-provider.ts +++ b/wowup-electron/src/app/addon-providers/curse-addon-provider.ts @@ -16,6 +16,7 @@ import { AddonChannelType, AddonDependencyType } from "../../common/wowup/models import { AppConfig } from "../../environments/environment"; import { AppCurseScanResult } from "../models/curse/app-curse-scan-result"; import { + CurseAddonFileResponse, CurseAuthor, CurseDependency, CurseDependencyType, @@ -30,6 +31,7 @@ import { AddonFolder } from "../models/wowup/addon-folder"; import { AddonSearchResult } from "../models/wowup/addon-search-result"; import { AddonSearchResultDependency } from "../models/wowup/addon-search-result-dependency"; import { AddonSearchResultFile } from "../models/wowup/addon-search-result-file"; +import { ProtocolSearchResult } from "../models/wowup/protocol-search-result"; import { WowInstallation } from "../models/wowup/wow-installation"; import { ElectronService } from "../services"; import { CachingService } from "../services/caching/caching-service"; @@ -42,6 +44,11 @@ import { AddonProvider, GetAllResult } from "./addon-provider"; const API_URL = "https://addons-ecs.forgesvc.net/api/v2"; const CHANGELOG_CACHE_TTL_SEC = 30 * 60; +interface ProtocolData { + addonId: number; + fileId: number; +} + export class CurseAddonProvider extends AddonProvider { private readonly _circuitBreaker: CircuitBreakerWrapper; @@ -86,6 +93,51 @@ export class CurseAddonProvider extends AddonProvider { return ""; } + public isValidProtocol(protocol: string): boolean { + return protocol.toLowerCase().startsWith("curseforge://"); + } + + public async searchProtocol(protocol: string): Promise { + const protocolData = this.parseProtocol(protocol); + console.debug("protocolData", protocolData); + if (!protocolData.addonId || !protocolData.fileId) { + throw new Error("Invalid protocol data"); + } + + const addonResult = await this.getByIdBase(protocolData.addonId.toString()).toPromise(); + console.debug("addonResult", addonResult); + if (!addonResult) { + throw new Error(`Failed to get addon data`); + } + + const addonFileResponse = await this.getAddonFileById(protocolData.addonId, protocolData.fileId).toPromise(); + + // const targetFile = _.find(addonResult.latestFiles, (lf) => lf.id === protocolData.fileId); + console.debug("targetFile", addonFileResponse); + if (!addonFileResponse) { + throw new Error("Failed to get target file"); + } + + const searchResult: ProtocolSearchResult = { + protocol, + protocolAddonId: protocolData.addonId.toString(), + protocolReleaseId: protocolData.fileId.toString(), + validClientTypes: this.getValidClientTypes(addonFileResponse.gameVersionFlavor), + ...this.getAddonSearchResult(addonResult, [addonFileResponse]), + }; + console.debug("searchResult", searchResult); + + return searchResult; + } + + private parseProtocol(protocol: string): ProtocolData { + const url = new URL(protocol); + return { + addonId: +url.searchParams.get("addonId"), + fileId: +url.searchParams.get("fileId"), + }; + } + public async getChangelog( installation: WowInstallation, externalId: string, @@ -377,9 +429,7 @@ export class CurseAddonProvider extends AddonProvider { } public getById(addonId: string, installation: WowInstallation): Observable { - const url = `${API_URL}/addon/${addonId}`; - - return from(this._circuitBreaker.getJson(url)).pipe( + return this.getByIdBase(addonId).pipe( map((result) => { if (!result) { return null; @@ -395,6 +445,18 @@ export class CurseAddonProvider extends AddonProvider { ); } + private getByIdBase(addonId: string): Observable { + const url = `${API_URL}/addon/${addonId}`; + + return from(this._circuitBreaker.getJson(url)); + } + + private getAddonFileById(addonId: string | number, fileId: string | number): Observable { + const url = `${API_URL}/addon/${addonId}/file/${fileId}`; + + return from(this._circuitBreaker.getJson(url)); + } + public isValidAddonUri(addonUri: URL): boolean { return addonUri.host && addonUri.host.endsWith("curseforge.com") && addonUri.pathname.startsWith("/wow/addons"); } @@ -581,6 +643,15 @@ export class CurseAddonProvider extends AddonProvider { } } + private getValidClientTypes(gameVersionFlavor: string): WowClientType[] { + switch (gameVersionFlavor) { + case "wow_classic": + return [WowClientType.Classic, WowClientType.ClassicPtr]; + default: + return [WowClientType.Retail, WowClientType.RetailPtr, WowClientType.Beta]; + } + } + private getWowUpChannel(releaseType: CurseReleaseType): AddonChannelType { switch (releaseType) { case CurseReleaseType.Alpha: diff --git a/wowup-electron/src/app/app.component.ts b/wowup-electron/src/app/app.component.ts index 20a7464b..f390f10a 100644 --- a/wowup-electron/src/app/app.component.ts +++ b/wowup-electron/src/app/app.component.ts @@ -132,7 +132,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { map((appOptions) => { this.showPreLoad = false; this.quitEnabled = appOptions.quit; - this.openInstallFromUrlDialog(appOptions.install); this._cdRef.detectChanges(); }), catchError((err) => { @@ -192,8 +191,8 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { this._electronService.applyZoom(ZoomDirection.ZoomReset).catch((e) => console.error(e)); }; - public onRequestInstallFromUrl = async (evt: any, path?: string): Promise => { - await this.openInstallFromUrlDialog(path); + public onRequestInstallFromUrl = (evt: any, path?: string): void => { + this.openInstallFromUrlDialog(path); }; public openDialog(): void { @@ -209,9 +208,12 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { }); } - private async openInstallFromUrlDialog(path?: string) { - if (!path) return; - var dialogRef = await this._dialog.open(InstallFromUrlDialogComponent); + private openInstallFromUrlDialog(path?: string) { + if (!path) { + return; + } + + const dialogRef = this._dialog.open(InstallFromUrlDialogComponent); dialogRef.componentInstance.query = path; } diff --git a/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.html b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.html new file mode 100644 index 00000000..17914f0c --- /dev/null +++ b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.html @@ -0,0 +1,9 @@ +
+ +
+
+
+ {{ getLetter() }} +
+
\ No newline at end of file diff --git a/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.scss b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.scss new file mode 100644 index 00000000..dadb6472 --- /dev/null +++ b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.scss @@ -0,0 +1,26 @@ +.addon-logo-container { + width: 40px; + height: 40px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + flex-shrink: 0; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + overflow: hidden; + + .addon-logo { + height: 100%; + } + + .addon-logo-letter { + font-size: 2em; + font-weight: 400; + } + + img { + height: 100%; + } +} diff --git a/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.spec.ts b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.spec.ts new file mode 100644 index 00000000..a737e31d --- /dev/null +++ b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddonThumbnailComponent } from './addon-thumbnail.component'; + +describe('AddonThumbnailComponent', () => { + let component: AddonThumbnailComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AddonThumbnailComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddonThumbnailComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.ts b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.ts new file mode 100644 index 00000000..e5d82759 --- /dev/null +++ b/wowup-electron/src/app/components/addon-thumbnail/addon-thumbnail.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, OnInit } from "@angular/core"; + +@Component({ + selector: "app-addon-thumbnail", + templateUrl: "./addon-thumbnail.component.html", + styleUrls: ["./addon-thumbnail.component.scss"], +}) +export class AddonThumbnailComponent implements OnInit { + @Input() public url = ""; + @Input() public name = ""; + @Input() public size = 40; + + public constructor() {} + + public ngOnInit(): void {} + + public hasUrl(): boolean { + return !!this.url; + } + + public getLetter(): string { + return this.name?.charAt(0).toUpperCase() ?? ""; + } +} diff --git a/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.html b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.html new file mode 100644 index 00000000..4a4a6f22 --- /dev/null +++ b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.html @@ -0,0 +1,53 @@ +

{{ "DIALOGS.INSTALL_FROM_PROTOCOL.TITLE" | translate:{providerName: + getProviderName()} }}

+
+
+ +
+
+
+ +
+

{{getName()}}

+

{{getAuthor()}}

+

{{getVersion()}}

+
+
+ + + WoW Installation + + {{ installation.label + }} + + + + + +
+
+

Error

+

{{ error | translate:{protocol: data.protocol} }}

+
+
+

{{ 'DIALOGS.INSTALL_FROM_PROTOCOL.ADDON_INSTALLING' | translate }}

+ +
+
+
+ +
+

{{ 'DIALOGS.INSTALL_FROM_PROTOCOL.ADDON_INSTALLED' | translate }}

+
+
+
+ + +
\ No newline at end of file diff --git a/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.scss b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.scss new file mode 100644 index 00000000..5a274c75 --- /dev/null +++ b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.scss @@ -0,0 +1,22 @@ +.content { + min-width: 300px; + + .control { + width: 100%; + } + + .success-icon { + text-align: center; + filter: invert(23%) sepia(99%) saturate(1821%) hue-rotate(104deg) brightness(96%) contrast(106%); + } +} +.error { + color: #f04747; +} + +.select { + .mat-icon { + height: 17px; + width: 17px; + } +} diff --git a/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.spec.ts b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.spec.ts new file mode 100644 index 00000000..5e7f9bc8 --- /dev/null +++ b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstallFromProtocolDialogComponent } from './install-from-protocol-dialog.component'; + +describe('InstallFromProtocolDialogComponent', () => { + let component: InstallFromProtocolDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InstallFromProtocolDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InstallFromProtocolDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.ts b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.ts new file mode 100644 index 00000000..89f8621e --- /dev/null +++ b/wowup-electron/src/app/components/install-from-protocol-dialog/install-from-protocol-dialog.component.ts @@ -0,0 +1,169 @@ +import { AfterViewInit, Component, Inject, OnInit } from "@angular/core"; +import { FormControl } from "@angular/forms"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { ProtocolSearchResult } from "app/models/wowup/protocol-search-result"; +import { WowInstallation } from "app/models/wowup/wow-installation"; +import { SessionService } from "app/services/session/session.service"; +import { WarcraftInstallationService } from "app/services/warcraft/warcraft-installation.service"; +import * as _ from "lodash"; +import { BehaviorSubject, from, of, Subject } from "rxjs"; +import { catchError, delay, filter, first, map, switchMap } from "rxjs/operators"; +import { AddonService } from "../../services/addons/addon.service"; + +export interface InstallFromProtocolDialogComponentData { + protocol: string; +} + +export interface WowInstallationWrapper extends WowInstallation { + isInstalled?: boolean; +} + +const ERROR_ADDON_NOT_FOUND = "DIALOGS.INSTALL_FROM_PROTOCOL.ERRORS.ADDON_NOT_FOUND"; +const ERROR_GENERIC = "DIALOGS.INSTALL_FROM_PROTOCOL.ERRORS.GENERIC"; +const ERROR_NO_VALID_WOW_INSTALLATIONS = "DIALOGS.INSTALL_FROM_PROTOCOL.ERRORS.NO_VALID_WOW_INSTALLATIONS"; + +@Component({ + selector: "app-install-from-protocol-dialog", + templateUrl: "./install-from-protocol-dialog.component.html", + styleUrls: ["./install-from-protocol-dialog.component.scss"], +}) +export class InstallFromProtocolDialogComponent implements OnInit, AfterViewInit { + public error = ""; + public ready = false; + public addon: ProtocolSearchResult; + public installations = new FormControl(); + public validWowInstallations: WowInstallationWrapper[] = []; + public installProgress = 0; + public isInstalling = false; + public isComplete = false; + + public constructor( + private _addonService: AddonService, + private _sessionService: SessionService, + private _warcraftInstallationService: WarcraftInstallationService, + @Inject(MAT_DIALOG_DATA) public data: InstallFromProtocolDialogComponentData, + public dialogRef: MatDialogRef + ) {} + + public ngOnInit(): void {} + + public ngAfterViewInit(): void { + of(true) + .pipe( + first(), + delay(1000), + switchMap(() => from(this.loadAddon())), + catchError((e) => { + console.error(e); + return of(undefined); + }) + ) + .subscribe(); + } + + public getVersion(): string { + return _.first(this.addon?.files)?.version ?? ""; + } + + public getName(): string { + return this.addon?.name ?? ""; + } + + public getThumbnailUrl(): string { + return this.addon?.thumbnailUrl ?? ""; + } + + public getAuthor(): string { + return this.addon?.author ?? "'"; + } + + public getProviderName(): string { + return this.addon?.providerName ?? ""; + } + + public onClose(): void { + this.dialogRef.close(); + } + + public onInstall = async (): Promise => { + console.debug("selectedInstallationId", this.installations.value); + const selectedInstallationIds: string[] = this.installations.value; + const selectedInstallations = this.validWowInstallations.filter((installation) => + selectedInstallationIds.includes(installation.id) + ); + const targetFile = _.first(this.addon.files); + + try { + this.isInstalling = true; + + const totalInstalls = selectedInstallations.length; + let installIdx = 0; + for (const installation of selectedInstallations) { + await this._addonService.installPotentialAddon( + this.addon, + installation, + (state, progress) => { + console.debug("Install Progress", progress); + this.installProgress = (installIdx * 100 + progress) / totalInstalls; + }, + targetFile + ); + installIdx += 1; + this._sessionService.notifyTargetFileInstallComplete(); + } + + this.isComplete = true; + } catch (e) { + console.error(`Failed to install addon for protocol: ${this.data.protocol}`, e); + this.error = ERROR_GENERIC; + } finally { + this.isInstalling = false; + } + }; + + private async loadAddon(): Promise { + try { + const searchResult = await this._addonService.getAddonForProtocol(this.data.protocol); + if (!searchResult) { + this.error = ERROR_ADDON_NOT_FOUND; + return; + } + + this.addon = searchResult; + + this.validWowInstallations = this._warcraftInstallationService.getWowInstallationsByClientTypes( + searchResult.validClientTypes + ); + if (this.validWowInstallations.length === 0) { + this.error = ERROR_NO_VALID_WOW_INSTALLATIONS; + return; + } + + this.validWowInstallations.forEach((installation) => { + installation.isInstalled = this._addonService.isInstalled(this.addon.externalId, installation); + }); + + if (this.validWowInstallations.length === 0) { + return; + } + + const allInstalled = _.every(this.validWowInstallations, (installation) => installation.isInstalled); + if (allInstalled) { + this.isComplete = true; + this.installations.setValue(this.validWowInstallations.map((installation) => installation.id)); + return; + } + + const installationId = _.find(this.validWowInstallations, (installation) => !installation.isInstalled)?.id; + if (!installationId) { + return; + } + this.installations.setValue([installationId]); + } catch (e) { + console.error(`Failed to load protocol addon`, e); + this.error = ERROR_GENERIC; + } finally { + this.ready = true; + } + } +} 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 fc106d5b..b64b0860 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 @@ -1,20 +1,7 @@
-
- -
-
-
- {{ listItem.thumbnailLetter }} -
-
- +
@@ -30,9 +17,9 @@
+ beta: listItem.isBetaChannel(), + alpha: listItem.isAlphaChannel() + }"> {{ channelTranslationKey | translate }}
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 fe83f7e7..d4e22460 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 @@ -63,6 +63,10 @@ export class MyAddonsAddonCellComponent implements AgRendererComponent { this._dialogFactory.getAddonDetailsDialog(this.listItem); } + public getThumbnailUrl(): string { + return this.listItem?.addon?.thumbnailUrl ?? ""; + } + public getRequireDependencyCount(): number { return this.listItem.getDependencies(AddonDependencyType.Required).length; } diff --git a/wowup-electron/src/app/components/options-app-section/options-app-section.component.html b/wowup-electron/src/app/components/options-app-section/options-app-section.component.html index 37fb928d..69925e3f 100644 --- a/wowup-electron/src/app/components/options-app-section/options-app-section.component.html +++ b/wowup-electron/src/app/components/options-app-section/options-app-section.component.html @@ -20,6 +20,7 @@
+
@@ -41,6 +42,7 @@
+
@@ -55,6 +57,7 @@ {{ "PAGES.OPTIONS.APPLICATION.TELEMETRY_DESCRIPTION" | translate }} +
@@ -67,6 +70,7 @@
{{ minimizeOnCloseDescription }} +
@@ -83,6 +87,7 @@ {{ "PAGES.OPTIONS.APPLICATION.ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION" | translate }} +
@@ -95,7 +100,9 @@
- {{ "PAGES.OPTIONS.APPLICATION.USE_HARDWARE_ACCELERATION_DESCRIPTION" | translate }} + +
@@ -109,6 +116,7 @@
{{ "PAGES.OPTIONS.APPLICATION.USE_SYMLINK_SUPPORT_DESCRIPTION" | translate }} +
@@ -123,6 +131,7 @@ (change)="onStartWithSystemChange($event)">
+
@@ -137,30 +146,50 @@ (change)="onStartMinimizedChange($event)">
+
-
-
-
{{ "PAGES.OPTIONS.APPLICATION.SCALE_LABEL" | translate }}
- {{ "PAGES.OPTIONS.APPLICATION.SCALE_DESCRIPTION" | translate }} +
+
+
+
{{ "PAGES.OPTIONS.APPLICATION.SCALE_LABEL" | translate }}
+ {{ "PAGES.OPTIONS.APPLICATION.SCALE_DESCRIPTION" | translate }} +
+ + + + {{ value * 100 | number:'1.0-0' }} % + + +
- - - - {{ value * 100 | number:'1.0-0' }} % - - - +
-
+ + +
+
+
+
+ {{ "PAGES.OPTIONS.APPLICATION.CURSE_PROTOCOL_LABEL" | translate }} +
+ {{ "PAGES.OPTIONS.APPLICATION.CURSE_PROTOCOL_DESCRIPTION" | translate }} +
+ + +
+
\ No newline at end of file diff --git a/wowup-electron/src/app/components/options-app-section/options-app-section.component.scss b/wowup-electron/src/app/components/options-app-section/options-app-section.component.scss index 32ad6eab..abc544b4 100644 --- a/wowup-electron/src/app/components/options-app-section/options-app-section.component.scss +++ b/wowup-electron/src/app/components/options-app-section/options-app-section.component.scss @@ -7,8 +7,16 @@ .hint { color: $white-3; } +} - .toggle { - margin-top: 1em; +.toggle { + margin-top: 1em; + margin-bottom: 20px; + + .divider { + margin-top: 20px; + height: 1px; + border-top: thin solid $dark-1; } } + diff --git a/wowup-electron/src/app/components/options-app-section/options-app-section.component.ts b/wowup-electron/src/app/components/options-app-section/options-app-section.component.ts index 27816184..7bbe5a0f 100644 --- a/wowup-electron/src/app/components/options-app-section/options-app-section.component.ts +++ b/wowup-electron/src/app/components/options-app-section/options-app-section.component.ts @@ -12,6 +12,8 @@ import { ConfirmDialogComponent } from "../confirm-dialog/confirm-dialog.compone import { ALLIANCE_LIGHT_THEME, ALLIANCE_THEME, + APP_PROTOCOL_NAME, + CURSE_PROTOCOL_NAME, DEFAULT_LIGHT_THEME, DEFAULT_THEME, HORDE_LIGHT_THEME, @@ -32,6 +34,9 @@ interface LocaleListItem { styleUrls: ["./options-app-section.component.scss"], }) export class OptionsAppSectionComponent implements OnInit { + public readonly curseProtocolName = CURSE_PROTOCOL_NAME; + public readonly wowupProtocolName = APP_PROTOCOL_NAME; + public collapseToTray = false; public minimizeOnCloseDescription = ""; public startMinimized = false; @@ -77,6 +82,9 @@ export class OptionsAppSectionComponent implements OnInit { }, ]; + public curseforgeProtocolHandled$ = from(this.electronService.isDefaultProtocolClient(CURSE_PROTOCOL_NAME)); + public wowupProtocolHandled$ = from(this.electronService.isDefaultProtocolClient(APP_PROTOCOL_NAME)); + public constructor( private _analyticsService: AnalyticsService, private _dialog: MatDialog, @@ -105,7 +113,6 @@ export class OptionsAppSectionComponent implements OnInit { this.useHardwareAcceleration = this.wowupService.useHardwareAcceleration; this.startWithSystem = this.wowupService.getStartWithSystem(); this.startMinimized = this.wowupService.startMinimized; - this.protocolRegistered = this.wowupService.protocolRegistered; this.currentLanguage = this.wowupService.currentLanguage; this.useSymlinkMode = this.wowupService.useSymlinkMode; @@ -115,6 +122,13 @@ export class OptionsAppSectionComponent implements OnInit { this.currentScale = zoomFactor; this._cdRef.detectChanges(); }); + + this.electronService + .isDefaultProtocolClient(APP_PROTOCOL_NAME) + .then((isDefault) => { + this.protocolRegistered = isDefault; + }) + .catch((e) => console.error(e)); } private async initScale() { @@ -149,11 +163,11 @@ export class OptionsAppSectionComponent implements OnInit { await this.wowupService.setStartMinimized(evt.checked); }; - public onProtocolRegisteredChange = async (evt: MatSlideToggleChange): Promise => { + public onProtocolHandlerChange = async (evt: MatSlideToggleChange, protocol: string): Promise => { try { - await this.wowupService.setProtocolRegistered(evt.checked); + await this.setProtocolHandler(protocol, evt.checked); } catch (e) { - console.error(`onProtocolRegisteredChange failed`, e); + console.error(`onProtocolHandlerChange failed: ${protocol}`, e); } }; @@ -260,4 +274,12 @@ export class OptionsAppSectionComponent implements OnInit { private async updateScale() { this.currentScale = await this.electronService.getZoomFactor(); } + + private setProtocolHandler(protocol: string, enabled: boolean): Promise { + if (enabled) { + return this.electronService.setAsDefaultProtocolClient(protocol); + } else { + return this.electronService.removeAsDefaultProtocolClient(protocol); + } + } } 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 342db255..117244f3 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 @@ -1,15 +1,7 @@
-
- -
-
-
- {{ thumbnailLetter }} -
-
- +
{{ addon.name }} diff --git a/wowup-electron/src/app/models/curse/curse-api.ts b/wowup-electron/src/app/models/curse/curse-api.ts index 33091e68..c69e6151 100644 --- a/wowup-electron/src/app/models/curse/curse-api.ts +++ b/wowup-electron/src/app/models/curse/curse-api.ts @@ -29,6 +29,9 @@ export interface CurseGetFeaturedResponse { RecentlyUpdated: CurseSearchResult[]; } +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface CurseAddonFileResponse extends CurseFile {} + export interface CurseSearchResult { id: number; name: string; diff --git a/wowup-electron/src/app/models/wowup/protocol-search-result.ts b/wowup-electron/src/app/models/wowup/protocol-search-result.ts new file mode 100644 index 00000000..13489991 --- /dev/null +++ b/wowup-electron/src/app/models/wowup/protocol-search-result.ts @@ -0,0 +1,9 @@ +import { 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[]; +} diff --git a/wowup-electron/src/app/pages/home/home.component.ts b/wowup-electron/src/app/pages/home/home.component.ts index b67d27f2..45dfac75 100644 --- a/wowup-electron/src/app/pages/home/home.component.ts +++ b/wowup-electron/src/app/pages/home/home.component.ts @@ -1,11 +1,16 @@ -import { from, interval } from "rxjs"; +import { from, Subscription } from "rxjs"; import { filter, first, map, switchMap, tap } from "rxjs/operators"; -import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, NgZone, OnDestroy } from "@angular/core"; +import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from "@angular/core"; import { MatSnackBar } from "@angular/material/snack-bar"; import { TranslateService } from "@ngx-translate/core"; -import { IPC_POWER_MONITOR_RESUME, IPC_POWER_MONITOR_UNLOCK } from "../../../common/constants"; +import { + APP_PROTOCOL_NAME, + CURSE_PROTOCOL_NAME, + IPC_POWER_MONITOR_RESUME, + IPC_POWER_MONITOR_UNLOCK, +} from "../../../common/constants"; import { AppConfig } from "../../../environments/environment"; import { AddonScanError } from "../../errors"; import { WowInstallation } from "../../models/wowup/wow-installation"; @@ -14,6 +19,9 @@ import { AddonService, ScanUpdate, ScanUpdateType } from "../../services/addons/ import { SessionService } from "../../services/session/session.service"; import { WarcraftInstallationService } from "../../services/warcraft/warcraft-installation.service"; import { WowUpService } from "../../services/wowup/wowup.service"; +import { DialogFactory } from "../../services/dialog/dialog.factory"; +import { getProtocol } from "../../utils/string.utils"; +import { InstallFromProtocolDialogComponent } from "app/components/install-from-protocol-dialog/install-from-protocol-dialog.component"; @Component({ selector: "app-home", @@ -23,6 +31,7 @@ import { WowUpService } from "../../services/wowup/wowup.service"; }) export class HomeComponent implements AfterViewInit, OnDestroy { private _appUpdateInterval?: number; + private _subscriptions: Subscription[] = []; public selectedIndex = 0; public hasWowClient = false; @@ -37,22 +46,61 @@ export class HomeComponent implements AfterViewInit, OnDestroy { private _wowupService: WowUpService, private _snackBar: MatSnackBar, private _cdRef: ChangeDetectorRef, - private _warcraftInstallationService: WarcraftInstallationService + private _warcraftInstallationService: WarcraftInstallationService, + private _dialogFactory: DialogFactory ) { - this._warcraftInstallationService.wowInstallations$.subscribe((installations) => { + const wowInstalledSub = this._warcraftInstallationService.wowInstallations$.subscribe((installations) => { this.hasWowClient = installations.length > 0; this.selectedIndex = this.hasWowClient ? 0 : 3; }); - this._addonService.scanError$.subscribe(this.onAddonScanError); + const customProtocolSub = this.electronService.customProtocol$ + .pipe( + filter((protocol) => !!protocol), + switchMap((protocol) => from(this.handleCustomProtocol(protocol))) + ) + .subscribe(); - this._addonService.scanUpdate$ + const scanErrorSub = this._addonService.scanError$.subscribe(this.onAddonScanError); + + const scanUpdateSub = this._addonService.scanUpdate$ .pipe(filter((update) => update.type !== ScanUpdateType.Unknown)) .subscribe(this.onScanUpdate); + + this._subscriptions.push(customProtocolSub, wowInstalledSub, scanErrorSub, scanUpdateSub); + } + + private handleCustomProtocol = async (protocol: string): Promise => { + console.debug("PROTOCOL RECEIEVED", protocol); + const protocolName = getProtocol(protocol); + try { + switch (protocolName) { + case APP_PROTOCOL_NAME: + 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) { + const dialog = this._dialogFactory.getDialog(InstallFromProtocolDialogComponent, { + disableClose: true, + data: { + protocol, + }, + }); + + await dialog.afterClosed().toPromise(); } public ngAfterViewInit(): void { - this.electronService.powerMonitor$.pipe(filter((evt) => !!evt)).subscribe((evt) => { + const powerMonitorSub = this.electronService.powerMonitor$.pipe(filter((evt) => !!evt)).subscribe((evt) => { console.log("Stopping app update check..."); this.destroyAppUpdateCheck(); @@ -61,9 +109,7 @@ export class HomeComponent implements AfterViewInit, OnDestroy { } }); - this.initAppUpdateCheck(); - - this._warcraftInstallationService.wowInstallations$ + const wowInstallInitialSub = this._warcraftInstallationService.wowInstallations$ .pipe( first((installations) => installations.length > 0), switchMap((installations) => { @@ -74,10 +120,15 @@ export class HomeComponent implements AfterViewInit, OnDestroy { this.appReady = true; this.detectChanges(); }); + + this.initAppUpdateCheck(); + + this._subscriptions.push(powerMonitorSub, wowInstallInitialSub); } public ngOnDestroy(): void { window.clearInterval(this._appUpdateInterval); + this._subscriptions.forEach((sub) => sub.unsubscribe()); } private initAppUpdateCheck() { diff --git a/wowup-electron/src/app/pages/home/home.module.ts b/wowup-electron/src/app/pages/home/home.module.ts index 643c1d64..4c5ccb1c 100644 --- a/wowup-electron/src/app/pages/home/home.module.ts +++ b/wowup-electron/src/app/pages/home/home.module.ts @@ -13,6 +13,7 @@ import { ConfirmDialogComponent } from "../../components/confirm-dialog/confirm- import { FundingButtonComponent } from "../../components/funding-button/funding-button.component"; import { GetAddonStatusColumnComponent } from "../../components/get-addon-status-column/get-addon-status-column.component"; import { InstallFromUrlDialogComponent } from "../../components/install-from-url-dialog/install-from-url-dialog.component"; +import { InstallFromProtocolDialogComponent } from "../../components/install-from-protocol-dialog/install-from-protocol-dialog.component"; import { MyAddonStatusColumnComponent } from "../../components/my-addon-status-column/my-addon-status-column.component"; import { MyAddonsAddonCellComponent } from "../../components/my-addons-addon-cell/my-addons-addon-cell.component"; import { OptionsAddonSectionComponent } from "../../components/options-addon-section/options-addon-section.component"; @@ -24,6 +25,7 @@ import { ProgressButtonComponent } from "../../components/progress-button/progre import { ProgressSpinnerComponent } from "../../components/progress-spinner/progress-spinner.component"; import { TelemetryDialogComponent } from "../../components/telemetry-dialog/telemetry-dialog.component"; import { WowClientOptionsComponent } from "../../components/wow-client-options/wow-client-options.component"; +import { AddonThumbnailComponent } from "../../components/addon-thumbnail/addon-thumbnail.component"; import { TableContextHeaderCellComponent } from "../../components/table-context-header-cell/table-context-header-cell.component"; import { CellWrapTextComponent } from "../../components/cell-wrap-text/cell-wrap-text.component"; import { DirectiveModule } from "../../directive.module"; @@ -59,6 +61,7 @@ import { HomeComponent } from "./home.component"; AlertDialogComponent, WowClientOptionsComponent, InstallFromUrlDialogComponent, + InstallFromProtocolDialogComponent, AddonDetailComponent, AddonInstallButtonComponent, GetAddonStatusColumnComponent, @@ -73,6 +76,7 @@ import { HomeComponent } from "./home.component"; CenteredSnackbarComponent, TableContextHeaderCellComponent, CellWrapTextComponent, + AddonThumbnailComponent, ], imports: [ CommonModule, 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 2acb559a..c546f0ce 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 @@ -13,7 +13,7 @@ import { import * as _ from "lodash"; import { join } from "path"; import { from, Observable, of, Subject, Subscription, zip } from "rxjs"; -import { catchError, debounceTime, map, switchMap, tap } from "rxjs/operators"; +import { catchError, debounceTime, first, map, switchMap, tap } from "rxjs/operators"; import { Overlay, OverlayRef } from "@angular/cdk/overlay"; import { AfterViewInit, ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from "@angular/core"; @@ -195,6 +195,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { this.subscriptions.push( this._sessionService.selectedHomeTab$.subscribe(this.onSelectedTabChange), this._sessionService.addonsChanged$.pipe(switchMap(() => from(this.onRefresh()))).subscribe(), + this._sessionService.targetFileInstallComplete$.pipe(switchMap(() => from(this.onRefresh()))).subscribe(), this.addonService.addonInstalled$.subscribe(this.onAddonInstalledEvent), this.addonService.addonRemoved$.subscribe(this.onAddonRemoved), filterInputSub @@ -531,6 +532,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { this.getRemoveAddonPrompt(addon.name) .afterClosed() .pipe( + first(), switchMap((result) => { if (!result) { return of(undefined); @@ -607,6 +609,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { dialogRef .afterClosed() .pipe( + first(), switchMap((result) => { if (!result) { return of(undefined); @@ -708,6 +711,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { dialogRef .afterClosed() .pipe( + first(), switchMap((result) => { if (!result) { return of(undefined); @@ -907,31 +911,6 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { } finally { this._cdRef.detectChanges(); } - - // return from().pipe( - // map((addons) => { - // const rowData = this.formatAddons(addons); - // this.enableControls = this.calculateControlState(); - - // this.rowData = rowData; - // this.gridApi.setRowData(rowData); - // this.gridApi.redrawRows(); - - // this.isBusy = false; - // this.setPageContextText(); - - // this._cdRef.detectChanges(); - // }), - // catchError((e) => { - // console.error(e); - // this.isBusy = false; - // this.enableControls = this.calculateControlState(); - // return of(undefined); - // }), - // tap(() => { - // this._cdRef.detectChanges(); - // }) - // ); }; private formatAddons(addons: Addon[]): AddonViewModel[] { @@ -977,6 +956,10 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { private onAddonInstalledEvent = (evt: AddonUpdateEvent) => { try { + if (evt.addon.installationId !== this.selectedInstallationId) { + return; + } + if ([AddonInstallState.Complete, AddonInstallState.Error].includes(evt.installState) === false) { this.enableControls = false; return; @@ -1013,11 +996,11 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit { }; private onAddonRemoved = (addonId: string) => { - const addons: AddonViewModel[] = this.rowData.slice(); - const listItemIdx = addons.findIndex((li) => li.addon.id === addonId); - addons.splice(listItemIdx, 1); + const listItemIdx = this._baseRowData.findIndex((li) => li.addon.id === addonId); + this._baseRowData.splice(listItemIdx, 1); - this.rowData = addons; + this.rowData = [...this._baseRowData]; + this._cdRef.detectChanges(); }; private showErrorMessage(title: string, message: string) { diff --git a/wowup-electron/src/app/services/addons/addon.service.ts b/wowup-electron/src/app/services/addons/addon.service.ts index 7e29e82d..fc513a49 100644 --- a/wowup-electron/src/app/services/addons/addon.service.ts +++ b/wowup-electron/src/app/services/addons/addon.service.ts @@ -32,10 +32,12 @@ import { AddonSearchResult } from "../../models/wowup/addon-search-result"; import { AddonSearchResultDependency } from "../../models/wowup/addon-search-result-dependency"; import { AddonSearchResultFile } from "../../models/wowup/addon-search-result-file"; import { AddonUpdateEvent } from "../../models/wowup/addon-update-event"; +import { ProtocolSearchResult } from "../../models/wowup/protocol-search-result"; import { Toc } from "../../models/wowup/toc"; import { WowInstallation } from "../../models/wowup/wow-installation"; import * as AddonUtils from "../../utils/addon.utils"; import { getEnumName } from "../../utils/enum.utils"; +import * as SearchResults from "../../utils/search-result.utils"; import { capitalizeString } from "../../utils/string.utils"; import { AnalyticsService } from "../analytics/analytics.service"; import { DownloadService } from "../download/download.service"; @@ -46,7 +48,6 @@ import { WarcraftInstallationService } from "../warcraft/warcraft-installation.s import { WarcraftService } from "../warcraft/warcraft.service"; import { WowUpService } from "../wowup/wowup.service"; import { AddonProviderFactory } from "./addon.provider.factory"; -import * as SearchResults from "../../utils/search-result.utils"; export enum ScanUpdateType { Start, @@ -312,14 +313,20 @@ export class AddonService { public async installPotentialAddon( potentialAddon: AddonSearchResult, installation: WowInstallation, - onUpdate: (installState: AddonInstallState, progress: number) => void = undefined + onUpdate: (installState: AddonInstallState, progress: number) => void = undefined, + targetFile?: AddonSearchResultFile ): Promise { const existingAddon = this._addonStorage.getByExternalId(potentialAddon.externalId, installation.id); if (existingAddon) { throw new Error("Addon already installed"); } - const addon = await this.getAddon(potentialAddon.externalId, potentialAddon.providerName, installation).toPromise(); + const addon = await this.getAddon( + potentialAddon.externalId, + potentialAddon.providerName, + installation, + targetFile + ).toPromise(); this._addonStorage.set(addon.id, addon); await this.installAddon(addon.id, onUpdate); @@ -477,8 +484,6 @@ export class AddonService { }; this._installQueue.next(installQueueItem); - onUpdate?.call(this, AddonInstallState.Pending, 0); - return promise; } @@ -816,7 +821,8 @@ export class AddonService { public getAddon( externalId: string, providerName: string, - installation: WowInstallation + installation: WowInstallation, + targetFile?: AddonSearchResultFile ): Observable { const targetAddonChannel = installation.defaultAddonChannelType; const provider = this.getProvider(providerName); @@ -827,7 +833,8 @@ export class AddonService { } const latestFile = SearchResults.getLatestFile(searchResult, targetAddonChannel); - return this.createAddon(latestFile.folders[0], searchResult, latestFile, installation); + const newAddon = this.createAddon(latestFile.folders[0], searchResult, targetFile ?? latestFile, installation); + return newAddon; }) ); } @@ -920,6 +927,19 @@ export class AddonService { return addons; } + public async getAddonForProtocol(protocol: string): Promise { + const addonProvider = this.getAddonProviderForProtocol(protocol); + if (!addonProvider) { + throw new Error(`No addon provider found for protocol ${protocol}`); + } + + return await addonProvider.searchProtocol(protocol); + } + + private getAddonProviderForProtocol(protocol: string): AddonProvider { + return _.find(this.getEnabledAddonProviders(), (provider) => provider.isValidProtocol(protocol)); + } + public async syncAllClients(): Promise { const installations = this._warcraftInstallationService.getWowInstallations(); for (const installation of installations) { diff --git a/wowup-electron/src/app/services/dialog/dialog.factory.ts b/wowup-electron/src/app/services/dialog/dialog.factory.ts index 7d7b7850..0898f659 100644 --- a/wowup-electron/src/app/services/dialog/dialog.factory.ts +++ b/wowup-electron/src/app/services/dialog/dialog.factory.ts @@ -1,5 +1,6 @@ +import { ComponentType } from "@angular/cdk/portal"; import { Injectable } from "@angular/core"; -import { MatDialog, MatDialogRef } from "@angular/material/dialog"; +import { MatDialog, MatDialogConfig, MatDialogRef } from "@angular/material/dialog"; import { TranslateService } from "@ngx-translate/core"; import { AddonChannelType } from "../../../common/wowup/models"; @@ -62,4 +63,8 @@ export class DialogFactory { data, }); } + + public getDialog(component: ComponentType, config?: MatDialogConfig): MatDialogRef { + return this._dialog.open(component, config); + } } diff --git a/wowup-electron/src/app/services/electron/electron.service.ts b/wowup-electron/src/app/services/electron/electron.service.ts index e448a86d..861624bd 100644 --- a/wowup-electron/src/app/services/electron/electron.service.ts +++ b/wowup-electron/src/app/services/electron/electron.service.ts @@ -1,18 +1,28 @@ +// 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 { IpcRendererEvent, OpenDialogOptions, OpenDialogReturnValue, OpenExternalOptions, Settings } from "electron"; +import { LoginItemSettings } from "electron/main"; +import { find } from "lodash"; +import * as minimist from "minimist"; +import { BehaviorSubject } from "rxjs"; +import { v4 as uuidv4 } from "uuid"; + import { Injectable } from "@angular/core"; + import { APP_UPDATE_CHECK_END, APP_UPDATE_CHECK_START, APP_UPDATE_DOWNLOADED, APP_UPDATE_START_DOWNLOAD, IPC_CLOSE_WINDOW, + IPC_CUSTOM_PROTOCOL_RECEIVED, + IPC_FOCUS_WINDOW, IPC_GET_APP_VERSION, IPC_GET_LAUNCH_ARGS, IPC_GET_LOCALE, IPC_GET_LOGIN_ITEM_SETTINGS, IPC_GET_ZOOM_FACTOR, - IPC_SET_LOGIN_ITEM_SETTINGS, - IPC_SET_ZOOM_FACTOR, - IPC_SET_ZOOM_LIMITS, + IPC_IS_DEFAULT_PROTOCOL_CLIENT, IPC_MAXIMIZE_WINDOW, IPC_MINIMIZE_WINDOW, IPC_POWER_MONITOR_LOCK, @@ -20,33 +30,27 @@ import { IPC_POWER_MONITOR_SUSPEND, IPC_POWER_MONITOR_UNLOCK, IPC_QUIT_APP, + IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT, IPC_RESTART_APP, + IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT, + IPC_SET_LOGIN_ITEM_SETTINGS, + IPC_SET_ZOOM_FACTOR, + IPC_SET_ZOOM_LIMITS, IPC_WINDOW_LEAVE_FULLSCREEN, IPC_WINDOW_MAXIMIZED, IPC_WINDOW_MINIMIZED, IPC_WINDOW_UNMAXIMIZED, ZOOM_FACTOR_KEY, - IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT, - IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT, - APP_PROTOCOL_NAME, - IPC_FOCUS_WINDOW, } from "../../../common/constants"; -import * as minimist from "minimist"; -import * as log from "electron-log"; -// 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 { IpcRendererEvent, OpenDialogOptions, OpenDialogReturnValue, OpenExternalOptions, Settings } from "electron"; -import { BehaviorSubject } from "rxjs"; -import { v4 as uuidv4 } from "uuid"; import { IpcRequest } from "../../../common/models/ipc-request"; import { IpcResponse } from "../../../common/models/ipc-response"; import { ValueRequest } from "../../../common/models/value-request"; import { ValueResponse } from "../../../common/models/value-response"; -import { AppOptions } from "../../../common/wowup/models"; -import { ZoomDirection, ZOOM_SCALE } from "../../utils/zoom.utils"; -import { PreferenceStorageService } from "../storage/preference-storage.service"; import { MainChannels, RendererChannels } from "../../../common/wowup"; -import { LoginItemSettings } from "electron/main"; +import { AppOptions } from "../../../common/wowup/models"; +import { isProtocol } from "../../utils/string.utils"; +import { ZOOM_SCALE, ZoomDirection } from "../../utils/zoom.utils"; +import { PreferenceStorageService } from "../storage/preference-storage.service"; @Injectable({ providedIn: "root", @@ -57,6 +61,7 @@ export class ElectronService { private readonly _ipcEventReceivedSrc = new BehaviorSubject(""); private readonly _zoomFactorChangeSrc = new BehaviorSubject(1.0); private readonly _powerMonitorSrc = new BehaviorSubject(""); + private readonly _customProtocolSrc = new BehaviorSubject(""); private _appVersion = ""; @@ -65,6 +70,7 @@ export class ElectronService { public readonly ipcEventReceived$ = this._ipcEventReceivedSrc.asObservable(); public readonly zoomFactor$ = this._zoomFactorChangeSrc.asObservable(); public readonly powerMonitor$ = this._powerMonitorSrc.asObservable(); + public readonly customProtocol$ = this._customProtocolSrc.asObservable(); public readonly isWin = process.platform === "win32"; public readonly isMac = process.platform === "darwin"; public readonly isLinux = process.platform === "linux"; @@ -121,6 +127,11 @@ export class ElectronService { this._windowMaximizedSrc.next(false); }); + this.onRendererEvent(IPC_CUSTOM_PROTOCOL_RECEIVED, (evt, protocol: string) => { + console.debug(IPC_CUSTOM_PROTOCOL_RECEIVED, protocol); + this._customProtocolSrc.next(protocol); + }); + this.onRendererEvent(IPC_POWER_MONITOR_LOCK, () => { console.log("POWER_MONITOR_LOCK received"); this._powerMonitorSrc.next(IPC_POWER_MONITOR_LOCK); @@ -180,33 +191,32 @@ export class ElectronService { return this.invoke(IPC_SET_LOGIN_ITEM_SETTINGS, settings); } - public setAsDefaultProtocolClient(): Promise { - return this.invoke(IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT); + public isDefaultProtocolClient(protocol: string): Promise { + return this.invoke(IPC_IS_DEFAULT_PROTOCOL_CLIENT, protocol); } - public async removeAsDefaultProtocolClient(): Promise { - return this.invoke(IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT); + public setAsDefaultProtocolClient(protocol: string): Promise { + return this.invoke(IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT, protocol); + } + + public async removeAsDefaultProtocolClient(protocol: string): Promise { + return this.invoke(IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT, protocol); } public async getAppOptions(): Promise { + // TODO check protocols here const launchArgs = await this.invoke(IPC_GET_LAUNCH_ARGS); - let opts; - - opts = (minimist(launchArgs.slice(1), { + const opts = (minimist(launchArgs.slice(1), { boolean: ["hidden", "quit"], string: ["install"], })) as AppOptions; - launchArgs.slice(1).forEach((arg) => { - try { - const url = new URL(arg); - if (url && url.protocol == APP_PROTOCOL_NAME + ":") { - opts = ({ install: url.searchParams.get("install") }) as AppOptions; - } - } catch (e) { - console.error(e); - } - }); + // Find the first protocol arg if any exist + const customProtocol = find(launchArgs, (arg) => isProtocol(arg)); + if (customProtocol) { + // If we did get a custom protocol notify the app + this._customProtocolSrc.next(customProtocol); + } return opts; } @@ -256,18 +266,6 @@ export class ElectronService { return new Notification(title, options); } - public isHandlingProtocol(protocol: string): boolean { - return window.wowup.isDefaultProtocolClient(protocol); - } - - public setHandleProtocol(protocol: string, enable: boolean): boolean { - if (enable) { - return window.wowup.setAsDefaultProtocolClient(protocol); - } else { - return window.wowup.removeAsDefaultProtocolClient(protocol); - } - } - public showOpenDialog(options: OpenDialogOptions): Promise { return window.wowup.showOpenDialog(options); } diff --git a/wowup-electron/src/app/services/icons/icon.service.ts b/wowup-electron/src/app/services/icons/icon.service.ts index 65ac12c3..7689c3e3 100644 --- a/wowup-electron/src/app/services/icons/icon.service.ts +++ b/wowup-electron/src/app/services/icons/icon.service.ts @@ -20,6 +20,7 @@ import { faCompressArrowsAlt, faPencilAlt, faArrowDown, + faCheckCircle, } from "@fortawesome/free-solid-svg-icons"; import { faQuestionCircle, faClock } from "@fortawesome/free-regular-svg-icons"; import { faDiscord, faGithub, faPatreon } from "@fortawesome/free-brands-svg-icons"; @@ -51,6 +52,7 @@ export class IconService { this.addSvg(faCoins); this.addSvg(faCompressArrowsAlt); this.addSvg(faPencilAlt); + this.addSvg(faCheckCircle); } private addSvg(icon: IconDefinition): void { diff --git a/wowup-electron/src/app/services/session/session.service.ts b/wowup-electron/src/app/services/session/session.service.ts index 12c32562..31cd9588 100644 --- a/wowup-electron/src/app/services/session/session.service.ts +++ b/wowup-electron/src/app/services/session/session.service.ts @@ -1,6 +1,6 @@ import * as _ from "lodash"; import { BehaviorSubject, Subject } from "rxjs"; -import { filter, first, map } from "rxjs/operators"; +import { filter } from "rxjs/operators"; import { Injectable } from "@angular/core"; @@ -8,10 +8,7 @@ import { SELECTED_DETAILS_TAB_KEY } from "../../../common/constants"; import { WowInstallation } from "../../models/wowup/wow-installation"; import { PreferenceStorageService } from "../storage/preference-storage.service"; import { WarcraftInstallationService } from "../warcraft/warcraft-installation.service"; -import { WarcraftService } from "../warcraft/warcraft.service"; -import { WowUpService } from "../wowup/wowup.service"; import { ColumnState } from "../../models/wowup/column-state"; -import { TranslateService } from "@ngx-translate/core"; @Injectable({ providedIn: "root", @@ -24,6 +21,7 @@ export class SessionService { private readonly _autoUpdateCompleteSrc = new BehaviorSubject(0); private readonly _addonsChangedSrc = new Subject(); private readonly _myAddonsColumnsSrc = new BehaviorSubject([]); + private readonly _targetFileInstallCompleteSrc = new Subject(); private readonly _getAddonsColumnsSrc = new Subject(); @@ -37,13 +35,11 @@ export class SessionService { public readonly addonsChanged$ = this._addonsChangedSrc.asObservable(); public readonly myAddonsHiddenColumns$ = this._myAddonsColumnsSrc.asObservable(); public readonly getAddonsHiddenColumns$ = this._getAddonsColumnsSrc.asObservable(); + public readonly targetFileInstallComplete$ = this._targetFileInstallCompleteSrc.asObservable(); public constructor( - private _warcraftService: WarcraftService, - private _wowUpService: WowUpService, private _warcraftInstallationService: WarcraftInstallationService, - private _preferenceStorageService: PreferenceStorageService, - private _translateService: TranslateService + private _preferenceStorageService: PreferenceStorageService ) { this._selectedDetailTabType = this._preferenceStorageService.getObject(SELECTED_DETAILS_TAB_KEY) || "description"; @@ -53,6 +49,10 @@ export class SessionService { .subscribe((installations) => this.onWowInstallationsChange(installations)); } + public notifyTargetFileInstallComplete(): void { + this._targetFileInstallCompleteSrc.next(true); + } + public notifyAddonsChanged(): void { this._addonsChangedSrc.next(true); } diff --git a/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts b/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts index d7eb710e..af31d3c5 100644 --- a/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts +++ b/wowup-electron/src/app/services/warcraft/warcraft-installation.service.ts @@ -68,6 +68,10 @@ export class WarcraftInstallationService { return _.filter(this.getWowInstallations(), (installation) => installation.clientType === clientType); } + public getWowInstallationsByClientTypes(clientTypes: WowClientType[]): WowInstallation[] { + return _.filter(this.getWowInstallations(), (installation) => clientTypes.includes(installation.clientType)); + } + public setWowInstallations(wowInstallations: WowInstallation[]): void { console.log(`Setting wow installations: ${wowInstallations.length}`); this._preferenceStorageService.setObject(WOW_INSTALLATIONS_KEY, wowInstallations); diff --git a/wowup-electron/src/app/services/wowup/wowup.service.ts b/wowup-electron/src/app/services/wowup/wowup.service.ts index 53c12ba0..2c30cdfe 100644 --- a/wowup-electron/src/app/services/wowup/wowup.service.ts +++ b/wowup-electron/src/app/services/wowup/wowup.service.ts @@ -235,19 +235,6 @@ export class WowUpService { await this.setAutoStartup(); } - public get protocolRegistered(): boolean { - const preference = this._preferenceStorageService.findByKey(PROTOCOL_REGISTERED_PREFERENCE_KEY); - return preference === "true"; - } - - public async setProtocolRegistered(value: boolean): Promise { - const key = PROTOCOL_REGISTERED_PREFERENCE_KEY; - this._preferenceStorageService.set(key, value); - this._preferenceChangeSrc.next({ key, value: value.toString() }); - - await this.registerProtocol(); - } - public get wowUpReleaseChannel(): WowUpReleaseChannelType { const preference = this._preferenceStorageService.findByKey(WOWUP_RELEASE_CHANNEL_PREFERENCE_KEY); return parseInt(preference, 10) as WowUpReleaseChannelType; @@ -479,12 +466,4 @@ export class WowUpService { }); } } - - private registerProtocol(): Promise { - if (this.protocolRegistered) { - return this._electronService.setAsDefaultProtocolClient(); - } else { - return this._electronService.removeAsDefaultProtocolClient(); - } - } } diff --git a/wowup-electron/src/app/utils/string.utils.ts b/wowup-electron/src/app/utils/string.utils.ts index 172a0d87..60ebb891 100644 --- a/wowup-electron/src/app/utils/string.utils.ts +++ b/wowup-electron/src/app/utils/string.utils.ts @@ -19,3 +19,12 @@ export function getSha1Hash(str: string): string { export function capitalizeString(str: string): string { return str.charAt(0).toUpperCase() + str.toLowerCase().slice(1); } + +export function isProtocol(arg: string): boolean { + return getProtocol(arg) != null; +} + +export function getProtocol(arg: string): string | null { + const match = /^([a-z][a-z0-9+\-.]*):/.exec(arg); + return match !== null && match.length > 1 ? match[1] : null; +} diff --git a/wowup-electron/src/assets/i18n/cs.json b/wowup-electron/src/assets/i18n/cs.json index e1e6be17..7cac32dd 100644 --- a/wowup-electron/src/assets/i18n/cs.json +++ b/wowup-electron/src/assets/i18n/cs.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "Ne", "POSITIVE_BUTTON": "Ano" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "URL adresa addonu", "ADDON_URL_INPUT_PLACEHOLDER": "GitHub nebo WowInterface URL adresa", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Jazyk", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Povolí různé systémové notifikace, např. po automatické aktualizaci addonů.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Povolit systémové notifikace", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "Minimalizovat do lišty při uzavírání WowUp okna.", diff --git a/wowup-electron/src/assets/i18n/de.json b/wowup-electron/src/assets/i18n/de.json index ee473adf..04a71819 100644 --- a/wowup-electron/src/assets/i18n/de.json +++ b/wowup-electron/src/assets/i18n/de.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "Nein", "POSITIVE_BUTTON": "Ja" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "Addon-URL", "ADDON_URL_INPUT_PLACEHOLDER": "z.B. GitHub- oder WoWInterface-URL", @@ -333,13 +345,15 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Aktuelle Sprache", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Aktivieren/Deaktivieren verschiedener Systembenachrichtigungen", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Systembenachrichtigungen aktivieren", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "Beim Schließen WowUp in die Menüleiste minimieren", "MINIMIZE_ON_CLOSE_DESCRIPTION_WINDOWS": "Beim Schließen des WowUp-Fensters in den Benachrichtigungsbereich der Taskleiste minimieren.", + "MINIMIZE_ON_CLOSE_LABEL": "Minimieren beim Schließen", "PROTOCOL_DESCRIPTION": "WowUp will register a custom URI protocol in your system and handle its incoming inquries", "PROTOCOL_LABEL": "Allow WowUp to handle wowup:// URI", - "MINIMIZE_ON_CLOSE_LABEL": "Minimieren beim Schließen", "SCALE_DESCRIPTION": "Den Zoomfaktor für die ganze Anwendung verändern.", "SCALE_LABEL": "Skalierung", "SET_LANGUAGE_CONFIRMATION_DESCRIPTION": "Zum Ändern der Standardsprache muss die Anwendung neu gestartet werden.", diff --git a/wowup-electron/src/assets/i18n/en.json b/wowup-electron/src/assets/i18n/en.json index 8e3b8edf..4d592108 100644 --- a/wowup-electron/src/assets/i18n/en.json +++ b/wowup-electron/src/assets/i18n/en.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "No", "POSITIVE_BUTTON": "Yes" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "Addon URL", "ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub or WowInterface URL", @@ -333,12 +345,14 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Current Language", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Enable various system notification popups, such as auto updated addons.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Enable system notifications", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "When closing the WowUp window, minimize to the menu bar.", "MINIMIZE_ON_CLOSE_DESCRIPTION_WINDOWS": "When closing the WowUp window, minimize to the taskbar notification area.", "MINIMIZE_ON_CLOSE_LABEL": "Minimize on Close", - "PROTOCOL_DESCRIPTION": "WowUp will register a custom URI protocol in your system and handle its incoming inquries", + "PROTOCOL_DESCRIPTION": "WowUp will register a custom URI protocol in your system and handle its incoming inquiries", "PROTOCOL_LABEL": "Enable the wowup:// URI protocol", "SCALE_DESCRIPTION": "Change zoom factor for entire app.", "SCALE_LABEL": "Scale", @@ -356,7 +370,7 @@ "THEME_LABEL": "Color Theme", "TITLE": "Application", "USE_HARDWARE_ACCELERATION_CONFIRMATION_LABEL": "Do you want to restart?", - "USE_HARDWARE_ACCELERATION_DESCRIPTION": "Disabling hardware acceleration might solve FPS issues and fix other rendering issues in this app. Changing this setting requires a restart.", + "USE_HARDWARE_ACCELERATION_DESCRIPTION": "Disabling hardware acceleration might solve FPS issues and fix other rendering issues in this app.
Changing this setting requires a restart.", "USE_HARDWARE_ACCELERATION_DISABLE_CONFIRMATION_DESCRIPTION": "Disabling hardware acceleration requires the application to restart.", "USE_HARDWARE_ACCELERATION_ENABLE_CONFIRMATION_DESCRIPTION": "Enabling hardware acceleration requires the application to restart.", "USE_HARDWARE_ACCELERATION_LABEL": "Enable Hardware Acceleration", diff --git a/wowup-electron/src/assets/i18n/es.json b/wowup-electron/src/assets/i18n/es.json index be377b8a..0f7701f0 100644 --- a/wowup-electron/src/assets/i18n/es.json +++ b/wowup-electron/src/assets/i18n/es.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "No", "POSITIVE_BUTTON": "Sí" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "URL del addon", "ADDON_URL_INPUT_PLACEHOLDER": "Ejemplo: URL de GitHub o WowInterface", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Idioma actual", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Activa varios mensajes de notificación del sistema, tales como cuando los addons son actualizados automáticamente.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Activar notificaciones del sistema", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "Al cerrar la ventana de WowUp, minimizarla a la barra de menú", diff --git a/wowup-electron/src/assets/i18n/fr.json b/wowup-electron/src/assets/i18n/fr.json index d938d8db..b4566b14 100644 --- a/wowup-electron/src/assets/i18n/fr.json +++ b/wowup-electron/src/assets/i18n/fr.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "Non", "POSITIVE_BUTTON": "Oui" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "URL de l'Addon", "ADDON_URL_INPUT_PLACEHOLDER": "Ex. URL de GitHub ou WowInterface", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Langue actuelle", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Activer les fenêtres contextuelles système comme la mise à jour auto des addons.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Activer le système de notification", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "Lorsque vous fermez la fenêtre WowUp, minimise dans la barre des menus", diff --git a/wowup-electron/src/assets/i18n/it.json b/wowup-electron/src/assets/i18n/it.json index e74d9bbf..aec5302a 100644 --- a/wowup-electron/src/assets/i18n/it.json +++ b/wowup-electron/src/assets/i18n/it.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "No", "POSITIVE_BUTTON": "Sì" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "Addon URL", "ADDON_URL_INPUT_PLACEHOLDER": "Esempio URL di GitHub o WowInterface", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Lingua Corrente", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Abilita i vari popup di notifica del sistema, come gli addons aggiornati automaticamente.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Notifiche di Sistema", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "Alla chiusura, WowUp verrà ridotto a icona nella barra dei menu.", diff --git a/wowup-electron/src/assets/i18n/ko.json b/wowup-electron/src/assets/i18n/ko.json index e5ba4060..4c693182 100644 --- a/wowup-electron/src/assets/i18n/ko.json +++ b/wowup-electron/src/assets/i18n/ko.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "취소", "POSITIVE_BUTTON": "확인" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "애드온 URL 주소", "ADDON_URL_INPUT_PLACEHOLDER": "예) GitHub 또는 WowInterface URL", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "현재 언어", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "애드온 자동업데이트 등과 같은 다양한 시스템 알림 팝업 활성화 여부", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "시스템 알림 활성화", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "창을 닫으면 메뉴바로 최소화", diff --git a/wowup-electron/src/assets/i18n/nb.json b/wowup-electron/src/assets/i18n/nb.json index ba0edefd..ebd12761 100644 --- a/wowup-electron/src/assets/i18n/nb.json +++ b/wowup-electron/src/assets/i18n/nb.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "Nei", "POSITIVE_BUTTON": "Ja" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "Utvidelsens URL", "ADDON_URL_INPUT_PLACEHOLDER": "F.Eks. GitHub eller WowInterface URL", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Current Language", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Aktiver/Deaktiver forskjellige systemvarsler, foreksempel: automatisk oppdatering av addons.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Aktiver systemvarsler", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "Når WowUp-vinduet lukkes, minimer til menylinjen.", diff --git a/wowup-electron/src/assets/i18n/pt.json b/wowup-electron/src/assets/i18n/pt.json index 32ff083e..b021e2d2 100644 --- a/wowup-electron/src/assets/i18n/pt.json +++ b/wowup-electron/src/assets/i18n/pt.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "Não", "POSITIVE_BUTTON": "Sim" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "URL do Addon ", "ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub ou WowInterface URL", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Língua Atual", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Habilitar várias notificações e popups do sistema, como os de aviso de Addons atualizados automaticamente.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Habilitar notificações do sistema", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "Ao fechar a janela do WowUp, minimize para a barra de menu", diff --git a/wowup-electron/src/assets/i18n/ru.json b/wowup-electron/src/assets/i18n/ru.json index 5886ce36..56181e31 100644 --- a/wowup-electron/src/assets/i18n/ru.json +++ b/wowup-electron/src/assets/i18n/ru.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "Нет", "POSITIVE_BUTTON": "Да" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "Ссылка на модификацию", "ADDON_URL_INPUT_PLACEHOLDER": "Например ссылки GitHub или WowInterface", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "Текущий язык", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "Включить различные окна системных уведомлений, такие как автоматически обновлённые модификации.", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "Включить системные уведомления", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "При закрытии окна WowUp сворачивается в меню статуса.", diff --git a/wowup-electron/src/assets/i18n/zh-TW.json b/wowup-electron/src/assets/i18n/zh-TW.json index 410d2060..d602968e 100644 --- a/wowup-electron/src/assets/i18n/zh-TW.json +++ b/wowup-electron/src/assets/i18n/zh-TW.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "否", "POSITIVE_BUTTON": "是" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "插件 URL", "ADDON_URL_INPUT_PLACEHOLDER": "例如,GitHub 或 WowInterface URL", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "當前語言", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "啟用各種系統通知彈窗,如自動更新插件通知。", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "啟用系統通知", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "關閉 WowUp 視窗時,最小化到選單欄。", diff --git a/wowup-electron/src/assets/i18n/zh.json b/wowup-electron/src/assets/i18n/zh.json index e47b0871..cb4af083 100644 --- a/wowup-electron/src/assets/i18n/zh.json +++ b/wowup-electron/src/assets/i18n/zh.json @@ -178,6 +178,18 @@ "NEGATIVE_BUTTON": "否", "POSITIVE_BUTTON": "是" }, + "INSTALL_FROM_PROTOCOL": { + "ADDON_INSTALLED": "Addon is installed!", + "ADDON_INSTALLING": "Installing addon", + "CANCEL_BUTTON": "Close", + "ERRORS": { + "ADDON_NOT_FOUND": "No addon was found for the protocol: {protocol}", + "GENERIC": "Error fetching data for protocol: {protocol}", + "NO_VALID_WOW_INSTALLATIONS": "No WoW client installed for protocol: {protocol}" + }, + "INSTALL_BUTTON": "Install", + "TITLE": "Install Addon From {providerName}" + }, "INSTALL_FROM_URL": { "ADDON_URL_INPUT_LABEL": "插件 URL", "ADDON_URL_INPUT_PLACEHOLDER": "例如,GitHub 或 WowInterface URL", @@ -333,6 +345,8 @@ }, "APPLICATION": { "CURRENT_LANGUAGE_LABEL": "当前语言", + "CURSE_PROTOCOL_DESCRIPTION": "When downloading addons from the CurseForge website, WowUp will handle the install", + "CURSE_PROTOCOL_LABEL": "Handle CurseForge download links", "ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION": "启用各种系统通知弹窗,如自动更新插件通知。", "ENABLE_SYSTEM_NOTIFICATIONS_LABEL": "启用系统通知", "MINIMIZE_ON_CLOSE_DESCRIPTION_MAC": "关闭 WowUp 窗口时,最小化到菜单栏。", diff --git a/wowup-electron/src/common/constants.ts b/wowup-electron/src/common/constants.ts index f5386f90..25f6f2e3 100644 --- a/wowup-electron/src/common/constants.ts +++ b/wowup-electron/src/common/constants.ts @@ -1,3 +1,5 @@ +export const APP_USER_MODEL_ID = "io.wowup.jliddev"; // Bundle ID + export const ADDON_PROVIDER_WOWINTERFACE = "WowInterface"; export const ADDON_PROVIDER_CURSEFORGE = "Curse"; export const ADDON_PROVIDER_GITHUB = "GitHub"; @@ -7,7 +9,9 @@ export const ADDON_PROVIDER_UNKNOWN = "Unknown"; export const ADDON_PROVIDER_HUB_LEGACY = "Hub"; export const ADDON_PROVIDER_HUB = "WowUpHub"; export const ADDON_PROVIDER_ZIP = "Zip"; + export const APP_PROTOCOL_NAME = "wowup"; +export const CURSE_PROTOCOL_NAME = "curseforge"; // IPC CHANNELS export const IPC_DOWNLOAD_FILE_CHANNEL = "download-file"; @@ -60,9 +64,11 @@ export const IPC_GET_LOGIN_ITEM_SETTINGS = "get-login-item-settings"; export const IPC_SET_LOGIN_ITEM_SETTINGS = "set-login-item-settings"; export const IPC_LIST_ENTRIES = "list-entries"; export const IPC_READDIR = "readdir"; +export const IPC_IS_DEFAULT_PROTOCOL_CLIENT = "is-default-protocol-client"; export const IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT = "set-as-default-protocol-client"; export const IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT = "remove-as-default-protocol-client"; export const IPC_REQUEST_INSTALL_FROM_URL = "request-install-from-url"; +export const IPC_CUSTOM_PROTOCOL_RECEIVED = "custom-protocol-received"; export const IPC_ADDONS_SAVE_ALL = "addons-save-all"; // PREFERENCES diff --git a/wowup-electron/src/common/wowup.d.ts b/wowup-electron/src/common/wowup.d.ts index 01500cef..5112b13d 100644 --- a/wowup-electron/src/common/wowup.d.ts +++ b/wowup-electron/src/common/wowup.d.ts @@ -16,7 +16,8 @@ declare type MainChannels = | "power-monitor-suspend" | "power-monitor-lock" | "power-monitor-unlock" - | "request-install-from-url"; + | "request-install-from-url" + | "custom-protocol-received"; // Events that can be sent from renderer to main declare type RendererChannels = @@ -56,6 +57,7 @@ declare type RendererChannels = | "list-entries" | "list-files" | "readdir" + | "is-default-protocol-client" | "set-as-default-protocol-client" | "remove-as-default-protocol-client" | "read-file-buffer" @@ -76,9 +78,6 @@ declare global { rendererInvoke: (channel: string, ...args: any[]) => Promise; rendererOff: (event: string | symbol, listener: (...args: any[]) => void) => void; rendererOn: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) => void; - isDefaultProtocolClient: (protocol: string, path?: string, args?: string[]) => boolean; - setAsDefaultProtocolClient: (protocol: string, path?: string, args?: string[]) => boolean; - removeAsDefaultProtocolClient: (protocol: string, path?: string, args?: string[]) => boolean; openExternal: (url: string, options?: OpenExternalOptions) => Promise; showOpenDialog: (options: OpenDialogOptions) => Promise; openPath: (path: string) => Promise; diff --git a/wowup-electron/src/common/wowup/models.ts b/wowup-electron/src/common/wowup/models.ts index 1534ce93..b02ddf6c 100644 --- a/wowup-electron/src/common/wowup/models.ts +++ b/wowup-electron/src/common/wowup/models.ts @@ -24,7 +24,6 @@ export interface AppOptions { serve?: boolean; hidden?: boolean; quit?: boolean; - install?: string; } export interface MenuConfig { diff --git a/wowup-electron/src/styles.scss b/wowup-electron/src/styles.scss index bfb2cf1a..2ae1fd04 100644 --- a/wowup-electron/src/styles.scss +++ b/wowup-electron/src/styles.scss @@ -80,6 +80,14 @@ img { padding: 0 0.25em; } +.pt-1 { + padding-top: 0.25em; +} + +.pt-2 { + padding-top: 0.5em; +} + .pt-3 { padding-top: 1em; } @@ -128,6 +136,14 @@ img { margin-bottom: 0.25em !important; } +.mb-2 { + margin-bottom: 0.5em !important; +} + +.mb-3 { + margin-bottom: 1em !important; +} + .w-100 { width: 100%; } @@ -164,6 +180,15 @@ img { } } +.option-icon { + width: 17px !important; + height: 17px !important; +} + +mat-icon.success-icon { + color: green; +} + .mat-tab-label, .mat-tab-label-content, .mat-select-value,