mirror of
https://github.com/WowUp/WowUp.git
synced 2026-04-22 23:09:38 -04:00
Make the wtf explorer more robust
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as fs from "fs-extra";
|
||||
import * as path from "path";
|
||||
import { max } from "lodash";
|
||||
import { max, sumBy } from "lodash";
|
||||
import { TreeNode } from "../src/common/models/ipc-events";
|
||||
|
||||
export async function readDirRecursive(sourcePath: string): Promise<string[]> {
|
||||
const dirFiles: string[] = [];
|
||||
@@ -19,6 +20,39 @@ export async function readDirRecursive(sourcePath: string): Promise<string[]> {
|
||||
return dirFiles;
|
||||
}
|
||||
|
||||
export async function getDirTree(sourcePath: string): Promise<TreeNode> {
|
||||
const files = await fs.readdir(sourcePath, { withFileTypes: true });
|
||||
|
||||
const node: TreeNode = {
|
||||
name: path.basename(sourcePath),
|
||||
path: sourcePath,
|
||||
children: [],
|
||||
isDirectory: true,
|
||||
size: 0,
|
||||
};
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(sourcePath, file.name);
|
||||
if (file.isDirectory()) {
|
||||
const nestedNode = await getDirTree(filePath);
|
||||
node.children.push(nestedNode);
|
||||
node.size = sumBy(node.children, (n) => n.size);
|
||||
} else {
|
||||
const stats = await fs.stat(filePath);
|
||||
node.size += stats.size;
|
||||
node.children.push({
|
||||
name: file.name,
|
||||
path: filePath,
|
||||
children: [],
|
||||
isDirectory: false,
|
||||
size: stats.size,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
export async function getLastModifiedFileDate(sourcePath: string): Promise<number> {
|
||||
const dirFiles = await readDirRecursive(sourcePath);
|
||||
const dates: number[] = [];
|
||||
|
||||
@@ -66,6 +66,8 @@ import {
|
||||
IPC_WINDOW_LEAVE_FULLSCREEN,
|
||||
IPC_WOWUP_GET_SCAN_RESULTS,
|
||||
IPC_WRITE_FILE_CHANNEL,
|
||||
IPC_LIST_DIR_RECURSIVE,
|
||||
IPC_GET_DIRECTORY_TREE,
|
||||
} from "../src/common/constants";
|
||||
import { CurseFolderScanResult } from "../src/common/curse/curse-folder-scan-result";
|
||||
import { Addon } from "../src/common/entities/addon";
|
||||
@@ -73,13 +75,13 @@ import { CopyFileRequest } from "../src/common/models/copy-file-request";
|
||||
import { DownloadRequest } from "../src/common/models/download-request";
|
||||
import { DownloadStatus } from "../src/common/models/download-status";
|
||||
import { DownloadStatusType } from "../src/common/models/download-status-type";
|
||||
import { FsDirent, FsStats } from "../src/common/models/ipc-events";
|
||||
import { FsDirent, FsStats, TreeNode } from "../src/common/models/ipc-events";
|
||||
import { UnzipRequest } from "../src/common/models/unzip-request";
|
||||
import { RendererChannels } from "../src/common/wowup";
|
||||
import { MenuConfig, SystemTrayConfig, WowUpScanResult } from "../src/common/wowup/models";
|
||||
import { createAppMenu } from "./app-menu";
|
||||
import { CurseFolderScanner } from "./curse-folder-scanner";
|
||||
import { getLastModifiedFileDate } from "./file.utils";
|
||||
import { getDirTree, getLastModifiedFileDate, readDirRecursive } from "./file.utils";
|
||||
import { addonStore } from "./stores";
|
||||
import { createTray, restoreWindow } from "./system-tray";
|
||||
import { WowUpFolderScanner } from "./wowup-folder-scanner";
|
||||
@@ -403,6 +405,14 @@ export function initializeIpcHandlers(window: BrowserWindow, userAgent: string):
|
||||
return getLastModifiedFileDate(dirPath);
|
||||
});
|
||||
|
||||
handle(IPC_LIST_DIR_RECURSIVE, (evt, dirPath: string): Promise<string[]> => {
|
||||
return readDirRecursive(dirPath);
|
||||
});
|
||||
|
||||
handle(IPC_GET_DIRECTORY_TREE, (evt, dirPath: string): Promise<TreeNode> => {
|
||||
return getDirTree(dirPath);
|
||||
});
|
||||
|
||||
handle(IPC_MINIMIZE_WINDOW, () => {
|
||||
if (window?.minimizable) {
|
||||
window.minimize();
|
||||
|
||||
16
wowup-electron/package-lock.json
generated
16
wowup-electron/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wowup",
|
||||
"version": "2.5.0-beta.2",
|
||||
"version": "2.5.0-beta.4",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -3820,6 +3820,15 @@
|
||||
"@bbob/preset": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"@circlon/angular-tree-component": {
|
||||
"version": "11.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@circlon/angular-tree-component/-/angular-tree-component-11.0.4.tgz",
|
||||
"integrity": "sha512-Ck86mG6Z9eWG03RiOACDzrCjuzEDXU8rcEDi0aw0+Ku62x6ZY2mx8G0VX3CLEkS1BAXM2ef6luOIcoSKAKtDaA==",
|
||||
"requires": {
|
||||
"mobx": "~4.14.1",
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@csstools/convert-colors": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz",
|
||||
@@ -14357,6 +14366,11 @@
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"mobx": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-4.14.1.tgz",
|
||||
"integrity": "sha512-Oyg7Sr7r78b+QPYLufJyUmxTWcqeQ96S1nmtyur3QL8SeI6e0TqcKKcxbG+sVJLWANhHQkBW/mDmgG5DDC4fdw=="
|
||||
},
|
||||
"mocha": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz",
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
"@bbob/core": "2.7.0",
|
||||
"@bbob/html": "2.7.0",
|
||||
"@bbob/preset-html5": "2.7.0",
|
||||
"@circlon/angular-tree-component": "11.0.4",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.35",
|
||||
"@fortawesome/free-brands-svg-icons": "5.15.3",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="container">
|
||||
|
||||
<div class="account-container">
|
||||
<mat-form-field>
|
||||
<div class="account-container row align-items-center">
|
||||
<mat-form-field class="mr-3">
|
||||
<mat-label>{{ "PAGES.GET_ADDONS.CLIENT_TYPE_SELECT_LABEL" | translate }}</mat-label>
|
||||
<mat-select [(value)]="selectedInstallationId" (selectionChange)="onClientChange()">
|
||||
<mat-option *ngFor="let installation of installations" [value]="installation.id">
|
||||
@@ -9,6 +9,11 @@
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div>
|
||||
<button mat-raised-button color="primary" [disabled]="(loading$ | async) === true" (click)="onClickRefresh()">
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(loading$ | async) === true" class="account-container">
|
||||
@@ -20,8 +25,30 @@
|
||||
<p>{{error$ | async}}</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(loading$ | async) === false" class="">
|
||||
<div *ngFor="let account of accountMap | async" class="account-container">
|
||||
<div *ngIf="(loading$ | async) === false" class="tree-container">
|
||||
<p>{{wtfPath}}</p>
|
||||
<div class="tree">
|
||||
<tree-root [nodes]="nodes$ | async">
|
||||
<ng-template #treeNodeTemplate let-node let-index="index" let-templates="templates">
|
||||
<div class="node-wrapper">
|
||||
<div class="text-2" (click)="node.toggleExpanded()" *ngIf="node?.data?.children?.length > 0">
|
||||
<mat-icon class="tree-icon" [ngClass]="{ 'expanded': node?.isExpanded }" svgIcon="fas:chevron-right">
|
||||
</mat-icon>
|
||||
</div>
|
||||
|
||||
<div class="node-content-wrapper">
|
||||
<span
|
||||
[ngClass]="{ 'text-warning': !node.data.ignore && node.data.isLua && !node.data.hasAddon, 'text-3': node.data.ignore}">{{
|
||||
node.data.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</tree-root>
|
||||
</div>
|
||||
|
||||
<!-- <div *ngFor="let account of accountMap | async" class="account-container">
|
||||
<h2>Account: {{account.name}}</h2>
|
||||
<mat-accordion class="variable-accordion" multi>
|
||||
<mat-expansion-panel>
|
||||
@@ -72,6 +99,6 @@
|
||||
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,3 +18,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
padding: 1em;
|
||||
position: relative;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -7,11 +7,14 @@ import { Component, Input, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ElectronService } from "../../services";
|
||||
import { SessionService } from "../../services/session/session.service";
|
||||
import { WarcraftService } from "../../services/warcraft/warcraft.service";
|
||||
import { WtfService } from "../../services/wtf/wtf.service";
|
||||
import { WtfNode, WtfService } from "../../services/wtf/wtf.service";
|
||||
import { removeExtension } from "../../utils/string.utils";
|
||||
import { AddonFolder } from "../../models/wowup/addon-folder";
|
||||
import { WowInstallation } from "../../models/wowup/wow-installation";
|
||||
import { WarcraftInstallationService } from "../../services/warcraft/warcraft-installation.service";
|
||||
import { TreeNode } from "../../../common/models/ipc-events";
|
||||
import { formatSize } from "../../utils/number.utils";
|
||||
import path from "path/posix";
|
||||
|
||||
interface SavedVariable {
|
||||
name: string;
|
||||
@@ -41,6 +44,14 @@ interface AccountItem {
|
||||
sizeMb: string;
|
||||
}
|
||||
|
||||
interface NodeModel {
|
||||
name: string;
|
||||
isLua: boolean;
|
||||
ignore: boolean;
|
||||
hasAddon: boolean;
|
||||
children?: NodeModel[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "app-extra",
|
||||
templateUrl: "./extra.component.html",
|
||||
@@ -64,8 +75,10 @@ export class ExtraComponent implements OnInit, OnDestroy {
|
||||
public accountMap = new BehaviorSubject<AccountItem[]>([]);
|
||||
public loading$ = new BehaviorSubject<boolean>(false);
|
||||
public error$ = new BehaviorSubject<string>("");
|
||||
public nodes$ = new BehaviorSubject<NodeModel[]>([]);
|
||||
public installations: WowInstallation[] = [];
|
||||
public selectedInstallationId = "";
|
||||
public wtfPath = "";
|
||||
|
||||
public get selectedInstallationLabel(): string {
|
||||
return this.installations.find((inst) => inst.id === this.selectedInstallationId)?.label ?? "";
|
||||
@@ -84,25 +97,78 @@ export class ExtraComponent implements OnInit, OnDestroy {
|
||||
|
||||
public onClientChange(): void {
|
||||
const installation = this.installations.find((inst) => inst.id === this.selectedInstallationId);
|
||||
from(this.loadAccounts(installation))
|
||||
.pipe(first())
|
||||
.subscribe((accounts) => {
|
||||
this.accountMap.next(accounts);
|
||||
});
|
||||
|
||||
this.wtfPath = this._wtfService.getWtfPath(installation);
|
||||
|
||||
// from(this.loadAccounts(installation))
|
||||
// .pipe(first())
|
||||
// .subscribe((accounts) => {
|
||||
// this.accountMap.next(accounts);
|
||||
// });
|
||||
|
||||
from(this.loadWtfStructure(installation)).pipe(first()).subscribe();
|
||||
}
|
||||
|
||||
public onClickRefresh(): void {
|
||||
this.onClientChange();
|
||||
}
|
||||
|
||||
private lazyLoad() {
|
||||
console.debug("lazyLoad");
|
||||
this.loading$.next(true);
|
||||
this.error$.next("");
|
||||
this.installations = this._warcraftInstallationService.getWowInstallations();
|
||||
this.selectedInstallationId = this.installations[0]?.id ?? "";
|
||||
|
||||
from(this.loadAccounts(this.installations[0]))
|
||||
.pipe(first())
|
||||
.subscribe((accounts) => {
|
||||
this.accountMap.next(accounts);
|
||||
});
|
||||
const installation = this.installations[0];
|
||||
this.selectedInstallationId = installation?.id ?? "";
|
||||
|
||||
this.wtfPath = this._wtfService.getWtfPath(installation);
|
||||
|
||||
from(this.loadWtfStructure(installation)).pipe(first()).subscribe();
|
||||
|
||||
// from(this.loadAccounts(installation))
|
||||
// .pipe(first())
|
||||
// .subscribe((accounts) => {
|
||||
// this.accountMap.next(accounts);
|
||||
// });
|
||||
}
|
||||
|
||||
private async loadWtfStructure(installation: WowInstallation) {
|
||||
this.loading$.next(true);
|
||||
|
||||
try {
|
||||
const addonFolders = await this._warcraftService.listAddons(installation);
|
||||
const wtfTree = await this._wtfService.getWtfContents(installation);
|
||||
this.nodes$.next(wtfTree.children.map((tn) => this.getNode(tn, addonFolders)));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.error$.next(e.message);
|
||||
} finally {
|
||||
this.loading$.next(false);
|
||||
}
|
||||
}
|
||||
|
||||
private getNode(treeNode: WtfNode, addonFolders: AddonFolder[]): NodeModel {
|
||||
let name = `${treeNode.name} (${formatSize(treeNode.size)})`;
|
||||
if (treeNode.isDirectory) {
|
||||
name = `${treeNode.name} (${treeNode.children.length} files ${formatSize(treeNode.size)})`;
|
||||
}
|
||||
const nodeModel: NodeModel = {
|
||||
name: name,
|
||||
children: treeNode.children.map((tn) => this.getNode(tn, addonFolders)),
|
||||
hasAddon: false,
|
||||
isLua: treeNode.isLua,
|
||||
ignore: treeNode.ignore,
|
||||
};
|
||||
|
||||
if (treeNode.isLua) {
|
||||
nodeModel.hasAddon = this.addonFolderExists(treeNode.name, addonFolders);
|
||||
}
|
||||
|
||||
return nodeModel;
|
||||
}
|
||||
|
||||
private addonFolderExists(fileName: string, addonFolders: AddonFolder[]): boolean {
|
||||
return addonFolders.some((af) => af.name === removeExtension(fileName));
|
||||
}
|
||||
|
||||
private async loadAccounts(installation: WowInstallation): Promise<AccountItem[]> {
|
||||
@@ -113,7 +179,6 @@ export class ExtraComponent implements OnInit, OnDestroy {
|
||||
|
||||
const accounts = await this._wtfService.getAccounts(installation);
|
||||
const addonFolders = await this._warcraftService.listAddons(installation);
|
||||
console.log("addonFolders", addonFolders);
|
||||
|
||||
const accountMap: AccountItem[] = [];
|
||||
for (const account of accounts) {
|
||||
@@ -147,7 +212,6 @@ export class ExtraComponent implements OnInit, OnDestroy {
|
||||
addonFolders: AddonFolder[]
|
||||
): Promise<SavedVariable[]> {
|
||||
const globalVariables = await this._wtfService.getGlobalVariables(installation, account);
|
||||
console.debug("globalVariables", globalVariables);
|
||||
const gVars: SavedVariable[] = globalVariables.map((gv) => {
|
||||
return {
|
||||
hasAddon: addonFolders.some((af) => af.name === removeExtension(gv.name)),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { AgGridModule } from "ag-grid-angular";
|
||||
import { LIGHTBOX_CONFIG, LightboxModule } from "ng-gallery/lightbox";
|
||||
|
||||
import { CommonModule, DatePipe } from "@angular/common";
|
||||
import { NgModule } from "@angular/core";
|
||||
import { ReactiveFormsModule } from "@angular/forms";
|
||||
import { TreeModule } from "@circlon/angular-tree-component";
|
||||
|
||||
import { AddonDetailComponent } from "../../components/addon-detail/addon-detail.component";
|
||||
import { AddonInstallButtonComponent } from "../../components/addon-install-button/addon-install-button.component";
|
||||
@@ -14,6 +16,7 @@ import { CenteredSnackbarComponent } from "../../components/centered-snackbar/ce
|
||||
import { ConfirmDialogComponent } from "../../components/confirm-dialog/confirm-dialog.component";
|
||||
import { DateTooltipCellComponent } from "../../components/date-tooltip-cell/date-tooltip-cell.component";
|
||||
import { ExternalUrlConfirmationDialogComponent } from "../../components/external-url-confirmation-dialog/external-url-confirmation-dialog.component";
|
||||
import { ExtraComponent } from "../../components/extra/extra.component";
|
||||
import { FundingButtonComponent } from "../../components/funding-button/funding-button.component";
|
||||
import { GetAddonStatusColumnComponent } from "../../components/get-addon-status-column/get-addon-status-column.component";
|
||||
import { InstallFromProtocolDialogComponent } from "../../components/install-from-protocol-dialog/install-from-protocol-dialog.component";
|
||||
@@ -39,6 +42,7 @@ import { GetAddonListItemFilePropPipe } from "../../pipes/get-addon-list-item-fi
|
||||
import { InterfaceFormatPipe } from "../../pipes/interface-format.pipe";
|
||||
import { NgxDatePipe } from "../../pipes/ngx-date.pipe";
|
||||
import { RelativeDurationPipe } from "../../pipes/relative-duration-pipe";
|
||||
import { SizeDisplayPipe } from "../../pipes/size-display.pipe";
|
||||
import { TrustHtmlPipe } from "../../pipes/trust-html.pipe";
|
||||
import { SharedModule } from "../../shared.module";
|
||||
import { AboutComponent } from "../about/about.component";
|
||||
@@ -47,9 +51,6 @@ import { MyAddonsComponent } from "../my-addons/my-addons.component";
|
||||
import { OptionsComponent } from "../options/options.component";
|
||||
import { HomeRoutingModule } from "./home-routing.module";
|
||||
import { HomeComponent } from "./home.component";
|
||||
import { LightboxModule, LIGHTBOX_CONFIG } from "ng-gallery/lightbox";
|
||||
import { ExtraComponent } from "../../components/extra/extra.component";
|
||||
import { SizeDisplayPipe } from "../../pipes/size-display.pipe";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -108,6 +109,7 @@ import { SizeDisplayPipe } from "../../pipes/size-display.pipe";
|
||||
TableContextHeaderCellComponent,
|
||||
]),
|
||||
LightboxModule,
|
||||
TreeModule,
|
||||
],
|
||||
providers: [
|
||||
DatePipe,
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
import { formatSize } from "../utils/number.utils";
|
||||
|
||||
@Pipe({ name: "sizeDisplay" })
|
||||
export class SizeDisplayPipe implements PipeTransform {
|
||||
public constructor() {}
|
||||
|
||||
public transform(size: number): string {
|
||||
if (size < 1024) {
|
||||
return `${size} bytes`;
|
||||
}
|
||||
|
||||
const sizeKb = Math.round(size / 1024);
|
||||
if (sizeKb < 1024) {
|
||||
return `${sizeKb} kb`;
|
||||
}
|
||||
|
||||
const sizeMb = Math.round(size / 1024 / 1024);
|
||||
return `${sizeMb} mb`;
|
||||
return formatSize(size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ import {
|
||||
IPC_READDIR,
|
||||
IPC_READ_FILE_BUFFER_CHANNEL,
|
||||
IPC_GET_LATEST_DIR_UPDATE_TIME,
|
||||
IPC_LIST_DIR_RECURSIVE,
|
||||
IPC_GET_DIRECTORY_TREE,
|
||||
} from "../../../common/constants";
|
||||
import { CopyFileRequest } from "../../../common/models/copy-file-request";
|
||||
import { UnzipRequest } from "../../../common/models/unzip-request";
|
||||
import { FsDirent, FsStats } from "../../../common/models/ipc-events";
|
||||
import { FsDirent, FsStats, TreeNode } from "../../../common/models/ipc-events";
|
||||
import { ElectronService } from "../electron/electron.service";
|
||||
|
||||
@Injectable({
|
||||
@@ -120,6 +122,14 @@ export class FileService {
|
||||
return await this._electronService.invoke(IPC_GET_LATEST_DIR_UPDATE_TIME, dirPath);
|
||||
}
|
||||
|
||||
public async listDirectoryRecursive(dirPath: string): Promise<string[]> {
|
||||
return await this._electronService.invoke(IPC_LIST_DIR_RECURSIVE, dirPath);
|
||||
}
|
||||
|
||||
public async getDirectoryTree(dirPath: string): Promise<TreeNode> {
|
||||
return await this._electronService.invoke(IPC_GET_DIRECTORY_TREE, dirPath);
|
||||
}
|
||||
|
||||
public async writeFile(sourcePath: string, contents: string): Promise<string> {
|
||||
return await this._electronService.invoke(IPC_WRITE_FILE_CHANNEL, sourcePath, contents);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
faCog,
|
||||
faAngleUp,
|
||||
faAngleDown,
|
||||
faChevronRight,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faQuestionCircle, faClock } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faDiscord, faGithub, faPatreon } from "@fortawesome/free-brands-svg-icons";
|
||||
@@ -66,6 +67,7 @@ export class IconService {
|
||||
this.addSvg(faCog);
|
||||
this.addSvg(faAngleUp);
|
||||
this.addSvg(faAngleDown);
|
||||
this.addSvg(faChevronRight);
|
||||
}
|
||||
|
||||
private addSvg(icon: IconDefinition): void {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable } from "@angular/core";
|
||||
import * as path from "path";
|
||||
|
||||
import { Addon } from "../../../common/entities/addon";
|
||||
import { FsStats } from "../../../common/models/ipc-events";
|
||||
import { FsStats, TreeNode } from "../../../common/models/ipc-events";
|
||||
import { WowInstallation } from "../../models/wowup/wow-installation";
|
||||
import { FileService } from "../files/file.service";
|
||||
|
||||
@@ -16,12 +16,45 @@ export interface FileStats {
|
||||
stats: FsStats;
|
||||
}
|
||||
|
||||
export interface WtfEntry {
|
||||
name: string;
|
||||
children: WtfEntry[];
|
||||
}
|
||||
|
||||
export interface WtfNode extends TreeNode {
|
||||
isLua: boolean;
|
||||
ignore: boolean;
|
||||
children: WtfNode[];
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class WtfService {
|
||||
public constructor(private _fileService: FileService) {}
|
||||
|
||||
public async getWtfContents(installation: WowInstallation): Promise<WtfNode> {
|
||||
const wtfPath = this.getWtfPath(installation);
|
||||
const tree = await this._fileService.getDirectoryTree(wtfPath);
|
||||
return this.getWtfNode(tree);
|
||||
}
|
||||
|
||||
public getWtfNode(treeNode: TreeNode): WtfNode {
|
||||
const wtfNode: WtfNode = {
|
||||
...treeNode,
|
||||
isLua: path.extname(treeNode.name) === ".lua",
|
||||
ignore: this.shouldIgnoreFile(treeNode.name),
|
||||
children: treeNode.children.map((tn) => this.getWtfNode(tn)),
|
||||
};
|
||||
|
||||
return wtfNode;
|
||||
}
|
||||
|
||||
private shouldIgnoreFile(fileName: string): boolean {
|
||||
const canonName = fileName.toLowerCase();
|
||||
return canonName.endsWith(".lua.bak") || canonName.startsWith("blizzard_");
|
||||
}
|
||||
|
||||
public getWtfPath(installation: WowInstallation): string {
|
||||
return path.join(path.dirname(installation.location), WTF_FOLDER);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,20 @@ export function shortenDownloadCount(value: number, nDigit: number): string {
|
||||
return shortValue.toFixed(0);
|
||||
}
|
||||
|
||||
export function formatSize(size: number): string {
|
||||
if (size < 1024) {
|
||||
return `${size} bytes`;
|
||||
}
|
||||
|
||||
const sizeKb = Math.round(size / 1024);
|
||||
if (sizeKb < 1024) {
|
||||
return `${sizeKb} kb`;
|
||||
}
|
||||
|
||||
const sizeMb = Math.round(size / 1024 / 1024);
|
||||
return `${sizeMb} mb`;
|
||||
}
|
||||
|
||||
// This is a horrifying way to round to the nearest tens place
|
||||
export function roundDownloadCount(value: number): number {
|
||||
if (value < 10) {
|
||||
|
||||
@@ -78,6 +78,8 @@ export const IPC_CUSTOM_PROTOCOL_RECEIVED = "custom-protocol-received";
|
||||
export const IPC_ADDONS_SAVE_ALL = "addons-save-all";
|
||||
export const IPC_GET_PENDING_OPEN_URLS = "get-pending-open-urls";
|
||||
export const IPC_GET_LATEST_DIR_UPDATE_TIME = "get-latest-dir-update-time";
|
||||
export const IPC_LIST_DIR_RECURSIVE = "list-dir-recursive";
|
||||
export const IPC_GET_DIRECTORY_TREE = "get-directory-tree";
|
||||
export const IPC_SYSTEM_PREFERENCES_GET_USER_DEFAULT = "system-preferences-get-user-default";
|
||||
export const IPC_SHOW_OPEN_DIALOG = "show-open-dialog";
|
||||
export const IPC_APP_UPDATE_STATE = "app-update-state";
|
||||
|
||||
@@ -37,3 +37,11 @@ export interface FsStats {
|
||||
ctime: Date;
|
||||
birthtime: Date;
|
||||
}
|
||||
|
||||
export interface TreeNode {
|
||||
name: string;
|
||||
path: string;
|
||||
isDirectory: boolean;
|
||||
children: TreeNode[];
|
||||
size: number;
|
||||
}
|
||||
|
||||
4
wowup-electron/src/common/wowup.d.ts
vendored
4
wowup-electron/src/common/wowup.d.ts
vendored
@@ -72,7 +72,9 @@ declare type RendererChannels =
|
||||
| "system-preferences-get-user-default"
|
||||
| "show-open-dialog"
|
||||
| "app-install-update"
|
||||
| "update-app-badge";
|
||||
| "update-app-badge"
|
||||
| "list-dir-recursive"
|
||||
| "get-directory-tree";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
||||
@@ -601,6 +601,124 @@ $alliance-theme-light: mat.define-light-theme(
|
||||
background-color: var(--background-secondary-2);
|
||||
}
|
||||
|
||||
// TREE VIEW
|
||||
div.tree div.tree-children::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-left: 1px dotted var(--text-2);
|
||||
height: 100%;
|
||||
top: -14px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
div.tree {
|
||||
padding-left: 0;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
div.tree div.tree-children {
|
||||
position: relative;
|
||||
padding-left: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
div.tree div.tree-children::before {
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
div.tree tree-node > div > .node-wrapper {
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
div.tree tree-node > div > .node-wrapper > .node-content-wrapper {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
div.tree tree-node > div.tree-node-leaf > .node-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
div.tree tree-node > div::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-bottom: 1px dotted var(--text-2);
|
||||
width: 7px;
|
||||
margin-top: 12px;
|
||||
left: 7px;
|
||||
}
|
||||
|
||||
// div.tree tree-node > div .toggle-children-wrapper {
|
||||
// width: 13px;
|
||||
// height: 13px;
|
||||
// border: 1px solid var(--text-2);
|
||||
// position: absolute;
|
||||
// left: 0;
|
||||
// margin-top: 5px;
|
||||
// margin-left: 0;
|
||||
// display: inline-block;
|
||||
// z-index: 1;
|
||||
// }
|
||||
|
||||
// div.tree tree-node > div .toggle-children-wrapper::before {
|
||||
// content: "";
|
||||
// display: inline-block;
|
||||
// width: 7px;
|
||||
// border-top: 1px solid var(--text-2);
|
||||
// position: absolute;
|
||||
// top: 5px;
|
||||
// left: 2px;
|
||||
// }
|
||||
|
||||
// div.tree tree-node > div .toggle-children-wrapper.toggle-children-wrapper-collapsed::after {
|
||||
// content: "";
|
||||
// display: inline-block;
|
||||
// height: 7px;
|
||||
// border-left: 1px solid var(--text-2);
|
||||
// position: absolute;
|
||||
// top: 2px;
|
||||
// left: 5px;
|
||||
// }
|
||||
|
||||
div.tree tree-node > div .toggle-children-wrapper .toggle-children {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.tree tree-node > div .node-content-wrapper {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
div.tree > tree-node > div::before {
|
||||
left: 14px;
|
||||
}
|
||||
|
||||
div.tree > tree-node > div > .node-wrapper > tree-node-expander > .toggle-children-wrapper {
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.node-content-wrapper:hover {
|
||||
background-color: var(--background-secondary-4);
|
||||
}
|
||||
|
||||
.node-content-wrapper-active,
|
||||
.node-content-wrapper-focused,
|
||||
.node-content-wrapper:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.node-content-wrapper-focused,
|
||||
.node-content-wrapper-active,
|
||||
.node-content-wrapper.node-content-wrapper-active:hover,
|
||||
.node-content-wrapper-active.node-content-wrapper-focused {
|
||||
background-color: var(--background-secondary-3);
|
||||
}
|
||||
|
||||
.tree-icon {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.tree-icon.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
// LIGHT BOX
|
||||
|
||||
.lb-outerContainer {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "~@circlon/angular-tree-component/css/angular-tree-component.css";
|
||||
@import "./markdown.scss";
|
||||
@import "./custom-theme.scss";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user