mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
Rework sidebar
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<div class="bg-neutral rounded-md">
|
||||
<div class="text-center text-neutral-content">
|
||||
{{ getEMHXString() }}
|
||||
</div>
|
||||
<div class="indicator">
|
||||
<img class="w-12 m-2 mt-0" src="assets/images/instruments/{{ instrument }}.png" />
|
||||
<span class="indicator-item indicator-bottom indicator-start badge badge-error badge-md ml-4 mb-4 font-bold">{{ getDiff() }}</span>
|
||||
<div class="flex gap-2 items-center">
|
||||
<img class="w-11 h-11" src="assets/images/instruments/{{ instrument }}.png" />
|
||||
<div class="leading-4">
|
||||
<span>Diff: {{ getDiff() }}</span>
|
||||
<div>
|
||||
{{ getEMHXString() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<div
|
||||
class="dropdown-content card card-compact p-2 shadow bg-neutral text-neutral-content z-10 cursor-auto border-2 border-base-300 max-w-[90vw] sm:max-w-[80vw] lg:max-w-[70vw] 2xl:max-w-[60vw]">
|
||||
<div class="card-body">
|
||||
<div class="flex">
|
||||
<div class="flex-1">
|
||||
<h1 class="menu-title pl-0 pb-0 whitespace-nowrap text-neutral-content">
|
||||
DOWNLOAD FORMAT
|
||||
<button class="btn btn-xs btn-circle btn-ghost" (click)="selectSngModal.showModal()">
|
||||
<i class="bi bi-info-circle text-sm hover:border-b-secondary-focus"></i>
|
||||
</button>
|
||||
<dialog #selectSngModal id="report_modal" class="modal whitespace-normal">
|
||||
<div class="modal-box bg-base-100 text-base-content flex flex-col gap-2">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<i class="bi bi-x-lg text-lg"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="flex gap-6">
|
||||
<div class="flex-1">
|
||||
<span class="font-bold text-lg">.sng (new)</span>
|
||||
<ul class="list-disc pl-5">
|
||||
<li>Single chart file</li>
|
||||
<li>Can be scanned in-game directly without extracting</li>
|
||||
<li>Currently only supported by YARG and Clone Hero v1.1</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<span class="font-bold text-lg">.zip</span>
|
||||
<ul class="list-disc pl-5">
|
||||
<li>Contains chart folder</li>
|
||||
<li>Must be extracted before it can be scanned in-game</li>
|
||||
<li>Supported across many games</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="text-xs">
|
||||
A program to convert between .sng files and chart folders can be found
|
||||
<a class="link" href="https://github.com/mdsitton/SngFileFormat/releases" target="_blank">here</a>.
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</h1>
|
||||
<div class="flex gap-2">
|
||||
<label class="label cursor-pointer">
|
||||
<input type="radio" class="radio radio-secondary mr-2" [value]="true" [formControl]="isSngControl" />
|
||||
.sng
|
||||
</label>
|
||||
|
||||
<label class="label cursor-pointer">
|
||||
<input type="radio" class="radio radio-secondary mr-2" [value]="false" [formControl]="isSngControl" />
|
||||
.zip
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-2">
|
||||
<button class="btn btn-secondary btn-xs flex-nowrap uppercase" (click)="reportModal.showModal()">
|
||||
<i class="bi bi-exclamation-triangle text-sm text-secondary-content"></i> Report issue
|
||||
</button>
|
||||
<dialog #reportModal id="report_modal" class="modal">
|
||||
@if (reportSent) {
|
||||
<div class="modal-box bg-base-100 text-base-content flex flex-col gap-2">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<i class="bi bi-x-lg text-lg"></i>
|
||||
</button>
|
||||
</form>
|
||||
<span class="text-xl text-center">{{ reportMessage }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="modal-box bg-base-100 text-base-content flex flex-col gap-2">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<i class="bi bi-x-lg text-lg"></i>
|
||||
</button>
|
||||
</form>
|
||||
<h3 class="font-bold text-lg">Report Issue</h3>
|
||||
<div>
|
||||
@for (option of reportOptions; track $index) {
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-normal gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedReportOption{{ selectedVersion.value.chartId }}"
|
||||
class="radio checked:bg-red-500"
|
||||
[value]="option"
|
||||
[formControl]="reportOption" />
|
||||
<span>{{ option }}</span>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<textarea class="textarea textarea-bordered h-24" placeholder="More details (optional)" [formControl]="reportExtraInfo"></textarea>
|
||||
</div>
|
||||
<div class="form-control flex-row justify-end">
|
||||
<button class="btn btn-primary" (click)="report()">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
@if (displayVersions.length > 1) {
|
||||
<h1 class="menu-title pl-0 pb-0 pt-4 whitespace-nowrap text-neutral-content">SELECT VERSION</h1>
|
||||
}
|
||||
<div
|
||||
class="pt-2 overflow-auto scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-track-neutral scrollbar-thumb-neutral-content scrollbar-thumb-rounded-full">
|
||||
<table class="table table-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
@if (displayVersions.length > 1) {
|
||||
<th></th>
|
||||
}
|
||||
<th class="text-neutral-content">Uploaded</th>
|
||||
<th class="text-neutral-content">
|
||||
<span class="label-text cursor-help underline decoration-dotted" title="The MD5 hash of the chart folder or .sng file.">Hash</span>
|
||||
</th>
|
||||
<th class="text-neutral-content">
|
||||
<span class="label-text cursor-help underline decoration-dotted" title="The MD5 hash of just the .chart or .mid file.">Chart Hash</span>
|
||||
</th>
|
||||
<th class="text-neutral-content">Google Drive Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (version of displayVersions; track version.chartId) {
|
||||
<tr>
|
||||
@if (displayVersions.length > 1) {
|
||||
<td>
|
||||
<input
|
||||
type="radio"
|
||||
name="selectedVersion{{ version.chartId }}"
|
||||
class="radio radio-secondary"
|
||||
[value]="version"
|
||||
[formControl]="selectedVersion" />
|
||||
</td>
|
||||
}
|
||||
<td>{{ version.modifiedTime | date: 'y/MM/dd' }}</td>
|
||||
<td>
|
||||
<div class="flex flex-nowrap items-center">
|
||||
{{ version.md5.substring(0, 7) }}
|
||||
<div class="tooltip tooltip-accent" data-tip="Copy full hash">
|
||||
<button class="btn btn-circle btn-ghost btn-xs" (click)="copyHash(version.md5)">
|
||||
<i class="bi bi-copy text-sx"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-nowrap items-center">
|
||||
{{ version.chartMd5.substring(0, 7) }}
|
||||
<div class="tooltip tooltip-accent" data-tip="Copy full hash">
|
||||
<button class="btn btn-circle btn-ghost btn-xs" (click)="copyHash(version.chartMd5)">
|
||||
<i class="bi bi-copy text-sx"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="breadcrumbs overflow-visible">
|
||||
<ul>
|
||||
@for (breadcrumb of getVersionBreadcrumbs(version); track $index) {
|
||||
<li>
|
||||
<ng-container *ngIf="breadcrumb.link">
|
||||
<a [href]="breadcrumb.link" target="_blank">{{ breadcrumb.name }}</a>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!breadcrumb.link">{{ breadcrumb.name }}</ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,92 @@
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
|
||||
import { FormControl } from '@angular/forms'
|
||||
|
||||
import { sortBy } from 'lodash'
|
||||
import { SearchService } from 'src-angular/app/core/services/search.service'
|
||||
import { environment } from 'src-angular/environments/environment'
|
||||
import { ChartData } from 'src-shared/interfaces/search.interface'
|
||||
import { driveLink } from 'src-shared/UtilFunctions'
|
||||
|
||||
@Component({
|
||||
selector: 'app-chart-sidebar-menu',
|
||||
templateUrl: './chart-sidebar-menu.component.html',
|
||||
})
|
||||
export class ChartSidebarMenutComponent implements OnInit {
|
||||
|
||||
@Input() chartVersions: ChartData[]
|
||||
@Output() selectedVersionChanges = new EventEmitter<ChartData>()
|
||||
|
||||
public selectedVersion: FormControl<ChartData>
|
||||
|
||||
public reportOptions = [`Doesn't follow Chorus guidelines`, `Doesn't meet chart quality standards`, 'Other']
|
||||
public reportOption: FormControl<string>
|
||||
public reportExtraInfo: FormControl<string>
|
||||
public reportSent = false
|
||||
public reportMessage = ''
|
||||
|
||||
constructor(
|
||||
private searchService: SearchService,
|
||||
private http: HttpClient,
|
||||
) { }
|
||||
|
||||
get isSngControl() {
|
||||
return this.searchService.isSng
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedVersion = new FormControl<ChartData>(this.displayVersions[0], { nonNullable: true })
|
||||
this.selectedVersion.valueChanges.subscribe(v => this.selectedVersionChanges.emit(v))
|
||||
|
||||
this.reportOption = new FormControl<string>(this.reportOptions[0], { nonNullable: true })
|
||||
this.reportExtraInfo = new FormControl<string>('', { nonNullable: true })
|
||||
}
|
||||
|
||||
get displayVersions() {
|
||||
return sortBy(this.chartVersions, v => v.modifiedTime).reverse()
|
||||
}
|
||||
|
||||
getVersionBreadcrumbs(version: ChartData) {
|
||||
const breadcrumbs: { name: string; link: string | null }[] = []
|
||||
|
||||
breadcrumbs.push({
|
||||
name: version.packName ?? `${version.applicationUsername}'s Charts`,
|
||||
link: driveLink(version.applicationDriveId),
|
||||
})
|
||||
|
||||
if (version.applicationDriveId !== version.parentFolderId) {
|
||||
breadcrumbs.push({ name: version.drivePath, link: driveLink(version.parentFolderId) })
|
||||
}
|
||||
|
||||
if (version.driveFileId) {
|
||||
breadcrumbs.push({ name: version.driveFileName!, link: driveLink(version.driveFileId) })
|
||||
|
||||
if (version.driveChartIsPack) {
|
||||
breadcrumbs.push({ name: this.joinPaths(version.archivePath!, version.chartFileName ?? ''), link: null })
|
||||
}
|
||||
}
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
|
||||
joinPaths(...args: string[]) {
|
||||
return args.join('/')
|
||||
.replace(/\/+/g, '/')
|
||||
.replace(/^\/|\/$/g, '')
|
||||
}
|
||||
|
||||
copyHash(hash: string) {
|
||||
navigator.clipboard.writeText(hash)
|
||||
}
|
||||
|
||||
report() {
|
||||
this.http.post(`${environment.apiUrl}/report`, {
|
||||
chartId: this.selectedVersion.value.chartId,
|
||||
reason: this.reportOption.value,
|
||||
extraInfo: this.reportExtraInfo.value,
|
||||
}).subscribe((response: { message: string }) => {
|
||||
this.reportMessage = response.message
|
||||
this.reportSent = true
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,59 @@
|
||||
<div id="sidebarCard" *ngIf="selectedChart" class="ui fluid card">
|
||||
<div class="ui placeholder">
|
||||
<div class="flex h-full flex-col" *ngIf="selectedChart">
|
||||
<div class="relative">
|
||||
@if (albumArtMd5) {
|
||||
<img src="https://files.enchor.us/{{ albumArtMd5 }}.jpg" alt="Album art" loading="lazy" class="object-cover w-40" />
|
||||
@if (hasIcons && icon) {
|
||||
<div class="tooltip absolute bottom-3 left-3" [attr.data-tip]="iconTooltip">
|
||||
<img
|
||||
class="w-16"
|
||||
src="https://clonehero.gitlab.io/sources/icons/{{ icon }}"
|
||||
[alt]="selectedChart.icon"
|
||||
(load)="iconLoading = false"
|
||||
(error)="iconLoading = false"
|
||||
[class.hidden]="iconLoading" />
|
||||
</div>
|
||||
}
|
||||
@if (albumLoading) {
|
||||
<div class="skeleton w-full aspect-square"></div>
|
||||
}
|
||||
<img
|
||||
src="https://files.enchor.us/{{ albumArtMd5 }}.jpg"
|
||||
alt="Album art"
|
||||
(load)="albumLoading = false"
|
||||
(error)="albumLoading = false"
|
||||
[class.hidden]="albumLoading" />
|
||||
}
|
||||
</div>
|
||||
<div *ngIf="charts && charts.length > 1" id="chartDropdown" class="ui fluid right labeled scrolling icon dropdown button">
|
||||
<input type="hidden" name="Chart" />
|
||||
<i id="chartDropdownIcon" class="dropdown icon"></i>
|
||||
<div class="default text"></div>
|
||||
<div id="chartDropdownMenu" class="menu"></div>
|
||||
</div>
|
||||
<div id="textPanel" class="content">
|
||||
<span class="header">{{ selectedChart.chartName }}</span>
|
||||
<div class="description">
|
||||
<div *ngIf="selectedChart.chartAlbum"><b>Album:</b> {{ selectedChart.chartAlbum }}</div>
|
||||
<div *ngIf="selectedChart.chartGenre"><b>Genre:</b> {{ selectedChart.chartGenre }}</div>
|
||||
<div *ngIf="selectedChart.chartYear"><b>Year:</b> {{ selectedChart.chartYear }}</div>
|
||||
<div><b>Charter:</b> {{ selectedChart.charter }}</div>
|
||||
<div><b>Audio Length:</b> {{ songLength }}</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui horizontal list">
|
||||
<div
|
||||
class="flex flex-1 p-2 gap-1 justify-between overflow-x-hidden overflow-y-auto scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-track-base-300 scrollbar-thumb-neutral scrollbar-thumb-rounded-full">
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<div>
|
||||
<!-- TODO: Change this to a dropdown if there is more than one chart -->
|
||||
<b>Charter: </b>
|
||||
<a class="link link-hover" (click)="onSourceLinkClicked()">{{ selectedChart.charter | removeStyleTags }}</a>
|
||||
</div>
|
||||
<div class="whitespace-nowrap">
|
||||
<span class="font-bold">Length:</span>
|
||||
{{ effectiveLength }} (+{{ extraLengthSeconds }}s)
|
||||
<div
|
||||
class="tooltip tooltip-bottom cursor-help [text-wrap:balance]"
|
||||
data-tip="The time between the first and last note. The second value is the extra time at the start and end of the song without notes.">
|
||||
<i class="bi bi-info-circle text-xs"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="flex-1">
|
||||
@for (pair of boolProperties; track $index) {
|
||||
<p class="flex items-center">
|
||||
<i class="bi text-2xl -my-3" [ngClass]="pair.value ? 'bi-check2' : 'bi-x'" [ngStyle]="{ color: pair.value ? 'green' : 'red' }"> </i>
|
||||
<span class="ml-1 whitespace-nowrap" [class.font-bold]="pair.value">{{ pair.text }}</span>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col gap-1">
|
||||
<div
|
||||
class="flex flex-wrap gap-1 max-h-40 min-h-[44px] max-w-[234px] overflow-y-auto scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-track-base-300 scrollbar-thumb-neutral scrollbar-thumb-rounded-full">
|
||||
@if (selectedChart.notesData.hasVocals) {
|
||||
<app-chart-sidebar-instrument [chart]="selectedChart" instrument="vocals" />
|
||||
}
|
||||
@@ -27,18 +61,86 @@
|
||||
<app-chart-sidebar-instrument [chart]="selectedChart" [instrument]="instrument" />
|
||||
}
|
||||
</div>
|
||||
<div id="sourceLinks">
|
||||
<a id="sourceLink" (click)="onSourceLinkClicked()">{{ selectedChart.packName ?? selectedChart.applicationUsername + "'s Chart" }}</a>
|
||||
<button *ngIf="shownFolderButton()" id="folderButton" class="mini ui icon button" (click)="onFolderButtonClicked()">
|
||||
<i class="folder open outline icon"></i>
|
||||
</button>
|
||||
<div class="flex flex-1 flex-col">
|
||||
@if (instruments.length > 1 || difficulties.length > 1) {
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@if (instruments.length > 1) {
|
||||
<select class="select select-bordered select-sm grow-[40]" [formControl]="instrumentDropdown">
|
||||
@for (instrument of instruments; track $index) {
|
||||
<option [value]="instrument">{{ shortInstrumentDisplay(instrument) }}</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
@if (difficulties.length > 1) {
|
||||
<select class="select select-bordered select-sm flex-1" [formControl]="difficultyDropdown">
|
||||
@for (difficulty of difficulties; track $index) {
|
||||
<option [value]="difficulty">{{ difficultyDisplay(difficulty) }}</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<p class="font-bold whitespace-nowrap">Average NPS: {{ averageNps || 'N/A' }}</p>
|
||||
<p class="font-bold whitespace-nowrap">Maximum NPS: {{ maximumNps }}</p>
|
||||
<p class="font-bold whitespace-nowrap">Note Count: {{ noteCount }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="downloadButtons" class="ui positive buttons">
|
||||
<div id="downloadButton" class="ui button" (click)="onDownloadClicked()">Download</div>
|
||||
<div id="versionDropdown" class="ui floating dropdown icon button">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="join">
|
||||
<button class="btn rounded-md flex-1 join-item btn-primary" (click)="onDownloadClicked()">Download</button>
|
||||
<dialog #selectSngModal id="report_modal" class="modal">
|
||||
<div class="modal-box bg-base-100 text-base-content flex flex-col gap-2">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<i class="bi bi-x-lg text-2xl"></i>
|
||||
</button>
|
||||
</form>
|
||||
<h3 class="font-bold text-lg">Select Download Format:</h3>
|
||||
<div class="flex gap-6">
|
||||
<div class="form-control flex-1">
|
||||
<label class="label cursor-pointer justify-normal gap-2">
|
||||
<input type="radio" name="selectedDownloadFormat{{ selectedChart.chartId }}" class="radio" (change)="selectDownloadFormat(true)" />
|
||||
<span>.sng (new)</span>
|
||||
</label>
|
||||
<ul class="list-disc pl-5">
|
||||
<li>Single chart file</li>
|
||||
<li>Can be scanned in-game directly without extracting</li>
|
||||
<li>Currently only supported by YARG and Clone Hero v1.1</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-control flex-1">
|
||||
<label class="label cursor-pointer justify-normal gap-2">
|
||||
<input type="radio" name="selectedDownloadFormat{{ selectedChart.chartId }}" class="radio" (change)="selectDownloadFormat(false)" />
|
||||
<span>.zip</span>
|
||||
</label>
|
||||
<ul class="list-disc pl-5">
|
||||
<li>Contains chart folder</li>
|
||||
<li>Must be extracted before it can be scanned in-game</li>
|
||||
<li>Supported across many games</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="text-xs">This can be changed later in the (<i class="bi bi-three-dots align-middle"></i>) menu.</div>
|
||||
<div class="text-xs">
|
||||
A program to convert between .sng files and chart folders can be found
|
||||
<a class="link" href="https://github.com/mdsitton/SngFileFormat/releases" target="_blank">here</a>.
|
||||
</div>
|
||||
<div class="form-control flex-row justify-end">
|
||||
<button class="btn btn-primary" [disabled]="!hasSelectedDownloadFormat" (click)="onDownloadClicked()">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<div
|
||||
#menu
|
||||
class="cursor-pointer bg-neutral rounded-md join-item dropdown dropdown-top dropdown-end p-1 flex items-center"
|
||||
(click)="showMenu()"
|
||||
[class.dropdown-open]="menuVisible">
|
||||
<i class="bi bi-three-dots text-2xl px-1 text-neutral-content"></i>
|
||||
<app-chart-sidebar-menu [chartVersions]="charts![0]" (selectedVersionChanges)="selectedChart = $event" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.ui.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.ui.placeholder {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
|
||||
#textPanel {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#versionDropdown, #folderButton {
|
||||
max-width: min-content;
|
||||
}
|
||||
|
||||
#chartDropdown {
|
||||
min-height: min-content;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
::ng-deep #chartDropdownMenu > div.item {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#sourceLinks {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#sourceLink {
|
||||
align-self: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#chartDropdownIcon, #versionDropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ui.horizontal.list>.item {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.ui.divider {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
#downloadButton {
|
||||
width: min-content;
|
||||
margin: 0px 1px 1px 1px;
|
||||
}
|
||||
|
||||
#versionDropdown {
|
||||
margin: 0px 1px 1px -3px;
|
||||
}
|
||||
@@ -1,32 +1,43 @@
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { Component, ElementRef, HostBinding, OnInit, Renderer2, ViewChild } from '@angular/core'
|
||||
import { FormControl } from '@angular/forms'
|
||||
|
||||
import { chain, flatMap, sortBy } from 'lodash'
|
||||
import { Instrument } from 'scan-chart'
|
||||
import { chain, compact, flatMap, intersection, round, sortBy } from 'lodash'
|
||||
import { Difficulty, Instrument } from 'scan-chart'
|
||||
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'
|
||||
import { driveLink, instruments } from 'src-shared/UtilFunctions'
|
||||
|
||||
interface Difficulty {
|
||||
instrument: string
|
||||
diffNumber: string
|
||||
chartedDifficulties: string
|
||||
}
|
||||
import { setlistNames } from 'src-shared/setlist-names'
|
||||
import { difficulties, difficultyDisplay, driveLink, instruments, msToRoughTime, removeStyleTags, shortInstrumentDisplay } from 'src-shared/UtilFunctions'
|
||||
|
||||
@Component({
|
||||
selector: 'app-chart-sidebar',
|
||||
templateUrl: './chart-sidebar.component.html',
|
||||
styleUrls: ['./chart-sidebar.component.scss'],
|
||||
})
|
||||
export class ChartSidebarComponent implements OnInit {
|
||||
@HostBinding('class.contents') contents = true
|
||||
|
||||
@ViewChild('menu') menu: ElementRef
|
||||
@ViewChild('selectSngModal') selectSngModal: ElementRef<HTMLDialogElement>
|
||||
|
||||
public shortInstrumentDisplay = shortInstrumentDisplay
|
||||
public difficultyDisplay = difficultyDisplay
|
||||
private guitarlikeInstruments: Instrument[] = [
|
||||
'guitar', 'guitarcoop', 'rhythm', 'bass', 'keys', 'guitarghl', 'guitarcoopghl', 'rhythmghl', 'bassghl',
|
||||
]
|
||||
private unlisten?: () => void
|
||||
|
||||
albumLoading = true
|
||||
iconLoading = true
|
||||
public menuVisible = false
|
||||
|
||||
selectedChart: ChartData | null = null
|
||||
charts: ChartData[][] | null = null
|
||||
|
||||
songLength: string
|
||||
difficultiesList: Difficulty[]
|
||||
public instrumentDropdown: FormControl<Instrument | null>
|
||||
public difficultyDropdown: FormControl<Difficulty | null>
|
||||
|
||||
constructor(
|
||||
private renderer: Renderer2,
|
||||
private searchService: SearchService,
|
||||
public settingsService: SettingsService
|
||||
) { }
|
||||
@@ -36,12 +47,67 @@ export class ChartSidebarComponent implements OnInit {
|
||||
this.charts = null
|
||||
this.selectedChart = null
|
||||
})
|
||||
this.instrumentDropdown = new FormControl<Instrument | null>(this.defaultInstrument)
|
||||
this.searchService.instrument.valueChanges.subscribe(instrument => {
|
||||
if (this.instruments.some(i => i === instrument)) {
|
||||
this.instrumentDropdown.setValue(instrument)
|
||||
}
|
||||
})
|
||||
this.instrumentDropdown.valueChanges.subscribe(() => {
|
||||
if (!this.difficulties.some(d => d === this.difficultyDropdown.value)) {
|
||||
this.difficultyDropdown.setValue(this.defaultDifficulty)
|
||||
}
|
||||
})
|
||||
this.difficultyDropdown = new FormControl<Difficulty | null>(this.defaultDifficulty)
|
||||
this.searchService.difficulty.valueChanges.subscribe(difficulty => {
|
||||
if (this.difficulties.some(d => d === difficulty)) {
|
||||
this.difficultyDropdown.setValue(difficulty)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public get albumArtMd5() {
|
||||
return flatMap(this.charts ?? []).find(c => !!c.albumArtMd5)?.albumArtMd5 || null
|
||||
}
|
||||
|
||||
public get hasIcons() { return !!this.searchService.availableIcons }
|
||||
public get icon() {
|
||||
const iconName = this.selectedChart!.icon || removeStyleTags(this.selectedChart!.charter ?? 'N/A').toLowerCase() + '.'
|
||||
if (iconName === 'unknown charter') { return null }
|
||||
return this.searchService.availableIcons?.find(i => i.toLowerCase().startsWith(iconName)) || null
|
||||
}
|
||||
|
||||
public get iconTooltip() {
|
||||
if (!this.selectedChart!.icon) {
|
||||
return null
|
||||
}
|
||||
return setlistNames[this.selectedChart!.icon] ?? null
|
||||
}
|
||||
|
||||
public get effectiveLength() {
|
||||
return msToRoughTime(this.selectedChart!.notesData.effectiveLength)
|
||||
}
|
||||
|
||||
public get extraLengthSeconds() {
|
||||
return round((this.selectedChart!.notesData.length - this.selectedChart!.notesData.effectiveLength) / 1000, 1)
|
||||
}
|
||||
|
||||
public get boolProperties(): ({ value: boolean; text: string })[] {
|
||||
const notesData = this.selectedChart!.notesData
|
||||
const showGuitarlikeProperties = intersection(this.instruments, this.guitarlikeInstruments).length > 0
|
||||
const showDrumlikeProperties = intersection(this.instruments, ['drums']).length > 0
|
||||
return compact([
|
||||
showGuitarlikeProperties ? { value: notesData.hasSoloSections, text: 'Solo Sections' } : null,
|
||||
{ value: notesData.hasLyrics, text: 'Lyrics' },
|
||||
showGuitarlikeProperties ? { value: notesData.hasForcedNotes, text: 'Forced Notes' } : null,
|
||||
showGuitarlikeProperties ? { value: notesData.hasTapNotes, text: 'Tap Notes' } : null,
|
||||
showGuitarlikeProperties ? { value: notesData.hasOpenNotes, text: 'Open Notes' } : null,
|
||||
showDrumlikeProperties ? { value: notesData.has2xKick, text: '2x Kick' } : null,
|
||||
showDrumlikeProperties ? { value: notesData.hasRollLanes, text: 'Roll Lanes' } : null,
|
||||
{ value: this.selectedChart!.hasVideoBackground, text: 'Video Background' },
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the information for the selected song.
|
||||
*/
|
||||
@@ -51,7 +117,15 @@ export class ChartSidebarComponent implements OnInit {
|
||||
.values()
|
||||
.map(versionGroup => sortBy(versionGroup, vg => vg.modifiedTime).reverse())
|
||||
.value()
|
||||
if (this.selectedChart?.albumArtMd5 !== this.charts[0][0].albumArtMd5) {
|
||||
this.albumLoading = true
|
||||
}
|
||||
if ((this.selectedChart?.icon || this.selectedChart?.charter) !== (this.charts[0][0].icon || this.charts[0][0].charter)) {
|
||||
this.iconLoading = true
|
||||
}
|
||||
this.selectedChart = this.charts[0][0]
|
||||
this.instrumentDropdown.setValue(this.defaultInstrument)
|
||||
this.difficultyDropdown.setValue(this.defaultDifficulty)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,18 +135,60 @@ export class ChartSidebarComponent implements OnInit {
|
||||
window.electron.emit.openUrl(driveLink(this.selectedChart!.applicationDriveId))
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if the source folder button should be shown.
|
||||
*/
|
||||
shownFolderButton() {
|
||||
return this.selectedChart!.applicationDriveId !== this.selectedChart!.parentFolderId
|
||||
public get defaultInstrument() {
|
||||
return this.instruments.some(i => i === this.searchService.instrument.value)
|
||||
? this.searchService.instrument.value!
|
||||
: this.instruments[0]
|
||||
}
|
||||
public get instruments(): Instrument[] {
|
||||
if (!this.selectedChart) { return [] }
|
||||
return chain(this.selectedChart.notesData.noteCounts)
|
||||
.map(nc => nc.instrument)
|
||||
.uniq()
|
||||
.sortBy(i => instruments.indexOf(i))
|
||||
.value()
|
||||
}
|
||||
public get defaultDifficulty() {
|
||||
return this.difficulties.some(d => d === this.searchService.difficulty.value)
|
||||
? this.searchService.difficulty.value!
|
||||
: this.difficulties[0]
|
||||
}
|
||||
public get difficulties(): Difficulty[] {
|
||||
if (!this.selectedChart) { return [] }
|
||||
return chain(this.selectedChart.notesData.noteCounts)
|
||||
.filter(nc => nc.instrument === this.instrumentDropdown.value && nc.count > 0)
|
||||
.map(nc => nc.difficulty)
|
||||
.sortBy(d => difficulties.indexOf(d))
|
||||
.value()
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the chart folder in the default browser.
|
||||
*/
|
||||
onFolderButtonClicked() {
|
||||
window.electron.emit.openUrl(driveLink(this.selectedChart!.parentFolderId))
|
||||
public get averageNps() {
|
||||
if (this.noteCount < 2) {
|
||||
return 0
|
||||
} else {
|
||||
return round(this.noteCount / (this.selectedChart!.notesData.effectiveLength / 1000), 1)
|
||||
}
|
||||
}
|
||||
|
||||
private currentTrackFilter = (track: { instrument: Instrument; difficulty: Difficulty }) => {
|
||||
return track.instrument === this.instrumentDropdown.value && track.difficulty === this.difficultyDropdown.value
|
||||
}
|
||||
public get maximumNps() {
|
||||
return this.selectedChart!.notesData.maxNps.filter(this.currentTrackFilter)[0].nps
|
||||
}
|
||||
|
||||
public get noteCount() {
|
||||
return this.selectedChart!.notesData.noteCounts.filter(this.currentTrackFilter)[0].count
|
||||
}
|
||||
|
||||
public get hasSelectedDownloadFormat() {
|
||||
// TODO
|
||||
return localStorage.getItem('selectedDownloadFormat') === 'true'
|
||||
}
|
||||
public selectDownloadFormat(isSng: boolean) {
|
||||
// TODO
|
||||
this.searchService.isSng.setValue(isSng)
|
||||
localStorage.setItem('selectedDownloadFormat', 'true')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,6 +196,12 @@ export class ChartSidebarComponent implements OnInit {
|
||||
*/
|
||||
onDownloadClicked() {
|
||||
// TODO
|
||||
if (!this.hasSelectedDownloadFormat) {
|
||||
this.selectSngModal.nativeElement.showModal()
|
||||
return
|
||||
} else {
|
||||
this.selectSngModal.nativeElement.close()
|
||||
}
|
||||
// this.downloadService.addDownload(
|
||||
// this.selectedChart.versionID, {
|
||||
// chartName: this.selectedChart.chartName,
|
||||
@@ -89,12 +211,16 @@ export class ChartSidebarComponent implements OnInit {
|
||||
// })
|
||||
}
|
||||
|
||||
public get instruments(): Instrument[] {
|
||||
if (!this.selectedChart) { return [] }
|
||||
return chain(this.selectedChart.notesData.noteCounts)
|
||||
.map(nc => nc.instrument)
|
||||
.uniq()
|
||||
.sortBy(i => instruments.indexOf(i))
|
||||
.value()
|
||||
public showMenu() {
|
||||
this.menuVisible = true
|
||||
this.unlisten = this.renderer.listen('window', 'click', (e: Event) => {
|
||||
if (this.menuVisible && !(this.menu.nativeElement as HTMLElement).contains(e.target as HTMLElement)) {
|
||||
this.menuVisible = false
|
||||
if (this.unlisten) {
|
||||
this.unlisten()
|
||||
this.unlisten = undefined
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user