mirror of
https://github.com/WowUp/WowUp.git
synced 2026-04-22 23:09:38 -04:00
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
This commit is contained in:
@@ -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 });
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<void> {
|
||||
return shell.openExternal(url, options);
|
||||
}
|
||||
@@ -71,7 +59,6 @@ function showOpenDialog(options: OpenDialogOptions): Promise<OpenDialogReturnVal
|
||||
}
|
||||
|
||||
if (window.opener === null) {
|
||||
console.log("NO OPENER");
|
||||
window.log = log;
|
||||
window.libs = {
|
||||
handlebars: require("handlebars"),
|
||||
@@ -84,9 +71,6 @@ if (window.opener === null) {
|
||||
rendererInvoke,
|
||||
rendererOff,
|
||||
rendererOn,
|
||||
isDefaultProtocolClient,
|
||||
setAsDefaultProtocolClient,
|
||||
removeAsDefaultProtocolClient,
|
||||
openExternal,
|
||||
showOpenDialog,
|
||||
openPath,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Addon } from "../../common/entities/addon";
|
||||
import { AddonChannelType } from "../../common/wowup/models";
|
||||
import { AddonFolder } from "../models/wowup/addon-folder";
|
||||
import { AddonSearchResult } from "../models/wowup/addon-search-result";
|
||||
import { ProtocolSearchResult } from "../models/wowup/protocol-search-result";
|
||||
|
||||
export type AddonProviderType = "Curse" | "GitHub" | "TukUI" | "WowInterface" | "WowUpHub" | "RaiderIO" | "Zip";
|
||||
|
||||
@@ -54,6 +55,10 @@ export abstract class AddonProvider {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
public searchProtocol(protocol: string): Promise<ProtocolSearchResult | undefined> {
|
||||
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,
|
||||
|
||||
@@ -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<ProtocolSearchResult | undefined> {
|
||||
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<AddonSearchResult> {
|
||||
const url = `${API_URL}/addon/${addonId}`;
|
||||
|
||||
return from(this._circuitBreaker.getJson<CurseSearchResult>(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<CurseSearchResult> {
|
||||
const url = `${API_URL}/addon/${addonId}`;
|
||||
|
||||
return from(this._circuitBreaker.getJson<CurseSearchResult>(url));
|
||||
}
|
||||
|
||||
private getAddonFileById(addonId: string | number, fileId: string | number): Observable<CurseAddonFileResponse> {
|
||||
const url = `${API_URL}/addon/${addonId}/file/${fileId}`;
|
||||
|
||||
return from(this._circuitBreaker.getJson<CurseAddonFileResponse>(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:
|
||||
|
||||
@@ -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<void> => {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<div *ngIf="hasUrl() === true" class="addon-logo-container bg-secondary-3" [style.width]="size + 'px'"
|
||||
[style.height]="size + 'px'">
|
||||
<img [src]="url" loading="lazy" />
|
||||
</div>
|
||||
<div *ngIf="hasUrl() === false" class="addon-logo-container">
|
||||
<div class="addon-logo-letter text-3">
|
||||
{{ getLetter() }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -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<AddonThumbnailComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AddonThumbnailComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddonThumbnailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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() ?? "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<h1 *ngIf="ready === true" mat-dialog-title>{{ "DIALOGS.INSTALL_FROM_PROTOCOL.TITLE" | translate:{providerName:
|
||||
getProviderName()} }}</h1>
|
||||
<div mat-dialog-content class="content">
|
||||
<div *ngIf="ready === false" class="row justify-content-center">
|
||||
<app-progress-spinner></app-progress-spinner>
|
||||
</div>
|
||||
<div *ngIf="ready === true">
|
||||
<div class="row mb-3">
|
||||
<app-addon-thumbnail [url]="getThumbnailUrl()" [name]="getName()" [size]="60" class="pt-1 mr-3"></app-addon-thumbnail>
|
||||
<div>
|
||||
<h3 class="m-0">{{getName()}}</h3>
|
||||
<p class="m-0">{{getAuthor()}}</p>
|
||||
<p class="m-0 text-2">{{getVersion()}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-form-field *ngIf="error.length === 0" class="control">
|
||||
<mat-label>WoW Installation</mat-label>
|
||||
<mat-select multiple class="select" [formControl]="installations"
|
||||
[disabled]="isInstalling === true || isComplete === true">
|
||||
<mat-option *ngFor="let installation of validWowInstallations" [value]="installation.id"
|
||||
[disabled]="installation.isInstalled">{{ installation.label
|
||||
}}
|
||||
<mat-icon *ngIf="installation.isInstalled" class="option-icon success-icon" svgIcon="fas:check-circle">
|
||||
</mat-icon>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div *ngIf="ready === true && error.length > 0" class="error">
|
||||
<h4>Error</h4>
|
||||
<p>{{ error | translate:{protocol: data.protocol} }}</p>
|
||||
</div>
|
||||
<div *ngIf="isInstalling === true">
|
||||
<p>{{ 'DIALOGS.INSTALL_FROM_PROTOCOL.ADDON_INSTALLING' | translate }}</p>
|
||||
<mat-progress-bar mode="determinate" [value]="installProgress"></mat-progress-bar>
|
||||
</div>
|
||||
<div *ngIf="isComplete === true" class="installed">
|
||||
<div class="success-icon">
|
||||
<img src="assets/images/checkbox-marked-circle-green.svg" class="icon-larger" />
|
||||
</div>
|
||||
<p class="text-center">{{ 'DIALOGS.INSTALL_FROM_PROTOCOL.ADDON_INSTALLED' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="ready === true" mat-dialog-actions>
|
||||
<button mat-button [disabled]="isInstalling" (click)="onClose()">
|
||||
{{ "DIALOGS.INSTALL_FROM_PROTOCOL.CANCEL_BUTTON" | translate }}
|
||||
</button>
|
||||
<button mat-flat-button color="primary" cdkFocusInitial (click)="onInstall()"
|
||||
[disabled]="error.length > 0 || installations.value.length === 0 || isInstalling === true || isComplete === true">
|
||||
{{ "DIALOGS.INSTALL_FROM_PROTOCOL.INSTALL_BUTTON" | translate }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<InstallFromProtocolDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InstallFromProtocolDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstallFromProtocolDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -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<InstallFromProtocolDialogComponent>
|
||||
) {}
|
||||
|
||||
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<void> => {
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,7 @@
|
||||
<div>
|
||||
<div class="addon-column row align-items-center">
|
||||
<div class="thumbnail-container">
|
||||
<div *ngIf="listItem.hasThumbnail === true" class="addon-logo-container bg-secondary-3">
|
||||
<img [src]="listItem.addon.thumbnailUrl" loading="lazy" />
|
||||
</div>
|
||||
<div *ngIf="listItem.hasThumbnail === false" class="addon-logo-container">
|
||||
<div class="addon-logo-letter text-3">
|
||||
{{ listItem.thumbnailLetter }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div *ngIf="listItem.isBetaChannel() || listItem.isAlphaChannel()" class="channel bg-secondary-3" [ngClass]="{
|
||||
beta: listItem.isBetaChannel(),
|
||||
alpha: listItem.isAlphaChannel()
|
||||
}">
|
||||
{{ listItem.isAlphaChannel() ? "Alpha" : "Beta" }}
|
||||
</div> -->
|
||||
<app-addon-thumbnail [url]="getThumbnailUrl()" [name]="listItem.addon.name"></app-addon-thumbnail>
|
||||
</div>
|
||||
<div class="version-container">
|
||||
<div class="title-container">
|
||||
@@ -30,9 +17,9 @@
|
||||
<div class="addon-version text-2 row align-items-center" [ngClass]="{ ignored: listItem.addon.isIgnored }">
|
||||
<div *ngIf="listItem.isBetaChannel() || listItem.isAlphaChannel()" class="channel bg-secondary-3 mr-2"
|
||||
[ngClass]="{
|
||||
beta: listItem.isBetaChannel(),
|
||||
alpha: listItem.isAlphaChannel()
|
||||
}">
|
||||
beta: listItem.isBetaChannel(),
|
||||
alpha: listItem.isAlphaChannel()
|
||||
}">
|
||||
{{ channelTranslationKey | translate }}
|
||||
</div>
|
||||
<div *ngIf="hasMultipleProviders === true" class="mr-2">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- THEME -->
|
||||
<div class="toggle">
|
||||
@@ -41,6 +42,7 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- TELEMETRY -->
|
||||
<div class="toggle">
|
||||
@@ -55,6 +57,7 @@
|
||||
<small class="text-2">
|
||||
{{ "PAGES.OPTIONS.APPLICATION.TELEMETRY_DESCRIPTION" | translate }}
|
||||
</small>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- MINIMIZE ON CLOSE -->
|
||||
<div class="toggle">
|
||||
@@ -67,6 +70,7 @@
|
||||
<mat-slide-toggle [(checked)]="collapseToTray" (change)="onCollapseChange($event)"> </mat-slide-toggle>
|
||||
</div>
|
||||
<small class="text-2">{{ minimizeOnCloseDescription }}</small>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- SYSTEM NOTIFICATIONS -->
|
||||
<div class="toggle">
|
||||
@@ -83,6 +87,7 @@
|
||||
<small class="text-2">
|
||||
{{ "PAGES.OPTIONS.APPLICATION.ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION" | translate }}
|
||||
</small>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- HARDWARE ACCELERATION -->
|
||||
<div class="toggle">
|
||||
@@ -95,7 +100,9 @@
|
||||
<mat-slide-toggle [(checked)]="useHardwareAcceleration" (change)="onUseHardwareAccelerationChange($event)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.USE_HARDWARE_ACCELERATION_DESCRIPTION" | translate }}</small>
|
||||
<small class="text-2"
|
||||
[innerHTML]="'PAGES.OPTIONS.APPLICATION.USE_HARDWARE_ACCELERATION_DESCRIPTION' | translate"></small>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- SYMLINK SUPPORT -->
|
||||
<div class="toggle">
|
||||
@@ -109,6 +116,7 @@
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.USE_SYMLINK_SUPPORT_DESCRIPTION" | translate }}</small>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- START WITH SYSTEM -->
|
||||
<div class="toggle">
|
||||
@@ -123,6 +131,7 @@
|
||||
(change)="onStartWithSystemChange($event)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- START MINIMIZED -->
|
||||
<div class="toggle">
|
||||
@@ -137,30 +146,50 @@
|
||||
(change)="onStartMinimizedChange($event)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- SCALE -->
|
||||
<div class="row row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div> {{ "PAGES.OPTIONS.APPLICATION.SCALE_LABEL" | translate }} </div>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.SCALE_DESCRIPTION" | translate }}</small>
|
||||
<div class="toggle">
|
||||
<div class="row row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div> {{ "PAGES.OPTIONS.APPLICATION.SCALE_LABEL" | translate }} </div>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.SCALE_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-form-field class="light-select">
|
||||
<mat-select [(value)]="currentScale" (selectionChange)="onScaleChange($event)">
|
||||
<mat-option *ngFor="let value of zoomScale" [value]="value">
|
||||
{{ value * 100 | number:'1.0-0' }} %
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-form-field class="light-select">
|
||||
<mat-select [(value)]="currentScale" (selectionChange)="onScaleChange($event)">
|
||||
<mat-option *ngFor="let value of zoomScale" [value]="value">
|
||||
{{ value * 100 | number:'1.0-0' }} %
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
<!-- APP PROTOCOL -->
|
||||
<div class="row row align-items-center">
|
||||
<!-- <div class="row row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>
|
||||
{{ "PAGES.OPTIONS.APPLICATION.PROTOCOL_LABEL" | translate }}
|
||||
</div>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.PROTOCOL_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-slide-toggle [(checked)]="protocolRegistered" (change)="onProtocolRegisteredChange($event)">
|
||||
<mat-slide-toggle [checked]="wowupProtocolHandled$ | async" (change)="onProtocolHandlerChange($event, wowupProtocolName)">
|
||||
</mat-slide-toggle>
|
||||
<div class="divider"></div>
|
||||
</div> -->
|
||||
<!-- CURSE PROTOCOL -->
|
||||
<div class="toggle">
|
||||
<div class="row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>
|
||||
{{ "PAGES.OPTIONS.APPLICATION.CURSE_PROTOCOL_LABEL" | translate }}
|
||||
</div>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.CURSE_PROTOCOL_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-slide-toggle [checked]="curseforgeProtocolHandled$ | async"
|
||||
(change)="onProtocolHandlerChange($event, curseProtocolName)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> => {
|
||||
public onProtocolHandlerChange = async (evt: MatSlideToggleChange, protocol: string): Promise<void> => {
|
||||
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<boolean> {
|
||||
if (enabled) {
|
||||
return this.electronService.setAsDefaultProtocolClient(protocol);
|
||||
} else {
|
||||
return this.electronService.removeAsDefaultProtocolClient(protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
<span>
|
||||
<div class="addon-column">
|
||||
<div class="thumbnail-container bg-secondary-3">
|
||||
<div *ngIf="hasThumbnail === true" class="addon-logo-container ">
|
||||
<img [src]="addon.thumbnailUrl" loading="lazy" />
|
||||
</div>
|
||||
<div *ngIf="hasThumbnail === false" class="addon-logo-container">
|
||||
<div class="addon-logo-letter text-3">
|
||||
{{ thumbnailLetter }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-addon-thumbnail [url]="addon.thumbnailUrl" [name]="addon.name"></app-addon-thumbnail>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; justify-content: space-between; min-height: 40px">
|
||||
<a class="addon-title hover-text-2 " (click)="viewDetails()">{{ addon.name }}</a>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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<void> => {
|
||||
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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<void> {
|
||||
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<Addon | undefined> {
|
||||
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<ProtocolSearchResult> {
|
||||
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<void> {
|
||||
const installations = this._warcraftInstallationService.getWowInstallations();
|
||||
for (const installation of installations) {
|
||||
|
||||
@@ -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<T, K>(component: ComponentType<T>, config?: MatDialogConfig<K>): MatDialogRef<T, any> {
|
||||
return this._dialog.open(component, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
return this.invoke(IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT);
|
||||
public isDefaultProtocolClient(protocol: string): Promise<boolean> {
|
||||
return this.invoke(IPC_IS_DEFAULT_PROTOCOL_CLIENT, protocol);
|
||||
}
|
||||
|
||||
public async removeAsDefaultProtocolClient(): Promise<void> {
|
||||
return this.invoke(IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT);
|
||||
public setAsDefaultProtocolClient(protocol: string): Promise<boolean> {
|
||||
return this.invoke(IPC_SET_AS_DEFAULT_PROTOCOL_CLIENT, protocol);
|
||||
}
|
||||
|
||||
public async removeAsDefaultProtocolClient(protocol: string): Promise<boolean> {
|
||||
return this.invoke(IPC_REMOVE_AS_DEFAULT_PROTOCOL_CLIENT, protocol);
|
||||
}
|
||||
|
||||
public async getAppOptions(): Promise<AppOptions> {
|
||||
// TODO check protocols here
|
||||
const launchArgs = await this.invoke(IPC_GET_LAUNCH_ARGS);
|
||||
let opts;
|
||||
|
||||
opts = (<any>minimist(launchArgs.slice(1), {
|
||||
const opts = (<any>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 = (<any>{ 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<OpenDialogReturnValue> {
|
||||
return window.wowup.showOpenDialog(options);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<boolean>();
|
||||
private readonly _myAddonsColumnsSrc = new BehaviorSubject<ColumnState[]>([]);
|
||||
private readonly _targetFileInstallCompleteSrc = new Subject<boolean>();
|
||||
|
||||
private readonly _getAddonsColumnsSrc = new Subject<ColumnState>();
|
||||
|
||||
@@ -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<DetailsTabType>(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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
if (this.protocolRegistered) {
|
||||
return this._electronService.setAsDefaultProtocolClient();
|
||||
} else {
|
||||
return this._electronService.removeAsDefaultProtocolClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.<br>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",
|
||||
|
||||
@@ -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ú",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "창을 닫으면 메뉴바로 최소화",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 сворачивается в меню статуса.",
|
||||
|
||||
@@ -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 視窗時,最小化到選單欄。",
|
||||
|
||||
@@ -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 窗口时,最小化到菜单栏。",
|
||||
|
||||
@@ -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
|
||||
|
||||
7
wowup-electron/src/common/wowup.d.ts
vendored
7
wowup-electron/src/common/wowup.d.ts
vendored
@@ -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<any>;
|
||||
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<void>;
|
||||
showOpenDialog: (options: OpenDialogOptions) => Promise<OpenDialogReturnValue>;
|
||||
openPath: (path: string) => Promise<string>;
|
||||
|
||||
@@ -24,7 +24,6 @@ export interface AppOptions {
|
||||
serve?: boolean;
|
||||
hidden?: boolean;
|
||||
quit?: boolean;
|
||||
install?: string;
|
||||
}
|
||||
|
||||
export interface MenuConfig {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user