forked from CIT/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