diff --git a/src/components/components.module.ts b/src/components/components.module.ts index c8c96e009..a3d1ec8ba 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -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 {} diff --git a/src/components/file/file.html b/src/components/file/file.html index e4dab9efb..9cf0d41c6 100644 --- a/src/components/file/file.html +++ b/src/components/file/file.html @@ -1,4 +1,4 @@ - +

{{fileName}}

diff --git a/src/components/local-file/local-file.html b/src/components/local-file/local-file.html new file mode 100644 index 000000000..62eeafe76 --- /dev/null +++ b/src/components/local-file/local-file.html @@ -0,0 +1,29 @@ + + {{fileExtension}} + + +

+ {{fileName}} + + + +

+ + +
+ + +
+ + +

{{ size }}

+

{{ timemodified }}

+ +
+ +
+ diff --git a/src/components/local-file/local-file.ts b/src/components/local-file/local-file.ts new file mode 100644 index 000000000..1961a8064 --- /dev/null +++ b/src/components/local-file/local-file.ts @@ -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; // Will notify when the file is deleted. + @Output() onRename?: EventEmitter; // Will notify when the file is renamed. Receives the FileEntry as the param. + @Output() onClick?: EventEmitter; // 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. + }); + } +} diff --git a/src/components/site-picker/site-picker.html b/src/components/site-picker/site-picker.html new file mode 100644 index 000000000..ee5fc727c --- /dev/null +++ b/src/components/site-picker/site-picker.html @@ -0,0 +1,6 @@ + + {{ 'core.site' | translate }} + + {{ site.fullNameAndSiteName }} + + diff --git a/src/components/site-picker/site-picker.ts b/src/components/site-picker/site-picker.ts new file mode 100644 index 000000000..1f4f92662 --- /dev/null +++ b/src/components/site-picker/site-picker.ts @@ -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: + * + */ +@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; // 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; + }); + }); + } + +}