From cddec0d9d496fb9395cdf5a62c919384a27a4620 Mon Sep 17 00:00:00 2001
From: Geomitron <22552797+Geomitron@users.noreply.github.com>
Date: Thu, 23 Nov 2023 13:00:30 -0600
Subject: [PATCH] Update formatting
---
package.json | 16 +
pnpm-lock.yaml | 1576 ++++++++++++++++-
src/app/app-routing.module.ts | 25 +-
src/app/app.component.html | 8 +-
src/app/app.component.ts | 19 +-
src/app/app.module.ts | 58 +-
.../components/browse/browse.component.html | 21 +-
.../components/browse/browse.component.scss | 2 +-
src/app/components/browse/browse.component.ts | 51 +-
.../chart-sidebar.component.html | 90 +-
.../chart-sidebar.component.scss | 2 +-
.../chart-sidebar/chart-sidebar.component.ts | 498 +++---
.../result-table-row.component.html | 17 +-
.../result-table-row.component.scss | 2 +-
.../result-table-row.component.ts | 57 +-
.../result-table/result-table.component.html | 57 +-
.../result-table/result-table.component.ts | 130 +-
.../search-bar/search-bar.component.html | 461 ++---
.../search-bar/search-bar.component.scss | 2 +-
.../browse/search-bar/search-bar.component.ts | 123 +-
.../downloads-modal.component.html | 52 +-
.../downloads-modal.component.scss | 2 +-
.../downloads-modal.component.ts | 81 +-
.../status-bar/status-bar.component.html | 80 +-
.../status-bar/status-bar.component.scss | 2 +-
.../browse/status-bar/status-bar.component.ts | 191 +-
.../settings/settings.component.html | 96 +-
.../settings/settings.component.scss | 2 +-
.../components/settings/settings.component.ts | 233 +--
.../components/toolbar/toolbar.component.html | 26 +-
.../components/toolbar/toolbar.component.scss | 2 +-
.../components/toolbar/toolbar.component.ts | 87 +-
src/app/core/directives/checkbox.directive.ts | 58 +-
.../core/directives/progress-bar.directive.ts | 33 +-
src/app/core/services/album-art.service.ts | 31 +-
src/app/core/services/download.service.ts | 143 +-
src/app/core/services/electron.service.ts | 117 +-
src/app/core/services/search.service.ts | 195 +-
src/app/core/services/selection.service.ts | 127 +-
src/app/core/services/settings.service.ts | 127 +-
src/app/core/tab-persist.strategy.ts | 42 +-
src/electron/ipc/CacheHandler.ipc.ts | 57 +-
src/electron/ipc/OpenURLHandler.ipc.ts | 19 +-
src/electron/ipc/SettingsHandler.ipc.ts | 145 +-
src/electron/ipc/UpdateHandler.ipc.ts | 151 +-
.../ipc/browse/AlbumArtHandler.ipc.ts | 20 +-
.../ipc/browse/BatchSongDetailsHandler.ipc.ts | 20 +-
src/electron/ipc/browse/SearchHandler.ipc.ts | 33 +-
.../ipc/browse/SongDetailsHandler.ipc.ts | 20 +-
src/electron/ipc/download/ChartDownload.ts | 470 ++---
src/electron/ipc/download/DownloadHandler.ts | 140 +-
src/electron/ipc/download/DownloadQueue.ts | 73 +-
src/electron/ipc/download/FileDownloader.ts | 574 +++---
src/electron/ipc/download/FileExtractor.ts | 263 +--
src/electron/ipc/download/FileTransfer.ts | 165 +-
.../ipc/download/FilesystemChecker.ts | 191 +-
src/electron/ipc/download/GoogleTimer.ts | 108 +-
src/electron/main.ts | 166 +-
src/electron/shared/ElectronUtilFunctions.ts | 11 +-
src/electron/shared/IPCHandler.ts | 161 +-
src/electron/shared/Paths.ts | 4 +-
src/electron/shared/Settings.ts | 18 +-
src/electron/shared/UtilFunctions.ts | 61 +-
.../shared/interfaces/download.interface.ts | 36 +-
.../shared/interfaces/search.interface.ts | 128 +-
.../interfaces/songDetails.interface.ts | 146 +-
.../shared/typeDefinitions/node-7z.d.ts | 1 -
src/environments/environment.prod.ts | 4 +-
src/environments/environment.ts | 4 +-
src/index.html | 20 +-
src/main.ts | 4 +-
src/polyfills.ts | 5 +-
src/styles.scss | 2 +-
src/typings.d.ts | 11 +-
tsconfig.json | 6 +-
75 files changed, 4900 insertions(+), 3279 deletions(-)
delete mode 100644 src/electron/shared/typeDefinitions/node-7z.d.ts
diff --git a/package.json b/package.json
index 3fad109..b39fb3e 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,10 @@
"devDependencies": {
"@angular-devkit/build-angular": "^17.0.3",
"@angular-eslint/builder": "17.1.0",
+ "@angular-eslint/eslint-plugin": "^17.1.0",
+ "@angular-eslint/eslint-plugin-template": "^17.1.0",
+ "@angular-eslint/schematics": "^17.1.0",
+ "@angular-eslint/template-parser": "^17.1.0",
"@angular/cli": "^17.0.3",
"@angular/compiler-cli": "^17.0.4",
"@angular/language-service": "^17.0.4",
@@ -65,10 +69,22 @@
"@types/randombytes": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
+ "concurrently": "^8.2.2",
"electron": "^27.1.2",
"electron-builder": "^23.6.0",
"eslint": "^8.54.0",
+ "eslint-config-prettier": "^9.0.0",
+ "eslint-plugin-import": "^2.29.0",
+ "eslint-plugin-jsdoc": "^46.9.0",
+ "eslint-plugin-prefer-arrow": "^1.2.3",
+ "eslint-plugin-prettier": "^5.0.1",
"nodemon": "^3.0.1",
+ "postcss": "^8.4.31",
+ "prettier": "^3.1.0",
+ "prettier-eslint": "^16.1.2",
+ "source-map-support": "^0.5.21",
+ "tailwindcss": "^3.3.5",
+ "tsx": "^4.4.0",
"typescript": "^5.2.2"
}
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6d0d412..8620cae 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -84,10 +84,22 @@ dependencies:
devDependencies:
'@angular-devkit/build-angular':
specifier: ^17.0.3
- version: 17.0.3(@angular/compiler-cli@17.0.4)(@types/node@18.16.0)(typescript@5.2.2)
+ version: 17.0.3(@angular/compiler-cli@17.0.4)(@types/node@18.16.0)(tailwindcss@3.3.5)(typescript@5.2.2)
'@angular-eslint/builder':
specifier: 17.1.0
version: 17.1.0(eslint@8.54.0)(typescript@5.2.2)
+ '@angular-eslint/eslint-plugin':
+ specifier: ^17.1.0
+ version: 17.1.0(eslint@8.54.0)(typescript@5.2.2)
+ '@angular-eslint/eslint-plugin-template':
+ specifier: ^17.1.0
+ version: 17.1.0(eslint@8.54.0)(typescript@5.2.2)
+ '@angular-eslint/schematics':
+ specifier: ^17.1.0
+ version: 17.1.0(@angular/cli@17.0.3)(eslint@8.54.0)(typescript@5.2.2)
+ '@angular-eslint/template-parser':
+ specifier: ^17.1.0
+ version: 17.1.0(eslint@8.54.0)(typescript@5.2.2)
'@angular/cli':
specifier: ^17.0.3
version: 17.0.3
@@ -118,6 +130,9 @@ devDependencies:
'@typescript-eslint/parser':
specifier: ^6.12.0
version: 6.12.0(eslint@8.54.0)(typescript@5.2.2)
+ concurrently:
+ specifier: ^8.2.2
+ version: 8.2.2
electron:
specifier: ^27.1.2
version: 27.1.2
@@ -127,9 +142,42 @@ devDependencies:
eslint:
specifier: ^8.54.0
version: 8.54.0
+ eslint-config-prettier:
+ specifier: ^9.0.0
+ version: 9.0.0(eslint@8.54.0)
+ eslint-plugin-import:
+ specifier: ^2.29.0
+ version: 2.29.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0)
+ eslint-plugin-jsdoc:
+ specifier: ^46.9.0
+ version: 46.9.0(eslint@8.54.0)
+ eslint-plugin-prefer-arrow:
+ specifier: ^1.2.3
+ version: 1.2.3(eslint@8.54.0)
+ eslint-plugin-prettier:
+ specifier: ^5.0.1
+ version: 5.0.1(eslint-config-prettier@9.0.0)(eslint@8.54.0)(prettier@3.1.0)
nodemon:
specifier: ^3.0.1
version: 3.0.1
+ postcss:
+ specifier: ^8.4.31
+ version: 8.4.31
+ prettier:
+ specifier: ^3.1.0
+ version: 3.1.0
+ prettier-eslint:
+ specifier: ^16.1.2
+ version: 16.1.2
+ source-map-support:
+ specifier: ^0.5.21
+ version: 0.5.21
+ tailwindcss:
+ specifier: ^3.3.5
+ version: 3.3.5
+ tsx:
+ specifier: ^4.4.0
+ version: 4.4.0
typescript:
specifier: ^5.2.2
version: 5.2.2
@@ -159,6 +207,11 @@ packages:
undici: 5.27.2
dev: false
+ /@alloc/quick-lru@5.2.0:
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+ dev: true
+
/@ampproject/remapping@2.2.1:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
@@ -177,7 +230,7 @@ packages:
- chokidar
dev: true
- /@angular-devkit/build-angular@17.0.3(@angular/compiler-cli@17.0.4)(@types/node@18.16.0)(typescript@5.2.2):
+ /@angular-devkit/build-angular@17.0.3(@angular/compiler-cli@17.0.4)(@types/node@18.16.0)(tailwindcss@3.3.5)(typescript@5.2.2):
resolution: {integrity: sha512-1lx0mERC1eTHX4vf8q7kUHZNHS0jwZxbwYHAISOplwHjkzRociX0W6rx04yMXn2NCSNhK+w3xbWyAIgyYbP9nA==}
engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
peerDependencies:
@@ -267,6 +320,7 @@ packages:
semver: 7.5.4
source-map-loader: 4.0.1(webpack@5.89.0)
source-map-support: 0.5.21
+ tailwindcss: 3.3.5
terser: 5.24.0
text-table: 0.2.0
tree-kill: 1.2.2
@@ -362,6 +416,90 @@ packages:
- debug
dev: true
+ /@angular-eslint/bundled-angular-compiler@17.1.0:
+ resolution: {integrity: sha512-Y+CN/8nQZaYjsb2b2sXbkQr0LrgBWhCzyLZ+rLfnLE60B9k4GeDt5b7z/OdSObi1xozXfqiaAZ1eXo0iQMN3JA==}
+ dev: true
+
+ /@angular-eslint/eslint-plugin-template@17.1.0(eslint@8.54.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-nL9VhChwFQLIRQM4xbTY8Vo095Q4/D77hPtqt3ShYIrORjYTwaWa8+neexToAqXVMapce7oFmFa/OqtxvEerLg==}
+ peerDependencies:
+ eslint: ^7.20.0 || ^8.0.0
+ typescript: '*'
+ dependencies:
+ '@angular-eslint/bundled-angular-compiler': 17.1.0
+ '@angular-eslint/utils': 17.1.0(eslint@8.54.0)(typescript@5.2.2)
+ '@typescript-eslint/type-utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
+ '@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
+ aria-query: 5.3.0
+ axobject-query: 4.0.0
+ eslint: 8.54.0
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@angular-eslint/eslint-plugin@17.1.0(eslint@8.54.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-pQac5h+XwsquDzaasK/xs9tjdQ/f9eLq8e5An9eXJGHWy4KcrMmQ1XrpaMMMg503LF3rRG/dHKBskGsYgSN9oQ==}
+ peerDependencies:
+ eslint: ^7.20.0 || ^8.0.0
+ typescript: '*'
+ dependencies:
+ '@angular-eslint/utils': 17.1.0(eslint@8.54.0)(typescript@5.2.2)
+ '@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
+ eslint: 8.54.0
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@angular-eslint/schematics@17.1.0(@angular/cli@17.0.3)(eslint@8.54.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-74gW1E5P4z3PvxNXOTXGaF6li/MLcSeJO8z7XtcP7wcXWu0fihOKlMJGgqB3rIcBa8lRcTDLekQERF+kRZ15aQ==}
+ peerDependencies:
+ '@angular/cli': '>= 17.0.0 < 18.0.0'
+ dependencies:
+ '@angular-eslint/eslint-plugin': 17.1.0(eslint@8.54.0)(typescript@5.2.2)
+ '@angular-eslint/eslint-plugin-template': 17.1.0(eslint@8.54.0)(typescript@5.2.2)
+ '@angular/cli': 17.0.3
+ '@nx/devkit': 17.1.2(nx@17.1.2)
+ ignore: 5.2.4
+ nx: 17.1.2
+ strip-json-comments: 3.1.1
+ tmp: 0.2.1
+ transitivePeerDependencies:
+ - '@swc-node/register'
+ - '@swc/core'
+ - debug
+ - eslint
+ - supports-color
+ - typescript
+ dev: true
+
+ /@angular-eslint/template-parser@17.1.0(eslint@8.54.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-CTxzB3stjynngTabdO8xTkiPc6Jvo15C2fxb1pYIlDIH2LgPJJxxCHi+IAt9oJpJOPa8QjLVF9VAXE3fLKAcpg==}
+ peerDependencies:
+ eslint: ^7.20.0 || ^8.0.0
+ typescript: '*'
+ dependencies:
+ '@angular-eslint/bundled-angular-compiler': 17.1.0
+ eslint: 8.54.0
+ eslint-scope: 7.2.2
+ typescript: 5.2.2
+ dev: true
+
+ /@angular-eslint/utils@17.1.0(eslint@8.54.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-AmG0xpRtnBQwrbHObonSilmD3hiFEtZHwFY3LT28VWxznB6WIAHFE7SrKWrRsRsXlib8LaRo4uobR5+MO8aLpw==}
+ peerDependencies:
+ eslint: ^7.20.0 || ^8.0.0
+ typescript: '*'
+ dependencies:
+ '@angular-eslint/bundled-angular-compiler': 17.1.0
+ '@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
+ eslint: 8.54.0
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@angular/animations@17.0.4(@angular/core@17.0.4):
resolution: {integrity: sha512-XHkTBZAoYf1t4Hb06RkYa6cgtjEA5JGq1ArXu/DckOS6G/ZuY+dwWULEmaf9ejJem8O78ol223ZQ5d7sXqpixQ==}
engines: {node: ^18.13.0 || >=20.9.0}
@@ -1815,6 +1953,15 @@ packages:
- supports-color
dev: true
+ /@es-joy/jsdoccomment@0.41.0:
+ resolution: {integrity: sha512-aKUhyn1QI5Ksbqcr3fFJj16p99QdjUxXAEuFst1Z47DRyoiMwivIH9MV/ARcJOCXVjPfjITciej8ZD2O/6qUmw==}
+ engines: {node: '>=16'}
+ dependencies:
+ comment-parser: 1.4.1
+ esquery: 1.5.0
+ jsdoc-type-pratt-parser: 4.0.0
+ dev: true
+
/@esbuild/android-arm64@0.18.17:
resolution: {integrity: sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==}
engines: {node: '>=12'}
@@ -1824,6 +1971,15 @@ packages:
dev: true
optional: true
+ /@esbuild/android-arm64@0.18.20:
+ resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/android-arm64@0.19.5:
resolution: {integrity: sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==}
engines: {node: '>=12'}
@@ -1842,6 +1998,15 @@ packages:
dev: true
optional: true
+ /@esbuild/android-arm@0.18.20:
+ resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/android-arm@0.19.5:
resolution: {integrity: sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==}
engines: {node: '>=12'}
@@ -1860,6 +2025,15 @@ packages:
dev: true
optional: true
+ /@esbuild/android-x64@0.18.20:
+ resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/android-x64@0.19.5:
resolution: {integrity: sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==}
engines: {node: '>=12'}
@@ -1878,6 +2052,15 @@ packages:
dev: true
optional: true
+ /@esbuild/darwin-arm64@0.18.20:
+ resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/darwin-arm64@0.19.5:
resolution: {integrity: sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==}
engines: {node: '>=12'}
@@ -1896,6 +2079,15 @@ packages:
dev: true
optional: true
+ /@esbuild/darwin-x64@0.18.20:
+ resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/darwin-x64@0.19.5:
resolution: {integrity: sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==}
engines: {node: '>=12'}
@@ -1914,6 +2106,15 @@ packages:
dev: true
optional: true
+ /@esbuild/freebsd-arm64@0.18.20:
+ resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/freebsd-arm64@0.19.5:
resolution: {integrity: sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==}
engines: {node: '>=12'}
@@ -1932,6 +2133,15 @@ packages:
dev: true
optional: true
+ /@esbuild/freebsd-x64@0.18.20:
+ resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/freebsd-x64@0.19.5:
resolution: {integrity: sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==}
engines: {node: '>=12'}
@@ -1950,6 +2160,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-arm64@0.18.20:
+ resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-arm64@0.19.5:
resolution: {integrity: sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==}
engines: {node: '>=12'}
@@ -1968,6 +2187,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-arm@0.18.20:
+ resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-arm@0.19.5:
resolution: {integrity: sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==}
engines: {node: '>=12'}
@@ -1986,6 +2214,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-ia32@0.18.20:
+ resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-ia32@0.19.5:
resolution: {integrity: sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==}
engines: {node: '>=12'}
@@ -2004,6 +2241,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-loong64@0.18.20:
+ resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-loong64@0.19.5:
resolution: {integrity: sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==}
engines: {node: '>=12'}
@@ -2022,6 +2268,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-mips64el@0.18.20:
+ resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-mips64el@0.19.5:
resolution: {integrity: sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==}
engines: {node: '>=12'}
@@ -2040,6 +2295,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-ppc64@0.18.20:
+ resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-ppc64@0.19.5:
resolution: {integrity: sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==}
engines: {node: '>=12'}
@@ -2058,6 +2322,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-riscv64@0.18.20:
+ resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-riscv64@0.19.5:
resolution: {integrity: sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==}
engines: {node: '>=12'}
@@ -2076,6 +2349,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-s390x@0.18.20:
+ resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-s390x@0.19.5:
resolution: {integrity: sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==}
engines: {node: '>=12'}
@@ -2094,6 +2376,15 @@ packages:
dev: true
optional: true
+ /@esbuild/linux-x64@0.18.20:
+ resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/linux-x64@0.19.5:
resolution: {integrity: sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==}
engines: {node: '>=12'}
@@ -2112,6 +2403,15 @@ packages:
dev: true
optional: true
+ /@esbuild/netbsd-x64@0.18.20:
+ resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/netbsd-x64@0.19.5:
resolution: {integrity: sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==}
engines: {node: '>=12'}
@@ -2130,6 +2430,15 @@ packages:
dev: true
optional: true
+ /@esbuild/openbsd-x64@0.18.20:
+ resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/openbsd-x64@0.19.5:
resolution: {integrity: sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==}
engines: {node: '>=12'}
@@ -2148,6 +2457,15 @@ packages:
dev: true
optional: true
+ /@esbuild/sunos-x64@0.18.20:
+ resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/sunos-x64@0.19.5:
resolution: {integrity: sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==}
engines: {node: '>=12'}
@@ -2166,6 +2484,15 @@ packages:
dev: true
optional: true
+ /@esbuild/win32-arm64@0.18.20:
+ resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/win32-arm64@0.19.5:
resolution: {integrity: sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==}
engines: {node: '>=12'}
@@ -2184,6 +2511,15 @@ packages:
dev: true
optional: true
+ /@esbuild/win32-ia32@0.18.20:
+ resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/win32-ia32@0.19.5:
resolution: {integrity: sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==}
engines: {node: '>=12'}
@@ -2202,6 +2538,15 @@ packages:
dev: true
optional: true
+ /@esbuild/win32-x64@0.18.20:
+ resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
/@esbuild/win32-x64@0.19.5:
resolution: {integrity: sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==}
engines: {node: '>=12'}
@@ -2716,6 +3061,18 @@ packages:
requiresBuild: true
optional: true
+ /@pkgr/utils@2.4.2:
+ resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ dependencies:
+ cross-spawn: 7.0.3
+ fast-glob: 3.3.2
+ is-glob: 4.0.3
+ open: 9.1.0
+ picocolors: 1.0.0
+ tslib: 2.6.2
+ dev: true
+
/@schematics/angular@17.0.3:
resolution: {integrity: sha512-pFHAqHMNm2WLoquJD4osSA/OAgH+wsFayPuqQnKjDEzeVW/YfJSbUksJ2iFt+uSfrhc/VxPf6pmGBMzi+9d0ng==}
engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
@@ -2922,6 +3279,10 @@ packages:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true
+ /@types/json5@0.0.29:
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ dev: true
+
/@types/jsonfile@6.1.4:
resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
dependencies:
@@ -3115,6 +3476,14 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/scope-manager@6.11.0:
+ resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.11.0
+ '@typescript-eslint/visitor-keys': 6.11.0
+ dev: true
+
/@typescript-eslint/scope-manager@6.12.0:
resolution: {integrity: sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -3123,6 +3492,26 @@ packages:
'@typescript-eslint/visitor-keys': 6.12.0
dev: true
+ /@typescript-eslint/type-utils@6.11.0(eslint@8.54.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
+ '@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
+ debug: 4.3.4
+ eslint: 8.54.0
+ ts-api-utils: 1.0.3(typescript@5.2.2)
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/type-utils@6.12.0(eslint@8.54.0)(typescript@5.2.2):
resolution: {integrity: sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -3143,11 +3532,37 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/types@6.11.0:
+ resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dev: true
+
/@typescript-eslint/types@6.12.0:
resolution: {integrity: sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==}
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
+ /@typescript-eslint/typescript-estree@6.11.0(typescript@5.2.2):
+ resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 6.11.0
+ '@typescript-eslint/visitor-keys': 6.11.0
+ debug: 4.3.4
+ globby: 11.1.0
+ is-glob: 4.0.3
+ semver: 7.5.4
+ ts-api-utils: 1.0.3(typescript@5.2.2)
+ typescript: 5.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/typescript-estree@6.12.0(typescript@5.2.2):
resolution: {integrity: sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -3169,6 +3584,25 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/utils@6.11.0(eslint@8.54.0)(typescript@5.2.2):
+ resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.6
+ '@typescript-eslint/scope-manager': 6.11.0
+ '@typescript-eslint/types': 6.11.0
+ '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
+ eslint: 8.54.0
+ semver: 7.5.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
/@typescript-eslint/utils@6.12.0(eslint@8.54.0)(typescript@5.2.2):
resolution: {integrity: sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -3188,6 +3622,14 @@ packages:
- typescript
dev: true
+ /@typescript-eslint/visitor-keys@6.11.0:
+ resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.11.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
/@typescript-eslint/visitor-keys@6.12.0:
resolution: {integrity: sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -3522,7 +3964,6 @@ packages:
/ansi-regex@2.1.1:
resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
engines: {node: '>=0.10.0'}
- dev: false
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@@ -3535,7 +3976,6 @@ packages:
/ansi-styles@2.2.1:
resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
engines: {node: '>=0.10.0'}
- dev: false
/ansi-styles@3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
@@ -3564,6 +4004,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+ dev: true
+
/any-shell-escape@0.1.1:
resolution: {integrity: sha512-36j4l5HVkboyRhIWgtMh1I9i8LTdFqVwDEHy1cp+QioJyKgAUG40X0W8s7jakWRta/Sjvm8mUG1fU6Tj8mWagQ==}
dev: false
@@ -3634,6 +4078,15 @@ packages:
resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==}
dev: false
+ /are-docs-informative@0.0.2:
+ resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==}
+ engines: {node: '>=14'}
+ dev: true
+
+ /arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+ dev: true
+
/argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
dependencies:
@@ -3642,6 +4095,12 @@ packages:
/argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ /aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+ dependencies:
+ dequal: 2.0.3
+ dev: true
+
/arr-diff@1.1.0:
resolution: {integrity: sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==}
engines: {node: '>=0.10.0'}
@@ -3684,6 +4143,13 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /array-buffer-byte-length@1.0.0:
+ resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
+ dependencies:
+ call-bind: 1.0.5
+ is-array-buffer: 3.0.2
+ dev: true
+
/array-differ@1.0.0:
resolution: {integrity: sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==}
engines: {node: '>=0.10.0'}
@@ -3702,6 +4168,17 @@ packages:
resolution: {integrity: sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==}
dev: true
+ /array-includes@3.1.7:
+ resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ get-intrinsic: 1.2.2
+ is-string: 1.0.7
+ dev: true
+
/array-initial@1.1.0:
resolution: {integrity: sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==}
engines: {node: '>=0.10.0'}
@@ -3757,6 +4234,50 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /array.prototype.findlastindex@1.2.3:
+ resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.2
+ get-intrinsic: 1.2.2
+ dev: true
+
+ /array.prototype.flat@1.3.2:
+ resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.2
+ dev: true
+
+ /array.prototype.flatmap@1.3.2:
+ resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ es-shim-unscopables: 1.0.2
+ dev: true
+
+ /arraybuffer.prototype.slice@1.0.2:
+ resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ array-buffer-byte-length: 1.0.0
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ get-intrinsic: 1.2.2
+ is-array-buffer: 3.0.2
+ is-shared-array-buffer: 1.0.2
+ dev: true
+
/asar@3.2.0:
resolution: {integrity: sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==}
engines: {node: '>=10.12.0'}
@@ -3865,6 +4386,11 @@ packages:
postcss: 8.4.31
postcss-value-parser: 4.2.0
+ /available-typed-arrays@1.0.5:
+ resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
/axios@0.21.4(debug@4.3.2):
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
@@ -3883,6 +4409,12 @@ packages:
- debug
dev: true
+ /axobject-query@4.0.0:
+ resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==}
+ dependencies:
+ dequal: 2.0.3
+ dev: true
+
/babel-loader@9.1.3(@babel/core@7.23.2)(webpack@5.89.0):
resolution: {integrity: sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==}
engines: {node: '>= 14.15.0'}
@@ -4004,6 +4536,11 @@ packages:
cli-table: 0.3.11
dev: false
+ /big-integer@1.6.52:
+ resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
+ engines: {node: '>=0.6'}
+ dev: true
+
/big.js@5.2.2:
resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==}
dev: true
@@ -4090,6 +4627,13 @@ packages:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
dev: false
+ /bplist-parser@0.2.0:
+ resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
+ engines: {node: '>= 5.10.0'}
+ dependencies:
+ big-integer: 1.6.52
+ dev: true
+
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
@@ -4286,12 +4830,24 @@ packages:
- supports-color
dev: true
+ /builtin-modules@3.3.0:
+ resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
+ engines: {node: '>=6'}
+ dev: true
+
/builtins@5.0.1:
resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
dependencies:
semver: 7.5.4
dev: true
+ /bundle-name@3.0.0:
+ resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
+ engines: {node: '>=12'}
+ dependencies:
+ run-applescript: 5.0.0
+ dev: true
+
/bytes@3.0.0:
resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
engines: {node: '>= 0.8'}
@@ -4363,6 +4919,11 @@ packages:
engines: {node: '>=6'}
dev: true
+ /camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+ dev: true
+
/camelcase@3.0.0:
resolution: {integrity: sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==}
engines: {node: '>=0.10.0'}
@@ -4385,7 +4946,6 @@ packages:
has-ansi: 2.0.0
strip-ansi: 3.0.1
supports-color: 2.0.0
- dev: false
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
@@ -4686,15 +5246,30 @@ packages:
graceful-readlink: 1.0.1
dev: true
+ /commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+ dev: true
+
/commander@5.1.0:
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
engines: {node: '>= 6'}
dev: true
+ /comment-parser@1.4.1:
+ resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
+ engines: {node: '>= 12.0.0'}
+ dev: true
+
/common-path-prefix@3.0.0:
resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==}
dev: true
+ /common-tags@1.8.2:
+ resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
+ engines: {node: '>=4.0.0'}
+ dev: true
+
/comparators@3.0.5:
resolution: {integrity: sha512-xWeNJYdL57+ySi53YVnHFFKBI7SF/o7xMHDXGVD5bX7lb4ECPmKWXwrOr65lZRvowqgb9VHFt+aPr4TjqvyyiQ==}
dev: false
@@ -4749,6 +5324,22 @@ packages:
source-map: 0.6.1
dev: false
+ /concurrently@8.2.2:
+ resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==}
+ engines: {node: ^14.13.0 || >=16.0.0}
+ hasBin: true
+ dependencies:
+ chalk: 4.1.2
+ date-fns: 2.30.0
+ lodash: 4.17.21
+ rxjs: 7.8.1
+ shell-quote: 1.8.1
+ spawn-command: 0.0.2
+ supports-color: 8.1.1
+ tree-kill: 1.2.2
+ yargs: 17.7.2
+ dev: true
+
/config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
dependencies:
@@ -4968,6 +5559,13 @@ packages:
type: 1.2.0
dev: false
+ /date-fns@2.30.0:
+ resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
+ engines: {node: '>=0.11'}
+ dependencies:
+ '@babel/runtime': 7.23.2
+ dev: true
+
/dateformat@2.2.0:
resolution: {integrity: sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==}
dev: false
@@ -5048,6 +5646,24 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /default-browser-id@3.0.0:
+ resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==}
+ engines: {node: '>=12'}
+ dependencies:
+ bplist-parser: 0.2.0
+ untildify: 4.0.0
+ dev: true
+
+ /default-browser@4.0.0:
+ resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==}
+ engines: {node: '>=14.16'}
+ dependencies:
+ bundle-name: 3.0.0
+ default-browser-id: 3.0.0
+ execa: 7.2.0
+ titleize: 3.0.0
+ dev: true
+
/default-compare@1.0.0:
resolution: {integrity: sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==}
engines: {node: '>=0.10.0'}
@@ -5089,6 +5705,11 @@ packages:
engines: {node: '>=8'}
dev: true
+ /define-lazy-prop@3.0.0:
+ resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+ engines: {node: '>=12'}
+ dev: true
+
/define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
@@ -5152,6 +5773,11 @@ packages:
resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
dev: false
+ /dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+ dev: true
+
/destroy@1.0.4:
resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==}
dev: true
@@ -5180,6 +5806,10 @@ packages:
hasBin: true
dev: true
+ /didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ dev: true
+
/diff-sequences@29.6.3:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -5206,6 +5836,10 @@ packages:
dependencies:
path-type: 4.0.0
+ /dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+ dev: true
+
/dmg-builder@23.6.0:
resolution: {integrity: sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA==}
dependencies:
@@ -5250,6 +5884,13 @@ packages:
'@leichtgewicht/ip-codec': 2.0.4
dev: true
+ /doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ esutils: 2.0.3
+ dev: true
+
/doctrine@3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
@@ -5586,10 +6227,79 @@ packages:
dependencies:
is-arrayish: 0.2.1
+ /es-abstract@1.22.3:
+ resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ array-buffer-byte-length: 1.0.0
+ arraybuffer.prototype.slice: 1.0.2
+ available-typed-arrays: 1.0.5
+ call-bind: 1.0.5
+ es-set-tostringtag: 2.0.2
+ es-to-primitive: 1.2.1
+ function.prototype.name: 1.1.6
+ get-intrinsic: 1.2.2
+ get-symbol-description: 1.0.0
+ globalthis: 1.0.3
+ gopd: 1.0.1
+ has-property-descriptors: 1.0.1
+ has-proto: 1.0.1
+ has-symbols: 1.0.3
+ hasown: 2.0.0
+ internal-slot: 1.0.6
+ is-array-buffer: 3.0.2
+ is-callable: 1.2.7
+ is-negative-zero: 2.0.2
+ is-regex: 1.1.4
+ is-shared-array-buffer: 1.0.2
+ is-string: 1.0.7
+ is-typed-array: 1.1.12
+ is-weakref: 1.0.2
+ object-inspect: 1.13.1
+ object-keys: 1.1.1
+ object.assign: 4.1.4
+ regexp.prototype.flags: 1.5.1
+ safe-array-concat: 1.0.1
+ safe-regex-test: 1.0.0
+ string.prototype.trim: 1.2.8
+ string.prototype.trimend: 1.0.7
+ string.prototype.trimstart: 1.0.7
+ typed-array-buffer: 1.0.0
+ typed-array-byte-length: 1.0.0
+ typed-array-byte-offset: 1.0.0
+ typed-array-length: 1.0.4
+ unbox-primitive: 1.0.2
+ which-typed-array: 1.1.13
+ dev: true
+
/es-module-lexer@1.4.1:
resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==}
dev: true
+ /es-set-tostringtag@2.0.2:
+ resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.2.2
+ has-tostringtag: 1.0.0
+ hasown: 2.0.0
+ dev: true
+
+ /es-shim-unscopables@1.0.2:
+ resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
+ dependencies:
+ hasown: 2.0.0
+ dev: true
+
+ /es-to-primitive@1.2.1:
+ resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.0.5
+ is-symbol: 1.0.4
+ dev: true
+
/es5-ext@0.10.62:
resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==}
engines: {node: '>=0.10'}
@@ -5665,6 +6375,36 @@ packages:
'@esbuild/win32-x64': 0.18.17
dev: true
+ /esbuild@0.18.20:
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/android-arm': 0.18.20
+ '@esbuild/android-arm64': 0.18.20
+ '@esbuild/android-x64': 0.18.20
+ '@esbuild/darwin-arm64': 0.18.20
+ '@esbuild/darwin-x64': 0.18.20
+ '@esbuild/freebsd-arm64': 0.18.20
+ '@esbuild/freebsd-x64': 0.18.20
+ '@esbuild/linux-arm': 0.18.20
+ '@esbuild/linux-arm64': 0.18.20
+ '@esbuild/linux-ia32': 0.18.20
+ '@esbuild/linux-loong64': 0.18.20
+ '@esbuild/linux-mips64el': 0.18.20
+ '@esbuild/linux-ppc64': 0.18.20
+ '@esbuild/linux-riscv64': 0.18.20
+ '@esbuild/linux-s390x': 0.18.20
+ '@esbuild/linux-x64': 0.18.20
+ '@esbuild/netbsd-x64': 0.18.20
+ '@esbuild/openbsd-x64': 0.18.20
+ '@esbuild/sunos-x64': 0.18.20
+ '@esbuild/win32-arm64': 0.18.20
+ '@esbuild/win32-ia32': 0.18.20
+ '@esbuild/win32-x64': 0.18.20
+ dev: true
+
/esbuild@0.19.5:
resolution: {integrity: sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==}
engines: {node: '>=12'}
@@ -5717,6 +6457,138 @@ packages:
engines: {node: '>=12'}
dev: true
+ /eslint-config-prettier@9.0.0(eslint@8.54.0):
+ resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+ dependencies:
+ eslint: 8.54.0
+ dev: true
+
+ /eslint-import-resolver-node@0.3.9:
+ resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
+ dependencies:
+ debug: 3.2.7(supports-color@5.5.0)
+ is-core-module: 2.13.1
+ resolve: 1.22.8
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.12.0)(eslint-import-resolver-node@0.3.9)(eslint@8.54.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 6.12.0(eslint@8.54.0)(typescript@5.2.2)
+ debug: 3.2.7(supports-color@5.5.0)
+ eslint: 8.54.0
+ eslint-import-resolver-node: 0.3.9
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0):
+ resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 6.12.0(eslint@8.54.0)(typescript@5.2.2)
+ array-includes: 3.1.7
+ array.prototype.findlastindex: 1.2.3
+ array.prototype.flat: 1.3.2
+ array.prototype.flatmap: 1.3.2
+ debug: 3.2.7(supports-color@5.5.0)
+ doctrine: 2.1.0
+ eslint: 8.54.0
+ eslint-import-resolver-node: 0.3.9
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.12.0)(eslint-import-resolver-node@0.3.9)(eslint@8.54.0)
+ hasown: 2.0.0
+ is-core-module: 2.13.1
+ is-glob: 4.0.3
+ minimatch: 3.1.2
+ object.fromentries: 2.0.7
+ object.groupby: 1.0.1
+ object.values: 1.1.7
+ semver: 6.3.1
+ tsconfig-paths: 3.14.2
+ transitivePeerDependencies:
+ - eslint-import-resolver-typescript
+ - eslint-import-resolver-webpack
+ - supports-color
+ dev: true
+
+ /eslint-plugin-jsdoc@46.9.0(eslint@8.54.0):
+ resolution: {integrity: sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ dependencies:
+ '@es-joy/jsdoccomment': 0.41.0
+ are-docs-informative: 0.0.2
+ comment-parser: 1.4.1
+ debug: 4.3.4
+ escape-string-regexp: 4.0.0
+ eslint: 8.54.0
+ esquery: 1.5.0
+ is-builtin-module: 3.2.1
+ semver: 7.5.4
+ spdx-expression-parse: 3.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-plugin-prefer-arrow@1.2.3(eslint@8.54.0):
+ resolution: {integrity: sha512-J9I5PKCOJretVuiZRGvPQxCbllxGAV/viI20JO3LYblAodofBxyMnZAJ+WGeClHgANnSJberTNoFWWjrWKBuXQ==}
+ peerDependencies:
+ eslint: '>=2.0.0'
+ dependencies:
+ eslint: 8.54.0
+ dev: true
+
+ /eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.0.0)(eslint@8.54.0)(prettier@3.1.0):
+ resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ '@types/eslint': '>=8.0.0'
+ eslint: '>=8.0.0'
+ eslint-config-prettier: '*'
+ prettier: '>=3.0.0'
+ peerDependenciesMeta:
+ '@types/eslint':
+ optional: true
+ eslint-config-prettier:
+ optional: true
+ dependencies:
+ eslint: 8.54.0
+ eslint-config-prettier: 9.0.0(eslint@8.54.0)
+ prettier: 3.1.0
+ prettier-linter-helpers: 1.0.0
+ synckit: 0.8.5
+ dev: true
+
/eslint-scope@5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
@@ -5869,6 +6741,21 @@ packages:
strip-final-newline: 2.0.0
dev: true
+ /execa@7.2.0:
+ resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==}
+ engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
+ dependencies:
+ cross-spawn: 7.0.3
+ get-stream: 6.0.1
+ human-signals: 4.3.1
+ is-stream: 3.0.0
+ merge-stream: 2.0.0
+ npm-run-path: 5.1.0
+ onetime: 6.0.0
+ signal-exit: 3.0.7
+ strip-final-newline: 3.0.0
+ dev: true
+
/expand-brackets@2.1.4:
resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
engines: {node: '>=0.10.0'}
@@ -6024,6 +6911,10 @@ packages:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true
+ /fast-diff@1.3.0:
+ resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+ dev: true
+
/fast-glob@3.3.1:
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
engines: {node: '>=8.6.0'}
@@ -6326,6 +7217,12 @@ packages:
- supports-color
dev: false
+ /for-each@0.3.3:
+ resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
+ dependencies:
+ is-callable: 1.2.7
+ dev: true
+
/for-in@1.0.2:
resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
engines: {node: '>=0.10.0'}
@@ -6477,6 +7374,20 @@ packages:
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ /function.prototype.name@1.1.6:
+ resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ functions-have-names: 1.2.3
+ dev: true
+
+ /functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+ dev: true
+
/gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@@ -6523,6 +7434,20 @@ packages:
engines: {node: '>=10'}
dev: true
+ /get-symbol-description@1.0.0:
+ resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ get-intrinsic: 1.2.2
+ dev: true
+
+ /get-tsconfig@4.7.2:
+ resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+ dev: true
+
/get-value@2.0.6:
resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==}
engines: {node: '>=0.10.0'}
@@ -6625,6 +7550,17 @@ packages:
path-is-absolute: 1.0.1
dev: true
+ /glob@7.1.6:
+ resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
/glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
dependencies:
@@ -6686,7 +7622,6 @@ packages:
requiresBuild: true
dependencies:
define-properties: 1.2.1
- optional: true
/globby@11.1.0:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
@@ -7070,7 +8005,10 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
ansi-regex: 2.1.1
- dev: false
+
+ /has-bigints@1.0.2:
+ resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
+ dev: true
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
@@ -7100,6 +8038,13 @@ packages:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
+ /has-tostringtag@1.0.0:
+ resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
/has-value@0.3.1:
resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==}
engines: {node: '>=0.10.0'}
@@ -7311,6 +8256,11 @@ packages:
engines: {node: '>=10.17.0'}
dev: true
+ /human-signals@4.3.1:
+ resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
+ engines: {node: '>=14.18.0'}
+ dev: true
+
/iconv-corefoundation@1.1.7:
resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==}
engines: {node: ^8.11.2 || >=10}
@@ -7357,6 +8307,11 @@ packages:
minimatch: 9.0.3
dev: true
+ /ignore@5.2.4:
+ resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
+ engines: {node: '>= 4'}
+ dev: true
+
/ignore@5.3.0:
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
engines: {node: '>= 4'}
@@ -7463,6 +8418,15 @@ packages:
wrap-ansi: 6.2.0
dev: true
+ /internal-slot@1.0.6:
+ resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ get-intrinsic: 1.2.2
+ hasown: 2.0.0
+ side-channel: 1.0.4
+ dev: true
+
/interpret@1.4.0:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
@@ -7507,9 +8471,23 @@ packages:
hasown: 2.0.0
dev: false
+ /is-array-buffer@3.0.2:
+ resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
+ dependencies:
+ call-bind: 1.0.5
+ get-intrinsic: 1.2.2
+ is-typed-array: 1.1.12
+ dev: true
+
/is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ /is-bigint@1.0.4:
+ resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
+ dependencies:
+ has-bigints: 1.0.2
+ dev: true
+
/is-binary-path@1.0.1:
resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==}
engines: {node: '>=0.10.0'}
@@ -7524,10 +8502,30 @@ packages:
binary-extensions: 2.2.0
dev: true
+ /is-boolean-object@1.1.2:
+ resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ has-tostringtag: 1.0.0
+ dev: true
+
/is-buffer@1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
dev: false
+ /is-builtin-module@3.2.1:
+ resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==}
+ engines: {node: '>=6'}
+ dependencies:
+ builtin-modules: 3.3.0
+ dev: true
+
+ /is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
/is-ci@3.0.1:
resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
hasBin: true
@@ -7547,6 +8545,13 @@ packages:
hasown: 2.0.0
dev: false
+ /is-date-object@1.0.5:
+ resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
/is-descriptor@0.1.7:
resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==}
engines: {node: '>= 0.4'}
@@ -7569,6 +8574,12 @@ packages:
hasBin: true
dev: true
+ /is-docker@3.0.0:
+ resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+ dev: true
+
/is-extendable@0.1.1:
resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'}
@@ -7609,6 +8620,14 @@ packages:
dependencies:
is-extglob: 2.1.1
+ /is-inside-container@1.0.0:
+ resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+ engines: {node: '>=14.16'}
+ hasBin: true
+ dependencies:
+ is-docker: 3.0.0
+ dev: true
+
/is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
@@ -7622,12 +8641,24 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /is-negative-zero@2.0.2:
+ resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
/is-number-like@1.0.8:
resolution: {integrity: sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==}
dependencies:
lodash.isfinite: 3.3.2
dev: true
+ /is-number-object@1.0.7:
+ resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
/is-number@3.0.0:
resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==}
engines: {node: '>=0.10.0'}
@@ -7678,6 +8709,14 @@ packages:
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
dev: false
+ /is-regex@1.1.4:
+ resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ has-tostringtag: 1.0.0
+ dev: true
+
/is-relative@1.0.0:
resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==}
engines: {node: '>=0.10.0'}
@@ -7685,11 +8724,43 @@ packages:
is-unc-path: 1.0.0
dev: false
+ /is-shared-array-buffer@1.0.2:
+ resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
+ dependencies:
+ call-bind: 1.0.5
+ dev: true
+
/is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
dev: true
+ /is-stream@3.0.0:
+ resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dev: true
+
+ /is-string@1.0.7:
+ resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-tostringtag: 1.0.0
+ dev: true
+
+ /is-symbol@1.0.4:
+ resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ has-symbols: 1.0.3
+ dev: true
+
+ /is-typed-array@1.1.12:
+ resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ which-typed-array: 1.1.13
+ dev: true
+
/is-unc-path@1.0.0:
resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==}
engines: {node: '>=0.10.0'}
@@ -7715,6 +8786,12 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /is-weakref@1.0.2:
+ resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
+ dependencies:
+ call-bind: 1.0.5
+ dev: true
+
/is-what@3.14.1:
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
@@ -7742,6 +8819,10 @@ packages:
/isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
+ /isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+ dev: true
+
/isbinaryfile@3.0.3:
resolution: {integrity: sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==}
engines: {node: '>=0.6.0'}
@@ -7880,6 +8961,11 @@ packages:
dependencies:
argparse: 2.0.1
+ /jsdoc-type-pratt-parser@4.0.0:
+ resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==}
+ engines: {node: '>=12.0.0'}
+ dev: true
+
/jsesc@0.5.0:
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
hasBin: true
@@ -7919,6 +9005,13 @@ packages:
requiresBuild: true
optional: true
+ /json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.8
+ dev: true
+
/json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -8108,6 +9201,16 @@ packages:
- supports-color
dev: false
+ /lilconfig@2.1.0:
+ resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /lilconfig@3.0.0:
+ resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
+ engines: {node: '>=14'}
+ dev: true
+
/limiter@1.1.5:
resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==}
dev: true
@@ -8378,6 +9481,18 @@ packages:
chalk: 4.1.2
is-unicode-supported: 0.1.0
+ /loglevel-colored-level-prefix@1.0.0:
+ resolution: {integrity: sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==}
+ dependencies:
+ chalk: 1.1.3
+ loglevel: 1.8.1
+ dev: true
+
+ /loglevel@1.8.1:
+ resolution: {integrity: sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==}
+ engines: {node: '>= 0.6.0'}
+ dev: true
+
/lowercase-keys@2.0.0:
resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
engines: {node: '>=8'}
@@ -8603,6 +9718,11 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
+ /mimic-fn@4.0.0:
+ resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
+ engines: {node: '>=12'}
+ dev: true
+
/mimic-response@1.0.1:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
engines: {node: '>=4'}
@@ -8810,6 +9930,14 @@ packages:
rimraf: 2.4.5
dev: false
+ /mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+ dev: true
+
/nan@2.18.0:
resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==}
requiresBuild: true
@@ -9091,6 +10219,13 @@ packages:
path-key: 3.1.1
dev: true
+ /npm-run-path@5.1.0:
+ resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ dependencies:
+ path-key: 4.0.0
+ dev: true
+
/nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
dependencies:
@@ -9183,6 +10318,11 @@ packages:
kind-of: 3.2.2
dev: false
+ /object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+ dev: true
+
/object-inspect@1.13.1:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
dev: true
@@ -9206,7 +10346,6 @@ packages:
define-properties: 1.2.1
has-symbols: 1.0.3
object-keys: 1.1.1
- dev: false
/object.defaults@1.1.0:
resolution: {integrity: sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==}
@@ -9218,6 +10357,24 @@ packages:
isobject: 3.0.1
dev: false
+ /object.fromentries@2.0.7:
+ resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ dev: true
+
+ /object.groupby@1.0.1:
+ resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ get-intrinsic: 1.2.2
+ dev: true
+
/object.map@1.0.1:
resolution: {integrity: sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==}
engines: {node: '>=0.10.0'}
@@ -9241,6 +10398,15 @@ packages:
make-iterator: 1.0.1
dev: false
+ /object.values@1.1.7:
+ resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ dev: true
+
/obuf@1.1.2:
resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
dev: true
@@ -9275,6 +10441,13 @@ packages:
dependencies:
mimic-fn: 2.1.0
+ /onetime@6.0.0:
+ resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ mimic-fn: 4.0.0
+ dev: true
+
/open@8.4.2:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
@@ -9284,6 +10457,16 @@ packages:
is-wsl: 2.2.0
dev: true
+ /open@9.1.0:
+ resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==}
+ engines: {node: '>=14.16'}
+ dependencies:
+ default-browser: 4.0.0
+ define-lazy-prop: 3.0.0
+ is-inside-container: 1.0.0
+ is-wsl: 2.2.0
+ dev: true
+
/openurl@1.1.1:
resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==}
dev: true
@@ -9540,6 +10723,11 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
+ /path-key@4.0.0:
+ resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+ engines: {node: '>=12'}
+ dev: true
+
/path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@@ -9597,7 +10785,6 @@ packages:
/pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
- dev: false
/pify@4.0.1:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
@@ -9617,6 +10804,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /pirates@4.0.6:
+ resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
+ engines: {node: '>= 6'}
+ dev: true
+
/piscina@4.1.0:
resolution: {integrity: sha512-sjbLMi3sokkie+qmtZpkfMCUJTpbxJm/wvaPzU28vmYSsTSW8xk9JcFUsbqGJdtPpIQ9tuj+iDcTtgZjwnOSig==}
dependencies:
@@ -9684,6 +10876,45 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
+ /postcss-import@15.1.0(postcss@8.4.31):
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.4.31
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.8
+ dev: true
+
+ /postcss-js@4.0.1(postcss@8.4.31):
+ resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.4.31
+ dev: true
+
+ /postcss-load-config@4.0.2(postcss@8.4.31):
+ resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ lilconfig: 3.0.0
+ postcss: 8.4.31
+ yaml: 2.3.4
+ dev: true
+
/postcss-loader@7.3.3(postcss@8.4.31)(typescript@5.2.2)(webpack@5.89.0):
resolution: {integrity: sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==}
engines: {node: '>= 14.15.0'}
@@ -9741,6 +10972,16 @@ packages:
postcss: 8.4.31
dev: true
+ /postcss-nested@6.0.1(postcss@8.4.31):
+ resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+ dependencies:
+ postcss: 8.4.31
+ postcss-selector-parser: 6.0.13
+ dev: true
+
/postcss-selector-parser@6.0.13:
resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==}
engines: {node: '>=4'}
@@ -9765,6 +11006,39 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
+ /prettier-eslint@16.1.2:
+ resolution: {integrity: sha512-mGFGZQbAh11FSnwW3H1zngzQYR2QMmHO8vdfgnAuzOFhnDeUZHYtwpqQvOMOMT0k818Dr1X+J4a/sVE0r34RKQ==}
+ engines: {node: '>=16.10.0'}
+ dependencies:
+ '@typescript-eslint/parser': 6.12.0(eslint@8.54.0)(typescript@5.2.2)
+ common-tags: 1.8.2
+ dlv: 1.1.3
+ eslint: 8.54.0
+ indent-string: 4.0.0
+ lodash.merge: 4.6.2
+ loglevel-colored-level-prefix: 1.0.0
+ prettier: 3.1.0
+ pretty-format: 29.7.0
+ require-relative: 0.8.7
+ typescript: 5.2.2
+ vue-eslint-parser: 9.3.2(eslint@8.54.0)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /prettier-linter-helpers@1.0.0:
+ resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ fast-diff: 1.3.0
+ dev: true
+
+ /prettier@3.1.0:
+ resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
+ engines: {node: '>=14'}
+ hasBin: true
+ dev: true
+
/pretty-bytes@5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
@@ -9902,6 +11176,12 @@ packages:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
dev: true
+ /read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+ dependencies:
+ pify: 2.3.0
+ dev: true
+
/read-config-file@6.2.0:
resolution: {integrity: sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg==}
engines: {node: '>=12.0.0'}
@@ -10038,6 +11318,15 @@ packages:
resolution: {integrity: sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==}
dev: true
+ /regexp.prototype.flags@1.5.1:
+ resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ set-function-name: 2.0.1
+ dev: true
+
/regexpu-core@5.3.2:
resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==}
engines: {node: '>=4'}
@@ -10141,6 +11430,10 @@ packages:
resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==}
dev: false
+ /require-relative@0.8.7:
+ resolution: {integrity: sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==}
+ dev: true
+
/requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: true
@@ -10173,6 +11466,10 @@ packages:
value-or-function: 3.0.0
dev: false
+ /resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+ dev: true
+
/resolve-url-loader@5.0.0:
resolution: {integrity: sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==}
engines: {node: '>=12'}
@@ -10323,6 +11620,13 @@ packages:
strip-json-comments: 3.1.1
dev: false
+ /run-applescript@5.0.0:
+ resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==}
+ engines: {node: '>=12'}
+ dependencies:
+ execa: 5.1.1
+ dev: true
+
/run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@@ -10347,12 +11651,30 @@ packages:
dependencies:
tslib: 2.6.2
+ /safe-array-concat@1.0.1:
+ resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==}
+ engines: {node: '>=0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ get-intrinsic: 1.2.2
+ has-symbols: 1.0.3
+ isarray: 2.0.5
+ dev: true
+
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ /safe-regex-test@1.0.0:
+ resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
+ dependencies:
+ call-bind: 1.0.5
+ get-intrinsic: 1.2.2
+ is-regex: 1.1.4
+ dev: true
+
/safe-regex@1.1.0:
resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==}
dependencies:
@@ -10594,6 +11916,15 @@ packages:
gopd: 1.0.1
has-property-descriptors: 1.0.1
+ /set-function-name@2.0.1:
+ resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ define-data-property: 1.1.1
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.1
+ dev: true
+
/set-value@2.0.1:
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
engines: {node: '>=0.10.0'}
@@ -10866,6 +12197,10 @@ packages:
engines: {node: '>= 0.10'}
dev: false
+ /spawn-command@0.0.2:
+ resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
+ dev: true
+
/spdx-correct@3.2.0:
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
dependencies:
@@ -11015,6 +12350,31 @@ packages:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
+ /string.prototype.trim@1.2.8:
+ resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ dev: true
+
+ /string.prototype.trimend@1.0.7:
+ resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ dev: true
+
+ /string.prototype.trimstart@1.0.7:
+ resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==}
+ dependencies:
+ call-bind: 1.0.5
+ define-properties: 1.2.1
+ es-abstract: 1.22.3
+ dev: true
+
/string_decoder@0.10.31:
resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
dev: false
@@ -11034,7 +12394,6 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
ansi-regex: 2.1.1
- dev: false
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
@@ -11080,6 +12439,11 @@ packages:
engines: {node: '>=6'}
dev: true
+ /strip-final-newline@3.0.0:
+ resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
+ engines: {node: '>=12'}
+ dev: true
+
/strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -11094,6 +12458,20 @@ packages:
through: 2.3.8
dev: true
+ /sucrase@3.34.0:
+ resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==}
+ engines: {node: '>=8'}
+ hasBin: true
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.3
+ commander: 4.1.1
+ glob: 7.1.6
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.6
+ ts-interface-checker: 0.1.13
+ dev: true
+
/sumchecker@3.0.1:
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
engines: {node: '>= 8.0'}
@@ -11105,7 +12483,6 @@ packages:
/supports-color@2.0.0:
resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
engines: {node: '>=0.8.0'}
- dev: false
/supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
@@ -11142,6 +12519,45 @@ packages:
engines: {node: '>=0.10'}
dev: true
+ /synckit@0.8.5:
+ resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ dependencies:
+ '@pkgr/utils': 2.4.2
+ tslib: 2.6.2
+ dev: true
+
+ /tailwindcss@3.3.5:
+ resolution: {integrity: sha512-5SEZU4J7pxZgSkv7FP1zY8i2TIAOooNZ1e/OGtxIEv6GltpoiXUqWvLy89+a10qYTB1N5Ifkuw9lqQkN9sscvA==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.5.3
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.2
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.0
+ lilconfig: 2.1.0
+ micromatch: 4.0.5
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.0.0
+ postcss: 8.4.31
+ postcss-import: 15.1.0(postcss@8.4.31)
+ postcss-js: 4.0.1(postcss@8.4.31)
+ postcss-load-config: 4.0.2(postcss@8.4.31)
+ postcss-nested: 6.0.1(postcss@8.4.31)
+ postcss-selector-parser: 6.0.13
+ resolve: 1.22.8
+ sucrase: 3.34.0
+ transitivePeerDependencies:
+ - ts-node
+ dev: true
+
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
@@ -11241,6 +12657,19 @@ packages:
engines: {node: '>=8'}
dev: false
+ /thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+ dependencies:
+ thenify: 3.3.1
+ dev: true
+
+ /thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ dependencies:
+ any-promise: 1.3.0
+ dev: true
+
/through2-filter@3.0.0:
resolution: {integrity: sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==}
dependencies:
@@ -11293,6 +12722,11 @@ packages:
next-tick: 1.1.0
dev: false
+ /titleize@3.0.0:
+ resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
+ engines: {node: '>=12'}
+ dev: true
+
/tmp-promise@3.0.3:
resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
dependencies:
@@ -11398,6 +12832,19 @@ packages:
typescript: 5.2.2
dev: true
+ /ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+ dev: true
+
+ /tsconfig-paths@3.14.2:
+ resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
+ dependencies:
+ '@types/json5': 0.0.29
+ json5: 1.0.2
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+ dev: true
+
/tsconfig-paths@4.2.0:
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
engines: {node: '>=6'}
@@ -11410,6 +12857,17 @@ packages:
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ /tsx@4.4.0:
+ resolution: {integrity: sha512-4fwcEjRUxW20ciSaMB8zkpGwCPxuRGnadDuj/pBk5S9uT29zvWz15PK36GrKJo45mSJomDxVejZ73c6lr3811Q==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+ dependencies:
+ esbuild: 0.18.20
+ get-tsconfig: 4.7.2
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
/tuf-js@2.1.0:
resolution: {integrity: sha512-eD7YPPjVlMzdggrOeE8zwoegUaG/rt6Bt3jwoQPunRiNVzgcCE009UDFJKJjG+Gk9wFu6W/Vi+P5d/5QpdD9jA==}
engines: {node: ^16.14.0 || >=18.0.0}
@@ -11463,6 +12921,44 @@ packages:
resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
dev: false
+ /typed-array-buffer@1.0.0:
+ resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ get-intrinsic: 1.2.2
+ is-typed-array: 1.1.12
+ dev: true
+
+ /typed-array-byte-length@1.0.0:
+ resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ call-bind: 1.0.5
+ for-each: 0.3.3
+ has-proto: 1.0.1
+ is-typed-array: 1.1.12
+ dev: true
+
+ /typed-array-byte-offset@1.0.0:
+ resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ available-typed-arrays: 1.0.5
+ call-bind: 1.0.5
+ for-each: 0.3.3
+ has-proto: 1.0.1
+ is-typed-array: 1.1.12
+ dev: true
+
+ /typed-array-length@1.0.4:
+ resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
+ dependencies:
+ call-bind: 1.0.5
+ for-each: 0.3.3
+ is-typed-array: 1.1.12
+ dev: true
+
/typed-assert@1.0.9:
resolution: {integrity: sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==}
dev: true
@@ -11487,6 +12983,15 @@ packages:
hasBin: true
dev: false
+ /unbox-primitive@1.0.2:
+ resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
+ dependencies:
+ call-bind: 1.0.5
+ has-bigints: 1.0.2
+ has-symbols: 1.0.3
+ which-boxed-primitive: 1.0.2
+ dev: true
+
/unc-path-regex@0.1.2:
resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==}
engines: {node: '>=0.10.0'}
@@ -11602,6 +13107,11 @@ packages:
isobject: 3.0.1
dev: false
+ /untildify@4.0.0:
+ resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
+ engines: {node: '>=8'}
+ dev: true
+
/upath@1.2.0:
resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==}
engines: {node: '>=4'}
@@ -11802,6 +13312,24 @@ packages:
fsevents: 2.3.3
dev: true
+ /vue-eslint-parser@9.3.2(eslint@8.54.0):
+ resolution: {integrity: sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==}
+ engines: {node: ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: '>=6.0.0'
+ dependencies:
+ debug: 4.3.4
+ eslint: 8.54.0
+ eslint-scope: 7.2.2
+ eslint-visitor-keys: 3.4.3
+ espree: 9.6.1
+ esquery: 1.5.0
+ lodash: 4.17.21
+ semver: 7.5.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/watchpack@2.4.0:
resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
engines: {node: '>=10.13.0'}
@@ -11996,10 +13524,31 @@ packages:
webidl-conversions: 3.0.1
dev: false
+ /which-boxed-primitive@1.0.2:
+ resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+ dependencies:
+ is-bigint: 1.0.4
+ is-boolean-object: 1.1.2
+ is-number-object: 1.0.7
+ is-string: 1.0.7
+ is-symbol: 1.0.4
+ dev: true
+
/which-module@1.0.0:
resolution: {integrity: sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==}
dev: false
+ /which-typed-array@1.1.13:
+ resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ available-typed-arrays: 1.0.5
+ call-bind: 1.0.5
+ for-each: 0.3.3
+ gopd: 1.0.1
+ has-tostringtag: 1.0.0
+ dev: true
+
/which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true
@@ -12124,6 +13673,11 @@ packages:
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ /yaml@2.3.4:
+ resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
+ engines: {node: '>= 14'}
+ dev: true
+
/yamljs@0.3.0:
resolution: {integrity: sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==}
hasBin: true
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 1380bc3..4aae81e 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -1,23 +1,24 @@
import { NgModule } from '@angular/core'
-import { Routes, RouterModule, RouteReuseStrategy } from '@angular/router'
+import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router'
+
import { BrowseComponent } from './components/browse/browse.component'
import { SettingsComponent } from './components/settings/settings.component'
import { TabPersistStrategy } from './core/tab-persist.strategy'
// TODO: replace these with the correct components
const routes: Routes = [
- { path: 'browse', component: BrowseComponent, data: { shouldReuse: true } },
- { path: 'library', redirectTo: '/browse' },
- { path: 'settings', component: SettingsComponent, data: { shouldReuse: true } },
- { path: 'about', redirectTo: '/browse' },
- { path: '**', redirectTo: '/browse' }
+ { path: 'browse', component: BrowseComponent, data: { shouldReuse: true } },
+ { path: 'library', redirectTo: '/browse' },
+ { path: 'settings', component: SettingsComponent, data: { shouldReuse: true } },
+ { path: 'about', redirectTo: '/browse' },
+ { path: '**', redirectTo: '/browse' },
]
@NgModule({
- imports: [RouterModule.forRoot(routes)],
- exports: [RouterModule],
- providers: [
- { provide: RouteReuseStrategy, useClass: TabPersistStrategy },
- ]
+ imports: [RouterModule.forRoot(routes)],
+ exports: [RouterModule],
+ providers: [
+ { provide: RouteReuseStrategy, useClass: TabPersistStrategy },
+ ],
})
-export class AppRoutingModule { }
\ No newline at end of file
+export class AppRoutingModule { }
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 69cc8fd..b77d2fd 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index e8c299e..dd5b534 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,17 +1,18 @@
import { Component } from '@angular/core'
+
import { SettingsService } from './core/services/settings.service'
@Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styles: []
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styles: [],
})
export class AppComponent {
- settingsLoaded = false
+ settingsLoaded = false
- constructor(private settingsService: SettingsService) {
- // Ensure settings are loaded before rendering the application
- settingsService.loadSettings().then(() => this.settingsLoaded = true)
- }
-}
\ No newline at end of file
+ constructor(private settingsService: SettingsService) {
+ // Ensure settings are loaded before rendering the application
+ settingsService.loadSettings().then(() => this.settingsLoaded = true)
+ }
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 3ecd56b..8f2187a 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,41 +1,41 @@
-import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
+import { FormsModule } from '@angular/forms'
+import { BrowserModule } from '@angular/platform-browser'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
-import { ToolbarComponent } from './components/toolbar/toolbar.component'
import { BrowseComponent } from './components/browse/browse.component'
-import { SearchBarComponent } from './components/browse/search-bar/search-bar.component'
-import { StatusBarComponent } from './components/browse/status-bar/status-bar.component'
-import { ResultTableComponent } from './components/browse/result-table/result-table.component'
import { ChartSidebarComponent } from './components/browse/chart-sidebar/chart-sidebar.component'
import { ResultTableRowComponent } from './components/browse/result-table/result-table-row/result-table-row.component'
+import { ResultTableComponent } from './components/browse/result-table/result-table.component'
+import { SearchBarComponent } from './components/browse/search-bar/search-bar.component'
import { DownloadsModalComponent } from './components/browse/status-bar/downloads-modal/downloads-modal.component'
-import { ProgressBarDirective } from './core/directives/progress-bar.directive'
-import { CheckboxDirective } from './core/directives/checkbox.directive'
+import { StatusBarComponent } from './components/browse/status-bar/status-bar.component'
import { SettingsComponent } from './components/settings/settings.component'
-import { FormsModule } from '@angular/forms'
+import { ToolbarComponent } from './components/toolbar/toolbar.component'
+import { CheckboxDirective } from './core/directives/checkbox.directive'
+import { ProgressBarDirective } from './core/directives/progress-bar.directive'
@NgModule({
- declarations: [
- AppComponent,
- ToolbarComponent,
- BrowseComponent,
- SearchBarComponent,
- StatusBarComponent,
- ResultTableComponent,
- ChartSidebarComponent,
- ResultTableRowComponent,
- DownloadsModalComponent,
- ProgressBarDirective,
- CheckboxDirective,
- SettingsComponent
- ],
- imports: [
- BrowserModule,
- AppRoutingModule,
- FormsModule
- ],
- bootstrap: [AppComponent]
+ declarations: [
+ AppComponent,
+ ToolbarComponent,
+ BrowseComponent,
+ SearchBarComponent,
+ StatusBarComponent,
+ ResultTableComponent,
+ ChartSidebarComponent,
+ ResultTableRowComponent,
+ DownloadsModalComponent,
+ ProgressBarDirective,
+ CheckboxDirective,
+ SettingsComponent,
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule,
+ FormsModule,
+ ],
+ bootstrap: [AppComponent],
})
-export class AppModule { }
\ No newline at end of file
+export class AppModule { }
diff --git a/src/app/components/browse/browse.component.html b/src/app/components/browse/browse.component.html
index bd722a8..c59bc87 100644
--- a/src/app/components/browse/browse.component.html
+++ b/src/app/components/browse/browse.component.html
@@ -1,15 +1,12 @@
-
\ No newline at end of file
+
diff --git a/src/app/components/browse/browse.component.scss b/src/app/components/browse/browse.component.scss
index b57ee60..e13ba8f 100644
--- a/src/app/components/browse/browse.component.scss
+++ b/src/app/components/browse/browse.component.scss
@@ -24,4 +24,4 @@
min-width: 175px;
overflow: hidden;
padding: 0;
-}
\ No newline at end of file
+}
diff --git a/src/app/components/browse/browse.component.ts b/src/app/components/browse/browse.component.ts
index cf0c321..1cee053 100644
--- a/src/app/components/browse/browse.component.ts
+++ b/src/app/components/browse/browse.component.ts
@@ -1,34 +1,35 @@
-import { Component, ViewChild, AfterViewInit } from '@angular/core'
-import { ChartSidebarComponent } from './chart-sidebar/chart-sidebar.component'
-import { StatusBarComponent } from './status-bar/status-bar.component'
-import { ResultTableComponent } from './result-table/result-table.component'
+import { AfterViewInit, Component, ViewChild } from '@angular/core'
+
import { SearchService } from 'src/app/core/services/search.service'
+import { ChartSidebarComponent } from './chart-sidebar/chart-sidebar.component'
+import { ResultTableComponent } from './result-table/result-table.component'
+import { StatusBarComponent } from './status-bar/status-bar.component'
@Component({
- selector: 'app-browse',
- templateUrl: './browse.component.html',
- styleUrls: ['./browse.component.scss']
+ selector: 'app-browse',
+ templateUrl: './browse.component.html',
+ styleUrls: ['./browse.component.scss'],
})
export class BrowseComponent implements AfterViewInit {
- @ViewChild('resultTable', { static: true }) resultTable: ResultTableComponent
- @ViewChild('chartSidebar', { static: true }) chartSidebar: ChartSidebarComponent
- @ViewChild('statusBar', { static: true }) statusBar: StatusBarComponent
+ @ViewChild('resultTable', { static: true }) resultTable: ResultTableComponent
+ @ViewChild('chartSidebar', { static: true }) chartSidebar: ChartSidebarComponent
+ @ViewChild('statusBar', { static: true }) statusBar: StatusBarComponent
- constructor(private searchService: SearchService) { }
+ constructor(private searchService: SearchService) { }
- ngAfterViewInit() {
- const $tableColumn = $('#table-column')
- $tableColumn.on('scroll', () => {
- const pos = $tableColumn[0].scrollTop + $tableColumn[0].offsetHeight
- const max = $tableColumn[0].scrollHeight
- if (pos >= max - 5) {
- this.searchService.updateScroll()
- }
- })
+ ngAfterViewInit() {
+ const $tableColumn = $('#table-column')
+ $tableColumn.on('scroll', () => {
+ const pos = $tableColumn[0].scrollTop + $tableColumn[0].offsetHeight
+ const max = $tableColumn[0].scrollHeight
+ if (pos >= max - 5) {
+ this.searchService.updateScroll()
+ }
+ })
- this.searchService.onNewSearch(() => {
- $tableColumn.scrollTop(0)
- })
- }
-}
\ No newline at end of file
+ this.searchService.onNewSearch(() => {
+ $tableColumn.scrollTop(0)
+ })
+ }
+}
diff --git a/src/app/components/browse/chart-sidebar/chart-sidebar.component.html b/src/app/components/browse/chart-sidebar/chart-sidebar.component.html
index 7c19a2b..27831bb 100644
--- a/src/app/components/browse/chart-sidebar/chart-sidebar.component.html
+++ b/src/app/components/browse/chart-sidebar/chart-sidebar.component.html
@@ -1,46 +1,46 @@
\ No newline at end of file
+
+
+
+ 1" id="chartDropdown" class="ui fluid right labeled scrolling icon dropdown button">
+
+
+
+
+
+
+
+
+
Album: {{ selectedVersion.album }} ({{ selectedVersion.year }})
+
Year: {{ selectedVersion.year }}
+
Genre: {{ selectedVersion.genre }}
+
+ {{ charterPlural }} {{ selectedVersion.charters }}
+
+
Tags: {{ selectedVersion.tags }}
+
Audio Length: {{ songLength }}
+
+
+
+
+
+
+ {{ difficulty.chartedDifficulties }}
+
+
+
+
+
+
+
+
diff --git a/src/app/components/browse/chart-sidebar/chart-sidebar.component.scss b/src/app/components/browse/chart-sidebar/chart-sidebar.component.scss
index 8e340a5..3497b0f 100644
--- a/src/app/components/browse/chart-sidebar/chart-sidebar.component.scss
+++ b/src/app/components/browse/chart-sidebar/chart-sidebar.component.scss
@@ -61,4 +61,4 @@
#versionDropdown {
margin: 0px 1px 1px -3px;
-}
\ No newline at end of file
+}
diff --git a/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts b/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts
index 57a894a..d0e45fc 100644
--- a/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts
+++ b/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts
@@ -1,286 +1,288 @@
import { Component, OnInit } from '@angular/core'
+import { DomSanitizer, SafeUrl } from '@angular/platform-browser'
+
+import { SearchService } from 'src/app/core/services/search.service'
+import { SettingsService } from 'src/app/core/services/settings.service'
+import { groupBy } from 'src/electron/shared/UtilFunctions'
import { SongResult } from '../../../../electron/shared/interfaces/search.interface'
-import { ElectronService } from '../../../core/services/electron.service'
-import { VersionResult, getInstrumentIcon, Instrument, ChartedDifficulty } from '../../../../electron/shared/interfaces/songDetails.interface'
+import { ChartedDifficulty, getInstrumentIcon, Instrument, VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
import { AlbumArtService } from '../../../core/services/album-art.service'
import { DownloadService } from '../../../core/services/download.service'
-import { groupBy } from 'src/electron/shared/UtilFunctions'
-import { SearchService } from 'src/app/core/services/search.service'
-import { DomSanitizer, SafeUrl } from '@angular/platform-browser'
-import { SettingsService } from 'src/app/core/services/settings.service'
+import { ElectronService } from '../../../core/services/electron.service'
interface Difficulty {
- instrument: string
- diffNumber: string
- chartedDifficulties: string
+ instrument: string
+ diffNumber: string
+ chartedDifficulties: string
}
@Component({
- selector: 'app-chart-sidebar',
- templateUrl: './chart-sidebar.component.html',
- styleUrls: ['./chart-sidebar.component.scss']
+ selector: 'app-chart-sidebar',
+ templateUrl: './chart-sidebar.component.html',
+ styleUrls: ['./chart-sidebar.component.scss'],
})
export class ChartSidebarComponent implements OnInit {
- songResult: SongResult
- selectedVersion: VersionResult
- charts: VersionResult[][]
+ songResult: SongResult
+ selectedVersion: VersionResult
+ charts: VersionResult[][]
- constructor(
- private electronService: ElectronService,
- private albumArtService: AlbumArtService,
- private downloadService: DownloadService,
- private searchService: SearchService,
- private sanitizer: DomSanitizer,
- public settingsService: SettingsService
- ) { }
+ albumArtSrc: SafeUrl = ''
+ charterPlural: string
+ songLength: string
+ difficultiesList: Difficulty[]
+ downloadButtonText: string
- ngOnInit() {
- this.searchService.onNewSearch(() => {
- this.selectVersion(undefined)
- this.songResult = undefined
- })
- }
+ constructor(
+ private electronService: ElectronService,
+ private albumArtService: AlbumArtService,
+ private downloadService: DownloadService,
+ private searchService: SearchService,
+ private sanitizer: DomSanitizer,
+ public settingsService: SettingsService
+ ) { }
- /**
- * Displays the information for the selected song.
- */
- async onRowClicked(result: SongResult) {
- if (this.songResult == undefined || result.id != this.songResult.id) { // Clicking the same row again will not reload
- this.songResult = result
- const albumArt = this.albumArtService.getImage(result.id)
- const results = await this.electronService.invoke('song-details', result.id)
- this.charts = groupBy(results, 'chartID').sort((v1, v2) => v1[0].chartName.length - v2[0].chartName.length)
- this.sortCharts()
- await this.selectChart(this.charts[0][0].chartID)
- this.initChartDropdown()
+ ngOnInit() {
+ this.searchService.onNewSearch(() => {
+ this.selectVersion(undefined)
+ this.songResult = undefined
+ })
+ }
- this.updateAlbumArtSrc(await albumArt)
- }
- }
+ /**
+ * Displays the information for the selected song.
+ */
+ async onRowClicked(result: SongResult) {
+ if (this.songResult == undefined || result.id != this.songResult.id) { // Clicking the same row again will not reload
+ this.songResult = result
+ const albumArt = this.albumArtService.getImage(result.id)
+ const results = await this.electronService.invoke('song-details', result.id)
+ this.charts = groupBy(results, 'chartID').sort((v1, v2) => v1[0].chartName.length - v2[0].chartName.length)
+ this.sortCharts()
+ await this.selectChart(this.charts[0][0].chartID)
+ this.initChartDropdown()
- /**
- * Sorts `this.charts` and its subarrays in the correct order.
- * The chart dropdown should display in a random order, but verified charters are prioritized.
- * The version dropdown should be ordered by lastModified date.
- * (but prefer the non-pack version if it's only a few days older)
- */
- private sortCharts() {
- for (const chart of this.charts) {
- // TODO: sort by verified charter
- this.searchService.sortChart(chart)
- }
- }
+ this.updateAlbumArtSrc(await albumArt)
+ }
+ }
- albumArtSrc: SafeUrl = ''
- /**
- * Updates the sidebar to display the album art.
- */
- updateAlbumArtSrc(albumArtBase64String?: string) {
- if (albumArtBase64String) {
- this.albumArtSrc = this.sanitizer.bypassSecurityTrustUrl('data:image/jpg;base64,' + albumArtBase64String)
- } else {
- this.albumArtSrc = null
- }
- }
+ /**
+ * Sorts `this.charts` and its subarrays in the correct order.
+ * The chart dropdown should display in a random order, but verified charters are prioritized.
+ * The version dropdown should be ordered by lastModified date.
+ * (but prefer the non-pack version if it's only a few days older)
+ */
+ private sortCharts() {
+ for (const chart of this.charts) {
+ // TODO: sort by verified charter
+ this.searchService.sortChart(chart)
+ }
+ }
- /**
- * Initializes the chart dropdown from `this.charts` (or removes it if there's only one chart).
- */
- private initChartDropdown() {
- const values = this.charts.map(chart => {
- const version = chart[0]
- return {
- value: version.chartID,
- text: version.chartName,
- name: `${version.chartName} [${version.charters}] `
- }
- })
- const $chartDropdown = $('#chartDropdown')
- $chartDropdown.dropdown('setup menu', { values })
- $chartDropdown.dropdown('setting', 'onChange', (chartID: number) => this.selectChart(chartID))
- $chartDropdown.dropdown('set selected', values[0].value)
- }
+ /**
+ * Updates the sidebar to display the album art.
+ */
+ updateAlbumArtSrc(albumArtBase64String?: string) {
+ if (albumArtBase64String) {
+ this.albumArtSrc = this.sanitizer.bypassSecurityTrustUrl('data:image/jpg;base64,' + albumArtBase64String)
+ } else {
+ this.albumArtSrc = null
+ }
+ }
- private async selectChart(chartID: number) {
- const chart = this.charts.find(chart => chart[0].chartID == chartID)
- await this.selectVersion(chart[0])
- this.initVersionDropdown()
- }
+ /**
+ * Initializes the chart dropdown from `this.charts` (or removes it if there's only one chart).
+ */
+ private initChartDropdown() {
+ const values = this.charts.map(chart => {
+ const version = chart[0]
+ return {
+ value: version.chartID,
+ text: version.chartName,
+ name: `${version.chartName} [${version.charters}] `,
+ }
+ })
+ const $chartDropdown = $('#chartDropdown')
+ $chartDropdown.dropdown('setup menu', { values })
+ $chartDropdown.dropdown('setting', 'onChange', (chartID: number) => this.selectChart(chartID))
+ $chartDropdown.dropdown('set selected', values[0].value)
+ }
- /**
- * Updates the sidebar to display the metadata for `selectedVersion`.
- */
- async selectVersion(selectedVersion: VersionResult) {
- this.selectedVersion = selectedVersion
- await new Promise(resolve => setTimeout(() => resolve(), 0)) // Wait for *ngIf to update DOM
+ private async selectChart(chartID: number) {
+ const chart = this.charts.find(chart => chart[0].chartID == chartID)
+ await this.selectVersion(chart[0])
+ this.initVersionDropdown()
+ }
- if (this.selectedVersion != undefined) {
- this.updateCharterPlural()
- this.updateSongLength()
- this.updateDifficultiesList()
- this.updateDownloadButtonText()
- }
- }
+ /**
+ * Updates the sidebar to display the metadata for `selectedVersion`.
+ */
+ async selectVersion(selectedVersion: VersionResult) {
+ this.selectedVersion = selectedVersion
+ await new Promise(resolve => setTimeout(() => resolve(), 0)) // Wait for *ngIf to update DOM
- charterPlural: string
- /**
- * Chooses to display 'Charter:' or 'Charters:'.
- */
- private updateCharterPlural() {
- this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
- }
+ if (this.selectedVersion != undefined) {
+ this.updateCharterPlural()
+ this.updateSongLength()
+ this.updateDifficultiesList()
+ this.updateDownloadButtonText()
+ }
+ }
- songLength: string
- /**
- * Converts `this.selectedVersion.chartMetadata.length` into a readable duration.
- */
- private updateSongLength() {
- let seconds = this.selectedVersion.songLength
- if (seconds < 60) { this.songLength = `${seconds} second${seconds == 1 ? '' : 's'}`; return }
- let minutes = Math.floor(seconds / 60)
- let hours = 0
- while (minutes > 59) {
- hours++
- minutes -= 60
- }
- seconds = Math.floor(seconds % 60)
- this.songLength = `${hours == 0 ? '' : hours + ':'}${minutes == 0 ? '' : minutes + ':'}${seconds < 10 ? '0' + seconds : seconds}`
- }
+ /**
+ * Chooses to display 'Charter:' or 'Charters:'.
+ */
+ private updateCharterPlural() {
+ this.charterPlural = this.selectedVersion.charterIDs.split('&').length == 1 ? 'Charter:' : 'Charters:'
+ }
- difficultiesList: Difficulty[]
- /**
- * Updates `dfficultiesList` with the difficulty information for the selected version.
- */
- private updateDifficultiesList() {
- const instruments = Object.keys(this.selectedVersion.chartData.noteCounts) as Instrument[]
- this.difficultiesList = []
- for (const instrument of instruments) {
- if (instrument != 'undefined') {
- this.difficultiesList.push({
- instrument: getInstrumentIcon(instrument),
- diffNumber: this.getDiffNumber(instrument),
- chartedDifficulties: this.getChartedDifficultiesText(instrument)
- })
- }
- }
- }
+ /**
+ * Converts `this.selectedVersion.chartMetadata.length` into a readable duration.
+ */
+ private updateSongLength() {
+ let seconds = this.selectedVersion.songLength
+ if (seconds < 60) { this.songLength = `${seconds} second${seconds == 1 ? '' : 's'}`; return }
+ let minutes = Math.floor(seconds / 60)
+ let hours = 0
+ while (minutes > 59) {
+ hours++
+ minutes -= 60
+ }
+ seconds = Math.floor(seconds % 60)
+ this.songLength = `${hours == 0 ? '' : hours + ':'}${minutes == 0 ? '' : minutes + ':'}${seconds < 10 ? '0' + seconds : seconds}`
+ }
- /**
- * @returns a string describing the difficulty number in the selected version.
- */
- private getDiffNumber(instrument: Instrument) {
- const diffNumber: number = this.selectedVersion[`diff_${instrument}`]
- return diffNumber == -1 || diffNumber == undefined ? 'Unknown' : String(diffNumber)
- }
+ /**
+ * Updates `dfficultiesList` with the difficulty information for the selected version.
+ */
+ private updateDifficultiesList() {
+ const instruments = Object.keys(this.selectedVersion.chartData.noteCounts) as Instrument[]
+ this.difficultiesList = []
+ for (const instrument of instruments) {
+ if (instrument != 'undefined') {
+ this.difficultiesList.push({
+ instrument: getInstrumentIcon(instrument),
+ diffNumber: this.getDiffNumber(instrument),
+ chartedDifficulties: this.getChartedDifficultiesText(instrument),
+ })
+ }
+ }
+ }
- /**
- * @returns a string describing the list of charted difficulties in the selected version.
- */
- private getChartedDifficultiesText(instrument: Instrument) {
- const difficulties = Object.keys(this.selectedVersion.chartData.noteCounts[instrument]) as ChartedDifficulty[]
- if (difficulties.length == 4) { return 'Full Difficulty' }
- const difficultyNames = []
- if (difficulties.includes('x')) { difficultyNames.push('Expert') }
- if (difficulties.includes('h')) { difficultyNames.push('Hard') }
- if (difficulties.includes('m')) { difficultyNames.push('Medium') }
- if (difficulties.includes('e')) { difficultyNames.push('Easy') }
+ /**
+ * @returns a string describing the difficulty number in the selected version.
+ */
+ private getDiffNumber(instrument: Instrument) {
+ const diffNumber: number = this.selectedVersion[`diff_${instrument}`]
+ return diffNumber == -1 || diffNumber == undefined ? 'Unknown' : String(diffNumber)
+ }
- return difficultyNames.join(', ')
- }
+ /**
+ * @returns a string describing the list of charted difficulties in the selected version.
+ */
+ private getChartedDifficultiesText(instrument: Instrument) {
+ const difficulties = Object.keys(this.selectedVersion.chartData.noteCounts[instrument]) as ChartedDifficulty[]
+ if (difficulties.length == 4) { return 'Full Difficulty' }
+ const difficultyNames = []
+ if (difficulties.includes('x')) { difficultyNames.push('Expert') }
+ if (difficulties.includes('h')) { difficultyNames.push('Hard') }
+ if (difficulties.includes('m')) { difficultyNames.push('Medium') }
+ if (difficulties.includes('e')) { difficultyNames.push('Easy') }
- downloadButtonText: string
- /**
- * Chooses the text to display on the download button.
- */
- private updateDownloadButtonText() {
- this.downloadButtonText = 'Download'
- if (this.selectedVersion.driveData.inChartPack) {
- this.downloadButtonText += ' Chart Pack'
- } else {
- this.downloadButtonText += (this.selectedVersion.driveData.isArchive ? ' Archive' : ' Files')
- }
+ return difficultyNames.join(', ')
+ }
- if (this.getSelectedChartVersions().length > 1) {
- if (this.selectedVersion.versionID == this.selectedVersion.latestVersionID) {
- this.downloadButtonText += ' (Latest)'
- } else {
- this.downloadButtonText += ` (${this.getLastModifiedText(this.selectedVersion.lastModified)})`
- }
- }
- }
+ /**
+ * Chooses the text to display on the download button.
+ */
+ private updateDownloadButtonText() {
+ this.downloadButtonText = 'Download'
+ if (this.selectedVersion.driveData.inChartPack) {
+ this.downloadButtonText += ' Chart Pack'
+ } else {
+ this.downloadButtonText += (this.selectedVersion.driveData.isArchive ? ' Archive' : ' Files')
+ }
- /**
- * Initializes the version dropdown from `this.selectedVersion` (or removes it if there's only one version).
- */
- private initVersionDropdown() {
- const $versionDropdown = $('#versionDropdown')
- const versions = this.getSelectedChartVersions()
- const values = versions.map(version => ({
- value: version.versionID,
- text: 'Uploaded ' + this.getLastModifiedText(version.lastModified),
- name: 'Uploaded ' + this.getLastModifiedText(version.lastModified)
- }))
+ if (this.getSelectedChartVersions().length > 1) {
+ if (this.selectedVersion.versionID == this.selectedVersion.latestVersionID) {
+ this.downloadButtonText += ' (Latest)'
+ } else {
+ this.downloadButtonText += ` (${this.getLastModifiedText(this.selectedVersion.lastModified)})`
+ }
+ }
+ }
- $versionDropdown.dropdown('setup menu', { values })
- $versionDropdown.dropdown('setting', 'onChange', (versionID: number) => {
- this.selectVersion(versions.find(version => version.versionID == versionID))
- })
- $versionDropdown.dropdown('set selected', values[0].value)
- }
+ /**
+ * Initializes the version dropdown from `this.selectedVersion` (or removes it if there's only one version).
+ */
+ private initVersionDropdown() {
+ const $versionDropdown = $('#versionDropdown')
+ const versions = this.getSelectedChartVersions()
+ const values = versions.map(version => ({
+ value: version.versionID,
+ text: 'Uploaded ' + this.getLastModifiedText(version.lastModified),
+ name: 'Uploaded ' + this.getLastModifiedText(version.lastModified),
+ }))
- /**
- * Returns the list of versions for the selected chart, sorted by `lastModified`.
- */
- getSelectedChartVersions() {
- return this.charts.find(chart => chart[0].chartID == this.selectedVersion.chartID)
- }
+ $versionDropdown.dropdown('setup menu', { values })
+ $versionDropdown.dropdown('setting', 'onChange', (versionID: number) => {
+ this.selectVersion(versions.find(version => version.versionID == versionID))
+ })
+ $versionDropdown.dropdown('set selected', values[0].value)
+ }
- /**
- * Converts the value to a user-readable format.
- * @param lastModified The UNIX timestamp for the lastModified date.
- */
- private getLastModifiedText(lastModified: string) {
- const date = new Date(lastModified)
- const day = date.getDate().toString().padStart(2, '0')
- const month = (date.getMonth() + 1).toString().padStart(2, '0')
- const year = date.getFullYear().toString().substr(-2)
- return `${month}/${day}/${year}`
- }
+ /**
+ * Returns the list of versions for the selected chart, sorted by `lastModified`.
+ */
+ getSelectedChartVersions() {
+ return this.charts.find(chart => chart[0].chartID == this.selectedVersion.chartID)
+ }
- /**
- * Opens the proxy link or source folder in the default browser.
- */
- onSourceLinkClicked() {
- const source = this.selectedVersion.driveData.source
- this.electronService.sendIPC('open-url', source.proxyLink ?? `https://drive.google.com/drive/folders/${source.sourceDriveID}`)
- }
+ /**
+ * Converts the value to a user-readable format.
+ * @param lastModified The UNIX timestamp for the lastModified date.
+ */
+ private getLastModifiedText(lastModified: string) {
+ const date = new Date(lastModified)
+ const day = date.getDate().toString().padStart(2, '0')
+ const month = (date.getMonth() + 1).toString().padStart(2, '0')
+ const year = date.getFullYear().toString().substr(-2)
+ return `${month}/${day}/${year}`
+ }
- /**
- * @returns `true` if the source folder button should be shown.
- */
- shownFolderButton() {
- const driveData = this.selectedVersion.driveData
- return driveData.source.proxyLink || driveData.source.sourceDriveID != driveData.folderID
- }
+ /**
+ * Opens the proxy link or source folder in the default browser.
+ */
+ onSourceLinkClicked() {
+ const source = this.selectedVersion.driveData.source
+ this.electronService.sendIPC('open-url', source.proxyLink ?? `https://drive.google.com/drive/folders/${source.sourceDriveID}`)
+ }
- /**
- * Opens the chart folder in the default browser.
- */
- onFolderButtonClicked() {
- this.electronService.sendIPC('open-url', `https://drive.google.com/drive/folders/${this.selectedVersion.driveData.folderID}`)
- }
+ /**
+ * @returns `true` if the source folder button should be shown.
+ */
+ shownFolderButton() {
+ const driveData = this.selectedVersion.driveData
+ return driveData.source.proxyLink || driveData.source.sourceDriveID != driveData.folderID
+ }
- /**
- * Adds the selected version to the download queue.
- */
- onDownloadClicked() {
- this.downloadService.addDownload(
- this.selectedVersion.versionID, {
- chartName: this.selectedVersion.chartName,
- artist: this.songResult.artist,
- charter: this.selectedVersion.charters,
- driveData: this.selectedVersion.driveData
- })
- }
-}
\ No newline at end of file
+ /**
+ * Opens the chart folder in the default browser.
+ */
+ onFolderButtonClicked() {
+ this.electronService.sendIPC('open-url', `https://drive.google.com/drive/folders/${this.selectedVersion.driveData.folderID}`)
+ }
+
+ /**
+ * Adds the selected version to the download queue.
+ */
+ onDownloadClicked() {
+ this.downloadService.addDownload(
+ this.selectedVersion.versionID, {
+ chartName: this.selectedVersion.chartName,
+ artist: this.songResult.artist,
+ charter: this.selectedVersion.charters,
+ driveData: this.selectedVersion.driveData,
+ })
+ }
+}
diff --git a/src/app/components/browse/result-table/result-table-row/result-table-row.component.html b/src/app/components/browse/result-table/result-table-row/result-table-row.component.html
index d048624..eaf63ea 100644
--- a/src/app/components/browse/result-table/result-table-row/result-table-row.component.html
+++ b/src/app/components/browse/result-table/result-table-row/result-table-row.component.html
@@ -1,9 +1,12 @@
-
-
-
+
+
+
- 1">{{result.chartCount}} {{result.name}}
-{{result.artist}}
-{{result.album || 'Various'}}
-{{result.genre || 'Various'}}
\ No newline at end of file
+
+ 1">{{ result.chartCount }} {{ result.name }}
+
+{{ result.artist }}
+{{ result.album || 'Various' }}
+{{ result.genre || 'Various' }}
diff --git a/src/app/components/browse/result-table/result-table-row/result-table-row.component.scss b/src/app/components/browse/result-table/result-table-row/result-table-row.component.scss
index 4e2a229..b11b505 100644
--- a/src/app/components/browse/result-table/result-table-row/result-table-row.component.scss
+++ b/src/app/components/browse/result-table/result-table-row/result-table-row.component.scss
@@ -7,4 +7,4 @@
border-radius: 3px;
padding: 1px 4px 2px 4px;
margin-right: 3px;
-}
\ No newline at end of file
+}
diff --git a/src/app/components/browse/result-table/result-table-row/result-table-row.component.ts b/src/app/components/browse/result-table/result-table-row/result-table-row.component.ts
index 5322afa..c6ae55d 100644
--- a/src/app/components/browse/result-table/result-table-row/result-table-row.component.ts
+++ b/src/app/components/browse/result-table/result-table-row/result-table-row.component.ts
@@ -1,39 +1,40 @@
-import { Component, AfterViewInit, Input, ViewChild, ElementRef } from '@angular/core'
+import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'
+
import { SongResult } from '../../../../../electron/shared/interfaces/search.interface'
import { SelectionService } from '../../../../core/services/selection.service'
@Component({
- selector: 'tr[app-result-table-row]',
- templateUrl: './result-table-row.component.html',
- styleUrls: ['./result-table-row.component.scss']
+ selector: 'tr[app-result-table-row]',
+ templateUrl: './result-table-row.component.html',
+ styleUrls: ['./result-table-row.component.scss'],
})
export class ResultTableRowComponent implements AfterViewInit {
- @Input() result: SongResult
+ @Input() result: SongResult
- @ViewChild('checkbox', { static: true }) checkbox: ElementRef
+ @ViewChild('checkbox', { static: true }) checkbox: ElementRef
- constructor(private selectionService: SelectionService) { }
+ constructor(private selectionService: SelectionService) { }
- get songID() {
- return this.result.id
- }
+ get songID() {
+ return this.result.id
+ }
- ngAfterViewInit() {
- this.selectionService.onSelectionChanged(this.songID, (isChecked) => {
- if (isChecked) {
- $(this.checkbox.nativeElement).checkbox('check')
- } else {
- $(this.checkbox.nativeElement).checkbox('uncheck')
- }
- })
+ ngAfterViewInit() {
+ this.selectionService.onSelectionChanged(this.songID, isChecked => {
+ if (isChecked) {
+ $(this.checkbox.nativeElement).checkbox('check')
+ } else {
+ $(this.checkbox.nativeElement).checkbox('uncheck')
+ }
+ })
- $(this.checkbox.nativeElement).checkbox({
- onChecked: () => {
- this.selectionService.selectSong(this.songID)
- },
- onUnchecked: () => {
- this.selectionService.deselectSong(this.songID)
- }
- })
- }
-}
\ No newline at end of file
+ $(this.checkbox.nativeElement).checkbox({
+ onChecked: () => {
+ this.selectionService.selectSong(this.songID)
+ },
+ onUnchecked: () => {
+ this.selectionService.deselectSong(this.songID)
+ },
+ })
+ }
+}
diff --git a/src/app/components/browse/result-table/result-table.component.html b/src/app/components/browse/result-table/result-table.component.html
index c10b968..6700527 100644
--- a/src/app/components/browse/result-table/result-table.component.html
+++ b/src/app/components/browse/result-table/result-table.component.html
@@ -1,27 +1,30 @@
-
\ No newline at end of file
+
diff --git a/src/app/components/browse/result-table/result-table.component.ts b/src/app/components/browse/result-table/result-table.component.ts
index 4e1aa8b..afc50fe 100644
--- a/src/app/components/browse/result-table/result-table.component.ts
+++ b/src/app/components/browse/result-table/result-table.component.ts
@@ -1,83 +1,85 @@
-import { Component, Output, EventEmitter, ViewChildren, QueryList, ViewChild, OnInit } from '@angular/core'
+import { Component, EventEmitter, OnInit, Output, QueryList, ViewChild, ViewChildren } from '@angular/core'
+
+import Comparators from 'comparators'
+
+import { SettingsService } from 'src/app/core/services/settings.service'
import { SongResult } from '../../../../electron/shared/interfaces/search.interface'
-import { ResultTableRowComponent } from './result-table-row/result-table-row.component'
import { CheckboxDirective } from '../../../core/directives/checkbox.directive'
import { SearchService } from '../../../core/services/search.service'
import { SelectionService } from '../../../core/services/selection.service'
-import { SettingsService } from 'src/app/core/services/settings.service'
-import Comparators from 'comparators'
+import { ResultTableRowComponent } from './result-table-row/result-table-row.component'
@Component({
- selector: 'app-result-table',
- templateUrl: './result-table.component.html',
- styleUrls: ['./result-table.component.scss']
+ selector: 'app-result-table',
+ templateUrl: './result-table.component.html',
+ styleUrls: ['./result-table.component.scss'],
})
export class ResultTableComponent implements OnInit {
- @Output() rowClicked = new EventEmitter()
+ @Output() rowClicked = new EventEmitter()
- @ViewChild(CheckboxDirective, { static: true }) checkboxColumn: CheckboxDirective
- @ViewChildren('tableRow') tableRows: QueryList
+ @ViewChild(CheckboxDirective, { static: true }) checkboxColumn: CheckboxDirective
+ @ViewChildren('tableRow') tableRows: QueryList
- results: SongResult[] = []
- activeRowID: number = null
- sortDirection: 'ascending' | 'descending' = 'descending'
- sortColumn: 'name' | 'artist' | 'album' | 'genre' | null = null
+ results: SongResult[] = []
+ activeRowID: number = null
+ sortDirection: 'ascending' | 'descending' = 'descending'
+ sortColumn: 'name' | 'artist' | 'album' | 'genre' | null = null
- constructor(
- private searchService: SearchService,
- private selectionService: SelectionService,
- public settingsService: SettingsService
- ) { }
+ constructor(
+ private searchService: SearchService,
+ private selectionService: SelectionService,
+ public settingsService: SettingsService
+ ) { }
- ngOnInit() {
- this.selectionService.onSelectAllChanged((selected) => {
- this.checkboxColumn.check(selected)
- })
+ ngOnInit() {
+ this.selectionService.onSelectAllChanged(selected => {
+ this.checkboxColumn.check(selected)
+ })
- this.searchService.onSearchChanged(results => {
- this.activeRowID = null
- this.results = results
- this.updateSort()
- })
+ this.searchService.onSearchChanged(results => {
+ this.activeRowID = null
+ this.results = results
+ this.updateSort()
+ })
- this.searchService.onNewSearch(() => {
- this.sortColumn = null
- })
- }
+ this.searchService.onNewSearch(() => {
+ this.sortColumn = null
+ })
+ }
- onRowClicked(result: SongResult) {
- this.activeRowID = result.id
- this.rowClicked.emit(result)
- }
+ onRowClicked(result: SongResult) {
+ this.activeRowID = result.id
+ this.rowClicked.emit(result)
+ }
- onColClicked(column: 'name' | 'artist' | 'album' | 'genre') {
- if (this.results.length == 0) { return }
- if (this.sortColumn != column) {
- this.sortColumn = column
- this.sortDirection = 'descending'
- } else if (this.sortDirection == 'descending') {
- this.sortDirection = 'ascending'
- } else {
- this.sortDirection = 'descending'
- }
- this.updateSort()
- }
+ onColClicked(column: 'name' | 'artist' | 'album' | 'genre') {
+ if (this.results.length == 0) { return }
+ if (this.sortColumn != column) {
+ this.sortColumn = column
+ this.sortDirection = 'descending'
+ } else if (this.sortDirection == 'descending') {
+ this.sortDirection = 'ascending'
+ } else {
+ this.sortDirection = 'descending'
+ }
+ this.updateSort()
+ }
- private updateSort() {
- if (this.sortColumn != null) {
- this.results.sort(Comparators.comparing(this.sortColumn, { reversed: this.sortDirection == 'ascending' }))
- }
- }
+ private updateSort() {
+ if (this.sortColumn != null) {
+ this.results.sort(Comparators.comparing(this.sortColumn, { reversed: this.sortDirection == 'ascending' }))
+ }
+ }
- /**
- * Called when the user checks the `checkboxColumn`.
- */
- checkAll(isChecked: boolean) {
- if (isChecked) {
- this.selectionService.selectAll()
- } else {
- this.selectionService.deselectAll()
- }
- }
-}
\ No newline at end of file
+ /**
+ * Called when the user checks the `checkboxColumn`.
+ */
+ checkAll(isChecked: boolean) {
+ if (isChecked) {
+ this.selectionService.selectAll()
+ } else {
+ this.selectionService.deselectAll()
+ }
+ }
+}
diff --git a/src/app/components/browse/search-bar/search-bar.component.html b/src/app/components/browse/search-bar/search-bar.component.html
index a047280..80118f6 100644
--- a/src/app/components/browse/search-bar/search-bar.component.html
+++ b/src/app/components/browse/search-bar/search-bar.component.html
@@ -1,235 +1,240 @@
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+ Video Background
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/browse/search-bar/search-bar.component.scss b/src/app/components/browse/search-bar/search-bar.component.scss
index 6ebad67..7adc781 100644
--- a/src/app/components/browse/search-bar/search-bar.component.scss
+++ b/src/app/components/browse/search-bar/search-bar.component.scss
@@ -48,4 +48,4 @@
visibility 0s linear 350ms,
max-width 0s linear 350ms,
max-height 0s linear 350ms !important;
-}
\ No newline at end of file
+}
diff --git a/src/app/components/browse/search-bar/search-bar.component.ts b/src/app/components/browse/search-bar/search-bar.component.ts
index 9c84385..38c4079 100644
--- a/src/app/components/browse/search-bar/search-bar.component.ts
+++ b/src/app/components/browse/search-bar/search-bar.component.ts
@@ -1,74 +1,75 @@
-import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core'
+import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
+
import { SearchService } from 'src/app/core/services/search.service'
-import { getDefaultSearch, SongSearch } from 'src/electron/shared/interfaces/search.interface'
+import { getDefaultSearch } from 'src/electron/shared/interfaces/search.interface'
@Component({
- selector: 'app-search-bar',
- templateUrl: './search-bar.component.html',
- styleUrls: ['./search-bar.component.scss']
+ selector: 'app-search-bar',
+ templateUrl: './search-bar.component.html',
+ styleUrls: ['./search-bar.component.scss'],
})
export class SearchBarComponent implements AfterViewInit {
- @ViewChild('searchIcon', { static: true }) searchIcon: ElementRef
- @ViewChild('quantityDropdown', { static: true }) quantityDropdown: ElementRef
- @ViewChild('similarityDropdown', { static: true }) similarityDropdown: ElementRef
- @ViewChild('diffSlider', { static: true }) diffSlider: ElementRef
+ @ViewChild('searchIcon', { static: true }) searchIcon: ElementRef
+ @ViewChild('quantityDropdown', { static: true }) quantityDropdown: ElementRef
+ @ViewChild('similarityDropdown', { static: true }) similarityDropdown: ElementRef
+ @ViewChild('diffSlider', { static: true }) diffSlider: ElementRef
- isError = false
- showAdvanced = false
- searchSettings = getDefaultSearch()
- private sliderInitialized = false
+ isError = false
+ showAdvanced = false
+ searchSettings = getDefaultSearch()
+ private sliderInitialized = false
- constructor(public searchService: SearchService) { }
+ constructor(public searchService: SearchService) { }
- ngAfterViewInit() {
- $(this.searchIcon.nativeElement).popup({
- onShow: () => this.isError // Only show the popup if there is an error
- })
- this.searchService.onSearchErrorStateUpdate((isError) => {
- this.isError = isError
- })
- $(this.quantityDropdown.nativeElement).dropdown({
- onChange: (value: string) => {
- this.searchSettings.quantity = value as 'all' | 'any'
- }
- })
- $(this.similarityDropdown.nativeElement).dropdown({
- onChange: (value: string) => {
- this.searchSettings.similarity = value as 'similar' | 'exact'
- }
- })
- }
+ ngAfterViewInit() {
+ $(this.searchIcon.nativeElement).popup({
+ onShow: () => this.isError, // Only show the popup if there is an error
+ })
+ this.searchService.onSearchErrorStateUpdate(isError => {
+ this.isError = isError
+ })
+ $(this.quantityDropdown.nativeElement).dropdown({
+ onChange: (value: string) => {
+ this.searchSettings.quantity = value as 'all' | 'any'
+ },
+ })
+ $(this.similarityDropdown.nativeElement).dropdown({
+ onChange: (value: string) => {
+ this.searchSettings.similarity = value as 'similar' | 'exact'
+ },
+ })
+ }
- onSearch(query: string) {
- this.searchSettings.query = query
- this.searchSettings.limit = 50 + 1
- this.searchSettings.offset = 0
- this.searchService.newSearch(this.searchSettings)
- }
+ onSearch(query: string) {
+ this.searchSettings.query = query
+ this.searchSettings.limit = 50 + 1
+ this.searchSettings.offset = 0
+ this.searchService.newSearch(this.searchSettings)
+ }
- onAdvancedSearchClick() {
- this.showAdvanced = !this.showAdvanced
+ onAdvancedSearchClick() {
+ this.showAdvanced = !this.showAdvanced
- if (!this.sliderInitialized) {
- setTimeout(() => { // Initialization requires this element to not be collapsed
- $(this.diffSlider.nativeElement).slider({
- min: 0,
- max: 6,
- start: 0,
- end: 6,
- step: 1,
- onChange: (_length: number, min: number, max: number) => {
- this.searchSettings.minDiff = min
- this.searchSettings.maxDiff = max
- }
- })
- }, 50)
- this.sliderInitialized = true
- }
- }
+ if (!this.sliderInitialized) {
+ setTimeout(() => { // Initialization requires this element to not be collapsed
+ $(this.diffSlider.nativeElement).slider({
+ min: 0,
+ max: 6,
+ start: 0,
+ end: 6,
+ step: 1,
+ onChange: (_length: number, min: number, max: number) => {
+ this.searchSettings.minDiff = min
+ this.searchSettings.maxDiff = max
+ },
+ })
+ }, 50)
+ this.sliderInitialized = true
+ }
+ }
- isLoading() {
- return this.searchService.isLoading()
- }
-}
\ No newline at end of file
+ isLoading() {
+ return this.searchService.isLoading()
+ }
+}
diff --git a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.html b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.html
index 5980ed1..139a2b3 100644
--- a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.html
+++ b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.html
@@ -1,30 +1,28 @@
0" class="ui segments">
-
\ No newline at end of file
+
+
+
diff --git a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.scss b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.scss
index 2e8f46d..d4ee7b5 100644
--- a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.scss
+++ b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.scss
@@ -23,4 +23,4 @@
i.close.icon, a {
cursor: pointer;
-}
\ No newline at end of file
+}
diff --git a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts
index a228a07..5cdbe9c 100644
--- a/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts
+++ b/src/app/components/browse/status-bar/downloads-modal/downloads-modal.component.ts
@@ -1,55 +1,56 @@
-import { Component, ChangeDetectorRef } from '@angular/core'
+import { ChangeDetectorRef, Component } from '@angular/core'
+
import { DownloadProgress } from '../../../../../electron/shared/interfaces/download.interface'
import { DownloadService } from '../../../../core/services/download.service'
import { ElectronService } from '../../../../core/services/electron.service'
@Component({
- selector: 'app-downloads-modal',
- templateUrl: './downloads-modal.component.html',
- styleUrls: ['./downloads-modal.component.scss']
+ selector: 'app-downloads-modal',
+ templateUrl: './downloads-modal.component.html',
+ styleUrls: ['./downloads-modal.component.scss'],
})
export class DownloadsModalComponent {
- downloads: DownloadProgress[] = []
+ downloads: DownloadProgress[] = []
- constructor(private electronService: ElectronService, private downloadService: DownloadService, ref: ChangeDetectorRef) {
- electronService.receiveIPC('queue-updated', (order) => {
- this.downloads.sort((a, b) => order.indexOf(a.versionID) - order.indexOf(b.versionID))
- })
+ constructor(private electronService: ElectronService, private downloadService: DownloadService, ref: ChangeDetectorRef) {
+ electronService.receiveIPC('queue-updated', order => {
+ this.downloads.sort((a, b) => order.indexOf(a.versionID) - order.indexOf(b.versionID))
+ })
- downloadService.onDownloadUpdated(download => {
- const index = this.downloads.findIndex(thisDownload => thisDownload.versionID == download.versionID)
- if (download.type == 'cancel') {
- this.downloads = this.downloads.filter(thisDownload => thisDownload.versionID != download.versionID)
- } else if (index == -1) {
- this.downloads.push(download)
- } else {
- this.downloads[index] = download
- }
- ref.detectChanges()
- })
- }
+ downloadService.onDownloadUpdated(download => {
+ const index = this.downloads.findIndex(thisDownload => thisDownload.versionID == download.versionID)
+ if (download.type == 'cancel') {
+ this.downloads = this.downloads.filter(thisDownload => thisDownload.versionID != download.versionID)
+ } else if (index == -1) {
+ this.downloads.push(download)
+ } else {
+ this.downloads[index] = download
+ }
+ ref.detectChanges()
+ })
+ }
- trackByVersionID(_index: number, item: DownloadProgress) {
- return item.versionID
- }
+ trackByVersionID(_index: number, item: DownloadProgress) {
+ return item.versionID
+ }
- cancelDownload(versionID: number) {
- this.downloadService.cancelDownload(versionID)
- }
+ cancelDownload(versionID: number) {
+ this.downloadService.cancelDownload(versionID)
+ }
- retryDownload(versionID: number) {
- this.downloadService.retryDownload(versionID)
- }
+ retryDownload(versionID: number) {
+ this.downloadService.retryDownload(versionID)
+ }
- getBackgroundColor(download: DownloadProgress) {
- switch (download.type) {
- case 'error': return '#a63a3a'
- default: return undefined
- }
- }
+ getBackgroundColor(download: DownloadProgress) {
+ switch (download.type) {
+ case 'error': return '#a63a3a'
+ default: return undefined
+ }
+ }
- openFolder(filepath: string) {
- this.electronService.showFolder(filepath)
- }
-}
\ No newline at end of file
+ openFolder(filepath: string) {
+ this.electronService.showFolder(filepath)
+ }
+}
diff --git a/src/app/components/browse/status-bar/status-bar.component.html b/src/app/components/browse/status-bar/status-bar.component.html
index bd490bf..31aa53c 100644
--- a/src/app/components/browse/status-bar/status-bar.component.html
+++ b/src/app/components/browse/status-bar/status-bar.component.html
@@ -1,42 +1,44 @@
\ No newline at end of file
+
+
diff --git a/src/app/components/browse/status-bar/status-bar.component.scss b/src/app/components/browse/status-bar/status-bar.component.scss
index 9f58dcf..0e6c7fd 100644
--- a/src/app/components/browse/status-bar/status-bar.component.scss
+++ b/src/app/components/browse/status-bar/status-bar.component.scss
@@ -20,4 +20,4 @@
margin-right: 30px;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/app/components/browse/status-bar/status-bar.component.ts b/src/app/components/browse/status-bar/status-bar.component.ts
index 260f685..874d981 100644
--- a/src/app/components/browse/status-bar/status-bar.component.ts
+++ b/src/app/components/browse/status-bar/status-bar.component.ts
@@ -1,114 +1,115 @@
-import { Component, ChangeDetectorRef } from '@angular/core'
+import { ChangeDetectorRef, Component } from '@angular/core'
+
+import { VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
+import { groupBy } from '../../../../electron/shared/UtilFunctions'
import { DownloadService } from '../../../core/services/download.service'
import { ElectronService } from '../../../core/services/electron.service'
-import { groupBy } from '../../../../electron/shared/UtilFunctions'
-import { VersionResult } from '../../../../electron/shared/interfaces/songDetails.interface'
import { SearchService } from '../../../core/services/search.service'
import { SelectionService } from '../../../core/services/selection.service'
@Component({
- selector: 'app-status-bar',
- templateUrl: './status-bar.component.html',
- styleUrls: ['./status-bar.component.scss']
+ selector: 'app-status-bar',
+ templateUrl: './status-bar.component.html',
+ styleUrls: ['./status-bar.component.scss'],
})
export class StatusBarComponent {
- resultCount = 0
- multipleCompleted = false
- downloading = false
- error = false
- percent = 0
- batchResults: VersionResult[]
- chartGroups: VersionResult[][]
+ resultCount = 0
+ multipleCompleted = false
+ downloading = false
+ error = false
+ percent = 0
+ batchResults: VersionResult[]
+ chartGroups: VersionResult[][]
- constructor(
- private electronService: ElectronService,
- private downloadService: DownloadService,
- private searchService: SearchService,
- private selectionService: SelectionService,
- ref: ChangeDetectorRef
- ) {
- downloadService.onDownloadUpdated(() => {
- setTimeout(() => { // Make sure this is the last callback executed to get the accurate downloadCount
- this.downloading = downloadService.downloadCount > 0
- this.multipleCompleted = downloadService.completedCount > 1
- this.percent = downloadService.totalDownloadingPercent
- this.error = downloadService.anyErrorsExist
- ref.detectChanges()
- }, 0)
- })
+ constructor(
+ private electronService: ElectronService,
+ private downloadService: DownloadService,
+ private searchService: SearchService,
+ private selectionService: SelectionService,
+ ref: ChangeDetectorRef
+ ) {
+ downloadService.onDownloadUpdated(() => {
+ setTimeout(() => { // Make sure this is the last callback executed to get the accurate downloadCount
+ this.downloading = downloadService.downloadCount > 0
+ this.multipleCompleted = downloadService.completedCount > 1
+ this.percent = downloadService.totalDownloadingPercent
+ this.error = downloadService.anyErrorsExist
+ ref.detectChanges()
+ }, 0)
+ })
- searchService.onSearchChanged(() => {
- this.resultCount = searchService.resultCount
- })
- }
+ searchService.onSearchChanged(() => {
+ this.resultCount = searchService.resultCount
+ })
+ }
- get allResultsVisible() {
- return this.searchService.allResultsVisible
- }
+ get allResultsVisible() {
+ return this.searchService.allResultsVisible
+ }
- get selectedResults() {
- return this.selectionService.getSelectedResults()
- }
+ get selectedResults() {
+ return this.selectionService.getSelectedResults()
+ }
- showDownloads() {
- $('#downloadsModal').modal('show')
- }
+ showDownloads() {
+ $('#downloadsModal').modal('show')
+ }
- async downloadSelected() {
- this.chartGroups = []
- this.batchResults = await this.electronService.invoke('batch-song-details', this.selectedResults.map(result => result.id))
- const versionGroups = groupBy(this.batchResults, 'songID')
- for (const versionGroup of versionGroups) {
- if (versionGroup.findIndex(version => version.chartID != versionGroup[0].chartID) != -1) {
- // Must have multiple charts of this song
- this.chartGroups.push(versionGroup.filter(version => version.versionID == version.latestVersionID))
- }
- }
+ async downloadSelected() {
+ this.chartGroups = []
+ this.batchResults = await this.electronService.invoke('batch-song-details', this.selectedResults.map(result => result.id))
+ const versionGroups = groupBy(this.batchResults, 'songID')
+ for (const versionGroup of versionGroups) {
+ if (versionGroup.findIndex(version => version.chartID != versionGroup[0].chartID) != -1) {
+ // Must have multiple charts of this song
+ this.chartGroups.push(versionGroup.filter(version => version.versionID == version.latestVersionID))
+ }
+ }
- if (this.chartGroups.length == 0) {
- for (const versions of versionGroups) {
- this.searchService.sortChart(versions)
- const downloadVersion = versions[0]
- const downloadSong = this.selectedResults.find(song => song.id == downloadVersion.songID)
- this.downloadService.addDownload(
- downloadVersion.versionID, {
- chartName: downloadVersion.chartName,
- artist: downloadSong.artist,
- charter: downloadVersion.charters,
- driveData: downloadVersion.driveData
- })
- }
- } else {
- $('#selectedModal').modal('show')
- // [download all charts for each song] [deselect these songs] [X]
- }
- }
+ if (this.chartGroups.length == 0) {
+ for (const versions of versionGroups) {
+ this.searchService.sortChart(versions)
+ const downloadVersion = versions[0]
+ const downloadSong = this.selectedResults.find(song => song.id == downloadVersion.songID)
+ this.downloadService.addDownload(
+ downloadVersion.versionID, {
+ chartName: downloadVersion.chartName,
+ artist: downloadSong.artist,
+ charter: downloadVersion.charters,
+ driveData: downloadVersion.driveData,
+ })
+ }
+ } else {
+ $('#selectedModal').modal('show')
+ // [download all charts for each song] [deselect these songs] [X]
+ }
+ }
- downloadAllCharts() {
- const songChartGroups = groupBy(this.batchResults, 'songID', 'chartID')
- for (const chart of songChartGroups) {
- this.searchService.sortChart(chart)
- const downloadVersion = chart[0]
- const downloadSong = this.selectedResults.find(song => song.id == downloadVersion.songID)
- this.downloadService.addDownload(
- downloadVersion.versionID, {
- chartName: downloadVersion.chartName,
- artist: downloadSong.artist,
- charter: downloadVersion.charters,
- driveData: downloadVersion.driveData
- }
- )
- }
- }
+ downloadAllCharts() {
+ const songChartGroups = groupBy(this.batchResults, 'songID', 'chartID')
+ for (const chart of songChartGroups) {
+ this.searchService.sortChart(chart)
+ const downloadVersion = chart[0]
+ const downloadSong = this.selectedResults.find(song => song.id == downloadVersion.songID)
+ this.downloadService.addDownload(
+ downloadVersion.versionID, {
+ chartName: downloadVersion.chartName,
+ artist: downloadSong.artist,
+ charter: downloadVersion.charters,
+ driveData: downloadVersion.driveData,
+ }
+ )
+ }
+ }
- deselectSongsWithMultipleCharts() {
- for (const chartGroup of this.chartGroups) {
- this.selectionService.deselectSong(chartGroup[0].songID)
- }
- }
+ deselectSongsWithMultipleCharts() {
+ for (const chartGroup of this.chartGroups) {
+ this.selectionService.deselectSong(chartGroup[0].songID)
+ }
+ }
- clearCompleted() {
- this.downloadService.cancelCompleted()
- }
-}
\ No newline at end of file
+ clearCompleted() {
+ this.downloadService.cancelCompleted()
+ }
+}
diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html
index 8993aee..8060e18 100644
--- a/src/app/components/settings/settings.component.html
+++ b/src/app/components/settings/settings.component.html
@@ -1,67 +1,71 @@
-Current Cache Size:
{{cacheSize}}
+
+ Current Cache Size:
+
{{ cacheSize }}
-
Clear Cache
+
Clear Cache
-
Google rate limit delay
-
+
Google rate limit delay
+
-
- Warning: downloading files from Google with a delay less than about 30 seconds will eventually cause Google to
- refuse download requests from this program for a few hours. This limitation will be removed in a future update.
+
+ Warning: downloading files from Google with a delay less than about 30 seconds will eventually cause Google to refuse download requests from
+ this program for a few hours. This limitation will be removed in a future update.
-
-
-
{{settingsService.theme}}
-
+
+
+
{{ settingsService.theme }}
+
-
-
- {{downloadUpdateText}}
-
- {{retryUpdateText}}
- {{currentVersion}}
-
+
+
+ {{ downloadUpdateText }}
+
+
+ {{ retryUpdateText }}
+
+ {{ currentVersion }}
+
-
-
-
-
\ No newline at end of file
+
+
+
+
diff --git a/src/app/components/settings/settings.component.scss b/src/app/components/settings/settings.component.scss
index ed3c076..13b8049 100644
--- a/src/app/components/settings/settings.component.scss
+++ b/src/app/components/settings/settings.component.scss
@@ -21,4 +21,4 @@
#versionNumberButton {
margin-right: 1em;
-}
\ No newline at end of file
+}
diff --git a/src/app/components/settings/settings.component.ts b/src/app/components/settings/settings.component.ts
index 5e4f41e..f4d9cef 100644
--- a/src/app/components/settings/settings.component.ts
+++ b/src/app/components/settings/settings.component.ts
@@ -1,139 +1,140 @@
-import { Component, OnInit, AfterViewInit, ChangeDetectorRef, ViewChild, ElementRef } from '@angular/core'
+import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'
+
import { CheckboxDirective } from 'src/app/core/directives/checkbox.directive'
import { ElectronService } from 'src/app/core/services/electron.service'
import { SettingsService } from 'src/app/core/services/settings.service'
@Component({
- selector: 'app-settings',
- templateUrl: './settings.component.html',
- styleUrls: ['./settings.component.scss']
+ selector: 'app-settings',
+ templateUrl: './settings.component.html',
+ styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent implements OnInit, AfterViewInit {
- @ViewChild('themeDropdown', { static: true }) themeDropdown: ElementRef
- @ViewChild(CheckboxDirective, { static: true }) videoCheckbox: CheckboxDirective
+ @ViewChild('themeDropdown', { static: true }) themeDropdown: ElementRef
+ @ViewChild(CheckboxDirective, { static: true }) videoCheckbox: CheckboxDirective
- cacheSize = 'Calculating...'
- updateAvailable = false
- loginClicked = false
- downloadUpdateText = 'Update available'
- retryUpdateText = 'Failed to check for update'
- updateDownloading = false
- updateDownloaded = false
- updateRetrying = false
- currentVersion = ''
+ cacheSize = 'Calculating...'
+ updateAvailable = false
+ loginClicked = false
+ downloadUpdateText = 'Update available'
+ retryUpdateText = 'Failed to check for update'
+ updateDownloading = false
+ updateDownloaded = false
+ updateRetrying = false
+ currentVersion = ''
- constructor(
- public settingsService: SettingsService,
- private electronService: ElectronService,
- private ref: ChangeDetectorRef
- ) { }
+ constructor(
+ public settingsService: SettingsService,
+ private electronService: ElectronService,
+ private ref: ChangeDetectorRef
+ ) { }
- async ngOnInit() {
- this.electronService.receiveIPC('update-available', (result) => {
- this.updateAvailable = result != null
- this.updateRetrying = false
- if (this.updateAvailable) {
- this.downloadUpdateText = `Update available (${result.version})`
- }
- this.ref.detectChanges()
- })
- this.electronService.receiveIPC('update-error', (err: Error) => {
- console.log(err)
- this.updateAvailable = null
- this.updateRetrying = false
- this.retryUpdateText = `Failed to check for update: ${err.message}`
- this.ref.detectChanges()
- })
- this.electronService.invoke('get-current-version', undefined).then(version => {
- this.currentVersion = `v${version}`
- this.ref.detectChanges()
- })
- this.electronService.invoke('get-update-available', undefined).then(isAvailable => {
- this.updateAvailable = isAvailable
- this.ref.detectChanges()
- })
+ async ngOnInit() {
+ this.electronService.receiveIPC('update-available', result => {
+ this.updateAvailable = result != null
+ this.updateRetrying = false
+ if (this.updateAvailable) {
+ this.downloadUpdateText = `Update available (${result.version})`
+ }
+ this.ref.detectChanges()
+ })
+ this.electronService.receiveIPC('update-error', (err: Error) => {
+ console.log(err)
+ this.updateAvailable = null
+ this.updateRetrying = false
+ this.retryUpdateText = `Failed to check for update: ${err.message}`
+ this.ref.detectChanges()
+ })
+ this.electronService.invoke('get-current-version', undefined).then(version => {
+ this.currentVersion = `v${version}`
+ this.ref.detectChanges()
+ })
+ this.electronService.invoke('get-update-available', undefined).then(isAvailable => {
+ this.updateAvailable = isAvailable
+ this.ref.detectChanges()
+ })
- const cacheSize = await this.settingsService.getCacheSize()
- this.cacheSize = Math.round(cacheSize / 1000000) + ' MB'
- }
+ const cacheSize = await this.settingsService.getCacheSize()
+ this.cacheSize = Math.round(cacheSize / 1000000) + ' MB'
+ }
- ngAfterViewInit() {
- $(this.themeDropdown.nativeElement).dropdown({
- onChange: (_value: string, text: string) => {
- this.settingsService.theme = text
- }
- })
+ ngAfterViewInit() {
+ $(this.themeDropdown.nativeElement).dropdown({
+ onChange: (_value: string, text: string) => {
+ this.settingsService.theme = text
+ },
+ })
- this.videoCheckbox.check(this.settingsService.downloadVideos)
- }
+ this.videoCheckbox.check(this.settingsService.downloadVideos)
+ }
- async clearCache() {
- this.cacheSize = 'Please wait...'
- await this.settingsService.clearCache()
- this.cacheSize = 'Cleared!'
- }
+ async clearCache() {
+ this.cacheSize = 'Please wait...'
+ await this.settingsService.clearCache()
+ this.cacheSize = 'Cleared!'
+ }
- async downloadVideos(isChecked: boolean) {
- this.settingsService.downloadVideos = isChecked
- }
+ async downloadVideos(isChecked: boolean) {
+ this.settingsService.downloadVideos = isChecked
+ }
- async getLibraryDirectory() {
- const result = await this.electronService.showOpenDialog({
- title: 'Choose library folder',
- buttonLabel: 'This is where my charts are!',
- defaultPath: this.settingsService.libraryDirectory || '',
- properties: ['openDirectory']
- })
+ async getLibraryDirectory() {
+ const result = await this.electronService.showOpenDialog({
+ title: 'Choose library folder',
+ buttonLabel: 'This is where my charts are!',
+ defaultPath: this.settingsService.libraryDirectory || '',
+ properties: ['openDirectory'],
+ })
- if (result.canceled == false) {
- this.settingsService.libraryDirectory = result.filePaths[0]
- }
- }
+ if (result.canceled == false) {
+ this.settingsService.libraryDirectory = result.filePaths[0]
+ }
+ }
- openLibraryDirectory() {
- this.electronService.openFolder(this.settingsService.libraryDirectory)
- }
+ openLibraryDirectory() {
+ this.electronService.openFolder(this.settingsService.libraryDirectory)
+ }
- changeRateLimit(event: Event) {
- const inputElement = event.srcElement as HTMLInputElement
- this.settingsService.rateLimitDelay = Number(inputElement.value)
- }
+ changeRateLimit(event: Event) {
+ const inputElement = event.srcElement as HTMLInputElement
+ this.settingsService.rateLimitDelay = Number(inputElement.value)
+ }
- downloadUpdate() {
- if (this.updateDownloaded) {
- this.electronService.sendIPC('quit-and-install', undefined)
- } else if (!this.updateDownloading) {
- this.updateDownloading = true
- this.electronService.sendIPC('download-update', undefined)
- this.downloadUpdateText = 'Downloading... (0%)'
- this.electronService.receiveIPC('update-progress', (result) => {
- this.downloadUpdateText = `Downloading... (${result.percent.toFixed(0)}%)`
- this.ref.detectChanges()
- })
- this.electronService.receiveIPC('update-downloaded', () => {
- this.downloadUpdateText = 'Quit and install update'
- this.updateDownloaded = true
- this.ref.detectChanges()
- })
- }
- }
+ downloadUpdate() {
+ if (this.updateDownloaded) {
+ this.electronService.sendIPC('quit-and-install', undefined)
+ } else if (!this.updateDownloading) {
+ this.updateDownloading = true
+ this.electronService.sendIPC('download-update', undefined)
+ this.downloadUpdateText = 'Downloading... (0%)'
+ this.electronService.receiveIPC('update-progress', result => {
+ this.downloadUpdateText = `Downloading... (${result.percent.toFixed(0)}%)`
+ this.ref.detectChanges()
+ })
+ this.electronService.receiveIPC('update-downloaded', () => {
+ this.downloadUpdateText = 'Quit and install update'
+ this.updateDownloaded = true
+ this.ref.detectChanges()
+ })
+ }
+ }
- retryUpdate() {
- if (this.updateRetrying == false) {
- this.updateRetrying = true
- this.retryUpdateText = 'Retrying...'
- this.ref.detectChanges()
- this.electronService.sendIPC('retry-update', undefined)
- }
- }
+ retryUpdate() {
+ if (this.updateRetrying == false) {
+ this.updateRetrying = true
+ this.retryUpdateText = 'Retrying...'
+ this.ref.detectChanges()
+ this.electronService.sendIPC('retry-update', undefined)
+ }
+ }
- toggleDevTools() {
- const toolsOpened = this.electronService.currentWindow.webContents.isDevToolsOpened()
+ toggleDevTools() {
+ const toolsOpened = this.electronService.currentWindow.webContents.isDevToolsOpened()
- if (toolsOpened) {
- this.electronService.currentWindow.webContents.closeDevTools()
- } else {
- this.electronService.currentWindow.webContents.openDevTools()
- }
- }
-}
\ No newline at end of file
+ if (toolsOpened) {
+ this.electronService.currentWindow.webContents.closeDevTools()
+ } else {
+ this.electronService.currentWindow.webContents.openDevTools()
+ }
+ }
+}
diff --git a/src/app/components/toolbar/toolbar.component.html b/src/app/components/toolbar/toolbar.component.html
index 036b84d..540a607 100644
--- a/src/app/components/toolbar/toolbar.component.html
+++ b/src/app/components/toolbar/toolbar.component.html
@@ -1,15 +1,15 @@
\ No newline at end of file
+
+
diff --git a/src/app/components/toolbar/toolbar.component.scss b/src/app/components/toolbar/toolbar.component.scss
index e20917a..fb67b91 100644
--- a/src/app/components/toolbar/toolbar.component.scss
+++ b/src/app/components/toolbar/toolbar.component.scss
@@ -24,4 +24,4 @@
.close:hover {
background: rgba(255, 0, 0, .15) !important;
-}
\ No newline at end of file
+}
diff --git a/src/app/components/toolbar/toolbar.component.ts b/src/app/components/toolbar/toolbar.component.ts
index 2c92d31..c53529d 100644
--- a/src/app/components/toolbar/toolbar.component.ts
+++ b/src/app/components/toolbar/toolbar.component.ts
@@ -1,55 +1,56 @@
-import { Component, OnInit, ChangeDetectorRef } from '@angular/core'
+import { ChangeDetectorRef, Component, OnInit } from '@angular/core'
+
import { ElectronService } from '../../core/services/electron.service'
@Component({
- selector: 'app-toolbar',
- templateUrl: './toolbar.component.html',
- styleUrls: ['./toolbar.component.scss']
+ selector: 'app-toolbar',
+ templateUrl: './toolbar.component.html',
+ styleUrls: ['./toolbar.component.scss'],
})
export class ToolbarComponent implements OnInit {
- isMaximized: boolean
- updateAvailable = false
+ isMaximized: boolean
+ updateAvailable = false
- constructor(private electronService: ElectronService, private ref: ChangeDetectorRef) { }
+ constructor(private electronService: ElectronService, private ref: ChangeDetectorRef) { }
- async ngOnInit() {
- this.isMaximized = this.electronService.currentWindow.isMaximized()
- this.electronService.currentWindow.on('unmaximize', () => {
- this.isMaximized = false
- this.ref.detectChanges()
- })
- this.electronService.currentWindow.on('maximize', () => {
- this.isMaximized = true
- this.ref.detectChanges()
- })
+ async ngOnInit() {
+ this.isMaximized = this.electronService.currentWindow.isMaximized()
+ this.electronService.currentWindow.on('unmaximize', () => {
+ this.isMaximized = false
+ this.ref.detectChanges()
+ })
+ this.electronService.currentWindow.on('maximize', () => {
+ this.isMaximized = true
+ this.ref.detectChanges()
+ })
- this.electronService.receiveIPC('update-available', (result) => {
- this.updateAvailable = result != null
- this.ref.detectChanges()
- })
- this.electronService.receiveIPC('update-error', () => {
- this.updateAvailable = null
- this.ref.detectChanges()
- })
- this.updateAvailable = await this.electronService.invoke('get-update-available', undefined)
- this.ref.detectChanges()
- }
+ this.electronService.receiveIPC('update-available', result => {
+ this.updateAvailable = result != null
+ this.ref.detectChanges()
+ })
+ this.electronService.receiveIPC('update-error', () => {
+ this.updateAvailable = null
+ this.ref.detectChanges()
+ })
+ this.updateAvailable = await this.electronService.invoke('get-update-available', undefined)
+ this.ref.detectChanges()
+ }
- minimize() {
- this.electronService.currentWindow.minimize()
- }
+ minimize() {
+ this.electronService.currentWindow.minimize()
+ }
- toggleMaximized() {
- if (this.isMaximized) {
- this.electronService.currentWindow.restore()
- } else {
- this.electronService.currentWindow.maximize()
- }
- this.isMaximized = !this.isMaximized
- }
+ toggleMaximized() {
+ if (this.isMaximized) {
+ this.electronService.currentWindow.restore()
+ } else {
+ this.electronService.currentWindow.maximize()
+ }
+ this.isMaximized = !this.isMaximized
+ }
- close() {
- this.electronService.quit()
- }
-}
\ No newline at end of file
+ close() {
+ this.electronService.quit()
+ }
+}
diff --git a/src/app/core/directives/checkbox.directive.ts b/src/app/core/directives/checkbox.directive.ts
index c8f027a..c61deaf 100644
--- a/src/app/core/directives/checkbox.directive.ts
+++ b/src/app/core/directives/checkbox.directive.ts
@@ -1,38 +1,38 @@
-import { Directive, ElementRef, Output, EventEmitter, AfterViewInit } from '@angular/core'
+import { AfterViewInit, Directive, ElementRef, EventEmitter, Output } from '@angular/core'
@Directive({
- selector: '[appCheckbox]'
+ selector: '[appCheckbox]',
})
export class CheckboxDirective implements AfterViewInit {
- @Output() checked = new EventEmitter()
+ @Output() checked = new EventEmitter()
- _isChecked = false
+ _isChecked = false
- constructor(private checkbox: ElementRef) { }
+ constructor(private checkbox: ElementRef) { }
- ngAfterViewInit() {
- $(this.checkbox.nativeElement).checkbox({
- onChecked: () => {
- this.checked.emit(true)
- this._isChecked = true
- },
- onUnchecked: () => {
- this.checked.emit(false)
- this._isChecked = false
- }
- })
- }
+ ngAfterViewInit() {
+ $(this.checkbox.nativeElement).checkbox({
+ onChecked: () => {
+ this.checked.emit(true)
+ this._isChecked = true
+ },
+ onUnchecked: () => {
+ this.checked.emit(false)
+ this._isChecked = false
+ },
+ })
+ }
- check(isChecked: boolean) {
- this._isChecked = isChecked
- if (isChecked) {
- $(this.checkbox.nativeElement).checkbox('check')
- } else {
- $(this.checkbox.nativeElement).checkbox('uncheck')
- }
- }
+ check(isChecked: boolean) {
+ this._isChecked = isChecked
+ if (isChecked) {
+ $(this.checkbox.nativeElement).checkbox('check')
+ } else {
+ $(this.checkbox.nativeElement).checkbox('uncheck')
+ }
+ }
- get isChecked() {
- return this._isChecked
- }
-}
\ No newline at end of file
+ get isChecked() {
+ return this._isChecked
+ }
+}
diff --git a/src/app/core/directives/progress-bar.directive.ts b/src/app/core/directives/progress-bar.directive.ts
index 6080f89..a8ec780 100644
--- a/src/app/core/directives/progress-bar.directive.ts
+++ b/src/app/core/directives/progress-bar.directive.ts
@@ -1,26 +1,27 @@
import { Directive, ElementRef, Input } from '@angular/core'
+
import * as _ from 'underscore'
@Directive({
- selector: '[appProgressBar]'
+ selector: '[appProgressBar]',
})
export class ProgressBarDirective {
- progress: (percent: number) => void
+ progress: (percent: number) => void
- @Input() set percent(percent: number) {
- this.progress(percent)
- }
+ @Input() set percent(percent: number) {
+ this.progress(percent)
+ }
- constructor(private element: ElementRef) {
- this.progress = _.throttle((percent: number) => this.$progressBar.progress('set').percent(percent), 100)
- }
+ constructor(private element: ElementRef) {
+ this.progress = _.throttle((percent: number) => this.$progressBar.progress('set').percent(percent), 100)
+ }
- private get $progressBar() {
- if (!this._$progressBar) {
- this._$progressBar = $(this.element.nativeElement)
- }
- return this._$progressBar
- }
- private _$progressBar: any
-}
\ No newline at end of file
+ private get $progressBar() {
+ if (!this._$progressBar) {
+ this._$progressBar = $(this.element.nativeElement)
+ }
+ return this._$progressBar
+ }
+ private _$progressBar: any
+}
diff --git a/src/app/core/services/album-art.service.ts b/src/app/core/services/album-art.service.ts
index 7852d1c..df08b0f 100644
--- a/src/app/core/services/album-art.service.ts
+++ b/src/app/core/services/album-art.service.ts
@@ -1,25 +1,26 @@
import { Injectable } from '@angular/core'
+
import { ElectronService } from './electron.service'
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class AlbumArtService {
- constructor(private electronService: ElectronService) { }
+ constructor(private electronService: ElectronService) { }
- private imageCache: { [songID: number]: string } = {}
+ private imageCache: { [songID: number]: string } = {}
- async getImage(songID: number): Promise {
- if (this.imageCache[songID] == undefined) {
- const albumArtResult = await this.electronService.invoke('album-art', songID)
- if (albumArtResult) {
- this.imageCache[songID] = albumArtResult.base64Art
- } else {
- this.imageCache[songID] = null
- }
- }
+ async getImage(songID: number): Promise {
+ if (this.imageCache[songID] == undefined) {
+ const albumArtResult = await this.electronService.invoke('album-art', songID)
+ if (albumArtResult) {
+ this.imageCache[songID] = albumArtResult.base64Art
+ } else {
+ this.imageCache[songID] = null
+ }
+ }
- return this.imageCache[songID]
- }
-}
\ No newline at end of file
+ return this.imageCache[songID]
+ }
+}
diff --git a/src/app/core/services/download.service.ts b/src/app/core/services/download.service.ts
index 0857d13..7c3215d 100644
--- a/src/app/core/services/download.service.ts
+++ b/src/app/core/services/download.service.ts
@@ -1,88 +1,89 @@
-import { Injectable, EventEmitter } from '@angular/core'
+import { EventEmitter, Injectable } from '@angular/core'
+
+import { DownloadProgress, NewDownload } from '../../../electron/shared/interfaces/download.interface'
import { ElectronService } from './electron.service'
-import { NewDownload, DownloadProgress } from '../../../electron/shared/interfaces/download.interface'
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class DownloadService {
- private downloadUpdatedEmitter = new EventEmitter()
- private downloads: DownloadProgress[] = []
+ private downloadUpdatedEmitter = new EventEmitter()
+ private downloads: DownloadProgress[] = []
- constructor(private electronService: ElectronService) {
- this.electronService.receiveIPC('download-updated', result => {
- // Update with result
- const thisDownloadIndex = this.downloads.findIndex(download => download.versionID == result.versionID)
- if (result.type == 'cancel') {
- this.downloads = this.downloads.filter(download => download.versionID != result.versionID)
- } else if (thisDownloadIndex == -1) {
- this.downloads.push(result)
- } else {
- this.downloads[thisDownloadIndex] = result
- }
+ constructor(private electronService: ElectronService) {
+ this.electronService.receiveIPC('download-updated', result => {
+ // Update with result
+ const thisDownloadIndex = this.downloads.findIndex(download => download.versionID == result.versionID)
+ if (result.type == 'cancel') {
+ this.downloads = this.downloads.filter(download => download.versionID != result.versionID)
+ } else if (thisDownloadIndex == -1) {
+ this.downloads.push(result)
+ } else {
+ this.downloads[thisDownloadIndex] = result
+ }
- this.downloadUpdatedEmitter.emit(result)
- })
- }
+ this.downloadUpdatedEmitter.emit(result)
+ })
+ }
- get downloadCount() {
- return this.downloads.length
- }
+ get downloadCount() {
+ return this.downloads.length
+ }
- get completedCount() {
- return this.downloads.filter(download => download.type == 'done').length
- }
+ get completedCount() {
+ return this.downloads.filter(download => download.type == 'done').length
+ }
- get totalDownloadingPercent() {
- let total = 0
- let count = 0
- for (const download of this.downloads) {
- if (!download.stale) {
- total += download.percent
- count++
- }
- }
- return total / count
- }
+ get totalDownloadingPercent() {
+ let total = 0
+ let count = 0
+ for (const download of this.downloads) {
+ if (!download.stale) {
+ total += download.percent
+ count++
+ }
+ }
+ return total / count
+ }
- get anyErrorsExist() {
- return this.downloads.find(download => download.type == 'error') ? true : false
- }
+ get anyErrorsExist() {
+ return this.downloads.find(download => download.type == 'error') ? true : false
+ }
- addDownload(versionID: number, newDownload: NewDownload) {
- if (!this.downloads.find(download => download.versionID == versionID)) { // Don't download something twice
- if (this.downloads.every(download => download.type == 'done')) { // Reset overall progress bar if it finished
- this.downloads.forEach(download => download.stale = true)
- }
- this.electronService.sendIPC('download', { action: 'add', versionID, data: newDownload })
- }
- }
+ addDownload(versionID: number, newDownload: NewDownload) {
+ if (!this.downloads.find(download => download.versionID == versionID)) { // Don't download something twice
+ if (this.downloads.every(download => download.type == 'done')) { // Reset overall progress bar if it finished
+ this.downloads.forEach(download => download.stale = true)
+ }
+ this.electronService.sendIPC('download', { action: 'add', versionID, data: newDownload })
+ }
+ }
- onDownloadUpdated(callback: (download: DownloadProgress) => void) {
- this.downloadUpdatedEmitter.subscribe(callback)
- }
+ onDownloadUpdated(callback: (download: DownloadProgress) => void) {
+ this.downloadUpdatedEmitter.subscribe(callback)
+ }
- cancelDownload(versionID: number) {
- const removedDownload = this.downloads.find(download => download.versionID == versionID)
- if (['error', 'done'].includes(removedDownload.type)) {
- this.downloads = this.downloads.filter(download => download.versionID != versionID)
- removedDownload.type = 'cancel'
- this.downloadUpdatedEmitter.emit(removedDownload)
- } else {
- this.electronService.sendIPC('download', { action: 'cancel', versionID })
- }
- }
+ cancelDownload(versionID: number) {
+ const removedDownload = this.downloads.find(download => download.versionID == versionID)
+ if (['error', 'done'].includes(removedDownload.type)) {
+ this.downloads = this.downloads.filter(download => download.versionID != versionID)
+ removedDownload.type = 'cancel'
+ this.downloadUpdatedEmitter.emit(removedDownload)
+ } else {
+ this.electronService.sendIPC('download', { action: 'cancel', versionID })
+ }
+ }
- cancelCompleted() {
- for (const download of this.downloads) {
- if (download.type == 'done') {
- this.cancelDownload(download.versionID)
- }
- }
- }
+ cancelCompleted() {
+ for (const download of this.downloads) {
+ if (download.type == 'done') {
+ this.cancelDownload(download.versionID)
+ }
+ }
+ }
- retryDownload(versionID: number) {
- this.electronService.sendIPC('download', { action: 'retry', versionID })
- }
-}
\ No newline at end of file
+ retryDownload(versionID: number) {
+ this.electronService.sendIPC('download', { action: 'retry', versionID })
+ }
+}
diff --git a/src/app/core/services/electron.service.ts b/src/app/core/services/electron.service.ts
index 7c073f2..0652e77 100644
--- a/src/app/core/services/electron.service.ts
+++ b/src/app/core/services/electron.service.ts
@@ -3,78 +3,79 @@ import { Injectable } from '@angular/core'
// If you import a module but never use any of the imported values other than as TypeScript types,
// the resulting javascript file will look as if you never imported the module at all.
import * as electron from 'electron'
-import { IPCInvokeEvents, IPCEmitEvents } from '../../../electron/shared/IPCHandler'
+
+import { IPCEmitEvents, IPCInvokeEvents } from '../../../electron/shared/IPCHandler'
const { app, getCurrentWindow, dialog, session } = window.require('@electron/remote')
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class ElectronService {
- electron: typeof electron
+ electron: typeof electron
- get isElectron() {
- return !!(window && window.process && window.process.type)
- }
+ get isElectron() {
+ return !!(window && window.process && window.process.type)
+ }
- constructor() {
- if (this.isElectron) {
- this.electron = window.require('electron')
- this.receiveIPC('log', results => results.forEach(result => console.log(result)))
- }
- }
+ constructor() {
+ if (this.isElectron) {
+ this.electron = window.require('electron')
+ this.receiveIPC('log', results => results.forEach(result => console.log(result)))
+ }
+ }
- get currentWindow() {
- return getCurrentWindow()
- }
+ get currentWindow() {
+ return getCurrentWindow()
+ }
- /**
- * Calls an async function in the main process.
- * @param event The name of the IPC event to invoke.
- * @param data The data object to send across IPC.
- * @returns A promise that resolves to the output data.
- */
- async invoke(event: E, data: IPCInvokeEvents[E]['input']) {
- return this.electron.ipcRenderer.invoke(event, data) as Promise
- }
+ /**
+ * Calls an async function in the main process.
+ * @param event The name of the IPC event to invoke.
+ * @param data The data object to send across IPC.
+ * @returns A promise that resolves to the output data.
+ */
+ async invoke(event: E, data: IPCInvokeEvents[E]['input']) {
+ return this.electron.ipcRenderer.invoke(event, data) as Promise
+ }
- /**
- * Sends an IPC message to the main process.
- * @param event The name of the IPC event to send.
- * @param data The data object to send across IPC.
- */
- sendIPC(event: E, data: IPCEmitEvents[E]) {
- this.electron.ipcRenderer.send(event, data)
- }
+ /**
+ * Sends an IPC message to the main process.
+ * @param event The name of the IPC event to send.
+ * @param data The data object to send across IPC.
+ */
+ sendIPC(event: E, data: IPCEmitEvents[E]) {
+ this.electron.ipcRenderer.send(event, data)
+ }
- /**
- * Receives an IPC message from the main process.
- * @param event The name of the IPC event to receive.
- * @param callback The data object to receive across IPC.
- */
- receiveIPC(event: E, callback: (result: IPCEmitEvents[E]) => void) {
- this.electron.ipcRenderer.on(event, (_event, ...results) => {
- callback(results[0])
- })
- }
+ /**
+ * Receives an IPC message from the main process.
+ * @param event The name of the IPC event to receive.
+ * @param callback The data object to receive across IPC.
+ */
+ receiveIPC(event: E, callback: (result: IPCEmitEvents[E]) => void) {
+ this.electron.ipcRenderer.on(event, (_event, ...results) => {
+ callback(results[0])
+ })
+ }
- quit() {
- app.exit()
- }
+ quit() {
+ app.exit()
+ }
- openFolder(filepath: string) {
- this.electron.shell.openPath(filepath)
- }
+ openFolder(filepath: string) {
+ this.electron.shell.openPath(filepath)
+ }
- showFolder(filepath: string) {
- this.electron.shell.showItemInFolder(filepath)
- }
+ showFolder(filepath: string) {
+ this.electron.shell.showItemInFolder(filepath)
+ }
- showOpenDialog(options: Electron.OpenDialogOptions) {
- return dialog.showOpenDialog(this.currentWindow, options)
- }
+ showOpenDialog(options: Electron.OpenDialogOptions) {
+ return dialog.showOpenDialog(this.currentWindow, options)
+ }
- get defaultSession() {
- return session.defaultSession
- }
-}
\ No newline at end of file
+ get defaultSession() {
+ return session.defaultSession
+ }
+}
diff --git a/src/app/core/services/search.service.ts b/src/app/core/services/search.service.ts
index 8c64774..a25309e 100644
--- a/src/app/core/services/search.service.ts
+++ b/src/app/core/services/search.service.ts
@@ -1,118 +1,119 @@
-import { Injectable, EventEmitter } from '@angular/core'
-import { ElectronService } from './electron.service'
+import { EventEmitter, Injectable } from '@angular/core'
+
import { SongResult, SongSearch } from 'src/electron/shared/interfaces/search.interface'
import { VersionResult } from 'src/electron/shared/interfaces/songDetails.interface'
+import { ElectronService } from './electron.service'
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class SearchService {
- private resultsChangedEmitter = new EventEmitter() // For when any results change
- private newResultsEmitter = new EventEmitter() // For when a new search happens
- private errorStateEmitter = new EventEmitter() // To indicate the search's error state
- private results: SongResult[] = []
- private awaitingResults = false
- private currentQuery: SongSearch
- private _allResultsVisible = true
+ private resultsChangedEmitter = new EventEmitter() // For when any results change
+ private newResultsEmitter = new EventEmitter() // For when a new search happens
+ private errorStateEmitter = new EventEmitter() // To indicate the search's error state
+ private results: SongResult[] = []
+ private awaitingResults = false
+ private currentQuery: SongSearch
+ private _allResultsVisible = true
- constructor(private electronService: ElectronService) { }
+ constructor(private electronService: ElectronService) { }
- async newSearch(query: SongSearch) {
- if (this.awaitingResults) { return }
- this.awaitingResults = true
- this.currentQuery = query
- try {
- this.results = this.trimLastChart(await this.electronService.invoke('song-search', this.currentQuery))
- this.errorStateEmitter.emit(false)
- } catch (err) {
- this.results = []
- console.log(err.message)
- this.errorStateEmitter.emit(true)
- }
- this.awaitingResults = false
+ async newSearch(query: SongSearch) {
+ if (this.awaitingResults) { return }
+ this.awaitingResults = true
+ this.currentQuery = query
+ try {
+ this.results = this.trimLastChart(await this.electronService.invoke('song-search', this.currentQuery))
+ this.errorStateEmitter.emit(false)
+ } catch (err) {
+ this.results = []
+ console.log(err.message)
+ this.errorStateEmitter.emit(true)
+ }
+ this.awaitingResults = false
- this.newResultsEmitter.emit(this.results)
- this.resultsChangedEmitter.emit(this.results)
- }
+ this.newResultsEmitter.emit(this.results)
+ this.resultsChangedEmitter.emit(this.results)
+ }
- isLoading() {
- return this.awaitingResults
- }
+ isLoading() {
+ return this.awaitingResults
+ }
- /**
- * Event emitted when new search results are returned
- * or when more results are added to an existing search.
- * (emitted after `onNewSearch`)
- */
- onSearchChanged(callback: (results: SongResult[]) => void) {
- this.resultsChangedEmitter.subscribe(callback)
- }
+ /**
+ * Event emitted when new search results are returned
+ * or when more results are added to an existing search.
+ * (emitted after `onNewSearch`)
+ */
+ onSearchChanged(callback: (results: SongResult[]) => void) {
+ this.resultsChangedEmitter.subscribe(callback)
+ }
- /**
- * Event emitted when a new search query is typed in.
- * (emitted before `onSearchChanged`)
- */
- onNewSearch(callback: (results: SongResult[]) => void) {
- this.newResultsEmitter.subscribe(callback)
- }
+ /**
+ * Event emitted when a new search query is typed in.
+ * (emitted before `onSearchChanged`)
+ */
+ onNewSearch(callback: (results: SongResult[]) => void) {
+ this.newResultsEmitter.subscribe(callback)
+ }
- /**
- * Event emitted when the error state of the search changes.
- * (emitted before `onSearchChanged`)
- */
- onSearchErrorStateUpdate(callback: (isError: boolean) => void) {
- this.errorStateEmitter.subscribe(callback)
- }
+ /**
+ * Event emitted when the error state of the search changes.
+ * (emitted before `onSearchChanged`)
+ */
+ onSearchErrorStateUpdate(callback: (isError: boolean) => void) {
+ this.errorStateEmitter.subscribe(callback)
+ }
- get resultCount() {
- return this.results.length
- }
+ get resultCount() {
+ return this.results.length
+ }
- async updateScroll() {
- if (!this.awaitingResults && !this._allResultsVisible) {
- this.awaitingResults = true
- this.currentQuery.offset += 50
- this.results.push(...this.trimLastChart(await this.electronService.invoke('song-search', this.currentQuery)))
- this.awaitingResults = false
+ async updateScroll() {
+ if (!this.awaitingResults && !this._allResultsVisible) {
+ this.awaitingResults = true
+ this.currentQuery.offset += 50
+ this.results.push(...this.trimLastChart(await this.electronService.invoke('song-search', this.currentQuery)))
+ this.awaitingResults = false
- this.resultsChangedEmitter.emit(this.results)
- }
- }
+ this.resultsChangedEmitter.emit(this.results)
+ }
+ }
- trimLastChart(results: SongResult[]) {
- if (results.length > 50) {
- results.splice(50, 1)
- this._allResultsVisible = false
- } else {
- this._allResultsVisible = true
- }
+ trimLastChart(results: SongResult[]) {
+ if (results.length > 50) {
+ results.splice(50, 1)
+ this._allResultsVisible = false
+ } else {
+ this._allResultsVisible = true
+ }
- return results
- }
+ return results
+ }
- get allResultsVisible() {
- return this._allResultsVisible
- }
+ get allResultsVisible() {
+ return this._allResultsVisible
+ }
- /**
- * Orders `versionResults` by lastModified date, but prefer the
- * non-pack version if it's only a few days older.
- */
- sortChart(versionResults: VersionResult[]) {
- const dates: { [versionID: number]: number } = {}
- versionResults.forEach(version => dates[version.versionID] = new Date(version.lastModified).getTime())
- versionResults.sort((v1, v2) => {
- const diff = dates[v2.versionID] - dates[v1.versionID]
- if (Math.abs(diff) < 6.048e+8 && v1.driveData.inChartPack != v2.driveData.inChartPack) {
- if (v1.driveData.inChartPack) {
- return 1 // prioritize v2
- } else {
- return -1 // prioritize v1
- }
- } else {
- return diff
- }
- })
- }
-}
\ No newline at end of file
+ /**
+ * Orders `versionResults` by lastModified date, but prefer the
+ * non-pack version if it's only a few days older.
+ */
+ sortChart(versionResults: VersionResult[]) {
+ const dates: { [versionID: number]: number } = {}
+ versionResults.forEach(version => dates[version.versionID] = new Date(version.lastModified).getTime())
+ versionResults.sort((v1, v2) => {
+ const diff = dates[v2.versionID] - dates[v1.versionID]
+ if (Math.abs(diff) < 6.048e+8 && v1.driveData.inChartPack != v2.driveData.inChartPack) {
+ if (v1.driveData.inChartPack) {
+ return 1 // prioritize v2
+ } else {
+ return -1 // prioritize v1
+ }
+ } else {
+ return diff
+ }
+ })
+ }
+}
diff --git a/src/app/core/services/selection.service.ts b/src/app/core/services/selection.service.ts
index 8a284c4..825dc98 100644
--- a/src/app/core/services/selection.service.ts
+++ b/src/app/core/services/selection.service.ts
@@ -1,89 +1,90 @@
-import { Injectable, EventEmitter } from '@angular/core'
+import { EventEmitter, Injectable } from '@angular/core'
+
import { SongResult } from '../../../electron/shared/interfaces/search.interface'
import { SearchService } from './search.service'
// Note: this class prevents event cycles by only emitting events if the checkbox changes
interface SelectionEvent {
- songID: number
- selected: boolean
+ songID: number
+ selected: boolean
}
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class SelectionService {
- private searchResults: SongResult[] = []
+ private searchResults: SongResult[] = []
- private selectAllChangedEmitter = new EventEmitter()
- private selectionChangedCallbacks: { [songID: number]: (selection: boolean) => void } = {}
+ private selectAllChangedEmitter = new EventEmitter()
+ private selectionChangedCallbacks: { [songID: number]: (selection: boolean) => void } = {}
- private allSelected = false
- private selections: { [songID: number]: boolean | undefined } = {}
+ private allSelected = false
+ private selections: { [songID: number]: boolean | undefined } = {}
- constructor(searchService: SearchService) {
- searchService.onSearchChanged((results) => {
- this.searchResults = results
- if (this.allSelected) {
- this.selectAll() // Select newly added rows if allSelected
- }
- })
+ constructor(searchService: SearchService) {
+ searchService.onSearchChanged(results => {
+ this.searchResults = results
+ if (this.allSelected) {
+ this.selectAll() // Select newly added rows if allSelected
+ }
+ })
- searchService.onNewSearch((results) => {
- this.searchResults = results
- this.selectionChangedCallbacks = {}
- this.selections = {}
- this.selectAllChangedEmitter.emit(false)
- })
- }
+ searchService.onNewSearch(results => {
+ this.searchResults = results
+ this.selectionChangedCallbacks = {}
+ this.selections = {}
+ this.selectAllChangedEmitter.emit(false)
+ })
+ }
- getSelectedResults() {
- return this.searchResults.filter(result => this.selections[result.id] == true)
- }
+ getSelectedResults() {
+ return this.searchResults.filter(result => this.selections[result.id] == true)
+ }
- onSelectAllChanged(callback: (selected: boolean) => void) {
- this.selectAllChangedEmitter.subscribe(callback)
- }
+ onSelectAllChanged(callback: (selected: boolean) => void) {
+ this.selectAllChangedEmitter.subscribe(callback)
+ }
- /**
- * Emits an event when the selection for `songID` needs to change.
- * (note: only one emitter can be registered per `songID`)
- */
- onSelectionChanged(songID: number, callback: (selection: boolean) => void) {
- this.selectionChangedCallbacks[songID] = callback
- }
+ /**
+ * Emits an event when the selection for `songID` needs to change.
+ * (note: only one emitter can be registered per `songID`)
+ */
+ onSelectionChanged(songID: number, callback: (selection: boolean) => void) {
+ this.selectionChangedCallbacks[songID] = callback
+ }
- deselectAll() {
- if (this.allSelected) {
- this.allSelected = false
- this.selectAllChangedEmitter.emit(false)
- }
+ deselectAll() {
+ if (this.allSelected) {
+ this.allSelected = false
+ this.selectAllChangedEmitter.emit(false)
+ }
- setTimeout(() => this.searchResults.forEach(result => this.deselectSong(result.id)), 0)
- }
+ setTimeout(() => this.searchResults.forEach(result => this.deselectSong(result.id)), 0)
+ }
- selectAll() {
- if (!this.allSelected) {
- this.allSelected = true
- this.selectAllChangedEmitter.emit(true)
- }
+ selectAll() {
+ if (!this.allSelected) {
+ this.allSelected = true
+ this.selectAllChangedEmitter.emit(true)
+ }
- setTimeout(() => this.searchResults.forEach(result => this.selectSong(result.id)), 0)
- }
+ setTimeout(() => this.searchResults.forEach(result => this.selectSong(result.id)), 0)
+ }
- deselectSong(songID: number) {
- if (this.selections[songID]) {
- this.selections[songID] = false
- this.selectionChangedCallbacks[songID](false)
- }
- }
+ deselectSong(songID: number) {
+ if (this.selections[songID]) {
+ this.selections[songID] = false
+ this.selectionChangedCallbacks[songID](false)
+ }
+ }
- selectSong(songID: number) {
- if (!this.selections[songID]) {
- this.selections[songID] = true
- this.selectionChangedCallbacks[songID](true)
- }
- }
-}
\ No newline at end of file
+ selectSong(songID: number) {
+ if (!this.selections[songID]) {
+ this.selections[songID] = true
+ this.selectionChangedCallbacks[songID](true)
+ }
+ }
+}
diff --git a/src/app/core/services/settings.service.ts b/src/app/core/services/settings.service.ts
index b0f2252..1af9b88 100644
--- a/src/app/core/services/settings.service.ts
+++ b/src/app/core/services/settings.service.ts
@@ -1,81 +1,82 @@
import { Injectable } from '@angular/core'
-import { ElectronService } from './electron.service'
+
import { Settings } from 'src/electron/shared/Settings'
+import { ElectronService } from './electron.service'
@Injectable({
- providedIn: 'root'
+ providedIn: 'root',
})
export class SettingsService {
- readonly builtinThemes = ['Default', 'Dark']
+ readonly builtinThemes = ['Default', 'Dark']
- private settings: Settings
- private currentThemeLink: HTMLLinkElement
+ private settings: Settings
+ private currentThemeLink: HTMLLinkElement
- constructor(private electronService: ElectronService) { }
+ constructor(private electronService: ElectronService) { }
- async loadSettings() {
- this.settings = await this.electronService.invoke('get-settings', undefined)
- if (this.settings.theme != this.builtinThemes[0]) {
- this.changeTheme(this.settings.theme)
- }
- }
+ async loadSettings() {
+ this.settings = await this.electronService.invoke('get-settings', undefined)
+ if (this.settings.theme != this.builtinThemes[0]) {
+ this.changeTheme(this.settings.theme)
+ }
+ }
- saveSettings() {
- this.electronService.sendIPC('set-settings', this.settings)
- }
+ saveSettings() {
+ this.electronService.sendIPC('set-settings', this.settings)
+ }
- changeTheme(theme: string) {
- if (this.currentThemeLink != undefined) this.currentThemeLink.remove()
- if (theme == 'Default') { return }
+ changeTheme(theme: string) {
+ if (this.currentThemeLink != undefined) this.currentThemeLink.remove()
+ if (theme == 'Default') { return }
- const link = document.createElement('link')
- link.type = 'text/css'
- link.rel = 'stylesheet'
- link.href = `./assets/themes/${theme.toLowerCase()}.css`
- this.currentThemeLink = document.head.appendChild(link)
- }
+ const link = document.createElement('link')
+ link.type = 'text/css'
+ link.rel = 'stylesheet'
+ link.href = `./assets/themes/${theme.toLowerCase()}.css`
+ this.currentThemeLink = document.head.appendChild(link)
+ }
- async getCacheSize() {
- return this.electronService.defaultSession.getCacheSize()
- }
+ async getCacheSize() {
+ return this.electronService.defaultSession.getCacheSize()
+ }
- async clearCache() {
- this.saveSettings()
- await this.electronService.defaultSession.clearCache()
- await this.electronService.invoke('clear-cache', undefined)
- }
+ async clearCache() {
+ this.saveSettings()
+ await this.electronService.defaultSession.clearCache()
+ await this.electronService.invoke('clear-cache', undefined)
+ }
- // Individual getters/setters
- get libraryDirectory() {
- return this.settings.libraryPath
- }
- set libraryDirectory(newValue: string) {
- this.settings.libraryPath = newValue
- this.saveSettings()
- }
+ // Individual getters/setters
+ get libraryDirectory() {
+ return this.settings.libraryPath
+ }
+ set libraryDirectory(newValue: string) {
+ this.settings.libraryPath = newValue
+ this.saveSettings()
+ }
- get downloadVideos() {
- return this.settings.downloadVideos
- }
- set downloadVideos(isChecked) {
- this.settings.downloadVideos = isChecked
- this.saveSettings()
- }
+ get downloadVideos() {
+ return this.settings.downloadVideos
+ }
+ set downloadVideos(isChecked) {
+ this.settings.downloadVideos = isChecked
+ this.saveSettings()
+ }
- get theme() {
- return this.settings.theme
- }
- set theme(newValue: string) {
- this.settings.theme = newValue
- this.changeTheme(newValue)
- this.saveSettings()
- }
+ get theme() {
+ return this.settings.theme
+ }
+ set theme(newValue: string) {
+ this.settings.theme = newValue
+ this.changeTheme(newValue)
+ this.saveSettings()
+ }
- get rateLimitDelay() {
- return this.settings.rateLimitDelay
- }
- set rateLimitDelay(delay: number) {
- this.settings.rateLimitDelay = delay
- this.saveSettings()
- }
-}
\ No newline at end of file
+ get rateLimitDelay() {
+ return this.settings.rateLimitDelay
+ }
+ set rateLimitDelay(delay: number) {
+ this.settings.rateLimitDelay = delay
+ this.saveSettings()
+ }
+}
diff --git a/src/app/core/tab-persist.strategy.ts b/src/app/core/tab-persist.strategy.ts
index 4b667fd..d4512e4 100644
--- a/src/app/core/tab-persist.strategy.ts
+++ b/src/app/core/tab-persist.strategy.ts
@@ -1,29 +1,29 @@
-import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router'
import { Injectable } from '@angular/core'
+import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
/**
* This makes each route with the 'reuse' data flag persist when not in focus.
*/
@Injectable()
export class TabPersistStrategy extends RouteReuseStrategy {
- private handles: { [path: string]: DetachedRouteHandle } = {}
+ private handles: { [path: string]: DetachedRouteHandle } = {}
- shouldDetach(route: ActivatedRouteSnapshot) {
- return route.data.shouldReuse || false
- }
- store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle) {
- if (route.data.shouldReuse) {
- this.handles[route.routeConfig.path] = handle
- }
- }
- shouldAttach(route: ActivatedRouteSnapshot) {
- return !!route.routeConfig && !!this.handles[route.routeConfig.path]
- }
- retrieve(route: ActivatedRouteSnapshot) {
- if (!route.routeConfig) return null
- return this.handles[route.routeConfig.path]
- }
- shouldReuseRoute(future: ActivatedRouteSnapshot) {
- return future.data.shouldReuse || false
- }
-}
\ No newline at end of file
+ shouldDetach(route: ActivatedRouteSnapshot) {
+ return route.data.shouldReuse || false
+ }
+ store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle) {
+ if (route.data.shouldReuse) {
+ this.handles[route.routeConfig.path] = handle
+ }
+ }
+ shouldAttach(route: ActivatedRouteSnapshot) {
+ return !!route.routeConfig && !!this.handles[route.routeConfig.path]
+ }
+ retrieve(route: ActivatedRouteSnapshot) {
+ if (!route.routeConfig) return null
+ return this.handles[route.routeConfig.path]
+ }
+ shouldReuseRoute(future: ActivatedRouteSnapshot) {
+ return future.data.shouldReuse || false
+ }
+}
diff --git a/src/electron/ipc/CacheHandler.ipc.ts b/src/electron/ipc/CacheHandler.ipc.ts
index 0d4e625..131f4dd 100644
--- a/src/electron/ipc/CacheHandler.ipc.ts
+++ b/src/electron/ipc/CacheHandler.ipc.ts
@@ -1,10 +1,11 @@
+import { Dirent, readdir as _readdir } from 'fs'
+import { join } from 'path'
+import { rimraf } from 'rimraf'
+import { inspect, promisify } from 'util'
+
+import { devLog } from '../shared/ElectronUtilFunctions'
import { IPCInvokeHandler } from '../shared/IPCHandler'
import { tempPath } from '../shared/Paths'
-import { rimraf } from 'rimraf'
-import { Dirent, readdir as _readdir } from 'fs'
-import { inspect, promisify } from 'util'
-import { join } from 'path'
-import { devLog } from '../shared/ElectronUtilFunctions'
const readdir = promisify(_readdir)
@@ -12,30 +13,30 @@ const readdir = promisify(_readdir)
* Handles the 'clear-cache' event.
*/
class ClearCacheHandler implements IPCInvokeHandler<'clear-cache'> {
- event: 'clear-cache' = 'clear-cache'
+ event = 'clear-cache' as const
- /**
- * Deletes all the files under `tempPath`
- */
- async handler() {
- let files: Dirent[]
- try {
- files = await readdir(tempPath, { withFileTypes: true })
- } catch (err) {
- devLog('Failed to read cache: ', err)
- return
- }
+ /**
+ * Deletes all the files under `tempPath`
+ */
+ async handler() {
+ let files: Dirent[]
+ try {
+ files = await readdir(tempPath, { withFileTypes: true })
+ } catch (err) {
+ devLog('Failed to read cache: ', err)
+ return
+ }
- for (const file of files) {
- try {
- devLog(`Deleting ${file.isFile() ? 'file' : 'folder'}: ${join(tempPath, file.name)}`)
- await rimraf(join(tempPath, file.name))
- } catch (err) {
- devLog(`Failed to delete ${file.isFile() ? 'file' : 'folder'}: `, inspect(err))
- return
- }
- }
- }
+ for (const file of files) {
+ try {
+ devLog(`Deleting ${file.isFile() ? 'file' : 'folder'}: ${join(tempPath, file.name)}`)
+ await rimraf(join(tempPath, file.name))
+ } catch (err) {
+ devLog(`Failed to delete ${file.isFile() ? 'file' : 'folder'}: `, inspect(err))
+ return
+ }
+ }
+ }
}
-export const clearCacheHandler = new ClearCacheHandler()
\ No newline at end of file
+export const clearCacheHandler = new ClearCacheHandler()
diff --git a/src/electron/ipc/OpenURLHandler.ipc.ts b/src/electron/ipc/OpenURLHandler.ipc.ts
index 54f3139..bdd5be5 100644
--- a/src/electron/ipc/OpenURLHandler.ipc.ts
+++ b/src/electron/ipc/OpenURLHandler.ipc.ts
@@ -1,18 +1,19 @@
-import { IPCEmitHandler } from '../shared/IPCHandler'
import { shell } from 'electron'
+import { IPCEmitHandler } from '../shared/IPCHandler'
+
/**
* Handles the 'open-url' event.
*/
class OpenURLHandler implements IPCEmitHandler<'open-url'> {
- event: 'open-url' = 'open-url'
+ event = 'open-url' as const
- /**
- * Opens `url` in the default browser.
- */
- handler(url: string) {
- shell.openExternal(url)
- }
+ /**
+ * Opens `url` in the default browser.
+ */
+ handler(url: string) {
+ shell.openExternal(url)
+ }
}
-export const openURLHandler = new OpenURLHandler()
\ No newline at end of file
+export const openURLHandler = new OpenURLHandler()
diff --git a/src/electron/ipc/SettingsHandler.ipc.ts b/src/electron/ipc/SettingsHandler.ipc.ts
index 9125e7a..bfda4b3 100644
--- a/src/electron/ipc/SettingsHandler.ipc.ts
+++ b/src/electron/ipc/SettingsHandler.ipc.ts
@@ -1,7 +1,8 @@
import * as fs from 'fs'
-import { dataPath, tempPath, themesPath, settingsPath } from '../shared/Paths'
import { promisify } from 'util'
-import { IPCInvokeHandler, IPCEmitHandler } from '../shared/IPCHandler'
+
+import { IPCEmitHandler, IPCInvokeHandler } from '../shared/IPCHandler'
+import { dataPath, settingsPath, tempPath, themesPath } from '../shared/Paths'
import { defaultSettings, Settings } from '../shared/Settings'
const exists = promisify(fs.exists)
@@ -11,83 +12,83 @@ const writeFile = promisify(fs.writeFile)
let settings: Settings
-/**
- * Handles the 'get-settings' event.
- */
-class GetSettingsHandler implements IPCInvokeHandler<'get-settings'> {
- event: 'get-settings' = 'get-settings'
-
- /**
- * @returns the current settings oject, or default settings if they couldn't be loaded.
- */
- handler() {
- return this.getSettings()
- }
-
- /**
- * @returns the current settings oject, or default settings if they couldn't be loaded.
- */
- getSettings() {
- if (settings == undefined) {
- return defaultSettings
- } else {
- return settings
- }
- }
-
- /**
- * If data directories don't exist, creates them and saves the default settings.
- * Otherwise, loads user settings from data directories.
- * If this process fails, default settings are used.
- */
- async initSettings() {
- try {
- // Create data directories if they don't exists
- for (const path of [dataPath, tempPath, themesPath]) {
- if (!await exists(path)) {
- await mkdir(path)
- }
- }
-
- // Read/create settings
- if (await exists(settingsPath)) {
- settings = JSON.parse(await readFile(settingsPath, 'utf8'))
- settings = Object.assign(JSON.parse(JSON.stringify(defaultSettings)), settings)
- } else {
- await SetSettingsHandler.saveSettings(defaultSettings)
- settings = defaultSettings
- }
- } catch (e) {
- console.error('Failed to initialize settings! Default settings will be used.')
- console.error(e)
- settings = defaultSettings
- }
- }
-}
-
/**
* Handles the 'set-settings' event.
*/
class SetSettingsHandler implements IPCEmitHandler<'set-settings'> {
- event: 'set-settings' = 'set-settings'
+ event = 'set-settings' as const
- /**
- * Updates Bridge's settings object to `newSettings` and saves them to Bridge's data directories.
- */
- handler(newSettings: Settings) {
- settings = newSettings
- SetSettingsHandler.saveSettings(settings)
- }
+ /**
+ * Updates Bridge's settings object to `newSettings` and saves them to Bridge's data directories.
+ */
+ handler(newSettings: Settings) {
+ settings = newSettings
+ SetSettingsHandler.saveSettings(settings)
+ }
- /**
- * Saves `settings` to Bridge's data directories.
- */
- static async saveSettings(settings: Settings) {
- const settingsJSON = JSON.stringify(settings, undefined, 2)
- await writeFile(settingsPath, settingsJSON, 'utf8')
- }
+ /**
+ * Saves `settings` to Bridge's data directories.
+ */
+ static async saveSettings(settings: Settings) {
+ const settingsJSON = JSON.stringify(settings, undefined, 2)
+ await writeFile(settingsPath, settingsJSON, 'utf8')
+ }
+}
+
+/**
+ * Handles the 'get-settings' event.
+ */
+class GetSettingsHandler implements IPCInvokeHandler<'get-settings'> {
+ event = 'get-settings' as const
+
+ /**
+ * @returns the current settings oject, or default settings if they couldn't be loaded.
+ */
+ handler() {
+ return this.getSettings()
+ }
+
+ /**
+ * @returns the current settings oject, or default settings if they couldn't be loaded.
+ */
+ getSettings() {
+ if (settings == undefined) {
+ return defaultSettings
+ } else {
+ return settings
+ }
+ }
+
+ /**
+ * If data directories don't exist, creates them and saves the default settings.
+ * Otherwise, loads user settings from data directories.
+ * If this process fails, default settings are used.
+ */
+ async initSettings() {
+ try {
+ // Create data directories if they don't exists
+ for (const path of [dataPath, tempPath, themesPath]) {
+ if (!await exists(path)) {
+ await mkdir(path)
+ }
+ }
+
+ // Read/create settings
+ if (await exists(settingsPath)) {
+ settings = JSON.parse(await readFile(settingsPath, 'utf8'))
+ settings = Object.assign(JSON.parse(JSON.stringify(defaultSettings)), settings)
+ } else {
+ await SetSettingsHandler.saveSettings(defaultSettings)
+ settings = defaultSettings
+ }
+ } catch (e) {
+ console.error('Failed to initialize settings! Default settings will be used.')
+ console.error(e)
+ settings = defaultSettings
+ }
+ }
}
export const getSettingsHandler = new GetSettingsHandler()
export const setSettingsHandler = new SetSettingsHandler()
-export function getSettings() { return getSettingsHandler.getSettings() }
\ No newline at end of file
+export function getSettings() { return getSettingsHandler.getSettings() }
diff --git a/src/electron/ipc/UpdateHandler.ipc.ts b/src/electron/ipc/UpdateHandler.ipc.ts
index e68b537..3ac6a66 100644
--- a/src/electron/ipc/UpdateHandler.ipc.ts
+++ b/src/electron/ipc/UpdateHandler.ipc.ts
@@ -1,12 +1,13 @@
-import { IPCEmitHandler, IPCInvokeHandler } from '../shared/IPCHandler'
import { autoUpdater, UpdateInfo } from 'electron-updater'
+
import { emitIPCEvent } from '../main'
+import { IPCEmitHandler, IPCInvokeHandler } from '../shared/IPCHandler'
export interface UpdateProgress {
- bytesPerSecond: number
- percent: number
- transferred: number
- total: number
+ bytesPerSecond: number
+ percent: number
+ transferred: number
+ total: number
}
let updateAvailable = false
@@ -15,44 +16,44 @@ let updateAvailable = false
* Checks for updates when the program is launched.
*/
class UpdateChecker implements IPCEmitHandler<'retry-update'> {
- event: 'retry-update' = 'retry-update'
+ event = 'retry-update' as const
- /**
- * Check for an update.
- */
- handler() {
- this.checkForUpdates()
- }
+ constructor() {
+ autoUpdater.autoDownload = false
+ autoUpdater.logger = null
+ this.registerUpdaterListeners()
+ }
- constructor() {
- autoUpdater.autoDownload = false
- autoUpdater.logger = null
- this.registerUpdaterListeners()
- }
+ /**
+ * Check for an update.
+ */
+ handler() {
+ this.checkForUpdates()
+ }
- checkForUpdates() {
- autoUpdater.checkForUpdates().catch(reason => {
- updateAvailable = null
- emitIPCEvent('update-error', reason)
- })
- }
+ checkForUpdates() {
+ autoUpdater.checkForUpdates().catch(reason => {
+ updateAvailable = null
+ emitIPCEvent('update-error', reason)
+ })
+ }
- private registerUpdaterListeners() {
- autoUpdater.on('error', (err: Error) => {
- updateAvailable = null
- emitIPCEvent('update-error', err)
- })
+ private registerUpdaterListeners() {
+ autoUpdater.on('error', (err: Error) => {
+ updateAvailable = null
+ emitIPCEvent('update-error', err)
+ })
- autoUpdater.on('update-available', (info: UpdateInfo) => {
- updateAvailable = true
- emitIPCEvent('update-available', info)
- })
+ autoUpdater.on('update-available', (info: UpdateInfo) => {
+ updateAvailable = true
+ emitIPCEvent('update-available', info)
+ })
- autoUpdater.on('update-not-available', (info: UpdateInfo) => {
- updateAvailable = false
- emitIPCEvent('update-available', null)
- })
- }
+ autoUpdater.on('update-not-available', (info: UpdateInfo) => {
+ updateAvailable = false
+ emitIPCEvent('update-available', null)
+ })
+ }
}
export const updateChecker = new UpdateChecker()
@@ -61,14 +62,14 @@ export const updateChecker = new UpdateChecker()
* Handles the 'get-update-available' event.
*/
class GetUpdateAvailableHandler implements IPCInvokeHandler<'get-update-available'> {
- event: 'get-update-available' = 'get-update-available'
+ event = 'get-update-available' as const
- /**
- * @returns `true` if an update is available.
- */
- handler() {
- return updateAvailable
- }
+ /**
+ * @returns `true` if an update is available.
+ */
+ handler() {
+ return updateAvailable
+ }
}
export const getUpdateAvailableHandler = new GetUpdateAvailableHandler()
@@ -77,14 +78,14 @@ export const getUpdateAvailableHandler = new GetUpdateAvailableHandler()
* Handles the 'get-current-version' event.
*/
class GetCurrentVersionHandler implements IPCInvokeHandler<'get-current-version'> {
- event: 'get-current-version' = 'get-current-version'
+ event = 'get-current-version' as const
- /**
- * @returns the current version of Bridge.
- */
- handler() {
- return autoUpdater.currentVersion.raw
- }
+ /**
+ * @returns the current version of Bridge.
+ */
+ handler() {
+ return autoUpdater.currentVersion.raw
+ }
}
export const getCurrentVersionHandler = new GetCurrentVersionHandler()
@@ -93,26 +94,26 @@ export const getCurrentVersionHandler = new GetCurrentVersionHandler()
* Handles the 'download-update' event.
*/
class DownloadUpdateHandler implements IPCEmitHandler<'download-update'> {
- event: 'download-update' = 'download-update'
- downloading = false
+ event = 'download-update' as const
+ downloading = false
- /**
- * Begins the process of downloading the latest update.
- */
- handler() {
- if (this.downloading) { return }
- this.downloading = true
+ /**
+ * Begins the process of downloading the latest update.
+ */
+ handler() {
+ if (this.downloading) { return }
+ this.downloading = true
- autoUpdater.on('download-progress', (updateProgress: UpdateProgress) => {
- emitIPCEvent('update-progress', updateProgress)
- })
+ autoUpdater.on('download-progress', (updateProgress: UpdateProgress) => {
+ emitIPCEvent('update-progress', updateProgress)
+ })
- autoUpdater.on('update-downloaded', () => {
- emitIPCEvent('update-downloaded', undefined)
- })
+ autoUpdater.on('update-downloaded', () => {
+ emitIPCEvent('update-downloaded', undefined)
+ })
- autoUpdater.downloadUpdate()
- }
+ autoUpdater.downloadUpdate()
+ }
}
export const downloadUpdateHandler = new DownloadUpdateHandler()
@@ -121,14 +122,14 @@ export const downloadUpdateHandler = new DownloadUpdateHandler()
* Handles the 'quit-and-install' event.
*/
class QuitAndInstallHandler implements IPCEmitHandler<'quit-and-install'> {
- event: 'quit-and-install' = 'quit-and-install'
+ event = 'quit-and-install' as const
- /**
- * Immediately closes the application and installs the update.
- */
- handler() {
- autoUpdater.quitAndInstall() // autoUpdater installs a downloaded update on the next program restart by default
- }
+ /**
+ * Immediately closes the application and installs the update.
+ */
+ handler() {
+ autoUpdater.quitAndInstall() // autoUpdater installs a downloaded update on the next program restart by default
+ }
}
-export const quitAndInstallHandler = new QuitAndInstallHandler()
\ No newline at end of file
+export const quitAndInstallHandler = new QuitAndInstallHandler()
diff --git a/src/electron/ipc/browse/AlbumArtHandler.ipc.ts b/src/electron/ipc/browse/AlbumArtHandler.ipc.ts
index 8c3f1db..1f6138b 100644
--- a/src/electron/ipc/browse/AlbumArtHandler.ipc.ts
+++ b/src/electron/ipc/browse/AlbumArtHandler.ipc.ts
@@ -1,20 +1,20 @@
-import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { AlbumArtResult } from '../../shared/interfaces/songDetails.interface'
+import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { serverURL } from '../../shared/Paths'
/**
* Handles the 'album-art' event.
*/
class AlbumArtHandler implements IPCInvokeHandler<'album-art'> {
- event: 'album-art' = 'album-art'
+ event = 'album-art' as const
- /**
- * @returns an `AlbumArtResult` object containing the album art for the song with `songID`.
- */
- async handler(songID: number): Promise {
- const response = await fetch(`https://${serverURL}/api/data/album-art/${songID}`)
- return await response.json()
- }
+ /**
+ * @returns an `AlbumArtResult` object containing the album art for the song with `songID`.
+ */
+ async handler(songID: number): Promise {
+ const response = await fetch(`https://${serverURL}/api/data/album-art/${songID}`)
+ return await response.json()
+ }
}
-export const albumArtHandler = new AlbumArtHandler()
\ No newline at end of file
+export const albumArtHandler = new AlbumArtHandler()
diff --git a/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts b/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts
index 0182db3..6a171c2 100644
--- a/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts
+++ b/src/electron/ipc/browse/BatchSongDetailsHandler.ipc.ts
@@ -1,20 +1,20 @@
-import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { VersionResult } from '../../shared/interfaces/songDetails.interface'
+import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { serverURL } from '../../shared/Paths'
/**
* Handles the 'batch-song-details' event.
*/
class BatchSongDetailsHandler implements IPCInvokeHandler<'batch-song-details'> {
- event: 'batch-song-details' = 'batch-song-details'
+ event = 'batch-song-details' as const
- /**
- * @returns an array of all the chart versions with a songID found in `songIDs`.
- */
- async handler(songIDs: number[]): Promise {
- const response = await fetch(`https://${serverURL}/api/data/song-versions/${songIDs.join(',')}`)
- return await response.json()
- }
+ /**
+ * @returns an array of all the chart versions with a songID found in `songIDs`.
+ */
+ async handler(songIDs: number[]): Promise {
+ const response = await fetch(`https://${serverURL}/api/data/song-versions/${songIDs.join(',')}`)
+ return await response.json()
+ }
}
-export const batchSongDetailsHandler = new BatchSongDetailsHandler()
\ No newline at end of file
+export const batchSongDetailsHandler = new BatchSongDetailsHandler()
diff --git a/src/electron/ipc/browse/SearchHandler.ipc.ts b/src/electron/ipc/browse/SearchHandler.ipc.ts
index 838f604..fab7dfc 100644
--- a/src/electron/ipc/browse/SearchHandler.ipc.ts
+++ b/src/electron/ipc/browse/SearchHandler.ipc.ts
@@ -1,27 +1,28 @@
-import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { SongResult, SongSearch } from '../../shared/interfaces/search.interface'
+import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { serverURL } from '../../shared/Paths'
/**
* Handles the 'song-search' event.
*/
class SearchHandler implements IPCInvokeHandler<'song-search'> {
- event: 'song-search' = 'song-search'
+ event = 'song-search' as const
- /**
- * @returns the top 50 songs that match `search`.
- */
- async handler(search: SongSearch): Promise {
- const response = await fetch(`https://${serverURL}/api/search`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(search)
- })
+ /**
+ * @returns the top 50 songs that match `search`.
+ */
+ async handler(search: SongSearch): Promise {
+ const response = await fetch(`https://${serverURL}/api/search`, {
+ method: 'POST',
+ headers: {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(search),
+ })
- return await response.json()
- }
+ return await response.json()
+ }
}
-export const searchHandler = new SearchHandler()
\ No newline at end of file
+export const searchHandler = new SearchHandler()
diff --git a/src/electron/ipc/browse/SongDetailsHandler.ipc.ts b/src/electron/ipc/browse/SongDetailsHandler.ipc.ts
index 8f257fa..bf66222 100644
--- a/src/electron/ipc/browse/SongDetailsHandler.ipc.ts
+++ b/src/electron/ipc/browse/SongDetailsHandler.ipc.ts
@@ -1,20 +1,20 @@
-import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { VersionResult } from '../../shared/interfaces/songDetails.interface'
+import { IPCInvokeHandler } from '../../shared/IPCHandler'
import { serverURL } from '../../shared/Paths'
/**
* Handles the 'song-details' event.
*/
class SongDetailsHandler implements IPCInvokeHandler<'song-details'> {
- event: 'song-details' = 'song-details'
+ event = 'song-details' as const
- /**
- * @returns the chart versions with `songID`.
- */
- async handler(songID: number): Promise {
- const response = await fetch(`https://${serverURL}/api/data/song-versions/${songID}`)
- return await response.json()
- }
+ /**
+ * @returns the chart versions with `songID`.
+ */
+ async handler(songID: number): Promise {
+ const response = await fetch(`https://${serverURL}/api/data/song-versions/${songID}`)
+ return await response.json()
+ }
}
-export const songDetailsHandler = new SongDetailsHandler()
\ No newline at end of file
+export const songDetailsHandler = new SongDetailsHandler()
diff --git a/src/electron/ipc/download/ChartDownload.ts b/src/electron/ipc/download/ChartDownload.ts
index ccf8de3..500adfe 100644
--- a/src/electron/ipc/download/ChartDownload.ts
+++ b/src/electron/ipc/download/ChartDownload.ts
@@ -1,282 +1,284 @@
-import { FileDownloader, getDownloader } from './FileDownloader'
import { join, parse } from 'path'
-import { FileExtractor } from './FileExtractor'
-import { sanitizeFilename, interpolate } from '../../shared/UtilFunctions'
-import { emitIPCEvent } from '../../main'
-import { ProgressType, NewDownload } from 'src/electron/shared/interfaces/download.interface'
-import { DriveFile } from 'src/electron/shared/interfaces/songDetails.interface'
-import { FileTransfer } from './FileTransfer'
import { rimraf } from 'rimraf'
-import { FilesystemChecker } from './FilesystemChecker'
-import { getSettings } from '../SettingsHandler.ipc'
-import { hasVideoExtension } from '../../shared/ElectronUtilFunctions'
-type EventCallback = {
- /** Note: this will not be the last event if `retry()` is called. */
- 'error': () => void
- 'complete': () => void
+import { NewDownload, ProgressType } from 'src/electron/shared/interfaces/download.interface'
+import { DriveFile } from 'src/electron/shared/interfaces/songDetails.interface'
+import { emitIPCEvent } from '../../main'
+import { hasVideoExtension } from '../../shared/ElectronUtilFunctions'
+import { interpolate, sanitizeFilename } from '../../shared/UtilFunctions'
+import { getSettings } from '../SettingsHandler.ipc'
+import { FileDownloader, getDownloader } from './FileDownloader'
+import { FileExtractor } from './FileExtractor'
+import { FilesystemChecker } from './FilesystemChecker'
+import { FileTransfer } from './FileTransfer'
+
+interface EventCallback {
+ /** Note: this will not be the last event if `retry()` is called. */
+ 'error': () => void
+ 'complete': () => void
}
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
-export type DownloadError = { header: string; body: string; isLink?: boolean }
+export interface DownloadError { header: string; body: string; isLink?: boolean }
export class ChartDownload {
- private retryFn: () => void | Promise
- private cancelFn: () => void
+ private retryFn: () => void | Promise
+ private cancelFn: () => void
- private callbacks = {} as Callbacks
- private files: DriveFile[]
- private percent = 0 // Needs to be stored here because errors won't know the exact percent
- private tempPath: string
- private wasCanceled = false
+ private callbacks = {} as Callbacks
+ private files: DriveFile[]
+ private percent = 0 // Needs to be stored here because errors won't know the exact percent
+ private tempPath: string
+ private wasCanceled = false
- private readonly individualFileProgressPortion: number
- private readonly destinationFolderName: string
+ private readonly individualFileProgressPortion: number
+ private readonly destinationFolderName: string
- private _allFilesProgress = 0
- get allFilesProgress() { return this._allFilesProgress }
- private _hasFailed = false
- /** If this chart download needs to be retried */
- get hasFailed() { return this._hasFailed }
- get isArchive() { return this.data.driveData.isArchive }
- get hash() { return this.data.driveData.filesHash }
+ private _allFilesProgress = 0
+ get allFilesProgress() { return this._allFilesProgress }
+ private _hasFailed = false
+ /** If this chart download needs to be retried */
+ get hasFailed() { return this._hasFailed }
+ get isArchive() { return this.data.driveData.isArchive }
+ get hash() { return this.data.driveData.filesHash }
- constructor(public versionID: number, private data: NewDownload) {
- this.updateGUI('', 'Waiting for other downloads to finish...', 'good')
- this.files = this.filterDownloadFiles(data.driveData.files)
- this.individualFileProgressPortion = 80 / this.files.length
- if (data.driveData.inChartPack) {
- this.destinationFolderName = sanitizeFilename(parse(data.driveData.files[0].name).name)
- } else {
- this.destinationFolderName = sanitizeFilename(`${this.data.artist} - ${this.data.chartName} (${this.data.charter})`)
- }
- }
+ constructor(public versionID: number, private data: NewDownload) {
+ this.updateGUI('', 'Waiting for other downloads to finish...', 'good')
+ this.files = this.filterDownloadFiles(data.driveData.files)
+ this.individualFileProgressPortion = 80 / this.files.length
+ if (data.driveData.inChartPack) {
+ this.destinationFolderName = sanitizeFilename(parse(data.driveData.files[0].name).name)
+ } else {
+ this.destinationFolderName = sanitizeFilename(`${this.data.artist} - ${this.data.chartName} (${this.data.charter})`)
+ }
+ }
- /**
- * Calls `callback` when `event` fires. (no events will be fired after `this.cancel()` is called)
- */
- on(event: E, callback: EventCallback[E]) {
- this.callbacks[event] = callback
- }
+ /**
+ * Calls `callback` when `event` fires. (no events will be fired after `this.cancel()` is called)
+ */
+ on(event: E, callback: EventCallback[E]) {
+ this.callbacks[event] = callback
+ }
- filterDownloadFiles(files: DriveFile[]) {
- return files.filter(file => {
- return (file.name != 'ch.dat') && (getSettings().downloadVideos || !hasVideoExtension(file.name))
- })
- }
+ filterDownloadFiles(files: DriveFile[]) {
+ return files.filter(file => {
+ return (file.name != 'ch.dat') && (getSettings().downloadVideos || !hasVideoExtension(file.name))
+ })
+ }
- /**
- * Retries the last failed step if it is running.
- */
- retry() { // Only allow it to be called once
- if (this.retryFn != undefined) {
- this._hasFailed = false
- const retryFn = this.retryFn
- this.retryFn = undefined
- retryFn()
- }
- }
+ /**
+ * Retries the last failed step if it is running.
+ */
+ retry() { // Only allow it to be called once
+ if (this.retryFn != undefined) {
+ this._hasFailed = false
+ const retryFn = this.retryFn
+ this.retryFn = undefined
+ retryFn()
+ }
+ }
- /**
- * Updates the GUI to indicate that a retry will be attempted.
- */
- displayRetrying() {
- this.updateGUI('', 'Waiting for other downloads to finish to retry...', 'good')
- }
+ /**
+ * Updates the GUI to indicate that a retry will be attempted.
+ */
+ displayRetrying() {
+ this.updateGUI('', 'Waiting for other downloads to finish to retry...', 'good')
+ }
- /**
- * Cancels the download if it is running.
- */
- cancel() { // Only allow it to be called once
- if (this.cancelFn != undefined) {
- const cancelFn = this.cancelFn
- this.cancelFn = undefined
- cancelFn()
- rimraf(this.tempPath).catch(() => { /** Do nothing */ }) // Delete temp folder
- }
- this.updateGUI('', '', 'cancel')
- this.wasCanceled = true
- }
+ /**
+ * Cancels the download if it is running.
+ */
+ cancel() { // Only allow it to be called once
+ if (this.cancelFn != undefined) {
+ const cancelFn = this.cancelFn
+ this.cancelFn = undefined
+ cancelFn()
+ rimraf(this.tempPath).catch(() => { /** Do nothing */ }) // Delete temp folder
+ }
+ this.updateGUI('', '', 'cancel')
+ this.wasCanceled = true
+ }
- /**
- * Updates the GUI with new information about this chart download.
- */
- private updateGUI(header: string, description: string, type: ProgressType, isLink = false) {
- if (this.wasCanceled) { return }
+ /**
+ * Updates the GUI with new information about this chart download.
+ */
+ private updateGUI(header: string, description: string, type: ProgressType, isLink = false) {
+ if (this.wasCanceled) { return }
- emitIPCEvent('download-updated', {
- versionID: this.versionID,
- title: `${this.data.chartName} - ${this.data.artist}`,
- header: header,
- description: description,
- percent: this.percent,
- type: type,
- isLink
- })
- }
+ emitIPCEvent('download-updated', {
+ versionID: this.versionID,
+ title: `${this.data.chartName} - ${this.data.artist}`,
+ header: header,
+ description: description,
+ percent: this.percent,
+ type: type,
+ isLink,
+ })
+ }
- /**
- * Save the retry function, update the GUI, and call the `error` callback.
- */
- private handleError(err: DownloadError, retry: () => void) {
- this._hasFailed = true
- this.retryFn = retry
- this.updateGUI(err.header, err.body, 'error', err.isLink == true)
- this.callbacks.error()
- }
+ /**
+ * Save the retry function, update the GUI, and call the `error` callback.
+ */
+ private handleError(err: DownloadError, retry: () => void) {
+ this._hasFailed = true
+ this.retryFn = retry
+ this.updateGUI(err.header, err.body, 'error', err.isLink == true)
+ this.callbacks.error()
+ }
- /**
- * Starts the download process.
- */
- async beginDownload() {
- // CHECK FILESYSTEM ACCESS
- const checker = new FilesystemChecker(this.destinationFolderName)
- this.cancelFn = () => checker.cancelCheck()
+ /**
+ * Starts the download process.
+ */
+ async beginDownload() {
+ // CHECK FILESYSTEM ACCESS
+ const checker = new FilesystemChecker(this.destinationFolderName)
+ this.cancelFn = () => checker.cancelCheck()
- const checkerComplete = this.addFilesystemCheckerEventListeners(checker)
- checker.beginCheck()
- await checkerComplete
+ const checkerComplete = this.addFilesystemCheckerEventListeners(checker)
+ checker.beginCheck()
+ await checkerComplete
- // DOWNLOAD FILES
- for (let i = 0; i < this.files.length; i++) {
- let wasCanceled = false
- this.cancelFn = () => { wasCanceled = true }
- const downloader = getDownloader(this.files[i].webContentLink, join(this.tempPath, this.files[i].name))
- if (wasCanceled) { return }
- this.cancelFn = () => downloader.cancelDownload()
+ // DOWNLOAD FILES
+ for (let i = 0; i < this.files.length; i++) {
+ let wasCanceled = false
+ this.cancelFn = () => { wasCanceled = true }
+ const downloader = getDownloader(this.files[i].webContentLink, join(this.tempPath, this.files[i].name))
+ if (wasCanceled) { return }
+ this.cancelFn = () => downloader.cancelDownload()
- const downloadComplete = this.addDownloadEventListeners(downloader, i)
- downloader.beginDownload()
- await downloadComplete
- }
+ const downloadComplete = this.addDownloadEventListeners(downloader, i)
+ downloader.beginDownload()
+ await downloadComplete
+ }
- // EXTRACT FILES
- if (this.isArchive) {
- const extractor = new FileExtractor(this.tempPath)
- this.cancelFn = () => extractor.cancelExtract()
+ // EXTRACT FILES
+ if (this.isArchive) {
+ const extractor = new FileExtractor(this.tempPath)
+ this.cancelFn = () => extractor.cancelExtract()
- const extractComplete = this.addExtractorEventListeners(extractor)
- extractor.beginExtract()
- await extractComplete
- }
+ const extractComplete = this.addExtractorEventListeners(extractor)
+ extractor.beginExtract()
+ await extractComplete
+ }
- // TRANSFER FILES
- const transfer = new FileTransfer(this.tempPath, this.destinationFolderName)
- this.cancelFn = () => transfer.cancelTransfer()
+ // TRANSFER FILES
+ const transfer = new FileTransfer(this.tempPath, this.destinationFolderName)
+ this.cancelFn = () => transfer.cancelTransfer()
- const transferComplete = this.addTransferEventListeners(transfer)
- transfer.beginTransfer()
- await transferComplete
+ const transferComplete = this.addTransferEventListeners(transfer)
+ transfer.beginTransfer()
+ await transferComplete
- this.callbacks.complete()
- }
+ this.callbacks.complete()
+ }
- /**
- * Defines what happens in reponse to `FilesystemChecker` events.
- * @returns a `Promise` that resolves when the filesystem has been checked.
- */
- private addFilesystemCheckerEventListeners(checker: FilesystemChecker) {
- checker.on('start', () => {
- this.updateGUI('Checking filesystem...', '', 'good')
- })
+ /**
+ * Defines what happens in reponse to `FilesystemChecker` events.
+ * @returns a `Promise` that resolves when the filesystem has been checked.
+ */
+ private addFilesystemCheckerEventListeners(checker: FilesystemChecker) {
+ checker.on('start', () => {
+ this.updateGUI('Checking filesystem...', '', 'good')
+ })
- checker.on('error', this.handleError.bind(this))
+ checker.on('error', this.handleError.bind(this))
- return new Promise(resolve => {
- checker.on('complete', (tempPath) => {
- this.tempPath = tempPath
- resolve()
- })
- })
- }
+ return new Promise(resolve => {
+ checker.on('complete', tempPath => {
+ this.tempPath = tempPath
+ resolve()
+ })
+ })
+ }
- /**
- * Defines what happens in response to `FileDownloader` events.
- * @returns a `Promise` that resolves when the download finishes.
- */
- private addDownloadEventListeners(downloader: FileDownloader, fileIndex: number) {
- let downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
- let downloadStartPoint = 0 // How far into the individual file progress portion the download progress starts
- let fileProgress = 0
+ /**
+ * Defines what happens in response to `FileDownloader` events.
+ * @returns a `Promise` that resolves when the download finishes.
+ */
+ private addDownloadEventListeners(downloader: FileDownloader, fileIndex: number) {
+ let downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
+ let downloadStartPoint = 0 // How far into the individual file progress portion the download progress starts
+ let fileProgress = 0
- downloader.on('waitProgress', (remainingSeconds: number, totalSeconds: number) => {
- downloadStartPoint = this.individualFileProgressPortion / 2
- this.percent = this._allFilesProgress + interpolate(remainingSeconds, totalSeconds, 0, 0, this.individualFileProgressPortion / 2)
- this.updateGUI(downloadHeader, `Waiting for Google rate limit... (${remainingSeconds}s)`, 'good')
- })
+ downloader.on('waitProgress', (remainingSeconds: number, totalSeconds: number) => {
+ downloadStartPoint = this.individualFileProgressPortion / 2
+ this.percent =
+ this._allFilesProgress + interpolate(remainingSeconds, totalSeconds, 0, 0, this.individualFileProgressPortion / 2)
+ this.updateGUI(downloadHeader, `Waiting for Google rate limit... (${remainingSeconds}s)`, 'good')
+ })
- downloader.on('requestSent', () => {
- fileProgress = downloadStartPoint
- this.percent = this._allFilesProgress + fileProgress
- this.updateGUI(downloadHeader, 'Sending request...', 'good')
- })
+ downloader.on('requestSent', () => {
+ fileProgress = downloadStartPoint
+ this.percent = this._allFilesProgress + fileProgress
+ this.updateGUI(downloadHeader, 'Sending request...', 'good')
+ })
- downloader.on('downloadProgress', (bytesDownloaded: number) => {
- downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
- const size = Number(this.files[fileIndex].size)
- fileProgress = interpolate(bytesDownloaded, 0, size, downloadStartPoint, this.individualFileProgressPortion)
- this.percent = this._allFilesProgress + fileProgress
- this.updateGUI(downloadHeader, `Downloading... (${Math.round(1000 * bytesDownloaded / size) / 10}%)`, 'good')
- })
+ downloader.on('downloadProgress', (bytesDownloaded: number) => {
+ downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
+ const size = Number(this.files[fileIndex].size)
+ fileProgress = interpolate(bytesDownloaded, 0, size, downloadStartPoint, this.individualFileProgressPortion)
+ this.percent = this._allFilesProgress + fileProgress
+ this.updateGUI(downloadHeader, `Downloading... (${Math.round(1000 * bytesDownloaded / size) / 10}%)`, 'good')
+ })
- downloader.on('error', this.handleError.bind(this))
+ downloader.on('error', this.handleError.bind(this))
- return new Promise(resolve => {
- downloader.on('complete', () => {
- this._allFilesProgress += this.individualFileProgressPortion
- resolve()
- })
- })
- }
+ return new Promise(resolve => {
+ downloader.on('complete', () => {
+ this._allFilesProgress += this.individualFileProgressPortion
+ resolve()
+ })
+ })
+ }
- /**
- * Defines what happens in response to `FileExtractor` events.
- * @returns a `Promise` that resolves when the extraction finishes.
- */
- private addExtractorEventListeners(extractor: FileExtractor) {
- let archive = ''
+ /**
+ * Defines what happens in response to `FileExtractor` events.
+ * @returns a `Promise` that resolves when the extraction finishes.
+ */
+ private addExtractorEventListeners(extractor: FileExtractor) {
+ let archive = ''
- extractor.on('start', (filename) => {
- archive = filename
- this.updateGUI(`[${archive}]`, 'Extracting...', 'good')
- })
+ extractor.on('start', filename => {
+ archive = filename
+ this.updateGUI(`[${archive}]`, 'Extracting...', 'good')
+ })
- extractor.on('extractProgress', (percent, filecount) => {
- this.percent = interpolate(percent, 0, 100, 80, 95)
- this.updateGUI(`[${archive}] (${filecount} file${filecount == 1 ? '' : 's'} extracted)`, `Extracting... (${percent}%)`, 'good')
- })
+ extractor.on('extractProgress', (percent, filecount) => {
+ this.percent = interpolate(percent, 0, 100, 80, 95)
+ this.updateGUI(`[${archive}] (${filecount} file${filecount == 1 ? '' : 's'} extracted)`, `Extracting... (${percent}%)`, 'good')
+ })
- extractor.on('error', this.handleError.bind(this))
+ extractor.on('error', this.handleError.bind(this))
- return new Promise(resolve => {
- extractor.on('complete', () => {
- this.percent = 95
- resolve()
- })
- })
- }
+ return new Promise(resolve => {
+ extractor.on('complete', () => {
+ this.percent = 95
+ resolve()
+ })
+ })
+ }
- /**
- * Defines what happens in response to `FileTransfer` events.
- * @returns a `Promise` that resolves when the transfer finishes.
- */
- private addTransferEventListeners(transfer: FileTransfer) {
- let destinationFolder: string
+ /**
+ * Defines what happens in response to `FileTransfer` events.
+ * @returns a `Promise` that resolves when the transfer finishes.
+ */
+ private addTransferEventListeners(transfer: FileTransfer) {
+ let destinationFolder: string
- transfer.on('start', (_destinationFolder) => {
- destinationFolder = _destinationFolder
- this.updateGUI('Moving files to library folder...', destinationFolder, 'good', true)
- })
+ transfer.on('start', _destinationFolder => {
+ destinationFolder = _destinationFolder
+ this.updateGUI('Moving files to library folder...', destinationFolder, 'good', true)
+ })
- transfer.on('error', this.handleError.bind(this))
+ transfer.on('error', this.handleError.bind(this))
- return new Promise(resolve => {
- transfer.on('complete', () => {
- this.percent = 100
- this.updateGUI('Download complete.', destinationFolder, 'done', true)
- resolve()
- })
- })
- }
-}
\ No newline at end of file
+ return new Promise(resolve => {
+ transfer.on('complete', () => {
+ this.percent = 100
+ this.updateGUI('Download complete.', destinationFolder, 'done', true)
+ resolve()
+ })
+ })
+ }
+}
diff --git a/src/electron/ipc/download/DownloadHandler.ts b/src/electron/ipc/download/DownloadHandler.ts
index a53b427..a840446 100644
--- a/src/electron/ipc/download/DownloadHandler.ts
+++ b/src/electron/ipc/download/DownloadHandler.ts
@@ -1,86 +1,86 @@
-import { IPCEmitHandler } from '../../shared/IPCHandler'
import { Download } from '../../shared/interfaces/download.interface'
+import { IPCEmitHandler } from '../../shared/IPCHandler'
import { ChartDownload } from './ChartDownload'
import { DownloadQueue } from './DownloadQueue'
class DownloadHandler implements IPCEmitHandler<'download'> {
- event: 'download' = 'download'
+ event = 'download' as const
- downloadQueue: DownloadQueue = new DownloadQueue()
- currentDownload: ChartDownload = undefined
- retryWaiting: ChartDownload[] = []
+ downloadQueue: DownloadQueue = new DownloadQueue()
+ currentDownload: ChartDownload = undefined
+ retryWaiting: ChartDownload[] = []
- handler(data: Download) {
- switch (data.action) {
- case 'add': this.addDownload(data); break
- case 'retry': this.retryDownload(data); break
- case 'cancel': this.cancelDownload(data); break
- }
- }
+ handler(data: Download) {
+ switch (data.action) {
+ case 'add': this.addDownload(data); break
+ case 'retry': this.retryDownload(data); break
+ case 'cancel': this.cancelDownload(data); break
+ }
+ }
- private addDownload(data: Download) {
- const filesHash = data.data.driveData.filesHash // Note: using versionID would cause chart packs to download multiple times
- if (this.currentDownload?.hash == filesHash || this.downloadQueue.isDownloadingLink(filesHash)) {
- return
- }
+ private addDownload(data: Download) {
+ const filesHash = data.data.driveData.filesHash // Note: using versionID would cause chart packs to download multiple times
+ if (this.currentDownload?.hash == filesHash || this.downloadQueue.isDownloadingLink(filesHash)) {
+ return
+ }
- const newDownload = new ChartDownload(data.versionID, data.data)
- this.addDownloadEventListeners(newDownload)
- if (this.currentDownload == undefined) {
- this.currentDownload = newDownload
- newDownload.beginDownload()
- } else {
- this.downloadQueue.push(newDownload)
- }
- }
+ const newDownload = new ChartDownload(data.versionID, data.data)
+ this.addDownloadEventListeners(newDownload)
+ if (this.currentDownload == undefined) {
+ this.currentDownload = newDownload
+ newDownload.beginDownload()
+ } else {
+ this.downloadQueue.push(newDownload)
+ }
+ }
- private retryDownload(data: Download) {
- const index = this.retryWaiting.findIndex(download => download.versionID == data.versionID)
- if (index != -1) {
- const retryDownload = this.retryWaiting.splice(index, 1)[0]
- retryDownload.displayRetrying()
- if (this.currentDownload == undefined) {
- this.currentDownload = retryDownload
- retryDownload.retry()
- } else {
- this.downloadQueue.push(retryDownload)
- }
- }
- }
+ private retryDownload(data: Download) {
+ const index = this.retryWaiting.findIndex(download => download.versionID == data.versionID)
+ if (index != -1) {
+ const retryDownload = this.retryWaiting.splice(index, 1)[0]
+ retryDownload.displayRetrying()
+ if (this.currentDownload == undefined) {
+ this.currentDownload = retryDownload
+ retryDownload.retry()
+ } else {
+ this.downloadQueue.push(retryDownload)
+ }
+ }
+ }
- private cancelDownload(data: Download) {
- if (this.currentDownload?.versionID == data.versionID) {
- this.currentDownload.cancel()
- this.currentDownload = undefined
- this.startNextDownload()
- } else {
- this.downloadQueue.remove(data.versionID)
- }
- }
+ private cancelDownload(data: Download) {
+ if (this.currentDownload?.versionID == data.versionID) {
+ this.currentDownload.cancel()
+ this.currentDownload = undefined
+ this.startNextDownload()
+ } else {
+ this.downloadQueue.remove(data.versionID)
+ }
+ }
- private addDownloadEventListeners(download: ChartDownload) {
- download.on('complete', () => {
- this.currentDownload = undefined
- this.startNextDownload()
- })
+ private addDownloadEventListeners(download: ChartDownload) {
+ download.on('complete', () => {
+ this.currentDownload = undefined
+ this.startNextDownload()
+ })
- download.on('error', () => {
- this.retryWaiting.push(this.currentDownload)
- this.currentDownload = undefined
- this.startNextDownload()
- })
- }
+ download.on('error', () => {
+ this.retryWaiting.push(this.currentDownload)
+ this.currentDownload = undefined
+ this.startNextDownload()
+ })
+ }
- private startNextDownload() {
- if (!this.downloadQueue.isEmpty()) {
- this.currentDownload = this.downloadQueue.shift()
- if (this.currentDownload.hasFailed) {
- this.currentDownload.retry()
- } else {
- this.currentDownload.beginDownload()
- }
- }
- }
+ private startNextDownload() {
+ if (!this.downloadQueue.isEmpty()) {
+ this.currentDownload = this.downloadQueue.shift()
+ if (this.currentDownload.hasFailed) {
+ this.currentDownload.retry()
+ } else {
+ this.currentDownload.beginDownload()
+ }
+ }
+ }
}
-export const downloadHandler = new DownloadHandler()
\ No newline at end of file
+export const downloadHandler = new DownloadHandler()
diff --git a/src/electron/ipc/download/DownloadQueue.ts b/src/electron/ipc/download/DownloadQueue.ts
index fc2479f..55a021e 100644
--- a/src/electron/ipc/download/DownloadQueue.ts
+++ b/src/electron/ipc/download/DownloadQueue.ts
@@ -1,50 +1,51 @@
import Comparators from 'comparators'
-import { ChartDownload } from './ChartDownload'
+
import { emitIPCEvent } from '../../main'
+import { ChartDownload } from './ChartDownload'
export class DownloadQueue {
- private downloadQueue: ChartDownload[] = []
+ private downloadQueue: ChartDownload[] = []
- isDownloadingLink(filesHash: string) {
- return this.downloadQueue.some(download => download.hash == filesHash)
- }
+ isDownloadingLink(filesHash: string) {
+ return this.downloadQueue.some(download => download.hash == filesHash)
+ }
- isEmpty() {
- return this.downloadQueue.length == 0
- }
+ isEmpty() {
+ return this.downloadQueue.length == 0
+ }
- push(chartDownload: ChartDownload) {
- this.downloadQueue.push(chartDownload)
- this.sort()
- }
+ push(chartDownload: ChartDownload) {
+ this.downloadQueue.push(chartDownload)
+ this.sort()
+ }
- shift() {
- return this.downloadQueue.shift()
- }
+ shift() {
+ return this.downloadQueue.shift()
+ }
- get(versionID: number) {
- return this.downloadQueue.find(download => download.versionID == versionID)
- }
+ get(versionID: number) {
+ return this.downloadQueue.find(download => download.versionID == versionID)
+ }
- remove(versionID: number) {
- const index = this.downloadQueue.findIndex(download => download.versionID == versionID)
- if (index != -1) {
- this.downloadQueue[index].cancel()
- this.downloadQueue.splice(index, 1)
- emitIPCEvent('queue-updated', this.downloadQueue.map(download => download.versionID))
- }
- }
+ remove(versionID: number) {
+ const index = this.downloadQueue.findIndex(download => download.versionID == versionID)
+ if (index != -1) {
+ this.downloadQueue[index].cancel()
+ this.downloadQueue.splice(index, 1)
+ emitIPCEvent('queue-updated', this.downloadQueue.map(download => download.versionID))
+ }
+ }
- private sort() {
- let comparator = Comparators.comparing('allFilesProgress', { reversed: true })
+ private sort() {
+ let comparator = Comparators.comparing('allFilesProgress', { reversed: true })
- const prioritizeArchives = true
- if (prioritizeArchives) {
- comparator = comparator.thenComparing('isArchive', { reversed: true })
- }
+ const prioritizeArchives = true
+ if (prioritizeArchives) {
+ comparator = comparator.thenComparing('isArchive', { reversed: true })
+ }
- this.downloadQueue.sort(comparator)
- emitIPCEvent('queue-updated', this.downloadQueue.map(download => download.versionID))
- }
-}
\ No newline at end of file
+ this.downloadQueue.sort(comparator)
+ emitIPCEvent('queue-updated', this.downloadQueue.map(download => download.versionID))
+ }
+}
diff --git a/src/electron/ipc/download/FileDownloader.ts b/src/electron/ipc/download/FileDownloader.ts
index 7d194a9..71323eb 100644
--- a/src/electron/ipc/download/FileDownloader.ts
+++ b/src/electron/ipc/download/FileDownloader.ts
@@ -1,42 +1,44 @@
-import { AnyFunction } from '../../shared/UtilFunctions'
-import { devLog } from '../../shared/ElectronUtilFunctions'
+import Bottleneck from 'bottleneck'
import { createWriteStream, writeFile as _writeFile } from 'fs'
+import { google } from 'googleapis'
import * as needle from 'needle'
+import { join } from 'path'
import { Readable } from 'stream'
+import { inspect, promisify } from 'util'
+
+import { devLog } from '../../shared/ElectronUtilFunctions'
+import { tempPath } from '../../shared/Paths'
+import { AnyFunction } from '../../shared/UtilFunctions'
+import { DownloadError } from './ChartDownload'
// TODO: replace needle with got (for cancel() method) (if before-headers event is possible?)
import { googleTimer } from './GoogleTimer'
-import { DownloadError } from './ChartDownload'
-import { google } from 'googleapis'
-import Bottleneck from 'bottleneck'
-import { inspect, promisify } from 'util'
-import { join } from 'path'
-import { tempPath } from '../../shared/Paths'
+
const drive = google.drive('v3')
const limiter = new Bottleneck({
- minTime: 200 // Wait 200 ms between API requests
+ minTime: 200, // Wait 200 ms between API requests
})
const RETRY_MAX = 2
const writeFile = promisify(_writeFile)
-type EventCallback = {
- 'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
- /** Note: this event can be called multiple times if the connection times out or a large file is downloaded */
- 'requestSent': () => void
- 'downloadProgress': (bytesDownloaded: number) => void
- /** Note: after calling retry, the event lifecycle restarts */
- 'error': (err: DownloadError, retry: () => void) => void
- 'complete': () => void
+interface EventCallback {
+ 'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
+ /** Note: this event can be called multiple times if the connection times out or a large file is downloaded */
+ 'requestSent': () => void
+ 'downloadProgress': (bytesDownloaded: number) => void
+ /** Note: after calling retry, the event lifecycle restarts */
+ 'error': (err: DownloadError, retry: () => void) => void
+ 'complete': () => void
}
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
export type FileDownloader = APIFileDownloader | SlowFileDownloader
const downloadErrors = {
- timeout: (type: string) => { return { header: 'Timeout', body: `The download server could not be reached. (type=${type})` } },
- connectionError: (err: Error) => { return { header: 'Connection Error', body: `${err.name}: ${err.message}` } },
- responseError: (statusCode: string) => { return { header: 'Connection failed', body: `Server returned status code: ${statusCode}` } },
- htmlError: (path: string) => { return { header: 'Download server returned HTML instead of a file.', body: path, isLink: true } },
- linkError: (url: string) => { return { header: 'Invalid link', body: `The download link is not formatted correctly: ${url}` } }
+ timeout: (type: string) => { return { header: 'Timeout', body: `The download server could not be reached. (type=${type})` } },
+ connectionError: (err: Error) => { return { header: 'Connection Error', body: `${err.name}: ${err.message}` } },
+ responseError: (statusCode: string) => { return { header: 'Connection failed', body: `Server returned status code: ${statusCode}` } },
+ htmlError: (path: string) => { return { header: 'Download server returned HTML instead of a file.', body: path, isLink: true } },
+ linkError: (url: string) => { return { header: 'Invalid link', body: `The download link is not formatted correctly: ${url}` } },
}
/**
@@ -48,7 +50,7 @@ const downloadErrors = {
* @param fullPath The full path to where this file should be stored (including the filename).
*/
export function getDownloader(url: string, fullPath: string): FileDownloader {
- return new SlowFileDownloader(url, fullPath)
+ return new SlowFileDownloader(url, fullPath)
}
/**
@@ -56,141 +58,141 @@ export function getDownloader(url: string, fullPath: string): FileDownloader {
* On error, provides the ability to retry.
*/
class APIFileDownloader {
- private readonly URL_REGEX = /uc\?id=([^&]*)&export=download/u
+ private readonly URL_REGEX = /uc\?id=([^&]*)&export=download/u
- private callbacks = {} as Callbacks
- private retryCount: number
- private wasCanceled = false
- private fileID: string
- private downloadStream: Readable
+ private callbacks = {} as Callbacks
+ private retryCount: number
+ private wasCanceled = false
+ private fileID: string
+ private downloadStream: Readable
- /**
- * @param url The download link.
- * @param fullPath The full path to where this file should be stored (including the filename).
- */
- constructor(private url: string, private fullPath: string) {
- // url looks like: "https://drive.google.com/uc?id=1TlxtOZlVgRgX7-1tyW0d5QzXVfL-MC3Q&export=download"
- this.fileID = this.URL_REGEX.exec(url)[1]
- }
+ /**
+ * @param url The download link.
+ * @param fullPath The full path to where this file should be stored (including the filename).
+ */
+ constructor(private url: string, private fullPath: string) {
+ // url looks like: "https://drive.google.com/uc?id=1TlxtOZlVgRgX7-1tyW0d5QzXVfL-MC3Q&export=download"
+ this.fileID = this.URL_REGEX.exec(url)[1]
+ }
- /**
- * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
- */
- on(event: E, callback: EventCallback[E]) {
- this.callbacks[event] = callback
- }
+ /**
+ * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
+ */
+ on(event: E, callback: EventCallback[E]) {
+ this.callbacks[event] = callback
+ }
- /**
- * Download the file after waiting for the google rate limit.
- */
- beginDownload() {
- if (this.fileID == undefined) {
- this.failDownload(downloadErrors.linkError(this.url))
- }
+ /**
+ * Download the file after waiting for the google rate limit.
+ */
+ beginDownload() {
+ if (this.fileID == undefined) {
+ this.failDownload(downloadErrors.linkError(this.url))
+ }
- this.startDownloadStream()
- }
+ this.startDownloadStream()
+ }
- /**
- * Uses the Google Drive API to start a download stream for the file with `this.fileID`.
- */
- private startDownloadStream() {
- limiter.schedule(this.cancelable(async () => {
- this.callbacks.requestSent()
- try {
- this.downloadStream = (await drive.files.get({
- fileId: this.fileID,
- alt: 'media'
- }, {
- responseType: 'stream'
- })).data
+ /**
+ * Uses the Google Drive API to start a download stream for the file with `this.fileID`.
+ */
+ private startDownloadStream() {
+ limiter.schedule(this.cancelable(async () => {
+ this.callbacks.requestSent()
+ try {
+ this.downloadStream = (await drive.files.get({
+ fileId: this.fileID,
+ alt: 'media',
+ }, {
+ responseType: 'stream',
+ })).data
- if (this.wasCanceled) { return }
+ if (this.wasCanceled) { return }
- this.handleDownloadResponse()
- } catch (err) {
- this.retryCount++
- if (this.retryCount <= RETRY_MAX) {
- devLog(`Failed to get file: Retry attempt ${this.retryCount}...`)
- if (this.wasCanceled) { return }
- this.startDownloadStream()
- } else {
- devLog(inspect(err))
- if (err?.code && err?.response?.statusText) {
- this.failDownload(downloadErrors.responseError(`${err.code} (${err.response.statusText})`))
- } else {
- this.failDownload(downloadErrors.responseError(err?.code ?? 'unknown'))
- }
- }
- }
- }))
- }
+ this.handleDownloadResponse()
+ } catch (err) {
+ this.retryCount++
+ if (this.retryCount <= RETRY_MAX) {
+ devLog(`Failed to get file: Retry attempt ${this.retryCount}...`)
+ if (this.wasCanceled) { return }
+ this.startDownloadStream()
+ } else {
+ devLog(inspect(err))
+ if (err?.code && err?.response?.statusText) {
+ this.failDownload(downloadErrors.responseError(`${err.code} (${err.response.statusText})`))
+ } else {
+ this.failDownload(downloadErrors.responseError(err?.code ?? 'unknown'))
+ }
+ }
+ }
+ }))
+ }
- /**
- * Pipes the data from a download response to `this.fullPath`.
- * @param req The download request.
- */
- private handleDownloadResponse() {
- this.callbacks.downloadProgress(0)
- let downloadedSize = 0
- const writeStream = createWriteStream(this.fullPath)
+ /**
+ * Pipes the data from a download response to `this.fullPath`.
+ * @param req The download request.
+ */
+ private handleDownloadResponse() {
+ this.callbacks.downloadProgress(0)
+ let downloadedSize = 0
+ const writeStream = createWriteStream(this.fullPath)
- try {
- this.downloadStream.pipe(writeStream)
- } catch (err) {
- this.failDownload(downloadErrors.connectionError(err))
- }
+ try {
+ this.downloadStream.pipe(writeStream)
+ } catch (err) {
+ this.failDownload(downloadErrors.connectionError(err))
+ }
- this.downloadStream.on('data', this.cancelable((chunk: Buffer) => {
- downloadedSize += chunk.length
- }))
+ this.downloadStream.on('data', this.cancelable((chunk: Buffer) => {
+ downloadedSize += chunk.length
+ }))
- const progressUpdater = setInterval(() => {
- this.callbacks.downloadProgress(downloadedSize)
- }, 100)
+ const progressUpdater = setInterval(() => {
+ this.callbacks.downloadProgress(downloadedSize)
+ }, 100)
- this.downloadStream.on('error', this.cancelable((err: Error) => {
- clearInterval(progressUpdater)
- this.failDownload(downloadErrors.connectionError(err))
- }))
+ this.downloadStream.on('error', this.cancelable((err: Error) => {
+ clearInterval(progressUpdater)
+ this.failDownload(downloadErrors.connectionError(err))
+ }))
- this.downloadStream.on('end', this.cancelable(() => {
- clearInterval(progressUpdater)
- writeStream.end()
- this.downloadStream.destroy()
- this.downloadStream = null
+ this.downloadStream.on('end', this.cancelable(() => {
+ clearInterval(progressUpdater)
+ writeStream.end()
+ this.downloadStream.destroy()
+ this.downloadStream = null
- this.callbacks.complete()
- }))
- }
+ this.callbacks.complete()
+ }))
+ }
- /**
- * Display an error message and provide a function to retry the download.
- */
- private failDownload(error: DownloadError) {
- this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
- }
+ /**
+ * Display an error message and provide a function to retry the download.
+ */
+ private failDownload(error: DownloadError) {
+ this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
+ }
- /**
- * Stop the process of downloading the file. (no more events will be fired after this is called)
- */
- cancelDownload() {
- this.wasCanceled = true
- googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
- if (this.downloadStream) {
- this.downloadStream.destroy()
- }
- }
+ /**
+ * Stop the process of downloading the file. (no more events will be fired after this is called)
+ */
+ cancelDownload() {
+ this.wasCanceled = true
+ googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
+ if (this.downloadStream) {
+ this.downloadStream.destroy()
+ }
+ }
- /**
- * Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
- */
- private cancelable(fn: F) {
- return (...args: Parameters): ReturnType => {
- if (this.wasCanceled) { return }
- return fn(...Array.from(args))
- }
- }
+ /**
+ * Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
+ */
+ private cancelable(fn: F) {
+ return (...args: Parameters): ReturnType => {
+ if (this.wasCanceled) { return }
+ return fn(...Array.from(args))
+ }
+ }
}
/**
@@ -201,166 +203,166 @@ class APIFileDownloader {
*/
class SlowFileDownloader {
- private callbacks = {} as Callbacks
- private retryCount: number
- private wasCanceled = false
- private req: NodeJS.ReadableStream
+ private callbacks = {} as Callbacks
+ private retryCount: number
+ private wasCanceled = false
+ private req: NodeJS.ReadableStream
- /**
- * @param url The download link.
- * @param fullPath The full path to where this file should be stored (including the filename).
- */
- constructor(private url: string, private fullPath: string) { }
+ /**
+ * @param url The download link.
+ * @param fullPath The full path to where this file should be stored (including the filename).
+ */
+ constructor(private url: string, private fullPath: string) { }
- /**
- * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
- */
- on(event: E, callback: EventCallback[E]) {
- this.callbacks[event] = callback
- }
+ /**
+ * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
+ */
+ on(event: E, callback: EventCallback[E]) {
+ this.callbacks[event] = callback
+ }
- /**
- * Download the file after waiting for the google rate limit.
- */
- beginDownload() {
- googleTimer.on('waitProgress', this.cancelable((remainingSeconds, totalSeconds) => {
- this.callbacks.waitProgress(remainingSeconds, totalSeconds)
- }))
+ /**
+ * Download the file after waiting for the google rate limit.
+ */
+ beginDownload() {
+ googleTimer.on('waitProgress', this.cancelable((remainingSeconds, totalSeconds) => {
+ this.callbacks.waitProgress(remainingSeconds, totalSeconds)
+ }))
- googleTimer.on('complete', this.cancelable(() => {
- this.requestDownload()
- }))
- }
+ googleTimer.on('complete', this.cancelable(() => {
+ this.requestDownload()
+ }))
+ }
- /**
- * Sends a request to download the file at `this.url`.
- * @param cookieHeader the "cookie=" header to include this request.
- */
- private requestDownload(cookieHeader?: string) {
- this.callbacks.requestSent()
- this.req = needle.get(this.url, {
- 'follow_max': 10,
- 'open_timeout': 5000,
- 'headers': Object.assign({
- 'Referer': this.url,
- 'Accept': '*/*'
- },
- (cookieHeader ? { 'Cookie': cookieHeader } : undefined)
- )
- })
+ /**
+ * Sends a request to download the file at `this.url`.
+ * @param cookieHeader the "cookie=" header to include this request.
+ */
+ private requestDownload(cookieHeader?: string) {
+ this.callbacks.requestSent()
+ this.req = needle.get(this.url, {
+ 'follow_max': 10,
+ 'open_timeout': 5000,
+ 'headers': Object.assign({
+ 'Referer': this.url,
+ 'Accept': '*/*',
+ },
+ (cookieHeader ? { 'Cookie': cookieHeader } : undefined)
+ ),
+ })
- this.req.on('timeout', this.cancelable((type: string) => {
- this.retryCount++
- if (this.retryCount <= RETRY_MAX) {
- devLog(`TIMEOUT: Retry attempt ${this.retryCount}...`)
- this.requestDownload(cookieHeader)
- } else {
- this.failDownload(downloadErrors.timeout(type))
- }
- }))
+ this.req.on('timeout', this.cancelable((type: string) => {
+ this.retryCount++
+ if (this.retryCount <= RETRY_MAX) {
+ devLog(`TIMEOUT: Retry attempt ${this.retryCount}...`)
+ this.requestDownload(cookieHeader)
+ } else {
+ this.failDownload(downloadErrors.timeout(type))
+ }
+ }))
- this.req.on('err', this.cancelable((err: Error) => {
- this.failDownload(downloadErrors.connectionError(err))
- }))
+ this.req.on('err', this.cancelable((err: Error) => {
+ this.failDownload(downloadErrors.connectionError(err))
+ }))
- this.req.on('header', this.cancelable((statusCode, headers: Headers) => {
- if (statusCode != 200) {
- this.failDownload(downloadErrors.responseError(statusCode))
- return
- }
+ this.req.on('header', this.cancelable((statusCode, headers: Headers) => {
+ if (statusCode != 200) {
+ this.failDownload(downloadErrors.responseError(statusCode))
+ return
+ }
- if (headers['content-type'].startsWith('text/html')) {
- this.handleHTMLResponse(headers['set-cookie'])
- } else {
- this.handleDownloadResponse()
- }
- }))
- }
+ if (headers['content-type'].startsWith('text/html')) {
+ this.handleHTMLResponse(headers['set-cookie'])
+ } else {
+ this.handleDownloadResponse()
+ }
+ }))
+ }
- /**
- * A Google Drive HTML response to a download request usually means this is the "file too large to scan for viruses" warning.
- * This function sends the request that results from clicking "download anyway", or generates an error if it can't be found.
- * @param cookieHeader The "cookie=" header of this request.
- */
- private handleHTMLResponse(cookieHeader: string) {
- let virusScanHTML = ''
- this.req.on('data', this.cancelable(data => virusScanHTML += data))
- this.req.on('done', this.cancelable((err: Error) => {
- if (err) {
- this.failDownload(downloadErrors.connectionError(err))
- } else {
- try {
- const confirmTokenRegex = /confirm=([0-9A-Za-z\-_]+)&/g
- const confirmTokenResults = confirmTokenRegex.exec(virusScanHTML)
- const confirmToken = confirmTokenResults[1]
- const downloadID = this.url.substr(this.url.indexOf('id=') + 'id='.length)
- this.url = `https://drive.google.com/uc?confirm=${confirmToken}&id=${downloadID}`
- const warningCode = /download_warning_([^=]*)=/.exec(cookieHeader)[1]
- const NID = /NID=([^;]*);/.exec(cookieHeader)[1].replace('=', '%')
- const newHeader = `download_warning_${warningCode}=${confirmToken}; NID=${NID}`
- this.requestDownload(newHeader)
- } catch(e) {
- this.saveHTMLError(virusScanHTML).then((path) => {
- this.failDownload(downloadErrors.htmlError(path))
- })
- }
- }
- }))
- }
+ /**
+ * A Google Drive HTML response to a download request usually means this is the "file too large to scan for viruses" warning.
+ * This function sends the request that results from clicking "download anyway", or generates an error if it can't be found.
+ * @param cookieHeader The "cookie=" header of this request.
+ */
+ private handleHTMLResponse(cookieHeader: string) {
+ let virusScanHTML = ''
+ this.req.on('data', this.cancelable(data => virusScanHTML += data))
+ this.req.on('done', this.cancelable((err: Error) => {
+ if (err) {
+ this.failDownload(downloadErrors.connectionError(err))
+ } else {
+ try {
+ const confirmTokenRegex = /confirm=([0-9A-Za-z\-_]+)&/g
+ const confirmTokenResults = confirmTokenRegex.exec(virusScanHTML)
+ const confirmToken = confirmTokenResults[1]
+ const downloadID = this.url.substr(this.url.indexOf('id=') + 'id='.length)
+ this.url = `https://drive.google.com/uc?confirm=${confirmToken}&id=${downloadID}`
+ const warningCode = /download_warning_([^=]*)=/.exec(cookieHeader)[1]
+ const NID = /NID=([^;]*);/.exec(cookieHeader)[1].replace('=', '%')
+ const newHeader = `download_warning_${warningCode}=${confirmToken}; NID=${NID}`
+ this.requestDownload(newHeader)
+ } catch (e) {
+ this.saveHTMLError(virusScanHTML).then(path => {
+ this.failDownload(downloadErrors.htmlError(path))
+ })
+ }
+ }
+ }))
+ }
- /**
- * Pipes the data from a download response to `this.fullPath`.
- * @param req The download request.
- */
- private handleDownloadResponse() {
- this.callbacks.downloadProgress(0)
- let downloadedSize = 0
- this.req.pipe(createWriteStream(this.fullPath))
- this.req.on('data', this.cancelable((data) => {
- downloadedSize += data.length
- this.callbacks.downloadProgress(downloadedSize)
- }))
+ /**
+ * Pipes the data from a download response to `this.fullPath`.
+ * @param req The download request.
+ */
+ private handleDownloadResponse() {
+ this.callbacks.downloadProgress(0)
+ let downloadedSize = 0
+ this.req.pipe(createWriteStream(this.fullPath))
+ this.req.on('data', this.cancelable(data => {
+ downloadedSize += data.length
+ this.callbacks.downloadProgress(downloadedSize)
+ }))
- this.req.on('err', this.cancelable((err: Error) => {
- this.failDownload(downloadErrors.connectionError(err))
- }))
+ this.req.on('err', this.cancelable((err: Error) => {
+ this.failDownload(downloadErrors.connectionError(err))
+ }))
- this.req.on('end', this.cancelable(() => {
- this.callbacks.complete()
- }))
- }
+ this.req.on('end', this.cancelable(() => {
+ this.callbacks.complete()
+ }))
+ }
- private async saveHTMLError(text: string) {
- const errorPath = join(tempPath, 'HTMLError.html')
- await writeFile(errorPath, text)
- return errorPath
- }
+ private async saveHTMLError(text: string) {
+ const errorPath = join(tempPath, 'HTMLError.html')
+ await writeFile(errorPath, text)
+ return errorPath
+ }
- /**
- * Display an error message and provide a function to retry the download.
- */
- private failDownload(error: DownloadError) {
- this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
- }
+ /**
+ * Display an error message and provide a function to retry the download.
+ */
+ private failDownload(error: DownloadError) {
+ this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
+ }
- /**
- * Stop the process of downloading the file. (no more events will be fired after this is called)
- */
- cancelDownload() {
- this.wasCanceled = true
- googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
- if (this.req) {
- // TODO: destroy request
- }
- }
+ /**
+ * Stop the process of downloading the file. (no more events will be fired after this is called)
+ */
+ cancelDownload() {
+ this.wasCanceled = true
+ googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
+ if (this.req) {
+ // TODO: destroy request
+ }
+ }
- /**
- * Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
- */
- private cancelable(fn: F) {
- return (...args: Parameters): ReturnType => {
- if (this.wasCanceled) { return }
- return fn(...Array.from(args))
- }
- }
-}
\ No newline at end of file
+ /**
+ * Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
+ */
+ private cancelable(fn: F) {
+ return (...args: Parameters): ReturnType => {
+ if (this.wasCanceled) { return }
+ return fn(...Array.from(args))
+ }
+ }
+}
diff --git a/src/electron/ipc/download/FileExtractor.ts b/src/electron/ipc/download/FileExtractor.ts
index b7a130d..1235ca1 100644
--- a/src/electron/ipc/download/FileExtractor.ts
+++ b/src/electron/ipc/download/FileExtractor.ts
@@ -1,163 +1,164 @@
-import { readdir, unlink, mkdir as _mkdir } from 'fs'
-import { promisify } from 'util'
-import { join, extname } from 'path'
-import { AnyFunction } from '../../shared/UtilFunctions'
-import { devLog } from '../../shared/ElectronUtilFunctions'
-import * as node7z from 'node-7z'
import * as zipBin from '7zip-bin'
+import { mkdir as _mkdir, readdir, unlink } from 'fs'
+import * as node7z from 'node-7z'
import * as unrarjs from 'node-unrar-js' // TODO find better rar library that has async extraction
import { FailReason } from 'node-unrar-js/dist/js/extractor'
+import { extname, join } from 'path'
+import { promisify } from 'util'
+
+import { devLog } from '../../shared/ElectronUtilFunctions'
+import { AnyFunction } from '../../shared/UtilFunctions'
import { DownloadError } from './ChartDownload'
const mkdir = promisify(_mkdir)
-type EventCallback = {
- 'start': (filename: string) => void
- 'extractProgress': (percent: number, fileCount: number) => void
- 'error': (err: DownloadError, retry: () => void | Promise) => void
- 'complete': () => void
+interface EventCallback {
+ 'start': (filename: string) => void
+ 'extractProgress': (percent: number, fileCount: number) => void
+ 'error': (err: DownloadError, retry: () => void | Promise) => void
+ 'complete': () => void
}
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
const extractErrors = {
- readError: (err: NodeJS.ErrnoException) => { return { header: `Failed to read file (${err.code})`, body: `${err.name}: ${err.message}` } },
- emptyError: () => { return { header: 'Failed to extract archive', body: 'File archive was downloaded but could not be found' } },
- rarmkdirError: (err: NodeJS.ErrnoException, sourceFile: string) => {
- return { header: `Extracting archive failed. (${err.code})`, body: `${err.name}: ${err.message} (${sourceFile})`}
- },
- rarextractError: (result: { reason: FailReason; msg: string }, sourceFile: string) => {
- return { header: `Extracting archive failed: ${result.reason}`, body: `${result.msg} (${sourceFile})`}
- }
+ readError: (err: NodeJS.ErrnoException) => { return { header: `Failed to read file (${err.code})`, body: `${err.name}: ${err.message}` } },
+ emptyError: () => { return { header: 'Failed to extract archive', body: 'File archive was downloaded but could not be found' } },
+ rarmkdirError: (err: NodeJS.ErrnoException, sourceFile: string) => {
+ return { header: `Extracting archive failed. (${err.code})`, body: `${err.name}: ${err.message} (${sourceFile})` }
+ },
+ rarextractError: (result: { reason: FailReason; msg: string }, sourceFile: string) => {
+ return { header: `Extracting archive failed: ${result.reason}`, body: `${result.msg} (${sourceFile})` }
+ },
}
export class FileExtractor {
- private callbacks = {} as Callbacks
- private wasCanceled = false
- constructor(private sourceFolder: string) { }
+ private callbacks = {} as Callbacks
+ private wasCanceled = false
+ constructor(private sourceFolder: string) { }
- /**
- * Calls `callback` when `event` fires. (no events will be fired after `this.cancelExtract()` is called)
- */
- on(event: E, callback: EventCallback[E]) {
- this.callbacks[event] = callback
- }
+ /**
+ * Calls `callback` when `event` fires. (no events will be fired after `this.cancelExtract()` is called)
+ */
+ on(event: E, callback: EventCallback[E]) {
+ this.callbacks[event] = callback
+ }
- /**
- * Extract the chart from `this.sourceFolder`. (assumes there is exactly one archive file in that folder)
- */
- beginExtract() {
- setTimeout(this.cancelable(() => {
- readdir(this.sourceFolder, (err, files) => {
- if (err) {
- this.callbacks.error(extractErrors.readError(err), () => this.beginExtract())
- } else if (files.length == 0) {
- this.callbacks.error(extractErrors.emptyError(), () => this.beginExtract())
- } else {
- this.callbacks.start(files[0])
- this.extract(join(this.sourceFolder, files[0]), extname(files[0]) == '.rar')
- }
- })
- }), 100) // Wait for filesystem to process downloaded file
- }
+ /**
+ * Extract the chart from `this.sourceFolder`. (assumes there is exactly one archive file in that folder)
+ */
+ beginExtract() {
+ setTimeout(this.cancelable(() => {
+ readdir(this.sourceFolder, (err, files) => {
+ if (err) {
+ this.callbacks.error(extractErrors.readError(err), () => this.beginExtract())
+ } else if (files.length == 0) {
+ this.callbacks.error(extractErrors.emptyError(), () => this.beginExtract())
+ } else {
+ this.callbacks.start(files[0])
+ this.extract(join(this.sourceFolder, files[0]), extname(files[0]) == '.rar')
+ }
+ })
+ }), 100) // Wait for filesystem to process downloaded file
+ }
- /**
- * Extracts the file at `fullPath` to `this.sourceFolder`.
- */
- private async extract(fullPath: string, useRarExtractor: boolean) {
- if (useRarExtractor) {
- await this.extractRar(fullPath) // Use node-unrar-js to extract the archive
- } else {
- this.extract7z(fullPath) // Use node-7z to extract the archive
- }
- }
+ /**
+ * Extracts the file at `fullPath` to `this.sourceFolder`.
+ */
+ private async extract(fullPath: string, useRarExtractor: boolean) {
+ if (useRarExtractor) {
+ await this.extractRar(fullPath) // Use node-unrar-js to extract the archive
+ } else {
+ this.extract7z(fullPath) // Use node-7z to extract the archive
+ }
+ }
- /**
- * Extracts a .rar archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
- * @throws an `ExtractError` if this fails.
- */
- private async extractRar(fullPath: string) {
- const extractor = unrarjs.createExtractorFromFile(fullPath, this.sourceFolder)
+ /**
+ * Extracts a .rar archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
+ * @throws an `ExtractError` if this fails.
+ */
+ private async extractRar(fullPath: string) {
+ const extractor = unrarjs.createExtractorFromFile(fullPath, this.sourceFolder)
- const fileList = extractor.getFileList()
+ const fileList = extractor.getFileList()
- if (fileList[0].state != 'FAIL') {
+ if (fileList[0].state != 'FAIL') {
- // Create directories for nested archives (because unrarjs didn't feel like handling that automatically)
- const headers = fileList[1].fileHeaders
- for (const header of headers) {
- if (header.flags.directory) {
- try {
- await mkdir(join(this.sourceFolder, header.name), { recursive: true })
- } catch (err) {
- this.callbacks.error(extractErrors.rarmkdirError(err, fullPath), () => this.extract(fullPath, extname(fullPath) == '.rar'))
- return
- }
- }
- }
- }
+ // Create directories for nested archives (because unrarjs didn't feel like handling that automatically)
+ const headers = fileList[1].fileHeaders
+ for (const header of headers) {
+ if (header.flags.directory) {
+ try {
+ await mkdir(join(this.sourceFolder, header.name), { recursive: true })
+ } catch (err) {
+ this.callbacks.error(extractErrors.rarmkdirError(err, fullPath), () => this.extract(fullPath, extname(fullPath) == '.rar'))
+ return
+ }
+ }
+ }
+ }
- const extractResult = extractor.extractAll()
+ const extractResult = extractor.extractAll()
- if (extractResult[0].state == 'FAIL') {
- this.callbacks.error(extractErrors.rarextractError(extractResult[0], fullPath), () => this.extract(fullPath, extname(fullPath) == '.rar'))
- } else {
- this.deleteArchive(fullPath)
- }
- }
+ if (extractResult[0].state == 'FAIL') {
+ this.callbacks.error(extractErrors.rarextractError(extractResult[0], fullPath), () => this.extract(fullPath, extname(fullPath) == '.rar'))
+ } else {
+ this.deleteArchive(fullPath)
+ }
+ }
- /**
- * Extracts a .zip or .7z archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
- */
- private extract7z(fullPath: string) {
- const zipBinPath = zipBin.path7za.replace('app.asar', 'app.asar.unpacked') // I love electron-builder packaging :)
- const stream = node7z.extractFull(fullPath, this.sourceFolder, { $progress: true, $bin: zipBinPath })
+ /**
+ * Extracts a .zip or .7z archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
+ */
+ private extract7z(fullPath: string) {
+ const zipBinPath = zipBin.path7za.replace('app.asar', 'app.asar.unpacked') // I love electron-builder packaging :)
+ const stream = node7z.extractFull(fullPath, this.sourceFolder, { $progress: true, $bin: zipBinPath })
- stream.on('progress', this.cancelable((progress: { percent: number; fileCount: number }) => {
- this.callbacks.extractProgress(progress.percent, isNaN(progress.fileCount) ? 0 : progress.fileCount)
- }))
+ stream.on('progress', this.cancelable((progress: { percent: number; fileCount: number }) => {
+ this.callbacks.extractProgress(progress.percent, isNaN(progress.fileCount) ? 0 : progress.fileCount)
+ }))
- let extractErrorOccured = false
- stream.on('error', this.cancelable(() => {
- extractErrorOccured = true
- devLog(`Failed to extract [${fullPath}]; retrying with .rar extractor...`)
- this.extract(fullPath, true)
- }))
+ let extractErrorOccured = false
+ stream.on('error', this.cancelable(() => {
+ extractErrorOccured = true
+ devLog(`Failed to extract [${fullPath}]; retrying with .rar extractor...`)
+ this.extract(fullPath, true)
+ }))
- stream.on('end', this.cancelable(() => {
- if (!extractErrorOccured) {
- this.deleteArchive(fullPath)
- }
- }))
- }
+ stream.on('end', this.cancelable(() => {
+ if (!extractErrorOccured) {
+ this.deleteArchive(fullPath)
+ }
+ }))
+ }
- /**
- * Tries to delete the archive at `fullPath`.
- */
- private deleteArchive(fullPath: string) {
- unlink(fullPath, this.cancelable((err) => {
- if (err && err.code != 'ENOENT') {
- devLog(`Warning: failed to delete archive at [${fullPath}]`)
- }
+ /**
+ * Tries to delete the archive at `fullPath`.
+ */
+ private deleteArchive(fullPath: string) {
+ unlink(fullPath, this.cancelable(err => {
+ if (err && err.code != 'ENOENT') {
+ devLog(`Warning: failed to delete archive at [${fullPath}]`)
+ }
- this.callbacks.complete()
- }))
- }
+ this.callbacks.complete()
+ }))
+ }
- /**
- * Stop the process of extracting the file. (no more events will be fired after this is called)
- */
- cancelExtract() {
- this.wasCanceled = true
- }
+ /**
+ * Stop the process of extracting the file. (no more events will be fired after this is called)
+ */
+ cancelExtract() {
+ this.wasCanceled = true
+ }
- /**
- * Wraps a function that is able to be prevented if `this.cancelExtract()` was called.
- */
- private cancelable(fn: F) {
- return (...args: Parameters): ReturnType => {
- if (this.wasCanceled) { return }
- return fn(...Array.from(args))
- }
- }
-}
\ No newline at end of file
+ /**
+ * Wraps a function that is able to be prevented if `this.cancelExtract()` was called.
+ */
+ private cancelable(fn: F) {
+ return (...args: Parameters): ReturnType => {
+ if (this.wasCanceled) { return }
+ return fn(...Array.from(args))
+ }
+ }
+}
diff --git a/src/electron/ipc/download/FileTransfer.ts b/src/electron/ipc/download/FileTransfer.ts
index 113dcf1..8953c7e 100644
--- a/src/electron/ipc/download/FileTransfer.ts
+++ b/src/electron/ipc/download/FileTransfer.ts
@@ -1,108 +1,109 @@
import { Dirent, readdir as _readdir } from 'fs'
-import { promisify } from 'util'
-import { getSettings } from '../SettingsHandler.ipc'
import * as mv from 'mv'
import { join } from 'path'
import { rimraf } from 'rimraf'
+import { promisify } from 'util'
+
+import { getSettings } from '../SettingsHandler.ipc'
import { DownloadError } from './ChartDownload'
const readdir = promisify(_readdir)
-type EventCallback = {
- 'start': (destinationFolder: string) => void
- 'error': (err: DownloadError, retry: () => void | Promise) => void
- 'complete': () => void
+interface EventCallback {
+ 'start': (destinationFolder: string) => void
+ 'error': (err: DownloadError, retry: () => void | Promise) => void
+ 'complete': () => void
}
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
const transferErrors = {
- readError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to read file.'),
- deleteError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete file.'),
- rimrafError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete folder.'),
- mvError: (err: NodeJS.ErrnoException) => fsError(err, `Failed to move folder to library.${err.code == 'EPERM' ? ' (does the chart already exist?)' : ''}`)
+ readError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to read file.'),
+ deleteError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete file.'),
+ rimrafError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete folder.'),
+ mvError: (err: NodeJS.ErrnoException) => fsError(err, `Failed to move folder to library.${err.code == 'EPERM' ? ' (does the chart already exist?)' : ''}`),
}
function fsError(err: NodeJS.ErrnoException, description: string) {
- return { header: description, body: `${err.name}: ${err.message}` }
+ return { header: description, body: `${err.name}: ${err.message}` }
}
export class FileTransfer {
- private callbacks = {} as Callbacks
- private wasCanceled = false
- private destinationFolder: string
- private nestedSourceFolder: string // The top-level folder that is copied to the library folder
- constructor(private sourceFolder: string, destinationFolderName: string) {
- this.destinationFolder = join(getSettings().libraryPath, destinationFolderName)
- this.nestedSourceFolder = sourceFolder
- }
+ private callbacks = {} as Callbacks
+ private wasCanceled = false
+ private destinationFolder: string
+ private nestedSourceFolder: string // The top-level folder that is copied to the library folder
+ constructor(private sourceFolder: string, destinationFolderName: string) {
+ this.destinationFolder = join(getSettings().libraryPath, destinationFolderName)
+ this.nestedSourceFolder = sourceFolder
+ }
- /**
- * Calls `callback` when `event` fires. (no events will be fired after `this.cancelTransfer()` is called)
- */
- on(event: E, callback: EventCallback[E]) {
- this.callbacks[event] = callback
- }
+ /**
+ * Calls `callback` when `event` fires. (no events will be fired after `this.cancelTransfer()` is called)
+ */
+ on(event: E, callback: EventCallback[E]) {
+ this.callbacks[event] = callback
+ }
- async beginTransfer() {
- this.callbacks.start(this.destinationFolder)
- await this.cleanFolder()
- if (this.wasCanceled) { return }
- this.moveFolder()
- }
+ async beginTransfer() {
+ this.callbacks.start(this.destinationFolder)
+ await this.cleanFolder()
+ if (this.wasCanceled) { return }
+ this.moveFolder()
+ }
- /**
- * Fixes common problems with the download chart folder.
- */
- private async cleanFolder() {
- let files: Dirent[]
- try {
- files = await readdir(this.nestedSourceFolder, { withFileTypes: true })
- } catch (err) {
- this.callbacks.error(transferErrors.readError(err), () => this.cleanFolder())
- return
- }
+ /**
+ * Fixes common problems with the download chart folder.
+ */
+ private async cleanFolder() {
+ let files: Dirent[]
+ try {
+ files = await readdir(this.nestedSourceFolder, { withFileTypes: true })
+ } catch (err) {
+ this.callbacks.error(transferErrors.readError(err), () => this.cleanFolder())
+ return
+ }
- // Remove nested folders
- if (files.length == 1 && !files[0].isFile()) {
- this.nestedSourceFolder = join(this.nestedSourceFolder, files[0].name)
- await this.cleanFolder()
- return
- }
+ // Remove nested folders
+ if (files.length == 1 && !files[0].isFile()) {
+ this.nestedSourceFolder = join(this.nestedSourceFolder, files[0].name)
+ await this.cleanFolder()
+ return
+ }
- // Delete '__MACOSX' folder
- for (const file of files) {
- if (!file.isFile() && file.name == '__MACOSX') {
- try {
- await rimraf(join(this.nestedSourceFolder, file.name))
- } catch (err) {
- this.callbacks.error(transferErrors.rimrafError(err), () => this.cleanFolder())
- return
- }
- } else {
- // TODO: handle other common problems, like chart/audio files not named correctly
- }
- }
- }
+ // Delete '__MACOSX' folder
+ for (const file of files) {
+ if (!file.isFile() && file.name == '__MACOSX') {
+ try {
+ await rimraf(join(this.nestedSourceFolder, file.name))
+ } catch (err) {
+ this.callbacks.error(transferErrors.rimrafError(err), () => this.cleanFolder())
+ return
+ }
+ } else {
+ // TODO: handle other common problems, like chart/audio files not named correctly
+ }
+ }
+ }
- /**
- * Moves the downloaded chart to the library path.
- */
- private moveFolder() {
- mv(this.nestedSourceFolder, this.destinationFolder, { mkdirp: true }, (err) => {
- if (err) {
- this.callbacks.error(transferErrors.mvError(err), () => this.moveFolder())
- } else {
- rimraf(this.sourceFolder) // Delete temp folder
- this.callbacks.complete()
- }
- })
- }
+ /**
+ * Moves the downloaded chart to the library path.
+ */
+ private moveFolder() {
+ mv(this.nestedSourceFolder, this.destinationFolder, { mkdirp: true }, err => {
+ if (err) {
+ this.callbacks.error(transferErrors.mvError(err), () => this.moveFolder())
+ } else {
+ rimraf(this.sourceFolder) // Delete temp folder
+ this.callbacks.complete()
+ }
+ })
+ }
- /**
- * Stop the process of transfering the file. (no more events will be fired after this is called)
- */
- cancelTransfer() {
- this.wasCanceled = true
- }
-}
\ No newline at end of file
+ /**
+ * Stop the process of transfering the file. (no more events will be fired after this is called)
+ */
+ cancelTransfer() {
+ this.wasCanceled = true
+ }
+}
diff --git a/src/electron/ipc/download/FilesystemChecker.ts b/src/electron/ipc/download/FilesystemChecker.ts
index 59d1693..6eba562 100644
--- a/src/electron/ipc/download/FilesystemChecker.ts
+++ b/src/electron/ipc/download/FilesystemChecker.ts
@@ -1,121 +1,122 @@
-import { DownloadError } from './ChartDownload'
-import { tempPath } from '../../shared/Paths'
-import { AnyFunction } from '../../shared/UtilFunctions'
-import { devLog } from '../../shared/ElectronUtilFunctions'
import { randomBytes as _randomBytes } from 'crypto'
-import { mkdir, access, constants } from 'fs'
+import { access, constants, mkdir } from 'fs'
import { join } from 'path'
import { promisify } from 'util'
+
+import { devLog } from '../../shared/ElectronUtilFunctions'
+import { tempPath } from '../../shared/Paths'
+import { AnyFunction } from '../../shared/UtilFunctions'
import { getSettings } from '../SettingsHandler.ipc'
+import { DownloadError } from './ChartDownload'
const randomBytes = promisify(_randomBytes)
-type EventCallback = {
- 'start': () => void
- 'error': (err: DownloadError, retry: () => void | Promise) => void
- 'complete': (tempPath: string) => void
+interface EventCallback {
+ 'start': () => void
+ 'error': (err: DownloadError, retry: () => void | Promise) => void
+ 'complete': (tempPath: string) => void
}
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
const filesystemErrors = {
- libraryFolder: () => { return { header: 'Library folder not specified', body: 'Please go to the settings to set your library folder.' } },
- libraryAccess: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to access library folder.'),
- destinationFolderExists: (destinationPath: string) => {
- return { header: 'This chart already exists in your library folder.', body: destinationPath, isLink: true }
- },
- mkdirError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to create temporary folder.')
+ libraryFolder: () => { return { header: 'Library folder not specified', body: 'Please go to the settings to set your library folder.' } },
+ libraryAccess: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to access library folder.'),
+ destinationFolderExists: (destinationPath: string) => {
+ return { header: 'This chart already exists in your library folder.', body: destinationPath, isLink: true }
+ },
+ mkdirError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to create temporary folder.'),
}
function fsError(err: NodeJS.ErrnoException, description: string) {
- return { header: description, body: `${err.name}: ${err.message}` }
+ return { header: description, body: `${err.name}: ${err.message}` }
}
export class FilesystemChecker {
- private callbacks = {} as Callbacks
- private wasCanceled = false
- constructor(private destinationFolderName: string) { }
+ private callbacks = {} as Callbacks
+ private wasCanceled = false
+ constructor(private destinationFolderName: string) { }
- /**
- * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
- */
- on(event: E, callback: EventCallback[E]) {
- this.callbacks[event] = callback
- }
+ /**
+ * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
+ */
+ on(event: E, callback: EventCallback[E]) {
+ this.callbacks[event] = callback
+ }
- /**
- * Check that the filesystem is set up for the download.
- */
- beginCheck() {
- this.callbacks.start()
- this.checkLibraryFolder()
- }
+ /**
+ * Check that the filesystem is set up for the download.
+ */
+ beginCheck() {
+ this.callbacks.start()
+ this.checkLibraryFolder()
+ }
- /**
- * Verifies that the user has specified a library folder.
- */
- private checkLibraryFolder() {
- if (getSettings().libraryPath == undefined) {
- this.callbacks.error(filesystemErrors.libraryFolder(), () => this.beginCheck())
- } else {
- access(getSettings().libraryPath, constants.W_OK, this.cancelable((err) => {
- if (err) {
- this.callbacks.error(filesystemErrors.libraryAccess(err), () => this.beginCheck())
- } else {
- this.checkDestinationFolder()
- }
- }))
- }
- }
+ /**
+ * Verifies that the user has specified a library folder.
+ */
+ private checkLibraryFolder() {
+ if (getSettings().libraryPath == undefined) {
+ this.callbacks.error(filesystemErrors.libraryFolder(), () => this.beginCheck())
+ } else {
+ access(getSettings().libraryPath, constants.W_OK, this.cancelable(err => {
+ if (err) {
+ this.callbacks.error(filesystemErrors.libraryAccess(err), () => this.beginCheck())
+ } else {
+ this.checkDestinationFolder()
+ }
+ }))
+ }
+ }
- /**
- * Checks that the destination folder doesn't already exist.
- */
- private checkDestinationFolder() {
- const destinationPath = join(getSettings().libraryPath, this.destinationFolderName)
- access(destinationPath, constants.F_OK, this.cancelable((err) => {
- if (err) { // File does not exist
- this.createDownloadFolder()
- } else {
- this.callbacks.error(filesystemErrors.destinationFolderExists(destinationPath), () => this.beginCheck())
- }
- }))
- }
+ /**
+ * Checks that the destination folder doesn't already exist.
+ */
+ private checkDestinationFolder() {
+ const destinationPath = join(getSettings().libraryPath, this.destinationFolderName)
+ access(destinationPath, constants.F_OK, this.cancelable(err => {
+ if (err) { // File does not exist
+ this.createDownloadFolder()
+ } else {
+ this.callbacks.error(filesystemErrors.destinationFolderExists(destinationPath), () => this.beginCheck())
+ }
+ }))
+ }
- /**
- * Attempts to create a unique folder in Bridge's data paths.
- */
- private async createDownloadFolder(retryCount = 0) {
- const tempChartPath = join(tempPath, `chart_${(await randomBytes(5)).toString('hex')}`)
+ /**
+ * Attempts to create a unique folder in Bridge's data paths.
+ */
+ private async createDownloadFolder(retryCount = 0) {
+ const tempChartPath = join(tempPath, `chart_${(await randomBytes(5)).toString('hex')}`)
- mkdir(tempChartPath, this.cancelable((err) => {
- if (err) {
- if (retryCount < 5) {
- devLog(`Error creating folder [${tempChartPath}], retrying with a different folder...`)
- this.createDownloadFolder(retryCount + 1)
- } else {
- this.callbacks.error(filesystemErrors.mkdirError(err), () => this.createDownloadFolder())
- }
- } else {
- this.callbacks.complete(tempChartPath)
- }
- }))
- }
+ mkdir(tempChartPath, this.cancelable(err => {
+ if (err) {
+ if (retryCount < 5) {
+ devLog(`Error creating folder [${tempChartPath}], retrying with a different folder...`)
+ this.createDownloadFolder(retryCount + 1)
+ } else {
+ this.callbacks.error(filesystemErrors.mkdirError(err), () => this.createDownloadFolder())
+ }
+ } else {
+ this.callbacks.complete(tempChartPath)
+ }
+ }))
+ }
- /**
- * Stop the process of checking the filesystem permissions. (no more events will be fired after this is called)
- */
- cancelCheck() {
- this.wasCanceled = true
- }
+ /**
+ * Stop the process of checking the filesystem permissions. (no more events will be fired after this is called)
+ */
+ cancelCheck() {
+ this.wasCanceled = true
+ }
- /**
- * Wraps a function that is able to be prevented if `this.cancelCheck()` was called.
- */
- private cancelable(fn: F) {
- return (...args: Parameters): ReturnType => {
- if (this.wasCanceled) { return }
- return fn(...Array.from(args))
- }
- }
-}
\ No newline at end of file
+ /**
+ * Wraps a function that is able to be prevented if `this.cancelCheck()` was called.
+ */
+ private cancelable(fn: F) {
+ return (...args: Parameters): ReturnType => {
+ if (this.wasCanceled) { return }
+ return fn(...Array.from(args))
+ }
+ }
+}
diff --git a/src/electron/ipc/download/GoogleTimer.ts b/src/electron/ipc/download/GoogleTimer.ts
index 1ef6421..94add51 100644
--- a/src/electron/ipc/download/GoogleTimer.ts
+++ b/src/electron/ipc/download/GoogleTimer.ts
@@ -1,72 +1,72 @@
import { getSettings } from '../SettingsHandler.ipc'
-type EventCallback = {
- 'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
- 'complete': () => void
+interface EventCallback {
+ 'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
+ 'complete': () => void
}
type Callbacks = { [E in keyof EventCallback]?: EventCallback[E] }
class GoogleTimer {
- private rateLimitCounter = Infinity
- private callbacks: Callbacks = {}
+ private rateLimitCounter = Infinity
+ private callbacks: Callbacks = {}
- /**
- * Initializes the timer to call the callbacks if they are defined.
- */
- constructor() {
- setInterval(() => {
- this.rateLimitCounter++
- this.updateCallbacks()
- }, 1000)
- }
+ /**
+ * Initializes the timer to call the callbacks if they are defined.
+ */
+ constructor() {
+ setInterval(() => {
+ this.rateLimitCounter++
+ this.updateCallbacks()
+ }, 1000)
+ }
- /**
- * Calls `callback` when `event` fires. (no events will be fired after `this.cancelTimer()` is called)
- */
- on(event: E, callback: EventCallback[E]) {
- this.callbacks[event] = callback
- this.updateCallbacks() // Fire events immediately after the listeners have been added
- }
+ /**
+ * Calls `callback` when `event` fires. (no events will be fired after `this.cancelTimer()` is called)
+ */
+ on(event: E, callback: EventCallback[E]) {
+ this.callbacks[event] = callback
+ this.updateCallbacks() // Fire events immediately after the listeners have been added
+ }
- /**
- * Check the state of the callbacks and call them if necessary.
- */
- private updateCallbacks() {
- if (this.hasTimerEnded() && this.callbacks.complete != undefined) {
- this.endTimer()
- } else if (this.callbacks.waitProgress != undefined) {
- const delay = getSettings().rateLimitDelay
- this.callbacks.waitProgress(delay - this.rateLimitCounter, delay)
- }
- }
+ /**
+ * Check the state of the callbacks and call them if necessary.
+ */
+ private updateCallbacks() {
+ if (this.hasTimerEnded() && this.callbacks.complete != undefined) {
+ this.endTimer()
+ } else if (this.callbacks.waitProgress != undefined) {
+ const delay = getSettings().rateLimitDelay
+ this.callbacks.waitProgress(delay - this.rateLimitCounter, delay)
+ }
+ }
- /**
- * Prevents the callbacks from activating when the timer ends.
- */
- cancelTimer() {
- this.callbacks = {}
- }
+ /**
+ * Prevents the callbacks from activating when the timer ends.
+ */
+ cancelTimer() {
+ this.callbacks = {}
+ }
- /**
- * Checks if enough time has elapsed since the last timer activation.
- */
- private hasTimerEnded() {
- return this.rateLimitCounter > getSettings().rateLimitDelay
- }
+ /**
+ * Checks if enough time has elapsed since the last timer activation.
+ */
+ private hasTimerEnded() {
+ return this.rateLimitCounter > getSettings().rateLimitDelay
+ }
- /**
- * Activates the completion callback and resets the timer.
- */
- private endTimer() {
- this.rateLimitCounter = 0
- const completeCallback = this.callbacks.complete
- this.callbacks = {}
- completeCallback()
- }
+ /**
+ * Activates the completion callback and resets the timer.
+ */
+ private endTimer() {
+ this.rateLimitCounter = 0
+ const completeCallback = this.callbacks.complete
+ this.callbacks = {}
+ completeCallback()
+ }
}
/**
* Important: this instance cannot be used by more than one file download at a time.
*/
-export const googleTimer = new GoogleTimer()
\ No newline at end of file
+export const googleTimer = new GoogleTimer()
diff --git a/src/electron/main.ts b/src/electron/main.ts
index 4dd63b0..e7b00d8 100644
--- a/src/electron/main.ts
+++ b/src/electron/main.ts
@@ -1,16 +1,16 @@
import { app, BrowserWindow, ipcMain } from 'electron'
-import { updateChecker } from './ipc/UpdateHandler.ipc'
import * as windowStateKeeper from 'electron-window-state'
import * as path from 'path'
import * as url from 'url'
-require('electron-unhandled')({ showDialog: true })
-
-// IPC Handlers
-import { getIPCInvokeHandlers, getIPCEmitHandlers, IPCEmitEvents } from './shared/IPCHandler'
import { getSettingsHandler } from './ipc/SettingsHandler.ipc'
+import { updateChecker } from './ipc/UpdateHandler.ipc'
+// IPC Handlers
+import { getIPCEmitHandlers, getIPCInvokeHandlers, IPCEmitEvents } from './shared/IPCHandler'
import { dataPath } from './shared/Paths'
+require('electron-unhandled')({ showDialog: true })
+
export let mainWindow: BrowserWindow
const args = process.argv.slice(1)
const isDevBuild = args.some(val => val == '--dev')
@@ -21,13 +21,13 @@ remote.initialize()
restrictToSingleInstance()
handleOSXWindowClosed()
app.on('ready', () => {
- // Load settings from file before the window is created
- getSettingsHandler.initSettings().then(() => {
- createBridgeWindow()
- if (!isDevBuild) {
- updateChecker.checkForUpdates()
- }
- })
+ // Load settings from file before the window is created
+ getSettingsHandler.initSettings().then(() => {
+ createBridgeWindow()
+ if (!isDevBuild) {
+ updateChecker.checkForUpdates()
+ }
+ })
})
/**
@@ -35,14 +35,14 @@ app.on('ready', () => {
* If this is attempted, restore the open window instead.
*/
function restrictToSingleInstance() {
- const isFirstBridgeInstance = app.requestSingleInstanceLock()
- if (!isFirstBridgeInstance) app.quit()
- app.on('second-instance', () => {
- if (mainWindow != undefined) {
- if (mainWindow.isMinimized()) mainWindow.restore()
- mainWindow.focus()
- }
- })
+ const isFirstBridgeInstance = app.requestSingleInstanceLock()
+ if (!isFirstBridgeInstance) app.quit()
+ app.on('second-instance', () => {
+ if (mainWindow != undefined) {
+ if (mainWindow.isMinimized()) mainWindow.restore()
+ mainWindow.focus()
+ }
+ })
}
/**
@@ -50,17 +50,17 @@ function restrictToSingleInstance() {
* minimize when closed and maximize when opened.
*/
function handleOSXWindowClosed() {
- app.on('window-all-closed', () => {
- if (process.platform != 'darwin') {
- app.quit()
- }
- })
+ app.on('window-all-closed', () => {
+ if (process.platform != 'darwin') {
+ app.quit()
+ }
+ })
- app.on('activate', () => {
- if (mainWindow == undefined) {
- createBridgeWindow()
- }
- })
+ app.on('activate', () => {
+ if (mainWindow == undefined) {
+ createBridgeWindow()
+ }
+ })
}
/**
@@ -68,81 +68,81 @@ function handleOSXWindowClosed() {
*/
function createBridgeWindow() {
- // Load window size and maximized/restored state from previous session
- const windowState = windowStateKeeper({
- defaultWidth: 1000,
- defaultHeight: 800,
- path: dataPath
- })
+ // Load window size and maximized/restored state from previous session
+ const windowState = windowStateKeeper({
+ defaultWidth: 1000,
+ defaultHeight: 800,
+ path: dataPath,
+ })
- // Create the browser window
- mainWindow = createBrowserWindow(windowState)
+ // Create the browser window
+ mainWindow = createBrowserWindow(windowState)
- // Store window size and maximized/restored state for next session
- windowState.manage(mainWindow)
+ // Store window size and maximized/restored state for next session
+ windowState.manage(mainWindow)
- // Don't use a system menu
- mainWindow.setMenu(null)
+ // Don't use a system menu
+ mainWindow.setMenu(null)
- // IPC handlers
- getIPCInvokeHandlers().map(handler => ipcMain.handle(handler.event, (_event, ...args) => handler.handler(args[0])))
- getIPCEmitHandlers().map(handler => ipcMain.on(handler.event, (_event, ...args) => handler.handler(args[0])))
+ // IPC handlers
+ getIPCInvokeHandlers().map(handler => ipcMain.handle(handler.event, (_event, ...args) => handler.handler(args[0])))
+ getIPCEmitHandlers().map(handler => ipcMain.on(handler.event, (_event, ...args) => handler.handler(args[0])))
- // Load angular app
- mainWindow.loadURL(getLoadUrl())
+ // Load angular app
+ mainWindow.loadURL(getLoadUrl())
- if (isDevBuild) {
- mainWindow.webContents.openDevTools()
- }
+ if (isDevBuild) {
+ mainWindow.webContents.openDevTools()
+ }
- mainWindow.on('closed', () => {
- mainWindow = null // Dereference mainWindow when the window is closed
- })
+ mainWindow.on('closed', () => {
+ mainWindow = null // Dereference mainWindow when the window is closed
+ })
- // enable the remote webcontents
- remote.enable(mainWindow.webContents)
+ // enable the remote webcontents
+ remote.enable(mainWindow.webContents)
}
/**
* Initialize a BrowserWindow object with initial parameters
*/
function createBrowserWindow(windowState: windowStateKeeper.State) {
- let options: Electron.BrowserWindowConstructorOptions = {
- x: windowState.x,
- y: windowState.y,
- width: windowState.width,
- height: windowState.height,
- frame: false,
- title: 'Bridge',
- webPreferences: {
- nodeIntegration: true,
- allowRunningInsecureContent: (isDevBuild) ? true : false,
- textAreasAreResizable: false,
- contextIsolation: false
- },
- simpleFullscreen: true,
- fullscreenable: false,
- backgroundColor: '#121212'
- }
+ let options: Electron.BrowserWindowConstructorOptions = {
+ x: windowState.x,
+ y: windowState.y,
+ width: windowState.width,
+ height: windowState.height,
+ frame: false,
+ title: 'Bridge',
+ webPreferences: {
+ nodeIntegration: true,
+ allowRunningInsecureContent: (isDevBuild) ? true : false,
+ textAreasAreResizable: false,
+ contextIsolation: false,
+ },
+ simpleFullscreen: true,
+ fullscreenable: false,
+ backgroundColor: '#121212',
+ }
- if (process.platform == 'linux' && !isDevBuild) {
- options = Object.assign(options, { icon: path.join(__dirname, '..', 'assets', 'images', 'system', 'icons', 'png', '48x48.png' ) })
- }
+ if (process.platform == 'linux' && !isDevBuild) {
+ options = Object.assign(options, { icon: path.join(__dirname, '..', 'assets', 'images', 'system', 'icons', 'png', '48x48.png') })
+ }
- return new BrowserWindow(options)
+ return new BrowserWindow(options)
}
/**
* Load from localhost during development; load from index.html in production
*/
function getLoadUrl() {
- return url.format({
- protocol: isDevBuild ? 'http:' : 'file:',
- pathname: isDevBuild ? '//localhost:4200/' : path.join(__dirname, '..', 'index.html'),
- slashes: true
- })
+ return url.format({
+ protocol: isDevBuild ? 'http:' : 'file:',
+ pathname: isDevBuild ? '//localhost:4200/' : path.join(__dirname, '..', 'index.html'),
+ slashes: true,
+ })
}
export function emitIPCEvent(event: E, data: IPCEmitEvents[E]) {
- mainWindow.webContents.send(event, data)
-}
\ No newline at end of file
+ mainWindow.webContents.send(event, data)
+}
diff --git a/src/electron/shared/ElectronUtilFunctions.ts b/src/electron/shared/ElectronUtilFunctions.ts
index 535c247..ff49f1a 100644
--- a/src/electron/shared/ElectronUtilFunctions.ts
+++ b/src/electron/shared/ElectronUtilFunctions.ts
@@ -1,4 +1,5 @@
import { basename, parse } from 'path'
+
import { getSettingsHandler } from '../ipc/SettingsHandler.ipc'
import { emitIPCEvent } from '../main'
import { lower } from './UtilFunctions'
@@ -7,15 +8,15 @@ import { lower } from './UtilFunctions'
* @returns The relative filepath from the library folder to `absoluteFilepath`.
*/
export function getRelativeFilepath(absoluteFilepath: string) {
- const settings = getSettingsHandler.getSettings()
- return basename(settings.libraryPath) + absoluteFilepath.substring(settings.libraryPath.length)
+ const settings = getSettingsHandler.getSettings()
+ return basename(settings.libraryPath) + absoluteFilepath.substring(settings.libraryPath.length)
}
/**
* @returns `true` if `name` has a valid video file extension.
*/
export function hasVideoExtension(name: string) {
- return (['.mp4', '.avi', '.webm', '.ogv', '.mpeg'].includes(parse(lower(name)).ext))
+ return (['.mp4', '.avi', '.webm', '.ogv', '.mpeg'].includes(parse(lower(name)).ext))
}
/**
@@ -23,5 +24,5 @@ export function hasVideoExtension(name: string) {
* Note: Error objects can't be serialized by this; use inspect(err) before passing it here.
*/
export function devLog(...messages: any[]) {
- emitIPCEvent('log', messages)
-}
\ No newline at end of file
+ emitIPCEvent('log', messages)
+}
diff --git a/src/electron/shared/IPCHandler.ts b/src/electron/shared/IPCHandler.ts
index 718b51a..169c958 100644
--- a/src/electron/shared/IPCHandler.ts
+++ b/src/electron/shared/IPCHandler.ts
@@ -1,17 +1,18 @@
-import { SongSearch, SongResult } from './interfaces/search.interface'
-import { VersionResult, AlbumArtResult } from './interfaces/songDetails.interface'
+import { UpdateInfo } from 'electron-updater'
+
+import { albumArtHandler } from '../ipc/browse/AlbumArtHandler.ipc'
+import { batchSongDetailsHandler } from '../ipc/browse/BatchSongDetailsHandler.ipc'
import { searchHandler } from '../ipc/browse/SearchHandler.ipc'
import { songDetailsHandler } from '../ipc/browse/SongDetailsHandler.ipc'
-import { albumArtHandler } from '../ipc/browse/AlbumArtHandler.ipc'
-import { Download, DownloadProgress } from './interfaces/download.interface'
-import { downloadHandler } from '../ipc/download/DownloadHandler'
-import { Settings } from './Settings'
-import { batchSongDetailsHandler } from '../ipc/browse/BatchSongDetailsHandler.ipc'
-import { getSettingsHandler, setSettingsHandler } from '../ipc/SettingsHandler.ipc'
import { clearCacheHandler } from '../ipc/CacheHandler.ipc'
-import { updateChecker, UpdateProgress, getCurrentVersionHandler, downloadUpdateHandler, quitAndInstallHandler, getUpdateAvailableHandler } from '../ipc/UpdateHandler.ipc'
-import { UpdateInfo } from 'electron-updater'
+import { downloadHandler } from '../ipc/download/DownloadHandler'
import { openURLHandler } from '../ipc/OpenURLHandler.ipc'
+import { getSettingsHandler, setSettingsHandler } from '../ipc/SettingsHandler.ipc'
+import { downloadUpdateHandler, getCurrentVersionHandler, getUpdateAvailableHandler, quitAndInstallHandler, updateChecker, UpdateProgress } from '../ipc/UpdateHandler.ipc'
+import { Download, DownloadProgress } from './interfaces/download.interface'
+import { SongResult, SongSearch } from './interfaces/search.interface'
+import { AlbumArtResult, VersionResult } from './interfaces/songDetails.interface'
+import { Settings } from './Settings'
/**
* To add a new IPC listener:
@@ -22,101 +23,101 @@ import { openURLHandler } from '../ipc/OpenURLHandler.ipc'
*/
export function getIPCInvokeHandlers(): IPCInvokeHandler[] {
- return [
- getSettingsHandler,
- clearCacheHandler,
- searchHandler,
- songDetailsHandler,
- batchSongDetailsHandler,
- albumArtHandler,
- getCurrentVersionHandler,
- getUpdateAvailableHandler,
- ]
+ return [
+ getSettingsHandler,
+ clearCacheHandler,
+ searchHandler,
+ songDetailsHandler,
+ batchSongDetailsHandler,
+ albumArtHandler,
+ getCurrentVersionHandler,
+ getUpdateAvailableHandler,
+ ]
}
/**
* The list of possible async IPC events that return values, mapped to their input and output types.
*/
-export type IPCInvokeEvents = {
- 'get-settings': {
- input: undefined
- output: Settings
- }
- 'clear-cache': {
- input: undefined
- output: void
- }
- 'song-search': {
- input: SongSearch
- output: SongResult[]
- }
- 'album-art': {
- input: SongResult['id']
- output: AlbumArtResult
- }
- 'song-details': {
- input: SongResult['id']
- output: VersionResult[]
- }
- 'batch-song-details': {
- input: number[]
- output: VersionResult[]
- }
- 'get-current-version': {
- input: undefined
- output: string
- }
- 'get-update-available': {
- input: undefined
- output: boolean
- }
+export interface IPCInvokeEvents {
+ 'get-settings': {
+ input: undefined
+ output: Settings
+ }
+ 'clear-cache': {
+ input: undefined
+ output: void
+ }
+ 'song-search': {
+ input: SongSearch
+ output: SongResult[]
+ }
+ 'album-art': {
+ input: SongResult['id']
+ output: AlbumArtResult
+ }
+ 'song-details': {
+ input: SongResult['id']
+ output: VersionResult[]
+ }
+ 'batch-song-details': {
+ input: number[]
+ output: VersionResult[]
+ }
+ 'get-current-version': {
+ input: undefined
+ output: string
+ }
+ 'get-update-available': {
+ input: undefined
+ output: boolean
+ }
}
/**
* Describes an object that handles the `E` async IPC event that will return a value.
*/
export interface IPCInvokeHandler {
- event: E
- handler(data: IPCInvokeEvents[E]['input']): Promise | IPCInvokeEvents[E]['output']
+ event: E
+ handler(data: IPCInvokeEvents[E]['input']): Promise | IPCInvokeEvents[E]['output']
}
export function getIPCEmitHandlers(): IPCEmitHandler[] {
- return [
- downloadHandler,
- setSettingsHandler,
- downloadUpdateHandler,
- updateChecker,
- quitAndInstallHandler,
- openURLHandler
- ]
+ return [
+ downloadHandler,
+ setSettingsHandler,
+ downloadUpdateHandler,
+ updateChecker,
+ quitAndInstallHandler,
+ openURLHandler,
+ ]
}
/**
* The list of possible async IPC events that don't return values, mapped to their input types.
*/
-export type IPCEmitEvents = {
- 'log': any[]
+export interface IPCEmitEvents {
+ 'log': any[]
- 'download': Download
- 'download-updated': DownloadProgress
- 'set-settings': Settings
- 'queue-updated': number[]
+ 'download': Download
+ 'download-updated': DownloadProgress
+ 'set-settings': Settings
+ 'queue-updated': number[]
- 'update-error': Error
- 'update-available': UpdateInfo
- 'update-progress': UpdateProgress
- 'update-downloaded': undefined
- 'download-update': undefined
- 'retry-update': undefined
- 'quit-and-install': undefined
- 'open-url': string
+ 'update-error': Error
+ 'update-available': UpdateInfo
+ 'update-progress': UpdateProgress
+ 'update-downloaded': undefined
+ 'download-update': undefined
+ 'retry-update': undefined
+ 'quit-and-install': undefined
+ 'open-url': string
}
/**
* Describes an object that handles the `E` async IPC event that will not return a value.
*/
export interface IPCEmitHandler {
- event: E
- handler(data: IPCEmitEvents[E]): void
-}
\ No newline at end of file
+ event: E
+ handler(data: IPCEmitEvents[E]): void
+}
diff --git a/src/electron/shared/Paths.ts b/src/electron/shared/Paths.ts
index 7722557..7e801cb 100644
--- a/src/electron/shared/Paths.ts
+++ b/src/electron/shared/Paths.ts
@@ -1,5 +1,5 @@
-import { join } from 'path'
import { app } from 'electron'
+import { join } from 'path'
// Data paths
export const dataPath = join(app.getPath('userData'), 'bridge_data')
@@ -15,4 +15,4 @@ export const serverURL = 'bridge-db.net'
export const SERVER_PORT = 42813
export const REDIRECT_BASE = `http://127.0.0.1:${SERVER_PORT}`
export const REDIRECT_PATH = `/oauth2callback`
-export const REDIRECT_URI = `${REDIRECT_BASE}${REDIRECT_PATH}`
\ No newline at end of file
+export const REDIRECT_URI = `${REDIRECT_BASE}${REDIRECT_PATH}`
diff --git a/src/electron/shared/Settings.ts b/src/electron/shared/Settings.ts
index 7c2239c..83b16fd 100644
--- a/src/electron/shared/Settings.ts
+++ b/src/electron/shared/Settings.ts
@@ -2,18 +2,18 @@
* Represents Bridge's user settings.
*/
export interface Settings {
- rateLimitDelay: number // Number of seconds to wait between each file download from Google servers
- downloadVideos: boolean // If background videos should be downloaded
- theme: string // The name of the currently enabled UI theme
- libraryPath: string // The path to the user's library
+ rateLimitDelay: number // Number of seconds to wait between each file download from Google servers
+ downloadVideos: boolean // If background videos should be downloaded
+ theme: string // The name of the currently enabled UI theme
+ libraryPath: string // The path to the user's library
}
/**
* Bridge's default user settings.
*/
export const defaultSettings: Settings = {
- rateLimitDelay: 31,
- downloadVideos: true,
- theme: 'Default',
- libraryPath: undefined
-}
\ No newline at end of file
+ rateLimitDelay: 31,
+ downloadVideos: true,
+ theme: 'Default',
+ libraryPath: undefined,
+}
diff --git a/src/electron/shared/UtilFunctions.ts b/src/electron/shared/UtilFunctions.ts
index 0b5a968..555e7db 100644
--- a/src/electron/shared/UtilFunctions.ts
+++ b/src/electron/shared/UtilFunctions.ts
@@ -1,4 +1,5 @@
import * as randomBytes from 'randombytes'
+
const sanitize = require('sanitize-filename')
// WARNING: do not import anything related to Electron; the code will not compile correctly.
@@ -10,52 +11,52 @@ export type AnyFunction = (...args: any) => any
* @returns `filename` with all invalid filename characters replaced.
*/
export function sanitizeFilename(filename: string): string {
- const newFilename = sanitize(filename, {
- replacement: ((invalidChar: string) => {
- switch (invalidChar) {
- case '<': return '❮'
- case '>': return '❯'
- case ':': return '꞉'
- case '"': return "'"
- case '/': return '/'
- case '\\': return '⧵'
- case '|': return '⏐'
- case '?': return '?'
- case '*': return '⁎'
- default: return '_'
- }
- })
- })
- return (newFilename == '' ? randomBytes(5).toString('hex') : newFilename)
+ const newFilename = sanitize(filename, {
+ replacement: ((invalidChar: string) => {
+ switch (invalidChar) {
+ case '<': return '❮'
+ case '>': return '❯'
+ case ':': return '꞉'
+ case '"': return "'"
+ case '/': return '/'
+ case '\\': return '⧵'
+ case '|': return '⏐'
+ case '?': return '?'
+ case '*': return '⁎'
+ default: return '_'
+ }
+ }),
+ })
+ return (newFilename == '' ? randomBytes(5).toString('hex') : newFilename)
}
/**
* @returns `text` converted to lower case.
*/
export function lower(text: string) {
- return text.toLowerCase()
+ return text.toLowerCase()
}
/**
* Converts `val` from the range (`fromStart`, `fromEnd`) to the range (`toStart`, `toEnd`).
*/
export function interpolate(val: number, fromStart: number, fromEnd: number, toStart: number, toEnd: number) {
- return ((val - fromStart) / (fromEnd - fromStart)) * (toEnd - toStart) + toStart
+ return ((val - fromStart) / (fromEnd - fromStart)) * (toEnd - toStart) + toStart
}
/**
* @returns `objectList` split into multiple groups, where each group contains objects where every one of its values in `keys` matches.
*/
export function groupBy(objectList: T[], ...keys: (keyof T)[]) {
- const results: T[][] = []
- for (const object of objectList) {
- const matchingGroup = results.find(result => keys.every(key => result[0][key] == object[key]))
- if (matchingGroup != undefined) {
- matchingGroup.push(object)
- } else {
- results.push([object])
- }
- }
+ const results: T[][] = []
+ for (const object of objectList) {
+ const matchingGroup = results.find(result => keys.every(key => result[0][key] == object[key]))
+ if (matchingGroup != undefined) {
+ matchingGroup.push(object)
+ } else {
+ results.push([object])
+ }
+ }
- return results
-}
\ No newline at end of file
+ return results
+}
diff --git a/src/electron/shared/interfaces/download.interface.ts b/src/electron/shared/interfaces/download.interface.ts
index b899488..8f7a18b 100644
--- a/src/electron/shared/interfaces/download.interface.ts
+++ b/src/electron/shared/interfaces/download.interface.ts
@@ -4,35 +4,35 @@ import { DriveChart } from './songDetails.interface'
* Represents a user's request to interact with the download system.
*/
export interface Download {
- action: 'add' | 'retry' | 'cancel'
- versionID: number
- data?: NewDownload // Should be defined if action == 'add'
+ action: 'add' | 'retry' | 'cancel'
+ versionID: number
+ data?: NewDownload // Should be defined if action == 'add'
}
/**
* Contains the data required to start downloading a single chart.
*/
export interface NewDownload {
- chartName: string
- artist: string
- charter: string
- driveData: DriveChart & { inChartPack: boolean }
+ chartName: string
+ artist: string
+ charter: string
+ driveData: DriveChart & { inChartPack: boolean }
}
/**
* Represents the download progress of a single chart.
*/
export interface DownloadProgress {
- versionID: number
- title: string
- header: string
- description: string
- percent: number
- type: ProgressType
- /** If `description` contains a filepath that can be clicked */
- isLink: boolean
- /** If the download should not appear in the total download progress */
- stale?: boolean
+ versionID: number
+ title: string
+ header: string
+ description: string
+ percent: number
+ type: ProgressType
+ /** If `description` contains a filepath that can be clicked */
+ isLink: boolean
+ /** If the download should not appear in the total download progress */
+ stale?: boolean
}
-export type ProgressType = 'good' | 'error' | 'cancel' | 'done'
\ No newline at end of file
+export type ProgressType = 'good' | 'error' | 'cancel' | 'done'
diff --git a/src/electron/shared/interfaces/search.interface.ts b/src/electron/shared/interfaces/search.interface.ts
index 0194471..06855b5 100644
--- a/src/electron/shared/interfaces/search.interface.ts
+++ b/src/electron/shared/interfaces/search.interface.ts
@@ -2,86 +2,90 @@
* Represents a user's song search query.
*/
export interface SongSearch { // TODO: make limit a setting that's not always 51
- query: string
- quantity: 'all' | 'any'
- similarity: 'similar' | 'exact'
- fields: SearchFields
- tags: SearchTags
- instruments: SearchInstruments
- difficulties: SearchDifficulties
- minDiff: number
- maxDiff: number
- limit: number
- offset: number
+ query: string
+ quantity: 'all' | 'any'
+ similarity: 'similar' | 'exact'
+ fields: SearchFields
+ tags: SearchTags
+ instruments: SearchInstruments
+ difficulties: SearchDifficulties
+ minDiff: number
+ maxDiff: number
+ limit: number
+ offset: number
}
export function getDefaultSearch(): SongSearch {
- return {
- query: '',
- quantity: 'all',
- similarity: 'similar',
- fields: { name: true, artist: true, album: true, genre: true, year: true, charter: true, tag: true },
- tags: { 'sections': false, 'star power': false, 'forcing': false, 'taps': false, 'lyrics': false,
- 'video': false, 'stems': false, 'solo sections': false, 'open notes': false },
- instruments: { guitar: false, bass: false, rhythm: false, keys: false,
- drums: false, guitarghl: false, bassghl: false, vocals: false },
- difficulties: { expert: false, hard: false, medium: false, easy: false },
- minDiff: 0,
- maxDiff: 6,
- limit: 50 + 1,
- offset: 0
- }
+ return {
+ query: '',
+ quantity: 'all',
+ similarity: 'similar',
+ fields: { name: true, artist: true, album: true, genre: true, year: true, charter: true, tag: true },
+ tags: {
+ 'sections': false, 'star power': false, 'forcing': false, 'taps': false, 'lyrics': false,
+ 'video': false, 'stems': false, 'solo sections': false, 'open notes': false
+ },
+ instruments: {
+ guitar: false, bass: false, rhythm: false, keys: false,
+ drums: false, guitarghl: false, bassghl: false, vocals: false
+ },
+ difficulties: { expert: false, hard: false, medium: false, easy: false },
+ minDiff: 0,
+ maxDiff: 6,
+ limit: 50 + 1,
+ offset: 0,
+ }
}
export interface SearchFields {
- name: boolean
- artist: boolean
- album: boolean
- genre: boolean
- year: boolean
- charter: boolean
- tag: boolean
+ name: boolean
+ artist: boolean
+ album: boolean
+ genre: boolean
+ year: boolean
+ charter: boolean
+ tag: boolean
}
export interface SearchTags {
- 'sections': boolean // Tag inverted
- 'star power': boolean // Tag inverted
- 'forcing': boolean // Tag inverted
- 'taps': boolean
- 'lyrics': boolean
- 'video': boolean
- 'stems': boolean
- 'solo sections': boolean
- 'open notes': boolean
+ 'sections': boolean // Tag inverted
+ 'star power': boolean // Tag inverted
+ 'forcing': boolean // Tag inverted
+ 'taps': boolean
+ 'lyrics': boolean
+ 'video': boolean
+ 'stems': boolean
+ 'solo sections': boolean
+ 'open notes': boolean
}
export interface SearchInstruments {
- guitar: boolean
- bass: boolean
- rhythm: boolean
- keys: boolean
- drums: boolean
- guitarghl: boolean
- bassghl: boolean
- vocals: boolean
+ guitar: boolean
+ bass: boolean
+ rhythm: boolean
+ keys: boolean
+ drums: boolean
+ guitarghl: boolean
+ bassghl: boolean
+ vocals: boolean
}
export interface SearchDifficulties {
- expert: boolean
- hard: boolean
- medium: boolean
- easy: boolean
+ expert: boolean
+ hard: boolean
+ medium: boolean
+ easy: boolean
}
/**
* Represents a single song search result.
*/
export interface SongResult {
- id: number
- chartCount: number
- name: string
- artist: string
- album: string
- genre: string
- year: string
-}
\ No newline at end of file
+ id: number
+ chartCount: number
+ name: string
+ artist: string
+ album: string
+ genre: string
+ year: string
+}
diff --git a/src/electron/shared/interfaces/songDetails.interface.ts b/src/electron/shared/interfaces/songDetails.interface.ts
index 5eff949..c6d11ef 100644
--- a/src/electron/shared/interfaces/songDetails.interface.ts
+++ b/src/electron/shared/interfaces/songDetails.interface.ts
@@ -2,100 +2,100 @@
* The image data for a song's album art.
*/
export interface AlbumArtResult {
- base64Art: string
+ base64Art: string
}
/**
* Represents a single chart version.
*/
export interface VersionResult {
- versionID: number
- chartID: number
- songID: number
- latestVersionID: number
- latestSetlistVersionID: number
- name: string
- chartName: string
- artist: string
- album: string
- genre: string
- year: string
- songDataIncorrect: boolean
- driveData: DriveChart & { inChartPack: boolean }
- md5: string
- lastModified: string
- icon: string
- charters: string
- charterIDs: string
- tags: string | null
- songLength: number
- diff_band: number
- diff_guitar: number
- diff_bass: number
- diff_rhythm: number
- diff_drums: number
- diff_keys: number
- diff_guitarghl: number
- diff_bassghl: number
- chartData: ChartData
+ versionID: number
+ chartID: number
+ songID: number
+ latestVersionID: number
+ latestSetlistVersionID: number
+ name: string
+ chartName: string
+ artist: string
+ album: string
+ genre: string
+ year: string
+ songDataIncorrect: boolean
+ driveData: DriveChart & { inChartPack: boolean }
+ md5: string
+ lastModified: string
+ icon: string
+ charters: string
+ charterIDs: string
+ tags: string | null
+ songLength: number
+ diff_band: number
+ diff_guitar: number
+ diff_bass: number
+ diff_rhythm: number
+ diff_drums: number
+ diff_keys: number
+ diff_guitarghl: number
+ diff_bassghl: number
+ chartData: ChartData
}
export interface DriveChart {
- source: DriveSource
- isArchive: boolean
- downloadPath: string
- filesHash: string
- folderName: string
- folderID: string
- files: DriveFile[]
+ source: DriveSource
+ isArchive: boolean
+ downloadPath: string
+ filesHash: string
+ folderName: string
+ folderID: string
+ files: DriveFile[]
}
export interface DriveSource {
- isSetlistSource: boolean
- isDriveFileSource?: boolean
- setlistIcon?: string
- sourceUserIDs: number[]
- sourceName: string
- sourceDriveID: string
- proxyLink?: string
+ isSetlistSource: boolean
+ isDriveFileSource?: boolean
+ setlistIcon?: string
+ sourceUserIDs: number[]
+ sourceName: string
+ sourceDriveID: string
+ proxyLink?: string
}
export interface DriveFile {
- id: string
- name: string
- mimeType: string
- webContentLink: string
- modifiedTime: string
- md5Checksum: string
- size: string
+ id: string
+ name: string
+ mimeType: string
+ webContentLink: string
+ modifiedTime: string
+ md5Checksum: string
+ size: string
}
export interface ChartData {
- hasSections: boolean
- hasStarPower: boolean
- hasForced: boolean
- hasTap: boolean
- hasOpen: {
- [instrument: string]: boolean
- }
- hasSoloSections: boolean
- hasLyrics: boolean
- is120: boolean
- hasBrokenNotes: boolean
- noteCounts: {
- [instrument in Instrument]: {
- [difficulty in ChartedDifficulty]: number
- }
- }
- /** number of seconds */
- length: number
- /** number of seconds */
- effectiveLength: number
+ hasSections: boolean
+ hasStarPower: boolean
+ hasForced: boolean
+ hasTap: boolean
+ hasOpen: {
+ [instrument: string]: boolean
+ }
+ hasSoloSections: boolean
+ hasLyrics: boolean
+ is120: boolean
+ hasBrokenNotes: boolean
+ noteCounts: {
+ [instrument in Instrument]: {
+ [difficulty in ChartedDifficulty]: number
+ }
+ }
+ /** number of seconds */
+ length: number
+ /** number of seconds */
+ effectiveLength: number
}
export type Instrument = 'guitar' | 'bass' | 'rhythm' | 'keys' | 'drums' | 'guitarghl' | 'bassghl' | 'vocals' | 'undefined'
export type ChartedDifficulty = 'x' | 'h' | 'm' | 'e'
export function getInstrumentIcon(instrument: Instrument) {
- return `${instrument}.png`
-}
\ No newline at end of file
+ return `${instrument}.png`
+}
diff --git a/src/electron/shared/typeDefinitions/node-7z.d.ts b/src/electron/shared/typeDefinitions/node-7z.d.ts
deleted file mode 100644
index e4e2478..0000000
--- a/src/electron/shared/typeDefinitions/node-7z.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-declare module 'node-7z'
\ No newline at end of file
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
index bb1665f..3cce312 100644
--- a/src/environments/environment.prod.ts
+++ b/src/environments/environment.prod.ts
@@ -1,3 +1,3 @@
export const environment = {
- production: true
-}
\ No newline at end of file
+ production: true,
+}
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 778ff31..fa3251a 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -3,7 +3,7 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
- production: false
+ production: false,
}
/*
@@ -13,4 +13,4 @@ export const environment = {
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
-// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
\ No newline at end of file
+// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
diff --git a/src/index.html b/src/index.html
index dab3b13..6308243 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,12 +1,12 @@
-
-
- Bridge
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+ Bridge
+
+
+
+
+
+
+