diff --git a/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.html b/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.html index 010397d..144e770 100644 --- a/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.html +++ b/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.html @@ -42,6 +42,40 @@
+ @if (hasIssues) { + + } + @if (selectedChart.modchart) { +
Modchart
+ } @for (pair of boolProperties; track $index) {

diff --git a/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.ts b/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.ts index 87b1671..a038fb4 100644 --- a/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.ts +++ b/src-angular/app/components/browse/chart-sidebar/chart-sidebar.component.ts @@ -1,13 +1,13 @@ import { Component, ElementRef, HostBinding, OnInit, Renderer2, ViewChild } from '@angular/core' import { FormControl } from '@angular/forms' -import { chain, compact, flatMap, intersection, round, sortBy } from 'lodash' -import { Difficulty, Instrument } from 'scan-chart' +import { capitalize, chain, compact, flatMap, intersection, round, sortBy } from 'lodash' +import { ChartIssueType, Difficulty, FolderIssueType, Instrument, MetadataIssueType, NoteIssueType, TrackIssueType } 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 { setlistNames } from 'src-shared/setlist-names' -import { difficulties, difficultyDisplay, driveLink, instruments, msToRoughTime, removeStyleTags, shortInstrumentDisplay } from 'src-shared/UtilFunctions' +import { difficulties, difficultyDisplay, driveLink, hasIssues, instruments, msToRoughTime, removeStyleTags, shortInstrumentDisplay } from 'src-shared/UtilFunctions' @Component({ selector: 'app-chart-sidebar', @@ -91,6 +91,103 @@ export class ChartSidebarComponent implements OnInit { return round((this.selectedChart!.notesData.length - this.selectedChart!.notesData.effectiveLength) / 1000, 1) } + public get hasIssues() { + return hasIssues(this.selectedChart!) + } + + public get metadataIssues() { + return this.selectedChart!.metadataIssues + } + public getMetadataIssueText(issue: MetadataIssueType) { + switch (issue) { + case 'noName': return 'Chart has no name' + case 'noArtist': return 'Chart has no artist' + case 'noAlbum': return 'Chart has no album' + case 'noGenre': return 'Chart has no genre' + case 'noYear': return 'Chart has no year' + case 'noCharter': return 'Chart has no charter' + case 'missingInstrumentDiff': return 'Metadata is missing an instrument intensity rating' + case 'extraInstrumentDiff': return 'Metadata contains an instrument intensity rating for an uncharted instrument' + case 'nonzeroDelay': return 'Chart uses "delay" for the audio offset' + case 'drumsSetTo4And5Lane': return 'It is unclear if the drums chart is intended to be 4-lane or 5-lane' + case 'nonzeroOffset': return 'Chart uses "delay" for the audio offset' + } + } + public get folderIssues() { + return chain(this.selectedChart!.folderIssues) + .filter(i => !['albumArtSize', 'invalidIni', 'multipleVideo', 'badIniLine'].includes(i.folderIssue)) + .map(i => i.folderIssue) + .uniq() + .value() + } + public getFolderIssueText(folderIssue: FolderIssueType) { + switch (folderIssue) { + case 'noMetadata': return `Metadata file is missing` + case 'invalidMetadata': return `Metadata file is invalid` + case 'multipleIniFiles': return `Multiple metadata files` + case 'noAlbumArt': return `Album art is missing` + case 'badAlbumArt': return `Album art is invalid` + case 'multipleAlbumArt': return `Multiple album art files` + case 'noAudio': return `Audio file is missing` + case 'invalidAudio': return `Audio file is invalid` + case 'badAudio': return `Audio file is invalid` + case 'multipleAudio': return `Audio file is invalid` + case 'noChart': return `Notes file is missing` + case 'invalidChart': return `Notes file is invalid` + case 'badChart': return `Notes file is invalid` + case 'multipleChart': return `Multiple notes files` + case 'badVideo': return `Video background won't work on Linux` + } + } + + public get chartIssues() { + return this.selectedChart!.notesData?.chartIssues.filter(i => i !== 'isDefaultBPM') + } + public getChartIssueText(issue: ChartIssueType) { + switch (issue) { + case 'noResolution': return 'No resolution in chart file' + case 'noSyncTrackSection': return 'No tempo map in chart file' + case 'noNotes': return 'No notes in chart file' + case 'noExpert': return 'Expert is not charted' + case 'misalignedTimeSignatures': return 'Broken time signatures' + case 'noSections': return 'No sections' + } + } + + public get trackIssuesGroups() { + return chain([ + ...this.selectedChart!.notesData.trackIssues.map(i => ({ ...i, issues: i.trackIssues })), + ...this.selectedChart!.notesData.noteIssues.map(i => ({ ...i, issues: i.noteIssues.map(ni => ni.issueType) })), + ]) + .sortBy(g => instruments.indexOf(g.instrument), g => difficulties.indexOf(g.difficulty)) + .groupBy(g => `${capitalize(g.instrument)} - ${capitalize(g.difficulty)} Issues Found:`) + .toPairs() + .map(([groupName, group]) => ({ + groupName, + issues: chain(group) + .flatMap(g => g.issues) + .filter(i => i !== 'babySustain' && i !== 'noNotesOnNonemptyTrack') + .uniq() + .value(), + })) + .value() + } + + public getTrackIssueText(issue: NoteIssueType | TrackIssueType) { + switch (issue) { + case 'babySustain': return 'Has baby sustains' + case 'badSustainGap': return 'Has sustain gaps that are too small' + case 'brokenNote': return 'Has broken notes' + case 'difficultyForbiddenNote': return 'Has notes not allowed on this difficulty' + case 'fiveNoteChord': return 'Has five-note chords' + case 'noDrumActivationLanes': return 'Has no activation lanes' + case 'has4And5LaneFeatures': return 'Has a mix of 4 and 5 lane features on the drum chart' + case 'noStarPower': return 'Has no star power' + case 'smallLeadingSilence': return 'Leading silence is too small' + case 'threeNoteDrumChord': return 'Has three-note drum chords' + } + } + public get boolProperties(): ({ value: boolean; text: string })[] { const notesData = this.selectedChart!.notesData const showGuitarlikeProperties = intersection(this.instruments, this.guitarlikeInstruments).length > 0 diff --git a/src-angular/styles.scss b/src-angular/styles.scss index b00c78a..7cdd07b 100644 --- a/src-angular/styles.scss +++ b/src-angular/styles.scss @@ -13,3 +13,7 @@ input[type='number'] { user-select: none; -webkit-user-drag: none; } + +.dropdown:not(:hover):not(:focus):not(:focus-within):not(.dropdown-open) .dropdown-content { + height: 0px; +} diff --git a/src-shared/UtilFunctions.ts b/src-shared/UtilFunctions.ts index e03c9d2..bac6d06 100644 --- a/src-shared/UtilFunctions.ts +++ b/src-shared/UtilFunctions.ts @@ -2,6 +2,8 @@ import _ from 'lodash' import sanitize from 'sanitize-filename' import { Difficulty, Instrument } from 'scan-chart' +import { ChartData } from './interfaces/search.interface' + // WARNING: do not import anything related to Electron; the code will not compile correctly. // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -166,3 +168,25 @@ export function removeStyleTags(text: string) { } while (newText !== oldText) return newText } + +export function hasIssues(chart: Pick) { + if (chart.metadataIssues.length > 0) { return true } + for (const folderIssue of chart.folderIssues) { + if (!['albumArtSize', 'invalidIni', 'multipleVideo', 'badIniLine'].includes(folderIssue.folderIssue)) { return true } + } + for (const chartIssue of chart.notesData?.chartIssues ?? []) { + if (chartIssue !== 'isDefaultBPM') { return true } + } + for (const trackIssue of chart.notesData?.trackIssues ?? []) { + for (const ti of trackIssue.trackIssues) { + if (ti !== 'noNotesOnNonemptyTrack') { return true } + } + } + for (const noteIssue of chart.notesData?.noteIssues ?? []) { + for (const ni of noteIssue.noteIssues) { + if (ni.issueType !== 'babySustain') { return true } + } + } + + return false +}