diff --git a/src/addon/files/pages/list/list.html b/src/addon/files/pages/list/list.html new file mode 100644 index 000000000..b55f063db --- /dev/null +++ b/src/addon/files/pages/list/list.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + +
+ + {{ 'addon.files.privatefiles' | translate }} + {{ 'addon.files.sitefiles' | translate }} + +
+ + +

{{ 'core.quotausage' | translate:{$a: {used: spaceUsed, total: userQuotaReadable} } }}

+ + + +
+ + +

{{file.filename}}

+
+ +
+
+ + + +
+ + + + + +
\ No newline at end of file diff --git a/src/addon/files/pages/list/list.module.ts b/src/addon/files/pages/list/list.module.ts new file mode 100644 index 000000000..50a40684d --- /dev/null +++ b/src/addon/files/pages/list/list.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { AddonFilesListPage } from './list'; + +@NgModule({ + declarations: [ + AddonFilesListPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(AddonFilesListPage), + TranslateModule.forChild() + ], +}) +export class AddonFilesListPageModule {} diff --git a/src/addon/files/pages/list/list.ts b/src/addon/files/pages/list/list.ts new file mode 100644 index 000000000..f79b3c19f --- /dev/null +++ b/src/addon/files/pages/list/list.ts @@ -0,0 +1,215 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, ViewChild, OnDestroy } from '@angular/core'; +import { IonicPage, NavParams, NavController } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '../../../../providers/app'; +import { CoreEventsProvider } from '../../../../providers/events'; +import { CoreSitesProvider } from '../../../../providers/sites'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; +import { AddonFilesProvider } from '../../providers/files'; +import { AddonFilesHelperProvider } from '../../providers/helper'; + +/** + * Page that displays the list of files. + */ +@IonicPage({ segment: 'addon-files-list' }) +@Component({ + selector: 'page-addon-files-list', + templateUrl: 'list.html', +}) +export class AddonFilesListPage implements OnDestroy { + + title: string; // Page title. + showPrivateFiles: boolean; // Whether the user can view private files. + showSiteFiles: boolean; // Whether the user can view site files. + showUpload: boolean; // Whether the user can upload files. + root: string; // The root of the files loaded: 'my' or 'site'. + path: string; // The path of the directory being loaded. If empty path it means the root is being loaded. + userQuota: number; // The user quota (in bytes). + filesInfo: any; // Info about private files (size, number of files, etc.). + spaceUsed: string; // Space used in a readable format. + userQuotaReadable: string; // User quota in a readable format. + files: any[]; // List of files. + component: string; // Component to link the file downloads to. + filesLoaded: boolean; // Whether the files are loaded. + + protected updateSiteObserver; + + constructor(navParams: NavParams, eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, + private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private appProvider: CoreAppProvider, + private filesProvider: AddonFilesProvider, private filesHelper: AddonFilesHelperProvider, + private textUtils: CoreTextUtilsProvider) { + this.title = navParams.get('title') || this.translate.instant('addon.files.files'); + this.root = navParams.get('root'); + this.path = navParams.get('path'); + + // Update visibility if current site info is updated. + this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => { + this.setVisibility(); + }, sitesProvider.getCurrentSiteId()); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.setVisibility(); + this.userQuota = this.sitesProvider.getCurrentSite().getInfo().userquota; + + if (!this.root) { + // Load private files by default. + if (this.showPrivateFiles) { + this.root = 'my'; + } else if (this.showSiteFiles) { + this.root = 'site'; + } + } + + if (this.root) { + this.rootChanged(); + } else { + this.filesLoaded = true; + } + } + + /** + * Refresh the data. + * + * @param {any} refresher Refresher. + */ + refreshData(refresher: any): void { + this.refreshFiles().finally(() => { + refresher.complete(); + }); + } + + /** + * Function called when the root has changed. + */ + rootChanged(): void { + this.filesLoaded = false; + this.component = this.root == 'my' ? AddonFilesProvider.PRIVATE_FILES_COMPONENT : AddonFilesProvider.SITE_FILES_COMPONENT; + + this.fetchFiles().finally(() => { + this.filesLoaded = true; + }); + } + + /** + * Upload a new file. + */ + uploadFile(): void { + this.filesProvider.versionCanUploadFiles().then((canUpload) => { + if (!canUpload) { + this.domUtils.showAlertTranslated('core.notice', 'addon.files.erroruploadnotworking'); + } else if (!this.appProvider.isOnline()) { + this.domUtils.showErrorModal('core.fileuploader.errormustbeonlinetoupload', true); + } else { + this.filesHelper.uploadPrivateFile(this.filesInfo).then(() => { + // File uploaded, refresh the list. + this.filesLoaded = false; + this.refreshFiles().finally(() => { + this.filesLoaded = true; + }); + }).catch(() => { + // Ignore errors, they're handled inside the function. + }); + } + }); + } + + /** + * Set visibility of some items based on site data. + */ + protected setVisibility(): void { + this.showPrivateFiles = this.filesProvider.canViewPrivateFiles(); + this.showSiteFiles = this.filesProvider.canViewSiteFiles(); + this.showUpload = this.filesProvider.canUploadFiles(); + } + + /** + * Fetch the files. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchFiles(): Promise { + let promise; + + if (!this.path) { + // The path is unknown, the user must be requesting a root. + if (this.root == 'site') { + this.title = this.translate.instant('addon.files.sitefiles'); + promise = this.filesProvider.getSiteFiles(); + } else if (this.root == 'my') { + this.title = this.translate.instant('addon.files.files'); + + promise = this.filesProvider.getPrivateFiles().then((files) => { + if (this.showUpload && this.filesProvider.canGetPrivateFilesInfo() && this.userQuota > 0) { + // Get the info to calculate the available size. + return this.filesProvider.getPrivateFilesInfo().then((info) => { + this.filesInfo = info; + this.spaceUsed = this.textUtils.bytesToSize(info.filesizewithoutreferences, 1); + this.userQuotaReadable = this.textUtils.bytesToSize(this.userQuota, 1); + + return files; + }); + } else { + // User quota isn't useful, delete it. + delete this.userQuota; + } + + return files; + }); + } else { + // Unknown root. + promise = Promise.reject(null); + } + } else { + // Path is set, serve the files the user requested. + promise = this.filesProvider.getFiles(this.path); + } + + return promise.then((files) => { + this.files = files; + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.files.couldnotloadfiles', true); + }); + } + + /** + * Refresh the displayed files. + * + * @return {Promise} Promise resolved when done. + */ + protected refreshFiles(): Promise { + const promises = []; + + promises.push(this.filesProvider.invalidateDirectory(this.root, this.path)); + promises.push(this.filesProvider.invalidatePrivateFilesInfoForUser()); + + return Promise.all(promises).finally(() => { + return this.fetchFiles(); + }); + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + this.updateSiteObserver && this.updateSiteObserver.off(); + } +} diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index 87b800364..e1bd4a08c 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -204,7 +204,7 @@ export class CoreUtilsProvider { } return newArray; - } else if (typeof source == 'object') { + } else if (typeof source == 'object' && source !== null) { // Clone the object and all the subproperties. const newObject = {}; for (const name in source) {