Large UI changes

Show Permission dialog on startup
This commit is contained in:
jliddev
2021-12-28 13:00:13 -06:00
parent f8f0808949
commit b78722acbc
30 changed files with 8918 additions and 1613 deletions

View File

@@ -47,4 +47,4 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }}
run: cd ./wowup-electron && npm i && npm run electron:publish
run: cd ./wowup-electron && npm ci && npm run electron:publish

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ node_modules/
bin/
obj/
.vs/
Packages/
Packages/
wowup-electron/package_workspace/

View File

@@ -8,44 +8,10 @@
"generateUpdatesFilesForAllChannels": true,
"publish": ["github"],
"nodeGypRebuild": true,
"files": [
"**/*",
"**\\*",
"build/Release/*.node",
"src/common/**/*.js",
"!**/*.ts",
"!**/*.scss",
"!**/*.map",
"!*.code-workspace",
"!LICENSE.md",
"!package.json",
"!package-lock.json",
"!src/app/*",
"!src/assets/*",
"!src/environments/*",
"!src/*.html",
"!src/*.js",
"!src/*.json",
"!e2e/",
"!hooks/",
"!angular.json",
"!_config.yml",
"!karma.conf.js",
"!tsconfig.json",
"!tslint.json",
"!**/.vscode/*",
"!node_modules/@angular",
"!node_modules/win-ca/pem",
"!native/",
"!*.npmrc",
"!*.eslintrc.json",
"!binding.gyp",
"!angular.webpack.js",
"!tsconfig*.json"
],
"files": ["dist/**/*.*", "assets/**/*.*", "app/**/*.js", "src/common/**/*.js", "build/Release/*.node"],
"win": {
"icon": "electron-build/icon.ico",
"target": ["nsis"],
"target": ["nsis", "portable"],
"forceCodeSigning": false,
"publisherName": "WowUp LLC"
},

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "wowup",
"productName": "WowUp",
"version": "2.6.0-beta.5",
"version": "2.6.0-beta.7",
"description": "World of Warcraft addon updater",
"homepage": "https://wowup.io",
"author": {
@@ -21,6 +21,7 @@
},
"scripts": {
"postinstall": "electron-builder install-app-deps",
"install:prod": "npm ci --only=prod --no-optional",
"ng": "ng",
"start": "npm-run-all -p electron:serve ng:serve",
"build": "npm run electron:serve-tsc && ng build --base-href ./",
@@ -33,10 +34,10 @@
"electron:local": "npm run build:prod && npx electron .",
"electron:build": "npm run build:prod && electron-builder build",
"electron:build:local": "npm run build:prod && electron-builder build -c electron-builder-local.json",
"electron:publish": "npm run lint && npm run build:prod && electron-builder build --publish always",
"electron:publish": "npx electron-builder build --publish always",
"electron:publish:linux": "docker-compose -f linux-compose.yml up",
"electron:publish:never": "npm run electron:build && electron-builder --publish never",
"electron:publish:never:local": "npm run build:prod && electron-builder -c electron-builder-local.json --publish never ",
"electron:publish:never:local": "npx electron-builder -c electron-builder-local.json --publish never ",
"test": "ng test --watch=false",
"test:watch": "ng test",
"test:locales": "ng test --watch=false --include='src/locales.spec.ts'",
@@ -48,7 +49,9 @@
"check-i18n": "npm run i18n -- --check",
"pretty": "npx prettier --write . && ng lint --fix",
"find-broken-test": "node ./test-fixer.js --find-break",
"restore-tests": "node ./test-fixer.js --repair"
"restore-tests": "node ./test-fixer.js --repair",
"package:prod": "npx gulp package",
"package:local": "npx gulp packageLocal"
},
"devDependencies": {
"@angular-builders/custom-webpack": "13.0.0",
@@ -83,6 +86,7 @@
"conventional-changelog-cli": "2.1.1",
"core-js": "3.17.2",
"cross-env": "7.0.3",
"del": "6.0.0",
"dotenv": "10.0.0",
"electron": "16.0.5",
"electron-builder": "22.14.5",
@@ -94,6 +98,7 @@
"eslint-plugin-jsdoc": "37.0.3",
"eslint-plugin-prefer-arrow": "1.2.3",
"flat": "5.0.2",
"gulp": "4.0.2",
"i18next-json-sync": "2.3.1",
"ignore": "5.1.9",
"jasmine-core": "3.9.0",

View File

@@ -1,32 +1,20 @@
<div *ngIf="quitEnabled === false" class="app" [ngClass]="[wowUpService.currentTheme, electronService.platform]">
<app-titlebar class="bg-primary"></app-titlebar>
<app-horizontal-tabs></app-horizontal-tabs>
<div class="content">
<div
*ngIf="(showPreLoad$ | async) === false && quitEnabled === false"
class="app"
[ngClass]="[wowUpService.currentTheme, electronService.platform]"
>
<app-titlebar class="gtitle"></app-titlebar>
<!-- <app-horizontal-tabs></app-horizontal-tabs> -->
<app-vertical-tabs class="gtabs"></app-vertical-tabs>
<div class="content bg-secondary-3 gmain">
<!-- <div class="tabs">
<app-vertical-tabs></app-vertical-tabs>
</div> -->
<div class="main">
<router-outlet></router-outlet>
</div>
<div *ngIf="sessionService.adSpace$ | async" class="ad-space text-1 bg-secondary-3">
<div class="ad-details p-3">
<div class="center-col">
<div>
<h2>Why am I seeing this ad?</h2>
<p>
In order to use wago.io as an addon provider and support their authors for their hard work on your
favorite addons we are required to show this advertisement.
</p>
<p>If you do not want to see this ad, you can always disable wago.io as a provider in the options tab.</p>
</div>
</div>
</div>
<div class="ad">
<app-webview *ngFor="let params of adPageParams" [options]="params"></app-webview>
</div>
</div>
</div>
<app-footer></app-footer>
<app-footer class="gfooter"></app-footer>
</div>
<app-animated-logo *ngIf="showPreLoad === true || quitEnabled === true"></app-animated-logo>
<app-animated-logo *ngIf="(showPreLoad$ | async) === true || quitEnabled === true"></app-animated-logo>

View File

@@ -1,8 +1,29 @@
.app {
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"title title"
"tabs main"
"tabs footer";
.gtitle {
grid-area: title;
}
.gtabs {
grid-area: tabs;
}
.gmain {
grid-area: main;
}
.gfooter {
grid-area: footer;
}
.content {
flex-grow: 1;
@@ -10,6 +31,8 @@
display: flex;
flex-direction: row;
border-radius: 4px;
.tabs {
flex-shrink: 0;
}
@@ -19,28 +42,5 @@
.extra {
flex-shrink: 0;
}
.ad-space {
width: 300px;
display: flex;
flex-direction: column;
.ad-details {
flex-grow: 1;
.center-col {
display: flex;
flex-direction: column;
justify-content: flex-end;
height: 100%;
}
}
.ad {
flex-shrink: 0;
width: 300px;
height: 250px;
}
}
}
}

View File

@@ -1,5 +1,5 @@
import * as _ from "lodash";
import { from, of } from "rxjs";
import { BehaviorSubject, from, of } from "rxjs";
import { catchError, delay, filter, first, map, switchMap } from "rxjs/operators";
import { OverlayContainer } from "@angular/cdk/overlay";
@@ -55,6 +55,10 @@ import { WowUpService } from "./services/wowup/wowup.service";
import { ZoomService } from "./services/zoom/zoom.service";
import { ZoomDirection } from "./utils/zoom.utils";
import { AddonProviderFactory } from "./services/addons/addon.provider.factory";
import {
ConsentDialogComponent,
ConsentDialogResult,
} from "./components/common/consent-dialog/consent-dialog.component";
@Component({
selector: "app-root",
@@ -83,8 +87,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
}
public quitEnabled?: boolean;
public showPreLoad = true;
public adPageParams: AdPageOptions[] = [];
public showPreLoad$ = new BehaviorSubject<boolean>(true);
public constructor(
private _addonService: AddonService,
@@ -138,17 +141,6 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
});
}
});
this.sessionService.adSpace$.subscribe((enabled) => {
if (enabled) {
const providers = this._addonProviderService.getAdRequiredProviders();
this.adPageParams = providers
.map((provider) => provider.getAdPageParams())
.filter((param) => param !== undefined);
} else {
this.adPageParams = [];
}
});
}
public ngOnInit(): void {
@@ -204,7 +196,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
.pipe(
first(),
map((appOptions) => {
this.showPreLoad = false;
this.showPreLoad$.next(this.shouldShowConsentDialog());
this.quitEnabled = appOptions.quit;
this._cdRef.detectChanges();
}),
@@ -233,8 +225,8 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
first(),
switchMap(() => from(this.createSystemTray())),
switchMap(() => {
if (this._analyticsService.shouldPromptTelemetry) {
return of(this.openDialog());
if (this.shouldShowConsentDialog()) {
return of(this.openConsentDialog());
} else {
return from(this._analyticsService.trackStartup());
}
@@ -247,6 +239,10 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
.subscribe();
}
private shouldShowConsentDialog() {
return this._analyticsService.shouldPromptTelemetry || this._addonProviderService.shouldShowConsentDialog();
}
public ngOnDestroy(): void {
this.electronService.off(IPC_MENU_ZOOM_IN_CHANNEL, this.onMenuZoomIn);
this.electronService.off(IPC_MENU_ZOOM_OUT_CHANNEL, this.onMenuZoomOut);
@@ -269,23 +265,29 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
this.openInstallFromUrlDialog(path);
};
public openDialog(): void {
const dialogRef = this._dialog.open(TelemetryDialogComponent, {
public openConsentDialog(): void {
const dialogRef = this._dialog.open(ConsentDialogComponent, {
disableClose: true,
});
dialogRef
.afterClosed()
.pipe(
switchMap((result) => {
this._analyticsService.telemetryEnabled = result;
if (result) {
switchMap((result: ConsentDialogResult) => {
this._addonProviderService.setProviderEnabled("Wago", result.wagoProvider);
this._addonProviderService.updateWagoConsent();
this._analyticsService.telemetryEnabled = result.telemetry;
if (result.telemetry) {
return from(this._analyticsService.trackStartup());
}
return of(undefined);
})
)
.subscribe();
.subscribe(() => {
this.showPreLoad$.next(false);
});
}
private openInstallFromUrlDialog(path?: string) {

View File

@@ -29,6 +29,7 @@ import { IconService } from "./services/icons/icon.service";
import { HorizontalTabsComponent } from "./components/common/horizontal-tabs/horizontal-tabs.component";
import { CommonUiModule } from "./modules/common-ui.module";
import { FooterComponent } from "./components/common/footer/footer.component";
import { VerticalTabsComponent } from "./components/common/vertical-tabs/vertical-tabs.component";
// AoT requires an exported function for factories
export function httpLoaderFactory(http: HttpClient): TranslateHttpLoader {
@@ -42,7 +43,7 @@ export function initializeApp(wowupService: WowUpService) {
}
@NgModule({
declarations: [AppComponent, TitlebarComponent, FooterComponent, HorizontalTabsComponent],
declarations: [AppComponent, TitlebarComponent, FooterComponent, HorizontalTabsComponent, VerticalTabsComponent],
imports: [
BrowserModule,
FormsModule,

View File

@@ -0,0 +1,26 @@
<h1 mat-dialog-title>{{ "WowUp Permissions Setup" | translate }}</h1>
<form [formGroup]="consentOptions" (ngSubmit)="onSubmit($event)">
<div mat-dialog-content>
<p>{{ "Before we get started we need to setup a few permissions for the app." | translate }}</p>
<div class="mb-3">
<div>
<mat-slide-toggle formControlName="telemetry">Allow Telemetry</mat-slide-toggle>
</div>
<mat-hint>{{ "DIALOGS.TELEMETRY.DESCRIPTION" | translate }}</mat-hint>
</div>
<div>
<div>
<mat-slide-toggle formControlName="wagoProvider">Enable Wago.io Provider</mat-slide-toggle>
</div>
<mat-hint>{{
"Enabled the Wago.io addon provider, this will display an ad required to use their service. The ads directly benefit the authors of your favorite addons!"
| translate
}}</mat-hint>
</div>
</div>
<div mat-dialog-actions align="end">
<button mat-raised-button color="primary" type="submit" cdkFocusInitial>
{{ "Confirm" | translate }}
</button>
</div>
</form>

View File

@@ -0,0 +1,36 @@
import { Component } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { MatDialogRef } from "@angular/material/dialog";
export interface ConsentDialogResult {
telemetry: boolean;
wagoProvider: boolean;
}
@Component({
selector: "app-consent-dialog",
templateUrl: "./consent-dialog.component.html",
styleUrls: ["./consent-dialog.component.scss"],
})
export class ConsentDialogComponent {
public consentOptions: FormGroup;
public constructor(public dialogRef: MatDialogRef<ConsentDialogComponent>) {
this.consentOptions = new FormGroup({
telemetry: new FormControl(true),
wagoProvider: new FormControl(false),
});
}
public onNoClick(): void {
this.dialogRef.close();
}
public onSubmit(evt: any): void {
evt.preventDefault();
console.log(this.consentOptions.value);
this.dialogRef.close(this.consentOptions.value);
}
}

View File

@@ -1,4 +1,4 @@
<footer class="bg-secondary-4 text-2">
<footer class="bg-secondary-3 text-2">
<a appExternalLink class="btn" href="https://www.patreon.com/jliddev"
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.PATREON_SUPPORT' | translate }}">
<img src="assets/Digital-Patreon-Wordmark_FieryCoral.png">

View File

@@ -1,39 +1,57 @@
<div class="titlebar text-1" [ngClass]="{
<div
class="titlebar bg-secondary-3 text-1"
[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="titlebar-drag-region" (dblclick)="onDblClick()"></div>
<div class="title-container">
<div>{{ (isFullscreen ? 'APP.WINDOW_TITLE_FULLSCREEN' : 'APP.WINDOW_TITLE') | translate }}</div>
<div>{{ (isFullscreen ? "APP.WINDOW_TITLE_FULLSCREEN" : "APP.WINDOW_TITLE") | translate }}</div>
</div>
<div *ngIf="electronService.isWin || electronService.isLinux" class="window-control-container row align-items-center">
<!-- <div class="bnet-control">
<a class="bnet-btn bnet-btn-sm" appExternalLink href="https://dev.wowup.io">Login with Battle.net</a>
</div> -->
<div *ngIf="isFullscreen === true" class="window-control hover-primary-2 text-2"
(click)="electronService.leaveFullScreen()">
<mat-icon class="icon" svgIcon="fas:compress-arrows-alt"
[matTooltip]="'APP.CLOSE_FULLSCREEN_BUTTON_TOOLTIP' | translate">
<div
*ngIf="isFullscreen === true"
class="window-control hover-primary-2 text-2"
(click)="electronService.leaveFullScreen()"
>
<mat-icon
class="icon"
svgIcon="fas:compress-arrows-alt"
[matTooltip]="'APP.CLOSE_FULLSCREEN_BUTTON_TOOLTIP' | translate"
>
</mat-icon>
</div>
<div *ngIf="isFullscreen === false" class="window-control hover-primary-2"
(click)="electronService.minimizeWindow()">
<div
*ngIf="isFullscreen === false"
class="window-control hover-primary-2"
(click)="electronService.minimizeWindow()"
>
<img src="assets/chrome-minimize.svg" />
</div>
<div *ngIf="isFullscreen === false && isMaximized === false" class="window-control hover-primary-2"
(click)="electronService.maximizeWindow()">
<div
*ngIf="isFullscreen === false && isMaximized === false"
class="window-control hover-primary-2"
(click)="electronService.maximizeWindow()"
>
<img src="assets/chrome-maximize.svg" />
</div>
<div *ngIf="isFullscreen === false && isMaximized === true" class="window-control hover-primary-2"
(click)="electronService.unmaximizeWindow()">
<div
*ngIf="isFullscreen === false && isMaximized === true"
class="window-control hover-primary-2"
(click)="electronService.unmaximizeWindow()"
>
<img src="assets/chrome-restore.svg" />
</div>
<div *ngIf="isFullscreen === false" class="window-control hover-primary-2" (click)="onClickClose()">
<img src="assets/chrome-close.svg" />
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,84 @@
<div
class="tab-strip bg-secondary-3 text-1"
[ngClass]="{
mac: electronService.isMac,
windows: electronService.isWin,
linux: electronService.isLinux
}"
>
<div class="theme-logo-glow"></div>
<div class="theme-logo">
<div class="logo-img"></div>
</div>
<div
*ngFor="let tab of tabsTop"
class="tab"
[ngClass]="{ selected: tab.isSelected$ | async, disabled: tab.isDisabled$ | async }"
(click)="tab.onClick(tab)"
>
<mat-icon
class="tab-icon tab-icon-inactive"
[ngClass]="{ 'tab-icon-active': tab.isSelected$ | async, 'tab-icon-disabled': tab.isDisabled$ | async }"
[svgIcon]="tab.icon"
>
</mat-icon>
<div class="tab-title" *ngIf="tab.titleKey">{{ tab.titleKey | translate }}</div>
<!-- <mat-icon *ngIf="tab.badge === true" class="tab-icon-inactive z-badge clear-badge" matBadge="1" matBadgeColor="accent"
[ngClass]="{'tab-icon-active': tab.isSelected$ | async }" [svgIcon]="tab.icon">
</mat-icon> -->
</div>
<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>
</a>
<!-- ACCOUNT TAB -->
<div
*ngIf="FEATURE_ACCOUNTS_ENABLED"
class="tab hover-bg-secondary-2"
[ngClass]="{ selected: isAccountSelected$ | async }"
(click)="onClickTab(TAB_INDEX_ACCOUNT)"
>
<mat-icon
class="tab-icon tab-icon-inactive"
svgIcon="fas:user-circle"
[ngClass]="{ 'tab-icon-active': isAccountSelected$ | async }"
>
</mat-icon>
<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"> -->
<mat-icon
class="tab-icon tab-icon-inactive"
[svgIcon]="tab.icon"
[ngClass]="{ 'tab-icon-active': tab.isSelected$ | async, 'tab-icon-disabled': tab.isDisabled$ | async }"
>
</mat-icon>
<!-- </div> -->
<div class="tab-title" *ngIf="tab.titleKey">{{ tab.titleKey | translate }}</div>
</div>
<div class="spacer"></div>
<div *ngIf="sessionService.adSpace$ | async" class="ad-space text-1 bg-secondary-3">
<button mat-button style="justify-self: center" (click)="onClickAdExplainer()">Why am I seeing this ad?</button>
<div class="ad">
<app-webview *ngFor="let params of adPageParams$ | async" [options]="params"></app-webview>
</div>
</div>
</div>

View File

@@ -0,0 +1,153 @@
.tab-strip {
box-sizing: border-box;
display: flex;
flex-direction: column;
max-width: 300px;
height: 100%;
padding-top: calc(1em + 50px);
&.mac {
padding-right: 87px;
padding-left: 5px;
}
.patreon-link {
padding: 0 0.25em;
.patron-img {
height: 25px;
}
}
.tab {
min-width: 40px;
height: 40px;
padding: 0 1em;
display: flex;
align-items: center;
box-sizing: border-box;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
text-decoration: none;
border-left: 0.5em solid transparent;
.icon-active {
svg {
fill: var(--tab-text) !important;
}
}
.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;
transition-property: color, max-width;
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;
}
&:hover {
cursor: pointer;
background-color: var(--background-primary-2);
}
&.selected {
border-left: 0.5em solid var(--background-primary);
// background-color: var(--background-primary-3);
.tab-icon {
margin-right: 0.5em;
}
.tab-title {
color: var(--tab-text);
max-width: 200px;
// margin-left: 0.5em;
}
}
&.disabled {
pointer-events: none;
}
}
.spacer {
flex-grow: 1;
}
}
.theme-logo-glow {
z-index: 1;
position: fixed;
border-radius: 50%;
top: -60px;
left: -60px;
height: 150px;
width: 150px;
background: var(--background-primary);
background: radial-gradient(circle, var(--background-primary) 0%, rgba(0, 0, 0, 0) 70%);
}
.theme-logo {
z-index: 1;
position: fixed;
top: 1em;
left: 0.5em;
height: 72px;
width: 72px;
overflow: hidden;
.logo-img {
width: 100%;
height: 100%;
background-image: var(--title-logo);
background-repeat: no-repeat;
background-size: contain;
background-position: center;
}
img {
position: relative;
left: 0;
height: 150%;
width: auto;
}
}
.ad-space {
width: 300px;
display: flex;
flex-direction: column;
.ad-details {
flex-grow: 1;
.center-col {
display: flex;
flex-direction: column;
justify-content: flex-end;
height: 100%;
}
}
.ad {
flex-shrink: 0;
width: 300px;
height: 250px;
}
}

View File

@@ -0,0 +1,160 @@
import { BehaviorSubject, Observable, of, Subject } from "rxjs";
import { map, takeUntil } from "rxjs/operators";
import { Component, OnDestroy, OnInit } from "@angular/core";
import {
FEATURE_ACCOUNTS_ENABLED,
TAB_INDEX_ABOUT,
TAB_INDEX_GET_ADDONS,
TAB_INDEX_MY_ADDONS,
TAB_INDEX_NEWS,
TAB_INDEX_SETTINGS,
} from "../../../../common/constants";
import { AppConfig } from "../../../../environments/environment";
import { ElectronService } from "../../../services";
import { SessionService } from "../../../services/session/session.service";
import { WarcraftInstallationService } from "../../../services/warcraft/warcraft-installation.service";
import { AdPageOptions } from "../../../../common/wowup/models";
import { AddonProviderFactory } from "../../../services/addons/addon.provider.factory";
import { MatDialog } from "@angular/material/dialog";
import { AlertDialogComponent } from "../alert-dialog/alert-dialog.component";
import { TranslateService } from "@ngx-translate/core";
interface Tab {
titleKey?: string;
tooltipKey: string;
icon: string;
badge?: boolean;
isSelected$: Observable<boolean>;
isDisabled$: Observable<boolean>;
onClick: (tab: Tab) => void;
}
@Component({
selector: "app-vertical-tabs",
templateUrl: "./vertical-tabs.component.html",
styleUrls: ["./vertical-tabs.component.scss"],
})
export class VerticalTabsComponent implements OnInit, OnDestroy {
private readonly destroy$: Subject<boolean> = new Subject<boolean>();
public wowUpWebsiteUrl = AppConfig.wowUpWebsiteUrl;
public TAB_INDEX_ACCOUNT = TAB_INDEX_ABOUT;
public FEATURE_ACCOUNTS_ENABLED = FEATURE_ACCOUNTS_ENABLED;
public adPageParams$ = new BehaviorSubject<AdPageOptions[]>([]);
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",
icon: "fas:dice-d6",
isSelected$: this.sessionService.selectedHomeTab$.pipe(map((result) => result === TAB_INDEX_MY_ADDONS)),
isDisabled$: this._warcraftInstallationService.wowInstallations$.pipe(
map((installations) => installations.length === 0)
),
onClick: (): void => {
this.sessionService.selectedHomeTab = TAB_INDEX_MY_ADDONS;
},
};
private getAddonsTab: Tab = {
titleKey: "PAGES.HOME.GET_ADDONS_TAB_TITLE",
tooltipKey: "PAGES.HOME.GET_ADDONS_TAB_TITLE",
icon: "fas:search",
isSelected$: this.sessionService.selectedHomeTab$.pipe(map((result) => result === TAB_INDEX_GET_ADDONS)),
isDisabled$: this._warcraftInstallationService.wowInstallations$.pipe(
map((installations) => installations.length === 0)
),
onClick: (): void => {
this.sessionService.selectedHomeTab = TAB_INDEX_GET_ADDONS;
},
};
private aboutTab: Tab = {
titleKey: "PAGES.HOME.ACCOUNT_TAB_TITLE",
tooltipKey: "PAGES.HOME.ACCOUNT_TAB_TITLE",
icon: "fas:user-circle",
isSelected$: this.sessionService.selectedHomeTab$.pipe(map((result) => result === TAB_INDEX_ABOUT)),
isDisabled$: of(false),
onClick: (): void => {
this.sessionService.selectedHomeTab = TAB_INDEX_ABOUT;
},
};
private newsTab: Tab = {
titleKey: "PAGES.HOME.NEWS_TAB_TITLE",
tooltipKey: "PAGES.HOME.NEWS_TAB_TITLE",
icon: "fas:newspaper",
badge: true,
isSelected$: this.sessionService.selectedHomeTab$.pipe(map((result) => result === TAB_INDEX_NEWS)),
isDisabled$: of(false),
onClick: (): void => {
this.sessionService.selectedHomeTab = TAB_INDEX_NEWS;
},
};
private settingsTab: Tab = {
titleKey: "PAGES.HOME.OPTIONS_TAB_TITLE",
tooltipKey: "PAGES.HOME.OPTIONS_TAB_TITLE",
icon: "fas:cog",
isSelected$: this.sessionService.selectedHomeTab$.pipe(map((result) => result === TAB_INDEX_SETTINGS)),
isDisabled$: of(false),
onClick: (): void => {
this.sessionService.selectedHomeTab = TAB_INDEX_SETTINGS;
},
};
public tabsTop: Tab[] = [this.myAddonsTab, this.getAddonsTab, this.newsTab];
public tabsBottom: Tab[] = [this.settingsTab];
public constructor(
public electronService: ElectronService,
public sessionService: SessionService,
private _dialog: MatDialog,
private _translateService: TranslateService,
private _addonProviderService: AddonProviderFactory,
private _warcraftInstallationService: WarcraftInstallationService
) {
this.sessionService.adSpace$.pipe(takeUntil(this.destroy$)).subscribe((enabled) => {
if (enabled) {
const providers = this._addonProviderService.getAdRequiredProviders();
const providerParams = providers
.map((provider) => provider.getAdPageParams())
.filter((param) => param !== undefined);
console.debug("providerParams", providerParams);
this.adPageParams$.next(providerParams);
} else {
this.adPageParams$.next([]);
}
});
}
public ngOnInit(): void {}
public ngOnDestroy(): void {
this.destroy$.next(true);
this.destroy$.unsubscribe();
}
public onClickTab(tabIndex: number): void {
this.sessionService.selectedHomeTab = tabIndex;
}
public onClickAdExplainer(): void {
const dialogRef = this._dialog.open(AlertDialogComponent, {
minWidth: 250,
disableClose: true,
data: {
title: this._translateService.instant("Why am I seeing this ad?"),
message: this._translateService.instant(
`In order to use wago.io as an addon provider and support their authors for their hard work on your favorite addons we are required to show this advertisement.\n\nIf you do not want to see this ad, you can always disable wago.io as a provider in the options tab.`
),
},
});
}
}

View File

@@ -12,7 +12,7 @@
<small class="text-2">{{ "PAGES.OPTIONS.ADDON.ENABLED_PROVIDERS.DESCRIPTION" | translate }}</small>
<div class="bg-secondary-3">
<mat-selection-list #shoes (selectionChange)="onProviderStateSelectionChange($event)">
<mat-list-option *ngFor="let state of addonProviderStates" [value]="state.providerName"
<mat-list-option *ngFor="let state of addonProviderStates$ | async" [value]="state.providerName"
[selected]="state.enabled">
{{state.providerName}}
</mat-list-option>

View File

@@ -1,11 +1,10 @@
import { Component, OnInit } from "@angular/core";
import { Component, NgZone, OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";
import { filter } from "lodash";
import { WowUpService } from "../../../services/wowup/wowup.service";
import { AddonService } from "../../../services/addons/addon.service";
import { AddonProviderState } from "../../../models/wowup/addon-provider-state";
import { MatSelectionListChange } from "@angular/material/list";
import { AddonProviderFactory } from "../../../services/addons/addon.provider.factory";
import { AddonProviderType } from "../../../addon-providers/addon-provider";
import { BehaviorSubject } from "rxjs";
@Component({
selector: "app-options-addon-section",
@@ -13,41 +12,28 @@ import { AddonProviderFactory } from "../../../services/addons/addon.provider.fa
styleUrls: ["./options-addon-section.component.scss"],
})
export class OptionsAddonSectionComponent implements OnInit {
public enabledAddonProviders = new FormControl();
public addonProviderStates: AddonProviderState[] = [];
public addonProviderStates$ = new BehaviorSubject<AddonProviderState[]>([]);
public constructor(
private _addonService: AddonService,
private _addonProviderService: AddonProviderFactory,
private _wowupService: WowUpService
) {}
public constructor(private _addonProviderService: AddonProviderFactory) {
this._addonProviderService.addonProviderChange$.subscribe(() => {
this.loadProviderStates();
});
}
public ngOnInit(): void {
this.addonProviderStates = filter(
this._addonProviderService.getAddonProviderStates(),
(provider) => provider.canEdit
);
this.enabledAddonProviders.setValue(this.getEnabledProviderNames());
this.loadProviderStates();
}
public onProviderStateSelectionChange(event: MatSelectionListChange): void {
event.options.forEach((option) => {
this._wowupService.setAddonProviderState({
providerName: option.value,
enabled: option.selected,
canEdit: true,
});
const providerName: string = option.value;
this._addonService.setProviderEnabled(providerName, option.selected);
const providerName: AddonProviderType = option.value;
this._addonProviderService.setProviderEnabled(providerName, option.selected);
});
}
private getEnabledProviders() {
return this.addonProviderStates.filter((state) => state.enabled);
}
private getEnabledProviderNames() {
return this.getEnabledProviders().map((provider) => provider.providerName);
private loadProviderStates() {
this.addonProviderStates$.next(
this._addonProviderService.getAddonProviderStates().filter((provider) => provider.canEdit)
);
}
}

View File

@@ -1,6 +1,6 @@
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { TranslateModule } from "@ngx-translate/core";
import { AlertDialogComponent } from "../components/common/alert-dialog/alert-dialog.component";
@@ -9,6 +9,7 @@ import { CellWrapTextComponent } from "../components/common/cell-wrap-text/cell-
import { CenteredSnackbarComponent } from "../components/common/centered-snackbar/centered-snackbar.component";
import { ClientSelectorComponent } from "../components/common/client-selector/client-selector.component";
import { ConfirmDialogComponent } from "../components/common/confirm-dialog/confirm-dialog.component";
import { ConsentDialogComponent } from "../components/common/consent-dialog/consent-dialog.component";
import { ExternalUrlConfirmationDialogComponent } from "../components/common/external-url-confirmation-dialog/external-url-confirmation-dialog.component";
import { PatchNotesDialogComponent } from "../components/common/patch-notes-dialog/patch-notes-dialog.component";
import { ProgressButtonComponent } from "../components/common/progress-button/progress-button.component";
@@ -28,11 +29,12 @@ import { PipesModule } from "./pipes.module";
PatchNotesDialogComponent,
ProgressButtonComponent,
TelemetryDialogComponent,
ConsentDialogComponent,
CellWrapTextComponent,
CenteredSnackbarComponent,
ClientSelectorComponent,
],
imports: [CommonModule, FormsModule, TranslateModule, MatModule, PipesModule],
imports: [CommonModule, FormsModule, TranslateModule, MatModule, PipesModule, ReactiveFormsModule],
exports: [
ProgressSpinnerComponent,
ProgressButtonComponent,
@@ -43,6 +45,7 @@ import { PipesModule } from "./pipes.module";
PatchNotesDialogComponent,
ProgressButtonComponent,
TelemetryDialogComponent,
ConsentDialogComponent,
CellWrapTextComponent,
CenteredSnackbarComponent,
ClientSelectorComponent,

View File

@@ -5,13 +5,15 @@
.wu-ag-table {
width: 100%;
height: calc(100% - 72px);
height: calc(100% - 86px);
}
.control-container {
display: flex;
flex-direction: row;
padding: 0 1em 0 1em;
margin: 1em 1em 0 1em;
border-radius: 4px;
.select-container {
padding-top: 1em;

View File

@@ -6,13 +6,14 @@
<div *ngIf="appReady === true" class="page-container bg-secondary-2" [ngClass]="{ linux: electronService.isLinux }">
<div class="tabs header-less-tabs">
<mat-tab-group
class="h-100 grow-tab-content"
mat-align-tabs="center"
animationDuration="0ms"
[backgroundColor]="'primary'"
[disablePagination]="true"
[selectedIndex]="sessionService.selectedHomeTab$ | async"
>
<mat-tab [disabled]="hasWowClient !== true" [label]="'PAGES.HOME.MY_ADDONS_TAB_TITLE' | translate">
<mat-tab class='h-100' [disabled]="hasWowClient !== true" [label]="'PAGES.HOME.MY_ADDONS_TAB_TITLE' | translate">
<app-my-addons [tabIndex]="TAB_INDEX_MY_ADDONS"></app-my-addons>
</mat-tab>
<mat-tab [disabled]="hasWowClient !== true" [label]="'PAGES.HOME.GET_ADDONS_TAB_TITLE' | translate">

View File

@@ -12,13 +12,14 @@
}
}
.page-container {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
// display: flex;
// flex-direction: column;
}
.tabs {
flex-grow: 1;
// flex-grow: 1;
height: 100%;
overflow: hidden;
}
.preload-container {

View File

@@ -1,5 +1,5 @@
<div
class="tab-container d-flex flex-col"
class="tab-container"
[ngClass]="{
mac: electronService.isMac,
windows: electronService.isWin,

View File

@@ -1,12 +1,14 @@
.wu-ag-table {
width: 100%;
height: calc(100% - 72px);
height: calc(100% - 86px);
}
.control-container {
display: flex;
flex-direction: row;
padding: 0 1em 0 1em;
margin: 1em 1em 0 1em;
border-radius: 4px;
.select-container {
padding-top: 1em;
@@ -79,7 +81,7 @@
}
.table-container {
height: calc(100% - 72px);
height: 100%;
overflow: auto;
overflow-x: hidden;

View File

@@ -1,6 +1,6 @@
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { AddonProvider } from "../../addon-providers/addon-provider";
import { AddonProvider, AddonProviderType } from "../../addon-providers/addon-provider";
import { CurseAddonProvider } from "../../addon-providers/curse-addon-provider";
import { GitHubAddonProvider } from "../../addon-providers/github-addon-provider";
import { TukUiAddonProvider } from "../../addon-providers/tukui-addon-provider";
@@ -19,14 +19,20 @@ import { WarcraftService } from "../warcraft/warcraft.service";
import { WowUpApiService } from "../wowup-api/wowup-api.service";
import { WagoAddonProvider } from "../../addon-providers/wago-addon-provider";
import { AddonProviderState } from "../../models/wowup/addon-provider-state";
import { ADDON_PROVIDER_UNKNOWN } from "../../../common/constants";
import { ADDON_PROVIDER_UNKNOWN, WAGO_PROMPT_KEY } from "../../../common/constants";
import { Subject } from "rxjs";
import { PreferenceStorageService } from "../storage/preference-storage.service";
@Injectable({
providedIn: "root",
})
export class AddonProviderFactory {
private readonly _addonProviderChangeSrc = new Subject<AddonProvider>();
private _providerMap: Map<string, AddonProvider> = new Map();
public readonly addonProviderChange$ = this._addonProviderChangeSrc.asObservable();
public constructor(
private _cachingService: CachingService,
private _electronService: ElectronService,
@@ -36,11 +42,40 @@ export class AddonProviderFactory {
private _fileService: FileService,
private _tocService: TocService,
private _warcraftService: WarcraftService,
private _wowupApiService: WowUpApiService
private _wowupApiService: WowUpApiService,
private _preferenceStorageService: PreferenceStorageService
) {
this.loadProviders();
}
public shouldShowConsentDialog(): boolean {
return this._preferenceStorageService.get(WAGO_PROMPT_KEY) === undefined;
}
public updateWagoConsent(): void {
return this._preferenceStorageService.set(WAGO_PROMPT_KEY, true);
}
public setProviderEnabled(type: AddonProviderType, enabled: boolean) {
if (!this._providerMap.has(type)) {
throw new Error("cannot set provider state, not found");
}
const provider = this._providerMap.get(type);
if (!provider.allowEdit) {
throw new Error(`this provider is not editable: ${type}`);
}
this._wowupService.setAddonProviderState({
providerName: type,
enabled: enabled,
canEdit: true,
});
provider.enabled = enabled;
this._addonProviderChangeSrc.next(provider);
}
public createWagoAddonProvider(): WagoAddonProvider {
return new WagoAddonProvider(
this._electronService,

View File

@@ -70,7 +70,7 @@ export class SessionService {
this.onWowInstallationsChange(installations)
);
this._addonService.addonProviderChange$.subscribe((provider) => {
this._addonProviderService.addonProviderChange$.subscribe((provider) => {
this.updateAdSpace();
});
@@ -79,6 +79,7 @@ export class SessionService {
private updateAdSpace() {
const allProviders = this._addonProviderService.getEnabledAddonProviders();
console.debug("updateAdSpace", allProviders);
this._adSpaceSrc.next(allProviders.findIndex((p) => p.adRequired) !== -1);
}

View File

@@ -20,6 +20,7 @@ const CHANGELOGS: ChangeLog[] = [
html: `
<h4 style="margin-top: 1em;">New Features</h4>
<ul>
<li>Added the new <a appExternalLink href="https://addons.wago.io/">Wago.io</a> addon provider</li>
<li>Added the ability to switch to Beta build release channel from the app</li>
</ul>
<h4 style="margin-top: 1em;">Changes</h4>

View File

@@ -139,6 +139,7 @@ export const CLASSIC_LOCATION_KEY = "wow_classic_location"; // TODO remove, depr
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 WAGO_PROMPT_KEY = "wago_prompt";
export const ACCT_FEATURE_KEYS = [ACCT_PUSH_ENABLED_KEY];

View File

@@ -216,6 +216,12 @@ mat-icon.success-icon {
color: green;
}
.grow-tab-content {
.mat-tab-body-wrapper {
flex-grow: 1;
}
}
.mat-tab-label,
.mat-tab-label-content,
.mat-select-value,
@@ -266,13 +272,14 @@ mat-icon.success-icon {
.tab-container {
overflow: auto;
overflow-x: hidden;
height: 100%;
&.mac {
height: calc(100vh - 95px);
}
&.windows {
height: calc(100vh - 103px);
// height: calc(100vh - 103px);
}
&.linux {