forked from EVOgeek/Vmeda.Online
		
	Merge pull request #2677 from dpalou/MOBILE-3651-2
MOBILE-3651 core: Implement attachments, files and local-file components
This commit is contained in:
		
						commit
						836a7bb812
					
				
							
								
								
									
										155
									
								
								src/core/components/attachments/attachments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/core/components/attachments/attachments.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,155 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty 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 } from '@angular/core'; | ||||||
|  | import { FileEntry } from '@ionic-native/file/ngx'; | ||||||
|  | 
 | ||||||
|  | import { CoreFileUploader, CoreFileUploaderTypeList } from '@features/fileuploader/services/fileuploader'; | ||||||
|  | import { CoreSites } from '@services/sites'; | ||||||
|  | import { CoreTextUtils } from '@services/utils/text'; | ||||||
|  | import { CoreWSExternalFile } from '@services/ws'; | ||||||
|  | import { Translate } from '@singletons'; | ||||||
|  | import { CoreApp } from '@services/app'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Component to render attachments, allow adding more and delete the current ones. | ||||||
|  |  * | ||||||
|  |  * All the changes done will be applied to the "files" input array, no file will be uploaded. The component using this | ||||||
|  |  * component should be the one uploading and moving the files. | ||||||
|  |  * | ||||||
|  |  * All the files added will be copied to the app temporary folder, so they should be deleted after uploading them | ||||||
|  |  * or if the user cancels the action. | ||||||
|  |  * | ||||||
|  |  * <core-attachments [files]="files" [maxSize]="configs.maxsubmissionsizebytes" [maxSubmissions]="configs.maxfilesubmissions" | ||||||
|  |  *     [component]="component" [componentId]="assign.cmid" [acceptedTypes]="configs.filetypeslist" [allowOffline]="allowOffline"> | ||||||
|  |  * </core-attachments> | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-attachments', | ||||||
|  |     templateUrl: 'core-attachments.html', | ||||||
|  | }) | ||||||
|  | export class CoreAttachmentsComponent implements OnInit { | ||||||
|  | 
 | ||||||
|  |     @Input() files?: (CoreWSExternalFile | FileEntry)[]; // List of attachments. New attachments will be added to this array.
 | ||||||
|  |     @Input() maxSize?: number; // Max size for attachments. -1 means unlimited, 0 means user max size, not defined means unknown.
 | ||||||
|  |     @Input() maxSubmissions?: number; // Max number of attachments. -1 means unlimited, not defined means unknown limit.
 | ||||||
|  |     @Input() component?: string; // Component the downloaded files will be linked to.
 | ||||||
|  |     @Input() componentId?: string | number; // Component ID.
 | ||||||
|  |     @Input() allowOffline?: boolean | string; // Whether to allow selecting files in offline.
 | ||||||
|  |     @Input() acceptedTypes?: string; // List of supported filetypes. If undefined, all types supported.
 | ||||||
|  |     @Input() required?: boolean; // Whether to display the required mark.
 | ||||||
|  | 
 | ||||||
|  |     maxSizeReadable?: string; | ||||||
|  |     maxSubmissionsReadable?: string; | ||||||
|  |     unlimitedFiles?: boolean; | ||||||
|  |     fileTypes?: CoreFileUploaderTypeList; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         this.files = this.files || []; | ||||||
|  |         this.maxSize = this.maxSize !== null ? Number(this.maxSize) : NaN; | ||||||
|  | 
 | ||||||
|  |         if (this.maxSize === 0) { | ||||||
|  |             const currentSite = CoreSites.instance.getCurrentSite(); | ||||||
|  |             const siteInfo = currentSite?.getInfo(); | ||||||
|  | 
 | ||||||
|  |             if (siteInfo?.usermaxuploadfilesize) { | ||||||
|  |                 this.maxSize = siteInfo.usermaxuploadfilesize; | ||||||
|  |                 this.maxSizeReadable = CoreTextUtils.instance.bytesToSize(this.maxSize, 2); | ||||||
|  |             } else { | ||||||
|  |                 this.maxSizeReadable = Translate.instance.instant('core.unknown'); | ||||||
|  |             } | ||||||
|  |         } else if (this.maxSize > 0) { | ||||||
|  |             this.maxSizeReadable = CoreTextUtils.instance.bytesToSize(this.maxSize, 2); | ||||||
|  |         } else if (this.maxSize === -1) { | ||||||
|  |             this.maxSizeReadable = Translate.instance.instant('core.unlimited'); | ||||||
|  |         } else { | ||||||
|  |             this.maxSizeReadable = Translate.instance.instant('core.unknown'); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (this.maxSubmissions === undefined || this.maxSubmissions < 0) { | ||||||
|  |             this.maxSubmissionsReadable = this.maxSubmissions === undefined ? | ||||||
|  |                 Translate.instance.instant('core.unknown') : undefined; | ||||||
|  |             this.unlimitedFiles = true; | ||||||
|  |         } else { | ||||||
|  |             this.maxSubmissionsReadable = String(this.maxSubmissions); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.acceptedTypes = this.acceptedTypes?.trim(); | ||||||
|  | 
 | ||||||
|  |         if (this.acceptedTypes && this.acceptedTypes != '*') { | ||||||
|  |             this.fileTypes = CoreFileUploader.instance.prepareFiletypeList(this.acceptedTypes); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Add a new attachment. | ||||||
|  |      */ | ||||||
|  |     async add(): Promise<void> { | ||||||
|  |         const allowOffline = !!this.allowOffline && this.allowOffline !== 'false'; | ||||||
|  | 
 | ||||||
|  |         if (!allowOffline && !CoreApp.instance.isOnline()) { | ||||||
|  |             CoreDomUtils.instance.showErrorModal('core.fileuploader.errormustbeonlinetoupload', true); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const mimetypes = this.fileTypes && this.fileTypes.mimetypes; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             const result = await CoreFileUploaderHelper.instance.selectFile(this.maxSize, allowOffline, undefined, mimetypes); | ||||||
|  | 
 | ||||||
|  |             this.files?.push(result); | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'Error selecting file.'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Delete a file from the list. | ||||||
|  |      * | ||||||
|  |      * @param index The index of the file. | ||||||
|  |      * @param askConfirm Whether to ask confirm. | ||||||
|  |      */ | ||||||
|  |     async delete(index: number, askConfirm?: boolean): Promise<void> { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         if (askConfirm) { | ||||||
|  |             try { | ||||||
|  |                 await CoreDomUtils.instance.showDeleteConfirm('core.confirmdeletefile'); | ||||||
|  |             } catch { | ||||||
|  |                 // User cancelled.
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Remove the file from the list.
 | ||||||
|  |         this.files?.splice(index, 1); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * A file was renamed. | ||||||
|  |      * | ||||||
|  |      * @param index Index of the file. | ||||||
|  |      * @param data The data received. | ||||||
|  |      */ | ||||||
|  |     renamed(index: number, data: { file: FileEntry }): void { | ||||||
|  |         this.files![index] = data.file; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								src/core/components/attachments/core-attachments.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/core/components/attachments/core-attachments.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | <ion-item class="ion-text-wrap"> | ||||||
|  |     <ion-label> | ||||||
|  |         <span *ngIf="maxSubmissionsReadable"> | ||||||
|  |             {{ 'core.maxsizeandattachments' | translate:{$a: {size: maxSizeReadable, attachments: maxSubmissionsReadable} } }} | ||||||
|  |         </span> | ||||||
|  |         <span *ngIf="!maxSubmissionsReadable">{{ 'core.maxfilesize' | translate:{$a: maxSizeReadable} }}</span> | ||||||
|  |         <span [core-mark-required]="required" class="core-mark-required"></span> | ||||||
|  |     </ion-label> | ||||||
|  | </ion-item> | ||||||
|  | <ion-item class="ion-text-wrap" *ngIf="fileTypes && fileTypes.mimetypes && fileTypes.mimetypes.length"> | ||||||
|  |     <ion-label> | ||||||
|  |         <p>{{ 'core.fileuploader.filesofthesetypes' | translate }}</p> | ||||||
|  |         <ul class="list-with-style"> | ||||||
|  |             <li *ngFor="let typeInfo of fileTypes.info"> | ||||||
|  |                 <strong *ngIf="typeInfo.name">{{typeInfo.name}} </strong>{{typeInfo.extlist}} | ||||||
|  |             </li> | ||||||
|  |         </ul> | ||||||
|  |     </ion-label> | ||||||
|  | </ion-item> | ||||||
|  | <div *ngFor="let file of files; let index=index"> | ||||||
|  |     <!-- Files already attached to the submission, either in online or in offline. --> | ||||||
|  |     <core-file *ngIf="!file.name" [file]="file" [component]="component" [componentId]="componentId" | ||||||
|  |         [canDelete]="true" (onDelete)="delete(index, true)" [canDownload]="!file.offline"> | ||||||
|  |     </core-file> | ||||||
|  | 
 | ||||||
|  |     <!-- Files added to draft but not attached to submission yet. --> | ||||||
|  |     <core-local-file *ngIf="file.name" [file]="file" [manage]="true" (onDelete)="delete(index, false)" | ||||||
|  |         (onRename)="renamed(index, $event)"> | ||||||
|  |     </core-local-file> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <!-- Button to add more files. --> | ||||||
|  | <ion-button expand="block" | ||||||
|  |     *ngIf="unlimitedFiles || (maxSubmissions !== undefined && maxSubmissions >= 0 && files && files.length < maxSubmissions)" | ||||||
|  |     class="ion-text-wrap ion-margin" (click)="add()"> | ||||||
|  |     <ion-icon name="fas-plus" slot="start"></ion-icon> | ||||||
|  |     {{ 'core.fileuploader.addfiletext' | translate }} | ||||||
|  | </ion-button> | ||||||
| @ -48,6 +48,9 @@ import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar'; | |||||||
| 
 | 
 | ||||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | import { CoreDirectivesModule } from '@directives/directives.module'; | ||||||
| import { CorePipesModule } from '@pipes/pipes.module'; | import { CorePipesModule } from '@pipes/pipes.module'; | ||||||
|  | import { CoreAttachmentsComponent } from './attachments/attachments'; | ||||||
|  | import { CoreFilesComponent } from './files/files'; | ||||||
|  | import { CoreLocalFileComponent } from './local-file/local-file'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     declarations: [ |     declarations: [ | ||||||
| @ -78,6 +81,9 @@ import { CorePipesModule } from '@pipes/pipes.module'; | |||||||
|         CoreSendMessageFormComponent, |         CoreSendMessageFormComponent, | ||||||
|         CoreTimerComponent, |         CoreTimerComponent, | ||||||
|         CoreNavigationBarComponent, |         CoreNavigationBarComponent, | ||||||
|  |         CoreAttachmentsComponent, | ||||||
|  |         CoreFilesComponent, | ||||||
|  |         CoreLocalFileComponent, | ||||||
|     ], |     ], | ||||||
|     imports: [ |     imports: [ | ||||||
|         CommonModule, |         CommonModule, | ||||||
| @ -115,6 +121,9 @@ import { CorePipesModule } from '@pipes/pipes.module'; | |||||||
|         CoreSendMessageFormComponent, |         CoreSendMessageFormComponent, | ||||||
|         CoreTimerComponent, |         CoreTimerComponent, | ||||||
|         CoreNavigationBarComponent, |         CoreNavigationBarComponent, | ||||||
|  |         CoreAttachmentsComponent, | ||||||
|  |         CoreFilesComponent, | ||||||
|  |         CoreLocalFileComponent, | ||||||
|     ], |     ], | ||||||
| }) | }) | ||||||
| export class CoreComponentsModule {} | export class CoreComponentsModule {} | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								src/core/components/files/core-files.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/core/components/files/core-files.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | <ng-container *ngIf="showInline && contentText"> | ||||||
|  |     <core-format-text [text]="contentText" [filter]="false"></core-format-text> | ||||||
|  | </ng-container> | ||||||
|  | <ng-container *ngFor="let file of files"> | ||||||
|  |     <!-- Files already attached to the filearea. --> | ||||||
|  |     <core-file *ngIf="!file.name && !file.embedType" [file]="file" [component]="component" [componentId]="componentId" | ||||||
|  |         [alwaysDownload]="alwaysDownload" [canDownload]="canDownload" [showSize]="showSize" [showTime]="showTime"> | ||||||
|  |     </core-file> | ||||||
|  | 
 | ||||||
|  |     <!-- Files stored in offline to be sent later. --> | ||||||
|  |     <core-local-file *ngIf="file.name && !file.embedType" [file]="file"></core-local-file> | ||||||
|  | </ng-container> | ||||||
							
								
								
									
										85
									
								
								src/core/components/files/files.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/core/components/files/files.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty 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, DoCheck, KeyValueDiffers } from '@angular/core'; | ||||||
|  | import { FileEntry } from '@ionic-native/file/ngx'; | ||||||
|  | 
 | ||||||
|  | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreWSExternalFile } from '@services/ws'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Component to render a file list. | ||||||
|  |  * | ||||||
|  |  * <core-files [files]="files" [component]="component" [componentId]="assign.cmid"> | ||||||
|  |  * </core-files> | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-files', | ||||||
|  |     templateUrl: 'core-files.html', | ||||||
|  | }) | ||||||
|  | export class CoreFilesComponent implements OnInit, DoCheck { | ||||||
|  | 
 | ||||||
|  |     @Input() files?: (CoreWSExternalFile | FileEntry)[]; // List of files.
 | ||||||
|  |     @Input() component?: string; // Component the downloaded files will be linked to.
 | ||||||
|  |     @Input() componentId?: string | number; // Component ID.
 | ||||||
|  |     @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded.
 | ||||||
|  |     @Input() canDownload?: boolean | string = true; // Whether file can be downloaded.
 | ||||||
|  |     @Input() showSize?: boolean | string = true; // Whether show filesize.
 | ||||||
|  |     @Input() showTime?: boolean | string = true; // Whether show file time modified.
 | ||||||
|  |     @Input() showInline = false; // If true, it will reorder and try to show inline files first.
 | ||||||
|  | 
 | ||||||
|  |     contentText?: string; | ||||||
|  | 
 | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||||
|  |     protected differ: any; // To detect changes in the data input.
 | ||||||
|  | 
 | ||||||
|  |     constructor(differs: KeyValueDiffers) { | ||||||
|  |         this.differ = differs.find([]).create(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     ngOnInit(): void { | ||||||
|  |         if (CoreUtils.instance.isTrueOrOne(this.showInline) && this.files) { | ||||||
|  |             this.renderInlineFiles(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). | ||||||
|  |      */ | ||||||
|  |     ngDoCheck(): void { | ||||||
|  |         if (CoreUtils.instance.isTrueOrOne(this.showInline) && this.files) { | ||||||
|  |             // Check if there's any change in the files array.
 | ||||||
|  |             const changes = this.differ.diff(this.files); | ||||||
|  |             if (changes) { | ||||||
|  |                 this.renderInlineFiles(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Calculate contentText based on fils that can be rendered inline. | ||||||
|  |      */ | ||||||
|  |     protected renderInlineFiles(): void { | ||||||
|  |         this.contentText = this.files!.reduce((previous, file) => { | ||||||
|  |             const text = CoreMimetypeUtils.instance.getEmbeddedHtml(file); | ||||||
|  | 
 | ||||||
|  |             return text ? previous + '<br>' + text : previous; | ||||||
|  |         }, ''); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								src/core/components/local-file/core-local-file.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/core/components/local-file/core-local-file.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | <form (ngSubmit)="changeName(newFileName, $event)" #nameForm> | ||||||
|  |     <ion-item class="ion-text-wrap item-media" (click)="fileClicked($event)"> <!-- [class.item-input]="editMode" --> | ||||||
|  |         <img [src]="fileIcon" alt="{{fileExtension}}" role="presentation" slot="start" /> | ||||||
|  | 
 | ||||||
|  |         <ion-label> | ||||||
|  |             <!-- File name and edit button (if editable). --> | ||||||
|  |             <h3 *ngIf="!editMode">{{fileName}}</h3> | ||||||
|  |             <!-- More data about the file. --> | ||||||
|  |             <p *ngIf="size && !editMode">{{ size }}</p> | ||||||
|  |             <p *ngIf="timemodified && !editMode">{{ timemodified }}</p> | ||||||
|  |         </ion-label> | ||||||
|  | 
 | ||||||
|  |         <!-- Form to edit the file's name. --> | ||||||
|  |         <ion-input type="text" name="filename" [placeholder]="'core.filename' | translate" autocapitalize="none" autocorrect="off" | ||||||
|  |             (click)="$event.stopPropagation()" [core-auto-focus] [(ngModel)]="newFileName" *ngIf="editMode"> | ||||||
|  |         </ion-input> | ||||||
|  | 
 | ||||||
|  |         <div class="buttons" slot="end" *ngIf="manage"> | ||||||
|  |             <ion-button *ngIf="!editMode" fill="clear" [core-suppress-events] (onClick)="activateEdit($event)" | ||||||
|  |                 [attr.aria-label]="'core.edit' | translate" color="dark"> | ||||||
|  |                 <ion-icon name="fas-pen" slot="icon-only"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  | 
 | ||||||
|  |             <ion-button *ngIf="editMode" fill="clear" [attr.aria-label]="'core.save' | translate" color="success" type="submit"> | ||||||
|  |                 <ion-icon name="fas-check" slot="icon-only"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  | 
 | ||||||
|  |             <ion-button fill="clear" (click)="deleteFile($event)" [attr.aria-label]="'core.delete' | translate" color="danger"> | ||||||
|  |                 <ion-icon name="fas-trash" slot="icon-only"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </div> | ||||||
|  |     </ion-item> | ||||||
|  | </form> | ||||||
							
								
								
									
										213
									
								
								src/core/components/local-file/local-file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								src/core/components/local-file/local-file.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,213 @@ | |||||||
|  | // (C) Copyright 2015 Moodle Pty 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, Output, OnInit, EventEmitter, ViewChild, ElementRef } from '@angular/core'; | ||||||
|  | import { FileEntry } from '@ionic-native/file/ngx'; | ||||||
|  | 
 | ||||||
|  | import { CoreIonLoadingElement } from '@classes/ion-loading'; | ||||||
|  | import { CoreFile } from '@services/file'; | ||||||
|  | import { CoreFileHelper } from '@services/file-helper'; | ||||||
|  | import { CoreSites } from '@services/sites'; | ||||||
|  | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
|  | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
|  | import { CoreTextUtils } from '@services/utils/text'; | ||||||
|  | import { CoreTimeUtils } from '@services/utils/time'; | ||||||
|  | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Component to handle a local file. Only files inside the app folder can be managed. | ||||||
|  |  * | ||||||
|  |  * Shows the file name, icon (depending on extension), size and time modified. | ||||||
|  |  * Also, if managing is enabled it will also show buttons to rename and delete the file. | ||||||
|  |  */ | ||||||
|  | @Component({ | ||||||
|  |     selector: 'core-local-file', | ||||||
|  |     templateUrl: 'core-local-file.html', | ||||||
|  | }) | ||||||
|  | export class CoreLocalFileComponent implements OnInit { | ||||||
|  | 
 | ||||||
|  |     @Input() file?: FileEntry; // A fileEntry retrieved using CoreFileProvider.getFile or similar.
 | ||||||
|  |     @Input() manage?: boolean | string; // Whether the user can manage the file (edit and delete).
 | ||||||
|  |     @Input() overrideClick?: boolean | string; // Whether the default item click should be overridden.
 | ||||||
|  |     @Output() onDelete = new EventEmitter<void>(); // Will notify when the file is deleted.
 | ||||||
|  |     @Output() onRename = new EventEmitter<{ file: FileEntry }>(); // Will notify when the file is renamed.
 | ||||||
|  |     @Output() onClick = new EventEmitter<void>(); // Will notify when the file is clicked. Only if overrideClick is true.
 | ||||||
|  | 
 | ||||||
|  |     @ViewChild('nameForm') formElement?: ElementRef; | ||||||
|  | 
 | ||||||
|  |     fileName?: string; | ||||||
|  |     fileIcon?: string; | ||||||
|  |     fileExtension?: string; | ||||||
|  |     size?: string; | ||||||
|  |     timemodified?: string; | ||||||
|  |     newFileName = ''; | ||||||
|  |     editMode = false; | ||||||
|  |     relativePath?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Component being initialized. | ||||||
|  |      */ | ||||||
|  |     async ngOnInit(): Promise<void> { | ||||||
|  |         this.manage = CoreUtils.instance.isTrueOrOne(this.manage); | ||||||
|  | 
 | ||||||
|  |         if (!this.file) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.loadFileBasicData(this.file); | ||||||
|  | 
 | ||||||
|  |         // Get the size and timemodified.
 | ||||||
|  |         const metadata = await CoreFile.instance.getMetadata(this.file); | ||||||
|  |         if (metadata.size >= 0) { | ||||||
|  |             this.size = CoreTextUtils.instance.bytesToSize(metadata.size, 2); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.timemodified = CoreTimeUtils.instance.userDate(metadata.modificationTime.getTime(), 'core.strftimedatetimeshort'); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Load the basic data for the file. | ||||||
|  |      */ | ||||||
|  |     protected loadFileBasicData(file: FileEntry): void { | ||||||
|  |         this.fileName = file.name; | ||||||
|  |         this.fileIcon = CoreMimetypeUtils.instance.getFileIcon(file.name); | ||||||
|  |         this.fileExtension = CoreMimetypeUtils.instance.getFileExtension(file.name); | ||||||
|  | 
 | ||||||
|  |         // Let's calculate the relative path for the file.
 | ||||||
|  |         this.relativePath = CoreFile.instance.removeBasePath(file.toURL()); | ||||||
|  |         if (!this.relativePath) { | ||||||
|  |             // Didn't find basePath, use fullPath but if the user tries to manage the file it'll probably fail.
 | ||||||
|  |             this.relativePath = file.fullPath; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * File clicked. | ||||||
|  |      * | ||||||
|  |      * @param e Click event. | ||||||
|  |      */ | ||||||
|  |     async fileClicked(e: Event): Promise<void> { | ||||||
|  |         if (this.editMode) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |         if (CoreUtils.instance.isTrueOrOne(this.overrideClick) && this.onClick.observers.length) { | ||||||
|  |             this.onClick.emit(); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!CoreFileHelper.instance.isOpenableInApp(this.file!)) { | ||||||
|  |             try { | ||||||
|  |                 await CoreFileHelper.instance.showConfirmOpenUnsupportedFile(); | ||||||
|  |             } catch (error) { | ||||||
|  |                 return; // Cancelled, stop.
 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         CoreUtils.instance.openFile(this.file!.toURL()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Activate the edit mode. | ||||||
|  |      * | ||||||
|  |      * @param e Click event. | ||||||
|  |      */ | ||||||
|  |     activateEdit(e: Event): void { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |         this.editMode = true; | ||||||
|  |         this.newFileName = this.file!.name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Rename the file. | ||||||
|  |      * | ||||||
|  |      * @param newName New name. | ||||||
|  |      * @param e Click event. | ||||||
|  |      */ | ||||||
|  |     async changeName(newName: string, e: Event): Promise<void> { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |         if (newName == this.file!.name) { | ||||||
|  |             // Name hasn't changed, stop.
 | ||||||
|  |             this.editMode = false; | ||||||
|  |             CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, CoreSites.instance.getCurrentSiteId()); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const modal = await CoreDomUtils.instance.showModalLoading(); | ||||||
|  |         const fileAndDir = CoreFile.instance.getFileAndDirectoryFromPath(this.relativePath!); | ||||||
|  |         const newPath = CoreTextUtils.instance.concatenatePaths(fileAndDir.directory, newName); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             // Check if there's a file with this name.
 | ||||||
|  |             await CoreFile.instance.getFile(newPath); | ||||||
|  | 
 | ||||||
|  |             // There's a file with this name, show error and stop.
 | ||||||
|  |             CoreDomUtils.instance.showErrorModal('core.errorfileexistssamename', true); | ||||||
|  |         } catch { | ||||||
|  |             try { | ||||||
|  |                 // File doesn't exist, move it.
 | ||||||
|  |                 const fileEntry = await CoreFile.instance.moveFile(this.relativePath!, newPath); | ||||||
|  | 
 | ||||||
|  |                 CoreDomUtils.instance.triggerFormSubmittedEvent(this.formElement, false, CoreSites.instance.getCurrentSiteId()); | ||||||
|  | 
 | ||||||
|  |                 this.editMode = false; | ||||||
|  |                 this.file = fileEntry; | ||||||
|  |                 this.loadFileBasicData(this.file); | ||||||
|  |                 this.onRename.emit({ file: this.file }); | ||||||
|  |             } catch (error) { | ||||||
|  |                 CoreDomUtils.instance.showErrorModalDefault(error, 'core.errorrenamefile', true); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             modal.dismiss(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Delete the file. | ||||||
|  |      * | ||||||
|  |      * @param e Click event. | ||||||
|  |      */ | ||||||
|  |     async deleteFile(e: Event): Promise<void> { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  | 
 | ||||||
|  |         let modal: CoreIonLoadingElement | undefined; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             // Ask confirmation.
 | ||||||
|  |             await CoreDomUtils.instance.showDeleteConfirm('core.confirmdeletefile'); | ||||||
|  | 
 | ||||||
|  |             modal = await CoreDomUtils.instance.showModalLoading('core.deleting', true); | ||||||
|  | 
 | ||||||
|  |             await CoreFile.instance.removeFile(this.relativePath!); | ||||||
|  | 
 | ||||||
|  |             this.onDelete.emit(); | ||||||
|  |         } catch (error) { | ||||||
|  |             CoreDomUtils.instance.showErrorModalDefault(error, 'core.errordeletefile', true); | ||||||
|  |         } finally { | ||||||
|  |             modal?.dismiss(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user