mirror of
https://github.com/WowUp/WowUp.git
synced 2026-05-23 22:15:25 -04:00
Merge branch 'develop' into pr/317
This commit is contained in:
1
wowup-electron/.gitignore
vendored
1
wowup-electron/.gitignore
vendored
@@ -10,6 +10,7 @@
|
||||
main.js
|
||||
ipc-events.js
|
||||
app-updater.js
|
||||
window-state.js
|
||||
src/**/*.js
|
||||
!src/karma.conf.js
|
||||
*.js.map
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
main.js
|
||||
ipc-events.js
|
||||
app-updater.js
|
||||
window-state.js
|
||||
src/**/*.js
|
||||
!src/karma.conf.js
|
||||
*.js.map
|
||||
*.hbs
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"printWidth": 120
|
||||
"printWidth": 120
|
||||
}
|
||||
|
||||
@@ -12,10 +12,7 @@ module.exports = (config, options) => {
|
||||
}
|
||||
|
||||
let fileReplacementParts = fileReplacement["with"].split(".");
|
||||
if (
|
||||
fileReplacementParts.length > 1 &&
|
||||
["web"].indexOf(fileReplacementParts[1]) >= 0
|
||||
) {
|
||||
if (fileReplacementParts.length > 1 && ["web"].indexOf(fileReplacementParts[1]) >= 0) {
|
||||
config.target = "web";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -13,9 +13,7 @@ import {
|
||||
APP_UPDATE_START_DOWNLOAD,
|
||||
} from "./src/common/constants";
|
||||
|
||||
export const checkForUpdates = async function checkForUpdates(
|
||||
win: BrowserWindow
|
||||
) {
|
||||
export const checkForUpdates = async function checkForUpdates(win: BrowserWindow) {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
@@ -63,14 +61,14 @@ export function initializeAppUpdateIpcHandlers(win: BrowserWindow) {
|
||||
return await autoUpdater.downloadUpdate();
|
||||
});
|
||||
|
||||
// Used this solution for Mac support
|
||||
// Used this solution for Mac support
|
||||
// https://github.com/electron-userland/electron-builder/issues/1604#issuecomment-372091881
|
||||
ipcMain.handle(APP_UPDATE_INSTALL, async () => {
|
||||
log.info(APP_UPDATE_INSTALL);
|
||||
app.removeAllListeners('window-all-closed');
|
||||
app.removeAllListeners("window-all-closed");
|
||||
var browserWindows = BrowserWindow.getAllWindows();
|
||||
browserWindows.forEach(function(browserWindow) {
|
||||
browserWindow.removeAllListeners('close');
|
||||
browserWindows.forEach(function (browserWindow) {
|
||||
browserWindow.removeAllListeners("close");
|
||||
});
|
||||
autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
@@ -5,22 +5,26 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuItemConstructorOptions,
|
||||
screen,
|
||||
powerMonitor,
|
||||
} from "electron";
|
||||
import * as log from "electron-log";
|
||||
import * as Store from "electron-store";
|
||||
import { arch, release } from "os";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { Subject } from "rxjs";
|
||||
import { debounceTime } from "rxjs/operators";
|
||||
import * as url from "url";
|
||||
import * as platform from "./platform";
|
||||
import { initializeAppUpdateIpcHandlers, initializeAppUpdater } from "./app-updater";
|
||||
import "./ipc-events";
|
||||
import { initializeIpcHanders } from "./ipc-events";
|
||||
import { COLLAPSE_TO_TRAY_PREFERENCE_KEY, USE_HARDWARE_ACCELERATION_PREFERENCE_KEY } from "./src/common/constants";
|
||||
import { WindowState } from "./src/common/models/window-state";
|
||||
import {
|
||||
COLLAPSE_TO_TRAY_PREFERENCE_KEY,
|
||||
CURRENT_THEME_KEY,
|
||||
DEFAULT_BG_COLOR,
|
||||
DEFAULT_LIGHT_BG_COLOR,
|
||||
USE_HARDWARE_ACCELERATION_PREFERENCE_KEY,
|
||||
} from "./src/common/constants";
|
||||
import { AppOptions } from "./src/common/wowup/app-options";
|
||||
import { windowStateManager } from "./window-state";
|
||||
|
||||
const startedAt = Date.now();
|
||||
const preferenceStore = new Store({ name: "preferences" });
|
||||
@@ -61,9 +65,8 @@ if (preferenceStore.get(USE_HARDWARE_ACCELERATION_PREFERENCE_KEY) === "false") {
|
||||
|
||||
app.commandLine.appendSwitch("disable-features", "OutOfBlinkCors");
|
||||
|
||||
const USER_AGENT = `WowUp-Client/${app.getVersion()} (${release()}; ${arch()};${
|
||||
isPortable ? " portable;" : ""
|
||||
} +https://wowup.io)`;
|
||||
const portableStr = isPortable ? " portable;" : "";
|
||||
const USER_AGENT = `WowUp-Client/${app.getVersion()} (${os.type()}; ${os.release()}; ${os.arch()}; ${portableStr} +https://wowup.io)`;
|
||||
log.info("USER_AGENT", USER_AGENT);
|
||||
|
||||
const argv = require("minimist")(process.argv.slice(1), {
|
||||
@@ -74,71 +77,10 @@ function canStartHidden() {
|
||||
return argv.hidden || app.getLoginItemSettings().wasOpenedAsHidden;
|
||||
}
|
||||
|
||||
function windowStateManager(windowName: string, { width, height }: { width: number; height: number }) {
|
||||
let window: BrowserWindow;
|
||||
let windowState: WindowState;
|
||||
const saveState$ = new Subject<void>();
|
||||
|
||||
function setState() {
|
||||
let setDefaults = false;
|
||||
windowState = preferenceStore.get(`${windowName}-window-state`) as WindowState;
|
||||
|
||||
if (!windowState) {
|
||||
setDefaults = true;
|
||||
} else {
|
||||
log.info("found window state:", windowState);
|
||||
|
||||
const valid = screen.getAllDisplays().some((display) => {
|
||||
return (
|
||||
windowState.x >= display.bounds.x &&
|
||||
windowState.y >= display.bounds.y &&
|
||||
windowState.x + windowState.width <= display.bounds.x + display.bounds.width &&
|
||||
windowState.y + windowState.height <= display.bounds.y + display.bounds.height
|
||||
);
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
log.info("reset window state, bounds are outside displays");
|
||||
setDefaults = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (setDefaults) {
|
||||
log.info("setting window defaults");
|
||||
windowState = <WindowState>{ width, height };
|
||||
}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
log.info("saving window state");
|
||||
if (!window.isMaximized() && !window.isFullScreen()) {
|
||||
windowState = { ...windowState, ...window.getBounds() };
|
||||
}
|
||||
windowState.isMaximized = window.isMaximized();
|
||||
windowState.isFullScreen = window.isFullScreen();
|
||||
preferenceStore.set(`${windowName}-window-state`, windowState);
|
||||
}
|
||||
|
||||
function monitorState(win: BrowserWindow) {
|
||||
window = win;
|
||||
|
||||
win.on("close", saveState);
|
||||
win.on("resize", () => saveState$.next());
|
||||
win.on("move", () => saveState$.next());
|
||||
win.on("closed", () => saveState$.unsubscribe());
|
||||
}
|
||||
|
||||
saveState$.pipe(debounceTime(500)).subscribe(() => saveState());
|
||||
|
||||
setState();
|
||||
|
||||
return {
|
||||
...windowState,
|
||||
monitorState,
|
||||
};
|
||||
}
|
||||
|
||||
function createWindow(): BrowserWindow {
|
||||
const savedTheme = preferenceStore.get(CURRENT_THEME_KEY) as string;
|
||||
const backgroundColor = savedTheme && savedTheme.indexOf("light") !== -1 ? DEFAULT_LIGHT_BG_COLOR : DEFAULT_BG_COLOR;
|
||||
|
||||
// Main object for managing window state
|
||||
// Initialize with a window name and default size
|
||||
const mainWindowManager = windowStateManager("main", {
|
||||
@@ -151,7 +93,7 @@ function createWindow(): BrowserWindow {
|
||||
height: mainWindowManager.height,
|
||||
x: mainWindowManager.x,
|
||||
y: mainWindowManager.y,
|
||||
backgroundColor: "#444444",
|
||||
backgroundColor,
|
||||
title: "WowUp",
|
||||
titleBarStyle: "hidden",
|
||||
webPreferences: {
|
||||
@@ -161,7 +103,7 @@ function createWindow(): BrowserWindow {
|
||||
webSecurity: false,
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
minWidth: 900,
|
||||
minWidth: 940,
|
||||
minHeight: 550,
|
||||
show: false,
|
||||
};
|
||||
@@ -170,6 +112,11 @@ function createWindow(): BrowserWindow {
|
||||
windowOptions.frame = false;
|
||||
}
|
||||
|
||||
// Attempt to fix the missing icon issue on Ubuntu
|
||||
if (platform.isLinux) {
|
||||
windowOptions.icon = path.join(__dirname, "assets", "wowup_logo_512np.png");
|
||||
}
|
||||
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow(windowOptions);
|
||||
initializeIpcHanders(win);
|
||||
@@ -181,6 +128,23 @@ function createWindow(): BrowserWindow {
|
||||
|
||||
win.webContents.userAgent = USER_AGENT;
|
||||
|
||||
// See https://www.electronjs.org/docs/api/web-contents#event-render-process-gone
|
||||
win.webContents.on("render-process-gone", (evt, details) => {
|
||||
log.error("webContents render-process-gone");
|
||||
log.error(evt);
|
||||
log.error(details);
|
||||
});
|
||||
|
||||
// See https://www.electronjs.org/docs/api/web-contents#event-unresponsive
|
||||
win.webContents.on("unresponsive", () => {
|
||||
log.error("webContents unresponsive");
|
||||
});
|
||||
|
||||
// See https://www.electronjs.org/docs/api/web-contents#event-responsive
|
||||
win.webContents.on("responsive", () => {
|
||||
log.error("webContents responsive");
|
||||
});
|
||||
|
||||
win.once("ready-to-show", () => {
|
||||
if (canStartHidden()) {
|
||||
return;
|
||||
@@ -198,16 +162,13 @@ function createWindow(): BrowserWindow {
|
||||
|
||||
if (platform.isMac) {
|
||||
win.on("close", (e) => {
|
||||
if (appIsQuitting) {
|
||||
if (appIsQuitting || preferenceStore.get(COLLAPSE_TO_TRAY_PREFERENCE_KEY) !== "true") {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
win.hide();
|
||||
|
||||
if (preferenceStore.get(COLLAPSE_TO_TRAY_PREFERENCE_KEY) === "true") {
|
||||
app.dock.hide();
|
||||
}
|
||||
app.dock.hide();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -231,23 +192,6 @@ function createWindow(): BrowserWindow {
|
||||
);
|
||||
}
|
||||
|
||||
// Emitted when the window is closed.
|
||||
// win.on('closed', () => {
|
||||
// // Dereference the window object, usually you would store window
|
||||
// // in an array if your app supports multi windows, this is the time
|
||||
// // when you should delete the corresponding element.
|
||||
// win = null;
|
||||
// });
|
||||
|
||||
// win.on('minimize', function (event) {
|
||||
// event.preventDefault();
|
||||
// win.hide();
|
||||
// });
|
||||
|
||||
// win.on('restore', function (event) {
|
||||
// win.show();
|
||||
// });
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
@@ -270,7 +214,7 @@ try {
|
||||
});
|
||||
}
|
||||
|
||||
app.allowRendererProcessReuse = true;
|
||||
app.allowRendererProcessReuse = false;
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
@@ -291,9 +235,9 @@ try {
|
||||
app.on("window-all-closed", () => {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
// if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
// }
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
@@ -308,6 +252,22 @@ try {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
powerMonitor.on("resume", () => {
|
||||
log.info("powerMonitor resume");
|
||||
});
|
||||
|
||||
powerMonitor.on("suspend", () => {
|
||||
log.info("powerMonitor suspend");
|
||||
});
|
||||
|
||||
powerMonitor.on("lock-screen", () => {
|
||||
log.info("powerMonitor lock-screen");
|
||||
});
|
||||
|
||||
powerMonitor.on("unlock-screen", () => {
|
||||
log.info("powerMonitor unlock-screen");
|
||||
});
|
||||
} catch (e) {
|
||||
// Catch Error
|
||||
// throw e;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "wowup",
|
||||
"productName": "WowUp",
|
||||
"version": "2.0.0-beta.11",
|
||||
"version": "2.0.0-beta.15",
|
||||
"description": "Word of Warcraft addon updater",
|
||||
"homepage": "https://wowup.io",
|
||||
"author": {
|
||||
@@ -46,21 +46,21 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/custom-webpack": "10.0.1",
|
||||
"@angular-devkit/build-angular": "0.1002.0",
|
||||
"@angular-devkit/build-angular": "0.1100.1",
|
||||
"@angular-eslint/builder": "0.6.0-beta.0",
|
||||
"@angular-eslint/eslint-plugin": "0.6.0-beta.0",
|
||||
"@angular-eslint/eslint-plugin-template": "0.6.0-beta.0",
|
||||
"@angular-eslint/template-parser": "0.6.0-beta.0",
|
||||
"@angular/cli": "10.2.0",
|
||||
"@angular/common": "10.2.2",
|
||||
"@angular/compiler": "10.2.2",
|
||||
"@angular/compiler-cli": "10.2.2",
|
||||
"@angular/core": "10.2.2",
|
||||
"@angular/forms": "10.2.2",
|
||||
"@angular/language-service": "10.2.2",
|
||||
"@angular/platform-browser": "10.2.2",
|
||||
"@angular/platform-browser-dynamic": "10.2.2",
|
||||
"@angular/router": "10.2.2",
|
||||
"@angular/cli": "11.0.1",
|
||||
"@angular/common": "11.0.0",
|
||||
"@angular/compiler": "11.0.0",
|
||||
"@angular/compiler-cli": "11.0.0",
|
||||
"@angular/core": "11.0.0",
|
||||
"@angular/forms": "11.0.0",
|
||||
"@angular/language-service": "11.0.0",
|
||||
"@angular/platform-browser": "11.0.0",
|
||||
"@angular/platform-browser-dynamic": "11.0.0",
|
||||
"@angular/router": "11.0.0",
|
||||
"@ngx-translate/core": "13.0.0",
|
||||
"@ngx-translate/http-loader": "6.0.0",
|
||||
"@types/adm-zip": "0.4.33",
|
||||
@@ -73,9 +73,9 @@
|
||||
"@types/opossum": "4.1.1",
|
||||
"@types/slug": "0.9.1",
|
||||
"@types/uuid": "8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.6.1",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "4.6.1",
|
||||
"@typescript-eslint/parser": "4.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "4.7.0",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "4.7.0",
|
||||
"@typescript-eslint/parser": "4.7.0",
|
||||
"chai": "4.2.0",
|
||||
"conventional-changelog-cli": "2.1.1",
|
||||
"core-js": "3.7.0",
|
||||
@@ -88,7 +88,7 @@
|
||||
"i18next-json-sync": "2.3.1",
|
||||
"jasmine-core": "3.6.0",
|
||||
"jasmine-spec-reporter": "6.0.0",
|
||||
"karma": "5.2.3",
|
||||
"karma": "5.1.1",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-electron": "6.3.1",
|
||||
"karma-jasmine": "4.0.1",
|
||||
@@ -105,19 +105,20 @@
|
||||
"typescript": "~4.0.5",
|
||||
"wait-on": "5.2.0",
|
||||
"webdriver-manager": "12.1.7",
|
||||
"zone.js": "~0.11.3"
|
||||
"zone.js": "0.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "~10.2.2",
|
||||
"@angular/cdk": "10.2.7",
|
||||
"@angular/material": "10.2.7",
|
||||
"@angular/animations": "~11.0.0",
|
||||
"@angular/cdk": "11.0.0",
|
||||
"@angular/material": "11.0.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.32",
|
||||
"@fortawesome/free-brands-svg-icons": "5.15.1",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.1",
|
||||
"@microsoft/applicationinsights-web": "2.5.9",
|
||||
"@types/lodash": "4.14.165",
|
||||
"adm-zip": "0.4.16",
|
||||
"async": "3.2.0",
|
||||
|
||||
@@ -8,26 +8,13 @@ import { AddonSearchResult } from "../models/wowup/addon-search-result";
|
||||
export interface AddonProvider {
|
||||
name: AddonProviderType;
|
||||
|
||||
getAll(
|
||||
clientType: WowClientType,
|
||||
addonIds: string[]
|
||||
): Promise<AddonSearchResult[]>;
|
||||
getAll(clientType: WowClientType, addonIds: string[]): Promise<AddonSearchResult[]>;
|
||||
|
||||
getFeaturedAddons(
|
||||
clientType: WowClientType,
|
||||
channelType?: AddonChannelType
|
||||
): Promise<AddonSearchResult[]>;
|
||||
getFeaturedAddons(clientType: WowClientType, channelType?: AddonChannelType): Promise<AddonSearchResult[]>;
|
||||
|
||||
searchByQuery(
|
||||
query: string,
|
||||
clientType: WowClientType,
|
||||
channelType?: AddonChannelType
|
||||
): Promise<AddonSearchResult[]>;
|
||||
searchByQuery(query: string, clientType: WowClientType, channelType?: AddonChannelType): Promise<AddonSearchResult[]>;
|
||||
|
||||
searchByUrl(
|
||||
addonUri: URL,
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult>;
|
||||
searchByUrl(addonUri: URL, clientType: WowClientType): Promise<AddonSearchResult>;
|
||||
|
||||
searchByName(
|
||||
addonName: string,
|
||||
@@ -36,25 +23,13 @@ export interface AddonProvider {
|
||||
nameOverride?: string
|
||||
): Promise<AddonSearchResult[]>;
|
||||
|
||||
getById(
|
||||
addonId: string,
|
||||
clientType: WowClientType
|
||||
): Observable<AddonSearchResult>;
|
||||
getById(addonId: string, clientType: WowClientType): Observable<AddonSearchResult>;
|
||||
|
||||
isValidAddonUri(addonUri: URL): boolean;
|
||||
|
||||
onPostInstall(addon: Addon): void;
|
||||
|
||||
scan(
|
||||
clientType: WowClientType,
|
||||
addonChannelType: AddonChannelType,
|
||||
addonFolders: AddonFolder[]
|
||||
): Promise<void>;
|
||||
scan(clientType: WowClientType, addonChannelType: AddonChannelType, addonFolders: AddonFolder[]): Promise<void>;
|
||||
}
|
||||
|
||||
export type AddonProviderType =
|
||||
| "Curse"
|
||||
| "GitHub"
|
||||
| "TukUI"
|
||||
| "WowInterface"
|
||||
| "WowUp";
|
||||
export type AddonProviderType = "Curse" | "GitHub" | "TukUI" | "WowInterface" | "WowUp";
|
||||
|
||||
@@ -58,10 +58,12 @@ export class GitHubAddonProvider implements AddonProvider {
|
||||
const results = await this.getReleases(repoPath).toPromise();
|
||||
const latestRelease = this.getLatestRelease(results);
|
||||
if (!latestRelease) {
|
||||
console.log("latestRelease results", results);
|
||||
throw new Error(`No release found in ${addonUri}`);
|
||||
}
|
||||
|
||||
const asset = this.getValidAsset(latestRelease, clientType);
|
||||
console.log("latestRelease", latestRelease);
|
||||
if (asset == null) {
|
||||
throw new Error(`No release assets found in ${addonUri}`);
|
||||
}
|
||||
|
||||
@@ -20,10 +20,7 @@ const API_URL = "https://www.tukui.org/api.php";
|
||||
const CLIENT_API_URL = "https://www.tukui.org/client-api.php";
|
||||
|
||||
export class TukUiAddonProvider implements AddonProvider {
|
||||
private readonly _circuitBreaker: CircuitBreaker<
|
||||
[clientType: WowClientType],
|
||||
TukUiAddon[]
|
||||
>;
|
||||
private readonly _circuitBreaker: CircuitBreaker<[clientType: WowClientType], TukUiAddon[]>;
|
||||
|
||||
public readonly name = "TukUI";
|
||||
|
||||
@@ -45,10 +42,7 @@ export class TukUiAddonProvider implements AddonProvider {
|
||||
});
|
||||
}
|
||||
|
||||
async getAll(
|
||||
clientType: WowClientType,
|
||||
addonIds: string[]
|
||||
): Promise<AddonSearchResult[]> {
|
||||
async getAll(clientType: WowClientType, addonIds: string[]): Promise<AddonSearchResult[]> {
|
||||
let results: AddonSearchResult[] = [];
|
||||
|
||||
try {
|
||||
@@ -63,32 +57,21 @@ export class TukUiAddonProvider implements AddonProvider {
|
||||
return results;
|
||||
}
|
||||
|
||||
public async getFeaturedAddons(
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult[]> {
|
||||
public async getFeaturedAddons(clientType: WowClientType): Promise<AddonSearchResult[]> {
|
||||
const tukUiAddons = await this.getAllAddons(clientType);
|
||||
return tukUiAddons.map((addon) => this.toSearchResult(addon));
|
||||
}
|
||||
|
||||
async searchByQuery(
|
||||
query: string,
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult[]> {
|
||||
async searchByQuery(query: string, clientType: WowClientType): Promise<AddonSearchResult[]> {
|
||||
const addons = await this.getAllAddons(clientType);
|
||||
const canonQuery = query.toLowerCase();
|
||||
let similarAddons = _.filter(
|
||||
addons,
|
||||
(addon) => addon.name.toLowerCase().indexOf(canonQuery) !== -1
|
||||
);
|
||||
let similarAddons = _.filter(addons, (addon) => addon.name.toLowerCase().indexOf(canonQuery) !== -1);
|
||||
similarAddons = _.orderBy(similarAddons, ["downloads"]);
|
||||
|
||||
return _.map(similarAddons, (addon) => this.toSearchResult(addon));
|
||||
}
|
||||
|
||||
searchByUrl(
|
||||
addonUri: URL,
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult> {
|
||||
searchByUrl(addonUri: URL, clientType: WowClientType): Promise<AddonSearchResult> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
@@ -112,10 +95,7 @@ export class TukUiAddonProvider implements AddonProvider {
|
||||
return results;
|
||||
}
|
||||
|
||||
getById(
|
||||
addonId: string,
|
||||
clientType: WowClientType
|
||||
): Observable<AddonSearchResult | undefined> {
|
||||
getById(addonId: string, clientType: WowClientType): Observable<AddonSearchResult | undefined> {
|
||||
return from(this.getAllAddons(clientType)).pipe(
|
||||
map((addons) => {
|
||||
const match = _.find(addons, (addon) => addon.id === addonId);
|
||||
@@ -139,15 +119,9 @@ export class TukUiAddonProvider implements AddonProvider {
|
||||
for (let addonFolder of addonFolders) {
|
||||
let tukUiAddon: TukUiAddon;
|
||||
if (addonFolder.toc?.tukUiProjectId) {
|
||||
tukUiAddon = _.find(
|
||||
allAddons,
|
||||
(addon) => addon.id.toString() === addonFolder.toc.tukUiProjectId
|
||||
);
|
||||
tukUiAddon = _.find(allAddons, (addon) => addon.id.toString() === addonFolder.toc.tukUiProjectId);
|
||||
} else {
|
||||
const results = await this.searchAddons(
|
||||
addonFolder.toc.title,
|
||||
clientType
|
||||
);
|
||||
const results = await this.searchAddons(addonFolder.toc.title, clientType);
|
||||
tukUiAddon = _.first(results);
|
||||
}
|
||||
|
||||
@@ -182,15 +156,10 @@ export class TukUiAddonProvider implements AddonProvider {
|
||||
|
||||
private async searchAddons(addonName: string, clientType: WowClientType) {
|
||||
var addons = await this.getAllAddons(clientType);
|
||||
return addons.filter(
|
||||
(addon) => addon.name.toLowerCase() === addonName.toLowerCase()
|
||||
);
|
||||
return addons.filter((addon) => addon.name.toLowerCase() === addonName.toLowerCase());
|
||||
}
|
||||
|
||||
private toSearchResult(
|
||||
addon: TukUiAddon,
|
||||
folderName?: string
|
||||
): AddonSearchResult | undefined {
|
||||
private toSearchResult(addon: TukUiAddon, folderName?: string): AddonSearchResult | undefined {
|
||||
if (!addon) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -217,9 +186,7 @@ export class TukUiAddonProvider implements AddonProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private getAllAddons = async (
|
||||
clientType: WowClientType
|
||||
): Promise<TukUiAddon[]> => {
|
||||
private getAllAddons = async (clientType: WowClientType): Promise<TukUiAddon[]> => {
|
||||
if (clientType === WowClientType.None) {
|
||||
return [];
|
||||
}
|
||||
@@ -246,9 +213,7 @@ export class TukUiAddonProvider implements AddonProvider {
|
||||
const url = new URL(API_URL);
|
||||
url.searchParams.append(query, "all");
|
||||
|
||||
const addons = await this._httpClient
|
||||
.get<TukUiAddon[]>(url.toString())
|
||||
.toPromise();
|
||||
const addons = await this._httpClient.get<TukUiAddon[]>(url.toString()).toPromise();
|
||||
if (this.isRetail(clientType)) {
|
||||
addons.push(await this.getTukUiRetailAddon().toPromise());
|
||||
addons.push(await this.getElvUiRetailAddon().toPromise());
|
||||
|
||||
@@ -20,10 +20,7 @@ const API_URL = "https://api.mmoui.com/v4/game/WOW";
|
||||
const ADDON_URL = "https://www.wowinterface.com/downloads/info";
|
||||
|
||||
export class WowInterfaceAddonProvider implements AddonProvider {
|
||||
private readonly _circuitBreaker: CircuitBreaker<
|
||||
[addonId: string],
|
||||
AddonDetailsResponse
|
||||
>;
|
||||
private readonly _circuitBreaker: CircuitBreaker<[addonId: string], AddonDetailsResponse>;
|
||||
|
||||
public readonly name = "WowInterface";
|
||||
|
||||
@@ -45,10 +42,7 @@ export class WowInterfaceAddonProvider implements AddonProvider {
|
||||
});
|
||||
}
|
||||
|
||||
async getAll(
|
||||
clientType: WowClientType,
|
||||
addonIds: string[]
|
||||
): Promise<AddonSearchResult[]> {
|
||||
async getAll(clientType: WowClientType, addonIds: string[]): Promise<AddonSearchResult[]> {
|
||||
var searchResults: AddonSearchResult[] = [];
|
||||
|
||||
for (let addonId of addonIds) {
|
||||
@@ -63,23 +57,15 @@ export class WowInterfaceAddonProvider implements AddonProvider {
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
public async getFeaturedAddons(
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult[]> {
|
||||
public async getFeaturedAddons(clientType: WowClientType): Promise<AddonSearchResult[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async searchByQuery(
|
||||
query: string,
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult[]> {
|
||||
async searchByQuery(query: string, clientType: WowClientType): Promise<AddonSearchResult[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async searchByUrl(
|
||||
addonUri: URL,
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult> {
|
||||
async searchByUrl(addonUri: URL, clientType: WowClientType): Promise<AddonSearchResult> {
|
||||
const addonId = this.getAddonId(addonUri);
|
||||
if (!addonId) {
|
||||
throw new Error(`Addon ID not found ${addonUri}`);
|
||||
@@ -102,14 +88,9 @@ export class WowInterfaceAddonProvider implements AddonProvider {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
getById(
|
||||
addonId: string,
|
||||
clientType: WowClientType
|
||||
): Observable<AddonSearchResult> {
|
||||
getById(addonId: string, clientType: WowClientType): Observable<AddonSearchResult> {
|
||||
return from(this._circuitBreaker.fire(addonId)).pipe(
|
||||
map((result) =>
|
||||
result ? this.toAddonSearchResult(result, "") : undefined
|
||||
)
|
||||
map((result) => (result ? this.toAddonSearchResult(result, "") : undefined))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,16 +112,9 @@ export class WowInterfaceAddonProvider implements AddonProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
const details = await this._circuitBreaker.fire(
|
||||
addonFolder.toc.wowInterfaceId
|
||||
);
|
||||
const details = await this._circuitBreaker.fire(addonFolder.toc.wowInterfaceId);
|
||||
|
||||
addonFolder.matchingAddon = this.toAddon(
|
||||
details,
|
||||
clientType,
|
||||
addonChannelType,
|
||||
addonFolder
|
||||
);
|
||||
addonFolder.matchingAddon = this.toAddon(details, clientType, addonChannelType, addonFolder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,9 +135,7 @@ export class WowInterfaceAddonProvider implements AddonProvider {
|
||||
throw new Error(`Unhandled URL: ${addonUri}`);
|
||||
}
|
||||
|
||||
private getAddonDetails = (
|
||||
addonId: string
|
||||
): Promise<AddonDetailsResponse> => {
|
||||
private getAddonDetails = (addonId: string): Promise<AddonDetailsResponse> => {
|
||||
console.debug("getAddonDetails");
|
||||
const url = new URL(`${API_URL}/filedetails/${addonId}.json`);
|
||||
|
||||
@@ -212,10 +184,7 @@ export class WowInterfaceAddonProvider implements AddonProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private toAddonSearchResult(
|
||||
response: AddonDetailsResponse,
|
||||
folderName?: string
|
||||
): AddonSearchResult {
|
||||
private toAddonSearchResult(response: AddonDetailsResponse, folderName?: string): AddonSearchResult {
|
||||
try {
|
||||
var searchResultFile: AddonSearchResultFile = {
|
||||
channelType: AddonChannelType.Stable,
|
||||
|
||||
@@ -22,43 +22,27 @@ const API_URL = AppConfig.wowUpHubUrl;
|
||||
export class WowUpAddonProvider implements AddonProvider {
|
||||
public readonly name = "WowUp";
|
||||
|
||||
constructor(
|
||||
private _httpClient: HttpClient,
|
||||
private _electronService: ElectronService
|
||||
) {}
|
||||
constructor(private _httpClient: HttpClient, private _electronService: ElectronService) {}
|
||||
|
||||
async getAll(
|
||||
clientType: WowClientType,
|
||||
addonIds: string[]
|
||||
): Promise<AddonSearchResult[]> {
|
||||
async getAll(clientType: WowClientType, addonIds: string[]): Promise<AddonSearchResult[]> {
|
||||
const url = `${API_URL}/addons`;
|
||||
const addons = await this._httpClient
|
||||
.get<WowUpAddonRepresentation[]>(url.toString())
|
||||
.toPromise();
|
||||
const addons = await this._httpClient.get<WowUpAddonRepresentation[]>(url.toString()).toPromise();
|
||||
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
public async getFeaturedAddons(
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult[]> {
|
||||
public async getFeaturedAddons(clientType: WowClientType): Promise<AddonSearchResult[]> {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
async searchByQuery(
|
||||
query: string,
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult[]> {
|
||||
async searchByQuery(query: string, clientType: WowClientType): Promise<AddonSearchResult[]> {
|
||||
// TODO
|
||||
return [];
|
||||
}
|
||||
|
||||
async searchByUrl(
|
||||
addonUri: URL,
|
||||
clientType: WowClientType
|
||||
): Promise<AddonSearchResult> {
|
||||
async searchByUrl(addonUri: URL, clientType: WowClientType): Promise<AddonSearchResult> {
|
||||
// TODO
|
||||
return undefined;
|
||||
}
|
||||
@@ -73,10 +57,7 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
return [];
|
||||
}
|
||||
|
||||
getById(
|
||||
addonId: string,
|
||||
clientType: WowClientType
|
||||
): Observable<AddonSearchResult> {
|
||||
getById(addonId: string, clientType: WowClientType): Observable<AddonSearchResult> {
|
||||
// TODO
|
||||
return of(undefined);
|
||||
}
|
||||
@@ -90,11 +71,7 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
async scan(
|
||||
clientType: WowClientType,
|
||||
addonChannelType: any,
|
||||
addonFolders: AddonFolder[]
|
||||
): Promise<void> {
|
||||
async scan(clientType: WowClientType, addonChannelType: any, addonFolders: AddonFolder[]): Promise<void> {
|
||||
// const url = `${API_URL}/addons`;
|
||||
// const addons = await this._httpClient
|
||||
// .get<WuAddon[]>(url.toString())
|
||||
@@ -130,9 +107,7 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
}
|
||||
|
||||
const matchedScanResults = scanResults.filter((sr) => !!sr.exactMatch);
|
||||
const matchedScanResultIds = matchedScanResults.map(
|
||||
(sr) => sr.exactMatch.id
|
||||
);
|
||||
const matchedScanResultIds = matchedScanResults.map((sr) => sr.exactMatch.id);
|
||||
|
||||
for (let addonFolder of addonFolders) {
|
||||
var scanResult = scanResults.find((sr) => sr.path === addonFolder.path);
|
||||
@@ -142,11 +117,7 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
}
|
||||
|
||||
try {
|
||||
const newAddon = this.getAddon(
|
||||
clientType,
|
||||
addonChannelType,
|
||||
scanResult
|
||||
);
|
||||
const newAddon = this.getAddon(clientType, addonChannelType, scanResult);
|
||||
|
||||
addonFolder.matchingAddon = newAddon;
|
||||
} catch (err) {
|
||||
@@ -158,19 +129,11 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private hasMatchingFingerprint(
|
||||
scanResult: WowUpScanResult,
|
||||
release: WowUpAddonReleaseRepresentation
|
||||
) {
|
||||
return release.addonFolders.some(
|
||||
(addonFolder) => addonFolder.fingerprint == scanResult.fingerprint
|
||||
);
|
||||
private hasMatchingFingerprint(scanResult: WowUpScanResult, release: WowUpAddonReleaseRepresentation) {
|
||||
return release.addonFolders.some((addonFolder) => addonFolder.fingerprint == scanResult.fingerprint);
|
||||
}
|
||||
|
||||
private isGameType(
|
||||
release: WowUpAddonReleaseRepresentation,
|
||||
clientType: WowClientType
|
||||
) {
|
||||
private isGameType(release: WowUpAddonReleaseRepresentation, clientType: WowClientType) {
|
||||
return release.game_type === this.getWowGameType(clientType);
|
||||
}
|
||||
|
||||
@@ -187,9 +150,7 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private getAddonsByFingerprints(
|
||||
fingerprints: string[]
|
||||
): Observable<GetAddonsByFingerprintResponse> {
|
||||
private getAddonsByFingerprints(fingerprints: string[]): Observable<GetAddonsByFingerprintResponse> {
|
||||
const url = `${API_URL}/addons/fingerprint`;
|
||||
|
||||
return this._httpClient.post<any>(url, {
|
||||
@@ -197,9 +158,7 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
});
|
||||
}
|
||||
|
||||
private getScanResults = async (
|
||||
addonFolders: AddonFolder[]
|
||||
): Promise<AppWowUpScanResult[]> => {
|
||||
private getScanResults = async (addonFolders: AddonFolder[]): Promise<AppWowUpScanResult[]> => {
|
||||
const t1 = Date.now();
|
||||
|
||||
const filePaths = addonFolders.map((addonFolder) => addonFolder.path);
|
||||
@@ -224,9 +183,7 @@ export class WowUpAddonProvider implements AddonProvider {
|
||||
(af) => af.load_on_demand === false
|
||||
);
|
||||
const authors = scanResult.exactMatch.owner_name;
|
||||
const folderList = scanResult.exactMatch.matched_release.addonFolders
|
||||
.map((af) => af.folder_name)
|
||||
.join(", ");
|
||||
const folderList = scanResult.exactMatch.matched_release.addonFolders.map((af) => af.folder_name).join(", ");
|
||||
|
||||
let channelType = addonChannelType;
|
||||
let latestVersion = primaryAddonFolder.version;
|
||||
|
||||
@@ -16,7 +16,7 @@ const routes: Routes = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes), HomeRoutingModule],
|
||||
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: "legacy" }), HomeRoutingModule],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
<div *ngIf="quitEnabled === false" class="app">
|
||||
<app-titlebar></app-titlebar>
|
||||
<div *ngIf="quitEnabled === false" class="app" [ngClass]="[wowUpService.currentTheme]">
|
||||
<app-titlebar class="bg-primary"></app-titlebar>
|
||||
<div class="content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
|
||||
<div *ngIf="quitEnabled === true" class="pre-load-container" style="
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
">
|
||||
<div
|
||||
*ngIf="quitEnabled === true"
|
||||
class="pre-load-container"
|
||||
style="
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
"
|
||||
>
|
||||
<img class="logo" src="assets/wowup_logo_512np.png" style="width: 200px" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
:host {
|
||||
}
|
||||
.app {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { async, TestBed } from "@angular/core/testing";
|
||||
import { TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { AppComponent } from "./app.component";
|
||||
import { ElectronService } from "./services";
|
||||
|
||||
describe("AppComponent", () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
providers: [ElectronService],
|
||||
imports: [RouterTestingModule, TranslateModule.forRoot()],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
providers: [ElectronService],
|
||||
imports: [RouterTestingModule, TranslateModule.forRoot()],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
it("should create the app", async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(
|
||||
"should create the app",
|
||||
waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, OnInit } from "@angular/core";
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { CREATE_TRAY_MENU_CHANNEL } from "../common/constants";
|
||||
import { OverlayContainer } from "@angular/cdk/overlay";
|
||||
import {
|
||||
ALLIANCE_LIGHT_THEME,
|
||||
ALLIANCE_THEME,
|
||||
CREATE_TRAY_MENU_CHANNEL,
|
||||
CURRENT_THEME_KEY,
|
||||
DEFAULT_LIGHT_THEME,
|
||||
DEFAULT_THEME,
|
||||
HORDE_LIGHT_THEME,
|
||||
HORDE_THEME,
|
||||
} from "../common/constants";
|
||||
import { SystemTrayConfig } from "../common/wowup/system-tray-config";
|
||||
import { TelemetryDialogComponent } from "./components/telemetry-dialog/telemetry-dialog.component";
|
||||
import { ElectronService } from "./services";
|
||||
@@ -10,6 +20,7 @@ import { AnalyticsService } from "./services/analytics/analytics.service";
|
||||
import { FileService } from "./services/files/file.service";
|
||||
import { WowUpService } from "./services/wowup/wowup.service";
|
||||
import { IconService } from "./services/icons/icon.service";
|
||||
import { SessionService } from "./services/session/session.service";
|
||||
import { filter } from "rxjs/operators";
|
||||
|
||||
const AUTO_UPDATE_PERIOD_MS = 60 * 60 * 1000; // 1 hour
|
||||
@@ -20,7 +31,7 @@ const AUTO_UPDATE_PERIOD_MS = 60 * 60 * 1000; // 1 hour
|
||||
styleUrls: ["./app.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AppComponent implements AfterViewInit {
|
||||
export class AppComponent implements OnInit, AfterViewInit {
|
||||
private _autoUpdateInterval?: number;
|
||||
|
||||
public get quitEnabled() {
|
||||
@@ -32,12 +43,32 @@ export class AppComponent implements AfterViewInit {
|
||||
private _electronService: ElectronService,
|
||||
private _fileService: FileService,
|
||||
private translate: TranslateService,
|
||||
private _wowUpService: WowUpService,
|
||||
private _dialog: MatDialog,
|
||||
private _addonService: AddonService,
|
||||
private _iconService: IconService
|
||||
private _iconService: IconService,
|
||||
private _sessionService: SessionService,
|
||||
public overlayContainer: OverlayContainer,
|
||||
public wowUpService: WowUpService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.overlayContainer.getContainerElement().classList.add(this.wowUpService.currentTheme);
|
||||
|
||||
this.wowUpService.preferenceChange$.pipe(filter((pref) => pref.key === CURRENT_THEME_KEY)).subscribe((pref) => {
|
||||
this.overlayContainer
|
||||
.getContainerElement()
|
||||
.classList.remove(
|
||||
HORDE_THEME,
|
||||
HORDE_LIGHT_THEME,
|
||||
ALLIANCE_THEME,
|
||||
ALLIANCE_LIGHT_THEME,
|
||||
DEFAULT_THEME,
|
||||
DEFAULT_LIGHT_THEME
|
||||
);
|
||||
this.overlayContainer.getContainerElement().classList.add(pref.value);
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.createSystemTray();
|
||||
|
||||
@@ -48,7 +79,10 @@ export class AppComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
this.onAutoUpdateInterval();
|
||||
this._autoUpdateInterval = window.setInterval(this.onAutoUpdateInterval, AUTO_UPDATE_PERIOD_MS);
|
||||
this._autoUpdateInterval = window.setInterval(() => {
|
||||
this.onAutoUpdateInterval();
|
||||
this._sessionService.autoUpdateComplete();
|
||||
}, AUTO_UPDATE_PERIOD_MS);
|
||||
}
|
||||
|
||||
openDialog(): void {
|
||||
@@ -73,7 +107,7 @@ export class AppComponent implements AfterViewInit {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._wowUpService.enableSystemNotifications) {
|
||||
if (this.wowUpService.enableSystemNotifications) {
|
||||
const iconPath = await this._fileService.getAssetFilePath("wowup_logo_512np.png");
|
||||
const translated = await this.translate
|
||||
.get(["APP.AUTO_UPDATE_NOTIFICATION_TITLE", "APP.AUTO_UPDATE_NOTIFICATION_BODY"], {
|
||||
|
||||
@@ -30,5 +30,4 @@ export class GetAddonListItem {
|
||||
constructor(searchResult: AddonSearchResult) {
|
||||
this.searchResult = searchResult;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -116,5 +116,4 @@ export class AddonViewModel {
|
||||
? this.addon.dependencies
|
||||
: _.filter(this.addon.dependencies, (dep) => dep.type === dependencyType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<div class="addon-detail-view">
|
||||
<div class="row align-items-start">
|
||||
<h3 mat-dialog-title class="flex-grow-1">{{ title }}</h3>
|
||||
<mat-icon class="close-icon" color="accent" [mat-dialog-close]="true" [ngStyle]="{ cursor: 'pointer' }"
|
||||
svgIcon="fas:times">
|
||||
<mat-icon
|
||||
class="close-icon"
|
||||
color="accent"
|
||||
[mat-dialog-close]="true"
|
||||
[ngStyle]="{ cursor: 'pointer' }"
|
||||
svgIcon="fas:times"
|
||||
>
|
||||
</mat-icon>
|
||||
</div>
|
||||
<div *ngIf="defaultImageUrl" class="row justify-content-center image-row">
|
||||
<div *ngIf="defaultImageUrl" class="row justify-content-center image-row bg-secondary-4">
|
||||
<img class="image" [src]="defaultImageUrl" alt="Addon Picture" />
|
||||
</div>
|
||||
|
||||
@@ -19,12 +24,15 @@
|
||||
<div>
|
||||
<div class="row align-items-center">
|
||||
<div class="mr-2">
|
||||
<div> {{ 'DIALOGS.ADDON_DETAILS.VIEW_ON_PROVIDER_PREFIX' | translate }} {{ provider }}</div>
|
||||
<div>{{ "DIALOGS.ADDON_DETAILS.VIEW_ON_PROVIDER_PREFIX" | translate }} {{ provider }}</div>
|
||||
<div class="sub-text text-right selectable">{{ getExternalId() }}</div>
|
||||
</div>
|
||||
<a class="icon-link" appExternalLink [href]="externalUrl" [matTooltip]="
|
||||
'DIALOGS.ADDON_DETAILS.VIEW_IN_BROWSER_BUTTON' | translate
|
||||
">
|
||||
<a
|
||||
class="icon-link"
|
||||
appExternalLink
|
||||
[href]="externalUrl"
|
||||
[matTooltip]="'DIALOGS.ADDON_DETAILS.VIEW_IN_BROWSER_BUTTON' | translate"
|
||||
>
|
||||
<mat-icon class="open-in-browser-icon" color="accent" svgIcon="fas:external-link-alt"></mat-icon>
|
||||
</a>
|
||||
</div>
|
||||
@@ -33,7 +41,7 @@
|
||||
<!-- DEPENDENCIES -->
|
||||
<div *ngIf="hasRequiredDependencies()" class="addon-dependencies row align-items-center">
|
||||
<mat-icon svgIcon="fas:link" class="mr-1"></mat-icon>
|
||||
<span translate [translateParams]="{ 'dependencyCount': getRequiredDependencyCount() }">
|
||||
<span translate [translateParams]="{ dependencyCount: getRequiredDependencyCount() }">
|
||||
DIALOGS.ADDON_DETAILS.DEPENDENCY_TEXT
|
||||
</span>
|
||||
</div>
|
||||
@@ -41,9 +49,12 @@
|
||||
<div class="mat-caption addon-detail-summary">{{ summary }}</div>
|
||||
</div>
|
||||
<div mat-dialog-actions class="row align-items-center justify-content-end">
|
||||
<app-addon-install-button *ngIf="showInstallButton" [addonSearchResult]="model.searchResult"
|
||||
(onViewUpdated)="onInstallUpdated()">
|
||||
<app-addon-install-button
|
||||
*ngIf="showInstallButton"
|
||||
[addonSearchResult]="model.searchResult"
|
||||
(onViewUpdated)="onInstallUpdated()"
|
||||
>
|
||||
</app-addon-install-button>
|
||||
<app-addon-update-button *ngIf="showUpdateButton" [listItem]="model.listItem"></app-addon-update-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@import "../../../variables.scss";
|
||||
|
||||
.image-row {
|
||||
background-color: $dark-4;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.image {
|
||||
max-width: 100%;
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
} from "@angular/core";
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Subscription } from "rxjs";
|
||||
import { filter } from "rxjs/operators";
|
||||
@@ -43,9 +36,7 @@ export class AddonInstallButtonComponent implements OnInit, OnDestroy {
|
||||
this._sessionService.selectedClientType
|
||||
);
|
||||
this.disableButton = isInstalled;
|
||||
this.buttonText = this.getButtonText(
|
||||
isInstalled ? AddonInstallState.Complete : AddonInstallState.Unknown
|
||||
);
|
||||
this.buttonText = this.getButtonText(isInstalled ? AddonInstallState.Complete : AddonInstallState.Unknown);
|
||||
|
||||
const addonInstalledSub = this._addonService.addonInstalled$
|
||||
.pipe(
|
||||
@@ -72,10 +63,7 @@ export class AddonInstallButtonComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public getIsButtonActive(installState: AddonInstallState) {
|
||||
return (
|
||||
installState !== AddonInstallState.Unknown &&
|
||||
installState !== AddonInstallState.Complete
|
||||
);
|
||||
return installState !== AddonInstallState.Unknown && installState !== AddonInstallState.Complete;
|
||||
}
|
||||
|
||||
public getIsButtonDisabled(installState: AddonInstallState) {
|
||||
@@ -108,9 +96,6 @@ export class AddonInstallButtonComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public async onInstallUpdateClick() {
|
||||
await this._addonService.installPotentialAddon(
|
||||
this.addonSearchResult,
|
||||
this._sessionService.selectedClientType
|
||||
);
|
||||
await this._addonService.installPotentialAddon(this.addonSearchResult, this._sessionService.selectedClientType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
} from "@angular/core";
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core";
|
||||
import { AddonProviderType } from "../../addon-providers/addon-provider";
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -8,16 +8,8 @@ describe("AddonUpdateButtonComponent", () => {
|
||||
it("should create", () => {
|
||||
inject(
|
||||
[AddonService, AnalyticsService, TranslateService],
|
||||
(
|
||||
addonService: AddonService,
|
||||
analyticsService: AnalyticsService,
|
||||
translateService: TranslateService
|
||||
) => {
|
||||
const instance = new AddonUpdateButtonComponent(
|
||||
addonService,
|
||||
analyticsService,
|
||||
translateService
|
||||
);
|
||||
(addonService: AddonService, analyticsService: AnalyticsService, translateService: TranslateService) => {
|
||||
const instance = new AddonUpdateButtonComponent(addonService, analyticsService, translateService);
|
||||
expect(instance).toBeTruthy();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
} from "@angular/core";
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Subscription } from "rxjs";
|
||||
import { filter } from "rxjs/operators";
|
||||
@@ -52,9 +45,9 @@ export class AddonUpdateButtonComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public getActionLabel() {
|
||||
return `${getEnumName(WowClientType, this.listItem?.addon?.clientType)}|${
|
||||
this.listItem?.addon.providerName
|
||||
}|${this.listItem?.addon.externalId}|${this.listItem?.addon.name}`;
|
||||
return `${getEnumName(WowClientType, this.listItem?.addon?.clientType)}|${this.listItem?.addon.providerName}|${
|
||||
this.listItem?.addon.externalId
|
||||
}|${this.listItem?.addon.name}`;
|
||||
}
|
||||
|
||||
public getInstallProgress() {
|
||||
@@ -69,10 +62,7 @@ export class AddonUpdateButtonComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public getIsButtonDisabled() {
|
||||
return (
|
||||
this.listItem?.isUpToDate ||
|
||||
this.listItem?.installState < AddonInstallState.Unknown
|
||||
);
|
||||
return this.listItem?.isUpToDate || this.listItem?.installState < AddonInstallState.Unknown;
|
||||
}
|
||||
|
||||
public getButtonText() {
|
||||
@@ -84,35 +74,23 @@ export class AddonUpdateButtonComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public onInstallUpdateClick() {
|
||||
this._analyticsService.trackUserAction(
|
||||
"addons",
|
||||
"update_addon",
|
||||
this.getActionLabel()
|
||||
);
|
||||
|
||||
this._addonService.installAddon(this.listItem.addon.id);
|
||||
}
|
||||
|
||||
public getStatusText() {
|
||||
if (this.listItem?.needsInstall) {
|
||||
return this._translateService.instant(
|
||||
"PAGES.MY_ADDONS.TABLE.ADDON_INSTALL_BUTTON"
|
||||
);
|
||||
return this._translateService.instant("PAGES.MY_ADDONS.TABLE.ADDON_INSTALL_BUTTON");
|
||||
}
|
||||
|
||||
if (this.listItem?.needsUpdate) {
|
||||
return this._translateService.instant(
|
||||
"PAGES.MY_ADDONS.TABLE.ADDON_UPDATE_BUTTON"
|
||||
);
|
||||
return this._translateService.instant("PAGES.MY_ADDONS.TABLE.ADDON_UPDATE_BUTTON");
|
||||
}
|
||||
|
||||
if (!this.listItem) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return this._translateService.instant(
|
||||
this.listItem.stateTextTranslationKey
|
||||
);
|
||||
return this._translateService.instant(this.listItem.stateTextTranslationKey);
|
||||
}
|
||||
|
||||
private getInstallStateText(installState: AddonInstallState) {
|
||||
@@ -122,9 +100,7 @@ export class AddonUpdateButtonComponent implements OnInit, OnDestroy {
|
||||
case AddonInstallState.Complete:
|
||||
return this._translateService.instant("COMMON.ADDON_STATE.UPTODATE");
|
||||
case AddonInstallState.Downloading:
|
||||
return this._translateService.instant(
|
||||
"COMMON.ADDON_STATUS.DOWNLOADING"
|
||||
);
|
||||
return this._translateService.instant("COMMON.ADDON_STATUS.DOWNLOADING");
|
||||
case AddonInstallState.Installing:
|
||||
return this._translateService.instant("COMMON.ADDON_STATUS.INSTALLING");
|
||||
case AddonInstallState.Pending:
|
||||
|
||||
@@ -7,12 +7,7 @@
|
||||
<button mat-button [mat-dialog-close]="false" color="primary">
|
||||
{{ "DIALOGS.CONFIRM.NEGATIVE_BUTTON" | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-raised-button
|
||||
[mat-dialog-close]="true"
|
||||
cdkFocusInitial
|
||||
color="primary"
|
||||
>
|
||||
<button mat-raised-button [mat-dialog-close]="true" cdkFocusInitial color="primary">
|
||||
{{ "DIALOGS.CONFIRM.POSITIVE_BUTTON" | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,38 +1,59 @@
|
||||
<footer class="bg-dark-4 text-light-2">
|
||||
<a appExternalLink class="patreon-link mr-2" href="https://www.patreon.com/jliddev" matTooltip="{{
|
||||
'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.PATREON_SUPPORT' | translate
|
||||
}}">
|
||||
<footer class="bg-secondary-4 text-light-2">
|
||||
<a
|
||||
appExternalLink
|
||||
class="patreon-link hover-bg-secondary-2 mr-2"
|
||||
href="https://www.patreon.com/jliddev"
|
||||
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.PATREON_SUPPORT' | translate }}"
|
||||
>
|
||||
<img class="patron-img" src="assets/images/patreon_logo_small.png" />
|
||||
</a>
|
||||
<a appExternalLink class="discord-link mr-2" href="https://discord.gg/rk4F5aD" matTooltip="{{
|
||||
'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.JOIN_DISCORD' | translate
|
||||
}}">
|
||||
<img class="discord-img" src="assets/images/discord_logo_small.png" />
|
||||
<a
|
||||
appExternalLink
|
||||
class="link discord-link text-1 hover-bg-secondary-2 mr-2"
|
||||
href="https://discord.gg/rk4F5aD"
|
||||
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.JOIN_DISCORD' | translate }}"
|
||||
>
|
||||
<mat-icon svgIcon="fab:discord"></mat-icon>
|
||||
</a>
|
||||
<a appExternalLink class="github-link mr-2" href="https://github.com/WowUp/WowUp" matTooltip="{{
|
||||
'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.VIEW_GITHUB' | translate
|
||||
}}">
|
||||
<img class="github-img" src="assets/images/github_logo.png" />
|
||||
<a
|
||||
appExternalLink
|
||||
class="link github-link text-1 hover-bg-secondary-2 mr-2"
|
||||
href="https://github.com/WowUp/WowUp"
|
||||
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.VIEW_GITHUB' | translate }}"
|
||||
>
|
||||
<mat-icon svgIcon="fab:github"></mat-icon>
|
||||
</a>
|
||||
<a appExternalLink class="guide-link mr-2" href="https://wowup.io/guide/section/my-addons/overview" matTooltip="{{
|
||||
'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.VIEW_GUIDE' | translate
|
||||
}}">
|
||||
<a
|
||||
appExternalLink
|
||||
class="link guide-link text-1 hover-bg-secondary-2 mr-2"
|
||||
href="https://wowup.io/guide/section/my-addons/overview"
|
||||
matTooltip="{{ 'PAGES.MY_ADDONS.PAGE_CONTEXT_FOOTER.VIEW_GUIDE' | translate }}"
|
||||
>
|
||||
<mat-icon svgIcon="far:question-circle"></mat-icon>
|
||||
</a>
|
||||
<p class="flex-grow-1">{{ sessionService.statusText$ | async }}</p>
|
||||
<div class="row align-items-center">
|
||||
<p class="flex-grow-1 text-1">{{ sessionService.statusText$ | async }}</p>
|
||||
<div class="row align-items-center text-1">
|
||||
<p class="mr-3">{{ sessionService.pageContextText$ | async }}</p>
|
||||
<p>v{{ wowUpService.applicationVersion }}</p>
|
||||
<button *ngIf="isWowUpUpdateAvailable && !isWowUpdateDownloading" class="footer-button update-button ml-3"
|
||||
matTooltip="{{ updateIconTooltip | translate }}" [disabled]="isUpdatingWowUp" (click)="onClickUpdateWowup()">
|
||||
<button
|
||||
*ngIf="isWowUpUpdateAvailable && !isWowUpdateDownloading"
|
||||
class="footer-button update-button ml-3"
|
||||
matTooltip="{{ updateIconTooltip | translate }}"
|
||||
[disabled]="isUpdatingWowUp"
|
||||
(click)="onClickUpdateWowup()"
|
||||
>
|
||||
<mat-icon svgIcon="fas:arrow-up"></mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isWowUpdateDownloading" class="footer-button downloading-button ml-3 animate">
|
||||
<button *ngIf="isWowUpdateDownloading" class="footer-button downloading-button text-2 ml-3 animate">
|
||||
<mat-icon svgIcon="fas:angle-double-down"></mat-icon>
|
||||
</button>
|
||||
<button *ngIf="!isWowUpUpdateAvailable && !isWowUpdateDownloading" class="footer-button check-update-button ml-3"
|
||||
[ngClass]="{'animate': isCheckingForUpdates}" (click)="onClickCheckForUpdates()"
|
||||
matTooltip="{{'APP.SYSTEM_TRAY.CHECK_UPDATE' | translate}}">
|
||||
<button
|
||||
*ngIf="!isWowUpUpdateAvailable && !isWowUpdateDownloading"
|
||||
class="footer-button text-1 check-update-button ml-3"
|
||||
[ngClass]="{ animate: isCheckingForUpdates }"
|
||||
(click)="onClickCheckForUpdates()"
|
||||
matTooltip="{{ 'APP.SYSTEM_TRAY.CHECK_UPDATE' | translate }}"
|
||||
>
|
||||
<mat-icon svgIcon="fas:sync-alt"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -21,51 +21,33 @@ footer {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.link {
|
||||
.mat-icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.patreon-link {
|
||||
padding: 0 0.25em;
|
||||
&:hover {
|
||||
background-color: $dark-3;
|
||||
}
|
||||
|
||||
.patron-img {
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.discord-link {
|
||||
padding: 0 0.25em;
|
||||
|
||||
&:hover {
|
||||
background-color: $dark-3;
|
||||
}
|
||||
|
||||
.discord-img {
|
||||
height: 25px;
|
||||
}
|
||||
height: 20px;
|
||||
padding: 2.5px 0.25em;
|
||||
}
|
||||
|
||||
.github-link {
|
||||
height: 20px;
|
||||
padding: 2.5px 0.25em;
|
||||
&:hover {
|
||||
background-color: $dark-3;
|
||||
}
|
||||
.github-img {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.guide-link {
|
||||
height: 20px;
|
||||
padding: 2.5px 0.25em;
|
||||
color: $white-1;
|
||||
&:hover {
|
||||
background-color: $dark-3;
|
||||
}
|
||||
.mat-icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-button {
|
||||
@@ -78,7 +60,6 @@ footer {
|
||||
|
||||
&.update-button {
|
||||
background-color: green;
|
||||
color: $white-1;
|
||||
|
||||
&:hover {
|
||||
background-color: darkgreen;
|
||||
@@ -97,8 +78,6 @@ footer {
|
||||
}
|
||||
|
||||
&.check-update-button {
|
||||
color: $white-2;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -109,8 +88,6 @@ footer {
|
||||
}
|
||||
|
||||
&.downloading-button {
|
||||
color: $white-2;
|
||||
|
||||
&.animate {
|
||||
animation: fadeInDown 1s infinite linear;
|
||||
}
|
||||
|
||||
@@ -5,14 +5,10 @@
|
||||
</p>
|
||||
<p>{{ "DIALOGS.INSTALL_FROM_URL.SUPPORTED_SOURCES" | translate }}</p>
|
||||
<mat-form-field class="url-input-container">
|
||||
<mat-label>{{
|
||||
"DIALOGS.INSTALL_FROM_URL.ADDON_URL_INPUT_LABEL" | translate
|
||||
}}</mat-label>
|
||||
<mat-label>{{ "DIALOGS.INSTALL_FROM_URL.ADDON_URL_INPUT_LABEL" | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[placeholder]="
|
||||
'DIALOGS.INSTALL_FROM_URL.ADDON_URL_INPUT_PLACEHOLDER' | translate
|
||||
"
|
||||
[placeholder]="'DIALOGS.INSTALL_FROM_URL.ADDON_URL_INPUT_PLACEHOLDER' | translate"
|
||||
[(ngModel)]="query"
|
||||
(keyup.enter)="onImportUrl()"
|
||||
[disabled]="isBusy === true"
|
||||
@@ -34,37 +30,30 @@
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
<div *ngIf="isBusy === false && addon !== undefined" class="addon-container">
|
||||
<div
|
||||
class="addon-thumb"
|
||||
[style.backgroundImage]="'url(' + addon.thumbnailUrl + ')'"
|
||||
></div>
|
||||
<div class="addon-thumb" [style.backgroundImage]="'url(' + addon.thumbnailUrl + ')'"></div>
|
||||
<div class="addon-info">
|
||||
<h4>{{ addon.name }}</h4>
|
||||
<p>{{ addon.author }}</p>
|
||||
<p class="addon-download-count">
|
||||
{{ addon.downloadCount | downloadCount }} downloads on
|
||||
{{ addon.providerName }}
|
||||
{{
|
||||
"DIALOGS.INSTALL_FROM_URL.DOWNLOAD_COUNT"
|
||||
| translate
|
||||
: {
|
||||
count: addon.downloadCount,
|
||||
textCount: (addon.downloadCount | downloadCount),
|
||||
provider: addon.providerName
|
||||
}
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="install-container">
|
||||
<mat-spinner
|
||||
*ngIf="showInstallSpinner === true"
|
||||
diameter="40"
|
||||
></mat-spinner>
|
||||
<button
|
||||
*ngIf="showInstallButton === true"
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
(click)="onInstall()"
|
||||
>
|
||||
<mat-spinner *ngIf="showInstallSpinner === true" diameter="40"></mat-spinner>
|
||||
<button *ngIf="showInstallButton === true" mat-flat-button color="primary" (click)="onInstall()">
|
||||
{{ "DIALOGS.INSTALL_FROM_URL.INSTALL_BUTTON" | translate }}
|
||||
</button>
|
||||
<div [hidden]="showInstallSuccess === false">
|
||||
<div class="success-icon">
|
||||
<img
|
||||
src="assets/images/checkbox-marked-circle-green.svg"
|
||||
class="icon-larger"
|
||||
/>
|
||||
<img src="assets/images/checkbox-marked-circle-green.svg" class="icon-larger" />
|
||||
</div>
|
||||
<div>
|
||||
{{ "DIALOGS.INSTALL_FROM_URL.INSTALL_SUCCESS_LABEL" | translate }}
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
|
||||
.success-icon {
|
||||
text-align: center;
|
||||
filter: invert(23%) sepia(99%) saturate(1821%) hue-rotate(104deg)
|
||||
brightness(96%) contrast(106%);
|
||||
filter: invert(23%) sepia(99%) saturate(1821%) hue-rotate(104deg) brightness(96%) contrast(106%);
|
||||
}
|
||||
|
||||
.icon-larger {
|
||||
|
||||
@@ -51,10 +51,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy {
|
||||
this.showInstallSpinner = true;
|
||||
|
||||
this._installSubscription = from(
|
||||
this._addonService.installPotentialAddon(
|
||||
this.addon,
|
||||
this._sessionService.selectedClientType
|
||||
)
|
||||
this._addonService.installPotentialAddon(this.addon, this._sessionService.selectedClientType)
|
||||
).subscribe({
|
||||
next: () => {
|
||||
this.showInstallSpinner = false;
|
||||
@@ -64,11 +61,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy {
|
||||
console.error(err);
|
||||
this.showInstallSpinner = false;
|
||||
this.showInstallButton = true;
|
||||
this.showErrorMessage(
|
||||
this._translateService.instant(
|
||||
"DIALOGS.INSTALL_FROM_URL.ERROR.INSTALL_FAILED"
|
||||
)
|
||||
);
|
||||
this.showErrorMessage(this._translateService.instant("DIALOGS.INSTALL_FROM_URL.ERROR.INSTALL_FAILED"));
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -88,10 +81,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
try {
|
||||
const importedAddon = await this._addonService.getAddonByUrl(
|
||||
url,
|
||||
this._sessionService.selectedClientType
|
||||
);
|
||||
const importedAddon = await this._addonService.getAddonByUrl(url, this._sessionService.selectedClientType);
|
||||
|
||||
console.debug(importedAddon);
|
||||
if (!importedAddon) {
|
||||
@@ -112,14 +102,10 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
let message = err.message;
|
||||
if (err instanceof HttpErrorResponse) {
|
||||
message = this._translateService.instant(
|
||||
"DIALOGS.INSTALL_FROM_URL.ERROR.NO_ADDON_FOUND"
|
||||
);
|
||||
message = this._translateService.instant("DIALOGS.INSTALL_FROM_URL.ERROR.NO_ADDON_FOUND");
|
||||
} else if (err.code && err.code === "EOPENBREAKER") {
|
||||
// Provider circuit breaker is open
|
||||
message = this._translateService.instant(
|
||||
"DIALOGS.INSTALL_FROM_URL.ERROR.FAILED_TO_CONNECT"
|
||||
);
|
||||
message = this._translateService.instant("DIALOGS.INSTALL_FROM_URL.ERROR.FAILED_TO_CONNECT");
|
||||
}
|
||||
|
||||
this.showErrorMessage(message);
|
||||
@@ -127,10 +113,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private addonExists(externalId: string) {
|
||||
return this._addonService.isInstalled(
|
||||
externalId,
|
||||
this._sessionService.selectedClientType
|
||||
);
|
||||
return this._addonService.isInstalled(externalId, this._sessionService.selectedClientType);
|
||||
}
|
||||
|
||||
private getUrlFromQuery(): URL | undefined {
|
||||
@@ -138,11 +121,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy {
|
||||
return new URL(this.query);
|
||||
} catch (err) {
|
||||
console.error(`Invalid url: ${this.query}`);
|
||||
this.showErrorMessage(
|
||||
this._translateService.instant(
|
||||
"DIALOGS.INSTALL_FROM_URL.ERROR.INVALID_URL"
|
||||
)
|
||||
);
|
||||
this.showErrorMessage(this._translateService.instant("DIALOGS.INSTALL_FROM_URL.ERROR.INVALID_URL"));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -151,9 +130,7 @@ export class InstallFromUrlDialogComponent implements OnInit, OnDestroy {
|
||||
const dialogRef = this._dialog.open(AlertDialogComponent, {
|
||||
minWidth: 250,
|
||||
data: {
|
||||
title: this._translateService.instant(
|
||||
"DIALOGS.INSTALL_FROM_URL.ERROR.TITLE"
|
||||
),
|
||||
title: this._translateService.instant("DIALOGS.INSTALL_FROM_URL.ERROR.TITLE"),
|
||||
message: errorMessage,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
} from "@angular/core";
|
||||
import { Component, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { AddonViewModel } from "../../business-objects/my-addon-list-item";
|
||||
|
||||
@@ -24,10 +16,7 @@ export class MyAddonStatusColumnComponent implements OnInit, OnDestroy {
|
||||
return this.listItem?.isUpToDate || this.listItem?.isIgnored;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _translateService: TranslateService,
|
||||
private _ngzone: NgZone
|
||||
) {}
|
||||
constructor(private _translateService: TranslateService, private _ngzone: NgZone) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
|
||||
@@ -1,52 +1,80 @@
|
||||
<div class="addon-column row align-items-center">
|
||||
<div class="thumbnail-container">
|
||||
<div *ngIf="listItem.hasThumbnail === true" class="addon-logo-container"
|
||||
[style.backgroundImage]="'url(' + listItem.addon.thumbnailUrl + ')'"></div>
|
||||
<div
|
||||
*ngIf="listItem.hasThumbnail === true"
|
||||
class="addon-logo-container bg-secondary-3"
|
||||
[style.backgroundImage]="'url(' + listItem.addon.thumbnailUrl + ')'"
|
||||
></div>
|
||||
<div *ngIf="listItem.hasThumbnail === false" class="addon-logo-container">
|
||||
<div class="addon-logo-letter">
|
||||
<div class="addon-logo-letter text-3">
|
||||
{{ listItem.thumbnailLetter }}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="listItem.isBetaChannel || listItem.isAlphaChannel" class="channel" [ngClass]="{
|
||||
<div
|
||||
*ngIf="listItem.isBetaChannel || listItem.isAlphaChannel"
|
||||
class="channel bg-secondary-3"
|
||||
[ngClass]="{
|
||||
beta: listItem.isBetaChannel,
|
||||
alpha: listItem.isAlphaChannel
|
||||
}">
|
||||
}"
|
||||
>
|
||||
{{ listItem.isAlphaChannel ? "Alpha" : "Beta" }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="addon-title mat-subheading-2" (click)="viewDetails()"
|
||||
[ngClass]="{ ignored: listItem.isIgnored }">{{ listItem.addon.name }}</a>
|
||||
<a class="addon-title hover-text-2 mat-subheading-2" (click)="viewDetails()" [ngClass]="{ 'text-3': listItem.isIgnored }">{{
|
||||
listItem.addon.name
|
||||
}}</a>
|
||||
<div class="addon-funding">
|
||||
<a *ngIf="listItem.addon.patreonFundingLink" appExternalLink [href]="listItem.addon.patreonFundingLink"
|
||||
matTooltip="Support the author on Patreon">
|
||||
<a
|
||||
*ngIf="listItem.addon.patreonFundingLink"
|
||||
appExternalLink
|
||||
[href]="listItem.addon.patreonFundingLink"
|
||||
matTooltip="Support the author on Patreon"
|
||||
>
|
||||
<img class="funding-icon" src="assets/images/patreon_logo_small.png" />
|
||||
</a>
|
||||
<a *ngIf="listItem.addon.githubFundingLink" appExternalLink [href]="listItem.addon.githubFundingLink"
|
||||
matTooltip="Support the author on GitHub">
|
||||
<a
|
||||
*ngIf="listItem.addon.githubFundingLink"
|
||||
appExternalLink
|
||||
[href]="listItem.addon.githubFundingLink"
|
||||
matTooltip="Support the author on GitHub"
|
||||
>
|
||||
<img class="funding-icon" src="assets/images/github_logo_small.png" />
|
||||
</a>
|
||||
<a *ngIf="listItem.addon.customFundingLink" appExternalLink [href]="listItem.addon.customFundingLink"
|
||||
matTooltip="Support this author">
|
||||
<a
|
||||
*ngIf="listItem.addon.customFundingLink"
|
||||
appExternalLink
|
||||
[href]="listItem.addon.customFundingLink"
|
||||
matTooltip="Support this author"
|
||||
>
|
||||
<img class="funding-icon" src="assets/images/custom_funding_logo_small.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="addon-version row align-items-center" [ngClass]="{ ignored: listItem.isIgnored }">
|
||||
<div class="addon-version text-2 row align-items-center" [ngClass]="{ ignored: listItem.isIgnored }">
|
||||
<div *ngIf="this.listItem.isAutoUpdate === true" class="mr-2">
|
||||
<mat-icon class="auto-update-icon" [matTooltip]="'PAGES.MY_ADDONS.TABLE.AUTO_UPDATE_ICON_TOOLTIP' | translate"
|
||||
svgIcon="far:clock">
|
||||
<mat-icon
|
||||
class="auto-update-icon text-2"
|
||||
[matTooltip]="'PAGES.MY_ADDONS.TABLE.AUTO_UPDATE_ICON_TOOLTIP' | translate"
|
||||
svgIcon="far:clock"
|
||||
>
|
||||
</mat-icon>
|
||||
</div>
|
||||
<div *ngIf="hasRequiredDependencies()" class=" mr-2"
|
||||
[matTooltip]="'COMMON.DEPENDENCY.TOOLTIP' | translate:dependencyTooltip">
|
||||
<div
|
||||
*ngIf="hasRequiredDependencies()"
|
||||
class="mr-2"
|
||||
[matTooltip]="'COMMON.DEPENDENCY.TOOLTIP' | translate: dependencyTooltip"
|
||||
>
|
||||
<mat-icon class="auto-update-icon" svgIcon="fas:link"></mat-icon>
|
||||
</div>
|
||||
{{ listItem.addon.installedVersion }}
|
||||
<div class="update-available row"
|
||||
*ngIf="showUpdateToVersion && listItem.addon.latestVersion !== listItem.addon.installedVersion">
|
||||
<div
|
||||
class="update-available row"
|
||||
*ngIf="showUpdateToVersion && listItem.addon.latestVersion !== listItem.addon.installedVersion"
|
||||
>
|
||||
<mat-icon class="upgrade-icon" svgIcon="fas:play"></mat-icon>
|
||||
<div>{{ listItem.addon.latestVersion }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
margin-right: 11px;
|
||||
|
||||
.addon-logo-container {
|
||||
background-color: $dark-2;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
@@ -25,14 +24,12 @@
|
||||
}
|
||||
|
||||
.addon-logo-letter {
|
||||
color: $white-4;
|
||||
font-size: 2em;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.channel {
|
||||
background: $dark-4;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: 0.8em;
|
||||
@@ -51,21 +48,15 @@
|
||||
// word-break: break-all;
|
||||
white-space: normal;
|
||||
text-decoration: none;
|
||||
color: $white-1;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: $white-2;
|
||||
}
|
||||
|
||||
&.ignored {
|
||||
color: $white-4;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-version {
|
||||
color: $white-2;
|
||||
|
||||
.update-available {
|
||||
color: $artifact;
|
||||
@@ -78,14 +69,11 @@
|
||||
.auto-update-icon {
|
||||
height: 11px;
|
||||
width: 11px;
|
||||
//vertical-align: text-bottom;
|
||||
color: $white-1;
|
||||
}
|
||||
|
||||
.addon-funding {
|
||||
a {
|
||||
margin-right: 1em;
|
||||
color: $white-1;
|
||||
}
|
||||
.funding-icon {
|
||||
width: 15px;
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<div>
|
||||
{{ "PAGES.OPTIONS.APPLICATION.SET_LANGUAGE_LABEL" | translate }}
|
||||
</div>
|
||||
<small class="hint">{{ "PAGES.OPTIONS.APPLICATION.SET_LANGUAGE_DESCRIPTION" | translate }}</small>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.SET_LANGUAGE_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-form-field class="light-select">
|
||||
<mat-form-field class="">
|
||||
<mat-label>{{ "PAGES.OPTIONS.APPLICATION.CURRENT_LANGUAGE_LABEL" | translate }}</mat-label>
|
||||
<mat-select [value]="currentLanguage" (selectionChange)="onCurrentLanguageChange($event)">
|
||||
<mat-option *ngFor="let language of languages" [value]="language.localeId">
|
||||
@@ -21,6 +21,25 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- THEME -->
|
||||
<div class="toggle">
|
||||
<div class="row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>
|
||||
{{ "PAGES.OPTIONS.APPLICATION.THEME_LABEL" | translate }}
|
||||
</div>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.THEME_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-form-field class="">
|
||||
<mat-label>{{ "PAGES.OPTIONS.APPLICATION.THEME_LABEL" | translate }}</mat-label>
|
||||
<mat-select [(value)]="wowupService.currentTheme">
|
||||
<mat-option *ngFor="let theme of themes" [value]="theme.class">
|
||||
{{ theme.display | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TELEMETRY -->
|
||||
<div class="toggle">
|
||||
<div class="row align-items-center">
|
||||
@@ -31,7 +50,7 @@
|
||||
</div>
|
||||
<mat-slide-toggle [checked]="telemetryEnabled" (change)="onTelemetryChange($event)"></mat-slide-toggle>
|
||||
</div>
|
||||
<small class="hint">
|
||||
<small class="text-2">
|
||||
{{ "PAGES.OPTIONS.APPLICATION.TELEMETRY_DESCRIPTION" | translate }}
|
||||
</small>
|
||||
</div>
|
||||
@@ -43,11 +62,9 @@
|
||||
{{ "PAGES.OPTIONS.APPLICATION.MINIMIZE_ON_CLOSE_LABEL" | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<mat-slide-toggle [(checked)]="collapseToTray" (change)="onCollapseChange($event)" appUserActionTracker
|
||||
category="Options" action="CollapseToTray" [label]="collapseToTray">
|
||||
</mat-slide-toggle>
|
||||
<mat-slide-toggle [(checked)]="collapseToTray" (change)="onCollapseChange($event)"> </mat-slide-toggle>
|
||||
</div>
|
||||
<small class="hint">{{ minimizeOnCloseDescription }}</small>
|
||||
<small class="text-2">{{ minimizeOnCloseDescription }}</small>
|
||||
</div>
|
||||
<!-- SYSTEM NOTIFICATIONS -->
|
||||
<div class="toggle">
|
||||
@@ -57,12 +74,13 @@
|
||||
{{ "PAGES.OPTIONS.APPLICATION.ENABLE_SYSTEM_NOTIFICATIONS_LABEL" | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<mat-slide-toggle [checked]="wowupService.enableSystemNotifications"
|
||||
(change)="onEnableSystemNotifications($event)" appUserActionTracker category="Options"
|
||||
action="EnableSystemNotifications" [label]="wowupService.enableSystemNotifications">
|
||||
<mat-slide-toggle
|
||||
[checked]="wowupService.enableSystemNotifications"
|
||||
(change)="onEnableSystemNotifications($event)"
|
||||
>
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<small class="hint">
|
||||
<small class="text-2">
|
||||
{{ "PAGES.OPTIONS.APPLICATION.ENABLE_SYSTEM_NOTIFICATIONS_DESCRIPTION" | translate }}
|
||||
</small>
|
||||
</div>
|
||||
@@ -74,11 +92,10 @@
|
||||
{{ "PAGES.OPTIONS.APPLICATION.USE_HARDWARE_ACCELERATION_LABEL" | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<mat-slide-toggle [(checked)]="useHardwareAcceleration" (change)="onUseHardwareAccelerationChange($event)"
|
||||
appUserActionTracker category="Options" action="UseHardwareAcceleration" [label]="useHardwareAcceleration">
|
||||
<mat-slide-toggle [(checked)]="useHardwareAcceleration" (change)="onUseHardwareAccelerationChange($event)">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
<small class="hint">{{ "PAGES.OPTIONS.APPLICATION.USE_HARDWARE_ACCELERATION_DESCRIPTION" | translate }}</small>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.USE_HARDWARE_ACCELERATION_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<!-- START WITH SYSTEM -->
|
||||
<div class="toggle">
|
||||
@@ -87,11 +104,13 @@
|
||||
<div>
|
||||
{{ "PAGES.OPTIONS.APPLICATION.START_WITH_SYSTEM_LABEL" | translate }}
|
||||
</div>
|
||||
<small class="hint">{{ "PAGES.OPTIONS.APPLICATION.START_WITH_SYSTEM_DESCRIPTION" | translate }}</small>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.START_WITH_SYSTEM_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="startWithSystem" [(checked)]="startWithSystem"
|
||||
(change)="onStartWithSystemChange($event)" appUserActionTracker category="Options" action="StartWithSystem"
|
||||
[label]="startWithSystem">
|
||||
<mat-slide-toggle
|
||||
[(ngModel)]="startWithSystem"
|
||||
[(checked)]="startWithSystem"
|
||||
(change)="onStartWithSystemChange($event)"
|
||||
>
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,12 +121,15 @@
|
||||
<div>
|
||||
{{ "PAGES.OPTIONS.APPLICATION.START_MINIMIZED_LABEL" | translate }}
|
||||
</div>
|
||||
<small class="hint">{{ "PAGES.OPTIONS.APPLICATION.START_MINIMIZED_DESCRIPTION" | translate }}</small>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.APPLICATION.START_MINIMIZED_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="startMinimized" [(disabled)]="!startWithSystem" [(checked)]="startMinimized"
|
||||
(change)="onStartMinimizedChange($event)" appUserActionTracker category="Options" action="StartMinimized"
|
||||
[label]="startMinimized">
|
||||
<mat-slide-toggle
|
||||
[(ngModel)]="startMinimized"
|
||||
[(disabled)]="!startWithSystem"
|
||||
[(checked)]="startMinimized"
|
||||
(change)="onStartMinimizedChange($event)"
|
||||
>
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,10 +3,20 @@ import { MatDialog } from "@angular/material/dialog";
|
||||
import { MatSelectChange } from "@angular/material/select";
|
||||
import { MatSlideToggleChange } from "@angular/material/slide-toggle";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { ElectronService } from "app/services";
|
||||
import { AnalyticsService } from "app/services/analytics/analytics.service";
|
||||
import { WowUpService } from "app/services/wowup/wowup.service";
|
||||
import { ElectronService } from "../../services";
|
||||
import { AnalyticsService } from "../../services/analytics/analytics.service";
|
||||
import { SessionService } from "../../services/session/session.service";
|
||||
import { WowUpService } from "../../services/wowup/wowup.service";
|
||||
import { Theme } from "../../models/wowup/theme";
|
||||
import { ConfirmDialogComponent } from "../confirm-dialog/confirm-dialog.component";
|
||||
import {
|
||||
ALLIANCE_LIGHT_THEME,
|
||||
ALLIANCE_THEME,
|
||||
DEFAULT_LIGHT_THEME,
|
||||
DEFAULT_THEME,
|
||||
HORDE_LIGHT_THEME,
|
||||
HORDE_THEME,
|
||||
} from "../../../common/constants";
|
||||
|
||||
interface LocaleListItem {
|
||||
localeId: string;
|
||||
@@ -39,11 +49,21 @@ export class OptionsAppSectionComponent implements OnInit {
|
||||
{ localeId: "zh", label: "简体中文" },
|
||||
];
|
||||
|
||||
public themes: Theme[] = [
|
||||
{ display: "APP.THEME.DEFAULT", class: DEFAULT_THEME },
|
||||
{ display: "APP.THEME.DEFAULT_LIGHT", class: DEFAULT_LIGHT_THEME },
|
||||
{ display: "APP.THEME.ALLIANCE", class: ALLIANCE_THEME },
|
||||
{ display: "APP.THEME.ALLIANCE_LIGHT", class: ALLIANCE_LIGHT_THEME },
|
||||
{ display: "APP.THEME.HORDE", class: HORDE_THEME },
|
||||
{ display: "APP.THEME.HORDE_LIGHT", class: HORDE_LIGHT_THEME },
|
||||
];
|
||||
|
||||
constructor(
|
||||
private _analyticsService: AnalyticsService,
|
||||
private _dialog: MatDialog,
|
||||
private _electronService: ElectronService,
|
||||
private _translateService: TranslateService,
|
||||
public sessionService: SessionService,
|
||||
public wowupService: WowUpService
|
||||
) {}
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
<div class="row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>{{ "PAGES.OPTIONS.DEBUG.LOG_FILES_LABEL" | translate }}</div>
|
||||
<small class="hint">{{
|
||||
"PAGES.OPTIONS.DEBUG.LOG_FILES_DESCRIPTION" | translate
|
||||
}}</small>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.DEBUG.LOG_FILES_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
@@ -30,9 +28,7 @@
|
||||
<div class="row align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div>{{ "PAGES.OPTIONS.DEBUG.DEBUG_DATA_LABEL" | translate }}</div>
|
||||
<small class="hint">{{
|
||||
"PAGES.OPTIONS.DEBUG.DEBUG_DATA_DESCRIPTION" | translate
|
||||
}}</small>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.DEBUG.DEBUG_DATA_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
.container {
|
||||
padding: 1em;
|
||||
|
||||
.hint {
|
||||
color: $white-3;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,7 @@ import { WowUpService } from "app/services/wowup/wowup.service";
|
||||
styleUrls: ["./options-debug-section.component.scss"],
|
||||
})
|
||||
export class OptionsDebugSectionComponent implements OnInit {
|
||||
constructor(
|
||||
private _addonService: AddonService,
|
||||
private _wowupService: WowUpService
|
||||
) {}
|
||||
constructor(private _addonService: AddonService, private _wowupService: WowUpService) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
{{ "PAGES.OPTIONS.WOW.RESCAN_CLIENTS_BUTTON" | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<app-wow-client-options
|
||||
class="client-section"
|
||||
*ngFor="let clientType of wowClientTypes"
|
||||
[clientType]="clientType"
|
||||
>
|
||||
<app-wow-client-options class="client-section" *ngFor="let clientType of wowClientTypes" [clientType]="clientType">
|
||||
</app-wow-client-options>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,17 +21,12 @@ export class OptionsWowSectionComponent implements OnInit {
|
||||
public wowUpReleaseChannels: {
|
||||
type: WowUpReleaseChannelType;
|
||||
name: string;
|
||||
}[] = getEnumList(WowUpReleaseChannelType).map(
|
||||
(type: WowUpReleaseChannelType) => ({
|
||||
type,
|
||||
name: getEnumName(WowUpReleaseChannelType, type),
|
||||
})
|
||||
);
|
||||
}[] = getEnumList(WowUpReleaseChannelType).map((type: WowUpReleaseChannelType) => ({
|
||||
type,
|
||||
name: getEnumName(WowUpReleaseChannelType, type),
|
||||
}));
|
||||
|
||||
constructor(
|
||||
private _warcraftService: WarcraftService,
|
||||
private _wowupService: WowUpService
|
||||
) {}
|
||||
constructor(private _warcraftService: WarcraftService, private _wowupService: WowUpService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.wowUpReleaseChannel = this._wowupService.wowUpReleaseChannel;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="addon-column row align-items-center">
|
||||
<div class="thumbnail-container">
|
||||
<div *ngIf="hasThumbnail === true" class="addon-logo-container"
|
||||
<div class="thumbnail-container bg-secondary-3">
|
||||
<div *ngIf="hasThumbnail === true" class="addon-logo-container "
|
||||
[style.backgroundImage]="'url(' + addon.thumbnailUrl + ')'"></div>
|
||||
<div *ngIf="hasThumbnail === false" class="addon-logo-container">
|
||||
<div class="addon-logo-letter">
|
||||
<div class="addon-logo-letter text-3">
|
||||
{{ thumbnailLetter }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -13,16 +13,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="addon-title mat-subheading-2" (click)="viewDetails()">{{
|
||||
addon.name
|
||||
}}</a>
|
||||
<div class="addon-version">
|
||||
<a class="addon-title hover-text-2 mat-subheading-2" (click)="viewDetails()">{{ addon.name }}</a>
|
||||
<div class="addon-version text-2">
|
||||
<span *ngIf="hasRequiredDependencies()" class="dependency-icon mr-1"
|
||||
[matTooltip]="'COMMON.DEPENDENCY.TOOLTIP' | translate:dependencyTooltip">
|
||||
[matTooltip]="'COMMON.DEPENDENCY.TOOLTIP' | translate: dependencyTooltip">
|
||||
<mat-icon svgIcon="fas:link"></mat-icon>
|
||||
</span>
|
||||
{{ addon | getAddonListItemFileProp: "version":channel }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,7 +8,6 @@
|
||||
margin-right: 11px;
|
||||
|
||||
.addon-logo-container {
|
||||
background-color: $dark-2;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
@@ -41,7 +40,6 @@
|
||||
}
|
||||
|
||||
.addon-logo-letter {
|
||||
color: $white-4;
|
||||
font-size: 2em;
|
||||
font-weight: 400;
|
||||
}
|
||||
@@ -50,7 +48,6 @@
|
||||
.addon-title {
|
||||
white-space: normal;
|
||||
text-decoration: none;
|
||||
color: $white-1;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
@@ -67,7 +64,6 @@
|
||||
.mat-icon {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
color: $white-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,9 @@ import { PotentialAddonTableColumnComponent } from "./potential-addon-table-colu
|
||||
|
||||
describe("PotentialAddonTableColumnComponent", () => {
|
||||
it("should create", () => {
|
||||
inject(
|
||||
[GetAddonListItemFilePropPipe],
|
||||
(propPipe: GetAddonListItemFilePropPipe) => {
|
||||
const pipe = new PotentialAddonTableColumnComponent(propPipe);
|
||||
expect(pipe).toBeTruthy();
|
||||
}
|
||||
);
|
||||
inject([GetAddonListItemFilePropPipe], (propPipe: GetAddonListItemFilePropPipe) => {
|
||||
const pipe = new PotentialAddonTableColumnComponent(propPipe);
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
class="progress-button"
|
||||
(click)="onClickButton($event)"
|
||||
[disabled]="disable"
|
||||
>
|
||||
<mat-progress-bar
|
||||
class="progress-bar"
|
||||
color="secondary"
|
||||
[value]="value"
|
||||
*ngIf="showProgress"
|
||||
></mat-progress-bar>
|
||||
<button mat-flat-button color="primary" class="progress-button" (click)="onClickButton($event)" [disabled]="disable">
|
||||
<mat-progress-bar class="progress-bar" color="secondary" [value]="value" *ngIf="showProgress"></mat-progress-bar>
|
||||
<ng-content></ng-content>
|
||||
</button>
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
} from "@angular/core";
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-progress-button",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="busy-container">
|
||||
<div class="busy-container text-1">
|
||||
<mat-spinner [diameter]="50"></mat-spinner>
|
||||
<pre>{{ message || defaultMessage }}</pre>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 2em;
|
||||
color: $white-1;
|
||||
}
|
||||
|
||||
pre {
|
||||
|
||||
@@ -14,10 +14,8 @@ export class ProgressSpinnerComponent implements OnInit {
|
||||
constructor(private _translateService: TranslateService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._translateService
|
||||
.get("COMMON.PROGRESS_SPINNER.LOADING")
|
||||
.subscribe((translatedStr) => {
|
||||
this.defaultMessage = translatedStr;
|
||||
});
|
||||
this._translateService.get("COMMON.PROGRESS_SPINNER.LOADING").subscribe((translatedStr) => {
|
||||
this.defaultMessage = translatedStr;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,49 @@
|
||||
<div class="titlebar bg-purple-1 text-light" [ngClass]="{
|
||||
<div
|
||||
class="titlebar bg-primary text-light"
|
||||
[ngClass]="{
|
||||
mac: electronService.isMac,
|
||||
windows: electronService.isWin,
|
||||
linux: electronService.isLinux
|
||||
}">
|
||||
<div class="titlebar-drag-region" (dblclick)="onDblClick()"></div>
|
||||
<div class="window-logo-container" *ngIf="electronService.isWin">
|
||||
<img src="assets/wowup_logo_512np.png" />
|
||||
}"
|
||||
>
|
||||
<div class="theme-logo">
|
||||
<img [src]="getLogoPath()" />
|
||||
</div>
|
||||
<div class="titlebar-drag-region" (dblclick)="onDblClick()"></div>
|
||||
<!-- <div class="window-logo-container" *ngIf="electronService.isWin">
|
||||
<img src="assets/wowup_logo_512np.png" />
|
||||
</div> -->
|
||||
<div class="title-container">
|
||||
<div>WowUp.io</div>
|
||||
</div>
|
||||
<div *ngIf="isMac" class="window-control-container pointer">
|
||||
<div class="window-control" (click)="onClickDebug()">
|
||||
<div class="window-control hover-primary-2" (click)="onClickDebug()">
|
||||
<mat-icon class="debug-button pointer" svgIcon="fas:bug"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="electronService.isWin || electronService.isLinux" class="window-control-container row align-items-center">
|
||||
<div class="window-control" (click)="onClickDebug()">
|
||||
<div class="window-control hover-primary-2" (click)="onClickDebug()">
|
||||
<mat-icon class="debug-button" svgIcon="fas:bug"></mat-icon>
|
||||
</div>
|
||||
<div class="window-control" (click)="electronService.minimizeWindow()">
|
||||
<div class="window-control hover-primary-2" (click)="electronService.minimizeWindow()">
|
||||
<img src="assets/chrome-minimize.svg" />
|
||||
</div>
|
||||
<div class="window-control" *ngIf="isMaximized === false" (click)="electronService.maximizeWindow()">
|
||||
<div
|
||||
class="window-control hover-primary-2"
|
||||
*ngIf="isMaximized === false"
|
||||
(click)="electronService.maximizeWindow()"
|
||||
>
|
||||
<img src="assets/chrome-maximize.svg" />
|
||||
</div>
|
||||
<div class="window-control" *ngIf="isMaximized === true" (click)="electronService.unmaximizeWindow()">
|
||||
<div
|
||||
class="window-control hover-primary-2"
|
||||
*ngIf="isMaximized === true"
|
||||
(click)="electronService.unmaximizeWindow()"
|
||||
>
|
||||
<img src="assets/chrome-restore.svg" />
|
||||
</div>
|
||||
<div class="window-control" (click)="onClickClose()">
|
||||
<div class="window-control hover-primary-2" (click)="onClickClose()">
|
||||
<img src="assets/chrome-close.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,14 +5,39 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin-top: 4px;
|
||||
|
||||
.debug-button {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.theme-logo {
|
||||
z-index: 1;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 4px;
|
||||
height: 82px;
|
||||
opacity: 0.3;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
position: relative;
|
||||
left: 0;
|
||||
height: 150%;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mac {
|
||||
height: 22px;
|
||||
|
||||
.theme-logo {
|
||||
height: 70px;
|
||||
right: 4px;
|
||||
left: initial;
|
||||
}
|
||||
}
|
||||
.windows,
|
||||
.linux {
|
||||
@@ -64,7 +89,6 @@
|
||||
z-index: 2500;
|
||||
|
||||
&:hover {
|
||||
background-color: $purple-2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Component, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import {
|
||||
ALLIANCE_LIGHT_THEME,
|
||||
ALLIANCE_THEME,
|
||||
DEFAULT_LIGHT_THEME,
|
||||
DEFAULT_THEME,
|
||||
HORDE_LIGHT_THEME,
|
||||
HORDE_THEME,
|
||||
} from "common/constants";
|
||||
import { platform } from "os";
|
||||
import { Subscription } from "rxjs";
|
||||
import { AppConfig } from "../../../environments/environment";
|
||||
@@ -21,16 +29,10 @@ export class TitlebarComponent implements OnInit, OnDestroy {
|
||||
|
||||
private _subscriptions: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
public electronService: ElectronService,
|
||||
private _wowUpService: WowUpService,
|
||||
private _ngZone: NgZone
|
||||
) {
|
||||
const windowMaximizedSubscription = this.electronService.windowMaximized$.subscribe(
|
||||
(maximized) => {
|
||||
this._ngZone.run(() => (this.isMaximized = maximized));
|
||||
}
|
||||
);
|
||||
constructor(public electronService: ElectronService, private _wowUpService: WowUpService, private _ngZone: NgZone) {
|
||||
const windowMaximizedSubscription = this.electronService.windowMaximized$.subscribe((maximized) => {
|
||||
this._ngZone.run(() => (this.isMaximized = maximized));
|
||||
});
|
||||
|
||||
this._subscriptions = [windowMaximizedSubscription];
|
||||
}
|
||||
@@ -41,6 +43,21 @@ export class TitlebarComponent implements OnInit, OnDestroy {
|
||||
this._subscriptions.forEach((subscription) => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
getLogoPath() {
|
||||
switch (this._wowUpService.currentTheme) {
|
||||
case HORDE_THEME:
|
||||
case HORDE_LIGHT_THEME:
|
||||
return "assets/images/horde-1.png";
|
||||
case ALLIANCE_THEME:
|
||||
case ALLIANCE_LIGHT_THEME:
|
||||
return "assets/images/alliance-1.png";
|
||||
case DEFAULT_THEME:
|
||||
case DEFAULT_LIGHT_THEME:
|
||||
default:
|
||||
return "assets/images/wowup-white-1.png";
|
||||
}
|
||||
}
|
||||
|
||||
onClickClose() {
|
||||
if (this._wowUpService.collapseToTray) {
|
||||
this.electronService.hideWindow();
|
||||
@@ -57,10 +74,7 @@ export class TitlebarComponent implements OnInit, OnDestroy {
|
||||
const win = this.electronService.remote.getCurrentWindow();
|
||||
|
||||
if (this.isMac) {
|
||||
const action = this.electronService.remote.systemPreferences.getUserDefault(
|
||||
"AppleActionOnDoubleClick",
|
||||
"string"
|
||||
);
|
||||
const action = this.electronService.remote.systemPreferences.getUserDefault("AppleActionOnDoubleClick", "string");
|
||||
if (action === "Maximize") {
|
||||
if (win.isMaximized()) {
|
||||
win.unmaximize();
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<mat-divider></mat-divider>
|
||||
<div class="title">{{ clientTypeName }}</div>
|
||||
<div class="title">{{ clientTypeName | translate }}</div>
|
||||
<div class="row">
|
||||
<mat-form-field class="grow folder-input">
|
||||
<mat-label>{{
|
||||
"PAGES.OPTIONS.WOW.CLIENT_TYPE_PATH_LABEL"
|
||||
| translate: { clientTypeName: clientTypeName }
|
||||
"PAGES.OPTIONS.WOW.CLIENT_TYPE_PATH_LABEL" | translate: { clientTypeName: (clientTypeName | translate) }
|
||||
}}</mat-label>
|
||||
<input matInput disabled [value]="clientLocation" />
|
||||
<mat-hint>
|
||||
<mat-hint class="text-2">
|
||||
{{
|
||||
"PAGES.OPTIONS.WOW.CLIENT_TYPE_INPUT_HINT"
|
||||
| translate
|
||||
@@ -34,10 +33,8 @@
|
||||
<div class="grow">
|
||||
<p>{{ "PAGES.OPTIONS.WOW.DEFAULT_ADDON_CHANNEL_LABEL" | translate }}</p>
|
||||
</div>
|
||||
<mat-form-field class="light-select">
|
||||
<mat-label>{{
|
||||
"PAGES.OPTIONS.WOW.DEFAULT_ADDON_CHANNEL_SELECT_LABEL" | translate
|
||||
}}</mat-label>
|
||||
<mat-form-field class="">
|
||||
<mat-label>{{ "PAGES.OPTIONS.WOW.DEFAULT_ADDON_CHANNEL_SELECT_LABEL" | translate }}</mat-label>
|
||||
<mat-select
|
||||
[(value)]="selectedAddonChannelType"
|
||||
(selectionChange)="onDefaultAddonChannelChange($event)"
|
||||
@@ -46,20 +43,16 @@
|
||||
[action]="clientTypeName + 'DefaultChannel'"
|
||||
[label]="selectedAddonChannelType"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let channel of addonChannelInfos"
|
||||
[value]="channel.type"
|
||||
>{{ channel.name | translate }}</mat-option
|
||||
>
|
||||
<mat-option *ngFor="let channel of addonChannelInfos" [value]="channel.type">{{
|
||||
channel.name | translate
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="grow">
|
||||
<p>{{ "PAGES.OPTIONS.WOW.AUTO_UPDATE_LABEL" | translate }}</p>
|
||||
<small class="hint">{{
|
||||
"PAGES.OPTIONS.WOW.AUTO_UPDATE_DESCRIPTION" | translate
|
||||
}}</small>
|
||||
<small class="text-2">{{ "PAGES.OPTIONS.WOW.AUTO_UPDATE_DESCRIPTION" | translate }}</small>
|
||||
</div>
|
||||
<mat-slide-toggle
|
||||
[(checked)]="clientAutoUpdate"
|
||||
|
||||
@@ -24,13 +24,7 @@ describe("WowClientOptionsComponent", () => {
|
||||
|
||||
it("should create", () => {
|
||||
inject(
|
||||
[
|
||||
MatDialog,
|
||||
ElectronService,
|
||||
WarcraftService,
|
||||
WowUpService,
|
||||
ChangeDetectorRef,
|
||||
],
|
||||
[MatDialog, ElectronService, WarcraftService, WowUpService, ChangeDetectorRef],
|
||||
(
|
||||
matDialog: MatDialog,
|
||||
electronService: ElectronService,
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from "@angular/core";
|
||||
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from "@angular/core";
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { MatSelectChange } from "@angular/material/select";
|
||||
import { MatSlideToggleChange } from "@angular/material/slide-toggle";
|
||||
@@ -50,33 +44,23 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
this.addonChannelInfos = this.getAddonChannelInfos();
|
||||
|
||||
const warcraftProductSubscription = this._warcraftService.products$.subscribe(
|
||||
(products) => {
|
||||
const product = products.find((p) => p.clientType === this.clientType);
|
||||
if (product) {
|
||||
this.clientLocation = product.location;
|
||||
this._cdRef.detectChanges();
|
||||
}
|
||||
const warcraftProductSubscription = this._warcraftService.products$.subscribe((products) => {
|
||||
const product = products.find((p) => p.clientType === this.clientType);
|
||||
if (product) {
|
||||
this.clientLocation = product.location;
|
||||
this._cdRef.detectChanges();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.subscriptions.push(warcraftProductSubscription);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedAddonChannelType = this._wowupService.getDefaultAddonChannel(
|
||||
this.clientType
|
||||
);
|
||||
this.clientAutoUpdate = this._wowupService.getDefaultAutoUpdate(
|
||||
this.clientType
|
||||
);
|
||||
this.clientTypeName = getEnumName(WowClientType, this.clientType);
|
||||
this.clientFolderName = this._warcraftService.getClientFolderName(
|
||||
this.clientType
|
||||
);
|
||||
this.clientLocation = this._warcraftService.getClientLocation(
|
||||
this.clientType
|
||||
);
|
||||
this.selectedAddonChannelType = this._wowupService.getDefaultAddonChannel(this.clientType);
|
||||
this.clientAutoUpdate = this._wowupService.getDefaultAutoUpdate(this.clientType);
|
||||
this.clientTypeName = `COMMON.CLIENT_TYPES.${getEnumName(WowClientType, this.clientType).toUpperCase()}`;
|
||||
this.clientFolderName = this._warcraftService.getClientFolderName(this.clientType);
|
||||
this.clientLocation = this._warcraftService.getClientLocation(this.clientType);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -108,14 +92,10 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private async selectWowClientPath(
|
||||
clientType: WowClientType
|
||||
): Promise<string> {
|
||||
const dialogResult = await this._electronService.remote.dialog.showOpenDialog(
|
||||
{
|
||||
properties: ["openDirectory"],
|
||||
}
|
||||
);
|
||||
private async selectWowClientPath(clientType: WowClientType): Promise<string> {
|
||||
const dialogResult = await this._electronService.remote.dialog.showOpenDialog({
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (dialogResult.canceled) {
|
||||
return "";
|
||||
@@ -129,28 +109,15 @@ export class WowClientOptionsComponent implements OnInit, OnDestroy {
|
||||
|
||||
console.log("dialogResult", selectedPath);
|
||||
|
||||
const clientRelativePath = this._warcraftService.getClientRelativePath(
|
||||
clientType,
|
||||
selectedPath
|
||||
);
|
||||
const clientRelativePath = this._warcraftService.getClientRelativePath(clientType, selectedPath);
|
||||
|
||||
if (
|
||||
this._warcraftService.setWowFolderPath(clientType, clientRelativePath)
|
||||
) {
|
||||
if (this._warcraftService.setWowFolderPath(clientType, clientRelativePath)) {
|
||||
return clientRelativePath;
|
||||
}
|
||||
|
||||
const clientFolderName = this._warcraftService.getClientFolderName(
|
||||
clientType
|
||||
);
|
||||
const clientExecutableName = this._warcraftService.getExecutableName(
|
||||
clientType
|
||||
);
|
||||
const clientExecutablePath = path.join(
|
||||
clientRelativePath,
|
||||
clientFolderName,
|
||||
clientExecutableName
|
||||
);
|
||||
const clientFolderName = this._warcraftService.getClientFolderName(clientType);
|
||||
const clientExecutableName = this._warcraftService.getExecutableName(clientType);
|
||||
const clientExecutablePath = path.join(clientRelativePath, clientFolderName, clientExecutableName);
|
||||
const dialogRef = this._dialog.open(AlertDialogComponent, {
|
||||
data: {
|
||||
title: `Alert`,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { ExternalLinkDirective } from "./external-link.directive";
|
||||
|
||||
describe("ExternalLinkDirective", () => {
|
||||
let directive: ExternalLinkDirective;
|
||||
let fixture: ComponentFixture<ExternalLinkDirective>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ExternalLinkDirective],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ExternalLinkDirective],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
it("should create an instance", () => {
|
||||
fixture = TestBed.createComponent(ExternalLinkDirective);
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
HttpHandler,
|
||||
HttpInterceptor,
|
||||
HttpRequest,
|
||||
} from "@angular/common/http";
|
||||
import { HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http";
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
@Injectable()
|
||||
|
||||
5
wowup-electron/src/app/models/wowup/select-item.ts
Normal file
5
wowup-electron/src/app/models/wowup/select-item.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Attempt to make a simple container for localizing select items
|
||||
export interface SelectItem<T> {
|
||||
display: string;
|
||||
value: T;
|
||||
}
|
||||
4
wowup-electron/src/app/models/wowup/theme.ts
Normal file
4
wowup-electron/src/app/models/wowup/theme.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Theme {
|
||||
display: string;
|
||||
class: string;
|
||||
}
|
||||
@@ -3,11 +3,14 @@
|
||||
windows: electronService.isWin,
|
||||
linux: electronService.isLinux
|
||||
}">
|
||||
<div class="theme-logo">
|
||||
<img [src]="getLogoPath()" />
|
||||
</div>
|
||||
<div class="about">
|
||||
<div class="header">
|
||||
<div class="header text-1">
|
||||
<img class="logo" src="assets/wowup_logo_512np.png" />
|
||||
<h2>{{ "PAGES.ABOUT.TITLE" | translate }}</h2>
|
||||
<div class="version">v{{ version }}</div>
|
||||
<div class="version text-2">v{{ version }}</div>
|
||||
<div class="link-container">
|
||||
<a appExternalLink mat-stroked-button href="https://wowup.io" appUserActionTracker category="About"
|
||||
action="ViewWebsite">
|
||||
@@ -15,14 +18,14 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="changelog-container">
|
||||
<div class="changelog-container text-1">
|
||||
<h2>
|
||||
{{ "PAGES.ABOUT.CHANGE_LOG_SECTION_LABEL" | translate }}
|
||||
</h2>
|
||||
<ul class="change-log-list">
|
||||
<li class="changelog" *ngFor="let cl of changeLogs">
|
||||
<div class="version mat-subheading-2">{{ cl.Version }}</div>
|
||||
<pre *ngIf="cl.Description" class="description selectable">{{ cl.Description }}</pre>
|
||||
<li class="changelog bg-secondary-4 border-primary" *ngFor="let cl of changeLogs">
|
||||
<div class="version mat-subheading-2">{{ cl.Version }}</div>
|
||||
<pre *ngIf="cl.Description" class="description selectable">{{ cl.Description }}</pre>
|
||||
<pre *ngIf="cl.changes" class="description selectable">{{ formatChanges(cl) }}</pre>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -6,6 +6,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.theme-logo {
|
||||
opacity: 0.02;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
img {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
.about-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -15,7 +31,6 @@
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
color: $white-1;
|
||||
|
||||
.logo {
|
||||
width: 150px;
|
||||
@@ -25,10 +40,6 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.version {
|
||||
color: $white-3;
|
||||
}
|
||||
|
||||
.link-container {
|
||||
margin-top: 1em;
|
||||
}
|
||||
@@ -40,7 +51,6 @@
|
||||
|
||||
.changelog-container {
|
||||
padding: 1em;
|
||||
color: $white-1;
|
||||
|
||||
.change-log-list {
|
||||
padding-left: 0;
|
||||
@@ -49,14 +59,9 @@
|
||||
.changelog {
|
||||
list-style-type: none;
|
||||
margin: 1em 0;
|
||||
border-left: 3px solid $purple-1;
|
||||
border-left-width: 3px;
|
||||
border-left-style: solid;
|
||||
padding: 0.5em 1em 1em 1em;
|
||||
background-color: $dark-4;
|
||||
|
||||
.version {
|
||||
// font-size: 1em;
|
||||
// font-weight: bold;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { AboutComponent } from "./about.component";
|
||||
|
||||
describe("AboutComponent", () => {
|
||||
let component: AboutComponent;
|
||||
let fixture: ComponentFixture<AboutComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AboutComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AboutComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AboutComponent);
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core";
|
||||
import {
|
||||
ALLIANCE_LIGHT_THEME,
|
||||
ALLIANCE_THEME,
|
||||
DEFAULT_LIGHT_THEME,
|
||||
DEFAULT_THEME,
|
||||
HORDE_LIGHT_THEME,
|
||||
HORDE_THEME,
|
||||
} from "common/constants";
|
||||
import { remote } from "electron";
|
||||
import * as ChangeLogJson from "../../../assets/changelog.json";
|
||||
import { ChangeLog } from "../../models/wowup/change-log";
|
||||
@@ -17,7 +25,7 @@ export class AboutComponent implements OnInit {
|
||||
public version = "";
|
||||
public changeLogs: ChangeLog[] = ChangeLogJson.ChangeLogs;
|
||||
|
||||
constructor(private wowup: WowUpService, public electronService: ElectronService) {}
|
||||
constructor(private wowUpService: WowUpService, public electronService: ElectronService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.version = remote.app.getVersion();
|
||||
@@ -26,4 +34,22 @@ export class AboutComponent implements OnInit {
|
||||
formatChanges(changeLog: ChangeLog): string {
|
||||
return changeLog.changes.join("\n");
|
||||
}
|
||||
|
||||
getLogoPath() {
|
||||
switch (this.wowUpService.currentTheme) {
|
||||
case HORDE_THEME:
|
||||
return "assets/images/horde-1.png";
|
||||
case HORDE_LIGHT_THEME:
|
||||
return "assets/images/horde-dark-1.png";
|
||||
case ALLIANCE_THEME:
|
||||
return "assets/images/alliance-1.png";
|
||||
case ALLIANCE_LIGHT_THEME:
|
||||
return "assets/images/alliance-dark-1.png";
|
||||
case DEFAULT_LIGHT_THEME:
|
||||
return "assets/images/wowup-dark-1.png";
|
||||
case DEFAULT_THEME:
|
||||
default:
|
||||
return "assets/images/wowup-white-1.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,11 @@
|
||||
<div class="control-container">
|
||||
<div class="select-container">
|
||||
<mat-form-field>
|
||||
<mat-label>{{
|
||||
"PAGES.GET_ADDONS.CLIENT_TYPE_SELECT_LABEL" | translate
|
||||
}}</mat-label>
|
||||
<mat-label>{{ "PAGES.GET_ADDONS.CLIENT_TYPE_SELECT_LABEL" | translate }}</mat-label>
|
||||
<mat-select class="select" [(value)]="selectedClient" (selectionChange)="onClientChange()"
|
||||
[disabled]="isBusy === true">
|
||||
<mat-option [value]="clientType" *ngFor="
|
||||
let clientType of warcraftService.installedClientTypes$ | async
|
||||
">
|
||||
{{ warcraftService.getClientDisplayName(clientType) }}
|
||||
<mat-option [value]="clientType.value" *ngFor="let clientType of warcraftService.installedClientTypesSelectItems$ | async">
|
||||
{{ clientType.display | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@@ -22,9 +18,7 @@
|
||||
<div class="right-container">
|
||||
<div class="search-container">
|
||||
<mat-form-field>
|
||||
<mat-label>{{
|
||||
"PAGES.GET_ADDONS.SEARCH_LABEL" | translate
|
||||
}}</mat-label>
|
||||
<mat-label>{{ "PAGES.GET_ADDONS.SEARCH_LABEL" | translate }}</mat-label>
|
||||
<input matInput type="text" [(ngModel)]="query" (keyup.enter)="onSearch()" />
|
||||
<button mat-button color="accent" *ngIf="query" matSuffix mat-icon-button aria-label="Clear"
|
||||
(click)="onClearSearch()">
|
||||
@@ -49,7 +43,7 @@
|
||||
<app-progress-spinner> </app-progress-spinner>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isBusy === false && dataSource.data.length === 0" class="no-addons-container flex-grow-1">
|
||||
<div *ngIf="isBusy === false && dataSource.data.length === 0" class="no-addons-container flex-grow-1 text-1">
|
||||
<h1>{{ "COMMON.SEARCH.NO_ADDONS" | translate }}</h1>
|
||||
</div>
|
||||
|
||||
@@ -69,9 +63,7 @@
|
||||
|
||||
<ng-container matColumnDef="downloadCount">
|
||||
<th mat-header-cell mat-sort-header *matHeaderCellDef>
|
||||
{{
|
||||
"PAGES.GET_ADDONS.TABLE.DOWNLOAD_COUNT_COLUMN_HEADER" | translate
|
||||
}}
|
||||
{{ "PAGES.GET_ADDONS.TABLE.DOWNLOAD_COUNT_COLUMN_HEADER" | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{ element.downloadCount | downloadCount }}
|
||||
@@ -84,13 +76,10 @@
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<ng-template let-releasedAt [ngTemplateOutletContext]="{
|
||||
$implicit:
|
||||
element
|
||||
| getAddonListItemFileProp: 'releaseDate':defaultAddonChannel
|
||||
$implicit: element | getAddonListItemFileProp: 'releaseDate':defaultAddonChannel
|
||||
}" [ngTemplateOutlet]="t" #t>
|
||||
<div [matTooltip]="
|
||||
'COMMON.DATES.DATETIME_SHORT' | translate: { d: releasedAt }
|
||||
" matTooltipPosition="above" matTooltipShowDelay="500">
|
||||
<div [matTooltip]="'COMMON.DATES.DATETIME_SHORT' | translate: { d: releasedAt }" matTooltipPosition="above"
|
||||
matTooltipShowDelay="500">
|
||||
{{ releasedAt | relativeDuration }}
|
||||
</div>
|
||||
</ng-template>
|
||||
@@ -129,10 +118,10 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true" class="hover-bg-secondary-2"
|
||||
(contextmenu)="onHeaderContext($event); $event.preventDefault()"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns; let i = index" (dblclick)="onDoubleClickRow(row)">
|
||||
</tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns; let i = index" class="hover-bg-secondary-2"
|
||||
(dblclick)="onDoubleClickRow(row)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
|
||||
.no-addons-container {
|
||||
display: flex;
|
||||
color: $white-1;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -64,34 +63,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tr {
|
||||
&:hover {
|
||||
background-color: $dark-1;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-logo-container {
|
||||
background-color: $dark-2;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-right: 0.5em;
|
||||
flex-shrink: 0;
|
||||
|
||||
.addon-logo {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-title {
|
||||
font-weight: 400;
|
||||
font-size: 1.1em;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.cell-padding {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { GetAddonsComponent } from "./get-addons.component";
|
||||
|
||||
describe("GetAddonsComponent", () => {
|
||||
let component: GetAddonsComponent;
|
||||
let fixture: ComponentFixture<GetAddonsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GetAddonsComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GetAddonsComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GetAddonsComponent);
|
||||
|
||||
@@ -138,7 +138,7 @@ export class GetAddonsComponent implements OnInit, OnDestroy {
|
||||
if (this._automaticSort) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.table) {
|
||||
this.table.nativeElement.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="page-container" [ngClass]="{ linux: electronService.isLinux }">
|
||||
<div class="page-container bg-secondary-3" [ngClass]="{ linux: electronService.isLinux }">
|
||||
<div class="tabs">
|
||||
<mat-tab-group
|
||||
mat-align-tabs="center"
|
||||
@@ -8,22 +8,13 @@
|
||||
[(selectedIndex)]="selectedIndex"
|
||||
(selectedIndexChange)="onSelectedIndexChange($event)"
|
||||
>
|
||||
<mat-tab
|
||||
[disabled]="hasWowClient !== true"
|
||||
[label]="'PAGES.HOME.MY_ADDONS_TAB_TITLE' | translate"
|
||||
>
|
||||
<mat-tab [disabled]="hasWowClient !== true" [label]="'PAGES.HOME.MY_ADDONS_TAB_TITLE' | translate">
|
||||
<app-my-addons [tabIndex]="0"></app-my-addons>
|
||||
</mat-tab>
|
||||
<mat-tab
|
||||
[disabled]="hasWowClient !== true"
|
||||
[label]="'PAGES.HOME.GET_ADDONS_TAB_TITLE' | translate"
|
||||
>
|
||||
<mat-tab [disabled]="hasWowClient !== true" [label]="'PAGES.HOME.GET_ADDONS_TAB_TITLE' | translate">
|
||||
<app-get-addons [tabIndex]="1"></app-get-addons>
|
||||
</mat-tab>
|
||||
<mat-tab
|
||||
[disabled]="hasWowClient !== true"
|
||||
[label]="'PAGES.HOME.ABOUT_TAB_TITLE' | translate"
|
||||
>
|
||||
<mat-tab [disabled]="hasWowClient !== true" [label]="'PAGES.HOME.ABOUT_TAB_TITLE' | translate">
|
||||
<app-about [tabIndex]="2"></app-about>
|
||||
</mat-tab>
|
||||
<mat-tab [label]="'PAGES.HOME.OPTIONS_TAB_TITLE' | translate">
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
@import "../../../custom-theme.scss";
|
||||
|
||||
:host {
|
||||
}
|
||||
.app-logo {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { HomeComponent } from "./home.component";
|
||||
@@ -7,12 +7,14 @@ describe("HomeComponent", () => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [HomeComponent],
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [HomeComponent],
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
@@ -24,10 +26,11 @@ describe("HomeComponent", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should render title in a h1 tag", async(() => {
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector("h1").textContent).toContain(
|
||||
"PAGES.HOME.TITLE"
|
||||
);
|
||||
}));
|
||||
it(
|
||||
"should render title in a h1 tag",
|
||||
waitForAsync(() => {
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector("h1").textContent).toContain("PAGES.HOME.TITLE");
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -64,13 +64,7 @@ import { HomeComponent } from "./home.component";
|
||||
OptionsDebugSectionComponent,
|
||||
OptionsAddonSectionComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
HomeRoutingModule,
|
||||
MatModule,
|
||||
DirectiveModule,
|
||||
],
|
||||
imports: [CommonModule, SharedModule, HomeRoutingModule, MatModule, DirectiveModule],
|
||||
providers: [DatePipe, GetAddonListItemFilePropPipe],
|
||||
})
|
||||
export class HomeModule {}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
.control-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 1em 0 1em;
|
||||
}
|
||||
.control-container .select-container {
|
||||
padding-top: 1em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.control-container .button-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.control-container .button-container button:not(:last-child) {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow: auto;
|
||||
}
|
||||
.table-container table {
|
||||
width: 100%;
|
||||
}
|
||||
.table-container tr:hover {
|
||||
background-color: #666666;
|
||||
}
|
||||
.table-container .addon-logo-container {
|
||||
background-color: #555555;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-right: 0.5em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.table-container .addon-logo-container .addon-logo {
|
||||
width: 100%;
|
||||
}
|
||||
.table-container .addon-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.table-container .status-column {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 130px;
|
||||
align-items: center;
|
||||
}
|
||||
.table-container .status-column .auto-update-icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.table-container .status-column .progress-text {
|
||||
text-align: center;
|
||||
}
|
||||
.table-container .status-column .addon-progress {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.author-column {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.provider-column {
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.column-menu {
|
||||
padding: 0.5em;
|
||||
color: #ffffff;
|
||||
}
|
||||
.column-menu p {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
@@ -3,14 +3,15 @@
|
||||
windows: electronService.isWin,
|
||||
linux: electronService.isLinux
|
||||
}">
|
||||
<div class="control-container">
|
||||
<div class="control-container bg-secondary-3">
|
||||
<div class="select-container">
|
||||
<mat-form-field>
|
||||
<mat-label>{{ "PAGES.MY_ADDONS.CLIENT_TYPE_SELECT_LABEL" | translate }}</mat-label>
|
||||
<mat-select class="select pointer" [(value)]="selectedClient" (selectionChange)="onClientChange()"
|
||||
[disabled]="enableControls === false">
|
||||
<mat-option [value]="clientType" *ngFor="let clientType of warcraftService.installedClientTypes$ | async">
|
||||
{{ warcraftService.getClientDisplayName(clientType) }}
|
||||
<mat-option [value]="clientType.value"
|
||||
*ngFor="let clientType of warcraftService.installedClientTypesSelectItems$ | async">
|
||||
{{ clientType.display | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@@ -52,11 +53,11 @@
|
||||
<app-progress-spinner [message]="spinnerMessage"> </app-progress-spinner>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isBusy === false && sortedListItems.length === 0" class="no-addons-container flex-grow-1">
|
||||
<div *ngIf="isBusy === false && sortedListItems.length === 0" class="no-addons-container text-1 flex-grow-1">
|
||||
<h1>{{ "COMMON.SEARCH.NO_ADDONS" | translate }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="table-container flex-grow-1" [hidden]="isBusy === true || sortedListItems.length === 0">
|
||||
<div class="table-container bg-secondary-2 flex-grow-1" [hidden]="isBusy === true || sortedListItems.length === 0">
|
||||
<table #table mat-table matSort (matSortChange)="onSortChange()" [dataSource]="dataSource"
|
||||
[matSortActive]="activeSort" [matSortDirection]="activeSortDirection" class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="addon.name">
|
||||
@@ -160,13 +161,14 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true" class="hover-bg-secondary-2"
|
||||
(contextmenu)="onHeaderContext($event); $event.preventDefault()"></tr>
|
||||
|
||||
<tr mat-row *matRowDef="let row; let i = index; columns: displayedColumns" tabindex="0"
|
||||
[ngClass]="{ 'selected-row': row.selected }" (click)="onRowClicked($event, row, i)"
|
||||
(dblclick)="openDetailDialog(row)" (contextmenu)="onCellContext($event, row)"
|
||||
(keydown.control.a)="selectAllRows($event)" (keydown.meta.a)="selectAllRows($event)"></tr>
|
||||
class="hover-bg-secondary-2" [ngClass]="{ 'bg-secondary-4': row.selected }"
|
||||
(click)="onRowClicked($event, row, i)" (dblclick)="openDetailDialog(row)"
|
||||
(contextmenu)="onCellContext($event, row)" (keydown.control.a)="selectAllRows($event)"
|
||||
(keydown.meta.a)="selectAllRows($event)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -53,22 +53,13 @@
|
||||
}
|
||||
|
||||
.table-container {
|
||||
// height: calc(100% - 75px);
|
||||
background-color: $dark-2;
|
||||
overflow: auto;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tr {
|
||||
&:hover {
|
||||
background-color: $dark-1;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-logo-container {
|
||||
background-color: $dark-2;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
@@ -120,10 +111,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.selected-row {
|
||||
background: $dark-4;
|
||||
}
|
||||
|
||||
.addon-provider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Overlay, OverlayRef } from "@angular/cdk/overlay";
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
@@ -19,8 +20,8 @@ import { MatTableDataSource } from "@angular/material/table";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { AddonUpdateEvent } from "app/models/wowup/addon-update-event";
|
||||
import * as _ from "lodash";
|
||||
import { BehaviorSubject, from, Subscription } from "rxjs";
|
||||
import { map } from "rxjs/operators";
|
||||
import { BehaviorSubject, from, Observable, Subscription } from "rxjs";
|
||||
import { filter, map } from "rxjs/operators";
|
||||
import { AddonViewModel } from "../../business-objects/my-addon-list-item";
|
||||
import { AddonDetailComponent, AddonDetailModel } from "../../components/addon-detail/addon-detail.component";
|
||||
import { ConfirmDialogComponent } from "../../components/confirm-dialog/confirm-dialog.component";
|
||||
@@ -28,6 +29,7 @@ import { Addon } from "../../entities/addon";
|
||||
import { WowClientType } from "../../models/warcraft/wow-client-type";
|
||||
import { AddonInstallState } from "../../models/wowup/addon-install-state";
|
||||
import { ColumnState } from "../../models/wowup/column-state";
|
||||
import { SelectItem } from "../../models/wowup/select-item";
|
||||
import { ElectronService } from "../../services";
|
||||
import { AddonService } from "../../services/addons/addon.service";
|
||||
import { SessionService } from "../../services/session/session.service";
|
||||
@@ -36,7 +38,6 @@ import { WowUpService } from "../../services/wowup/wowup.service";
|
||||
import { getEnumName } from "../../utils/enum.utils";
|
||||
import { stringIncludes } from "../../utils/string.utils";
|
||||
import { WowUpAddonService } from "../../services/wowup/wowup-addon.service";
|
||||
import { AddonChannelType } from "app/models/wowup/addon-channel-type";
|
||||
|
||||
@Component({
|
||||
selector: "app-my-addons",
|
||||
@@ -44,16 +45,16 @@ import { AddonChannelType } from "app/models/wowup/addon-channel-type";
|
||||
styleUrls: ["./my-addons.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MyAddonsComponent implements OnInit, OnDestroy {
|
||||
export class MyAddonsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
@Input("tabIndex") tabIndex: number;
|
||||
|
||||
@ViewChild("addonContextMenuTrigger") contextMenu: MatMenuTrigger;
|
||||
@ViewChild("addonMultiContextMenuTrigger") multiContextMenu: MatMenuTrigger;
|
||||
@ViewChild("columnContextMenuTrigger") columnContextMenu: MatMenuTrigger;
|
||||
@ViewChild("updateAllContextMenuTrigger")
|
||||
@ViewChild("addonContextMenuTrigger", { static: false }) contextMenu: MatMenuTrigger;
|
||||
@ViewChild("addonMultiContextMenuTrigger", { static: false }) multiContextMenu: MatMenuTrigger;
|
||||
@ViewChild("columnContextMenuTrigger", { static: false }) columnContextMenu: MatMenuTrigger;
|
||||
@ViewChild("updateAllContextMenuTrigger", { static: false })
|
||||
updateAllContextMenu: MatMenuTrigger;
|
||||
@ViewChild(MatSort, { static: false }) sort: MatSort;
|
||||
@ViewChild("table", { read: ElementRef }) table: ElementRef;
|
||||
@ViewChild("table", { static: false, read: ElementRef }) table: ElementRef;
|
||||
|
||||
private readonly _displayAddonsSrc = new BehaviorSubject<AddonViewModel[]>([]);
|
||||
|
||||
@@ -193,6 +194,13 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
|
||||
this.subscriptions.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
this._sessionService.autoUpdateComplete$.subscribe(() => {
|
||||
this._cdRef.markForCheck();
|
||||
this.onRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
public isLatestUpdateColumnVisible(): boolean {
|
||||
return this.columns.find((column) => column.name === "addon.latestVersion").visible;
|
||||
}
|
||||
@@ -637,7 +645,6 @@ export class MyAddonsComponent implements OnInit, OnDestroy {
|
||||
private loadAddons(clientType: WowClientType, rescan = false) {
|
||||
this.isBusy = true;
|
||||
this.enableControls = false;
|
||||
this._cdRef.detectChanges();
|
||||
|
||||
console.log("Load-addons", clientType);
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
}"
|
||||
>
|
||||
<div class="nav-container">
|
||||
<div class="nav-item-container">
|
||||
<div class="theme-logo">
|
||||
<img [src]="getLogoPath()" />
|
||||
</div>
|
||||
<div class="nav-item-container" [ngClass]="[this.wowUpService.currentTheme]">
|
||||
<div class="nav-item-list-wrap">
|
||||
<div class="nav-item-list">
|
||||
<mat-action-list>
|
||||
@@ -27,14 +30,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-content-container">
|
||||
<div class="nav-content-container bg-secondary-2 text-1">
|
||||
<div class="nav-content-wrap">
|
||||
<div class="nav-content">
|
||||
<mat-tab-group
|
||||
class="no-tabs content-tab"
|
||||
animationDuration="0ms"
|
||||
[(selectedIndex)]="optionTabIndex"
|
||||
>
|
||||
<mat-tab-group class="no-tabs content-tab" animationDuration="0ms" [(selectedIndex)]="optionTabIndex">
|
||||
<mat-tab label="WorldOfWarcraft" class="tab">
|
||||
<app-options-wow-section></app-options-wow-section>
|
||||
</mat-tab>
|
||||
|
||||
@@ -10,13 +10,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
.theme-logo {
|
||||
content: "";
|
||||
opacity: 0.02;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
img {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.nav-item-container {
|
||||
background: $dark-3;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
@@ -27,6 +43,7 @@
|
||||
z-index: 1;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
// overflow: hidden;
|
||||
|
||||
.nav-item-list-wrap {
|
||||
flex: 1 0 auto;
|
||||
@@ -49,8 +66,6 @@
|
||||
}
|
||||
}
|
||||
.nav-content-container {
|
||||
background: $dark-2;
|
||||
color: $white-1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
-webkit-box-flex: 1;
|
||||
@@ -83,6 +98,7 @@
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
-webkit-box-flex: 1;
|
||||
z-index: 2;
|
||||
|
||||
.content-tab {
|
||||
position: relative;
|
||||
@@ -97,84 +113,3 @@
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.option-card {
|
||||
margin-top: 1em;
|
||||
background-color: $dark-2;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.5em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.setting-section {
|
||||
background-color: $dark-4;
|
||||
}
|
||||
|
||||
.folder-input {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
// margin: 1em 0;
|
||||
padding: 0.5em;
|
||||
&.np {
|
||||
padding: 0;
|
||||
}
|
||||
&.sub {
|
||||
padding-left: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.col {
|
||||
padding: 0.5em;
|
||||
}
|
||||
hr {
|
||||
border: 1px solid $white-4;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.hint {
|
||||
color: $white-3;
|
||||
}
|
||||
.hover {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.no-shrink {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
section {
|
||||
color: $white-1;
|
||||
padding: 1em;
|
||||
|
||||
.select-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
small {
|
||||
color: $white-2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from "@angular/core";
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
} from "@angular/core";
|
||||
ALLIANCE_LIGHT_THEME,
|
||||
ALLIANCE_THEME,
|
||||
DEFAULT_LIGHT_THEME,
|
||||
DEFAULT_THEME,
|
||||
HORDE_LIGHT_THEME,
|
||||
HORDE_THEME,
|
||||
} from "common/constants";
|
||||
import { ElectronService } from "../../services";
|
||||
import { WowUpService } from "../../services/wowup/wowup.service";
|
||||
|
||||
@@ -18,10 +21,25 @@ export class OptionsComponent implements OnInit {
|
||||
|
||||
public optionTabIndex = 0;
|
||||
|
||||
constructor(
|
||||
public wowupService: WowUpService,
|
||||
public electronService: ElectronService
|
||||
) {}
|
||||
constructor(public wowUpService: WowUpService, public electronService: ElectronService) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
getLogoPath() {
|
||||
switch (this.wowUpService.currentTheme) {
|
||||
case HORDE_THEME:
|
||||
return "assets/images/horde-1.png";
|
||||
case HORDE_LIGHT_THEME:
|
||||
return "assets/images/horde-dark-1.png";
|
||||
case ALLIANCE_THEME:
|
||||
return "assets/images/alliance-1.png";
|
||||
case ALLIANCE_LIGHT_THEME:
|
||||
return "assets/images/alliance-dark-1.png";
|
||||
case DEFAULT_LIGHT_THEME:
|
||||
return "assets/images/wowup-dark-1.png";
|
||||
case DEFAULT_THEME:
|
||||
default:
|
||||
return "assets/images/wowup-white-1.png";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { DownloadCountPipe } from "./download-count.pipe";
|
||||
|
||||
describe("DownloadCountPipe", () => {
|
||||
let directive: DownloadCountPipe;
|
||||
let fixture: ComponentFixture<DownloadCountPipe>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DownloadCountPipe],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DownloadCountPipe],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
it("should create an instance", () => {
|
||||
fixture = TestBed.createComponent(DownloadCountPipe);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { GetAddonListItemFilePropPipe } from "./get-addon-list-item-file-prop.pipe";
|
||||
|
||||
describe("GetAddonListItemFilePropPipe", () => {
|
||||
let directive: GetAddonListItemFilePropPipe;
|
||||
let fixture: ComponentFixture<GetAddonListItemFilePropPipe>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GetAddonListItemFilePropPipe],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [GetAddonListItemFilePropPipe],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
it("should create an instance", () => {
|
||||
fixture = TestBed.createComponent(GetAddonListItemFilePropPipe);
|
||||
|
||||
@@ -8,11 +8,7 @@ import * as SearchResults from "../utils/search-result.utils";
|
||||
name: "getAddonListItemFileProp",
|
||||
})
|
||||
export class GetAddonListItemFilePropPipe implements PipeTransform {
|
||||
transform(
|
||||
item: GetAddonListItem,
|
||||
prop: string,
|
||||
channel: AddonChannelType
|
||||
): any {
|
||||
transform(item: GetAddonListItem, prop: string, channel: AddonChannelType): any {
|
||||
let file = SearchResults.getLatestFile(item.searchResult, channel);
|
||||
if (!file) {
|
||||
file = _.first(_.orderBy(item.searchResult.files, "releaseDate", "desc"));
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { InterfaceFormatPipe } from "./interface-format.pipe";
|
||||
|
||||
describe("InterfaceFormatPipe", () => {
|
||||
let directive: InterfaceFormatPipe;
|
||||
let fixture: ComponentFixture<InterfaceFormatPipe>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [InterfaceFormatPipe],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [InterfaceFormatPipe],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
it("should create an instance", () => {
|
||||
fixture = TestBed.createComponent(InterfaceFormatPipe);
|
||||
|
||||
@@ -5,12 +5,9 @@ import { RelativeDurationPipe } from "./relative-duration-pipe";
|
||||
|
||||
describe("RelativeDurationPipe", () => {
|
||||
it("create an instance", () => {
|
||||
inject(
|
||||
[DatePipe, TranslateService],
|
||||
(datePipe: DatePipe, translateService: TranslateService) => {
|
||||
const pipe = new RelativeDurationPipe(translateService);
|
||||
expect(pipe).toBeTruthy();
|
||||
}
|
||||
);
|
||||
inject([DatePipe, TranslateService], (datePipe: DatePipe, translateService: TranslateService) => {
|
||||
const pipe = new RelativeDurationPipe(translateService);
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,10 @@ export class AddonService {
|
||||
var searchTasks = this.getEnabledAddonProviders().map((p) => p.searchByQuery(query, clientType));
|
||||
var searchResults = await Promise.all(searchTasks);
|
||||
|
||||
await this._analyticsService.trackUserAction("addons", "search", `${clientType}|${query}`);
|
||||
await this._analyticsService.trackAction("addon-search", {
|
||||
clientType: getEnumName(WowClientType, clientType),
|
||||
query,
|
||||
});
|
||||
|
||||
const flatResults = searchResults.flat(1);
|
||||
|
||||
@@ -270,7 +273,11 @@ export class AddonService {
|
||||
const actionLabel = `${getEnumName(WowClientType, addon.clientType)}|${addon.providerName}|${addon.externalId}|${
|
||||
addon.name
|
||||
}`;
|
||||
this._analyticsService.trackUserAction("addons", "install_by_id", actionLabel);
|
||||
this._analyticsService.trackAction("install-addon", {
|
||||
clientType: getEnumName(WowClientType, addon.clientType),
|
||||
provider: addon.providerName,
|
||||
addon: actionLabel,
|
||||
});
|
||||
|
||||
await this.installDependencies(addon, onUpdate);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { HttpClient, HttpParams } from "@angular/common/http";
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ApplicationInsights } from "@microsoft/applicationinsights-web";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { AppConfig } from "../../../environments/environment";
|
||||
@@ -16,6 +17,8 @@ export class AnalyticsService {
|
||||
private readonly _appVersion: string;
|
||||
private readonly _telemetryEnabledSrc = new BehaviorSubject(false);
|
||||
|
||||
private _insights?: ApplicationInsights;
|
||||
|
||||
public readonly telemetryPromptUsedKey = "telemetry_prompt_sent";
|
||||
public readonly telemetryEnabledKey = "telemetry_enabled";
|
||||
public readonly telemetryEnabled$ = this._telemetryEnabledSrc.asObservable();
|
||||
@@ -25,19 +28,24 @@ export class AnalyticsService {
|
||||
}
|
||||
|
||||
public get shouldPromptTelemetry() {
|
||||
return (
|
||||
this._preferenceStorageService.get(this.telemetryEnabledKey) === undefined
|
||||
);
|
||||
return this._preferenceStorageService.get(this.telemetryEnabledKey) === undefined;
|
||||
}
|
||||
|
||||
public get telemetryEnabled() {
|
||||
const preference = this._preferenceStorageService.findByKey(
|
||||
this.telemetryEnabledKey
|
||||
);
|
||||
return preference === true.toString();
|
||||
const preference = this._preferenceStorageService.findByKey(this.telemetryEnabledKey);
|
||||
const value = preference === true.toString();
|
||||
|
||||
this.configureAppInsights(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public set telemetryEnabled(value: boolean) {
|
||||
if (this._insights) {
|
||||
this._insights.appInsights.config.disableTelemetry = value;
|
||||
console.debug("disableTelemetry", this._insights.appInsights.config.disableTelemetry);
|
||||
}
|
||||
|
||||
this._preferenceStorageService.set(this.telemetryEnabledKey, value);
|
||||
this._telemetryEnabledSrc.next(value);
|
||||
}
|
||||
@@ -54,23 +62,41 @@ export class AnalyticsService {
|
||||
}
|
||||
|
||||
public async trackStartup() {
|
||||
await this.track((params) => {
|
||||
params.set("t", "pageview");
|
||||
params.set("dp", "app/startup");
|
||||
});
|
||||
//Record an event
|
||||
await this.track2("app-startup");
|
||||
|
||||
// await this.track((params) => {
|
||||
// params.set("t", "pageview");
|
||||
// params.set("dp", "app/startup");
|
||||
// });
|
||||
}
|
||||
|
||||
public async trackUserAction(
|
||||
category: string,
|
||||
action: string,
|
||||
label: string = null
|
||||
) {
|
||||
await this.track((params) => {
|
||||
params.set("t", "event");
|
||||
params.set("ec", category);
|
||||
params.set("ea", action);
|
||||
params.set("el", label);
|
||||
public async trackAction(name: string, properties: object = undefined) {
|
||||
await this.track2(name, properties);
|
||||
}
|
||||
|
||||
public async trackUserAction(category: string, action: string, label: string = null) {
|
||||
await this.track2(category, {
|
||||
action,
|
||||
label,
|
||||
});
|
||||
|
||||
// await this.track((params) => {
|
||||
// params.set("t", "event");
|
||||
// params.set("ec", category);
|
||||
// params.set("ea", action);
|
||||
// params.set("el", label);
|
||||
// });
|
||||
}
|
||||
|
||||
private async track2(name: string, properties: object = undefined) {
|
||||
if (!this.telemetryEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._insights?.trackEvent({ name, properties });
|
||||
|
||||
console.debug("Track", name);
|
||||
}
|
||||
|
||||
private async track(action: (params: HttpParams) => void = undefined) {
|
||||
@@ -109,9 +135,7 @@ export class AnalyticsService {
|
||||
}
|
||||
|
||||
private loadInstallId() {
|
||||
let installId = this._preferenceStorageService.findByKey(
|
||||
this.installIdPreferenceKey
|
||||
);
|
||||
let installId = this._preferenceStorageService.findByKey(this.installIdPreferenceKey);
|
||||
if (installId) {
|
||||
return installId;
|
||||
}
|
||||
@@ -121,4 +145,25 @@ export class AnalyticsService {
|
||||
|
||||
return installId;
|
||||
}
|
||||
|
||||
private configureAppInsights(enable: boolean) {
|
||||
if (!enable || this._insights) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._insights = new ApplicationInsights({
|
||||
config: {
|
||||
instrumentationKey: AppConfig.azure.applicationInsightsKey,
|
||||
},
|
||||
});
|
||||
this._insights.loadAppInsights();
|
||||
this._insights.trackPageView();
|
||||
|
||||
// If telemetry is off, dont let it track anything
|
||||
this._insights.addTelemetryInitializer((envelop) => {
|
||||
if (!this.telemetryEnabled) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,7 @@ export class DownloadSevice {
|
||||
|
||||
const eventHandler = (_evt: any, arg: DownloadStatus) => {
|
||||
if (arg.type !== DownloadStatusType.Progress) {
|
||||
this._electronService.ipcRenderer.off(
|
||||
request.responseKey,
|
||||
eventHandler
|
||||
);
|
||||
this._electronService.ipcRenderer.off(request.responseKey, eventHandler);
|
||||
}
|
||||
|
||||
switch (arg.type) {
|
||||
|
||||
@@ -195,6 +195,18 @@ export class ElectronService {
|
||||
return new Notification(title, options);
|
||||
}
|
||||
|
||||
public isHandlingProtocol(protocol: string): boolean {
|
||||
return this.remote.app.isDefaultProtocolClient(protocol);
|
||||
}
|
||||
|
||||
public setHandleProtocol(protocol: string, enable: boolean) {
|
||||
if (enable) {
|
||||
return this.remote.app.setAsDefaultProtocolClient(protocol);
|
||||
} else {
|
||||
return this.remote.app.removeAsDefaultProtocolClient(protocol);
|
||||
}
|
||||
}
|
||||
|
||||
public async sendIpcValueMessage<TIN, TOUT>(channel: string, value: TIN): Promise<TOUT> {
|
||||
const request: ValueRequest<TIN> = {
|
||||
value,
|
||||
|
||||
@@ -25,24 +25,15 @@ export class FileService {
|
||||
constructor(private _electronService: ElectronService) {}
|
||||
|
||||
public async getAssetFilePath(fileName: string) {
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
GET_ASSET_FILE_PATH,
|
||||
fileName
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(GET_ASSET_FILE_PATH, fileName);
|
||||
}
|
||||
|
||||
public async createDirectory(directoryPath: string) {
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
CREATE_DIRECTORY_CHANNEL,
|
||||
directoryPath
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(CREATE_DIRECTORY_CHANNEL, directoryPath);
|
||||
}
|
||||
|
||||
public async showDirectory(sourceDir: string) {
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
SHOW_DIRECTORY,
|
||||
sourceDir
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(SHOW_DIRECTORY, sourceDir);
|
||||
}
|
||||
|
||||
public async pathExists(sourcePath: string): Promise<boolean> {
|
||||
@@ -50,10 +41,7 @@ export class FileService {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
PATH_EXISTS_CHANNEL,
|
||||
sourcePath
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(PATH_EXISTS_CHANNEL, sourcePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,19 +52,13 @@ export class FileService {
|
||||
throw new Error("remove sourcePath required");
|
||||
}
|
||||
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
DELETE_DIRECTORY_CHANNEL,
|
||||
sourcePath
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(DELETE_DIRECTORY_CHANNEL, sourcePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file or folder
|
||||
*/
|
||||
public async copy(
|
||||
sourceFilePath: string,
|
||||
destinationFilePath: string
|
||||
): Promise<string> {
|
||||
public async copy(sourceFilePath: string, destinationFilePath: string): Promise<string> {
|
||||
const request: CopyFileRequest = {
|
||||
destinationFilePath,
|
||||
sourceFilePath,
|
||||
@@ -96,33 +78,21 @@ export class FileService {
|
||||
}
|
||||
|
||||
public async readFile(sourcePath: string): Promise<string> {
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
READ_FILE_CHANNEL,
|
||||
sourcePath
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(READ_FILE_CHANNEL, sourcePath);
|
||||
}
|
||||
|
||||
public async writeFile(sourcePath: string, contents: string): Promise<string> {
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
WRITE_FILE_CHANNEL,
|
||||
sourcePath,
|
||||
contents,
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(WRITE_FILE_CHANNEL, sourcePath, contents);
|
||||
}
|
||||
|
||||
public async listDirectories(sourcePath: string): Promise<string[]> {
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
LIST_DIRECTORIES_CHANNEL,
|
||||
sourcePath
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(LIST_DIRECTORIES_CHANNEL, sourcePath);
|
||||
}
|
||||
|
||||
public listEntries(sourcePath: string, filter: string) {
|
||||
const globFilter = globrex(filter);
|
||||
|
||||
return fs
|
||||
.readdirSync(sourcePath, { withFileTypes: true })
|
||||
.filter((entry) => !!globFilter.regex.test(entry.name));
|
||||
return fs.readdirSync(sourcePath, { withFileTypes: true }).filter((entry) => !!globFilter.regex.test(entry.name));
|
||||
}
|
||||
|
||||
public listFiles(sourcePath: string, filter: string) {
|
||||
@@ -134,10 +104,7 @@ export class FileService {
|
||||
.map((entry) => entry.name);
|
||||
}
|
||||
|
||||
public async unzipFile(
|
||||
zipFilePath: string,
|
||||
outputFolder: string
|
||||
): Promise<string> {
|
||||
public async unzipFile(zipFilePath: string, outputFolder: string): Promise<string> {
|
||||
console.log("unzipFile", zipFilePath);
|
||||
|
||||
const request: UnzipRequest = {
|
||||
@@ -146,9 +113,6 @@ export class FileService {
|
||||
responseKey: uuidv4(),
|
||||
};
|
||||
|
||||
return await this._electronService.ipcRenderer.invoke(
|
||||
UNZIP_FILE_CHANNEL,
|
||||
request
|
||||
);
|
||||
return await this._electronService.ipcRenderer.invoke(UNZIP_FILE_CHANNEL, request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
faLink,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faQuestionCircle, faClock } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faDiscord, faGithub } from "@fortawesome/free-brands-svg-icons";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
@@ -30,6 +31,8 @@ export class IconService {
|
||||
this.addSvg(faClock);
|
||||
this.addSvg(faBug);
|
||||
this.addSvg(faLink);
|
||||
this.addSvg(faDiscord);
|
||||
this.addSvg(faGithub);
|
||||
}
|
||||
|
||||
async addSvg(icon: IconDefinition) {
|
||||
|
||||
@@ -9,25 +9,26 @@ import { WowUpService } from "../wowup/wowup.service";
|
||||
providedIn: "root",
|
||||
})
|
||||
export class SessionService {
|
||||
private readonly _selectedClientTypeSrc = new BehaviorSubject(
|
||||
WowClientType.None
|
||||
);
|
||||
private readonly _selectedClientTypeSrc = new BehaviorSubject(WowClientType.None);
|
||||
private readonly _pageContextTextSrc = new BehaviorSubject(""); // right side bar text, context to the screen
|
||||
private readonly _statusTextSrc = new BehaviorSubject(""); // left side bar text, context to the app
|
||||
private readonly _selectedHomeTabSrc = new BehaviorSubject(0);
|
||||
private readonly _autoUpdateCompleteSrc = new BehaviorSubject(0);
|
||||
|
||||
public readonly selectedClientType$ = this._selectedClientTypeSrc.asObservable();
|
||||
public readonly statusText$ = this._statusTextSrc.asObservable();
|
||||
public readonly selectedHomeTab$ = this._selectedHomeTabSrc.asObservable();
|
||||
public readonly pageContextText$ = this._pageContextTextSrc.asObservable();
|
||||
public readonly autoUpdateComplete$ = this._autoUpdateCompleteSrc.asObservable();
|
||||
|
||||
constructor(
|
||||
private _warcraftService: WarcraftService,
|
||||
private _wowUpService: WowUpService
|
||||
) {
|
||||
constructor(private _warcraftService: WarcraftService, private _wowUpService: WowUpService) {
|
||||
this.loadInitialClientType().pipe(first()).subscribe();
|
||||
}
|
||||
|
||||
public autoUpdateComplete() {
|
||||
this._autoUpdateCompleteSrc.next(Date.now());
|
||||
}
|
||||
|
||||
public setContextText(tabIndex: number, text: string) {
|
||||
if (tabIndex !== this._selectedHomeTabSrc.value) {
|
||||
return;
|
||||
@@ -62,15 +63,10 @@ export class SessionService {
|
||||
console.log("installedClientTypes", installedClientTypes);
|
||||
const lastSelectedType = this._wowUpService.lastSelectedClientType;
|
||||
console.log("lastSelectedType", lastSelectedType);
|
||||
let initialClientType = installedClientTypes.length
|
||||
? installedClientTypes[0]
|
||||
: WowClientType.None;
|
||||
let initialClientType = installedClientTypes.length ? installedClientTypes[0] : WowClientType.None;
|
||||
|
||||
// If the user has no stored type, or the type is no longer found just set it.
|
||||
if (
|
||||
lastSelectedType == WowClientType.None ||
|
||||
!installedClientTypes.some((ct) => ct == lastSelectedType)
|
||||
) {
|
||||
if (lastSelectedType == WowClientType.None || !installedClientTypes.some((ct) => ct == lastSelectedType)) {
|
||||
this._wowUpService.lastSelectedClientType = initialClientType;
|
||||
} else {
|
||||
initialClientType = lastSelectedType;
|
||||
|
||||
@@ -70,19 +70,13 @@ export class AddonStorageService {
|
||||
return addons[0];
|
||||
}
|
||||
|
||||
public getAllForClientType(
|
||||
clientType: WowClientType,
|
||||
validator?: (addon: Addon) => boolean
|
||||
) {
|
||||
public getAllForClientType(clientType: WowClientType, validator?: (addon: Addon) => boolean) {
|
||||
const addons: Addon[] = [];
|
||||
|
||||
this.query((store) => {
|
||||
for (const result of store) {
|
||||
const addon = result[1] as Addon;
|
||||
if (
|
||||
addon.clientType === clientType &&
|
||||
(!validator || validator(addon))
|
||||
) {
|
||||
if (addon.clientType === clientType && (!validator || validator(addon))) {
|
||||
addons.push(addon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as path from "path";
|
||||
import { BehaviorSubject, from } from "rxjs";
|
||||
import { filter, map, switchMap } from "rxjs/operators";
|
||||
import { ElectronService } from "..";
|
||||
import { SelectItem } from "../../models/wowup/select-item";
|
||||
import { InstalledProduct } from "../../models/warcraft/installed-product";
|
||||
import { ProductDb } from "../../models/warcraft/product-db";
|
||||
import { WowClientType } from "../../models/warcraft/wow-client-type";
|
||||
@@ -40,19 +41,31 @@ const BETA_LOCATION_KEY = "wow_beta_location";
|
||||
export class WarcraftService {
|
||||
private readonly _impl: WarcraftServiceImpl;
|
||||
private readonly _productsSrc = new BehaviorSubject<InstalledProduct[]>([]);
|
||||
private readonly _installedClientTypesSrc = new BehaviorSubject<
|
||||
WowClientType[] | undefined
|
||||
>(undefined);
|
||||
private readonly _installedClientTypesSrc = new BehaviorSubject<WowClientType[] | undefined>(undefined);
|
||||
|
||||
private _productDbPath = "";
|
||||
|
||||
public products$ = this._productsSrc.asObservable();
|
||||
public productsReady$ = this.products$.pipe(
|
||||
filter((products) => Array.isArray(products))
|
||||
);
|
||||
public productsReady$ = this.products$.pipe(filter((products) => Array.isArray(products)));
|
||||
|
||||
public installedClientTypes$ = this._installedClientTypesSrc.asObservable();
|
||||
|
||||
// Map the client types so that we can localize them
|
||||
public installedClientTypesSelectItems$ = this._installedClientTypesSrc.pipe(
|
||||
filter((clientTypes) => !!clientTypes),
|
||||
map((clientTypes) => {
|
||||
return clientTypes.map(
|
||||
(ct): SelectItem<WowClientType> => {
|
||||
const clientTypeName = getEnumName(WowClientType, ct).toUpperCase();
|
||||
return {
|
||||
display: `COMMON.CLIENT_TYPES.${clientTypeName}`,
|
||||
value: ct,
|
||||
};
|
||||
}
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
constructor(
|
||||
private _electronService: ElectronService,
|
||||
private _fileService: FileService,
|
||||
@@ -66,9 +79,7 @@ export class WarcraftService {
|
||||
map((productDbPath) => (this._productDbPath = productDbPath)),
|
||||
map(() => this.scanProducts()),
|
||||
switchMap(() => from(this.getWowClientTypes())),
|
||||
map((installedClientTypes) =>
|
||||
this._installedClientTypesSrc.next(installedClientTypes)
|
||||
)
|
||||
map((installedClientTypes) => this._installedClientTypesSrc.next(installedClientTypes))
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
@@ -123,9 +134,7 @@ export class WarcraftService {
|
||||
|
||||
public getProductLocation(clientType: WowClientType) {
|
||||
const clientFolderName = this.getClientFolderName(clientType);
|
||||
const clientLocation = this._productsSrc.value.find(
|
||||
(product) => product.name === clientFolderName
|
||||
);
|
||||
const clientLocation = this._productsSrc.value.find((product) => product.name === clientFolderName);
|
||||
return clientLocation?.location ?? "";
|
||||
}
|
||||
|
||||
@@ -147,9 +156,7 @@ export class WarcraftService {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`clientLocation ${clientLocation}, productLocation ${productLocation}`
|
||||
);
|
||||
console.log(`clientLocation ${clientLocation}, productLocation ${productLocation}`);
|
||||
if (this.arePathsEqual(clientLocation, productLocation)) {
|
||||
continue;
|
||||
}
|
||||
@@ -178,9 +185,7 @@ export class WarcraftService {
|
||||
return addonFolders;
|
||||
}
|
||||
|
||||
const directories = await this._fileService.listDirectories(
|
||||
addonFolderPath
|
||||
);
|
||||
const directories = await this._fileService.listDirectories(addonFolderPath);
|
||||
// const directories = files.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
|
||||
for (let i = 0; i < directories.length; i += 1) {
|
||||
const dir = directories[i];
|
||||
@@ -193,10 +198,7 @@ export class WarcraftService {
|
||||
return addonFolders;
|
||||
}
|
||||
|
||||
private async getAddonFolder(
|
||||
addonFolderPath: string,
|
||||
dir: string
|
||||
): Promise<AddonFolder> {
|
||||
private async getAddonFolder(addonFolderPath: string, dir: string): Promise<AddonFolder> {
|
||||
try {
|
||||
const dirPath = path.join(addonFolderPath, dir);
|
||||
const dirFiles = fs.readdirSync(dirPath);
|
||||
@@ -233,10 +235,7 @@ export class WarcraftService {
|
||||
return this._preferenceStorageService.set(clientLocationKey, clientPath);
|
||||
}
|
||||
|
||||
public setWowFolderPath(
|
||||
clientType: WowClientType,
|
||||
folderPath: string
|
||||
): boolean {
|
||||
public setWowFolderPath(clientType: WowClientType, folderPath: string): boolean {
|
||||
const relativePath = this.getClientRelativePath(clientType, folderPath);
|
||||
|
||||
if (!this.isClientFolder(clientType, relativePath)) {
|
||||
@@ -246,11 +245,7 @@ export class WarcraftService {
|
||||
this.setClientLocation(clientType, relativePath);
|
||||
|
||||
from(this.getWowClientTypes())
|
||||
.pipe(
|
||||
map((wowClientTypes) =>
|
||||
this._installedClientTypesSrc.next(wowClientTypes)
|
||||
)
|
||||
)
|
||||
.pipe(map((wowClientTypes) => this._installedClientTypesSrc.next(wowClientTypes)))
|
||||
.subscribe();
|
||||
|
||||
return true;
|
||||
@@ -259,10 +254,7 @@ export class WarcraftService {
|
||||
public getClientRelativePath(clientType: WowClientType, folderPath: string) {
|
||||
const clientFolderName = this.getClientFolderName(clientType);
|
||||
const clientFolderIdx = folderPath.indexOf(clientFolderName);
|
||||
const relativePath =
|
||||
clientFolderIdx === -1
|
||||
? folderPath
|
||||
: folderPath.substring(0, clientFolderIdx);
|
||||
const relativePath = clientFolderIdx === -1 ? folderPath : folderPath.substring(0, clientFolderIdx);
|
||||
|
||||
return path.normalize(relativePath);
|
||||
}
|
||||
@@ -272,11 +264,7 @@ export class WarcraftService {
|
||||
const relativePath = this.getClientRelativePath(clientType, folderPath);
|
||||
|
||||
const executableName = this.getExecutableName(clientType);
|
||||
const executablePath = path.join(
|
||||
relativePath,
|
||||
clientFolderName,
|
||||
executableName
|
||||
);
|
||||
const executablePath = path.join(relativePath, clientFolderName, executableName);
|
||||
|
||||
return fs.existsSync(executablePath);
|
||||
}
|
||||
@@ -345,12 +333,7 @@ export class WarcraftService {
|
||||
case WowClientType.Beta:
|
||||
return BETA_LOCATION_KEY;
|
||||
default:
|
||||
throw new Error(
|
||||
`Failed to get client location key: ${clientType}, ${getEnumName(
|
||||
WowClientType,
|
||||
clientType
|
||||
)}`
|
||||
);
|
||||
throw new Error(`Failed to get client location key: ${clientType}, ${getEnumName(WowClientType, clientType)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,7 @@ export class WarcraftServiceWin implements WarcraftServiceImpl {
|
||||
const driveNames = diskInfo.map((i) => i.mounted);
|
||||
|
||||
for (const name of driveNames) {
|
||||
const agentPath = path.join(
|
||||
name,
|
||||
WINDOWS_BLIZZARD_AGENT_PATH,
|
||||
BLIZZARD_PRODUCT_DB_NAME
|
||||
);
|
||||
const agentPath = path.join(name, WINDOWS_BLIZZARD_AGENT_PATH, BLIZZARD_PRODUCT_DB_NAME);
|
||||
const exists = await FileUtils.exists(agentPath);
|
||||
|
||||
if (exists) {
|
||||
|
||||
@@ -16,8 +16,6 @@ export class WowUpApiService {
|
||||
public getLatestVersion(): Observable<LatestVersionResponse> {
|
||||
const url = new URL(`${API_URL}/wowup/latest`);
|
||||
|
||||
return this._httpClient
|
||||
.get<LatestVersionResponse>(url.toString())
|
||||
.pipe(tap((res) => console.log(res)));
|
||||
return this._httpClient.get<LatestVersionResponse>(url.toString()).pipe(tap((res) => console.log(res)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
GET_ADDONS_HIDDEN_COLUMNS_KEY,
|
||||
GET_ADDONS_SORT_ORDER,
|
||||
ENABLED_ADDON_PROVIDERS_KEY,
|
||||
CURRENT_THEME_KEY,
|
||||
DEFAULT_THEME,
|
||||
} from "../../../common/constants";
|
||||
import { WowClientType } from "../../models/warcraft/wow-client-type";
|
||||
import { AddonChannelType } from "../../models/wowup/addon-channel-type";
|
||||
@@ -154,6 +156,16 @@ export class WowUpService {
|
||||
this._preferenceChangeSrc.next({ key, value: value.toString() });
|
||||
}
|
||||
|
||||
public get currentTheme() {
|
||||
return this._preferenceStorageService.get(CURRENT_THEME_KEY);
|
||||
}
|
||||
|
||||
public set currentTheme(value: string) {
|
||||
const key = CURRENT_THEME_KEY;
|
||||
this._preferenceStorageService.set(key, value);
|
||||
this._preferenceChangeSrc.next({ key, value: value });
|
||||
}
|
||||
|
||||
public get useHardwareAcceleration() {
|
||||
const preference = this._preferenceStorageService.findByKey(USE_HARDWARE_ACCELERATION_PREFERENCE_KEY);
|
||||
return preference === "true";
|
||||
@@ -348,6 +360,7 @@ export class WowUpService {
|
||||
this.setDefaultPreference(ENABLE_SYSTEM_NOTIFICATIONS_PREFERENCE_KEY, true);
|
||||
this.setDefaultPreference(COLLAPSE_TO_TRAY_PREFERENCE_KEY, true);
|
||||
this.setDefaultPreference(USE_HARDWARE_ACCELERATION_PREFERENCE_KEY, true);
|
||||
this.setDefaultPreference(CURRENT_THEME_KEY, DEFAULT_THEME);
|
||||
this.setDefaultPreference(WOWUP_RELEASE_CHANNEL_PREFERENCE_KEY, this.getDefaultReleaseChannel());
|
||||
this.setDefaultPreference(ENABLED_ADDON_PROVIDERS_KEY, this._addonProviderFactory
|
||||
.getAll()
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { async, ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { PageNotFoundComponent } from "./page-not-found.component";
|
||||
|
||||
describe("PageNotFoundComponent", () => {
|
||||
let component: PageNotFoundComponent;
|
||||
let fixture: ComponentFixture<PageNotFoundComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PageNotFoundComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PageNotFoundComponent],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PageNotFoundComponent);
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export function stringIncludes(value: string, search: string) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
return value.trim().toLowerCase().indexOf(search.trim().toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sha1 hash of a string
|
||||
*/
|
||||
export function getSha1Hash(str: string): string {
|
||||
const shasum = createHash("sha1");
|
||||
shasum.update(str);
|
||||
return shasum.digest("hex");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,57 @@
|
||||
{
|
||||
"ChangeLogs": [
|
||||
{
|
||||
"Version": "2.0.0-beta.15",
|
||||
"changes": [
|
||||
"Fix an issue where light/dark themes could overlap",
|
||||
"Fix an issue with a nested translate call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Version": "2.0.0-beta.14",
|
||||
"changes": [
|
||||
"Russian locale updates (Medok)",
|
||||
"Spanish locale updates (SkollVargr)",
|
||||
"German locale updates (Glow)",
|
||||
"Portuguese locale updates (Dogo)",
|
||||
"Korean locale updates (soaakim)",
|
||||
"French locale updates (Zazou)",
|
||||
"Theme updates (NoobTaco)",
|
||||
"Add light themes",
|
||||
"App can now be resized via the top of the frame",
|
||||
"Fix an issue with the scrollbar in options not being clickable anymore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Version": "2.0.0-beta.13",
|
||||
"changes": [
|
||||
"German locale updates (chops)",
|
||||
"French locale updates (Zazou)",
|
||||
"Italian locale updates (Bito)",
|
||||
"Theme updates (NoobTaco)",
|
||||
"Fix Korean locale",
|
||||
"Fix Chinese locale",
|
||||
"Fix NB locale"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Version": "2.0.0-beta.12",
|
||||
"changes": [
|
||||
"Russian locale updates (Medok)",
|
||||
"German locale updates (Glow)",
|
||||
"French locale updates (Zazou)",
|
||||
"Chinese locale updates (CyanoHao)",
|
||||
"Korean locale updates (soaakim)",
|
||||
"Spanish locale updates (SkollVargr)",
|
||||
"Add the ability to localize the 'Install by URL' download count (chops)",
|
||||
"Add a new theming feature for Alliance and Horde",
|
||||
"Auto update timer will now refresh the 'My Addons' list",
|
||||
"Update to Angular 11",
|
||||
"Fix issue with window snap fullscreen not saving screen",
|
||||
"Fix an issue with the app icon not showing up on ubuntu",
|
||||
"The app will now quit if you close the window on Mac and 'Minimize to Tray' is off"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Version": "2.0.0-beta.11",
|
||||
"changes": ["Italian locale updates (Bito)", "Fix a bug with the Spanish locale file"]
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
"QUIT_ACTION": "Beenden",
|
||||
"SHOW_ACTION": "Öffnen"
|
||||
},
|
||||
"THEME": {
|
||||
"ALLIANCE": "Allianz",
|
||||
"ALLIANCE_LIGHT": "Alliance Light",
|
||||
"DEFAULT": "Standard",
|
||||
"DEFAULT_LIGHT": "Default Light",
|
||||
"HORDE": "Horde",
|
||||
"HORDE_LIGHT": "Horde Light"
|
||||
},
|
||||
"WOWUP_UPDATE": {
|
||||
"DOWNLOADED_TOOLTIP": "WowUp Update installieren",
|
||||
"INSTALL_MESSAGE": "Willst du WowUp neu Starten um das Update zu installieren?",
|
||||
@@ -38,6 +46,13 @@
|
||||
"UNINSTALLING": "Deinstalliere",
|
||||
"UPDATING": "Aktualisiere..."
|
||||
},
|
||||
"CLIENT_TYPES": {
|
||||
"BETA": "Beta",
|
||||
"CLASSIC": "Classic",
|
||||
"CLASSICPTR": "Classic PTR",
|
||||
"RETAIL": "Retail",
|
||||
"RETAILPTR": "Retail PTR"
|
||||
},
|
||||
"DATES": {
|
||||
"DATETIME_SHORT": "{d, date, short} {d, time, short}",
|
||||
"DAYS_AGO": "Vor {count} {count, plural, one{Tag} other{Tagen}}",
|
||||
@@ -48,7 +63,7 @@
|
||||
"YESTERDAY": "Gestern"
|
||||
},
|
||||
"DEPENDENCY": {
|
||||
"TOOLTIP": "{dependencyCount} {dependencyCount, plural, one{Abhängigkeit} other{Abhängigkeiten} benötigt}"
|
||||
"TOOLTIP": "{dependencyCount} {dependencyCount, plural, one{Abhängigkeit} other{Abhängigkeiten}} benötigt"
|
||||
},
|
||||
"DOWNLOAD_COUNT": {
|
||||
"BILLION": "{count} Milliarden",
|
||||
@@ -88,6 +103,7 @@
|
||||
"ADDON_URL_INPUT_PLACEHOLDER": "z.B. GitHub oder WowInterface URL",
|
||||
"CLOSE_BUTTON": "Schließen",
|
||||
"DESCRIPTION": "Wenn Sie ein Addon direkt von einer URL installieren möchten, fügen Sie es unten in der Zeile ein.",
|
||||
"DOWNLOAD_COUNT": "{textCount} {count, plural, one{Download} other{Downloads}} auf {provider}",
|
||||
"ERROR": {
|
||||
"FAILED_TO_CONNECT": "Keine Verbindung mit der API möglich, warte ein wenig und versuche es erneut.",
|
||||
"INSTALL_FAILED": "Beim Versuch das Addon zu installieren ist etwas schief gelaufen, bitte versuche er erneut.\n\nWenn diese Nachricht weiterhin erscheint, kannst du auf unsrem Discord im Kamlen #wow-support nach Hilfe fragen.",
|
||||
@@ -227,6 +243,8 @@
|
||||
"START_WITH_SYSTEM_LABEL": "Starte WowUp mit dem System",
|
||||
"TELEMETRY_DESCRIPTION": "Helfen Sie WowUp zu verbessern, indem Sie anonyme Installationsdaten und/oder Fehler senden.",
|
||||
"TELEMETRY_LABEL": "Telemetrie",
|
||||
"THEME_DESCRIPTION": "Ändere das Farbschma nach deinen Wünschen",
|
||||
"THEME_LABEL": "Farbschema",
|
||||
"TITLE": "Applikation",
|
||||
"USE_HARDWARE_ACCELERATION_CONFIRMATION_LABEL": "Neustart der App?",
|
||||
"USE_HARDWARE_ACCELERATION_DESCRIPTION": "Das Deaktivieren der Hardwarebeschleunigung kann FPS Probleme und Probleme beim Rendern beheben. Eine Änderung dieser Einstellung benötigt einen Neustart der App.",
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
"QUIT_ACTION": "Quit",
|
||||
"SHOW_ACTION": "Show"
|
||||
},
|
||||
"THEME": {
|
||||
"ALLIANCE": "Alliance",
|
||||
"ALLIANCE_LIGHT": "Alliance Light",
|
||||
"DEFAULT": "Default",
|
||||
"DEFAULT_LIGHT": "Default Light",
|
||||
"HORDE": "Horde",
|
||||
"HORDE_LIGHT": "Horde Light"
|
||||
},
|
||||
"WOWUP_UPDATE": {
|
||||
"DOWNLOADED_TOOLTIP": "Install WowUp update",
|
||||
"INSTALL_MESSAGE": "Do you want to restart WowUp and install the update?",
|
||||
@@ -38,17 +46,24 @@
|
||||
"UNINSTALLING": "Uninstalling",
|
||||
"UPDATING": "Updating..."
|
||||
},
|
||||
"CLIENT_TYPES": {
|
||||
"BETA": "Beta",
|
||||
"CLASSIC": "Classic",
|
||||
"CLASSICPTR": "Classic PTR",
|
||||
"RETAIL": "Retail",
|
||||
"RETAILPTR": "Retail PTR"
|
||||
},
|
||||
"DATES": {
|
||||
"DATETIME_SHORT": "{d, date, short} {d, time, short}",
|
||||
"DAYS_AGO": "{count} {count, plural, one{day} other{days}} ago",
|
||||
"HOURS_AGO": "{count} {count, plural, one{hour} other{hours}} ago",
|
||||
"DAYS_AGO": "{count} {count, plural, =1{day} other{days}} ago",
|
||||
"HOURS_AGO": "{count} {count, plural, =1{hour} other{hours}} ago",
|
||||
"JUST_NOW": "Just now",
|
||||
"MONTHS_AGO": "{count} {count, plural, one{month} other{months}} ago",
|
||||
"YEARS_AGO": "{count} {count, plural, one{year} other{years}} ago",
|
||||
"MONTHS_AGO": "{count} {count, plural, =1{month} other{months}} ago",
|
||||
"YEARS_AGO": "{count} {count, plural, =1{year} other{years}} ago",
|
||||
"YESTERDAY": "Yesterday"
|
||||
},
|
||||
"DEPENDENCY": {
|
||||
"TOOLTIP": "{dependencyCount} required {dependencyCount, plural, one{dependency} other{dependencies}}"
|
||||
"TOOLTIP": "{dependencyCount} required {dependencyCount, plural, =1{dependency} other{dependencies}}"
|
||||
},
|
||||
"DOWNLOAD_COUNT": {
|
||||
"BILLION": "{count} billion",
|
||||
@@ -72,7 +87,7 @@
|
||||
"DIALOGS": {
|
||||
"ADDON_DETAILS": {
|
||||
"BY_AUTHOR": "By {authorName}",
|
||||
"DEPENDENCY_TEXT": "This addon has {dependencyCount} required {dependencyCount, plural, one{dependency} other{dependencies}}",
|
||||
"DEPENDENCY_TEXT": "This addon has {dependencyCount} required {dependencyCount, plural, =1{dependency} other{dependencies}}",
|
||||
"VIEW_IN_BROWSER_BUTTON": "View in browser",
|
||||
"VIEW_ON_PROVIDER_PREFIX": "View on"
|
||||
},
|
||||
@@ -88,6 +103,7 @@
|
||||
"ADDON_URL_INPUT_PLACEHOLDER": "Ex. GitHub or WowInterface URL",
|
||||
"CLOSE_BUTTON": "Close",
|
||||
"DESCRIPTION": "If you want to install an addon directly from a URL paste it below to get started.",
|
||||
"DOWNLOAD_COUNT": "{textCount} {count, plural, =1{download} other{downloads}} on {provider}",
|
||||
"ERROR": {
|
||||
"FAILED_TO_CONNECT": "Cannot connect to API, please wait a bit and try again.",
|
||||
"INSTALL_FAILED": "Something went wrong when trying to install the addon, please try again.\n\nIf this message keeps showing up, you can get help on Discord in the #wow-support channel.",
|
||||
@@ -190,7 +206,7 @@
|
||||
"CONFIRMATION_LESS_THAN_THREE": "Are you sure you want to remove the following {count} addons?",
|
||||
"CONFIRMATION_MORE_THAN_THREE": "Are you sure you want to remove the selected {count} addons?",
|
||||
"CONFIRMATION_ONE": "Are you sure you want to remove {addonName}?",
|
||||
"DEPENDENCY_MESSAGE": "{addonName} has {dependencyCount} {dependencyCount, plural, one{dependency} other{dependencies}}. Do you want to remove them also?",
|
||||
"DEPENDENCY_MESSAGE": "{addonName} has {dependencyCount} {dependencyCount, plural, =1{dependency} other{dependencies}}. Do you want to remove them also?",
|
||||
"DEPENDENCY_TITLE": "Remove Addon Dependencies?",
|
||||
"TITLE": "Uninstall {count, plural, =1{Addon} other{Addons}}?"
|
||||
},
|
||||
@@ -227,6 +243,8 @@
|
||||
"START_WITH_SYSTEM_LABEL": "Launch WowUp with system",
|
||||
"TELEMETRY_DESCRIPTION": "Help improve WowUp by sending anonymous install data and/or errors.",
|
||||
"TELEMETRY_LABEL": "Telemetry",
|
||||
"THEME_DESCRIPTION": "Change the color theme to whatever you like",
|
||||
"THEME_LABEL": "Color Theme",
|
||||
"TITLE": "Application",
|
||||
"USE_HARDWARE_ACCELERATION_CONFIRMATION_LABEL": "Do you want to restart?",
|
||||
"USE_HARDWARE_ACCELERATION_DESCRIPTION": "Disabling hardware acceleration might solve FPS issues and fix other rendering issues in this app. Changing this setting requires a restart.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user