mirror of
https://github.com/WowUp/WowUp.git
synced 2026-04-22 15:00:38 -04:00
Merge branch 'feature/toggle-addon-specific-auto-update-notifications' of https://github.com/tellier-dev/WowUp into feature/toggle-addon-specific-auto-update-notifications
This commit is contained in:
@@ -70,6 +70,10 @@ import {
|
||||
IPC_WOWUP_GET_SCAN_RESULTS,
|
||||
IPC_WRITE_FILE_CHANNEL,
|
||||
DEFAULT_FILE_MODE,
|
||||
IPC_PUSH_INIT,
|
||||
IPC_PUSH_REGISTER,
|
||||
IPC_PUSH_UNREGISTER,
|
||||
IPC_PUSH_SUBSCRIBE,
|
||||
} from "../src/common/constants";
|
||||
import { CurseFolderScanResult } from "../src/common/curse/curse-folder-scan-result";
|
||||
import { Addon } from "../src/common/entities/addon";
|
||||
@@ -89,6 +93,7 @@ import { chmodDir, copyDir, getDirTree, getLastModifiedFileDate, readDirRecursiv
|
||||
import { addonStore } from "./stores";
|
||||
import { createTray, restoreWindow } from "./system-tray";
|
||||
import { WowUpFolderScanner } from "./wowup-folder-scanner";
|
||||
import * as push from "./push";
|
||||
|
||||
let USER_AGENT = "";
|
||||
let PENDING_OPEN_URLS: string[] = [];
|
||||
@@ -478,6 +483,22 @@ export function initializeIpcHandlers(window: BrowserWindow, userAgent: string):
|
||||
return await dialog.showOpenDialog(options);
|
||||
});
|
||||
|
||||
handle(IPC_PUSH_INIT, (evt) => {
|
||||
return push.startPushService();
|
||||
});
|
||||
|
||||
handle(IPC_PUSH_REGISTER, async (evt, appId: string) => {
|
||||
return await push.registerForPush(appId);
|
||||
});
|
||||
|
||||
handle(IPC_PUSH_UNREGISTER, async (evt) => {
|
||||
return await push.unregisterPush();
|
||||
});
|
||||
|
||||
handle(IPC_PUSH_SUBSCRIBE, async (evt, channel) => {
|
||||
return await push.subscribeToChannel(channel);
|
||||
});
|
||||
|
||||
ipcMain.on(IPC_DOWNLOAD_FILE_CHANNEL, (evt, arg: DownloadRequest) => {
|
||||
handleDownloadFile(arg).catch((e) => console.error(e));
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
IPC_POWER_MONITOR_RESUME,
|
||||
IPC_POWER_MONITOR_SUSPEND,
|
||||
IPC_POWER_MONITOR_UNLOCK,
|
||||
IPC_PUSH_NOTIFICATION,
|
||||
IPC_WINDOW_ENTER_FULLSCREEN,
|
||||
IPC_WINDOW_LEAVE_FULLSCREEN,
|
||||
IPC_WINDOW_MAXIMIZED,
|
||||
@@ -40,6 +41,7 @@ import { MainChannels } from "../src/common/wowup";
|
||||
import { AppOptions } from "../src/common/wowup/models";
|
||||
import { initializeStoreIpcHandlers, preferenceStore } from "./stores";
|
||||
import { windowStateManager } from "./window-state";
|
||||
import { pushEvents, PUSH_NOTIFICATION_EVENT } from "./push";
|
||||
|
||||
// LOGGING SETUP
|
||||
// Override the default log path so they aren't a pain to find on Mac
|
||||
@@ -282,6 +284,10 @@ function createWindow(): BrowserWindow {
|
||||
initializeIpcHandlers(win, USER_AGENT);
|
||||
initializeStoreIpcHandlers();
|
||||
|
||||
pushEvents.on(PUSH_NOTIFICATION_EVENT, (data) => {
|
||||
win.webContents.send(IPC_PUSH_NOTIFICATION, data);
|
||||
});
|
||||
|
||||
// Keep track of window state
|
||||
mainWindowManager.monitorState(win);
|
||||
|
||||
@@ -351,6 +357,7 @@ function createWindow(): BrowserWindow {
|
||||
|
||||
win.on("close", (e) => {
|
||||
if (appIsQuitting || preferenceStore.get(COLLAPSE_TO_TRAY_PREFERENCE_KEY) !== "true") {
|
||||
pushEvents.removeAllListeners(PUSH_NOTIFICATION_EVENT);
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
64
wowup-electron/app/push.ts
Normal file
64
wowup-electron/app/push.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as log from "electron-log";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
export const PUSH_NOTIFICATION_EVENT = "push-notification";
|
||||
export const pushEvents = new EventEmitter();
|
||||
|
||||
const channelSubscriptions = new Map<string, boolean>();
|
||||
|
||||
let Pushy: any;
|
||||
export function startPushService(): boolean {
|
||||
if (!Pushy) {
|
||||
Pushy = require("pushy-electron");
|
||||
}
|
||||
|
||||
// Listen for push notifications
|
||||
Pushy.setNotificationListener((data) => {
|
||||
pushEvents.emit(PUSH_NOTIFICATION_EVENT, data);
|
||||
});
|
||||
|
||||
Pushy.listen();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function registerForPush(appId: string): Promise<string> {
|
||||
if (!Pushy) {
|
||||
throw new Error("Push not started");
|
||||
}
|
||||
if (!appId) {
|
||||
throw new Error("Invalid push app id");
|
||||
}
|
||||
|
||||
return await Pushy.register({ appId });
|
||||
}
|
||||
|
||||
export async function unregisterPush(): Promise<void> {
|
||||
for (const [key] of channelSubscriptions) {
|
||||
try {
|
||||
await Pushy.unsubscribe(key);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
channelSubscriptions.clear();
|
||||
|
||||
Pushy.disconnect();
|
||||
}
|
||||
|
||||
export async function subscribeToChannel(channel: string): Promise<void> {
|
||||
// Make sure the user is registered
|
||||
if (!Pushy.isRegistered()) {
|
||||
throw new Error("Push services not registered");
|
||||
}
|
||||
|
||||
if (channelSubscriptions.has(channel)) {
|
||||
log.warn(`Already listening: ${channel}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Subscribe the user to a topic
|
||||
await Pushy.subscribe(channel);
|
||||
channelSubscriptions.set(channel, true);
|
||||
log.debug(`Subscribed: ${channel}`);
|
||||
}
|
||||
2795
wowup-electron/package-lock.json
generated
2795
wowup-electron/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wowup",
|
||||
"productName": "WowUp",
|
||||
"version": "2.5.0-beta.11",
|
||||
"version": "2.5.0-beta.13",
|
||||
"description": "World of Warcraft addon updater",
|
||||
"homepage": "https://wowup.io",
|
||||
"author": {
|
||||
@@ -153,6 +153,7 @@
|
||||
"opossum": "6.2.0",
|
||||
"p-limit": "3.1.0",
|
||||
"protobufjs": "6.11.2",
|
||||
"pushy-electron": "1.0.8",
|
||||
"rxjs": "6.6.7",
|
||||
"slug": "5.1.0",
|
||||
"string-similarity": "4.0.4",
|
||||
|
||||
@@ -343,6 +343,7 @@ export class CurseAddonProvider extends AddonProvider {
|
||||
{
|
||||
fingerprints,
|
||||
},
|
||||
undefined,
|
||||
AppConfig.wowUpHubHttpTimeoutMs
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div *ngIf="hasUrl() === true" class="addon-logo-container bg-secondary-3" [style.width]="size + 'px'"
|
||||
<div *ngIf="hasUrl() === true" class="addon-logo-container bg-secondary-3 rounded" [style.width]="size + 'px'"
|
||||
[style.height]="size + 'px'">
|
||||
<img [src]="url" loading="lazy" />
|
||||
</div>
|
||||
<div *ngIf="hasUrl() === false" class="addon-logo-container bg-secondary-3">
|
||||
<div *ngIf="hasUrl() === false" class="addon-logo-container bg-secondary-3 rounded">
|
||||
<div class="addon-logo-letter text-3">
|
||||
{{ getLetter() }}
|
||||
</div>
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.JOIN_DISCORD' | translate }}">
|
||||
<mat-icon class="tab-icon tab-icon-inactive" svgIcon="fab:discord"></mat-icon>
|
||||
</a>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === false">
|
||||
<div class="bnet-btn bnet-btn-sm ml-2" (click)="wowUpService.login()">Login</div>
|
||||
</div>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === true" class="btn">
|
||||
<mat-icon class="tab-icon tab-icon-inactive" svgIcon="fas:check-circle"
|
||||
matTooltip="{{ 'You\'re logged in!' | translate }}"></mat-icon>
|
||||
<!-- <div *ngIf="(sessionService.wowUpAuthenticated$ | async) === false">
|
||||
<div class="bnet-btn bnet-btn-sm ml-2" (click)="onClickAccount()">Login</div>
|
||||
</div>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === true" class="btn" (click)="onClickAccount()"
|
||||
matTooltip="{{ 'You\'re logged in!' | translate }}">
|
||||
{{accountDisplayName$ | async}}
|
||||
</div> -->
|
||||
<p class="text-1">{{ sessionService.statusText$ | async }}</p>
|
||||
<div class="flex-grow-1"></div>
|
||||
<p class="mr-3">{{ sessionService.pageContextText$ | async }}</p>
|
||||
|
||||
@@ -15,12 +15,16 @@ import { MatIcon } from "@angular/material/icon";
|
||||
import { MatIconTestingModule } from "@angular/material/icon/testing";
|
||||
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { mockPreload } from "../../tests/test-helpers";
|
||||
import { ElectronService } from "../../services";
|
||||
import { LinkService } from "../../services/links/link.service";
|
||||
|
||||
/** Fix icon warning? https://stackoverflow.com/a/62277810 */
|
||||
describe("FooterComponent", () => {
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
let wowUpServiceSpy: WowUpService;
|
||||
let sessionServiceSpy: SessionService;
|
||||
let electronService: ElectronService;
|
||||
let linkService: LinkService;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockPreload();
|
||||
@@ -35,6 +39,13 @@ describe("FooterComponent", () => {
|
||||
sessionServiceSpy = jasmine.createSpyObj("SessionService", [""], {
|
||||
statusText$: new BehaviorSubject(""),
|
||||
pageContextText$: new BehaviorSubject(""),
|
||||
wowUpAccount$: new Subject(),
|
||||
});
|
||||
|
||||
linkService = jasmine.createSpyObj("LinkService", [""], {});
|
||||
|
||||
electronService = jasmine.createSpyObj("ElectronService", [""], {
|
||||
appUpdate$: new Subject(),
|
||||
});
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -61,8 +72,10 @@ describe("FooterComponent", () => {
|
||||
.overrideComponent(FooterComponent, {
|
||||
set: {
|
||||
providers: [
|
||||
{ provide: ElectronService, useValue: electronService },
|
||||
{ provide: WowUpService, useValue: wowUpServiceSpy },
|
||||
{ provide: SessionService, useValue: sessionServiceSpy },
|
||||
{ provide: LinkService, useValue: linkService },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -9,6 +9,8 @@ import { ConfirmDialogComponent } from "../confirm-dialog/confirm-dialog.compone
|
||||
import { combineLatest, from, Observable, of } from "rxjs";
|
||||
import { catchError, map, switchMap } from "rxjs/operators";
|
||||
import { AppUpdateState } from "../../../common/wowup/models";
|
||||
import { TAB_INDEX_ABOUT } from "../../../common/constants";
|
||||
import { LinkService } from "../../services/links/link.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-footer",
|
||||
@@ -26,6 +28,9 @@ export class FooterComponent implements OnInit {
|
||||
public appUpdateState = AppUpdateState;
|
||||
|
||||
public appUpdateState$: Observable<AppUpdateState> = this.electronService.appUpdate$.pipe(map((evt) => evt.state));
|
||||
public accountDisplayName$: Observable<string> = this.sessionService.wowUpAccount$.pipe(
|
||||
map((account) => account?.displayName ?? "")
|
||||
);
|
||||
|
||||
public appUpdateProgress$: Observable<number> = combineLatest([
|
||||
of(0),
|
||||
@@ -39,7 +44,8 @@ export class FooterComponent implements OnInit {
|
||||
public wowUpService: WowUpService,
|
||||
public sessionService: SessionService,
|
||||
private electronService: ElectronService,
|
||||
private _wowupService: WowUpService
|
||||
private _wowupService: WowUpService,
|
||||
private _linkService: LinkService
|
||||
) {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
@@ -57,6 +63,10 @@ export class FooterComponent implements OnInit {
|
||||
this.wowUpService.checkForAppUpdate();
|
||||
}
|
||||
|
||||
public onClickAccount(): void {
|
||||
this.sessionService.selectedHomeTab = TAB_INDEX_ABOUT;
|
||||
}
|
||||
|
||||
private portableUpdate() {
|
||||
const dialogRef = this._dialog.open(ConfirmDialogComponent, {
|
||||
data: {
|
||||
@@ -74,7 +84,7 @@ export class FooterComponent implements OnInit {
|
||||
}
|
||||
|
||||
return from(
|
||||
this._wowupService.openExternalLink(
|
||||
this._linkService.openExternalLink(
|
||||
`${AppConfig.wowupRepositoryUrl}/releases/tag/v${this.wowUpService.availableVersion}`
|
||||
)
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
</a>
|
||||
<a *ngIf="size === 'small'" class="funding-button small text-1 hover-primary-2 text-1-hover" appExternalLink
|
||||
[matTooltip]="tooltipKey | translate:{platform:fundingName}" [href]="funding.url">
|
||||
<mat-icon *ngIf="isFontIcon === true" [svgIcon]="iconSrc"></mat-icon>
|
||||
<mat-icon *ngIf="isFontIcon === true" [class]="getClassName()" [svgIcon]="iconSrc">
|
||||
</mat-icon>
|
||||
<img *ngIf="isFontIcon === false" class="funding-icon" [src]="iconSrc" />
|
||||
</a>
|
||||
@@ -41,3 +41,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.patreon-icon {
|
||||
color: var(--patreon-color);
|
||||
}
|
||||
.custom-icon {
|
||||
color: var(--wow-gold-color);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,16 @@ export class FundingButtonComponent implements OnInit {
|
||||
return `PAGES.MY_ADDONS.FUNDING_TOOLTIP.${this.funding.platform.toUpperCase()}`;
|
||||
}
|
||||
|
||||
public getClassName(): string {
|
||||
switch (this.funding.platform) {
|
||||
case "PATREON":
|
||||
return "patreon-icon";
|
||||
case "GITHUB":
|
||||
return "github-icon";
|
||||
default:
|
||||
return "custom-icon";
|
||||
}
|
||||
}
|
||||
private getIsFontIcon(): boolean {
|
||||
switch (this.funding.platform) {
|
||||
case "PATREON":
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</mat-icon> -->
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<a appExternalLink class="text-1 tab hover-bg-secondary-2" href="https://wowup.io/guide/section/my-addons/overview"
|
||||
<a appExternalLink class="text-1 tab hover-bg-secondary-2" [href]="wowUpWebsiteUrl + '/guide'"
|
||||
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.VIEW_GUIDE' | translate }}">
|
||||
<mat-icon class="tab-icon tab-icon-inactive" svgIcon="far:question-circle"></mat-icon>
|
||||
<div class="tab-title">{{ 'PAGES.HOME.GUIDE_TAB_TITLE' | translate }}</div>
|
||||
@@ -28,6 +28,20 @@
|
||||
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.JOIN_DISCORD' | translate }}">
|
||||
<mat-icon class="tab-icon tab-icon-inactive" svgIcon="fab:discord"></mat-icon>
|
||||
</a> -->
|
||||
<div class="tab hover-bg-secondary-2" [ngClass]="{ selected: isAccountSelected$ | async }"
|
||||
(click)="onClickTab(TAB_INDEX_ACCOUNT)">
|
||||
<!-- <div class="tab-icon"> -->
|
||||
<mat-icon class="tab-icon tab-icon-inactive" svgIcon="fas:user-circle"
|
||||
[ngClass]="{ 'tab-icon-active': isAccountSelected$ | async }">
|
||||
</mat-icon>
|
||||
<!-- </div> -->
|
||||
<div>
|
||||
<div class="tab-title">{{ 'PAGES.HOME.ACCOUNT_TAB_TITLE' | translate }}
|
||||
</div>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === true" class="tab-subtitle">Logged in</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let tab of tabsBottom" class="tab hover-bg-secondary-2"
|
||||
[ngClass]="{ selected: tab.isSelected$ | async, disabled: tab.isDisabled$ | async }" (click)="tab.onClick(tab)">
|
||||
<!-- <div class="tab-icon"> -->
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
|
||||
.tab-title {
|
||||
color: var(--text-4);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease 0.1s;
|
||||
@@ -47,6 +50,13 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-subtitle {
|
||||
font-size: 0.8em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
margin-right: 0.5em;
|
||||
transition: margin-right 0.3s ease 0.1s;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { BehaviorSubject, Subject } from "rxjs";
|
||||
import { ElectronService } from "../../services";
|
||||
import { SessionService } from "../../services/session/session.service";
|
||||
import { WarcraftInstallationService } from "../../services/warcraft/warcraft-installation.service";
|
||||
import { getStandardImports, mockPreload } from "../../tests/test-helpers";
|
||||
|
||||
import { HorizontalTabsComponent } from "./horizontal-tabs.component";
|
||||
|
||||
describe("VerticalTabsComponent", () => {
|
||||
describe("HorizontalTabsComponent", () => {
|
||||
let component: HorizontalTabsComponent;
|
||||
let fixture: ComponentFixture<HorizontalTabsComponent>;
|
||||
let sessionService: SessionService;
|
||||
let electronService: any;
|
||||
let warcraftInstallationService: WarcraftInstallationService;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -18,8 +20,11 @@ describe("VerticalTabsComponent", () => {
|
||||
sessionService = jasmine.createSpyObj("SessionService", ["getSelectedClientType", "getSelectedDetailsTab"], {
|
||||
getSelectedWowInstallation: () => "description",
|
||||
selectedHomeTab$: new BehaviorSubject(1),
|
||||
wowUpAccount$: new Subject(),
|
||||
});
|
||||
|
||||
electronService = jasmine.createSpyObj("ElectronService", [""], {});
|
||||
|
||||
warcraftInstallationService = jasmine.createSpyObj("WarcraftInstallationService", [""], {
|
||||
wowInstallations$: new BehaviorSubject([]),
|
||||
});
|
||||
@@ -32,6 +37,7 @@ describe("VerticalTabsComponent", () => {
|
||||
testBed = testBed.overrideComponent(HorizontalTabsComponent, {
|
||||
set: {
|
||||
providers: [
|
||||
{ provide: ElectronService, useValue: electronService },
|
||||
{ provide: SessionService, useValue: sessionService },
|
||||
{ provide: WarcraftInstallationService, useValue: warcraftInstallationService },
|
||||
],
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { Observable, of } from "rxjs";
|
||||
import { WarcraftInstallationService } from "../../services/warcraft/warcraft-installation.service";
|
||||
import { ElectronService } from "../../services";
|
||||
import { AppConfig } from "../../../environments/environment";
|
||||
|
||||
interface Tab {
|
||||
titleKey?: string;
|
||||
@@ -30,6 +31,11 @@ interface Tab {
|
||||
styleUrls: ["./horizontal-tabs.component.scss"],
|
||||
})
|
||||
export class HorizontalTabsComponent implements OnInit {
|
||||
public wowUpWebsiteUrl = AppConfig.wowUpWebsiteUrl;
|
||||
public TAB_INDEX_ACCOUNT = TAB_INDEX_ABOUT;
|
||||
|
||||
public isAccountSelected$ = this.sessionService.selectedHomeTab$.pipe(map((result) => result === TAB_INDEX_ABOUT));
|
||||
|
||||
private myAddonsTab: Tab = {
|
||||
titleKey: "PAGES.HOME.MY_ADDONS_TAB_TITLE",
|
||||
tooltipKey: "PAGES.HOME.MY_ADDONS_TAB_TITLE",
|
||||
@@ -92,7 +98,7 @@ export class HorizontalTabsComponent implements OnInit {
|
||||
|
||||
public tabsTop: Tab[] = [this.myAddonsTab, this.getAddonsTab, this.newsTab];
|
||||
|
||||
public tabsBottom: Tab[] = [this.aboutTab, this.settingsTab];
|
||||
public tabsBottom: Tab[] = [this.settingsTab];
|
||||
|
||||
public constructor(
|
||||
public electronService: ElectronService,
|
||||
@@ -101,4 +107,8 @@ export class HorizontalTabsComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
public ngOnInit(): void {}
|
||||
|
||||
public onClickTab(tabIndex: number): void {
|
||||
this.sessionService.selectedHomeTab = tabIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
listItem.addon?.name
|
||||
}}</a>
|
||||
</div>
|
||||
<div *ngIf="listItem.addon?.fundingLinks" class="addon-funding">
|
||||
<app-funding-button *ngFor="let link of listItem.addon?.fundingLinks ?? []" [funding]="link" size="small">
|
||||
</app-funding-button>
|
||||
</div>
|
||||
|
||||
<div class="addon-version text-2 row align-items-center" [ngClass]="{ ignored: listItem.addon?.isIgnored }">
|
||||
<div *ngIf="listItem.addon?.fundingLinks" class="addon-funding mr-2">
|
||||
<app-funding-button *ngFor="let link of listItem.addon?.fundingLinks ?? []" [funding]="link" size="small">
|
||||
</app-funding-button>
|
||||
</div>
|
||||
<div *ngIf="listItem.isBetaChannel() || listItem.isAlphaChannel()" class="channel bg-secondary-3 mr-2"
|
||||
[ngClass]="{
|
||||
beta: listItem.isBetaChannel(),
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
@import "../../../variables.scss";
|
||||
|
||||
.addon-column {
|
||||
padding-top: 0.6em;
|
||||
padding-bottom: 0.6em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
|
||||
.thumbnail-container {
|
||||
margin-right: 11px;
|
||||
@@ -57,7 +61,7 @@
|
||||
}
|
||||
|
||||
.title-container {
|
||||
padding-bottom: 0.5em;
|
||||
// padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.addon-title {
|
||||
|
||||
@@ -28,3 +28,17 @@ export interface WowUpSearchAddonsResponse {
|
||||
addons: WowUpAddonRepresentation[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface WowUpGetAccountResponse {
|
||||
displayName: string;
|
||||
patreonTier: string;
|
||||
config: WowUpAccountConfig;
|
||||
}
|
||||
|
||||
export interface WowUpAccountConfig {
|
||||
pushAppId: string;
|
||||
pushChannels: {
|
||||
addonUpdates: string;
|
||||
alerts: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface Toc {
|
||||
fileName: string;
|
||||
filePath: string;
|
||||
interface: string;
|
||||
title?: string;
|
||||
author?: string;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="tab-container about-container" [ngClass]="{
|
||||
<div class="about-container" [ngClass]="{
|
||||
mac: electronService.isMac,
|
||||
windows: electronService.isWin,
|
||||
linux: electronService.isLinux
|
||||
}">
|
||||
<div class="theme-logo">
|
||||
<!-- <div class="theme-logo">
|
||||
<div class="logo-img"></div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="about">
|
||||
<div class="header text-1">
|
||||
<img class="logo mt-3 mat-elevation-z2" loading="lazy" src="assets/wowup_logo_purple.png" />
|
||||
|
||||
@@ -3,33 +3,73 @@
|
||||
windows: electronService.isWin,
|
||||
linux: electronService.isLinux
|
||||
}">
|
||||
<div class="control-container text-1">
|
||||
<div class="theme-logo">
|
||||
<div class="logo-img"></div>
|
||||
</div>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === false" class="control-container text-1">
|
||||
<div class="account-container p-3 bg-secondary-4 mat-elevation-z8 rounded">
|
||||
<h1>Account <span class="text-control"><i>Beta</i></span></h1>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === false">
|
||||
<p>Login now to get the most out of your WowUp application.</p>
|
||||
<button mat-flat-button color="primary" (click)="testLogin()">
|
||||
{{ "Login Now!" | translate }}
|
||||
<h1>{{'PAGES.ACCOUNT.TITLE' | translate}} <span class="text-control"><i><sup>{{'PAGES.ACCOUNT.BETA' |
|
||||
translate}}</sup></i></span></h1>
|
||||
<p>Login now to get the most out of your WowUp application.</p>
|
||||
<p>We're working on bringing in some optional cloud based services that we think can really enhance your addon
|
||||
management experience.</p>
|
||||
<p>By clicking the login button you will be taken to the WowUp website to start the process.</p>
|
||||
<div class="mt-3 row justify-content-center">
|
||||
<button mat-flat-button color="primary" (click)="onClickLogin()">
|
||||
{{ "PAGES.ACCOUNT.LOGIN_BUTTON" | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === true">
|
||||
<h4>You're logged in!</h4>
|
||||
<p>You're now able to access our various cloud services to help you maximize your addon experience.</p>
|
||||
<div class="row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>
|
||||
{{ "Instant Updates" | translate }}
|
||||
</div>
|
||||
<small class="text-2">{{ "You should get addon updates as soon as we know about them" | translate }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="(sessionService.wowUpAuthenticated$ | async) === true" class="control-container text-1">
|
||||
<div class="authorized-container p-3 bg-secondary-4 mat-elevation-z8 rounded">
|
||||
<h1>{{'PAGES.ACCOUNT.TITLE' | translate}} <span class="text-control"><i><sup>{{'PAGES.ACCOUNT.BETA' |
|
||||
translate}}</sup></i></span></h1>
|
||||
<h3>You're logged in!</h3>
|
||||
<p>You're now able to access our various cloud services to help you maximize your addon experience.</p>
|
||||
<div class="toggle-row row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>
|
||||
{{ "Instant Updates" | translate }}
|
||||
</div>
|
||||
<mat-slide-toggle>
|
||||
</mat-slide-toggle>
|
||||
<small class="text-2">{{ "No more waiting for timers, get updates as soon as we see them" | translate
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="row justify-content-end mt-3">
|
||||
<button mat-flat-button color="warn" (click)="logout()">
|
||||
{{ "Logout" | translate }}
|
||||
</button>
|
||||
<mat-slide-toggle [checked]="sessionService.wowUpAccountPushEnabled$ | async" (change)="onToggleAccountPush($event)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="toggle-row row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>
|
||||
{{ "Cloud Addon Sync" | translate }}
|
||||
</div>
|
||||
<small class="text-2">{{ "The simplest way to manage your addon backups for all your machines" | translate
|
||||
}}</small>
|
||||
</div>
|
||||
<mat-slide-toggle disabled>
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="toggle-row row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>
|
||||
{{ "Cloud Settings Sync" | translate }}
|
||||
</div>
|
||||
<small class="text-2">{{ "Easy to use, hopefully, way to manage your settings between characters and machines"
|
||||
|
|
||||
translate
|
||||
}}</small>
|
||||
</div>
|
||||
<mat-slide-toggle disabled>
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="mt-3 row">
|
||||
<button mat-button color="warn" class="mr-3" (click)="onClickLogout()">
|
||||
{{ "PAGES.ACCOUNT.LOGOUT_BUTTON" | translate }}
|
||||
</button>
|
||||
<div class="flex-grow-1"></div>
|
||||
<button mat-button color="primary" (click)="onClickManageAccount()">
|
||||
{{ "PAGES.ACCOUNT.MANAGE_ACCOUNT_BUTTON" | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,40 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.authorized-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
width: 500px;
|
||||
|
||||
.toggle-row {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.account-container {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.theme-logo {
|
||||
content: "";
|
||||
opacity: 0.02;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
.logo-img {
|
||||
height: 100vh;
|
||||
background-image: var(--theme-logo);
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { MatSlideToggleChange } from "@angular/material/slide-toggle";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { of } from "rxjs";
|
||||
import { catchError, map } from "rxjs/operators";
|
||||
import { AppConfig } from "../../../environments/environment";
|
||||
import { ElectronService } from "../../services";
|
||||
import { DialogFactory } from "../../services/dialog/dialog.factory";
|
||||
import { LinkService } from "../../services/links/link.service";
|
||||
import { SessionService } from "../../services/session/session.service";
|
||||
import { SnackbarService } from "../../services/snackbar/snackbar.service";
|
||||
import { WowUpService } from "../../services/wowup/wowup.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-account-page",
|
||||
@@ -8,15 +17,53 @@ import { SessionService } from "../../services/session/session.service";
|
||||
styleUrls: ["./account-page.component.scss"],
|
||||
})
|
||||
export class AccountPageComponent {
|
||||
public constructor(public electronService: ElectronService, public sessionService: SessionService) {}
|
||||
public constructor(
|
||||
public electronService: ElectronService,
|
||||
public sessionService: SessionService,
|
||||
public wowUpService: WowUpService,
|
||||
private _dialogFactory: DialogFactory,
|
||||
private _translateService: TranslateService,
|
||||
private _snackbarService: SnackbarService,
|
||||
private _linkService: LinkService
|
||||
) {}
|
||||
|
||||
public logout(): void {
|
||||
this.sessionService.clearWowUpAuthToken();
|
||||
public onClickLogin(): void {
|
||||
this.sessionService.login();
|
||||
}
|
||||
|
||||
public testLogin(): void {
|
||||
this.electronService._customProtocolSrc.next(
|
||||
"wowup://login/desktop/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
|
||||
);
|
||||
public onToggleAccountPush = async (evt: MatSlideToggleChange): Promise<void> => {
|
||||
try {
|
||||
await this.sessionService.toggleAccountPush(evt.checked);
|
||||
} catch (e) {
|
||||
evt.source.checked = !evt.source.checked;
|
||||
console.error("Failed to toggle account push", e);
|
||||
this._snackbarService.showErrorSnackbar("COMMON.ERRORS.ACCOUNT_PUSH_TOGGLE_FAILED_ERROR");
|
||||
}
|
||||
};
|
||||
|
||||
public onClickLogout(): void {
|
||||
const title = this._translateService.instant("PAGES.ACCOUNT.LOGOUT_CONFIRMATION_TITLE");
|
||||
const message = this._translateService.instant("PAGES.ACCOUNT.LOGOUT_CONFIRMATION_MESSAGE");
|
||||
|
||||
const dialogRef = this._dialogFactory.getConfirmDialog(title, message);
|
||||
|
||||
dialogRef
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
map((result) => {
|
||||
if (result) {
|
||||
this.sessionService.logout();
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error(error);
|
||||
return of(undefined);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
public onClickManageAccount(): void {
|
||||
this._linkService.openExternalLink(`${AppConfig.wowUpWebsiteUrl}/account`).catch((e) => console.error(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ export class HomeComponent implements AfterViewInit, OnDestroy {
|
||||
};
|
||||
|
||||
public onSelectedIndexChange(index: number): void {
|
||||
this.sessionService.selectedHomeTab = index;
|
||||
// this.sessionService.selectedHomeTab = index;
|
||||
}
|
||||
|
||||
private onAddonScanError = (error: AddonScanError) => {
|
||||
|
||||
@@ -65,17 +65,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="spinner-container flex-grow-1" *ngIf="isBusy === true">
|
||||
<div class="spinner-container flex-grow-1" *ngIf="(isBusy$ | async) === true">
|
||||
<app-progress-spinner [message]="spinnerMessage"> </app-progress-spinner>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isBusy === false && hasData === false" class="no-addons-container text-1 flex-grow-1">
|
||||
<div *ngIf="(isBusy$ | async) === false && hasData === false" class="no-addons-container text-1 flex-grow-1">
|
||||
<h1>{{ "COMMON.SEARCH.NO_ADDONS" | translate }}</h1>
|
||||
</div>
|
||||
|
||||
<ag-grid-angular class="wu-ag-table ag-theme-material" [hidden]="isBusy === true || hasData === false"
|
||||
<ag-grid-angular class="wu-ag-table ag-theme-material" [hidden]="(isBusy$ | async) === true || hasData === false"
|
||||
[rowData]="rowData" [columnDefs]="columnDefs" [rowSelection]="'multiple'" [getRowNodeId]="getRowNodeId"
|
||||
[frameworkComponents]="frameworkComponents" [rowHeight]="65" [immutableData]="true" [rowClassRules]="rowClassRules"
|
||||
[frameworkComponents]="frameworkComponents" [rowHeight]="63" [immutableData]="true" [rowClassRules]="rowClassRules"
|
||||
[overlayNoRowsTemplate]="overlayNoRowsTemplate" (gridReady)="onGridReady($event)"
|
||||
(rowDoubleClicked)="onRowDoubleClicked($event)" (rowClicked)="onRowClicked($event)"
|
||||
(rowDataUpdated)="onRowDataChanged()" (sortChanged)="onSortChanged($event)"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import "../../../variables.scss";
|
||||
|
||||
.wu-ag-table {
|
||||
width: calc(100% - 6px);
|
||||
width: 100%;
|
||||
height: calc(100% - 72px);
|
||||
}
|
||||
|
||||
@@ -81,78 +81,80 @@
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: calc(100% - 72px);
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.addon-logo-container {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-right: 0.5em;
|
||||
flex-shrink: 0;
|
||||
// .addon-logo-container {
|
||||
// width: 40px;
|
||||
// height: 40px;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// justify-content: center;
|
||||
// margin-right: 0.5em;
|
||||
// flex-shrink: 0;
|
||||
|
||||
.addon-logo {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
// .addon-logo {
|
||||
// width: 100%;
|
||||
// }
|
||||
// }
|
||||
|
||||
.addon-title {
|
||||
font-weight: 400;
|
||||
font-size: 1.1em;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
// .addon-title {
|
||||
// font-weight: 400;
|
||||
// font-size: 1.1em;
|
||||
// word-break: break-all;
|
||||
// white-space: pre-wrap;
|
||||
// }
|
||||
|
||||
.game-version-cell {
|
||||
min-width: 110px;
|
||||
}
|
||||
// .game-version-cell {
|
||||
// min-width: 110px;
|
||||
// }
|
||||
|
||||
.installed-at-cell,
|
||||
.released-at-cell {
|
||||
min-width: 100px;
|
||||
}
|
||||
// .installed-at-cell,
|
||||
// .released-at-cell {
|
||||
// min-width: 100px;
|
||||
// }
|
||||
|
||||
.status-column {
|
||||
// display: flex;
|
||||
width: 130px;
|
||||
// align-items: center;
|
||||
// .status-column {
|
||||
// // display: flex;
|
||||
// width: 130px;
|
||||
// // align-items: center;
|
||||
|
||||
.auto-update-icon {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
// .auto-update-icon {
|
||||
// margin-left: 0.5em;
|
||||
// }
|
||||
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
}
|
||||
// .progress-text {
|
||||
// text-align: center;
|
||||
// }
|
||||
|
||||
.addon-progress {
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
// .addon-progress {
|
||||
// width: 110px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.status-icon {
|
||||
height: 11px;
|
||||
width: 11px;
|
||||
}
|
||||
// .status-icon {
|
||||
// height: 11px;
|
||||
// width: 11px;
|
||||
// }
|
||||
|
||||
.addon-provider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// .addon-provider {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
|
||||
.addon-provider-name {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
// .addon-provider-name {
|
||||
// margin-right: 0.25em;
|
||||
// }
|
||||
|
||||
.provider-logo {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
// .provider-logo {
|
||||
// width: 15px;
|
||||
// height: 15px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.cell-padding {
|
||||
padding-right: 1em;
|
||||
|
||||
@@ -24,6 +24,7 @@ import { MyAddonsComponent } from "./my-addons.component";
|
||||
import { overrideIconModule } from "../../tests/mock-mat-icon";
|
||||
import { WarcraftInstallationService } from "../../services/warcraft/warcraft-installation.service";
|
||||
import { RelativeDurationPipe } from "../../pipes/relative-duration-pipe";
|
||||
import { PushService } from "../../services/push/push.service";
|
||||
|
||||
describe("MyAddonsComponent", () => {
|
||||
let component: MyAddonsComponent;
|
||||
@@ -35,6 +36,7 @@ describe("MyAddonsComponent", () => {
|
||||
let addonServiceSpy: any;
|
||||
let warcraftServiceSpy: any;
|
||||
let warcraftInstallationService: WarcraftInstallationService;
|
||||
let pushService: PushService;
|
||||
|
||||
beforeEach(async () => {
|
||||
wowUpAddonServiceSpy = jasmine.createSpyObj("WowUpAddonService", ["updateForClientType"], {
|
||||
@@ -52,6 +54,10 @@ describe("MyAddonsComponent", () => {
|
||||
}
|
||||
);
|
||||
|
||||
pushService = jasmine.createSpyObj("PushService", [""], {
|
||||
addonUpdate$: new Subject<any>(),
|
||||
});
|
||||
|
||||
const testSortOrder: SortOrder = {
|
||||
colId: "name",
|
||||
sort: "asc",
|
||||
@@ -113,6 +119,7 @@ describe("MyAddonsComponent", () => {
|
||||
{ provide: SessionService, useValue: sessionServiceSpy },
|
||||
{ provide: WarcraftService, useValue: warcraftServiceSpy },
|
||||
{ provide: WarcraftInstallationService, useValue: warcraftInstallationService },
|
||||
{ provide: PushService, useValue: pushService },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
} from "ag-grid-community";
|
||||
import * as _ from "lodash";
|
||||
import { join } from "path";
|
||||
import { from, Observable, of, Subject, Subscription, zip } from "rxjs";
|
||||
import { catchError, debounceTime, delay, first, map, switchMap, tap } from "rxjs/operators";
|
||||
import { BehaviorSubject, from, Observable, of, Subject, Subscription, zip } from "rxjs";
|
||||
import { catchError, debounceTime, delay, filter, 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";
|
||||
@@ -49,6 +49,7 @@ import { WowUpService } from "../../services/wowup/wowup.service";
|
||||
import * as AddonUtils from "../../utils/addon.utils";
|
||||
import { stringIncludes } from "../../utils/string.utils";
|
||||
import { SortOrder } from "../../models/wowup/sort-order";
|
||||
import { PushService } from "../../services/push/push.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-my-addons",
|
||||
@@ -64,6 +65,9 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
// @HostListener("window:keydown", ["$event"])
|
||||
private readonly _operationErrorSrc = new Subject<Error>();
|
||||
private readonly _isBusySrc = new BehaviorSubject<boolean>(true);
|
||||
|
||||
public readonly isBusy$ = this._isBusySrc.asObservable();
|
||||
|
||||
private _subscriptions: Subscription[] = [];
|
||||
private isSelectedTab = false;
|
||||
@@ -84,7 +88,6 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
public selectedInstallation: WowInstallation | undefined = undefined;
|
||||
public wowClientType = WowClientType;
|
||||
public overlayRef: OverlayRef | null = null;
|
||||
public isBusy = true;
|
||||
public enableControls = true;
|
||||
public wowInstallations$: Observable<WowInstallation[]>;
|
||||
public selectedInstallationId!: string;
|
||||
@@ -178,6 +181,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
private _wowUpAddonService: WowUpAddonService,
|
||||
private _translateService: TranslateService,
|
||||
private _snackbarService: SnackbarService,
|
||||
private _pushService: PushService,
|
||||
public addonService: AddonService,
|
||||
public electronService: ElectronService,
|
||||
public overlay: Overlay,
|
||||
@@ -211,10 +215,23 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// If an update push comes in, check if we have any addons installed with any of the ids, if so refresh
|
||||
const pushUpdateSub = this._pushService.addonUpdate$
|
||||
.pipe(
|
||||
debounceTime(5000),
|
||||
filter((addons) => {
|
||||
const addonIds = addons.map((addon) => addon.addonId);
|
||||
return this.addonService.hasAnyWithExternalAddonIds(addonIds);
|
||||
}),
|
||||
switchMap(() => from(this.onRefresh()))
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
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(),
|
||||
pushUpdateSub,
|
||||
addonInstalledSub,
|
||||
addonRemovedSub,
|
||||
filterInputSub
|
||||
@@ -307,7 +324,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
|
||||
this.loadSortOrder();
|
||||
|
||||
this.rowDataChange$.pipe(debounceTime(50)).subscribe(() => {
|
||||
this.rowDataChange$.pipe(debounceTime(500)).subscribe(() => {
|
||||
this.redrawRows();
|
||||
});
|
||||
}
|
||||
@@ -334,10 +351,10 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
from(this.lazyLoad())
|
||||
.pipe(
|
||||
first(),
|
||||
delay(400),
|
||||
map(() => {
|
||||
this.redrawRows();
|
||||
}),
|
||||
// delay(400),
|
||||
// map(() => {
|
||||
// this.redrawRows();
|
||||
// }),
|
||||
catchError((e) => {
|
||||
console.error(e);
|
||||
return of(undefined);
|
||||
@@ -364,7 +381,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
|
||||
this._isRefreshing = true;
|
||||
this.isBusy = true;
|
||||
this._isBusySrc.next(true);
|
||||
this.enableControls = false;
|
||||
|
||||
try {
|
||||
@@ -380,7 +397,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
} catch (e) {
|
||||
console.error(`Failed to refresh addons`, e);
|
||||
} finally {
|
||||
this.isBusy = false;
|
||||
this._isBusySrc.next(false);
|
||||
|
||||
this.enableControls = true;
|
||||
this._isRefreshing = false;
|
||||
}
|
||||
@@ -930,7 +948,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
|
||||
this._lazyLoaded = true;
|
||||
this.isBusy = true;
|
||||
this._isBusySrc.next(true);
|
||||
|
||||
this.enableControls = false;
|
||||
|
||||
// TODO this shouldn't be here
|
||||
@@ -960,7 +979,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
|
||||
private async updateAllWithSpinner(...installations: WowInstallation[]): Promise<void> {
|
||||
this.isBusy = true;
|
||||
this._isBusySrc.next(true);
|
||||
|
||||
this.spinnerMessage = this._translateService.instant("PAGES.MY_ADDONS.SPINNER.GATHERING_ADDONS");
|
||||
this.enableControls = false;
|
||||
|
||||
@@ -1014,7 +1034,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
await this.loadAddons(this.selectedInstallation);
|
||||
} catch (err) {
|
||||
console.error("Failed to update classic/retail", err);
|
||||
this.isBusy = false;
|
||||
this._isBusySrc.next(false);
|
||||
|
||||
this._cdRef.detectChanges();
|
||||
} finally {
|
||||
this.spinnerMessage = "";
|
||||
@@ -1032,7 +1053,8 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isBusy = true;
|
||||
this._isBusySrc.next(true);
|
||||
|
||||
this.enableControls = false;
|
||||
|
||||
if (!installation) {
|
||||
@@ -1056,15 +1078,14 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
this._baseRowData = rowData;
|
||||
this.rowData = this._baseRowData;
|
||||
|
||||
this.isBusy = false;
|
||||
this.setPageContextText();
|
||||
|
||||
this._cdRef.detectChanges();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.isBusy = false;
|
||||
this.enableControls = this.calculateControlState();
|
||||
} finally {
|
||||
this._isBusySrc.next(false);
|
||||
this._cdRef.detectChanges();
|
||||
}
|
||||
};
|
||||
@@ -1190,9 +1211,9 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
}
|
||||
|
||||
private redrawRows() {
|
||||
this.gridApi?.redrawRows();
|
||||
this.gridApi?.resetRowHeights();
|
||||
this.autoSizeColumns();
|
||||
// this.gridApi?.redrawRows();
|
||||
// this.gridApi?.resetRowHeights();
|
||||
// this.autoSizeColumns();
|
||||
this._cdRef.detectChanges();
|
||||
}
|
||||
|
||||
@@ -1229,7 +1250,7 @@ export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
minWidth: 300,
|
||||
headerName: this._translateService.instant("PAGES.MY_ADDONS.TABLE.ADDON_COLUMN_HEADER"),
|
||||
sortable: true,
|
||||
autoHeight: true,
|
||||
// autoHeight: true,
|
||||
cellRenderer: "myAddonRenderer",
|
||||
colId: "name",
|
||||
valueGetter: (params) => {
|
||||
|
||||
@@ -515,6 +515,16 @@ export class AddonService {
|
||||
});
|
||||
}
|
||||
|
||||
public getAllByExternalAddonId(externalAddonIds: string[]): Addon[] {
|
||||
return this._addonStorage.queryAll((addon) => {
|
||||
return externalAddonIds.includes(addon.externalId);
|
||||
});
|
||||
}
|
||||
|
||||
public hasAnyWithExternalAddonIds(externalAddonIds: string[]): boolean {
|
||||
return this.getAllByExternalAddonId(externalAddonIds).length > 0;
|
||||
}
|
||||
|
||||
public updateAddon(
|
||||
addonId: string,
|
||||
onUpdate: (installState: AddonInstallState, progress: number) => void = () => {},
|
||||
@@ -1786,7 +1796,7 @@ export class AddonService {
|
||||
}
|
||||
|
||||
try {
|
||||
const tocPaths = this.getTocPaths(addon);
|
||||
const tocPaths = await this.getTocPaths(addon);
|
||||
const tocFiles = await Promise.all(_.map(tocPaths, (tocPath) => this._tocService.parse(tocPath)));
|
||||
const orderedTocFiles = _.orderBy(tocFiles, ["wowInterfaceId", "loadOnDemand"], ["desc", "asc"]);
|
||||
const primaryToc = _.first(orderedTocFiles);
|
||||
@@ -1808,7 +1818,7 @@ export class AddonService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public getTocPaths(addon: Addon): string[] {
|
||||
public async getTocPaths(addon: Addon): Promise<string[]> {
|
||||
if (!addon.installationId) {
|
||||
return [];
|
||||
}
|
||||
@@ -1820,9 +1830,14 @@ export class AddonService {
|
||||
|
||||
const addonFolderPath = this._warcraftService.getAddonFolderPath(installation);
|
||||
|
||||
return _.map(addon.installedFolderList, (installedFolder) =>
|
||||
path.join(addonFolderPath, installedFolder, `${installedFolder}.toc`)
|
||||
const addonTocs = await this._tocService.getAllTocs(
|
||||
addonFolderPath,
|
||||
addon.installedFolderList,
|
||||
installation.clientType
|
||||
);
|
||||
|
||||
const tocPaths = addonTocs.map((toc) => toc.filePath);
|
||||
return tocPaths;
|
||||
}
|
||||
|
||||
private getAddonProvider(addonUri: URL): AddonProvider | undefined {
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
IPC_SET_ZOOM_LIMITS,
|
||||
IPC_SHOW_OPEN_DIALOG,
|
||||
IPC_SYSTEM_PREFERENCES_GET_USER_DEFAULT,
|
||||
IPC_UPDATE_APP_BADGE,
|
||||
IPC_WINDOW_LEAVE_FULLSCREEN,
|
||||
IPC_WINDOW_MAXIMIZED,
|
||||
IPC_WINDOW_MINIMIZED,
|
||||
|
||||
@@ -10,6 +10,9 @@ import {
|
||||
ExternalUrlConfirmationDialogComponent,
|
||||
} from "../../components/external-url-confirmation-dialog/external-url-confirmation-dialog.component";
|
||||
import { WowUpService } from "../wowup/wowup.service";
|
||||
import { AnalyticsService } from "../analytics/analytics.service";
|
||||
import { USER_ACTION_OPEN_LINK } from "../../../common/constants";
|
||||
import { ElectronService } from "../electron/electron.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
@@ -17,16 +20,25 @@ import { WowUpService } from "../wowup/wowup.service";
|
||||
export class LinkService {
|
||||
public constructor(
|
||||
private _dialog: MatDialog,
|
||||
private _analyticsService: AnalyticsService,
|
||||
private _electronService: ElectronService,
|
||||
private _wowUpService: WowUpService,
|
||||
private _translateService: TranslateService
|
||||
) {}
|
||||
|
||||
public async openExternalLink(url: string): Promise<void> {
|
||||
this._analyticsService.trackAction(USER_ACTION_OPEN_LINK, {
|
||||
link: url,
|
||||
});
|
||||
await this._electronService.openExternal(url);
|
||||
}
|
||||
|
||||
public confirmLinkNavigation(href: string): Observable<any> {
|
||||
return from(this._wowUpService.isTrustedDomain(href)).pipe(
|
||||
first(),
|
||||
switchMap((isTrusted) => {
|
||||
if (isTrusted) {
|
||||
return from(this._wowUpService.openExternalLink(href));
|
||||
return from(this.openExternalLink(href));
|
||||
} else {
|
||||
return this.showLinkNavigationDialog(href);
|
||||
}
|
||||
@@ -56,10 +68,10 @@ export class LinkService {
|
||||
|
||||
if (result.trustDomain !== "") {
|
||||
return from(this._wowUpService.trustDomain(result.trustDomain)).pipe(
|
||||
switchMap(() => from(this._wowUpService.openExternalLink(href)))
|
||||
switchMap(() => from(this.openExternalLink(href)))
|
||||
);
|
||||
} else {
|
||||
return from(this._wowUpService.openExternalLink(href));
|
||||
return from(this.openExternalLink(href));
|
||||
}
|
||||
}),
|
||||
catchError((e) => {
|
||||
|
||||
@@ -32,7 +32,7 @@ export class CircuitBreakerWrapper {
|
||||
resetTimeout: resetTimeoutMs,
|
||||
errorFilter: (err) => {
|
||||
// Don't trip the breaker on a 404
|
||||
return err.status === 404;
|
||||
return err.status >= 400 && err.status < 500;
|
||||
},
|
||||
});
|
||||
this._cb.on("open", () => {
|
||||
@@ -47,10 +47,16 @@ export class CircuitBreakerWrapper {
|
||||
return (await this._cb.fire(action)) as TOUT;
|
||||
}
|
||||
|
||||
public getJson<T>(url: URL | string, timeoutMs?: number): Promise<T> {
|
||||
public getJson<T>(
|
||||
url: URL | string,
|
||||
headers: {
|
||||
[header: string]: string | string[];
|
||||
} = {},
|
||||
timeoutMs?: number
|
||||
): Promise<T> {
|
||||
return this.fire(() =>
|
||||
this._httpClient
|
||||
.get<T>(url.toString(), { headers: { ...CACHE_CONTROL_HEADERS } })
|
||||
.get<T>(url.toString(), { headers: { ...CACHE_CONTROL_HEADERS, ...headers } })
|
||||
.pipe(first(), timeout(timeoutMs ?? this._defaultTimeoutMs))
|
||||
.toPromise()
|
||||
);
|
||||
@@ -65,10 +71,34 @@ export class CircuitBreakerWrapper {
|
||||
);
|
||||
}
|
||||
|
||||
public postJson<T>(url: URL | string, body: unknown, timeoutMs?: number): Promise<T> {
|
||||
public postJson<T>(
|
||||
url: URL | string,
|
||||
body: unknown,
|
||||
headers: {
|
||||
[header: string]: string | string[];
|
||||
} = {},
|
||||
timeoutMs?: number
|
||||
): Promise<T> {
|
||||
const cheaders = headers || {};
|
||||
return this.fire<T>(() =>
|
||||
this._httpClient
|
||||
.post<T>(url.toString(), body)
|
||||
.post<T>(url.toString(), body, { headers: { ...cheaders } })
|
||||
.pipe(first(), timeout(timeoutMs ?? this._defaultTimeoutMs))
|
||||
.toPromise()
|
||||
);
|
||||
}
|
||||
|
||||
public deleteJson<T>(
|
||||
url: URL | string,
|
||||
headers: {
|
||||
[header: string]: string | string[];
|
||||
} = {},
|
||||
timeoutMs?: number
|
||||
): Promise<T> {
|
||||
const cheaders = headers || {};
|
||||
return this.fire<T>(() =>
|
||||
this._httpClient
|
||||
.delete<T>(url.toString(), { headers: { ...cheaders } })
|
||||
.pipe(first(), timeout(timeoutMs ?? this._defaultTimeoutMs))
|
||||
.toPromise()
|
||||
);
|
||||
|
||||
43
wowup-electron/src/app/services/push/push.service.ts
Normal file
43
wowup-electron/src/app/services/push/push.service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Subject } from "rxjs";
|
||||
import { ElectronService } from "..";
|
||||
import { IPC_PUSH_NOTIFICATION } from "../../../common/constants";
|
||||
import { AddonUpdatePushNotification, PushNotification } from "../../../common/wowup/models";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class PushService {
|
||||
private readonly _addonUpdateSrc = new Subject<AddonUpdatePushNotification[]>();
|
||||
|
||||
public readonly addonUpdate$ = this._addonUpdateSrc.asObservable();
|
||||
|
||||
public constructor(private _electronService: ElectronService) {
|
||||
this._electronService.onRendererEvent(IPC_PUSH_NOTIFICATION, (evt, data) => {
|
||||
try {
|
||||
this.parsePushNotification(data);
|
||||
} catch (e) {
|
||||
console.error("Failed to handle push notification", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private parsePushNotification(data: PushNotification<any>) {
|
||||
switch (data.action) {
|
||||
case "addon-update":
|
||||
this.parseAddonUpdateNotification(data);
|
||||
break;
|
||||
default:
|
||||
console.warn("Unhandled push notification", data.action);
|
||||
}
|
||||
}
|
||||
|
||||
private parseAddonUpdateNotification(note: PushNotification<AddonUpdatePushNotification[]>) {
|
||||
if (typeof note.message === "string") {
|
||||
note.message = JSON.parse(note.message) as AddonUpdatePushNotification[];
|
||||
}
|
||||
|
||||
console.debug("parseAddonUpdateNotification", note);
|
||||
this._addonUpdateSrc.next(note.message);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,16 @@
|
||||
import * as _ from "lodash";
|
||||
import { BehaviorSubject, from, Subject } from "rxjs";
|
||||
import { BehaviorSubject, Subject } from "rxjs";
|
||||
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import {
|
||||
APP_PROTOCOL_NAME,
|
||||
SELECTED_DETAILS_TAB_KEY,
|
||||
STORAGE_WOWUP_AUTH_TOKEN,
|
||||
TAB_INDEX_SETTINGS,
|
||||
} from "../../../common/constants";
|
||||
import { SELECTED_DETAILS_TAB_KEY, TAB_INDEX_SETTINGS } 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 { ColumnState } from "../../models/wowup/column-state";
|
||||
import { ElectronService } from "../electron/electron.service";
|
||||
import { filter, map, switchMap } from "rxjs/operators";
|
||||
import { getProtocol } from "../../utils/string.utils";
|
||||
import { map } from "rxjs/operators";
|
||||
import { WowUpApiService } from "../wowup-api/wowup-api.service";
|
||||
import { WowUpAccountService } from "../wowup/wowup-account.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
@@ -29,7 +24,6 @@ export class SessionService {
|
||||
private readonly _addonsChangedSrc = new Subject<boolean>();
|
||||
private readonly _myAddonsColumnsSrc = new BehaviorSubject<ColumnState[]>([]);
|
||||
private readonly _targetFileInstallCompleteSrc = new Subject<boolean>();
|
||||
private readonly _wowUpAuthTokenSrc = new BehaviorSubject<string>("");
|
||||
|
||||
private readonly _getAddonsColumnsSrc = new Subject<ColumnState>();
|
||||
|
||||
@@ -45,14 +39,16 @@ export class SessionService {
|
||||
public readonly getAddonsHiddenColumns$ = this._getAddonsColumnsSrc.asObservable();
|
||||
public readonly targetFileInstallComplete$ = this._targetFileInstallCompleteSrc.asObservable();
|
||||
public readonly editingWowInstallationId$ = new BehaviorSubject<string>("");
|
||||
public readonly wowUpAuthToken$ = this._wowUpAuthTokenSrc.asObservable();
|
||||
public readonly wowUpAuthToken$ = this._wowUpAccountService.wowUpAuthTokenSrc.asObservable();
|
||||
public readonly wowUpAccount$ = this._wowUpAccountService.wowUpAccountSrc.asObservable();
|
||||
public readonly wowUpAccountPushEnabled$ = this._wowUpAccountService.accountPushSrc.asObservable();
|
||||
|
||||
public readonly wowUpAuthenticated$ = this.wowUpAuthToken$.pipe(map((token) => !!token && token.length > 10));
|
||||
public readonly wowUpAuthenticated$ = this.wowUpAccount$.pipe(map((account) => account !== undefined));
|
||||
|
||||
public constructor(
|
||||
private _electronService: ElectronService,
|
||||
private _warcraftInstallationService: WarcraftInstallationService,
|
||||
private _preferenceStorageService: PreferenceStorageService
|
||||
private _preferenceStorageService: PreferenceStorageService,
|
||||
private _wowUpAccountService: WowUpAccountService
|
||||
) {
|
||||
this._selectedDetailTabType =
|
||||
this._preferenceStorageService.getObject<DetailsTabType>(SELECTED_DETAILS_TAB_KEY) || "description";
|
||||
@@ -60,53 +56,22 @@ export class SessionService {
|
||||
this._warcraftInstallationService.wowInstallations$.subscribe((installations) =>
|
||||
this.onWowInstallationsChange(installations)
|
||||
);
|
||||
|
||||
this._electronService.customProtocol$
|
||||
.pipe(
|
||||
filter((protocol) => !!protocol),
|
||||
map((protocol) => this.handleLoginProtocol(protocol))
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.loadAuthToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the post login protocol message
|
||||
* wowup://login/desktop/#{token}
|
||||
*/
|
||||
private handleLoginProtocol = (protocol: string): void => {
|
||||
const protocolName = getProtocol(protocol);
|
||||
if (protocolName !== APP_PROTOCOL_NAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = protocol.split("/");
|
||||
if (parts[2] !== "login" || parts[3] !== "desktop") {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = parts[4];
|
||||
if (typeof token !== "string" || token.length < 10) {
|
||||
console.warn("Invalid auth token", protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("GOT WOWUP PROTOCOL", protocol);
|
||||
window.localStorage.setItem(STORAGE_WOWUP_AUTH_TOKEN, token);
|
||||
this._wowUpAuthTokenSrc.next(token);
|
||||
};
|
||||
|
||||
private loadAuthToken(): void {
|
||||
const storedToken = window.localStorage.getItem(STORAGE_WOWUP_AUTH_TOKEN);
|
||||
if (storedToken) {
|
||||
this._wowUpAuthTokenSrc.next(storedToken);
|
||||
}
|
||||
public get wowUpAuthToken(): string {
|
||||
return this._wowUpAccountService.wowUpAuthTokenSrc.value;
|
||||
}
|
||||
|
||||
public clearWowUpAuthToken(): void {
|
||||
window.localStorage.removeItem(STORAGE_WOWUP_AUTH_TOKEN);
|
||||
this._wowUpAuthTokenSrc.next("");
|
||||
public login(): void {
|
||||
this._wowUpAccountService.login();
|
||||
}
|
||||
|
||||
public logout(): void {
|
||||
this._wowUpAccountService.logout();
|
||||
}
|
||||
|
||||
public async toggleAccountPush(enabled: boolean): Promise<void> {
|
||||
return await this._wowUpAccountService.toggleAccountPush(enabled);
|
||||
}
|
||||
|
||||
public isAuthenticated(): boolean {
|
||||
|
||||
@@ -43,6 +43,7 @@ export class TocService {
|
||||
|
||||
return {
|
||||
fileName,
|
||||
filePath: tocPath,
|
||||
author: this.getValue(TOC_AUTHOR, tocText),
|
||||
curseProjectId: this.getValue(TOC_X_CURSE_PROJECT_ID, tocText),
|
||||
interface: this.getValue(TOC_INTERFACE, tocText),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from "@angular/core";
|
||||
import { from, Observable, of } from "rxjs";
|
||||
import { tap } from "rxjs/operators";
|
||||
import { AppConfig } from "../../../environments/environment";
|
||||
import { WowUpGetAccountResponse } from "../../models/wowup-api/api-responses";
|
||||
import { BlockListRepresentation } from "../../models/wowup-api/block-list";
|
||||
import { CachingService } from "../caching/caching-service";
|
||||
import { CircuitBreakerWrapper, NetworkService } from "../network/network.service";
|
||||
@@ -21,6 +22,28 @@ export class WowUpApiService {
|
||||
this.getBlockList().subscribe();
|
||||
}
|
||||
|
||||
public async getAccount(authToken: string): Promise<WowUpGetAccountResponse> {
|
||||
const url = new URL(`${API_URL}/account`);
|
||||
return await this._circuitBreaker.getJson<WowUpGetAccountResponse>(
|
||||
url,
|
||||
this.getAuthorizationHeader(authToken),
|
||||
5000
|
||||
);
|
||||
}
|
||||
|
||||
public async registerPushToken(authToken: string, pushToken: string, deviceType: string): Promise<any> {
|
||||
const url = new URL(`${API_URL}/account/push`);
|
||||
url.searchParams.set("push_token", pushToken);
|
||||
url.searchParams.set("os", deviceType);
|
||||
|
||||
return this._circuitBreaker.postJson<any>(url, {}, this.getAuthorizationHeader(authToken));
|
||||
}
|
||||
|
||||
public async removePushToken(authToken: string, pushToken: string): Promise<any> {
|
||||
const url = new URL(`${API_URL}/account/push/${pushToken}`);
|
||||
return this._circuitBreaker.deleteJson<any>(url, this.getAuthorizationHeader(authToken));
|
||||
}
|
||||
|
||||
public getBlockList(): Observable<BlockListRepresentation> {
|
||||
const cached = this._cacheService.get<BlockListRepresentation>(BLOCKLIST_CACHE_KEY);
|
||||
if (cached) {
|
||||
@@ -35,4 +58,10 @@ export class WowUpApiService {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private getAuthorizationHeader(authToken: string): { Authorization: string } {
|
||||
return {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ const CHANGELOGS: ChangeLog[] = [
|
||||
<div>
|
||||
<h4 style="margin-top: 1em;">New Features</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<div>The first stage of a set of cloud based features you've been asking for</div>
|
||||
<img style="width: 80%;" loading="lazy" src="https://cdn.wowup.io/patch-notes/2-5/account-2.5-preview.png">
|
||||
</li>
|
||||
<li>Added WowUpHub category support</li>
|
||||
<li>Added WowUpHub preview support</li>
|
||||
<li>Added Mac M1 builds</li>
|
||||
|
||||
195
wowup-electron/src/app/services/wowup/wowup-account.service.ts
Normal file
195
wowup-electron/src/app/services/wowup/wowup-account.service.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { BehaviorSubject, from } from "rxjs";
|
||||
import { filter, map, switchMap } from "rxjs/operators";
|
||||
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import {
|
||||
ACCT_FEATURE_KEYS,
|
||||
ACCT_PUSH_ENABLED_KEY,
|
||||
APP_PROTOCOL_NAME,
|
||||
IPC_PUSH_INIT,
|
||||
IPC_PUSH_REGISTER,
|
||||
IPC_PUSH_SUBSCRIBE,
|
||||
IPC_PUSH_UNREGISTER,
|
||||
STORAGE_WOWUP_AUTH_TOKEN,
|
||||
} from "../../../common/constants";
|
||||
import { AppConfig } from "../../../environments/environment";
|
||||
import { getProtocol } from "../../utils/string.utils";
|
||||
import { ElectronService } from "../electron/electron.service";
|
||||
import { LinkService } from "../links/link.service";
|
||||
import { PreferenceStorageService } from "../storage/preference-storage.service";
|
||||
import { WowUpApiService } from "../wowup-api/wowup-api.service";
|
||||
import { WowUpGetAccountResponse } from "../../models/wowup-api/api-responses";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class WowUpAccountService {
|
||||
public readonly wowUpAuthTokenSrc = new BehaviorSubject<string>("");
|
||||
public readonly wowUpAccountSrc = new BehaviorSubject<WowUpGetAccountResponse | undefined>(undefined);
|
||||
public readonly accountPushSrc = new BehaviorSubject<boolean>(false);
|
||||
|
||||
public get accountPushEnabled(): boolean {
|
||||
return this._preferenceStorageService.findByKey(ACCT_PUSH_ENABLED_KEY) === true.toString();
|
||||
}
|
||||
|
||||
public set accountPushEnabled(enabled: boolean) {
|
||||
this._preferenceStorageService.set(ACCT_PUSH_ENABLED_KEY, enabled);
|
||||
}
|
||||
|
||||
public get authToken(): string {
|
||||
return this.wowUpAuthTokenSrc.value;
|
||||
}
|
||||
|
||||
public get account(): WowUpGetAccountResponse | undefined {
|
||||
return this.wowUpAccountSrc.value;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
private _electronService: ElectronService,
|
||||
private _linkService: LinkService,
|
||||
private _preferenceStorageService: PreferenceStorageService,
|
||||
private _wowUpApiService: WowUpApiService
|
||||
) {
|
||||
this.wowUpAuthTokenSrc
|
||||
.pipe(
|
||||
filter((token) => !!token && token.length > 10),
|
||||
switchMap((token) => from(this.onAuthTokenChanged(token)))
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this._electronService.customProtocol$
|
||||
.pipe(
|
||||
filter((protocol) => !!protocol),
|
||||
map((protocol) => this.handleLoginProtocol(protocol))
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.loadAuthToken();
|
||||
|
||||
console.debug("accountPushEnabled", this.accountPushEnabled);
|
||||
if (this.accountPushEnabled) {
|
||||
this.initializePush().catch((e) => console.error(e));
|
||||
this.accountPushSrc.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
public login(): void {
|
||||
this._linkService
|
||||
.openExternalLink(`${AppConfig.wowUpWebsiteUrl}/login?client=desktop`)
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
|
||||
public logout(): void {
|
||||
this.clearAuthToken();
|
||||
this.resetAccountPreferences();
|
||||
}
|
||||
|
||||
private onAuthTokenChanged = async (token: string) => {
|
||||
try {
|
||||
const account = await this._wowUpApiService.getAccount(token);
|
||||
console.debug("Account", account);
|
||||
this.wowUpAccountSrc.next(account);
|
||||
|
||||
if (this.accountPushEnabled) {
|
||||
await this.toggleAccountPush(true);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
// Check if user is no longer authorized
|
||||
if (e instanceof HttpErrorResponse && [403, 401].includes(e.status)) {
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private clearAuthToken(): void {
|
||||
window.localStorage.removeItem(STORAGE_WOWUP_AUTH_TOKEN);
|
||||
this.wowUpAuthTokenSrc.next("");
|
||||
this.wowUpAccountSrc.next(undefined);
|
||||
}
|
||||
|
||||
private loadAuthToken(): void {
|
||||
const storedToken = window.localStorage.getItem(STORAGE_WOWUP_AUTH_TOKEN);
|
||||
if (storedToken) {
|
||||
console.debug("loaded auth token", storedToken);
|
||||
this.wowUpAuthTokenSrc.next(storedToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the post login protocol message
|
||||
* wowup://login/desktop/#{token}
|
||||
*/
|
||||
private handleLoginProtocol = (protocol: string): void => {
|
||||
const protocolName = getProtocol(protocol);
|
||||
if (protocolName !== APP_PROTOCOL_NAME) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = protocol.split("/");
|
||||
if (parts[2] !== "login" || parts[3] !== "desktop") {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = parts[4];
|
||||
if (typeof token !== "string" || token.length < 10) {
|
||||
console.warn("Invalid auth token", protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("GOT WOWUP PROTOCOL", protocol);
|
||||
window.localStorage.setItem(STORAGE_WOWUP_AUTH_TOKEN, token);
|
||||
this.wowUpAuthTokenSrc.next(token);
|
||||
};
|
||||
|
||||
// ACCOUNT FEATURES
|
||||
public async toggleAccountPush(enabled: boolean): Promise<void> {
|
||||
try {
|
||||
if (enabled) {
|
||||
await this.initializePush();
|
||||
await this.registerForPush(this.authToken, this.account.config.pushAppId);
|
||||
|
||||
if (this.account.config?.pushChannels?.addonUpdates) {
|
||||
await this.subscribe(this.account.config.pushChannels.addonUpdates);
|
||||
}
|
||||
} else {
|
||||
await this.unregisterForPush(this.authToken, this.account.config.pushAppId);
|
||||
}
|
||||
this.accountPushEnabled = enabled;
|
||||
} catch (e) {
|
||||
console.error("Failed to toggle account push", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// LOCAL PREFERENCES
|
||||
private resetAccountPreferences(): void {
|
||||
for (const key of ACCT_FEATURE_KEYS) {
|
||||
this._preferenceStorageService.set(key, false);
|
||||
}
|
||||
}
|
||||
|
||||
// PUSH
|
||||
public async initializePush(): Promise<boolean> {
|
||||
return await this._electronService.invoke(IPC_PUSH_INIT);
|
||||
}
|
||||
|
||||
public async registerForPush(authToken: string, pushAppId: string): Promise<string> {
|
||||
const pushToken = await this._electronService.invoke(IPC_PUSH_REGISTER, pushAppId);
|
||||
await this._wowUpApiService.registerPushToken(authToken, pushToken, this._electronService.platform);
|
||||
return pushToken;
|
||||
}
|
||||
|
||||
public async unregisterForPush(authToken: string, pushAppId: string): Promise<void> {
|
||||
const pushToken = await this._electronService.invoke(IPC_PUSH_REGISTER, pushAppId);
|
||||
await this._electronService.invoke(IPC_PUSH_UNREGISTER);
|
||||
await this._wowUpApiService.removePushToken(authToken, pushToken);
|
||||
}
|
||||
|
||||
private async subscribe(channel: string): Promise<void> {
|
||||
await this._electronService.invoke(IPC_PUSH_SUBSCRIBE, channel);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { Injectable } from "@angular/core";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
import {
|
||||
ACCT_PUSH_ENABLED_KEY,
|
||||
ADDON_MIGRATION_VERSION_KEY,
|
||||
ADDON_PROVIDERS_KEY,
|
||||
COLLAPSE_TO_TRAY_PREFERENCE_KEY,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
DEFAULT_AUTO_UPDATE_PREFERENCE_KEY_SUFFIX,
|
||||
DEFAULT_CHANNEL_PREFERENCE_KEY_SUFFIX,
|
||||
DEFAULT_THEME,
|
||||
DEFAULT_TRUSTED_DOMAINS,
|
||||
ENABLE_APP_BADGE_KEY,
|
||||
ENABLE_SYSTEM_NOTIFICATIONS_PREFERENCE_KEY,
|
||||
GET_ADDONS_HIDDEN_COLUMNS_KEY,
|
||||
@@ -30,7 +32,6 @@ import {
|
||||
START_WITH_SYSTEM_PREFERENCE_KEY,
|
||||
TRUSTED_DOMAINS_KEY,
|
||||
UPDATE_NOTES_POPUP_VERSION_KEY,
|
||||
USER_ACTION_OPEN_LINK,
|
||||
USE_HARDWARE_ACCELERATION_PREFERENCE_KEY,
|
||||
USE_SYMLINK_MODE_PREFERENCE_KEY,
|
||||
WOWUP_RELEASE_CHANNEL_PREFERENCE_KEY,
|
||||
@@ -43,7 +44,6 @@ import { PreferenceChange } from "../../models/wowup/preference-change";
|
||||
import { SortOrder } from "../../models/wowup/sort-order";
|
||||
import { WowUpReleaseChannelType } from "../../models/wowup/wowup-release-channel-type";
|
||||
import { getEnumList, getEnumName } from "../../utils/enum.utils";
|
||||
import { AnalyticsService } from "../analytics/analytics.service";
|
||||
import { ElectronService } from "../electron/electron.service";
|
||||
import { FileService } from "../files/file.service";
|
||||
import { PreferenceStorageService } from "../storage/preference-storage.service";
|
||||
@@ -68,8 +68,7 @@ export class WowUpService {
|
||||
private _preferenceStorageService: PreferenceStorageService,
|
||||
private _electronService: ElectronService,
|
||||
private _fileService: FileService,
|
||||
private _translateService: TranslateService,
|
||||
private _analyticsService: AnalyticsService
|
||||
private _translateService: TranslateService
|
||||
) {
|
||||
this.setDefaultPreferences()
|
||||
// .then(() => console.debug("Set default preferences"))
|
||||
@@ -85,10 +84,6 @@ export class WowUpService {
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
|
||||
public login(): void {
|
||||
this.openExternalLink("http://localhost:3000/dev/login?client=desktop").catch((e) => console.error(e));
|
||||
}
|
||||
|
||||
public async getApplicationVersion(): Promise<string> {
|
||||
const appVersion = await this._electronService.invoke<string>(IPC_GET_APP_VERSION);
|
||||
return `${appVersion}${this._electronService.isPortable ? " (portable)" : ""}`;
|
||||
@@ -99,13 +94,6 @@ export class WowUpService {
|
||||
return appVersion.toLowerCase().indexOf("beta") != -1;
|
||||
}
|
||||
|
||||
public async openExternalLink(url: string): Promise<void> {
|
||||
this._analyticsService.trackAction(USER_ACTION_OPEN_LINK, {
|
||||
link: url,
|
||||
});
|
||||
await this._electronService.openExternal(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called before the app component is initialized in order to catch issues
|
||||
* with unsupported languages
|
||||
@@ -365,6 +353,10 @@ export class WowUpService {
|
||||
|
||||
public async isTrustedDomain(href: string | URL): Promise<boolean> {
|
||||
const url = href instanceof URL ? href : new URL(href);
|
||||
if (DEFAULT_TRUSTED_DOMAINS.includes(url.hostname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const trustedDomains = await this.getTrustedDomains();
|
||||
return trustedDomains.includes(url.hostname);
|
||||
}
|
||||
@@ -400,7 +392,8 @@ export class WowUpService {
|
||||
this.setDefaultPreference(WOWUP_RELEASE_CHANNEL_PREFERENCE_KEY, await this.getDefaultReleaseChannel());
|
||||
this.setDefaultPreference(USE_SYMLINK_MODE_PREFERENCE_KEY, false);
|
||||
this.setDefaultPreference(ENABLE_APP_BADGE_KEY, true);
|
||||
this.setDefaultPreference(TRUSTED_DOMAINS_KEY, ["wowup.io", "discord.gg", "www.patreon.com", "github.com"]);
|
||||
this.setDefaultPreference(TRUSTED_DOMAINS_KEY, DEFAULT_TRUSTED_DOMAINS);
|
||||
this.setDefaultPreference(ACCT_PUSH_ENABLED_KEY, false);
|
||||
this.setDefaultClientPreferences();
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Failed to install addon, {addonName}. Please try again later.",
|
||||
"ADDON_SCAN_ERROR": "Vyskytla se chyba při párování vaší složky addonů s poskytovatelem {providerName}, zkuste to prosím později.",
|
||||
"ADDON_SYNC_ERROR": "Vyskytla se chyba při zjišťování aktualizací od poskytovatele {providerName}, zkuste to prosím později.",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Navštivte naše webové stránky!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categories",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Addon Categories",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Addon '{addonName}' konnte nicht installiert werden. Versuche es später erneut.",
|
||||
"ADDON_SCAN_ERROR": "Beim Abgleichen deiner Addon-Ordner mit {providerName} ist ein Fehler aufgetreten. Versuche es später erneut.",
|
||||
"ADDON_SYNC_ERROR": "Beim Überprüfen auf Aktualisierungen von '{providerName}' ist ein Fehler aufgetreten.",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Schau dir die Website an!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Kategorien",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Addon Kategorien",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Failed to install addon, {addonName}. Please try again later.",
|
||||
"ADDON_SCAN_ERROR": "An error occurred matching your addon folders with {providerName}, please try again later.",
|
||||
"ADDON_SYNC_ERROR": "An error occurred checking for updates from {providerName}, please try again later.",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Check out the website!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categories",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Addon Categories",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Falló la instalación del addon {addonName}. Por favor, inténtelo de nuevo más tarde.",
|
||||
"ADDON_SCAN_ERROR": "Ocurrió un error al comparar sus carpetas de addons con {providerName}. Por favor, inténtelo de nuevo más tarde.",
|
||||
"ADDON_SYNC_ERROR": "Ocurrió un error al comprobar actualizaciones desde: {providerName}",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "¡Visite la página web!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categorías",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Categorías de addons",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Failed to install addon, {addonName}. Please try again later.",
|
||||
"ADDON_SCAN_ERROR": "Une erreur s'est produite lors de la correspondance de vos dossiers d'addons avec {providerName}, merci de réessayer plus tard.",
|
||||
"ADDON_SYNC_ERROR": "Une erreur s'est produite lors de la vérification des mises à jour depuis : {providerName}",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Découvrez le site web !"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categories",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Addon Categories",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Fallito nell'installare l'addon {addonName}. Per favore riprovare più tardi.",
|
||||
"ADDON_SCAN_ERROR": "Errore nel sincronizzare le cartelle dell'addon con {providerName}, per favore riprovare più tardi.",
|
||||
"ADDON_SYNC_ERROR": "Errore nel controllare gli aggioranementi da: {providerName}",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Dai un'occhiata al sito!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categorie",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Categorie Addons",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Failed to install addon, {addonName}. Please try again later.",
|
||||
"ADDON_SCAN_ERROR": "An error occurred matching your addon folders with {providerName}, please try again later.",
|
||||
"ADDON_SYNC_ERROR": "An error occurred checking for updates from: {providerName}",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "웹사이트로 이동"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categories",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Addon Categories",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Failed to install addon, {addonName}. Please try again later.",
|
||||
"ADDON_SCAN_ERROR": "An error occurred matching your addon folders with {providerName}, please try again later.",
|
||||
"ADDON_SYNC_ERROR": "An error occurred checking for updates from: {providerName}",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Besøk nettsiden!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categories",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Addon Categories",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Failed to install addon, {addonName}. Please try again later.",
|
||||
"ADDON_SCAN_ERROR": "An error occurred matching your addon folders with {providerName}, please try again later.",
|
||||
"ADDON_SYNC_ERROR": "An error occurred checking for updates from: {providerName}",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Conheça o nosso site!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Categories",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Addon Categories",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "Не удалось установить модификацию {addonName}. Попробуйте позже.",
|
||||
"ADDON_SCAN_ERROR": "Возникла ошибка при сравнении ваших папок с {providerName}, попробуйте позже.",
|
||||
"ADDON_SYNC_ERROR": "Возникла ошибка при проверке обновлений с: {providerName}",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "Посетите наш сайт!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "Категории",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "Категории модификаций",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "安裝插件 {addonName} 失敗。請稍後再試。",
|
||||
"ADDON_SCAN_ERROR": "在與 {providerName} 匹配插件資料夾時出現錯誤,請稍後再試。",
|
||||
"ADDON_SYNC_ERROR": "從 {providerName} 上檢查更新時出現錯誤。",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "訪問網站!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "類別",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "插件類別",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
}
|
||||
},
|
||||
"ERRORS": {
|
||||
"ACCOUNT_PUSH_TOGGLE_FAILED_ERROR": "Failed to toggle instant updates for your account. Please try again later, or reach out on Discord.",
|
||||
"ADDON_INSTALL_ERROR": "安装插件 {addonName} 失败。请稍后再试。",
|
||||
"ADDON_SCAN_ERROR": "在与 {providerName} 匹配插件文件夹时出现错误,请稍后再试。",
|
||||
"ADDON_SYNC_ERROR": "从 {providerName} 上检查更新时出现错误。",
|
||||
@@ -274,6 +275,15 @@
|
||||
"TITLE": "WowUp.io",
|
||||
"WEBSITE_LINK_LABEL": "访问网站!"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"BETA": "Beta",
|
||||
"LOGIN_BUTTON": "Login Now!",
|
||||
"LOGOUT_BUTTON": "Logout",
|
||||
"LOGOUT_CONFIRMATION_MESSAGE": "Are you sure you want to log out? All of your local account data will be removed, until you login again.",
|
||||
"LOGOUT_CONFIRMATION_TITLE": "Logout?",
|
||||
"MANAGE_ACCOUNT_BUTTON": "Manage Account",
|
||||
"TITLE": "Account"
|
||||
},
|
||||
"GET_ADDONS": {
|
||||
"ADDON_CATEGORIES_BUTTON": "类别",
|
||||
"ADDON_CATEGORIES_MENU_TITLE": "插件类别",
|
||||
|
||||
@@ -87,6 +87,11 @@ export const IPC_APP_INSTALL_UPDATE = "app-install-update";
|
||||
export const IPC_APP_CHECK_UPDATE = "app-check-update";
|
||||
export const IPC_UPDATE_APP_BADGE = "update-app-badge";
|
||||
export const IPC_WINDOW_RESUME = "window-resume";
|
||||
export const IPC_PUSH_INIT = "push-init";
|
||||
export const IPC_PUSH_REGISTER = "push-register";
|
||||
export const IPC_PUSH_UNREGISTER = "push-unregister";
|
||||
export const IPC_PUSH_SUBSCRIBE = "push-subscribe";
|
||||
export const IPC_PUSH_NOTIFICATION = "push-notification";
|
||||
|
||||
// IPC STORAGE
|
||||
export const IPC_STORE_GET_OBJECT = "store-get-object";
|
||||
@@ -129,6 +134,9 @@ export const RETAIL_PTR_LOCATION_KEY = "wow_retail_ptr_location"; // TODO remove
|
||||
export const CLASSIC_LOCATION_KEY = "wow_classic_location"; // TODO remove, deprecated
|
||||
export const CLASSIC_PTR_LOCATION_KEY = "wow_classic_ptr_location"; // TODO remove, deprecated
|
||||
export const BETA_LOCATION_KEY = "wow_beta_location"; // TODO remove, deprecated
|
||||
export const ACCT_PUSH_ENABLED_KEY = "acct_push_enabled";
|
||||
|
||||
export const ACCT_FEATURE_KEYS = [ACCT_PUSH_ENABLED_KEY];
|
||||
|
||||
// THEMES
|
||||
export const DEFAULT_THEME = "default-theme";
|
||||
@@ -154,7 +162,7 @@ export const MIN_VISIBLE_ON_SCREEN = 32;
|
||||
export const WOWUP_LOGO_FILENAME = "wowup_logo_purple.png";
|
||||
export const WOWUP_LOGO_MAC_SYSTEM_TRAY = "wowupBlackLgNopadTemplate.png";
|
||||
export const DEFAULT_FILE_MODE = 0o655;
|
||||
|
||||
export const DEFAULT_TRUSTED_DOMAINS = ["wowup.io", "dev.wowup.io", "discord.gg", "www.patreon.com", "github.com"];
|
||||
export const WOW_CLASSIC_FOLDER = "_classic_";
|
||||
export const WOW_CLASSIC_ERA_FOLDER = "_classic_era_";
|
||||
export const WOW_RETAIL_FOLDER = "_retail_";
|
||||
|
||||
9
wowup-electron/src/common/wowup.d.ts
vendored
9
wowup-electron/src/common/wowup.d.ts
vendored
@@ -19,7 +19,8 @@ declare type MainChannels =
|
||||
| "request-install-from-url"
|
||||
| "custom-protocol-received"
|
||||
| "app-update-state"
|
||||
| "window-resume";
|
||||
| "window-resume"
|
||||
| "push-notification";
|
||||
|
||||
// Events that can be sent from renderer to main
|
||||
declare type RendererChannels =
|
||||
@@ -74,7 +75,11 @@ declare type RendererChannels =
|
||||
| "app-install-update"
|
||||
| "update-app-badge"
|
||||
| "list-dir-recursive"
|
||||
| "get-directory-tree";
|
||||
| "get-directory-tree"
|
||||
| "push-init"
|
||||
| "push-register"
|
||||
| "push-unregister"
|
||||
| "push-subscribe";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
||||
@@ -114,3 +114,20 @@ export interface WowUpScanResult {
|
||||
folderName: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export type PushAction = "addon-update";
|
||||
|
||||
export interface PushNotification<T extends PushNotificationData | string> {
|
||||
action: PushAction;
|
||||
sender: string;
|
||||
message: string | T;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface PushNotificationData {}
|
||||
|
||||
export interface AddonUpdatePushNotification extends PushNotificationData {
|
||||
provider: string;
|
||||
addonName: string;
|
||||
addonId: string;
|
||||
}
|
||||
|
||||
@@ -214,6 +214,12 @@ $alliance-theme-light: mat.define-light-theme(
|
||||
}
|
||||
|
||||
// VARS
|
||||
body {
|
||||
--patreon-color: #e64049;
|
||||
--github-color: #9032ad;
|
||||
--wow-gold-color: #ffcc00;
|
||||
}
|
||||
|
||||
.default-theme {
|
||||
--background-primary: #6b69d6;
|
||||
--background-primary-2: #504fa1;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
export const AppConfig = {
|
||||
production: false,
|
||||
environment: "DEV",
|
||||
wowUpWebsiteUrl: "https://dev.wowup.io",
|
||||
wowUpApiUrl: "https://api.dev.wowup.io",
|
||||
wowUpHubUrl: "https://hub.dev.wowup.io",
|
||||
googleAnalyticsId: "UA-92563227-4",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
export const AppConfig = {
|
||||
production: true,
|
||||
environment: "PROD",
|
||||
wowUpApiUrl: "https://api.wowup.io",
|
||||
wowUpWebsiteUrl: "https://dev.wowup.io",
|
||||
wowUpApiUrl: "https://api.dev.wowup.io",
|
||||
wowUpHubUrl: "https://hub.dev.wowup.io",
|
||||
googleAnalyticsId: "UA-92563227-4",
|
||||
wowupRepositoryUrl: "https://github.com/WowUp/WowUp",
|
||||
warcraftTavernNewsFeedUrl: "https://www.warcrafttavern.com/?call_custom_simple_rss=1&csrp_post_type=wow-classic-news,tbc-classic-news,retail-news&csrp_thumbnail_size=medium",
|
||||
warcraftTavernNewsFeedUrl:
|
||||
"https://www.warcrafttavern.com/?call_custom_simple_rss=1&csrp_post_type=wow-classic-news,tbc-classic-news,retail-news&csrp_thumbnail_size=medium",
|
||||
azure: {
|
||||
applicationInsightsKey: "4a53e8d9-796c-4f80-b1a6-9a058374dd6d",
|
||||
},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
export const AppConfig = {
|
||||
production: false,
|
||||
environment: "LOCAL",
|
||||
wowUpWebsiteUrl: "https://dev.wowup.io",
|
||||
wowUpApiUrl: "https://api.dev.wowup.io",
|
||||
wowUpHubUrl: "https://hub.dev.wowup.io",
|
||||
googleAnalyticsId: "UA-92563227-4",
|
||||
wowupRepositoryUrl: "https://github.com/WowUp/WowUp",
|
||||
warcraftTavernNewsFeedUrl: "https://www.warcrafttavern.com/?call_custom_simple_rss=1&csrp_post_type=wow-classic-news,tbc-classic-news,retail-news&csrp_thumbnail_size=medium",
|
||||
warcraftTavernNewsFeedUrl:
|
||||
"https://www.warcrafttavern.com/?call_custom_simple_rss=1&csrp_post_type=wow-classic-news,tbc-classic-news,retail-news&csrp_thumbnail_size=medium",
|
||||
azure: {
|
||||
applicationInsightsKey: "4a53e8d9-796c-4f80-b1a6-9a058374dd6d",
|
||||
},
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
export const AppConfig = {
|
||||
production: false,
|
||||
environment: "DEV",
|
||||
wowUpWebsiteUrl: "https://dev.wowup.io",
|
||||
wowUpApiUrl: "https://api.dev.wowup.io",
|
||||
wowUpHubUrl: "https://hub.dev.wowup.io",
|
||||
googleAnalyticsId: "UA-92563227-4",
|
||||
warcraftTavernNewsFeedUrl: "https://www.warcrafttavern.com/?call_custom_simple_rss=1&csrp_post_type=wow-classic-news,tbc-classic-news,retail-news&csrp_thumbnail_size=medium",
|
||||
warcraftTavernNewsFeedUrl:
|
||||
"https://www.warcrafttavern.com/?call_custom_simple_rss=1&csrp_post_type=wow-classic-news,tbc-classic-news,retail-news&csrp_thumbnail_size=medium",
|
||||
newsRefreshIntervalMs: 3600000, // 1 hour
|
||||
featuredAddonsCacheTimeSec: 30, // 30 sec
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user