|
@ -37,5 +37,6 @@ UserInterfaceState.xcuserstate
|
||||||
e2e/build
|
e2e/build
|
||||||
/desktop/*
|
/desktop/*
|
||||||
!/desktop/assets/
|
!/desktop/assets/
|
||||||
|
!/desktop/electron.js
|
||||||
src/configconstants.ts
|
src/configconstants.ts
|
||||||
src/assets/lang
|
src/assets/lang
|
||||||
|
|
|
@ -14,6 +14,25 @@ const customConfig = {
|
||||||
'@directives': resolve('./src/directives'),
|
'@directives': resolve('./src/directives'),
|
||||||
'@pipes': resolve('./src/pipes')
|
'@pipes': resolve('./src/pipes')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
externals: [
|
||||||
|
(function () {
|
||||||
|
var IGNORES = ["fs","child_process","electron","path","assert","cluster","crypto","dns","domain","events","http","https","net","os","process","punycode","querystring","readline","repl","stream","string_decoder","tls","tty","dgram","url","util","v8","vm","zlib"];
|
||||||
|
return function (context, request, callback) {
|
||||||
|
if (IGNORES.indexOf(request) >= 0) {
|
||||||
|
return callback(null, "require('" + request + "')");
|
||||||
|
}
|
||||||
|
return callback();
|
||||||
|
};
|
||||||
|
})()
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.node$/,
|
||||||
|
use: 'node-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,253 @@
|
||||||
|
|
||||||
|
// dialog isn't used, but not requiring it throws an error.
|
||||||
|
const {app, BrowserWindow, ipcMain, shell, dialog, Menu} = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const url = require('url');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
|
let mainWindow,
|
||||||
|
appName = 'Moodle Desktop', // Default value.
|
||||||
|
isReady = false,
|
||||||
|
configRead = false;
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
// Create the browser window.
|
||||||
|
var width = 800,
|
||||||
|
height = 600;
|
||||||
|
|
||||||
|
const screen = require('electron').screen;
|
||||||
|
if (screen) {
|
||||||
|
const display = screen.getPrimaryDisplay();
|
||||||
|
if (display && display.workArea) {
|
||||||
|
width = display.workArea.width || width;
|
||||||
|
height = display.workArea.height || height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
minWidth: 400,
|
||||||
|
minHeight: 400,
|
||||||
|
textAreasAreResizable: false,
|
||||||
|
plugins: true,
|
||||||
|
show: false // Don't show it until it's ready to prevent showing a blank screen.
|
||||||
|
};
|
||||||
|
|
||||||
|
if (os.platform().indexOf('linux') === 0) {
|
||||||
|
options.icon = path.join(__dirname, '/../www/assets/icon/icon.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
|
// And load the index.html of the app.
|
||||||
|
mainWindow.loadURL(url.format({
|
||||||
|
pathname: path.join(__dirname, '/../www/index.html'),
|
||||||
|
protocol: 'file:',
|
||||||
|
slashes: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
mainWindow.once('ready-to-show', () => {
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.maximize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emitted when the window is closed.
|
||||||
|
mainWindow.on('closed', () => {
|
||||||
|
// Dereference the window object.
|
||||||
|
mainWindow = null
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.on('focus', () => {
|
||||||
|
mainWindow.webContents.send('coreAppFocused'); // Send an event to the main window.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on('ready', function() {
|
||||||
|
isReady = true;
|
||||||
|
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
if (configRead) {
|
||||||
|
setAppMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quit when all windows are closed.
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
app.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
// On macOS it's common to re-create a window in the app when the dock icon is clicked and there are no other windows open.
|
||||||
|
if (mainWindow === null) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Read the config.json file.
|
||||||
|
fs.readFile(path.join(__dirname, 'config.json'), 'utf8', (err, data) => {
|
||||||
|
configRead = true;
|
||||||
|
|
||||||
|
// Default values.
|
||||||
|
var ssoScheme = 'moodlemobile',
|
||||||
|
appId = 'com.moodle.moodlemobile';
|
||||||
|
|
||||||
|
if (!err) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
ssoScheme = data.customurlscheme;
|
||||||
|
appName = data.desktopappname;
|
||||||
|
appId = data.app_id;
|
||||||
|
} catch(ex) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default protocol (custom URL scheme).
|
||||||
|
app.setAsDefaultProtocolClient(ssoScheme);
|
||||||
|
|
||||||
|
// Fix notifications in Windows.
|
||||||
|
app.setAppUserModelId(appId);
|
||||||
|
|
||||||
|
if (isReady) {
|
||||||
|
setAppMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure that only a single instance of the app is running.
|
||||||
|
var shouldQuit = app.makeSingleInstance((argv, workingDirectory) => {
|
||||||
|
// Another instance was launched. If it was launched with a URL, it should be in the second param.
|
||||||
|
if (argv && argv[1]) {
|
||||||
|
appLaunched(argv[1]);
|
||||||
|
} else {
|
||||||
|
focusApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// For some reason, shouldQuit is always true in signed Mac apps so we should ingore it.
|
||||||
|
if (shouldQuit && os.platform().indexOf('darwin') == -1) {
|
||||||
|
// It's not the main instance of the app, kill it.
|
||||||
|
app.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for open-url events (Mac OS only).
|
||||||
|
app.on('open-url', (event, url) => {
|
||||||
|
event.preventDefault();
|
||||||
|
appLaunched(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
function appLaunched(url) {
|
||||||
|
// App was launched again with a URL. Focus the main window and send an event to treat the URL.
|
||||||
|
if (mainWindow) {
|
||||||
|
focusApp();
|
||||||
|
mainWindow.webContents.send('coreAppLaunched', url); // Send an event to the main window.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusApp() {
|
||||||
|
if (mainWindow) {
|
||||||
|
if (mainWindow.isMinimized()) {
|
||||||
|
mainWindow.restore();
|
||||||
|
}
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for events sent by the renderer processes (windows).
|
||||||
|
ipcMain.on('openItem', (event, path) => {
|
||||||
|
var result;
|
||||||
|
|
||||||
|
// Add file:// protocol if it isn't there.
|
||||||
|
if (path.indexOf('file://') == -1) {
|
||||||
|
path = 'file://' + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os.platform().indexOf('darwin') > -1) {
|
||||||
|
// Use openExternal in MacOS because openItem doesn't work in sandboxed apps.
|
||||||
|
// https://github.com/electron/electron/issues/9005
|
||||||
|
result = shell.openExternal(path);
|
||||||
|
} else {
|
||||||
|
result = shell.openItem(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
// Cannot open file, probably no app to handle it. Open the folder.
|
||||||
|
result = shell.showItemInFolder(path.replace('file://', ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
event.returnValue = result;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('closeSecondaryWindows', () => {
|
||||||
|
const windows = BrowserWindow.getAllWindows();
|
||||||
|
for (let i = 0; i < windows.length; i++) {
|
||||||
|
const win = windows[i];
|
||||||
|
if (!win.isDestroyed() && (!mainWindow || win.id != mainWindow.id)) {
|
||||||
|
win.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('focusApp', focusApp);
|
||||||
|
|
||||||
|
// Configure the app's menu.
|
||||||
|
function setAppMenu() {
|
||||||
|
let menuTemplate = [
|
||||||
|
{
|
||||||
|
label: appName,
|
||||||
|
role: 'window',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: 'CmdorCtrl+Q',
|
||||||
|
role: 'close'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Cut',
|
||||||
|
accelerator: 'CmdOrCtrl+X',
|
||||||
|
role: 'cut'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Copy',
|
||||||
|
accelerator: 'CmdOrCtrl+C',
|
||||||
|
role: 'copy'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Paste',
|
||||||
|
accelerator: 'CmdOrCtrl+V',
|
||||||
|
role: 'paste'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Select All',
|
||||||
|
accelerator: 'CmdOrCtrl+A',
|
||||||
|
role: 'selectall'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Help',
|
||||||
|
role: 'help',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Docs',
|
||||||
|
accelerator: 'CmdOrCtrl+H',
|
||||||
|
click() {
|
||||||
|
shell.openExternal('https://docs.moodle.org/en/Moodle_Mobile');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(Menu.buildFromTemplate(menuTemplate));
|
||||||
|
}
|
65
package.json
|
@ -32,7 +32,10 @@
|
||||||
"ionic:build:before": "gulp",
|
"ionic:build:before": "gulp",
|
||||||
"ionic:watch:before": "gulp",
|
"ionic:watch:before": "gulp",
|
||||||
"ionic:build:after": "gulp copy-component-templates",
|
"ionic:build:after": "gulp copy-component-templates",
|
||||||
"postionic:build": "gulp copy-component-templates"
|
"postionic:build": "gulp copy-component-templates",
|
||||||
|
"desktop.pack": "electron-builder --dir",
|
||||||
|
"desktop.dist": "electron-builder",
|
||||||
|
"windows.store": "electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store --flatten true -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "5.2.10",
|
"@angular/animations": "5.2.10",
|
||||||
|
@ -88,15 +91,21 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ionic/app-scripts": "3.1.9",
|
"@ionic/app-scripts": "3.1.9",
|
||||||
|
"electron-rebuild": "^1.8.1",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-clip-empty-files": "^0.1.2",
|
"gulp-clip-empty-files": "^0.1.2",
|
||||||
"gulp-flatten": "^0.4.0",
|
"gulp-flatten": "^0.4.0",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-rename": "^1.2.2",
|
||||||
"gulp-slash": "^1.1.3",
|
"gulp-slash": "^1.1.3",
|
||||||
|
"node-loader": "^0.6.0",
|
||||||
"through": "^2.3.8",
|
"through": "^2.3.8",
|
||||||
"typescript": "~2.6.2",
|
"typescript": "~2.6.2",
|
||||||
"webpack-merge": "^4.1.2"
|
"webpack-merge": "^4.1.2"
|
||||||
},
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"electron-windows-notifications": "^2.1.1",
|
||||||
|
"electron-builder-squirrel-windows": "^20.19.0"
|
||||||
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"electron": false
|
"electron": false
|
||||||
},
|
},
|
||||||
|
@ -105,5 +114,57 @@
|
||||||
"android",
|
"android",
|
||||||
"ios"
|
"ios"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"main": "desktop/electron.js",
|
||||||
|
"build": {
|
||||||
|
"appId": "com.moodle.moodledesktop",
|
||||||
|
"productName": "Moodle Desktop",
|
||||||
|
"files": [
|
||||||
|
"desktop/electron.js",
|
||||||
|
"www/**/*",
|
||||||
|
"!config",
|
||||||
|
"!desktop/assets",
|
||||||
|
"!desktop/dist",
|
||||||
|
"!node_modules",
|
||||||
|
"!**/e2e",
|
||||||
|
"!hooks",
|
||||||
|
"!platforms",
|
||||||
|
"!plugins",
|
||||||
|
"!resources",
|
||||||
|
"!src",
|
||||||
|
"!**/*.scss"
|
||||||
|
],
|
||||||
|
"directories": {
|
||||||
|
"output": "desktop/dist"
|
||||||
|
},
|
||||||
|
"protocols": [
|
||||||
|
{
|
||||||
|
"name": "Moodle Mobile URL",
|
||||||
|
"schemes": [
|
||||||
|
"moodlemobile"
|
||||||
|
],
|
||||||
|
"role": "Viewer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compression": "maximum",
|
||||||
|
"electronVersion": "2.0.4",
|
||||||
|
"mac": {
|
||||||
|
"category": "public.app-category.education",
|
||||||
|
"icon": "resources/desktop/icon.icns",
|
||||||
|
"target": "mas",
|
||||||
|
"extendInfo": {
|
||||||
|
"ElectronTeamID": "2NU57U5PAW"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"target": "appx",
|
||||||
|
"icon": "resources/desktop/icon.ico"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"category": "Education"
|
||||||
|
},
|
||||||
|
"snap": {
|
||||||
|
"confinement": "classic"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreFileProvider } from '@providers/file';
|
import { CoreFileProvider } from '@providers/file';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
@ -40,7 +41,8 @@ export class AddonModLtiProvider {
|
||||||
private textUtils: CoreTextUtilsProvider,
|
private textUtils: CoreTextUtilsProvider,
|
||||||
private urlUtils: CoreUrlUtilsProvider,
|
private urlUtils: CoreUrlUtilsProvider,
|
||||||
private utils: CoreUtilsProvider,
|
private utils: CoreUtilsProvider,
|
||||||
private translate: TranslateService) {}
|
private translate: TranslateService,
|
||||||
|
private appProvider: CoreAppProvider) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete launcher.
|
* Delete launcher.
|
||||||
|
@ -84,7 +86,11 @@ export class AddonModLtiProvider {
|
||||||
'</script> \n';
|
'</script> \n';
|
||||||
|
|
||||||
return this.fileProvider.writeFile(this.LAUNCHER_FILE_NAME, text).then((entry) => {
|
return this.fileProvider.writeFile(this.LAUNCHER_FILE_NAME, text).then((entry) => {
|
||||||
return entry.toURL();
|
if (this.appProvider.isDesktop()) {
|
||||||
|
return entry.toInternalURL();
|
||||||
|
} else {
|
||||||
|
return entry.toURL();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,10 @@ export class AddonModLtiModuleHandler implements CoreCourseModuleHandler {
|
||||||
const icon = ltiData.secureicon || ltiData.icon;
|
const icon = ltiData.secureicon || ltiData.icon;
|
||||||
if (icon) {
|
if (icon) {
|
||||||
const siteId = this.sitesProvider.getCurrentSiteId();
|
const siteId = this.sitesProvider.getCurrentSiteId();
|
||||||
this.filepoolProvider.downloadUrl(siteId, icon, false, AddonModLtiProvider.COMPONENT, module.id).then((url) => {
|
this.filepoolProvider.downloadUrl(siteId, icon, false, AddonModLtiProvider.COMPONENT, module.id).then(() => {
|
||||||
|
// Get the internal URL.
|
||||||
|
return this.filepoolProvider.getSrcByUrl(siteId, icon, AddonModLtiProvider.COMPONENT, module.id);
|
||||||
|
}).then((url) => {
|
||||||
data.icon = url;
|
data.icon = url;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Error downloading. If we're online we'll set the online url.
|
// Error downloading. If we're online we'll set the online url.
|
||||||
|
|
|
@ -58,10 +58,14 @@ export class AddonNotificationsProvider {
|
||||||
if (cid && cid[1]) {
|
if (cid && cid[1]) {
|
||||||
notification.courseid = cid[1];
|
notification.courseid = cid[1];
|
||||||
}
|
}
|
||||||
// Try to get the profile picture of the user.
|
if (notification.useridfrom > 0) {
|
||||||
this.userProvider.getProfile(notification.useridfrom, notification.courseid, true).then((user) => {
|
// Try to get the profile picture of the user.
|
||||||
notification.profileimageurlfrom = user.profileimageurl;
|
this.userProvider.getProfile(notification.useridfrom, notification.courseid, true).then((user) => {
|
||||||
});
|
notification.profileimageurlfrom = user.profileimageurl;
|
||||||
|
}).catch(() => {
|
||||||
|
// Error getting user. This can happen if device is offline or the user is deleted.
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 56 KiB |
|
@ -0,0 +1,606 @@
|
||||||
|
// (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 { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file includes all the classes needed to emulate file system in NodeJS (desktop apps).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulate Entry object of the Cordova file plugin using NodeJS functions.
|
||||||
|
* It includes fileSystem and nativeURL as the Cordova plugin does, but they aren't used.
|
||||||
|
*/
|
||||||
|
export class EntryMock {
|
||||||
|
|
||||||
|
protected fs = require('fs');
|
||||||
|
|
||||||
|
constructor(protected textUtils: CoreTextUtilsProvider, protected mimeUtils: CoreMimetypeUtilsProvider, public isFile: boolean,
|
||||||
|
public isDirectory: boolean, public name: string = '', public fullPath: string = '', public fileSystem?: FileSystem,
|
||||||
|
public nativeURL?: string) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the file or directory.
|
||||||
|
*
|
||||||
|
* @param {Entry} parent The folder where to move the file to.
|
||||||
|
* @param {string} newName The new name for the file.
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
copyTo(parent: Entry, newName: string, successCallback: Function, errorCallback: Function): void {
|
||||||
|
newName = newName || this.name;
|
||||||
|
|
||||||
|
// There is no function to copy a file, read the source and write the dest.
|
||||||
|
const srcPath = this.fullPath,
|
||||||
|
destPath = this.textUtils.concatenatePaths(parent.fullPath, newName),
|
||||||
|
reader = this.fs.createReadStream(srcPath),
|
||||||
|
writer = this.fs.createWriteStream(destPath);
|
||||||
|
|
||||||
|
// Use a promise to make sure only one callback is called.
|
||||||
|
new Promise((resolve, reject): void => {
|
||||||
|
reader.on('error', reject);
|
||||||
|
|
||||||
|
writer.on('error', reject);
|
||||||
|
|
||||||
|
writer.on('close', resolve);
|
||||||
|
|
||||||
|
reader.pipe(writer);
|
||||||
|
}).then(() => {
|
||||||
|
const constructor = this.isDirectory ? DirectoryEntryMock : FileEntryMock;
|
||||||
|
successCallback && successCallback(
|
||||||
|
new constructor(this.textUtils, this.mimeUtils, newName, destPath));
|
||||||
|
}).catch((error) => {
|
||||||
|
errorCallback && errorCallback(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the entry's metadata.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
getMetadata(successCallback: Function, errorCallback: Function): void {
|
||||||
|
this.fs.stat(this.fullPath, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
successCallback && successCallback({
|
||||||
|
size: stats.size,
|
||||||
|
modificationTime: stats.mtime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the parent directory.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
getParent(successCallback: Function, errorCallback: Function): void {
|
||||||
|
// Remove last slash if present and get the path of the parent.
|
||||||
|
const fullPath = this.fullPath.slice(-1) == '/' ? this.fullPath.slice(0, -1) : this.fullPath,
|
||||||
|
parentPath = fullPath.substr(0, fullPath.lastIndexOf('/'));
|
||||||
|
|
||||||
|
// Check that parent exists.
|
||||||
|
this.fs.stat(parentPath, (err, stats) => {
|
||||||
|
if (err || !stats.isDirectory()) {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
const fileName = parentPath.substr(parentPath.lastIndexOf('/') + 1);
|
||||||
|
successCallback && successCallback(
|
||||||
|
new DirectoryEntryMock(this.textUtils, this.mimeUtils, fileName, parentPath));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the file or directory.
|
||||||
|
*
|
||||||
|
* @param {Entry} parent The folder where to move the file to.
|
||||||
|
* @param {string} newName The new name for the file.
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
moveTo(parent: Entry, newName: string, successCallback: Function, errorCallback: Function): void {
|
||||||
|
newName = newName || this.name;
|
||||||
|
|
||||||
|
const srcPath = this.fullPath,
|
||||||
|
destPath = this.textUtils.concatenatePaths(parent.fullPath, newName);
|
||||||
|
|
||||||
|
this.fs.rename(srcPath, destPath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
const constructor = this.isDirectory ? DirectoryEntryMock : FileEntryMock;
|
||||||
|
successCallback && successCallback(
|
||||||
|
new constructor(this.textUtils, this.mimeUtils, newName, destPath));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the entry.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
remove(successCallback: Function, errorCallback: Function): void {
|
||||||
|
const removeFn = this.isDirectory ? this.fs.rmdir : this.fs.unlink;
|
||||||
|
removeFn(this.fullPath, (err) => {
|
||||||
|
if (err < 0) {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
successCallback && successCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the entry's metadata.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
* @param {any} metadataObject The metadata to set.
|
||||||
|
*/
|
||||||
|
setMetadata(successCallback: Function, errorCallback: Function, metadataObject: any): void {
|
||||||
|
// Not supported.
|
||||||
|
errorCallback && errorCallback('Not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the internal URL of the Entry.
|
||||||
|
*
|
||||||
|
* @return {string} Internal URL.
|
||||||
|
*/
|
||||||
|
toInternalURL(): string {
|
||||||
|
return 'file://' + this.fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL of the Entry.
|
||||||
|
*
|
||||||
|
* @return {string} URL.
|
||||||
|
*/
|
||||||
|
toURL(): string {
|
||||||
|
return this.fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulate DirectoryEntry object of the Cordova file plugin using NodeJS functions.
|
||||||
|
*/
|
||||||
|
export class DirectoryEntryMock extends EntryMock {
|
||||||
|
|
||||||
|
constructor(textUtils: CoreTextUtilsProvider, mimeUtils: CoreMimetypeUtilsProvider, name: string = '', fullPath: string = '',
|
||||||
|
fileSystem?: FileSystem, nativeURL?: string) {
|
||||||
|
|
||||||
|
super(textUtils, mimeUtils, false, true, name, fullPath, fileSystem, nativeURL);
|
||||||
|
|
||||||
|
// Add trailing slash if it is missing.
|
||||||
|
if ((this.fullPath) && !/\/$/.test(this.fullPath)) {
|
||||||
|
this.fullPath += '/';
|
||||||
|
}
|
||||||
|
if (this.nativeURL && !/\/$/.test(this.nativeURL)) {
|
||||||
|
this.nativeURL += '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create reader.
|
||||||
|
*
|
||||||
|
* @return {DirectoryReader} Reader.
|
||||||
|
*/
|
||||||
|
createReader(): DirectoryReader {
|
||||||
|
return new DirectoryReaderMock(this.textUtils, this.mimeUtils, this.fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an empty folder.
|
||||||
|
*
|
||||||
|
* @param {string} path Path of the folder.
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
protected deleteEmptyFolder(path: string, successCallback: Function, errorCallback: Function): void {
|
||||||
|
this.fs.rmdir(path, (err) => {
|
||||||
|
if (err) {
|
||||||
|
// Error removing directory.
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
successCallback && successCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a directory inside this directory entry.
|
||||||
|
*
|
||||||
|
* @param {string} path Path of the dir.
|
||||||
|
* @param {any} options Options.
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
getDirectory(path: string, options: any, successCallback: Function, errorCallback: Function): void {
|
||||||
|
this.getDirOrFile(true, path, options, successCallback, errorCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for getDirectory and getFile.
|
||||||
|
*
|
||||||
|
* @param {boolean} isDir True if getting a directory, false if getting a file.
|
||||||
|
* @param {string} path Path of the file or dir.
|
||||||
|
* @param {any} options Options.
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
protected getDirOrFile(isDir: boolean, path: string, options: any, successCallback: Function, errorCallback: Function): void {
|
||||||
|
|
||||||
|
// Success, return the DirectoryEntry or FileEntry.
|
||||||
|
const success = (): void => {
|
||||||
|
const constructor = isDir ? DirectoryEntryMock : FileEntryMock;
|
||||||
|
successCallback && successCallback(
|
||||||
|
new constructor(this.textUtils, this.mimeUtils, fileName, fileDirPath));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the file/dir.
|
||||||
|
const create = (done): void => {
|
||||||
|
if (isDir) {
|
||||||
|
this.fs.mkdir(fileDirPath, done);
|
||||||
|
} else {
|
||||||
|
this.fs.writeFile(fileDirPath, '', done);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileName = path.substr(path.lastIndexOf('/') + 1),
|
||||||
|
fileDirPath = this.textUtils.concatenatePaths(this.fullPath, path);
|
||||||
|
|
||||||
|
// Check if file/dir exists.
|
||||||
|
this.fs.stat(fileDirPath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
if (options.create) {
|
||||||
|
// File/Dir probably doesn't exist, create it.
|
||||||
|
create((error2) => {
|
||||||
|
if (!error2) {
|
||||||
|
// File created successfully, return it.
|
||||||
|
success();
|
||||||
|
} else if (error2.code === 'EEXIST') {
|
||||||
|
// File exists, success.
|
||||||
|
success();
|
||||||
|
} else if (error2.code === 'ENOENT') {
|
||||||
|
// Seems like the parent doesn't exist, create it too.
|
||||||
|
const parent = fileDirPath.substring(0, fileDirPath.lastIndexOf('/'));
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
this.getDirectory(parent, options, () => {
|
||||||
|
// Parent created, try to create the child again.
|
||||||
|
create((error3) => {
|
||||||
|
if (!error3) {
|
||||||
|
success();
|
||||||
|
} else {
|
||||||
|
errorCallback && errorCallback(error3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, errorCallback);
|
||||||
|
} else {
|
||||||
|
errorCallback && errorCallback(error2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorCallback && errorCallback(error2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a file inside this directory entry.
|
||||||
|
*
|
||||||
|
* @param {string} path Path of the dir.
|
||||||
|
* @param {any} options Options.
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
getFile(path: string, options: any, successCallback: Function, errorCallback: Function): void {
|
||||||
|
this.getDirOrFile(false, path, options, successCallback, errorCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the directory and all its contents.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
removeRecursively(successCallback: Function, errorCallback: Function): void {
|
||||||
|
// Use a promise to make sure only one callback is called.
|
||||||
|
new Promise((resolve, reject): void => {
|
||||||
|
this.removeRecursiveFn(this.fullPath, resolve, reject);
|
||||||
|
}).then(() => {
|
||||||
|
successCallback && successCallback();
|
||||||
|
}).catch((error) => {
|
||||||
|
errorCallback && errorCallback(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a file or folder recursively.
|
||||||
|
*
|
||||||
|
* @param {string} path Path of the folder.
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
protected removeRecursiveFn(path: string, successCallback: Function, errorCallback: Function): void {
|
||||||
|
// Check if it exists.
|
||||||
|
this.fs.stat(path, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
// File not found, reject.
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else if (stats.isFile()) {
|
||||||
|
// It's a file, remove it.
|
||||||
|
this.fs.unlink(path, (err) => {
|
||||||
|
if (err) {
|
||||||
|
// Error removing file, reject.
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
successCallback && successCallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// It's a directory, read the contents.
|
||||||
|
this.fs.readdir(path, (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
// Error reading directory contents, reject.
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else if (!files.length) {
|
||||||
|
// No files to delete, delete the folder.
|
||||||
|
this.deleteEmptyFolder(path, successCallback, errorCallback);
|
||||||
|
} else {
|
||||||
|
// Remove all the files and directories.
|
||||||
|
let removed = 0;
|
||||||
|
files.forEach((filename) => {
|
||||||
|
this.removeRecursiveFn(this.textUtils.concatenatePaths(path, filename), () => {
|
||||||
|
// Success deleting the file/dir.
|
||||||
|
removed++;
|
||||||
|
if (removed == files.length) {
|
||||||
|
// All files deleted, delete the folder.
|
||||||
|
this.deleteEmptyFolder(path, successCallback, errorCallback);
|
||||||
|
}
|
||||||
|
}, errorCallback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulate FileEntry object of the Cordova file plugin using NodeJS functions.
|
||||||
|
*/
|
||||||
|
export class FileEntryMock extends EntryMock {
|
||||||
|
|
||||||
|
constructor(textUtils: CoreTextUtilsProvider, mimeUtils: CoreMimetypeUtilsProvider, name: string = '', fullPath: string = '',
|
||||||
|
fileSystem?: FileSystem, nativeURL?: string) {
|
||||||
|
|
||||||
|
super(textUtils, mimeUtils, true, false, name, fullPath, fileSystem, nativeURL);
|
||||||
|
|
||||||
|
// Remove trailing slash if it is present.
|
||||||
|
if (this.fullPath && /\/$/.test(this.fullPath)) {
|
||||||
|
this.fullPath = this.fullPath.substring(0, this.fullPath.length - 1);
|
||||||
|
}
|
||||||
|
if (this.nativeURL && /\/$/.test(this.nativeURL)) {
|
||||||
|
this.nativeURL = this.nativeURL.substring(0, this.nativeURL.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create writer.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
createWriter(successCallback: Function, errorCallback: Function): void {
|
||||||
|
this.file((file) => {
|
||||||
|
successCallback && successCallback(new FileWriterMock(this.textUtils, this.mimeUtils, file));
|
||||||
|
}, errorCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file data.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
file(successCallback: Function, errorCallback: Function): void {
|
||||||
|
// Get the metadata to know the time modified.
|
||||||
|
this.getMetadata((metadata) => {
|
||||||
|
// Read the file.
|
||||||
|
this.fs.readFile(this.fullPath, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
// Create a File instance and return it.
|
||||||
|
data = Uint8Array.from(data).buffer; // Convert the NodeJS Buffer to ArrayBuffer.
|
||||||
|
|
||||||
|
const file: any = new File([data], this.name || '', {
|
||||||
|
lastModified: metadata.modificationTime || null,
|
||||||
|
type: this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(this.name)) || null
|
||||||
|
});
|
||||||
|
file.localURL = this.fullPath;
|
||||||
|
file.start = 0;
|
||||||
|
file.end = file.size;
|
||||||
|
|
||||||
|
successCallback && successCallback(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, errorCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulate DirectoryReader object of the Cordova file plugin using NodeJS functions.
|
||||||
|
*/
|
||||||
|
export class DirectoryReaderMock implements DirectoryReader {
|
||||||
|
|
||||||
|
protected fs = require('fs');
|
||||||
|
|
||||||
|
constructor(protected textUtils: CoreTextUtilsProvider, protected mimeUtils: CoreMimetypeUtilsProvider,
|
||||||
|
protected localURL: string = null) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read entries inside a folder.
|
||||||
|
*
|
||||||
|
* @param {Function} successCallback Success callback.
|
||||||
|
* @param {Function} errorCallback Error callback.
|
||||||
|
*/
|
||||||
|
readEntries(successCallback: Function, errorCallback: Function): void {
|
||||||
|
this.fs.readdir(this.localURL, (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
// Use try/catch because it includes sync calls.
|
||||||
|
try {
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const fileName = files[i],
|
||||||
|
filePath = this.textUtils.concatenatePaths(this.localURL, fileName),
|
||||||
|
stats = this.fs.statSync(filePath); // Use sync function to make code simpler.
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
entries.push(new DirectoryEntryMock(this.textUtils, this.mimeUtils, fileName,
|
||||||
|
filePath));
|
||||||
|
} else if (stats.isFile()) {
|
||||||
|
entries.push(new FileEntryMock(this.textUtils, this.mimeUtils, fileName, filePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
successCallback && successCallback(entries);
|
||||||
|
} catch (ex) {
|
||||||
|
errorCallback && errorCallback(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulate FileWriter object of the Cordova file plugin using NodeJS functions.
|
||||||
|
*/
|
||||||
|
export class FileWriterMock {
|
||||||
|
|
||||||
|
protected fs = require('fs');
|
||||||
|
|
||||||
|
fileName = '';
|
||||||
|
length = 0;
|
||||||
|
localURL: string;
|
||||||
|
size = 0;
|
||||||
|
position = 0; // Default is to write at the beginning of the file.
|
||||||
|
readyState = 0; // EMPTY.
|
||||||
|
result: any = null;
|
||||||
|
error: any = null;
|
||||||
|
|
||||||
|
// Event handlers.
|
||||||
|
onwritestart: (event?: ProgressEvent) => void; // When writing starts.
|
||||||
|
onprogress: (event?: ProgressEvent) => void; // While writing the file, and reporting partial file data.
|
||||||
|
onwrite: (event?: ProgressEvent) => void; // When the write has successfully completed.
|
||||||
|
onwriteend: (event?: ProgressEvent) => void; // When the request has completed (either in success or failure).
|
||||||
|
onabort: (event?: ProgressEvent) => void; // When the write has been aborted.
|
||||||
|
onerror: (event?: ProgressEvent) => void; // When the write has failed (see errors).
|
||||||
|
|
||||||
|
constructor(protected textUtils: CoreTextUtilsProvider, protected mimeUtils: CoreMimetypeUtilsProvider, protected file: any) {
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
this.localURL = file.localURL || file;
|
||||||
|
this.length = file.size || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate file operation.
|
||||||
|
*/
|
||||||
|
abort(): void {
|
||||||
|
// Not supported.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file position at which the next write will occur.
|
||||||
|
*
|
||||||
|
* @param {number} offset If nonnegative, an absolute byte offset into the file.
|
||||||
|
* If negative, an offset back from the end of the file.
|
||||||
|
*/
|
||||||
|
seek(offset: number): void {
|
||||||
|
this.position = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the length of the file to that specified. If shortening the file, data beyond the new length
|
||||||
|
* will be discarded. If extending the file, the existing data will be zero-padded up to the new length.
|
||||||
|
*
|
||||||
|
* @param {number} size The size to which the length of the file is to be adjusted, measured in bytes.
|
||||||
|
*/
|
||||||
|
truncate(size: number): void {
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write some data into the file.
|
||||||
|
*
|
||||||
|
* @param {any} data The data to write.
|
||||||
|
*/
|
||||||
|
write(data: any): void {
|
||||||
|
if (data && data.toString() == '[object Blob]') {
|
||||||
|
// Can't write Blobs, convert it to a Buffer.
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (): void => {
|
||||||
|
if (reader.readyState == 2) {
|
||||||
|
this.writeFile(new Buffer(reader.result));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(data);
|
||||||
|
} else if (data && data.toString() == '[object ArrayBuffer]') {
|
||||||
|
// Convert it to a Buffer.
|
||||||
|
data = new Uint8Array(data);
|
||||||
|
this.writeFile(Buffer.from(data));
|
||||||
|
} else {
|
||||||
|
this.writeFile(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write some data into the file.
|
||||||
|
*
|
||||||
|
* @param {Buffer} data The data to write.
|
||||||
|
*/
|
||||||
|
protected writeFile(data: Buffer): void {
|
||||||
|
this.fs.writeFile(this.localURL, data, (err) => {
|
||||||
|
if (err) {
|
||||||
|
this.onerror && this.onerror(err);
|
||||||
|
} else {
|
||||||
|
this.onwrite && this.onwrite();
|
||||||
|
}
|
||||||
|
this.onwriteend && this.onwriteend();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onwritestart && this.onwritestart();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,21 +16,20 @@ import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreFileProvider } from '@providers/file';
|
import { CoreFileProvider } from '@providers/file';
|
||||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||||
import { Observable, Observer } from 'rxjs';
|
import { Observable, Observer } from 'rxjs';
|
||||||
import { InAppBrowserObject, InAppBrowserEvent } from '@ionic-native/in-app-browser';
|
import { InAppBrowserEvent } from '@ionic-native/in-app-browser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emulates the Cordova InAppBrowserObject in desktop apps.
|
* Emulates the Cordova InAppBrowserObject in desktop apps.
|
||||||
|
* We aren't extending InAppBrowserObject because its constructor also opens a window, so we'd end up with 2 windows.
|
||||||
*/
|
*/
|
||||||
export class InAppBrowserObjectMock extends InAppBrowserObject {
|
export class InAppBrowserObjectMock {
|
||||||
protected window;
|
protected window;
|
||||||
protected browserWindow;
|
protected browserWindow;
|
||||||
protected screen;
|
protected screen;
|
||||||
protected isSSO: boolean;
|
protected isSSO: boolean;
|
||||||
protected isLinux: boolean;
|
|
||||||
|
|
||||||
constructor(appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private urlUtils: CoreUrlUtilsProvider,
|
constructor(appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private urlUtils: CoreUrlUtilsProvider,
|
||||||
private url: string, target?: string, options: string = '') {
|
private url: string, target?: string, options: string = 'location=yes') {
|
||||||
super(url, target, options);
|
|
||||||
|
|
||||||
if (!appProvider.isDesktop()) {
|
if (!appProvider.isDesktop()) {
|
||||||
// This plugin is only supported in desktop.
|
// This plugin is only supported in desktop.
|
||||||
|
@ -40,12 +39,21 @@ export class InAppBrowserObjectMock extends InAppBrowserObject {
|
||||||
this.browserWindow = require('electron').remote.BrowserWindow;
|
this.browserWindow = require('electron').remote.BrowserWindow;
|
||||||
this.screen = require('electron').screen;
|
this.screen = require('electron').screen;
|
||||||
this.isSSO = !!(url && url.match(/\/launch\.php\?service=.+&passport=/));
|
this.isSSO = !!(url && url.match(/\/launch\.php\?service=.+&passport=/));
|
||||||
this.isLinux = appProvider.isLinux();
|
|
||||||
|
|
||||||
let width = 800,
|
let width = 800,
|
||||||
height = 600,
|
height = 600,
|
||||||
display;
|
display;
|
||||||
const bwOptions: any = {};
|
|
||||||
|
const bwOptions: any = {
|
||||||
|
webPreferences: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a separate session for inappbrowser so we can clear its data without affecting the app.
|
||||||
|
bwOptions.webPreferences.session = require('electron').remote.session.fromPartition('inappbrowsersession');
|
||||||
|
} catch (ex) {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
if (screen) {
|
if (screen) {
|
||||||
display = this.screen.getPrimaryDisplay();
|
display = this.screen.getPrimaryDisplay();
|
||||||
|
@ -67,11 +75,14 @@ export class InAppBrowserObjectMock extends InAppBrowserObject {
|
||||||
if (options.indexOf('fullscreen=yes') != -1) {
|
if (options.indexOf('fullscreen=yes') != -1) {
|
||||||
bwOptions.fullscreen = true;
|
bwOptions.fullscreen = true;
|
||||||
}
|
}
|
||||||
|
if (options.indexOf('clearsessioncache=yes') != -1 && bwOptions.webPreferences.session) {
|
||||||
|
bwOptions.webPreferences.session.clearStorageData({storages: 'cookies'});
|
||||||
|
}
|
||||||
|
|
||||||
this.window = new this.browserWindow(bwOptions);
|
this.window = new this.browserWindow(bwOptions);
|
||||||
this.window.loadURL(url);
|
this.window.loadURL(url);
|
||||||
|
|
||||||
if (this.isLinux && this.isSSO) {
|
if (this.isSSO) {
|
||||||
// SSO in Linux. Simulate it's an iOS device so we can retrieve the launch URL.
|
// SSO in Linux. Simulate it's an iOS device so we can retrieve the launch URL.
|
||||||
// This is needed because custom URL scheme is not supported in Linux.
|
// This is needed because custom URL scheme is not supported in Linux.
|
||||||
const userAgent = 'Mozilla/5.0 (iPad) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60';
|
const userAgent = 'Mozilla/5.0 (iPad) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60';
|
||||||
|
@ -83,7 +94,16 @@ export class InAppBrowserObjectMock extends InAppBrowserObject {
|
||||||
* Close the window.
|
* Close the window.
|
||||||
*/
|
*/
|
||||||
close(): void {
|
close(): void {
|
||||||
this.window.close();
|
if (this.window.isDestroyed()) {
|
||||||
|
// Window already closed, nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.window.close();
|
||||||
|
} catch (ex) {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,11 +133,16 @@ export class InAppBrowserObjectMock extends InAppBrowserObject {
|
||||||
* @return {Promise<string>} Promise resolved with the launch URL.
|
* @return {Promise<string>} Promise resolved with the launch URL.
|
||||||
*/
|
*/
|
||||||
protected getLaunchUrl(retry: number = 0): Promise<string> {
|
protected getLaunchUrl(retry: number = 0): Promise<string> {
|
||||||
|
|
||||||
|
if (this.window.isDestroyed()) {
|
||||||
|
// Window is destroyed, stop.
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject): void => {
|
return new Promise((resolve, reject): void => {
|
||||||
// Execute Javascript to retrieve the launch link.
|
// Execute Javascript to retrieve the launch link.
|
||||||
const jsCode = 'var el = document.querySelector("#launchapp"); el && el.href;';
|
const jsCode = 'var el = document.querySelector("#launchapp"); el && el.href;';
|
||||||
let found = false;
|
let found = false;
|
||||||
|
|
||||||
this.window.webContents.executeJavaScript(jsCode).then((launchUrl) => {
|
this.window.webContents.executeJavaScript(jsCode).then((launchUrl) => {
|
||||||
found = true;
|
found = true;
|
||||||
resolve(launchUrl);
|
resolve(launchUrl);
|
||||||
|
@ -179,7 +204,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject {
|
||||||
// Helper functions to handle events.
|
// Helper functions to handle events.
|
||||||
const received = (event, url): void => {
|
const received = (event, url): void => {
|
||||||
try {
|
try {
|
||||||
event.url = url || this.window.getURL();
|
event.url = url || (this.window.isDestroyed() ? '' : this.window.getURL());
|
||||||
event.type = name;
|
event.type = name;
|
||||||
observer.next(event);
|
observer.next(event);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
@ -203,7 +228,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject {
|
||||||
case 'loadstart':
|
case 'loadstart':
|
||||||
this.window.webContents.on('did-start-loading', received);
|
this.window.webContents.on('did-start-loading', received);
|
||||||
|
|
||||||
if (this.isLinux && this.isSSO) {
|
if (this.isSSO) {
|
||||||
// Linux doesn't support custom URL Schemes. Check if launch page is loaded.
|
// Linux doesn't support custom URL Schemes. Check if launch page is loaded.
|
||||||
this.window.webContents.on('did-finish-load', finishLoad);
|
this.window.webContents.on('did-finish-load', finishLoad);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ import { CoreEmulatorHelperProvider } from './providers/helper';
|
||||||
import { CoreEmulatorCaptureHelperProvider } from './providers/capture-helper';
|
import { CoreEmulatorCaptureHelperProvider } from './providers/capture-helper';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreFileProvider } from '@providers/file';
|
import { CoreFileProvider } from '@providers/file';
|
||||||
|
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
@ -125,10 +126,11 @@ export const IONIC_NATIVE_PROVIDERS = [
|
||||||
Device,
|
Device,
|
||||||
{
|
{
|
||||||
provide: File,
|
provide: File,
|
||||||
deps: [CoreAppProvider, CoreTextUtilsProvider],
|
deps: [CoreAppProvider, CoreTextUtilsProvider, CoreMimetypeUtilsProvider],
|
||||||
useFactory: (appProvider: CoreAppProvider, textUtils: CoreTextUtilsProvider): File => {
|
useFactory: (appProvider: CoreAppProvider, textUtils: CoreTextUtilsProvider, mimeUtils: CoreMimetypeUtilsProvider)
|
||||||
|
: File => {
|
||||||
// Use platform instead of CoreAppProvider to prevent circular dependencies.
|
// Use platform instead of CoreAppProvider to prevent circular dependencies.
|
||||||
return appProvider.isMobile() ? new File() : new FileMock(appProvider, textUtils);
|
return appProvider.isMobile() ? new File() : new FileMock(appProvider, textUtils, mimeUtils);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -214,11 +216,16 @@ export class CoreEmulatorModule {
|
||||||
|
|
||||||
// Emulate Custom URL Scheme plugin in desktop apps.
|
// Emulate Custom URL Scheme plugin in desktop apps.
|
||||||
if (appProvider.isDesktop()) {
|
if (appProvider.isDesktop()) {
|
||||||
require('electron').ipcRenderer.on('mmAppLaunched', (event, url) => {
|
require('electron').ipcRenderer.on('coreAppLaunched', (event, url) => {
|
||||||
if (typeof win.handleOpenURL != 'undefined') {
|
if (typeof win.handleOpenURL != 'undefined') {
|
||||||
win.handleOpenURL(url);
|
win.handleOpenURL(url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Listen for 'resume' events.
|
||||||
|
require('electron').ipcRenderer.on('coreAppFocused', () => {
|
||||||
|
document.dispatchEvent(new Event('resume'));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!appProvider.isMobile()) {
|
if (!appProvider.isMobile()) {
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { File, Entry, DirectoryEntry, FileEntry, FileError, IWriteOptions } from '@ionic-native/file';
|
import { File, Entry, DirectoryEntry, FileEntry, FileError, IWriteOptions } from '@ionic-native/file';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreConfigConstants } from '../../../configconstants';
|
import { CoreConfigConstants } from '../../../configconstants';
|
||||||
|
import { FileEntryMock, DirectoryEntryMock } from '../classes/filesystem';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emulates the Cordova File plugin in desktop apps and in browser.
|
* Emulates the Cordova File plugin in desktop apps and in browser.
|
||||||
|
@ -25,7 +27,8 @@ import { CoreConfigConstants } from '../../../configconstants';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileMock extends File {
|
export class FileMock extends File {
|
||||||
|
|
||||||
constructor(private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider) {
|
constructor(private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider,
|
||||||
|
private mimeUtils: CoreMimetypeUtilsProvider) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +193,34 @@ export class FileMock extends File {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulate Cordova file plugin using NodeJS functions. This is only for NodeJS environments,
|
||||||
|
* browser works with the default resolveLocalFileSystemURL.
|
||||||
|
*
|
||||||
|
* @param {any} fs Node module 'fs'.
|
||||||
|
*/
|
||||||
|
protected emulateCordovaFileForDesktop(fs: any): void {
|
||||||
|
if (!this.appProvider.isDesktop()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement resolveLocalFileSystemURL.
|
||||||
|
window.resolveLocalFileSystemURL = (path: string, successCallback: Function, errorCallback: Function): void => {
|
||||||
|
// Check that the file/dir exists.
|
||||||
|
fs.stat(path, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
errorCallback && errorCallback(err);
|
||||||
|
} else {
|
||||||
|
// The file/dir exists, return an instance.
|
||||||
|
const constructor = stats.isDirectory() ? DirectoryEntryMock : FileEntryMock,
|
||||||
|
fileName = path.substr(path.lastIndexOf('/') + 1);
|
||||||
|
|
||||||
|
successCallback && successCallback(new constructor(this.textUtils, this.mimeUtils, fileName, path));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill the message for an error.
|
* Fill the message for an error.
|
||||||
*
|
*
|
||||||
|
@ -336,7 +367,7 @@ export class FileMock extends File {
|
||||||
const fs = require('fs'),
|
const fs = require('fs'),
|
||||||
app = require('electron').remote.app;
|
app = require('electron').remote.app;
|
||||||
|
|
||||||
// @todo emulateCordovaFileForDesktop(fs);
|
this.emulateCordovaFileForDesktop(fs);
|
||||||
|
|
||||||
// Initialize File System. Get the path to use.
|
// Initialize File System. Get the path to use.
|
||||||
basePath = app.getPath('documents') || app.getPath('home');
|
basePath = app.getPath('documents') || app.getPath('home');
|
||||||
|
@ -352,7 +383,6 @@ export class FileMock extends File {
|
||||||
fs.mkdir(basePath, (e) => {
|
fs.mkdir(basePath, (e) => {
|
||||||
if (!e || (e && e.code === 'EEXIST')) {
|
if (!e || (e && e.code === 'EEXIST')) {
|
||||||
// Create successful or it already exists. Resolve.
|
// Create successful or it already exists. Resolve.
|
||||||
// @todo this.fileProvider.setHTMLBasePath(basePath);
|
|
||||||
resolve(basePath);
|
resolve(basePath);
|
||||||
} else {
|
} else {
|
||||||
reject('Error creating base path.');
|
reject('Error creating base path.');
|
||||||
|
|
|
@ -13,9 +13,21 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Globalization } from '@ionic-native/globalization';
|
import { Globalization, GlobalizationOptions } from '@ionic-native/globalization';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock the Globalization Error.
|
||||||
|
*/
|
||||||
|
export class GlobalizationErrorMock implements GlobalizationError {
|
||||||
|
static UNKNOWN_ERROR = 0;
|
||||||
|
static FORMATTING_ERROR = 1;
|
||||||
|
static PARSING_ERROR = 2;
|
||||||
|
static PATTERN_ERROR = 3;
|
||||||
|
|
||||||
|
constructor(public code: number, public message: string) { }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emulates the Cordova Globalization plugin in desktop apps and in browser.
|
* Emulates the Cordova Globalization plugin in desktop apps and in browser.
|
||||||
*/
|
*/
|
||||||
|
@ -26,6 +38,28 @@ export class GlobalizationMock extends Globalization {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts date to string.
|
||||||
|
*
|
||||||
|
* @param {Date} date Date you wish to convert
|
||||||
|
* @param options Options for the converted date. Length, selector.
|
||||||
|
* @returns {Promise<{value: string}>} Returns a promise when the date has been converted.
|
||||||
|
*/
|
||||||
|
dateToString(date: Date, options: GlobalizationOptions): Promise<{ value: string; }> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pattern string to format and parse currency values according to the client's user preferences and ISO 4217
|
||||||
|
* currency code.
|
||||||
|
*
|
||||||
|
* @param {string} currencyCode Currency Code.
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
getCurrencyPattern(currencyCode: string): Promise<any> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current locale.
|
* Get the current locale.
|
||||||
*
|
*
|
||||||
|
@ -47,6 +81,35 @@ export class GlobalizationMock extends Globalization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of the names of the months or days of the week, depending on the client's user preferences and calendar.
|
||||||
|
*
|
||||||
|
* @param options Object with type (narrow or wide) and item (month or days).
|
||||||
|
* @returns {Promise<{value: Array<string>}>} Returns a promise.
|
||||||
|
*/
|
||||||
|
getDateNames(options: { type: string; item: string; }): Promise<{ value: Array<string>; }> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pattern string to format and parse dates according to the client's user preferences.
|
||||||
|
*
|
||||||
|
* @param options Object with the format length and selector
|
||||||
|
* @returns {Promise<any>} Returns a promise.
|
||||||
|
*/
|
||||||
|
getDatePattern(options: GlobalizationOptions): Promise<any> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first day of the week according to the client's user preferences and calendar.
|
||||||
|
*
|
||||||
|
* @returns {Promise<{value: string}>} returns a promise with the value
|
||||||
|
*/
|
||||||
|
getFirstDayOfWeek(): Promise<{ value: string; }> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current locale name.
|
* Get the current locale name.
|
||||||
*
|
*
|
||||||
|
@ -57,12 +120,21 @@ export class GlobalizationMock extends Globalization {
|
||||||
if (locale) {
|
if (locale) {
|
||||||
return Promise.resolve({ value: locale });
|
return Promise.resolve({ value: locale });
|
||||||
} else {
|
} else {
|
||||||
const error = { code: GlobalizationError.UNKNOWN_ERROR, message: 'Cannot get language' };
|
const error = new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Cannot get language');
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a pattern string to format and parse numbers according to the client's user preferences.
|
||||||
|
* @param options Can be decimal, percent, or currency.
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
getNumberPattern(options: { type: string; }): Promise<any> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the current preferred language.
|
* Get the current preferred language.
|
||||||
*
|
*
|
||||||
|
@ -71,4 +143,46 @@ export class GlobalizationMock extends Globalization {
|
||||||
getPreferredLanguage(): Promise<{ value: string }> {
|
getPreferredLanguage(): Promise<{ value: string }> {
|
||||||
return this.getLocaleName();
|
return this.getLocaleName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether daylight savings time is in effect for a given date using the client's time zone and calendar.
|
||||||
|
*
|
||||||
|
* @param {data} date Date to process.
|
||||||
|
* @returns {Promise<{dst: string}>} reutrns a promise with the value
|
||||||
|
*/
|
||||||
|
isDayLightSavingsTime(date: Date): Promise<{ dst: string; }> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a number formatted as a string according to the client's user preferences.
|
||||||
|
* @param numberToConvert {Number} The number to convert
|
||||||
|
* @param options {Object} Object with property `type` that can be set to: decimal, percent, or currency.
|
||||||
|
*/
|
||||||
|
numberToString(numberToConvert: number, options: { type: string; }): Promise<{ value: string; }> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a date formatted as a string, according to the client's user preferences and calendar using the time zone of the
|
||||||
|
* client, and returns the corresponding date object.
|
||||||
|
*
|
||||||
|
* @param {string} dateString Date as a string to be converted
|
||||||
|
* @param options Options for the converted date. Length, selector.
|
||||||
|
* @returns {Promise<any>} Returns a promise when the date has been converted.
|
||||||
|
*/
|
||||||
|
stringToDate(dateString: string, options: GlobalizationOptions): Promise<any> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} stringToConvert String you want to conver to a number.
|
||||||
|
*
|
||||||
|
* @param options The type of number you want to return. Can be decimal, percent, or currency.
|
||||||
|
* @returns {Promise<{ value: number | string }>} Returns a promise with the value.
|
||||||
|
*/
|
||||||
|
stringToNumber(stringToConvert: string, options: { type: string; }): Promise<{ value: number | string; }> {
|
||||||
|
return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { InAppBrowser, InAppBrowserObject } from '@ionic-native/in-app-browser';
|
import { InAppBrowser } from '@ionic-native/in-app-browser';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreFileProvider } from '@providers/file';
|
import { CoreFileProvider } from '@providers/file';
|
||||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||||
|
@ -36,9 +36,16 @@ export class InAppBrowserMock extends InAppBrowser {
|
||||||
* @param {string} url The URL to load.
|
* @param {string} url The URL to load.
|
||||||
* @param {string} [target] The target in which to load the URL, an optional parameter that defaults to _self.
|
* @param {string} [target] The target in which to load the URL, an optional parameter that defaults to _self.
|
||||||
* @param {string} [options] Options for the InAppBrowser.
|
* @param {string} [options] Options for the InAppBrowser.
|
||||||
* @return {InAppBrowserObject} The new instance.
|
* @return {any} The new instance.
|
||||||
*/
|
*/
|
||||||
create(url: string, target?: string, options: string = ''): InAppBrowserObject {
|
create(url: string, target?: string, options: string = 'location=yes'): any {
|
||||||
|
if (options && typeof options !== 'string') {
|
||||||
|
// Convert to string.
|
||||||
|
options = Object.keys(options).map((key) => {
|
||||||
|
return key + '=' + options[key];
|
||||||
|
}).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.appProvider.isDesktop()) {
|
if (!this.appProvider.isDesktop()) {
|
||||||
return super.create(url, target, options);
|
return super.create(url, target, options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,10 @@ export class LocalNotificationsMock extends LocalNotifications {
|
||||||
* @return {Void}
|
* @return {Void}
|
||||||
*/
|
*/
|
||||||
protected cancelNotification(id: number, omitEvent: boolean, eventName: string): void {
|
protected cancelNotification(id: number, omitEvent: boolean, eventName: string): void {
|
||||||
|
if (!this.scheduled[id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const notification = this.scheduled[id].notification;
|
const notification = this.scheduled[id].notification;
|
||||||
|
|
||||||
clearTimeout(this.scheduled[id].timeout);
|
clearTimeout(this.scheduled[id].timeout);
|
||||||
|
@ -698,7 +702,7 @@ export class LocalNotificationsMock extends LocalNotifications {
|
||||||
id : notification.id,
|
id : notification.id,
|
||||||
title: notification.title,
|
title: notification.title,
|
||||||
text: notification.text,
|
text: notification.text,
|
||||||
at: notification.at ? notification.at.getTime() : 0,
|
at: notification.at ? (typeof notification.at == 'object' ? notification.at.getTime() : notification.at) : 0,
|
||||||
data: notification.data ? JSON.stringify(notification.data) : '{}',
|
data: notification.data ? JSON.stringify(notification.data) : '{}',
|
||||||
triggered: triggered ? 1 : 0
|
triggered: triggered ? 1 : 0
|
||||||
};
|
};
|
||||||
|
|
|
@ -91,8 +91,11 @@ export class CoreMainMenuPage implements OnDestroy {
|
||||||
for (let i = 0; i < this.tabs.length; i++) {
|
for (let i = 0; i < this.tabs.length; i++) {
|
||||||
const tab = this.tabs[i];
|
const tab = this.tabs[i];
|
||||||
if (tab.page == this.redirectPage) {
|
if (tab.page == this.redirectPage) {
|
||||||
|
// Tab found. Set the params and unset the redirect page.
|
||||||
this.initialTab = i + 1;
|
this.initialTab = i + 1;
|
||||||
tab.pageParams = Object.assign(tab.pageParams || {}, this.redirectParams);
|
tab.pageParams = Object.assign(tab.pageParams || {}, this.redirectParams);
|
||||||
|
this.redirectPage = null;
|
||||||
|
this.redirectParams = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<p>{{ 'core.settings.total' | translate }}</p>
|
<p>{{ 'core.settings.total' | translate }}</p>
|
||||||
<p item-end>{{ totalUsage | coreBytesToSize }}</p>
|
<p item-end>{{ totalUsage | coreBytesToSize }}</p>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item-divider color="light">
|
<ion-item-divider color="light" *ngIf="showFreeSpace">
|
||||||
<p>{{ 'core.settings.estimatedfreespace' | translate }}</p>
|
<p>{{ 'core.settings.estimatedfreespace' | translate }}</p>
|
||||||
<p item-end>{{ freeSpace | coreBytesToSize }}</p>
|
<p item-end>{{ freeSpace | coreBytesToSize }}</p>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import { Component, } from '@angular/core';
|
import { Component, } from '@angular/core';
|
||||||
import { IonicPage } from 'ionic-angular';
|
import { IonicPage } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreFileProvider } from '@providers/file';
|
import { CoreFileProvider } from '@providers/file';
|
||||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
@ -36,11 +37,13 @@ export class CoreSettingsSpaceUsagePage {
|
||||||
currentSiteId = '';
|
currentSiteId = '';
|
||||||
totalUsage = 0;
|
totalUsage = 0;
|
||||||
freeSpace = 0;
|
freeSpace = 0;
|
||||||
|
showFreeSpace = true;
|
||||||
|
|
||||||
constructor(private fileProvider: CoreFileProvider, private filePoolProvider: CoreFilepoolProvider,
|
constructor(private fileProvider: CoreFileProvider, private filePoolProvider: CoreFilepoolProvider,
|
||||||
private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider,
|
private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider,
|
||||||
private translate: TranslateService, private domUtils: CoreDomUtilsProvider) {
|
private translate: TranslateService, private domUtils: CoreDomUtilsProvider, appProvider: CoreAppProvider) {
|
||||||
this.currentSiteId = this.sitesProvider.getCurrentSiteId();
|
this.currentSiteId = this.sitesProvider.getCurrentSiteId();
|
||||||
|
this.showFreeSpace = !appProvider.isDesktop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,10 +115,15 @@ export class CoreSettingsSpaceUsagePage {
|
||||||
* @return {Promise<any>} Resolved when done.
|
* @return {Promise<any>} Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected fetchData(): Promise<any> {
|
protected fetchData(): Promise<any> {
|
||||||
return Promise.all([
|
const promises = [
|
||||||
this.calculateSizeUsage().then(() => this.calculateTotalUsage()),
|
this.calculateSizeUsage().then(() => this.calculateTotalUsage()),
|
||||||
this.calculateFreeSpace(),
|
];
|
||||||
]);
|
|
||||||
|
if (this.showFreeSpace) {
|
||||||
|
promises.push(this.calculateFreeSpace());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from './user-delegate';
|
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from './user-delegate';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile links email handler.
|
* Profile links email handler.
|
||||||
|
@ -25,7 +26,7 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler {
|
||||||
priority = 700;
|
priority = 700;
|
||||||
type = CoreUserDelegate.TYPE_COMMUNICATION;
|
type = CoreUserDelegate.TYPE_COMMUNICATION;
|
||||||
|
|
||||||
constructor(protected sitesProvider: CoreSitesProvider) { }
|
constructor(protected sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if handler is enabled.
|
* Check if handler is enabled.
|
||||||
|
@ -63,7 +64,8 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler {
|
||||||
action: (event, navCtrl, user, courseId): void => {
|
action: (event, navCtrl, user, courseId): void => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
window.open('mailto:' + user.email, '_blank');
|
|
||||||
|
this.utils.openInBrowser('mailto:' + user.email);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1757,7 +1757,12 @@ export class CoreFilepoolProvider {
|
||||||
if (this.fileProvider.isAvailable()) {
|
if (this.fileProvider.isAvailable()) {
|
||||||
return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => {
|
return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => {
|
||||||
return this.fileProvider.getFile(path).then((fileEntry) => {
|
return this.fileProvider.getFile(path).then((fileEntry) => {
|
||||||
return fileEntry.toURL();
|
// This URL is usually used to launch files or put them in HTML. In desktop we need the internal URL.
|
||||||
|
if (this.appProvider.isDesktop()) {
|
||||||
|
return fileEntry.toInternalURL();
|
||||||
|
} else {
|
||||||
|
return fileEntry.toURL();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,10 +105,6 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
{
|
{
|
||||||
name: 'desktop_local_notifications',
|
name: 'desktop_local_notifications',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
|
||||||
name: 'at',
|
|
||||||
type: 'date'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'data',
|
name: 'data',
|
||||||
type: 'object'
|
type: 'object'
|
||||||
|
|
|
@ -853,7 +853,7 @@ export class CoreUtilsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the rest of platforms we need to open them in InAppBrowser.
|
// In the rest of platforms we need to open them in InAppBrowser.
|
||||||
window.open(url, '_blank');
|
this.openInApp(url);
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|