MOBILE-2322 files: Implement list page
parent
55393cd1ee
commit
052208dac7
|
@ -0,0 +1,44 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="filesLoaded && (showPrivateFiles || showSiteFiles)" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-loading [hideUntil]="filesLoaded" *ngIf="showPrivateFiles || showSiteFiles">
|
||||
<!-- Allow selecting the files to see: private or site. -->
|
||||
<div no-padding *ngIf="showPrivateFiles && showSiteFiles && !path">
|
||||
<ion-select [(ngModel)]="root" (ngModelChange)="rootChanged()" interface="popover">
|
||||
<ion-option value="my">{{ 'addon.files.privatefiles' | translate }}</ion-option>
|
||||
<ion-option value="site">{{ 'addon.files.sitefiles' | translate }}</ion-option>
|
||||
</ion-select>
|
||||
</div>
|
||||
|
||||
<!-- Display info about space used and space left. -->
|
||||
<p class="core-info-card" *ngIf="userQuota && filesInfo && filesInfo.filecount > 0">{{ 'core.quotausage' | translate:{$a: {used: spaceUsed, total: userQuotaReadable} } }}</p>
|
||||
|
||||
<!-- List of files. -->
|
||||
<ion-list *ngIf="files && files.length > 0">
|
||||
<div *ngFor="let file of files">
|
||||
<a *ngIf="file.isdir" ion-item class="item-media" [navPush]="'AddonFilesListPage'" [navParams]="{path: file.link, title: file.filename}">
|
||||
<img [src]="file.imgPath" alt="" role="presentation" item-start>
|
||||
<p>{{file.filename}}</p>
|
||||
</a>
|
||||
<core-file *ngIf="!file.isdir" [file]="file" [component]="component" [componentId]="file.contextid"></core-file>
|
||||
</div>
|
||||
</ion-list>
|
||||
|
||||
<!-- Message telling there are no files. -->
|
||||
<core-empty-box *ngIf="!files || !files.length" icon="folder" [message]="'addon.files.emptyfilelist' | translate"></core-empty-box>
|
||||
</core-loading>
|
||||
|
||||
<!-- Upload a private file. -->
|
||||
<ion-fab bottom right *ngIf="showUpload && root != 'site' && !path">
|
||||
<button ion-fab (click)="uploadFile()" [attr.aria-label]="'core.fileuploader.uploadafile' | translate">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</button>
|
||||
</ion-fab>
|
||||
</ion-content>
|
|
@ -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 {}
|
|
@ -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<any>} Promise resolved when done.
|
||||
*/
|
||||
protected fetchFiles(): Promise<any> {
|
||||
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<any>} Promise resolved when done.
|
||||
*/
|
||||
protected refreshFiles(): Promise<any> {
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue