import {Component, ElementRef, Inject, Input, OnInit, ViewChild} from '@angular/core'; import {ApiService, ResourceNotAvailableOfflineError} from '../../../service/api.service'; import { APP_BASE_HREF } from '@angular/common'; import {EditorService} from '../../../service/editor.service'; import {EditorNodeContract} from '../EditorNode.contract'; import {HttpClient} from '@angular/common/http'; import {AlertController, LoadingController, PopoverController} from '@ionic/angular'; import {OptionMenuComponent} from '../../option-menu/option-menu.component'; export interface FileBoxFile { type: 'file'; title: string; mime: string; uploaded: string; id: string; category: string; } export interface FileBoxRecord { type: 'folder'; title: string; UUID: string; name: string; pageId: string; fileIds: string[]; rootUUID: string; parentUUID?: string; } export type FileBoxItem = FileBoxFile | FileBoxRecord; @Component({ selector: 'editor-file-box', templateUrl: './file-box.component.html', styleUrls: ['./file-box.component.scss'], }) export class FileBoxComponent extends EditorNodeContract implements OnInit { @Input() nodeId: string; @Input() editorUUID: string; @ViewChild('fileInput') fileInput: ElementRef; categoryIcons = { document: 'fa fa-file-word', spreadsheet: 'fa fa-file-excel', presentation: 'fa fa-file-powerpoint', image: 'fa fa-file-image', pdf: 'fa fa-file-pdf', video: 'fa fa-file-video', code: 'fa fa-file-code', text: 'fa fa-file-alt', other: 'fa fa-file', }; protected dirty = false; protected pendingSetup = true; public notAvailableOffline = false; public fileBoxName = 'New File Box'; public record?: FileBoxRecord; public history: FileBoxRecord[] = []; public items: FileBoxItem[] = []; public get readonly() { return !this.node || !this.editorService.canEdit(); } constructor( protected api: ApiService, protected http: HttpClient, public editorService: EditorService, protected loading: LoadingController, protected popover: PopoverController, protected alerts: AlertController, @Inject(APP_BASE_HREF) private baseHref: string ) { super(); } public isDark() { return document.body.classList.contains('dark'); } public isDirty(): boolean | Promise { return this.dirty; } public writeChangesToNode(): void | Promise { this.node.Value.Mode = 'files'; } public needsLoad(): boolean | Promise { return this.node && this.pendingSetup; } public async performLoad(): Promise { if ( !this.node.Value ) { this.node.Value = {}; } if ( this.api.isOffline ) { this.notAvailableOffline = true; this.pendingSetup = false; return; } console.log('file box compt', this); if ( !this.node.Value.Value && !this.readonly ) { this.record = await this.api.createFileBox(this.page.UUID, this.fileBoxName); console.log(this.record); this.node.Value.Value = this.record.UUID; this.node.value = this.record.UUID; this.dirty = true; } else if ( this.node.Value.Value ) { this.record = await this.api.getFileBox(this.page.UUID, this.node.Value.Value); console.log(this.record); } if ( !this.record ) { this.notAvailableOffline = true; this.pendingSetup = false; return; } this.fileBoxName = this.record.name; await this.loadBox(); if ( this.dirty ) { this.editorService.triggerSave(); } if ( this.fileInput ) { this.fileInput.nativeElement.value = null; } this.pendingSetup = false; } public async loadBox(): Promise { this.fileBoxName = this.record.name; const files = await this.api.getFileBoxFiles(this.page.UUID, this.record.UUID); const children = await this.api.getFileBoxChildren(this.page.UUID, this.record.UUID); this.items = [...children, ...files]; } public async navigateUp() { if ( this.history.length < 1 ) { return; } const last = this.history[this.history.length - 1]; if ( last ) { await this.navigateBack(last); } } public async navigateBack(record: FileBoxRecord) { const newHistory: FileBoxRecord[] = []; const found = this.history.some(row => { if ( row.UUID === record.UUID ) { return true; } else { newHistory.push(row); } }); if ( found ) { this.history = newHistory; } else { this.history = []; } this.record = record; await this.loadBox(); } public async performDelete(): Promise { } ngOnInit() { this.editorService = this.editorService.getEditor(this.editorUUID); this.editorService.registerNodeEditor(this.nodeId, this).then(() => { }); } surfaceContextItems() { return [ { name: 'New Folder', icon: 'fa fa-folder-plus', value: 'folder-add', title: 'Create a new folder in the current file box', }, ]; } async onSurfaceContextMenu(event: MouseEvent) { if ( !event.ctrlKey ) { event.preventDefault(); event.stopPropagation(); const popover = await this.popover.create({ event, component: OptionMenuComponent, componentProps: { menuItems: [ ...this.surfaceContextItems(), ], }, }); popover.onDidDismiss().then(result => { if ( result.data ) { this.onActionClick(result.data); } }); await popover.present(); } } itemContextItems(item: FileBoxItem) { return [ { name: 'Rename', value: 'rename', icon: 'fa fa-edit', title: 'Rename this ' + item.type, }, { name: 'Delete', value: 'delete', icon: 'fa fa-trash', title: 'Delete this ' + item.type, }, ]; } async onRecordNameChange(event) { const name = event.target.value; this.fileBoxName = name; this.record.name = name; this.record.title = name; await this.api.updateFileBox(this.page.UUID, this.record.UUID, { name }); } async onItemContextMenu(item: FileBoxItem, event: MouseEvent) { if ( !event.ctrlKey ) { event.preventDefault(); event.stopPropagation(); const popover = await this.popover.create({ event, component: OptionMenuComponent, componentProps: { menuItems: [ ...this.itemContextItems(item), ...this.surfaceContextItems(), ], }, }); popover.onDidDismiss().then(result => { if ( result.data ) { this.onActionClick(result.data, item); } }); await popover.present(); } } async onUploadFilesClick(event) { if ( this.fileInput ) { this.fileInput.nativeElement.click(); } } async onUploadFilesChange(event) { if ( this.readonly ) { return; } const loader = await this.loading.create({ message: 'Uploading files...', }); await loader.present(); const fileList: FileList = this.fileInput?.nativeElement?.files; if ( !fileList ) { return; } if ( fileList.length > 0 ) { const formData: FormData = new FormData(); // tslint:disable-next-line:prefer-for-of for ( let i = 0; i < fileList.length; i += 1 ) { const file = fileList[i]; formData.append(`uploaded_file_${i}`, file, file.name); } await this.api.uploadFileBoxFiles(this.page.UUID, this.record.UUID, formData); await this.loadBox(); } await loader.dismiss(); } async onItemActivate(item: FileBoxItem) { if ( item.type === 'folder' ) { this.history.push(this.record); this.record = item; await this.loadBox(); } else if ( item.type === 'file' ) { const url = this.api.getFileBoxFileDownloadUrl(this.page.UUID, this.record.UUID, item.id); window.open(url, '_blank'); } } async onActionClick(action: string, item?: FileBoxItem) { if ( action === 'folder-add' ) { await this.actionNewFolder(); } else if ( action === 'rename' && item ) { await this.actionRename(item); } else if ( action === 'delete' && item ) { await this.actionDelete(item); } } async actionNewFolder() { const alert = await this.alerts.create({ header: 'New Folder', message: 'Enter a name for the new folder:', inputs: [ { name: 'name', placeholder: 'New Folder', }, ], buttons: [ { text: 'Create', role: 'ok', }, { text: 'Cancel', role: 'cancel', }, ], }); alert.onDidDismiss().then(async result => { if ( result.role === 'ok' ) { const name = result.data?.values?.name?.trim() || 'New Folder'; await this.api.createFileBox(this.page.UUID, name, this.record.rootUUID, this.record.UUID); await this.loadBox(); } }); await alert.present(); } async actionRename(item: FileBoxItem) { const alert = await this.alerts.create({ header: 'Rename ' + item.type, message: `Enter a new name for the ${item.type}:`, inputs: [ { name: 'name', value: item.title, }, ], buttons: [ { text: 'Rename', role: 'ok', }, { text: 'Cancel', role: 'cancel', }, ], }); alert.onDidDismiss().then(async result => { const name = result.data?.values?.name?.trim(); if ( result.role === 'ok' && name ) { if ( item.type === 'folder' ) { item.name = name; item.title = name; await this.api.updateFileBox(this.page.UUID, item.UUID, { name }); } else if ( item.type === 'file' ) { item.title = name; await this.api.updateFileBoxFile(this.page.UUID, this.record.UUID, item.id, { name }); } } }); await alert.present(); } async actionDelete(item: FileBoxItem) { const alert = await this.alerts.create({ header: `Delete ${item.type}?`, message: `Are you sure you want to delete the ${item.type} "${item.title}"? This action cannot be undone.`, buttons: [ { text: 'Delete It', role: 'ok', }, { text: 'Keep It', role: 'cancel', }, ], }); alert.onDidDismiss().then(async result => { if ( result.role === 'ok' ) { if ( item.type === 'file' ) { await this.api.deleteFileBoxFile(this.page.UUID, this.record.UUID, item.id); await this.loadBox(); } else if ( item.type === 'folder' ) { await this.api.deleteFileBox(this.page.UUID, item.UUID); await this.loadBox(); } } }); await alert.present(); } }