diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.html b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.html index 487f65e02f55f..5b77a5cec3e29 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.html +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.html @@ -23,7 +23,10 @@
View - + @for (groupOption of viewAsOptions; track groupOption.text) { @if (groupOption.options.length > 1) { diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.ts index e5f703796ce35..ba6e503cb98e7 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/content-viewer/feature/content-viewer.component.ts @@ -22,7 +22,7 @@ import { loadContentViewerOptions, resetContentViewerOptions } from '../state/vi import { FormBuilder, FormGroup } from '@angular/forms'; import { selectBundledViewerOptions, selectViewerOptions } from '../state/viewer-options/viewer-options.selectors'; import { ContentViewer, HEX_VIEWER_URL, SupportedMimeTypes } from '../state/viewer-options'; -import { isDefinedAndNotNull, SelectGroup, SelectOption, selectQueryParams } from '@nifi/shared'; +import { isDefinedAndNotNull, NiFiCommon, SelectGroup, SelectOption, selectQueryParams } from '@nifi/shared'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { concatLatestFrom } from '@ngrx/operators'; import { navigateToBundledContentViewer, resetContent, setRef } from '../state/content/content.actions'; @@ -31,6 +31,12 @@ import { loadAbout } from '../../../state/about/about.actions'; import { selectAbout } from '../../../state/about/about.selectors'; import { filter, map, switchMap, take } from 'rxjs'; import { navigateToExternalViewer } from '../state/external-viewer/external-viewer.actions'; +import { snackBarError } from '../../../state/error'; + +interface SupportedContentViewer { + supportedMimeTypes: SupportedMimeTypes; + contentViewer: ContentViewer; +} @Component({ selector: 'content-viewer', @@ -40,17 +46,19 @@ import { navigateToExternalViewer } from '../state/external-viewer/external-view export class ContentViewerComponent implements OnInit, OnDestroy { viewerForm: FormGroup; viewAsOptions: SelectGroup[] = []; + panelWidth: string | null = 'auto'; private supportedMimeTypeId = 0; - private supportedMimeTypeLookup: Map = new Map(); - private supportedMimeTypeContentViewerLookup: Map = new Map(); - private mimeTypeDisplayNameLookup: Map = new Map(); + private supportedContentViewerLookup: Map = new Map< + number, + SupportedContentViewer + >(); private mimeTypeIdsSupportedByBundledUis: Set = new Set(); private defaultSupportedMimeTypeId: number | null = null; viewerSelected: boolean = false; - private mimeType: string | undefined; + mimeType: string | undefined; private clientId: string | undefined; private queryParamsLoaded = false; @@ -58,6 +66,7 @@ export class ContentViewerComponent implements OnInit, OnDestroy { constructor( private store: Store, + private nifiCommon: NiFiCommon, private formBuilder: FormBuilder ) { this.viewerForm = this.formBuilder.group({ viewAs: null }); @@ -70,10 +79,8 @@ export class ContentViewerComponent implements OnInit, OnDestroy { takeUntilDestroyed() ) .subscribe(([externalViewerOptions, bundledViewerOptions]) => { - this.supportedMimeTypeLookup.clear(); - this.supportedMimeTypeContentViewerLookup.clear(); + this.supportedContentViewerLookup.clear(); this.mimeTypeIdsSupportedByBundledUis.clear(); - this.mimeTypeDisplayNameLookup.clear(); // maps a given content (by display name) to the supported mime type id // which can be used to look up the corresponding content viewer @@ -81,54 +88,69 @@ export class ContentViewerComponent implements OnInit, OnDestroy { // process all external viewer options externalViewerOptions.forEach((contentViewer) => { - contentViewer.supportedMimeTypes.forEach((supportedMimeType) => { + contentViewer.supportedMimeTypes.forEach((supportedMimeTypes) => { const supportedMimeTypeId = this.supportedMimeTypeId++; - if (!supportedMimeTypeMapping.has(supportedMimeType.displayName)) { - supportedMimeTypeMapping.set(supportedMimeType.displayName, []); + if (!supportedMimeTypeMapping.has(supportedMimeTypes.displayName)) { + supportedMimeTypeMapping.set(supportedMimeTypes.displayName, []); } - supportedMimeTypeMapping.get(supportedMimeType.displayName)?.push(supportedMimeTypeId); + supportedMimeTypeMapping.get(supportedMimeTypes.displayName)?.push(supportedMimeTypeId); - this.supportedMimeTypeLookup.set(supportedMimeTypeId, supportedMimeType); - this.supportedMimeTypeContentViewerLookup.set(supportedMimeTypeId, contentViewer); + this.supportedContentViewerLookup.set(supportedMimeTypeId, { + supportedMimeTypes, + contentViewer + }); }); }); // process all bundled options bundledViewerOptions.forEach((contentViewer) => { - contentViewer.supportedMimeTypes.forEach((supportedMimeType) => { + contentViewer.supportedMimeTypes.forEach((supportedMimeTypes) => { const supportedMimeTypeId = this.supportedMimeTypeId++; if (contentViewer.uri === HEX_VIEWER_URL) { this.defaultSupportedMimeTypeId = supportedMimeTypeId; } - if (!supportedMimeTypeMapping.has(supportedMimeType.displayName)) { - supportedMimeTypeMapping.set(supportedMimeType.displayName, []); + if (!supportedMimeTypeMapping.has(supportedMimeTypes.displayName)) { + supportedMimeTypeMapping.set(supportedMimeTypes.displayName, []); } - supportedMimeTypeMapping.get(supportedMimeType.displayName)?.push(supportedMimeTypeId); + supportedMimeTypeMapping.get(supportedMimeTypes.displayName)?.push(supportedMimeTypeId); this.mimeTypeIdsSupportedByBundledUis.add(supportedMimeTypeId); - this.supportedMimeTypeLookup.set(supportedMimeTypeId, supportedMimeType); - this.supportedMimeTypeContentViewerLookup.set(supportedMimeTypeId, contentViewer); + this.supportedContentViewerLookup.set(supportedMimeTypeId, { + supportedMimeTypes, + contentViewer + }); }); }); const newViewAsOptions: SelectGroup[] = []; supportedMimeTypeMapping.forEach((contentViewers, displayName) => { const options: SelectOption[] = []; - contentViewers.forEach((contentViewerId) => { - this.mimeTypeDisplayNameLookup.set(contentViewerId, displayName); - - const contentViewer = this.supportedMimeTypeContentViewerLookup.get(contentViewerId); - if (contentViewer) { + contentViewers.forEach((supportedMimeTypeId) => { + const supportedContentViewer = this.supportedContentViewerLookup.get(supportedMimeTypeId); + if (supportedContentViewer) { const option: SelectOption = { - text: contentViewer.displayName, - value: String(contentViewerId) + text: supportedContentViewer.contentViewer.displayName, + value: String(supportedMimeTypeId) }; options.push(option); } }); + + // if there is more than one content viewer for this mime type we sent the panel + // width to null which will allow the select menu to expand to the width of + // the content which will be a lengthy nar version string + if (options.length > 1) { + this.panelWidth = null; + } + + // sort by option text + options.sort((a, b) => { + return this.nifiCommon.compareString(a.text, b.text); + }); + const groupOption: SelectGroup = { text: displayName, options @@ -136,6 +158,11 @@ export class ContentViewerComponent implements OnInit, OnDestroy { newViewAsOptions.push(groupOption); }); + // sort by option text + newViewAsOptions.sort((a, b) => { + return this.nifiCommon.compareString(a.text, b.text); + }); + this.viewAsOptions = newViewAsOptions; this.viewerOptionsLoaded = true; @@ -197,16 +224,22 @@ export class ContentViewerComponent implements OnInit, OnDestroy { if (!this.viewerSelected) { if (this.mimeType) { const compatibleViewerOption = this.getCompatibleViewer(this.mimeType); - if (Number.isNaN(compatibleViewerOption)) { - if (!Number.isNaN(this.defaultSupportedMimeTypeId)) { + if (compatibleViewerOption === null) { + if (this.defaultSupportedMimeTypeId !== null) { this.viewerForm.get('viewAs')?.setValue(String(this.defaultSupportedMimeTypeId)); this.loadBundledContentViewer(this.defaultSupportedMimeTypeId); } + + this.store.dispatch( + snackBarError({ + error: `No compatible content viewer found for mime type [${this.mimeType}]` + }) + ); } else { this.viewerForm.get('viewAs')?.setValue(String(compatibleViewerOption)); this.loadBundledContentViewer(compatibleViewerOption); } - } else if (!Number.isNaN(this.defaultSupportedMimeTypeId)) { + } else if (this.defaultSupportedMimeTypeId !== null) { this.viewerForm.get('viewAs')?.setValue(String(this.defaultSupportedMimeTypeId)); this.loadBundledContentViewer(this.defaultSupportedMimeTypeId); } @@ -217,10 +250,10 @@ export class ContentViewerComponent implements OnInit, OnDestroy { for (const group of this.viewAsOptions) { for (const option of group.options) { const supportedMimeTypeId: number = Number(option.value); - if (!Number.isNaN(supportedMimeTypeId)) { - const supportedMimeType = this.supportedMimeTypeLookup.get(supportedMimeTypeId); - if (supportedMimeType) { - if (supportedMimeType.mimeTypes.includes(mimeType)) { + if (Number.isInteger(supportedMimeTypeId)) { + const supportedContentViewer = this.supportedContentViewerLookup.get(supportedMimeTypeId); + if (supportedContentViewer) { + if (supportedContentViewer.supportedMimeTypes.mimeTypes.includes(mimeType)) { return supportedMimeTypeId; } } @@ -237,9 +270,11 @@ export class ContentViewerComponent implements OnInit, OnDestroy { loadBundledContentViewer(value: number | null): void { if (value !== null) { - const viewer = this.supportedMimeTypeContentViewerLookup.get(value); + const supportedContentViewer = this.supportedContentViewerLookup.get(value); + + if (supportedContentViewer) { + const viewer = supportedContentViewer.contentViewer; - if (viewer) { this.viewerSelected = true; if (this.mimeTypeIdsSupportedByBundledUis.has(value)) { @@ -249,18 +284,15 @@ export class ContentViewerComponent implements OnInit, OnDestroy { }) ); } else { - const mimeTypeDisplayName = this.mimeTypeDisplayNameLookup.get(value); - if (mimeTypeDisplayName) { - this.store.dispatch( - navigateToExternalViewer({ - request: { - url: viewer.uri, - mimeTypeDisplayName, - clientId: this.clientId - } - }) - ); - } + this.store.dispatch( + navigateToExternalViewer({ + request: { + url: viewer.uri, + mimeTypeDisplayName: supportedContentViewer.supportedMimeTypes.displayName, + clientId: this.clientId + } + }) + ); } } } diff --git a/nifi-frontend/src/main/frontend/apps/standard-content-viewer/src/app/pages/standard-content-viewer/feature/standard-content-viewer.component.ts b/nifi-frontend/src/main/frontend/apps/standard-content-viewer/src/app/pages/standard-content-viewer/feature/standard-content-viewer.component.ts index 517a3308a6a74..8438bbf5f9550 100644 --- a/nifi-frontend/src/main/frontend/apps/standard-content-viewer/src/app/pages/standard-content-viewer/feature/standard-content-viewer.component.ts +++ b/nifi-frontend/src/main/frontend/apps/standard-content-viewer/src/app/pages/standard-content-viewer/feature/standard-content-viewer.component.ts @@ -70,6 +70,8 @@ export class StandardContentViewer { if (this.ref && this.mimeTypeDisplayName) { this.setMode(this.mimeTypeDisplayName); + this.contentLoaded = false; + const formatted: string = this.contentFormGroup.get('formatted')?.value; this.contentViewerService .getContent(this.ref, this.mimeTypeDisplayName, formatted, this.clientId) @@ -110,7 +112,7 @@ export class StandardContentViewer { this.mode = 'application/xml'; break; case 'yaml': - this.mode = 'application/yaml'; + this.mode = 'text/x-yaml'; break; case 'text': this.mode = 'text/plain';