MOBILE-2312 core: Implement local file and site picker components
parent
aa3562cfe7
commit
efe4b0f66d
|
@ -30,6 +30,8 @@ import { CoreContextMenuComponent } from './context-menu/context-menu';
|
|||
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
||||
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
||||
import { CoreChronoComponent } from './chrono/chrono';
|
||||
import { CoreLocalFileComponent } from './local-file/local-file';
|
||||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -45,7 +47,9 @@ import { CoreChronoComponent } from './chrono/chrono';
|
|||
CoreContextMenuComponent,
|
||||
CoreContextMenuItemComponent,
|
||||
CoreContextMenuPopoverComponent,
|
||||
CoreChronoComponent
|
||||
CoreChronoComponent,
|
||||
CoreLocalFileComponent,
|
||||
CoreSitePickerComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CoreContextMenuPopoverComponent
|
||||
|
@ -68,7 +72,9 @@ import { CoreChronoComponent } from './chrono/chrono';
|
|||
CoreFileComponent,
|
||||
CoreContextMenuComponent,
|
||||
CoreContextMenuItemComponent,
|
||||
CoreChronoComponent
|
||||
CoreChronoComponent,
|
||||
CoreLocalFileComponent,
|
||||
CoreSitePickerComponent
|
||||
]
|
||||
})
|
||||
export class CoreComponentsModule {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<a ion-item text-wrap class="item-media" (click)="download($event, true)" [class.item-2-button-right]="canDelete">
|
||||
<a ion-item text-wrap class="item-media" (click)="download($event, true)" [class.item-2-button-right]="canDelete" detail-none>
|
||||
<img [src]="fileIcon" alt="" role="presentation" item-start />
|
||||
<p>{{fileName}}</p>
|
||||
<div class="buttons" item-end>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<a ion-item text-wrap class="item-media" (click)="fileClicked($event)" detail-none>
|
||||
<img [src]="fileIcon" alt="{{fileExtension}}" role="presentation" item-start />
|
||||
|
||||
<!-- File name and edit button (if editable). -->
|
||||
<p *ngIf="!editMode" class="core-text-with-icon-right">
|
||||
{{fileName}}
|
||||
<a ion-button icon-only clear *ngIf="manage" (click)="activateEdit($event)" [attr.aria-label]="'core.edit' | translate">
|
||||
<ion-icon name="create" ios="md-create"></ion-icon>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- Form to edit the file's name. -->
|
||||
<form *ngIf="editMode" (ngSubmit)="changeName(newFileName)">
|
||||
<ion-input type="text" name="filename" [(ngModel)]="newFileName" [placeholder]="'core.filename' | translate" autocapitalize="none" autocorrect="off" (click)="$event.stopPropagation()" [core-auto-focus]></ion-input>
|
||||
<button type="submit" ion-button icon-only clear class="core-button-icon-small" [attr.aria-label]="'core.save' | translate">
|
||||
<ion-icon name="checkmark"></ion-icon>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- More data about the file. -->
|
||||
<p *ngIf="size">{{ size }}</p>
|
||||
<p *ngIf="timemodified">{{ timemodified }}</p>
|
||||
|
||||
<div class="buttons" item-end *ngIf="manage">
|
||||
<button ion-button clear icon-only (click)="deleteFile($event)" [attr.aria-label]="'core.delete' | translate" color="danger">
|
||||
<ion-icon name="trash"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
|
@ -0,0 +1,187 @@
|
|||
// (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, Input, Output, OnInit, EventEmitter } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreFileProvider } from '../../providers/file';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
|
||||
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
* 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: 'local-file.html'
|
||||
})
|
||||
export class CoreLocalFileComponent implements OnInit {
|
||||
@Input() file: any; // 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?: EventEmitter<void>; // Will notify when the file is deleted.
|
||||
@Output() onRename?: EventEmitter<any>; // Will notify when the file is renamed. Receives the FileEntry as the param.
|
||||
@Output() onClick?: EventEmitter<void>; // Will notify when the file is clicked. Only if overrideClick is true.
|
||||
|
||||
fileName: string;
|
||||
fileIcon: string;
|
||||
fileExtension: string;
|
||||
size: string;
|
||||
timemodified: string;
|
||||
newFileName: string = '';
|
||||
editMode: boolean;
|
||||
relativePath: string;
|
||||
|
||||
constructor(private mimeUtils: CoreMimetypeUtilsProvider, private utils: CoreUtilsProvider, private translate: TranslateService,
|
||||
private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider,
|
||||
private domUtils: CoreDomUtilsProvider) {
|
||||
this.onDelete = new EventEmitter();
|
||||
this.onRename = new EventEmitter();
|
||||
this.onClick = new EventEmitter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.manage = this.utils.isTrueOrOne(this.manage);
|
||||
|
||||
// Let's calculate the relative path for the file.
|
||||
this.relativePath = this.fileProvider.removeBasePath(this.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 = this.file.fullPath;
|
||||
}
|
||||
|
||||
this.loadFileBasicData();
|
||||
|
||||
// Get the size and timemodified.
|
||||
this.fileProvider.getMetadata(this.file).then((metadata) => {
|
||||
if (metadata.size >= 0) {
|
||||
this.size = this.textUtils.bytesToSize(metadata.size, 2);
|
||||
}
|
||||
|
||||
this.timemodified = moment(metadata.modificationTime).format('LLL');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the basic data for the file.
|
||||
*
|
||||
* @param {[type]} scope [description]
|
||||
* @param {[type]} file [description]
|
||||
*/
|
||||
protected loadFileBasicData() {
|
||||
this.fileName = this.file.name;
|
||||
this.fileIcon = this.mimeUtils.getFileIcon(this.file.name);
|
||||
this.fileExtension = this.mimeUtils.getFileExtension(this.file.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* File clicked.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
fileClicked(e: Event) : void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.utils.isTrueOrOne(this.overrideClick) && this.onClick.observers.length) {
|
||||
this.onClick.emit();
|
||||
} else {
|
||||
this.utils.openFile(this.file.toURL());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate the edit mode.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
activateEdit(e: Event) : void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.editMode = true;
|
||||
this.newFileName = this.file.name;
|
||||
|
||||
// @todo For some reason core-auto-focus isn't working right. Focus the input manually.
|
||||
// $timeout(function() {
|
||||
// $mmUtil.focusElement(element[0].querySelector('input'));
|
||||
// });
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename the file.
|
||||
*
|
||||
* @param {string} newName New name.
|
||||
*/
|
||||
changeName(newName: string) : void {
|
||||
if (newName == this.file.name) {
|
||||
// Name hasn't changed, stop.
|
||||
this.editMode = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let modal = this.domUtils.showModalLoading(),
|
||||
fileAndDir = this.fileProvider.getFileAndDirectoryFromPath(this.relativePath),
|
||||
newPath = this.textUtils.concatenatePaths(fileAndDir.directory, newName);
|
||||
|
||||
// Check if there's a file with this name.
|
||||
this.fileProvider.getFile(newPath).then(() => {
|
||||
// There's a file with this name, show error and stop.
|
||||
this.domUtils.showErrorModal('core.errorfileexistssamename', true);
|
||||
}).catch(() => {
|
||||
// File doesn't exist, move it.
|
||||
return this.fileProvider.moveFile(this.relativePath, newPath).then((fileEntry) => {
|
||||
this.editMode = false;
|
||||
this.file = fileEntry;
|
||||
this.loadFileBasicData();
|
||||
this.onRename.emit({file: this.file});
|
||||
}).catch(() => {
|
||||
this.domUtils.showErrorModal('core.errorrenamefile', true);
|
||||
});
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the file.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
*/
|
||||
deleteFile(e: Event) : void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Ask confirmation.
|
||||
this.domUtils.showConfirm(this.translate.instant('core.confirmdeletefile')).then(() => {
|
||||
let modal = this.domUtils.showModalLoading();
|
||||
this.fileProvider.removeFile(this.relativePath).then(() => {
|
||||
this.onDelete.emit();
|
||||
}).catch(() => {
|
||||
this.domUtils.showErrorModal('core.errordeletefile', true);
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}).catch(() => {
|
||||
// User cancelled.
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<ion-item>
|
||||
<ion-label>{{ 'core.site' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="selectedSite" (ngModelChange)="siteSelected.emit(selectedSite)">
|
||||
<ion-option *ngFor="let site of sites" [value]="site.id">{{ site.fullNameAndSiteName }}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
|
@ -0,0 +1,66 @@
|
|||
// (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, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSitesProvider } from '../../providers/sites';
|
||||
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||
|
||||
/**
|
||||
* Component to display a site selector. It will display a select with the list of sites. If the selected site changes,
|
||||
* an output will be emitted with the site ID.
|
||||
*
|
||||
* Example usage:
|
||||
* <core-site-picker (siteSelected)="changeSite($event)"></core-site-picker>
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-site-picker',
|
||||
templateUrl: 'site-picker.html'
|
||||
})
|
||||
export class CoreSitePickerComponent implements OnInit {
|
||||
@Input() initialSite?: string; // Initial site. If not provided, current site.
|
||||
@Output() siteSelected: EventEmitter<string>; // Emit an event when a site is selected. Sends the siteId as parameter.
|
||||
|
||||
selectedSite: string;
|
||||
sites: any[];
|
||||
|
||||
constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider,
|
||||
private textUtils: CoreTextUtilsProvider) {
|
||||
this.siteSelected = new EventEmitter();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.selectedSite = this.initialSite || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Load the sites.
|
||||
this.sitesProvider.getSites().then((sites) => {
|
||||
let promises = [];
|
||||
|
||||
sites.forEach((site: any) => {
|
||||
// Format the site name.
|
||||
promises.push(this.textUtils.formatText(site.siteName, true, true).catch(() => {
|
||||
return site.siteName;
|
||||
}).then((formatted) => {
|
||||
site.fullNameAndSiteName = this.translate.instant('core.fullnameandsitename',
|
||||
{fullname: site.fullName, sitename: formatted});
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
this.sites = sites;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue