diff --git a/src/app/app.scss b/src/app/app.scss index 56f3c6d57..ed0a5508b 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -216,3 +216,12 @@ core-format-text, *[core-format-text] { display: inline; } } + +// Media item, ideal for icons. +.item-media { + min-height: $item-media-height + ($content-padding * 2); + > img:first-child { + max-width: $item-media-width; + max-height: $item-media-height; + } +} diff --git a/src/assets/img/files/archive-64.png b/src/assets/img/files/archive-64.png new file mode 100644 index 000000000..ba7111a02 Binary files /dev/null and b/src/assets/img/files/archive-64.png differ diff --git a/src/assets/img/files/audio-64.png b/src/assets/img/files/audio-64.png new file mode 100644 index 000000000..e98c78dd7 Binary files /dev/null and b/src/assets/img/files/audio-64.png differ diff --git a/src/assets/img/files/avi-64.png b/src/assets/img/files/avi-64.png new file mode 100644 index 000000000..ec1a942cd Binary files /dev/null and b/src/assets/img/files/avi-64.png differ diff --git a/src/assets/img/files/base-64.png b/src/assets/img/files/base-64.png new file mode 100644 index 000000000..cf698f525 Binary files /dev/null and b/src/assets/img/files/base-64.png differ diff --git a/src/assets/img/files/bmp-64.png b/src/assets/img/files/bmp-64.png new file mode 100644 index 000000000..562e7bbab Binary files /dev/null and b/src/assets/img/files/bmp-64.png differ diff --git a/src/assets/img/files/calc-64.png b/src/assets/img/files/calc-64.png new file mode 100644 index 000000000..b813dd2cb Binary files /dev/null and b/src/assets/img/files/calc-64.png differ diff --git a/src/assets/img/files/chart-64.png b/src/assets/img/files/chart-64.png new file mode 100644 index 000000000..4b8f85bd6 Binary files /dev/null and b/src/assets/img/files/chart-64.png differ diff --git a/src/assets/img/files/database-64.png b/src/assets/img/files/database-64.png new file mode 100644 index 000000000..33d5043c1 Binary files /dev/null and b/src/assets/img/files/database-64.png differ diff --git a/src/assets/img/files/document-64.png b/src/assets/img/files/document-64.png new file mode 100644 index 000000000..0888ebbbd Binary files /dev/null and b/src/assets/img/files/document-64.png differ diff --git a/src/assets/img/files/draw-64.png b/src/assets/img/files/draw-64.png new file mode 100644 index 000000000..1b827c7c6 Binary files /dev/null and b/src/assets/img/files/draw-64.png differ diff --git a/src/assets/img/files/eps-64.png b/src/assets/img/files/eps-64.png new file mode 100644 index 000000000..c42492441 Binary files /dev/null and b/src/assets/img/files/eps-64.png differ diff --git a/src/assets/img/files/epub-64.png b/src/assets/img/files/epub-64.png new file mode 100644 index 000000000..298d5dcd9 Binary files /dev/null and b/src/assets/img/files/epub-64.png differ diff --git a/src/assets/img/files/flash-64.png b/src/assets/img/files/flash-64.png new file mode 100644 index 000000000..01d28e03e Binary files /dev/null and b/src/assets/img/files/flash-64.png differ diff --git a/src/assets/img/files/folder-64.png b/src/assets/img/files/folder-64.png new file mode 100644 index 000000000..2508ab252 Binary files /dev/null and b/src/assets/img/files/folder-64.png differ diff --git a/src/assets/img/files/folder-open-64.png b/src/assets/img/files/folder-open-64.png new file mode 100644 index 000000000..27f7271bd Binary files /dev/null and b/src/assets/img/files/folder-open-64.png differ diff --git a/src/assets/img/files/gif-64.png b/src/assets/img/files/gif-64.png new file mode 100644 index 000000000..2373292b7 Binary files /dev/null and b/src/assets/img/files/gif-64.png differ diff --git a/src/assets/img/files/html-64.png b/src/assets/img/files/html-64.png new file mode 100644 index 000000000..7f703bb83 Binary files /dev/null and b/src/assets/img/files/html-64.png differ diff --git a/src/assets/img/files/image-64.png b/src/assets/img/files/image-64.png new file mode 100644 index 000000000..2d8f9e4fa Binary files /dev/null and b/src/assets/img/files/image-64.png differ diff --git a/src/assets/img/files/impress-64.png b/src/assets/img/files/impress-64.png new file mode 100644 index 000000000..c279c62ef Binary files /dev/null and b/src/assets/img/files/impress-64.png differ diff --git a/src/assets/img/files/isf-64.png b/src/assets/img/files/isf-64.png new file mode 100644 index 000000000..ad5a18867 Binary files /dev/null and b/src/assets/img/files/isf-64.png differ diff --git a/src/assets/img/files/jpeg-64.png b/src/assets/img/files/jpeg-64.png new file mode 100644 index 000000000..b4fc0c998 Binary files /dev/null and b/src/assets/img/files/jpeg-64.png differ diff --git a/src/assets/img/files/markup-64.png b/src/assets/img/files/markup-64.png new file mode 100644 index 000000000..b89072713 Binary files /dev/null and b/src/assets/img/files/markup-64.png differ diff --git a/src/assets/img/files/math-64.png b/src/assets/img/files/math-64.png new file mode 100644 index 000000000..d98beea61 Binary files /dev/null and b/src/assets/img/files/math-64.png differ diff --git a/src/assets/img/files/moodle-64.png b/src/assets/img/files/moodle-64.png new file mode 100644 index 000000000..44ad3a37d Binary files /dev/null and b/src/assets/img/files/moodle-64.png differ diff --git a/src/assets/img/files/mp3-64.png b/src/assets/img/files/mp3-64.png new file mode 100644 index 000000000..13b8da0b8 Binary files /dev/null and b/src/assets/img/files/mp3-64.png differ diff --git a/src/assets/img/files/mpeg-64.png b/src/assets/img/files/mpeg-64.png new file mode 100644 index 000000000..05d77fa7b Binary files /dev/null and b/src/assets/img/files/mpeg-64.png differ diff --git a/src/assets/img/files/oth-64.png b/src/assets/img/files/oth-64.png new file mode 100644 index 000000000..8ffa8b466 Binary files /dev/null and b/src/assets/img/files/oth-64.png differ diff --git a/src/assets/img/files/pdf-64.png b/src/assets/img/files/pdf-64.png new file mode 100644 index 000000000..b7cdae7e9 Binary files /dev/null and b/src/assets/img/files/pdf-64.png differ diff --git a/src/assets/img/files/png-64.png b/src/assets/img/files/png-64.png new file mode 100644 index 000000000..3ecf3e5df Binary files /dev/null and b/src/assets/img/files/png-64.png differ diff --git a/src/assets/img/files/powerpoint-64.png b/src/assets/img/files/powerpoint-64.png new file mode 100644 index 000000000..4d44c7d2b Binary files /dev/null and b/src/assets/img/files/powerpoint-64.png differ diff --git a/src/assets/img/files/psd-64.png b/src/assets/img/files/psd-64.png new file mode 100644 index 000000000..0b800dffa Binary files /dev/null and b/src/assets/img/files/psd-64.png differ diff --git a/src/assets/img/files/publisher-64.png b/src/assets/img/files/publisher-64.png new file mode 100644 index 000000000..9633ef3dd Binary files /dev/null and b/src/assets/img/files/publisher-64.png differ diff --git a/src/assets/img/files/quicktime-64.png b/src/assets/img/files/quicktime-64.png new file mode 100644 index 000000000..90f2fbc0d Binary files /dev/null and b/src/assets/img/files/quicktime-64.png differ diff --git a/src/assets/img/files/sourcecode-64.png b/src/assets/img/files/sourcecode-64.png new file mode 100644 index 000000000..ad25537e4 Binary files /dev/null and b/src/assets/img/files/sourcecode-64.png differ diff --git a/src/assets/img/files/spreadsheet-64.png b/src/assets/img/files/spreadsheet-64.png new file mode 100644 index 000000000..00427c0e7 Binary files /dev/null and b/src/assets/img/files/spreadsheet-64.png differ diff --git a/src/assets/img/files/text-64.png b/src/assets/img/files/text-64.png new file mode 100644 index 000000000..7b397cea5 Binary files /dev/null and b/src/assets/img/files/text-64.png differ diff --git a/src/assets/img/files/tiff-64.png b/src/assets/img/files/tiff-64.png new file mode 100644 index 000000000..c11a85e28 Binary files /dev/null and b/src/assets/img/files/tiff-64.png differ diff --git a/src/assets/img/files/unknown-64.png b/src/assets/img/files/unknown-64.png new file mode 100644 index 000000000..7f703bb83 Binary files /dev/null and b/src/assets/img/files/unknown-64.png differ diff --git a/src/assets/img/files/video-64.png b/src/assets/img/files/video-64.png new file mode 100644 index 000000000..570c4b2b3 Binary files /dev/null and b/src/assets/img/files/video-64.png differ diff --git a/src/assets/img/files/wav-64.png b/src/assets/img/files/wav-64.png new file mode 100644 index 000000000..819781a9b Binary files /dev/null and b/src/assets/img/files/wav-64.png differ diff --git a/src/assets/img/files/wmv-64.png b/src/assets/img/files/wmv-64.png new file mode 100644 index 000000000..570c4b2b3 Binary files /dev/null and b/src/assets/img/files/wmv-64.png differ diff --git a/src/assets/img/files/writer-64.png b/src/assets/img/files/writer-64.png new file mode 100644 index 000000000..6285b6ffa Binary files /dev/null and b/src/assets/img/files/writer-64.png differ diff --git a/src/classes/sqlitedb.ts b/src/classes/sqlitedb.ts index 04f9fa6f6..3c6c248d5 100644 --- a/src/classes/sqlitedb.ts +++ b/src/classes/sqlitedb.ts @@ -329,6 +329,21 @@ export class SQLiteDB { }); } + /** + * Format the data to insert in the database. Removes undefined entries so they are stored as null instead of 'undefined'. + * + * @param {object} data Data to insert. + */ + protected formatDataToInsert(data: object) : void { + // Remove undefined entries and convert null to "NULL". + for (let name in data) { + let value = data[name]; + if (typeof value == 'undefined') { + delete data[name]; + } + } + } + /** * Get all the records from a table. * @@ -585,6 +600,8 @@ export class SQLiteDB { * @return {any[]} Array with the SQL query and the params. */ protected getSqlInsertQuery(table: string, data: object) : any[] { + this.formatDataToInsert(data); + let keys = Object.keys(data), fields = keys.join(','), questionMarks = ',?'.repeat(keys.length).substr(1); @@ -773,6 +790,8 @@ export class SQLiteDB { sql, params; + this.formatDataToInsert(data); + for (let key in data) { sets.push(`${key} = ?`); } diff --git a/src/components/components.module.ts b/src/components/components.module.ts index c814ce0ec..2b462dd4d 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -24,6 +24,7 @@ import { CoreIframeComponent } from './iframe/iframe'; import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; import { CoreSearchBoxComponent } from './search-box/search-box'; +import { CoreFileComponent } from './file/file'; @NgModule({ declarations: [ @@ -34,7 +35,8 @@ import { CoreSearchBoxComponent } from './search-box/search-box'; CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, - CoreSearchBoxComponent + CoreSearchBoxComponent, + CoreFileComponent ], imports: [ IonicModule, @@ -49,7 +51,8 @@ import { CoreSearchBoxComponent } from './search-box/search-box'; CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, - CoreSearchBoxComponent + CoreSearchBoxComponent, + CoreFileComponent ] }) export class CoreComponentsModule {} diff --git a/src/components/file/file.html b/src/components/file/file.html new file mode 100644 index 000000000..e4dab9efb --- /dev/null +++ b/src/components/file/file.html @@ -0,0 +1,13 @@ + + +

{{fileName}}

+
+ + +
+ +
diff --git a/src/components/file/file.scss b/src/components/file/file.scss new file mode 100644 index 000000000..6279a3b37 --- /dev/null +++ b/src/components/file/file.scss @@ -0,0 +1,28 @@ +core-loading { + .mm-loading-container { + width: 100%; + text-align: center; + padding-top: 10px; + clear: both; + } + + .mm-loading-content { + padding-bottom: 1px; /* This makes height be real */ + } + + &.mm-loading-noheight .mm-loading-content { + height: auto; + } +} + +.scroll-content > .padding > core-loading > .mm-loading-container, +ion-content[padding] > .scroll-content > core-loading > .mm-loading-container, +.mm-loading-center .mm-loading-container { + display: table; + + .mm-loading-spinner { + display: table-cell; + text-align: center; + vertical-align: middle; + } +} diff --git a/src/components/file/file.ts b/src/components/file/file.ts new file mode 100644 index 000000000..0506bea4f --- /dev/null +++ b/src/components/file/file.ts @@ -0,0 +1,291 @@ +// (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, OnDestroy, EventEmitter } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '../../providers/app'; +import { CoreEventsProvider } from '../../providers/events'; +import { CoreFileProvider } from '../../providers/file'; +import { CoreFilepoolProvider } from '../../providers/filepool'; +import { CoreSitesProvider } from '../../providers/sites'; +import { CoreDomUtilsProvider } from '../../providers/utils/dom'; +import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype'; +import { CoreUtilsProvider } from '../../providers/utils/utils'; +import { CoreConstants } from '../../core/constants'; + +/** + * Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button + * to download/refresh it. + */ +@Component({ + selector: 'core-file', + templateUrl: 'file.html' +}) +export class CoreFileComponent implements OnInit, OnDestroy { + @Input() file: any; // The file. Must have a property 'filename' and a 'fileurl' or 'url' + @Input() component?: string; // Component the file belongs to. + @Input() componentId?: string|number; // Component ID. + @Input() timemodified?: number; // If set, the value will be used to check if the file is outdated. + @Input() canDelete?: boolean|string; // Whether file can be deleted. + @Input() alwaysDownload?: boolean|string; // Whether it should always display the refresh button when the file is downloaded. + // Use it for files that you cannot determine if they're outdated or not. + @Input() canDownload?: boolean|string = true; // Whether file can be downloaded. + @Output() onDelete?: EventEmitter; // Will notify when the delete button is clicked. + + isDownloaded: boolean; + isDownloading: boolean; + showDownload: boolean; + fileIcon: string; + fileName: string; + + protected fileUrl: string; + protected siteId: string; + protected fileSize: number; + protected observer; + + constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, + private domUtils: CoreDomUtilsProvider, private filepoolProvider: CoreFilepoolProvider, + private fileProvider: CoreFileProvider, private appProvider: CoreAppProvider, + private mimeUtils: CoreMimetypeUtilsProvider, private eventsProvider: CoreEventsProvider) { + this.onDelete = new EventEmitter(); + } + + /** + * Component being initialized. + */ + ngOnInit() { + this.canDelete = this.utils.isTrueOrOne(this.canDelete); + this.alwaysDownload = this.utils.isTrueOrOne(this.alwaysDownload); + this.canDownload = this.utils.isTrueOrOne(this.canDownload); + this.timemodified = this.timemodified || 0; + + this.fileUrl = this.file.fileurl || this.file.url; + this.siteId = this.sitesProvider.getCurrentSiteId(); + this.fileSize = this.file.filesize; + this.fileName = this.file.filename; + + if (this.file.isexternalfile) { + this.alwaysDownload = true; // Always show the download button in external files. + } + + this.fileIcon = this.mimeUtils.getFileIcon(this.file.filename); + + if (this.canDownload) { + this.calculateState(); + + // Update state when receiving events about this file. + this.filepoolProvider.getFileEventNameByUrl(this.siteId, this.fileUrl).then((eventName) => { + this.observer = this.eventsProvider.on(eventName, () => { + this.calculateState(); + }); + }); + } + } + + /** + * Convenience function to get the file state and set variables based on it. + * + * @return {Promise} Promise resolved when state has been calculated. + */ + protected calculateState() : Promise { + return this.filepoolProvider.getFileStateByUrl(this.siteId, this.fileUrl, this.timemodified).then((state) => { + let canDownload = this.sitesProvider.getCurrentSite().canDownloadFiles(); + + this.isDownloaded = state === CoreConstants.downloaded || state === CoreConstants.outdated; + this.isDownloading = canDownload && state === CoreConstants.downloading; + this.showDownload = canDownload && (state === CoreConstants.notDownloaded || state === CoreConstants.outdated || + (this.alwaysDownload && state === CoreConstants.downloaded)); + }); + } + + /** + * Download the file. + * + * @return {Promise} Promise resolved when file is downloaded. + */ + protected downloadFile() : Promise { + if (!this.sitesProvider.getCurrentSite().canDownloadFiles()) { + this.domUtils.showErrorModal('core.cannotdownloadfiles', true); + return Promise.reject(null); + } + + this.isDownloading = true; + return this.filepoolProvider.downloadUrl(this.siteId, this.fileUrl, false, this.component, this.componentId, + this.timemodified, undefined, undefined, this.file).catch(() => { + + // Call calculateState to make sure we have the right state. + return this.calculateState().then(() => { + if (this.isDownloaded) { + return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl); + } else { + return Promise.reject(null); + } + }); + }); + } + + /** + * Convenience function to open a file, downloading it if needed. + * + * @return {Promise} Promise resolved when file is opened. + */ + protected openFile() : Promise { + let fixedUrl = this.sitesProvider.getCurrentSite().fixPluginfileURL(this.fileUrl), + promise; + + if (this.fileProvider.isAvailable()) { + promise = Promise.resolve().then(() => { + // The file system is available. + let isWifi = !this.appProvider.isNetworkAccessLimited(), + isOnline = this.appProvider.isOnline(); + + if (this.isDownloaded && !this.showDownload) { + // File is downloaded, get the local file URL. + return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl, + this.component, this.componentId, this.timemodified, false, false, this.file); + } else { + if (!isOnline && !this.isDownloaded) { + // Not downloaded and user is offline, reject. + return Promise.reject(this.translate.instant('core.networkerrormsg')); + } + + let isDownloading = this.isDownloading; + this.isDownloading = true; // This check could take a while, show spinner. + return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, this.fileSize).then(() => { + if (isDownloading) { + // It's already downloading, stop. + return; + } + // Download and then return the local URL. + return this.downloadFile(); + }, () => { + // Start the download if in wifi, but return the URL right away so the file is opened. + if (isWifi && isOnline) { + this.downloadFile(); + } + + if (isDownloading || !this.isDownloaded || isOnline) { + // Not downloaded or outdated and online, return the online URL. + return fixedUrl; + } else { + // Outdated but offline, so we return the local URL. + return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl, + this.component, this.componentId, this.timemodified, false, false, this.file); + } + }); + } + }); + } else { + // Use the online URL. + promise = Promise.resolve(fixedUrl); + } + + return promise.then((url) => { + if (!url) { + return; + } + + if (url.indexOf('http') === 0) { + return this.utils.openOnlineFile(url).catch((error) => { + // Error opening the file, some apps don't allow opening online files. + if (!this.fileProvider.isAvailable()) { + return Promise.reject(error); + } else if (this.isDownloading) { + return Promise.reject(this.translate.instant('core.erroropenfiledownloading')); + } + + let subPromise; + + if (status === CoreConstants.notDownloaded) { + // File is not downloaded, download and then return the local URL. + subPromise = this.downloadFile(); + } else { + // File is outdated and can't be opened in online, return the local URL. + subPromise = this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl); + } + + return subPromise.then((url) => { + return this.utils.openFile(url); + }); + }); + } else { + return this.utils.openFile(url); + } + }); + } + + /** + * Download a file and, optionally, open it afterwards. + * + * @param {Event} e Click event. + * @param {boolean} openAfterDownload Whether the file should be opened after download. + */ + download(e: Event, openAfterDownload: boolean) : void { + e.preventDefault(); + e.stopPropagation(); + + let promise; + + if (this.isDownloading && !openAfterDownload) { + return; + } + + if (!this.appProvider.isOnline() && (!openAfterDownload || (openAfterDownload && !this.isDownloaded))) { + this.domUtils.showErrorModal('core.networkerrormsg', true); + return; + } + + if (openAfterDownload) { + // File needs to be opened now. + this.openFile().catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); + }); + } else { + // File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big. + promise = this.fileSize ? this.domUtils.confirmDownloadSize({size: this.fileSize, total: true}) : Promise.resolve(); + promise.then(() => { + // User confirmed, add the file to queue. + this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => { + this.isDownloading = true; + this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component, + this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); + this.calculateState(); + }); + }); + }); + } + }; + + /** + * Delete the file. + * + * @param {Event} e Click event. + */ + deleteFile(e: Event) : void { + e.preventDefault(); + e.stopPropagation(); + + if (this.canDelete) { + this.onDelete.emit(); + } + } + + /** + * Component destroyed. + */ + ngOnDestroy() { + this.observer && this.observer.off(); + } +} diff --git a/src/core/courses/pages/course-preview/course-preview.html b/src/core/courses/pages/course-preview/course-preview.html index 143e07f10..9bda961bf 100644 --- a/src/core/courses/pages/course-preview/course-preview.html +++ b/src/core/courses/pages/course-preview/course-preview.html @@ -25,7 +25,7 @@

