mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
Fix downloads
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<app-search-bar></app-search-bar>
|
||||
<div class="flex flex-1 overflow-y-hidden">
|
||||
<!-- TODO: adjust sizing -->
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<div
|
||||
class="basis-2/3 flex-1 overflow-y-auto scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-track-base-300 scrollbar-thumb-neutral scrollbar-thumb-rounded-full"
|
||||
(scroll)="resultTable.tableScrolled($event)">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FormControl } from '@angular/forms'
|
||||
|
||||
import { capitalize, chain, compact, flatMap, intersection, round, sortBy } from 'lodash'
|
||||
import { ChartIssueType, Difficulty, FolderIssueType, Instrument, MetadataIssueType, NoteIssueType, TrackIssueType } from 'scan-chart'
|
||||
import { DownloadService } from 'src-angular/app/core/services/download.service'
|
||||
import { SearchService } from 'src-angular/app/core/services/search.service'
|
||||
import { SettingsService } from 'src-angular/app/core/services/settings.service'
|
||||
import { ChartData } from 'src-shared/interfaces/search.interface'
|
||||
@@ -38,6 +39,7 @@ export class ChartSidebarComponent implements OnInit {
|
||||
constructor(
|
||||
private renderer: Renderer2,
|
||||
private searchService: SearchService,
|
||||
private downloadService: DownloadService,
|
||||
public settingsService: SettingsService
|
||||
) { }
|
||||
|
||||
@@ -278,17 +280,11 @@ export class ChartSidebarComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the selected version to the download queue.
|
||||
* Adds the selected chart to the download queue.
|
||||
*/
|
||||
onDownloadClicked() {
|
||||
// TODO
|
||||
// this.downloadService.addDownload(
|
||||
// this.selectedChart.versionID, {
|
||||
// chartName: this.selectedChart.chartName,
|
||||
// artist: this.songResult!.artist,
|
||||
// charter: this.selectedChart.charters,
|
||||
// driveData: this.selectedChart.driveData,
|
||||
// })
|
||||
this.downloadService.addDownload(this.selectedChart!.md5, `${this.selectedChart!.artist ?? 'Unknown Artist'
|
||||
} - ${this.selectedChart!.name ?? 'Unknown Name'} (${this.selectedChart!.charter ?? 'Unknown Charter'})`)
|
||||
}
|
||||
|
||||
public showMenu() {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<table id="resultTable" class="table table-zebra table-pin-rows">
|
||||
<!-- TODO: maybe have some of these tags customizable? E.g. small/large/compact/padded -->
|
||||
<!-- TODO: learn semantic themes in order to change the $mobileBreakpoint global variable (better search table adjustment) -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="collapsing" id="checkboxColumn">
|
||||
|
||||
@@ -107,16 +107,6 @@ export class SearchBarComponent implements OnInit, AfterViewInit {
|
||||
return dayjs().format('YYYY-MM-DD')
|
||||
}
|
||||
|
||||
// TODO: run this when infinite scroll should happen
|
||||
// @HostListener("window:scroll", [])
|
||||
// onScroll(): void {
|
||||
// if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
|
||||
// if (this.searchService.areMorePages && !this.searchService.searchLoading) {
|
||||
// this.searchService.search(this.searchControl.value || '*', true).subscribe()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
initializeAdvancedSearchForm() {
|
||||
this.advancedSearchForm = this.getAdvancedSearchForm() as ReturnType<this['getAdvancedSearchForm']>
|
||||
|
||||
|
||||
@@ -1,24 +1,64 @@
|
||||
<div *ngIf="downloads.length > 0" class="ui segments">
|
||||
<div *ngFor="let download of downloads; trackBy: trackByVersionID" class="ui segment" [style.background-color]="getBackgroundColor(download)">
|
||||
<h3 id="downloadTitle">
|
||||
<span style="flex-grow: 1">{{ download.title }}</span>
|
||||
<i class="inside right close icon" (click)="cancelDownload(download.versionID)"></i>
|
||||
</h3>
|
||||
|
||||
<div id="downloadText">
|
||||
<span style="flex-grow: 1">{{ download.header }}</span>
|
||||
<span *ngIf="!download.isLink" class="description">{{ download.description }}</span>
|
||||
<span *ngIf="download.isLink" class="description">
|
||||
<a (click)="showFile(download.description)">{{ download.description }}</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="downloadProgressDiv">
|
||||
<progress [value]="download.percent" max="100" class="progress w-96" [class.progress-error]="download.type === 'error'"></progress>
|
||||
<button *ngIf="download.type === 'error'" class="ui right attached labeled compact icon button" (click)="retryDownload(download.versionID)">
|
||||
<i class="redo icon"></i>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 min-h-0">
|
||||
<div
|
||||
class="flex flex-col gap-1 p-1 overflow-y-auto max-h-[75vh] scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-thumb-neutral scrollbar-thumb-rounded-full">
|
||||
@for (download of downloadService.downloads; track download.md5) {
|
||||
<div
|
||||
class="card bg-neutral text-neutral-content shadow-xl"
|
||||
[class.border-error]="download.type === 'error'"
|
||||
[class.border-2]="download.type === 'error'">
|
||||
<div class="card-body">
|
||||
<div class="card-actions justify-end">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" (click)="downloadService.cancelDownload(download.md5)">
|
||||
<i class="bi bi-x-lg text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<h2 class="card-title">{{ download.chartName }}</h2>
|
||||
<progress
|
||||
[attr.value]="download.percent"
|
||||
max="100"
|
||||
class="progress progress-primary w-full"
|
||||
[class.progress-error]="download.type === 'error'"></progress>
|
||||
<div class="flex justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-lg">
|
||||
@if (download.type === 'done') {
|
||||
<a class="link link-hover" (click)="showFile(download.body)">{{ download.header }}</a>
|
||||
} @else {
|
||||
{{ download.header }}
|
||||
}
|
||||
</p>
|
||||
@if (download.type !== 'done') {
|
||||
@if (download.isPath) {
|
||||
<p>
|
||||
<a (click)="showFile(download.body)" class="link">{{ download.body }}</a>
|
||||
</p>
|
||||
} @else {
|
||||
<p>{{ download.body }}</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div *ngIf="download.type === 'error'" class="flex flex-col justify-end">
|
||||
<button class="btn btn-outline btn-error flex-nowrap" (click)="downloadService.retryDownload(download.md5)">
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="downloadTitle">
|
||||
<span style="flex-grow: 1"></span>
|
||||
<i class="inside right close icon" (click)="downloadService.cancelDownload(download.md5)"></i>
|
||||
</div>
|
||||
<div id="downloadText">
|
||||
<span style="flex-grow: 1"></span>
|
||||
<span *ngIf="!download.isPath" class="description"></span>
|
||||
<span *ngIf="download.isPath" class="description"> </span>
|
||||
</div>
|
||||
<div id="downloadProgressDiv"></div>
|
||||
}
|
||||
</div>
|
||||
<div *ngIf="downloadService.completedCount > 1" class="flex justify-end">
|
||||
<button class="btn btn-success btn-sm" (click)="downloadService.cancelAllCompleted()">Clear completed</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ChangeDetectorRef, Component } from '@angular/core'
|
||||
import { Component, HostBinding } from '@angular/core'
|
||||
|
||||
import { DownloadProgress } from '../../../../../../src-shared/interfaces/download.interface'
|
||||
import { DownloadService } from '../../../../core/services/download.service'
|
||||
|
||||
@Component({
|
||||
@@ -8,45 +7,9 @@ import { DownloadService } from '../../../../core/services/download.service'
|
||||
templateUrl: './downloads-modal.component.html',
|
||||
})
|
||||
export class DownloadsModalComponent {
|
||||
@HostBinding('class.contents') contents = true
|
||||
|
||||
downloads: DownloadProgress[] = []
|
||||
|
||||
constructor(private downloadService: DownloadService, ref: ChangeDetectorRef) {
|
||||
window.electron.on.queueUpdated(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()
|
||||
})
|
||||
}
|
||||
|
||||
trackByVersionID(_index: number, item: DownloadProgress) {
|
||||
return item.versionID
|
||||
}
|
||||
|
||||
cancelDownload(versionID: number) {
|
||||
this.downloadService.cancelDownload(versionID)
|
||||
}
|
||||
|
||||
retryDownload(versionID: number) {
|
||||
this.downloadService.retryDownload(versionID)
|
||||
}
|
||||
|
||||
getBackgroundColor(download: DownloadProgress) {
|
||||
switch (download.type) {
|
||||
case 'error': return '#a63a3a'
|
||||
default: return undefined
|
||||
}
|
||||
}
|
||||
constructor(public downloadService: DownloadService) { }
|
||||
|
||||
showFile(filepath: string) {
|
||||
window.electron.emit.showFile(filepath)
|
||||
|
||||
@@ -7,18 +7,20 @@
|
||||
Download {{ selectedGroupIds.length }} Results
|
||||
</button>
|
||||
</div>
|
||||
<button *ngIf="downloading" (click)="showDownloads()" class="btn btn-sm btn-ghost w-[30vw]">
|
||||
<progress [value]="percent" max="100" class="progress h-3 rounded-md" [class.progress-error]="error"></progress>
|
||||
<button *ngIf="downloadService.downloadCount > 0" (click)="downloadsModal.showModal()" class="btn btn-sm btn-ghost w-[30vw]">
|
||||
<progress
|
||||
[attr.value]="downloadService.totalDownloadingPercent"
|
||||
max="100"
|
||||
class="progress h-3 rounded-md"
|
||||
[class.progress-error]="downloadService.anyErrorsExist"></progress>
|
||||
</button>
|
||||
|
||||
<div id="downloadsModal" class="ui modal">
|
||||
<i class="inside close icon"></i>
|
||||
<div class="header">
|
||||
<span>Downloads</span>
|
||||
<div *ngIf="multipleCompleted" class="ui positive compact button" (click)="clearCompleted()">Clear completed</div>
|
||||
<dialog #downloadsModal class="modal whitespace-normal">
|
||||
<div class="modal-box bg-base-100 text-base-content flex flex-col gap-2 w-9/12 max-w-7xl min-h-0 overflow-y-clip">
|
||||
<app-downloads-modal />
|
||||
</div>
|
||||
<div class="scrolling content">
|
||||
<app-downloads-modal></app-downloads-modal>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectorRef, Component } from '@angular/core'
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core'
|
||||
|
||||
import { keys, pickBy } from 'lodash'
|
||||
|
||||
@@ -12,6 +12,8 @@ import { SelectionService } from '../../../core/services/selection.service'
|
||||
})
|
||||
export class StatusBarComponent {
|
||||
|
||||
@ViewChild('downloadsModal', { static: false }) downloadsModalComponent: ElementRef
|
||||
|
||||
multipleCompleted = false
|
||||
downloading = false
|
||||
error = false
|
||||
@@ -23,19 +25,14 @@ export class StatusBarComponent {
|
||||
chartGroups: any[][]
|
||||
|
||||
constructor(
|
||||
private downloadService: DownloadService,
|
||||
public downloadService: DownloadService,
|
||||
public 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)
|
||||
this.downloadService.downloadCountChanges.subscribe(downloadCount => {
|
||||
if (downloadCount === 0) {
|
||||
this.downloadsModalComponent.nativeElement.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,11 +40,6 @@ export class StatusBarComponent {
|
||||
return keys(pickBy(this.selectionService.selections)).map(k => Number(k))
|
||||
}
|
||||
|
||||
showDownloads() {
|
||||
// TODO
|
||||
// $('#downloadsModal').modal('show')
|
||||
}
|
||||
|
||||
async downloadSelected() {
|
||||
// this.chartGroups = []
|
||||
// // TODO
|
||||
@@ -105,6 +97,6 @@ export class StatusBarComponent {
|
||||
}
|
||||
|
||||
clearCompleted() {
|
||||
this.downloadService.cancelCompleted()
|
||||
this.downloadService.cancelAllCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import { EventEmitter, Injectable } from '@angular/core'
|
||||
import { EventEmitter, Injectable, NgZone } from '@angular/core'
|
||||
|
||||
import { DownloadProgress, NewDownload } from '../../../../src-shared/interfaces/download.interface'
|
||||
import { assign } from 'lodash'
|
||||
|
||||
import { DownloadProgress } from '../../../../src-shared/interfaces/download.interface'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DownloadService {
|
||||
|
||||
private downloadUpdatedEmitter = new EventEmitter<DownloadProgress>()
|
||||
private downloads: DownloadProgress[] = []
|
||||
public downloadCountChanges = new EventEmitter<number>()
|
||||
public downloads: DownloadProgress[] = []
|
||||
|
||||
constructor() {
|
||||
window.electron.on.downloadUpdated(result => {
|
||||
// Update <this.downloads> 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)
|
||||
constructor(zone: NgZone) {
|
||||
window.electron.on.downloadQueueUpdate(download => zone.run(() => {
|
||||
const downloadIndex = this.downloads.findIndex(d => d.md5 === download.md5)
|
||||
if (download.type === 'cancel') {
|
||||
this.downloads = this.downloads.filter(d => d.md5 !== this.downloads[downloadIndex].md5)
|
||||
} else if (downloadIndex === -1) {
|
||||
this.downloads.push(download)
|
||||
} else {
|
||||
this.downloads[thisDownloadIndex] = result
|
||||
assign(this.downloads[downloadIndex], download)
|
||||
}
|
||||
|
||||
this.downloadUpdatedEmitter.emit(result)
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
get downloadCount() {
|
||||
@@ -39,50 +38,62 @@ export class DownloadService {
|
||||
let count = 0
|
||||
for (const download of this.downloads) {
|
||||
if (!download.stale) {
|
||||
total += download.percent
|
||||
total += download.percent ?? 0
|
||||
count++
|
||||
}
|
||||
}
|
||||
return total / count
|
||||
if (this.anyErrorsExist && total === 0) { return null }
|
||||
return count ? total / count : 0
|
||||
}
|
||||
|
||||
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)
|
||||
addDownload(md5: string, chartName: string) {
|
||||
if (!this.downloads.find(d => d.md5 === md5)) { // Don't download something twice
|
||||
if (this.downloads.every(d => d.type === 'done')) { // Reset overall progress bar if it finished
|
||||
this.downloads.forEach(d => d.stale = true)
|
||||
}
|
||||
window.electron.emit.download({ action: 'add', versionID, data: newDownload })
|
||||
this.downloads.push({
|
||||
md5,
|
||||
chartName,
|
||||
header: 'Waiting for other downloads to finish...',
|
||||
body: '',
|
||||
percent: 0,
|
||||
type: 'good',
|
||||
isPath: false,
|
||||
})
|
||||
window.electron.emit.download({ action: 'add', md5, chartName })
|
||||
}
|
||||
this.downloadCountChanges.emit(this.downloadCount)
|
||||
}
|
||||
|
||||
onDownloadUpdated(callback: (download: DownloadProgress) => void) {
|
||||
this.downloadUpdatedEmitter.subscribe(callback)
|
||||
cancelDownload(md5: string) {
|
||||
window.electron.emit.download({ action: 'remove', md5 })
|
||||
this.downloadCountChanges.emit(this.downloadCount - 1)
|
||||
}
|
||||
|
||||
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 {
|
||||
window.electron.emit.download({ action: 'cancel', versionID })
|
||||
}
|
||||
}
|
||||
|
||||
cancelCompleted() {
|
||||
cancelAllCompleted() {
|
||||
for (const download of this.downloads) {
|
||||
if (download.type === 'done') {
|
||||
this.cancelDownload(download.versionID)
|
||||
window.electron.emit.download({ action: 'remove', md5: download.md5 })
|
||||
}
|
||||
}
|
||||
this.downloads = this.downloads.filter(d => d.type !== 'done')
|
||||
this.downloadCountChanges.emit(this.downloadCount)
|
||||
}
|
||||
|
||||
retryDownload(versionID: number) {
|
||||
window.electron.emit.download({ action: 'retry', versionID })
|
||||
retryDownload(md5: string) {
|
||||
const chartDownload = this.downloads.find(d => d.md5 === md5)
|
||||
if (chartDownload) {
|
||||
chartDownload.type = 'good'
|
||||
chartDownload.header = 'Waiting to retry...'
|
||||
chartDownload.body = ''
|
||||
chartDownload.isPath = false
|
||||
chartDownload.percent = 0
|
||||
|
||||
window.electron.emit.download({ action: 'retry', md5 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,25 +159,3 @@ export class SearchService {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe use this, or delete it
|
||||
/**
|
||||
* 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
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user