/*! * @license * Copyright 2019 Alfresco Software, Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Component, Input, OnInit, OnChanges, OnDestroy, Optional, EventEmitter, ViewChild, SimpleChanges, Output } from '@angular/core'; import { Location } from '@angular/common'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Params, Router } from '@angular/router'; import { MinimalNodeEntity, NodePaging, Pagination, MinimalNodeEntryEntity, SiteEntry, SearchEntry, NodeEntry } from '@alfresco/js-api'; import { AlfrescoApiService, AuthenticationService, AppConfigService, AppConfigValues, ContentService, FolderCreatedEvent, LogService, NotificationService, UploadService, DataRow, UserPreferencesService, PaginationComponent, FormValues, DisplayMode, ShowHeaderMode, InfinitePaginationComponent, SharedLinksApiService, FormRenderingService, FileUploadEvent, NodesApiService } from '@alfresco/adf-core'; import { DocumentListComponent, PermissionStyleModel, UploadFilesEvent, ConfirmDialogComponent, LibraryDialogComponent, ContentMetadataService, FilterSearch, AspectListService } from '@alfresco/adf-content-services'; import { SelectAppsDialogComponent, ProcessFormRenderingService } from '@alfresco/adf-process-services'; import { VersionManagerDialogAdapterComponent } from './version-manager-dialog-adapter.component'; import { MetadataDialogAdapterComponent } from './metadata-dialog-adapter.component'; import { Subject } from 'rxjs'; import { PreviewService } from '../../services/preview.service'; import { takeUntil, debounceTime, scan } from 'rxjs/operators'; const DEFAULT_FOLDER_TO_SHOW = '-my-'; @Component({ selector: 'app-files-component', templateUrl: './files.component.html', providers: [ { provide: FormRenderingService, useClass: ProcessFormRenderingService } ] }) export class FilesComponent implements OnInit, OnChanges, OnDestroy { protected onDestroy$ = new Subject(); errorMessage: string = null; nodeId: any; showViewer = false; showVersions = false; allowDropFiles = true; displayMode = DisplayMode.List; includeFields = ['isFavorite', 'isLocked', 'aspectNames', 'definition']; baseShareUrl = ( this.appConfig.get(AppConfigValues.BASESHAREURL) || this.appConfig.get(AppConfigValues.ECMHOST)) + '/preview/s/'; toolbarColor = 'default'; selectionModes = [ { value: 'none', viewValue: 'None' }, { value: 'single', viewValue: 'Single' }, { value: 'multiple', viewValue: 'Multiple' } ]; // The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root- @Input() currentFolderId: string = DEFAULT_FOLDER_TO_SHOW; formValues: FormValues = {}; processId; @Input() sorting = ['name', 'ASC']; @Input() sortingMode = 'server'; @Input() showRecentFiles = true; @Input() showSitePicker = true; @Input() showSettingsPanel = true; @Input() showHeader: string = ShowHeaderMode.Always; @Input() selectionMode = 'multiple'; @Input() multiselect = false; @Input() multipleFileUpload = false; @Input() folderUpload = false; @Input() acceptedFilesTypeShow = false; @Input() maxSizeShow = false; @Input() showVersionComments = true; @Input() versioning = false; @Input() allowVersionDownload = true; @Input() acceptedFilesType = '.jpg,.pdf,.js'; @Input() maxFilesSize: number = null; @Input() enableUpload = true; @Input() nodeResult: NodePaging; @Input() pagination: Pagination; @Input() disableDragArea = false; @Input() showNameColumn = true; @Input() searchTerm = ''; @Input() navigationRoute = '/files'; @Input() headerFilters = false; @Input() paramValues: Map = null; @Input() filterSorting: string = null; @Output() documentListReady: EventEmitter = new EventEmitter(); @Output() changedPageSize: EventEmitter = new EventEmitter(); @Output() changedPageNumber: EventEmitter = new EventEmitter(); @Output() turnedNextPage: EventEmitter = new EventEmitter(); @Output() turnedPreviousPage: EventEmitter = new EventEmitter(); @Output() loadNext: EventEmitter = new EventEmitter(); @Output() deleteElementSuccess: EventEmitter = new EventEmitter(); @ViewChild('documentList', { static: true }) documentList: DocumentListComponent; @ViewChild('standardPagination') standardPagination: PaginationComponent; @ViewChild(InfinitePaginationComponent, { static: true }) infinitePaginationComponent: InfinitePaginationComponent; permissionsStyle: PermissionStyleModel[] = []; infiniteScrolling: boolean; stickyHeader: boolean; preselectNodes: boolean; warnOnMultipleUploads = false; thumbnails = false; enableCustomPermissionMessage = false; enableMediumTimeFormat = false; displayEmptyMetadata = false; hyperlinkNavigation = false; selectedNodes = []; constructor(private notificationService: NotificationService, private uploadService: UploadService, private contentService: ContentService, private dialog: MatDialog, private location: Location, private router: Router, private logService: LogService, private appConfig: AppConfigService, private preference: UserPreferencesService, private preview: PreviewService, @Optional() private route: ActivatedRoute, public authenticationService: AuthenticationService, public alfrescoApiService: AlfrescoApiService, private contentMetadataService: ContentMetadataService, private sharedLinksApiService: SharedLinksApiService, private aspectListService: AspectListService, private nodeService: NodesApiService) { } showFile(event) { const entry = event.value.entry; if (entry && entry.isFile) { this.preview.showResource(entry.id); } } toggleFolder() { this.multipleFileUpload = false; this.folderUpload = !this.folderUpload; return this.folderUpload; } toggleThumbnails() { this.thumbnails = !this.thumbnails; this.documentList.reload(); } toggleAllowDropFiles() { this.allowDropFiles = !this.allowDropFiles; this.documentList.reload(); } ngOnInit() { if (!this.pagination) { this.pagination = { maxItems: this.preference.paginationSize, skipCount: 0 }; } if (this.route) { this.route.params.forEach((params: Params) => { if (params['id'] && this.currentFolderId !== params['id']) { this.currentFolderId = params['id']; } if (params['mode'] && params['mode'] === DisplayMode.Gallery) { this.displayMode = DisplayMode.Gallery; } }); } this.uploadService.fileUploadComplete .pipe( debounceTime(300), scan((files, currentFile) => [...files, currentFile], []), takeUntil(this.onDestroy$) ) .subscribe((value: any[]) => { let selectedNodes: NodeEntry[] = []; if (this.preselectNodes) { if (value && value.length > 0 ) { if (this.selectionMode === 'single') { selectedNodes = [...[value[value.length - 1]].map((uploadedFile) => uploadedFile.data)]; } else { selectedNodes = [...value.map((uploadedFile) => uploadedFile.data)]; } this.selectedNodes = [...selectedNodes]; } } this.onFileUploadEvent(value[0]); }); this.uploadService.fileUploadDeleted .pipe(takeUntil(this.onDestroy$)) .subscribe(value => this.onFileUploadEvent(value)); this.contentService.folderCreated .pipe(takeUntil(this.onDestroy$)) .subscribe(value => this.onFolderCreated(value)); this.contentService.folderCreate .pipe(takeUntil(this.onDestroy$)) .subscribe(value => this.onFolderAction(value)); this.contentService.folderEdit .pipe(takeUntil(this.onDestroy$)) .subscribe(value => this.onFolderAction(value)); this.contentMetadataService.error .pipe(takeUntil(this.onDestroy$)) .subscribe((err: { message: string }) => { this.notificationService.showError(err.message); }); this.sharedLinksApiService.error .pipe(takeUntil(this.onDestroy$)) .subscribe((err: { message: string }) => { this.notificationService.showError(err.message); }); } onFileUploadEvent(event: FileUploadEvent) { if (event && event.file.options.parentId === this.documentList.currentFolderId) { this.documentList.reload(); } } ngOnDestroy() { this.onDestroy$.next(true); this.onDestroy$.complete(); } ngOnChanges(changes: SimpleChanges) { if (changes.nodeResult && changes.nodeResult.currentValue) { this.nodeResult = changes.nodeResult.currentValue; this.pagination = this.nodeResult.list.pagination; } if (!this.pagination) { this.giveDefaultPaginationWhenNotDefined(); } } giveDefaultPaginationWhenNotDefined() { this.pagination = { maxItems: this.preference.paginationSize, skipCount: 0, totalItems: 0, hasMoreItems: false }; } getCurrentDocumentListNode(): MinimalNodeEntity[] { if (this.documentList.folderNode) { return [{ entry: this.documentList.folderNode }]; } else { return []; } } onNavigationError(error: any) { if (error) { this.router.navigate(['/error', error.status]); } } resetError() { this.errorMessage = null; } onFolderCreated(event: FolderCreatedEvent) { this.logService.log('FOLDER CREATED'); this.logService.log(event); if (event && event.parentId === this.documentList.currentFolderId) { this.documentList.reload(); } } onFolderAction(node) { this.logService.log(node); if (node && node.parentId === this.documentList.currentFolderId) { this.documentList.reload(); } } onFolderChange($event) { this.router.navigate([this.navigationRoute, $event.value.id, 'display', this.displayMode]); } handlePermissionError(event: any) { this.notificationService.showError('PERMISSION.LACKOF', null, { permission: event.permission, action: event.action, type: event.type }); } openSnackMessageError(error: any) { this.notificationService.showError(error.value || error); } openSnackMessageInfo(message: string) { this.notificationService.showInfo(message); } emitReadyEvent(event: NodePaging) { this.documentListReady.emit(event); } pageIsEmpty(node: NodePaging) { return node && node.list && node.list.entries.length === 0; } onContentActionError(errors: any) { const errorStatusCode = JSON.parse(errors.message).error.statusCode; let message: string; switch (errorStatusCode) { case 403: message = 'OPERATION.ERROR.PERMISSION'; break; case 409: message = 'OPERATION.ERROR.CONFLICT'; break; default: message = 'OPERATION.ERROR.UNKNOWN'; } this.openSnackMessageError(message); } onContentActionSuccess(message: string) { this.openSnackMessageInfo(message); this.documentList.reload(); } onDeleteActionSuccess(message: string) { this.uploadService.fileDeleted.next(message); this.deleteElementSuccess.emit(); this.documentList.reload(); this.openSnackMessageInfo(message); } onPermissionRequested(node: any) { this.router.navigate(['/permissions', node.value.entry.id]); } onManageVersions(event: any) { const contentEntry = event.value.entry; const showComments = this.showVersionComments; const allowDownload = this.allowVersionDownload; if (this.contentService.hasAllowableOperations(contentEntry, 'update')) { this.dialog.open(VersionManagerDialogAdapterComponent, { data: { contentEntry, showComments, allowDownload }, panelClass: 'adf-version-manager-dialog', width: '630px' }); } else { this.openSnackMessageError('OPERATION.ERROR.PERMISSION'); } } onAspectUpdate(event: any) { this.aspectListService.openAspectListDialog(event.value.entry.id).subscribe((aspectList) => { this.nodeService.updateNode(event.value.entry.id, {aspectNames : [...aspectList]}).subscribe(() => { this.openSnackMessageInfo('Node Aspects Updated'); }); }); } onManageMetadata(event: any) { const contentEntry = event.value.entry; const displayEmptyMetadata = this.displayEmptyMetadata; if (this.contentService.hasAllowableOperations(contentEntry, 'update')) { this.dialog.open(MetadataDialogAdapterComponent, { data: { contentEntry, displayEmptyMetadata }, panelClass: 'adf-metadata-manager-dialog', width: '630px' }); } else { this.openSnackMessageError('OPERATION.ERROR.PERMISSION'); } } onSiteChange(site: SiteEntry) { this.currentFolderId = site.entry.guid; } hasSelection(selection: Array): boolean { return selection && selection.length > 0; } hasOneFileSelected(): boolean { const selection = this.documentList.selection; return selection && selection.length === 1 && selection[0].entry.isFile; } userHasPermissionToManageVersions(): boolean { const selection = this.documentList.selection; return this.contentService.hasAllowableOperations(selection[0].entry, 'update'); } getNodeNameTooltip(row: DataRow): string { if (row) { return row.getValue('name'); } return null; } canEditFolder(selection: Array): boolean { if (selection && selection.length === 1) { const entry = selection[0].entry; if (entry && entry.isFolder) { return this.contentService.hasAllowableOperations(entry, 'update'); } } return false; } canCreateContent(parentNode: MinimalNodeEntryEntity): boolean { if (parentNode) { return this.contentService.hasAllowableOperations(parentNode, 'create'); } return false; } startProcessAction($event: any) { this.formValues['file'] = $event.value.entry; const dialogRef = this.dialog.open(SelectAppsDialogComponent, { width: '630px', panelClass: 'adf-version-manager-dialog' }); dialogRef.afterClosed().subscribe((selectedProcess) => { this.processId = selectedProcess.id; }); } closeStartProcess() { this.processId = null; } onChangePageSize(event: Pagination): void { this.preference.paginationSize = event.maxItems; this.pagination.maxItems = event.maxItems; this.pagination.skipCount = event.skipCount; this.changedPageSize.emit(event); } onChangePageNumber(event: Pagination): void { this.pagination.maxItems = event.maxItems; this.pagination.skipCount = event.skipCount; this.changedPageNumber.emit(event); } onNextPage(event: Pagination): void { this.pagination.maxItems = event.maxItems; this.pagination.skipCount = event.skipCount; this.turnedNextPage.emit(event); } loadNextBatch(event: Pagination): void { this.pagination.maxItems = event.maxItems; this.pagination.skipCount = event.skipCount; this.loadNext.emit(event); } onPrevPage(event: Pagination): void { this.pagination.maxItems = event.maxItems; this.pagination.skipCount = event.skipCount; this.turnedPreviousPage.emit(event); } toggleGalleryView(): void { this.displayMode = this.displayMode === DisplayMode.List ? DisplayMode.Gallery : DisplayMode.List; const url = this .router .createUrlTree(['/files', this.currentFolderId, 'display', this.displayMode]) .toString(); this.location.go(url); } onInfiniteScrolling(): void { this.infiniteScrolling = !this.infiniteScrolling; this.infinitePaginationComponent.reset(); } canDownloadNode(node: MinimalNodeEntity): boolean { return node && node.entry && node.entry.name === 'custom'; } onBeginUpload(event: UploadFilesEvent) { if (this.warnOnMultipleUploads && event) { const files = event.files || []; if (files.length > 1) { event.pauseUpload(); const dialogRef = this.dialog.open(ConfirmDialogComponent, { data: { title: 'Upload', message: `Are you sure you want to upload ${files.length} file(s)?` }, minWidth: '250px' }); dialogRef.afterClosed().subscribe((result) => { if (result === true) { event.resumeUpload(); } }); } } } isCustomActionDisabled(node: MinimalNodeEntity): boolean { return !(node && node.entry && node.entry.name === 'custom'); } runCustomAction(event: any) { this.logService.log(event); } onUploadNewVersion(ev) { const contentEntry = ev.detail.data.node.entry; const showComments = this.showVersionComments; const allowDownload = this.allowVersionDownload; const newFileVersion = ev.detail.files[0].file; if (this.contentService.hasAllowableOperations(contentEntry, 'update')) { this.dialog.open(VersionManagerDialogAdapterComponent, { data: { contentEntry: contentEntry, showComments: showComments, allowDownload: allowDownload, newFileVersion: newFileVersion, showComparison: true }, panelClass: 'adf-version-manager-dialog', width: '630px' }); } else { this.openSnackMessageError('OPERATION.ERROR.PERMISSION'); } } getFileFiltering(): string { return this.acceptedFilesTypeShow ? this.acceptedFilesType : '*'; } createLibrary(): void { const dialogInstance: any = this.dialog.open(LibraryDialogComponent, { width: '400px' }); dialogInstance.componentInstance.error.subscribe((message: string) => { this.notificationService.openSnackMessage(message); }); } searchResultsHighlight(search: SearchEntry): string { if (search && search.highlight) { return search.highlight.map((currentHighlight) => currentHighlight.snippets).join(', '); } return ''; } onFilterSelected(activeFilters: FilterSearch[]) { if (activeFilters.length) { this.navigateToFilter(activeFilters); } else { this.clearFilterNavigation(); } } navigateToFilter(activeFilters: FilterSearch[]) { const objectFromMap = {}; activeFilters.forEach((filter: FilterSearch) => { let paramValue = null; if (filter.value && filter.value.from && filter.value.to) { paramValue = `${filter.value.from}||${filter.value.to}`; } else { paramValue = filter.value; } objectFromMap[filter.key] = paramValue; }); this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap }); } clearFilterNavigation() { this.documentList.node = null; if (this.currentFolderId === '-my-') { this.router.navigate([this.navigationRoute, '']); } else { this.router.navigate([this.navigationRoute, this.currentFolderId, 'display', this.displayMode]); } this.documentList.reload(); } setPreselectNodes(nodes: string) { this.selectedNodes = this.getArrayFromString(nodes); this.documentList.reload(); } isStringArray(str: string): boolean { try { const result = JSON.parse(str); return Array.isArray(result); } catch (e) { return false; } } private getArrayFromString(value: string): T[] { if (this.isStringArray(value)) { return JSON.parse(value); } else { return []; } } onMultipleFilesUpload() { this.selectedNodes = []; } }