{{ 'core.teachers' | translate }}

{{contact.fullname}}

- +

{{ instance.name }}

diff --git a/src/core/emulator/providers/file.ts b/src/core/emulator/providers/file.ts index 4eef27146..70c61ba0d 100644 --- a/src/core/emulator/providers/file.ts +++ b/src/core/emulator/providers/file.ts @@ -350,7 +350,6 @@ export class FileMock extends File { (navigator).webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => { window.requestFileSystem(LocalFileSystem.PERSISTENT, granted, (entry) => { basePath = entry.root.toURL(); - // this.fileProvider.setHTMLBasePath(basePath); resolve(basePath); }, reject); }, reject); diff --git a/src/providers/file.ts b/src/providers/file.ts index 895328ac9..bfe7f9c4c 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -98,7 +98,7 @@ export class CoreFileProvider { * @return {boolean} Whether the plugin is available. */ isAvailable() : boolean { - return typeof window.resolveLocalFileSystemURL !== 'undefined' && typeof FileTransfer !== 'undefined'; + return typeof window.resolveLocalFileSystemURL !== 'undefined'; } /** diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index a27518484..3a122ace8 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -327,7 +327,7 @@ export class CoreFilepoolProvider { component: component, componentId: componentId || '' }; - return db.insertOrUpdateRecord(this.LINKS_TABLE, newEntry, null); + return db.insertOrUpdateRecord(this.LINKS_TABLE, newEntry, undefined); }); } @@ -410,7 +410,7 @@ export class CoreFilepoolProvider { * @return {Promise} Promise resolved when the file is downloaded. */ protected addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number, timemodified: number, - filePath: string, options: any = {}, link?: any) : Promise { + filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any) : Promise { this.logger.debug(`Adding ${fileId} to the queue`); return this.appDB.insertRecord(this.QUEUE_TABLE, { @@ -429,7 +429,7 @@ export class CoreFilepoolProvider { // Check if the queue is running. this.checkQueueProcessing(); this.notifyFileDownloading(siteId, fileId); - return this.getQueuePromise(siteId, fileId); + return this.getQueuePromise(siteId, fileId, true, onProgress); }); } @@ -479,8 +479,7 @@ export class CoreFilepoolProvider { // Retrieve the queue deferred now if it exists to prevent errors if file is removed from queue // while we're checking if the file is in queue. - queueDeferred = this.getQueueDeferred(siteId, fileId, false); - queueDeferred.onProgress = onProgress; + queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress); return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => { let foundLink = false, @@ -530,7 +529,7 @@ export class CoreFilepoolProvider { // Update only when required. this.logger.debug(`Updating file ${fileId} which is already in queue`); return this.appDB.updateRecords(this.QUEUE_TABLE, newData, primaryKey).then(() => { - return this.getQueuePromise(siteId, fileId); + return this.getQueuePromise(siteId, fileId, true, onProgress); }); } @@ -540,14 +539,17 @@ export class CoreFilepoolProvider { // might have finished now and the deferred wouldn't be in the array anymore. return queueDeferred.promise; } else { - return this.getQueuePromise(siteId, fileId); + // Create a new deferred and return its promise. + return this.getQueuePromise(siteId, fileId, true, onProgress); } } else { - return this.addToQueue(siteId, fileId, fileUrl, priority, revision, timemodified, filePath, options, link); + return this.addToQueue( + siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); } }, () => { // Unsure why we could not get the record, let's add to the queue anyway. - return this.addToQueue(siteId, fileId, fileUrl, priority, revision, timemodified, filePath, options, link); + return this.addToQueue( + siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); }); }); }); @@ -596,15 +598,17 @@ export class CoreFilepoolProvider { // Check if the file should be downloaded. if (sizeUnknown) { if (downloadUnknown && isWifi) { - return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, null, null, 0, options); + return this.addToQueueByUrl( + siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); } } else if (size <= this.DOWNLOAD_THRESHOLD || (isWifi && size <= this.WIFI_DOWNLOAD_THRESHOLD)) { - return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, null, null, 0, options); + return this.addToQueueByUrl( + siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); } }); } else { // No need to check size, just add it to the queue. - return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, null, null, 0, options); + return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); } } @@ -817,9 +821,11 @@ export class CoreFilepoolProvider { } if (prefetch) { - promises.push(this.addToQueueByUrl(siteId, url, component, componentId, timemodified, null, null, 0, options)); + promises.push(this.addToQueueByUrl( + siteId, url, component, componentId, timemodified, undefined, undefined, 0, options)); } else { - promises.push(this.downloadUrl(siteId, url, ignoreStale, component, componentId, timemodified, null, null, options)); + promises.push(this.downloadUrl( + siteId, url, ignoreStale, component, componentId, timemodified, undefined, undefined, options)); } }); @@ -901,7 +907,7 @@ export class CoreFilepoolProvider { if (prefetch) { promise = this.addToQueueByUrl( - siteId, fileUrl, component, componentId, file.timemodified, path, null, 0, options); + siteId, fileUrl, component, componentId, file.timemodified, path, undefined, 0, options); } else { promise = this.downloadUrl( siteId, fileUrl, false, component, componentId, file.timemodified, onFileProgress, path, options); @@ -1816,9 +1822,10 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @param {string} fileId The file ID. * @param {boolean} [create=true] True if it should create a new deferred if it doesn't exist. + * @param {Function} [onProgress] Function to call on progress. * @return {any} Deferred. */ - protected getQueueDeferred(siteId: string, fileId: string, create = true): any { + protected getQueueDeferred(siteId: string, fileId: string, create = true, onProgress?: (event: any) => any): any { if (!this.queueDeferreds[siteId]) { if (!create) { return; @@ -1831,6 +1838,11 @@ export class CoreFilepoolProvider { } this.queueDeferreds[siteId][fileId] = this.utils.promiseDefer(); } + + if (onProgress) { + this.queueDeferreds[siteId][fileId].onProgress = onProgress; + } + return this.queueDeferreds[siteId][fileId]; } @@ -1854,10 +1866,11 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @param {string} fileId The file ID. * @param {boolean} [create=true] True if it should create a new promise if it doesn't exist. + * @param {Function} [onProgress] Function to call on progress. * @return {Promise} Promise. */ - protected getQueuePromise(siteId: string, fileId: string, create = true) : Promise { - return this.getQueueDeferred(siteId, fileId, create).promise; + protected getQueuePromise(siteId: string, fileId: string, create = true, onProgress?: (event: any) => any) : Promise { + return this.getQueueDeferred(siteId, fileId, create, onProgress).promise; } /** @@ -2248,7 +2261,9 @@ export class CoreFilepoolProvider { promise.then(() => { // All good, we schedule next execution. - setTimeout(this.processQueue, this.QUEUE_PROCESS_INTERVAL); + setTimeout(() => { + this.processQueue(); + }, this.QUEUE_PROCESS_INTERVAL); }, (error) => { @@ -2270,7 +2285,7 @@ export class CoreFilepoolProvider { * @return {Promise} Resolved on success. Rejected on failure. */ protected processImportantQueueItem() : Promise { - return this.appDB.getRecords(this.QUEUE_TABLE, null, 'priority DESC, added ASC', null, 0, 1).then((items) => { + return this.appDB.getRecords(this.QUEUE_TABLE, undefined, 'priority DESC, added ASC', undefined, 0, 1).then((items) => { let item = items.pop(); if (!item) { return Promise.reject(this.ERR_QUEUE_IS_EMPTY); @@ -2290,16 +2305,17 @@ export class CoreFilepoolProvider { * @return {Promise} Resolved on success. Rejected on failure. */ protected processQueueItem(item: CoreFilepoolQueueEntry) : Promise { + // Cast optional fields to undefined instead of null. let siteId = item.siteId, fileId = item.fileId, fileUrl = item.url, options = { - revision: item.revision, - timemodified: item.timemodified, - isexternalfile: item.isexternalfile, - repositorytype: item.repositorytype + revision: item.revision || undefined, + timemodified: item.timemodified || undefined, + isexternalfile: item.isexternalfile || undefined, + repositorytype: item.repositorytype || undefined }, - filePath = item.path, + filePath = item.path || undefined, links = item.links || []; this.logger.debug('Processing queue item: ' + siteId + ', ' + fileId); @@ -2335,7 +2351,7 @@ export class CoreFilepoolProvider { // Whoops, we have an error... let dropFromQueue = false; - if (typeof errorObject != 'undefined' && errorObject.source === fileUrl) { + if (errorObject && errorObject.source === fileUrl) { // This is most likely a FileTransfer error. if (errorObject.code === 1) { // FILE_NOT_FOUND_ERR. // The file was not found, most likely a 404, we remove from queue. diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index 4d55fff19..589c14b32 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -185,7 +185,7 @@ export class CoreMimetypeUtilsProvider { } } - return 'img/files/' + icon + '-64.png'; + return 'assets/img/files/' + icon + '-64.png'; } /** @@ -194,7 +194,7 @@ export class CoreMimetypeUtilsProvider { * @return {string} The path to a folder icon. */ getFolderIcon() : string { - return 'img/files/folder-64.png'; + return 'assets/img/files/folder-64.png'; } /** diff --git a/src/theme/variables.scss b/src/theme/variables.scss index 7aff83671..fd6c06cbb 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -171,3 +171,10 @@ $item-wp-avatar-size: 54px; @import "roboto"; @import "noto-sans"; + +// Moodle Mobile variables +// -------------------------------------------------- + +// Small avatar ideal for icons. +$item-media-width: 32px !default; +$item-media-height: 32px !default;