Merge pull request #1727 from moodlehq/integration
MOBILE-2795 release: Merge integration into mastermain
commit
caa3d11700
|
@ -22,12 +22,12 @@ dist/
|
||||||
node_modules/
|
node_modules/
|
||||||
tmp/
|
tmp/
|
||||||
temp/
|
temp/
|
||||||
hooks/
|
|
||||||
platforms/
|
platforms/
|
||||||
/plugins/
|
/plugins/
|
||||||
/plugins/android.json
|
/plugins/android.json
|
||||||
/plugins/ios.json
|
/plugins/ios.json
|
||||||
www/
|
www/
|
||||||
|
!www/README.md
|
||||||
$RECYCLE.BIN/
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
@ -39,4 +39,3 @@ e2e/build
|
||||||
!/desktop/assets/
|
!/desktop/assets/
|
||||||
!/desktop/electron.js
|
!/desktop/electron.js
|
||||||
src/configconstants.ts
|
src/configconstants.ts
|
||||||
src/assets/lang
|
|
||||||
|
|
|
@ -13,4 +13,8 @@ before_script:
|
||||||
- rm -Rf node_modules/electron-builder-squirrel-windows node_modules/electron-windows-notifications #Avoid electron fail
|
- rm -Rf node_modules/electron-builder-squirrel-windows node_modules/electron-windows-notifications #Avoid electron fail
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- npm run build
|
- npm run build
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- chmod +x scripts/aot.sh
|
||||||
|
- scripts/aot.sh
|
||||||
|
|
|
@ -12,4 +12,11 @@ This is the primary repository of source code for the official Moodle Mobile app
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
[Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
[Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
|
Big Thanks
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com)
|
||||||
|
|
||||||
|
![Sauce Labs Logo](https://user-images.githubusercontent.com/557037/43443976-d88d5a78-94a2-11e8-8915-9f06521423dd.png)
|
65
config.xml
65
config.xml
|
@ -1,5 +1,5 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<widget id="com.moodle.moodlemobile" version="3.5.2" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
<widget id="com.moodle.moodlemobile" version="3.6.0" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
<name>Moodle</name>
|
<name>Moodle</name>
|
||||||
<description>Moodle official app</description>
|
<description>Moodle official app</description>
|
||||||
<author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author>
|
<author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author>
|
||||||
|
@ -94,42 +94,39 @@
|
||||||
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
|
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
|
||||||
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
|
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
|
||||||
</platform>
|
</platform>
|
||||||
<plugin name="cordova-plugin-file" spec="^6.0.1" />
|
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="1.1.1" />
|
||||||
<plugin name="cordova-plugin-file-transfer" spec="^1.7.1" />
|
<plugin name="cordova-android-support-gradle-release" spec="2.0.1">
|
||||||
<plugin name="cordova-plugin-camera" spec="^4.0.3">
|
<variable name="ANDROID_SUPPORT_VERSION" value="27.1.0" />
|
||||||
<variable name="CAMERA_USAGE_DESCRIPTION" value="We need camera access to take pictures so you can attach them as part of your submissions." />
|
|
||||||
<variable name="PHOTOLIBRARY_USAGE_DESCRIPTION" value="We need photo library access to get pictures from there so you can attach them as part of your submissions." />
|
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin name="cordova-plugin-media-capture" spec="^3.0.2">
|
<plugin name="cordova-clipboard" spec="1.2.1" />
|
||||||
<variable name="CAMERA_USAGE_DESCRIPTION" value="We need camera access to take pictures so you can attach them as part of your submissions." />
|
<plugin name="cordova-plugin-badge" spec="0.8.8" />
|
||||||
<variable name="PHOTOLIBRARY_USAGE_DESCRIPTION" value="We need photo library access to get pictures from there so you can attach them as part of your submissions." />
|
<plugin name="cordova-plugin-camera" spec="4.0.3" />
|
||||||
<variable name="MICROPHONE_USAGE_DESCRIPTION" value="We need microphone access to record sounds so you can attach them as part of your submissions." />
|
<plugin name="cordova-plugin-customurlscheme" spec="4.3.0">
|
||||||
|
<variable name="URL_SCHEME" value="moodlemobile" />
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin name="cordova-plugin-device" spec="^2.0.2" />
|
<plugin name="cordova-plugin-device" spec="2.0.2" />
|
||||||
<plugin name="cordova-plugin-globalization" spec="^1.11.0" />
|
<plugin name="cordova-plugin-file" spec="6.0.1" />
|
||||||
<plugin name="cordova-plugin-inappbrowser" spec="^3.0.0" />
|
<plugin name="cordova-plugin-file-opener2" spec="2.0.19" />
|
||||||
<plugin name="cordova-plugin-network-information" spec="^2.0.1" />
|
<plugin name="cordova-plugin-file-transfer" spec="1.7.1" />
|
||||||
<plugin name="cordova-plugin-statusbar" spec="^2.4.2" />
|
<plugin name="cordova-plugin-globalization" spec="1.11.0" />
|
||||||
<plugin name="cordova-plugin-whitelist" spec="^1.3.3" />
|
<plugin name="cordova-plugin-inappbrowser" spec="3.0.0" />
|
||||||
<plugin name="cordova-plugin-splashscreen" spec="^5.0.2" />
|
<plugin name="cordova-plugin-ionic-keyboard" spec="2.1.3" />
|
||||||
<plugin name="cordova-clipboard" spec="^1.2.1" />
|
<plugin name="cordova-plugin-local-notifications-mm" spec="1.0.13" />
|
||||||
<plugin name="nl.kingsquare.cordova.background-audio" spec="^1.0.1" />
|
<plugin name="cordova-plugin-media-capture" spec="3.0.2" />
|
||||||
|
<plugin name="cordova-plugin-network-information" spec="2.0.1" />
|
||||||
|
<plugin name="cordova-plugin-screen-orientation" spec="3.0.1" />
|
||||||
|
<plugin name="cordova-plugin-splashscreen" spec="5.0.2" />
|
||||||
|
<plugin name="cordova-plugin-statusbar" spec="2.4.2" />
|
||||||
|
<plugin name="cordova-plugin-whitelist" spec="1.3.3" />
|
||||||
|
<plugin name="cordova-plugin-zip" spec="3.1.0" />
|
||||||
|
<plugin name="cordova-sqlite-storage" spec="2.6.0" />
|
||||||
|
<plugin name="nl.kingsquare.cordova.background-audio" spec="1.0.1" />
|
||||||
<plugin name="phonegap-plugin-push" spec="https://github.com/moodlemobile/phonegap-plugin-push.git#moodle">
|
<plugin name="phonegap-plugin-push" spec="https://github.com/moodlemobile/phonegap-plugin-push.git#moodle">
|
||||||
<variable name="SENDER_ID" value="694767596569" />
|
<variable name="SENDER_ID" value="694767596569" />
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin name="cordova-plugin-customurlscheme" spec="^4.3.0">
|
<edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application/activity[@android:name='MainActivity']">
|
||||||
<variable name="URL_SCHEME" value="moodlemobile" />
|
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|screenLayout|smallestScreenSize" android:debuggable="true" />
|
||||||
</plugin>
|
</edit-config>
|
||||||
<plugin name="ionic-plugin-keyboard" spec="^2.2.1" />
|
<engine name="android" spec="7.1.2" />
|
||||||
<plugin name="cordova-plugin-zip" spec="^3.1.0" />
|
<engine name="ios" spec="4.5.5" />
|
||||||
<plugin name="cordova-plugin-local-notifications-mm" spec="^1.0.13" />
|
|
||||||
<plugin name="cordova-plugin-file-opener2" spec="^2.0.19" />
|
|
||||||
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="^1.1.0" />
|
|
||||||
<plugin name="cordova-sqlite-evcore-extbuild-free" spec="^0.9.7" />
|
|
||||||
<plugin name="cordova-plugin-badge" spec="^0.8.7" />
|
|
||||||
<plugin name="cordova-android-support-gradle-release" spec="^1.4.4">
|
|
||||||
<variable name="ANDROID_SUPPORT_VERSION" value="27.1.0" />
|
|
||||||
</plugin>
|
|
||||||
<engine name="android" spec="7.0.0" />
|
|
||||||
<engine name="ios" spec="4.5.4" />
|
|
||||||
</widget>
|
</widget>
|
||||||
|
|
|
@ -14,5 +14,7 @@
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.files.user-selected.read-only</key>
|
<key>com.apple.security.files.user-selected.read-only</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
|
@ -6,7 +6,7 @@
|
||||||
<Identity Name="3312ADB7.MoodleDesktop"
|
<Identity Name="3312ADB7.MoodleDesktop"
|
||||||
ProcessorArchitecture="x64"
|
ProcessorArchitecture="x64"
|
||||||
Publisher="CN=33CDCDF6-1EB5-4827-9897-ED25C91A32F6"
|
Publisher="CN=33CDCDF6-1EB5-4827-9897-ED25C91A32F6"
|
||||||
Version="3.5.1.0" />
|
Version="3.6.0.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Moodle Desktop</DisplayName>
|
<DisplayName>Moodle Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Moodle Pty Ltd.</PublisherDisplayName>
|
<PublisherDisplayName>Moodle Pty Ltd.</PublisherDisplayName>
|
||||||
|
|
|
@ -5,6 +5,7 @@ const path = require('path');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
const userAgent = 'MoodleMobile';
|
||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// 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.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
|
@ -64,6 +65,9 @@ function createWindow() {
|
||||||
mainWindow.on('focus', () => {
|
mainWindow.on('focus', () => {
|
||||||
mainWindow.webContents.send('coreAppFocused'); // Send an event to the main window.
|
mainWindow.webContents.send('coreAppFocused'); // Send an event to the main window.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Append some text to the user agent.
|
||||||
|
mainWindow.webContents.setUserAgent(mainWindow.webContents.getUserAgent() + ' ' + userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method will be called when Electron has finished initialization and is ready to create browser windows.
|
// This method will be called when Electron has finished initialization and is ready to create browser windows.
|
||||||
|
|
234
gulpfile.js
234
gulpfile.js
|
@ -9,6 +9,7 @@ var gulp = require('gulp'),
|
||||||
flatten = require('gulp-flatten'),
|
flatten = require('gulp-flatten'),
|
||||||
npmPath = require('path'),
|
npmPath = require('path'),
|
||||||
File = gutil.File,
|
File = gutil.File,
|
||||||
|
exec = require('child_process').exec,
|
||||||
license = '' +
|
license = '' +
|
||||||
'// (C) Copyright 2015 Martin Dougiamas\n' +
|
'// (C) Copyright 2015 Martin Dougiamas\n' +
|
||||||
'//\n' +
|
'//\n' +
|
||||||
|
@ -24,17 +25,6 @@ var gulp = require('gulp'),
|
||||||
'// See the License for the specific language governing permissions and\n' +
|
'// See the License for the specific language governing permissions and\n' +
|
||||||
'// limitations under the License.\n\n';
|
'// limitations under the License.\n\n';
|
||||||
|
|
||||||
// Get the names of the JSON files inside a directory.
|
|
||||||
function getFilenames(dir) {
|
|
||||||
if (fs.existsSync(dir)) {
|
|
||||||
return fs.readdirSync(dir).filter(function(file) {
|
|
||||||
return file.indexOf('.json') > -1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy a property from one object to another, adding a prefix to the key if needed.
|
* Copy a property from one object to another, adding a prefix to the key if needed.
|
||||||
* @param {Object} target Object to copy the properties to.
|
* @param {Object} target Object to copy the properties to.
|
||||||
|
@ -75,7 +65,8 @@ function treatMergedData(data) {
|
||||||
var mergedOrdered = {};
|
var mergedOrdered = {};
|
||||||
|
|
||||||
for (var filepath in data) {
|
for (var filepath in data) {
|
||||||
var pathSplit = filepath.split('/');
|
var pathSplit = filepath.split('/'),
|
||||||
|
prefix;
|
||||||
|
|
||||||
pathSplit.pop();
|
pathSplit.pop();
|
||||||
|
|
||||||
|
@ -120,71 +111,53 @@ function treatMergedData(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build lang files.
|
* Build lang file.
|
||||||
*
|
*
|
||||||
* @param {String[]} filenames Names of the language files.
|
* @param {String} language Language to translate.
|
||||||
* @param {String[]} langPaths Paths to the possible language files.
|
* @param {String[]} langPaths Paths to the possible language files.
|
||||||
* @param {String} buildDest Path where to leave the built files.
|
* @param {String} buildDest Path where to leave the built files.
|
||||||
* @param {Function} done Function to call when done.
|
* @param {Function} done Function to call when done.
|
||||||
* @return {Void}
|
* @return {Void}
|
||||||
*/
|
*/
|
||||||
function buildLangs(filenames, langPaths, buildDest, done) {
|
function buildLang(language, langPaths, buildDest, done) {
|
||||||
if (!filenames || !filenames.length) {
|
var filename = language + '.json',
|
||||||
// If no filenames supplied, stop. Maybe it's an empty lang folder.
|
data = {},
|
||||||
done();
|
firstFile = null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = 0;
|
var paths = langPaths.map(function(path) {
|
||||||
|
if (path.slice(-1) != '/') {
|
||||||
function taskFinished() {
|
path = path + '/';
|
||||||
count++;
|
|
||||||
if (count == filenames.length) {
|
|
||||||
done();
|
|
||||||
}
|
}
|
||||||
}
|
return path + language + '.json';
|
||||||
|
|
||||||
// Now create the build files for each supported language.
|
|
||||||
filenames.forEach(function(filename) {
|
|
||||||
var language = filename.replace('.json', ''),
|
|
||||||
data = {},
|
|
||||||
firstFile = null;
|
|
||||||
|
|
||||||
var paths = langPaths.map(function(path) {
|
|
||||||
if (path.slice(-1) != '/') {
|
|
||||||
path = path + '/';
|
|
||||||
}
|
|
||||||
return path + language + '.json';
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.src(paths)
|
|
||||||
.pipe(slash())
|
|
||||||
.pipe(clipEmptyFiles())
|
|
||||||
.pipe(through(function(file) {
|
|
||||||
if (!firstFile) {
|
|
||||||
firstFile = file;
|
|
||||||
}
|
|
||||||
return treatFile(file, data);
|
|
||||||
}, function() {
|
|
||||||
/* This implementation is based on gulp-jsoncombine module.
|
|
||||||
* https://github.com/reflog/gulp-jsoncombine */
|
|
||||||
if (firstFile) {
|
|
||||||
var joinedPath = path.join(firstFile.base, language+'.json');
|
|
||||||
|
|
||||||
var joinedFile = new File({
|
|
||||||
cwd: firstFile.cwd,
|
|
||||||
base: firstFile.base,
|
|
||||||
path: joinedPath,
|
|
||||||
contents: treatMergedData(data)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.emit('data', joinedFile);
|
|
||||||
}
|
|
||||||
this.emit('end');
|
|
||||||
}))
|
|
||||||
.pipe(gulp.dest(buildDest))
|
|
||||||
.on('end', taskFinished);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.src(paths, { allowEmpty: true })
|
||||||
|
.pipe(slash())
|
||||||
|
.pipe(clipEmptyFiles())
|
||||||
|
.pipe(through(function(file) {
|
||||||
|
if (!firstFile) {
|
||||||
|
firstFile = file;
|
||||||
|
}
|
||||||
|
return treatFile(file, data);
|
||||||
|
}, function() {
|
||||||
|
/* This implementation is based on gulp-jsoncombine module.
|
||||||
|
* https://github.com/reflog/gulp-jsoncombine */
|
||||||
|
if (firstFile) {
|
||||||
|
var joinedPath = path.join(firstFile.base, language+'.json');
|
||||||
|
|
||||||
|
var joinedFile = new File({
|
||||||
|
cwd: firstFile.cwd,
|
||||||
|
base: firstFile.base,
|
||||||
|
path: joinedPath,
|
||||||
|
contents: treatMergedData(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('data', joinedFile);
|
||||||
|
}
|
||||||
|
this.emit('end');
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest(buildDest))
|
||||||
|
.on('end', done);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a folder and all its contents.
|
// Delete a folder and all its contents.
|
||||||
|
@ -204,10 +177,7 @@ function deleteFolderRecursive(path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of app lang files. To be used only if cannot get it from filesystem.
|
// List of app lang files. To be used only if cannot get it from filesystem.
|
||||||
var appLangFiles = ['ar.json', 'bg.json', 'ca.json', 'cs.json', 'da.json', 'de.json', 'en.json', 'es-mx.json', 'es.json', 'eu.json',
|
var paths = {
|
||||||
'fa.json', 'fr.json', 'he.json', 'hu.json', 'it.json', 'ja.json', 'nl.json', 'pl.json', 'pt-br.json', 'pt.json', 'ro.json',
|
|
||||||
'ru.json', 'sv.json', 'tr.json', 'zh-cn.json', 'zh-tw.json'],
|
|
||||||
paths = {
|
|
||||||
src: './src',
|
src: './src',
|
||||||
assets: './src/assets',
|
assets: './src/assets',
|
||||||
lang: [
|
lang: [
|
||||||
|
@ -220,76 +190,84 @@ var appLangFiles = ['ar.json', 'bg.json', 'ca.json', 'cs.json', 'da.json', 'de.j
|
||||||
config: './src/config.json',
|
config: './src/config.json',
|
||||||
};
|
};
|
||||||
|
|
||||||
gulp.task('default', ['lang', 'config']);
|
|
||||||
|
|
||||||
gulp.task('watch', function() {
|
|
||||||
var langsPaths = paths.lang.map(function(path) {
|
|
||||||
return path + '*.json';
|
|
||||||
});
|
|
||||||
gulp.watch(langsPaths, { interval: 500 }, ['lang']);
|
|
||||||
gulp.watch(paths.config, { interval: 500 }, ['config']);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build the language files into a single file per language.
|
// Build the language files into a single file per language.
|
||||||
gulp.task('lang', function(done) {
|
gulp.task('lang', function(done) {
|
||||||
// Get filenames to know which languages are available.
|
buildLang('en', paths.lang, path.join(paths.assets, 'lang'), done);
|
||||||
var filenames = getFilenames(paths.lang[0]);
|
|
||||||
|
|
||||||
buildLangs(filenames, paths.lang, path.join(paths.assets, 'lang'), done);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Convert config.json into a TypeScript class.
|
// Convert config.json into a TypeScript class.
|
||||||
gulp.task('config', function(done) {
|
gulp.task('config', function(done) {
|
||||||
gulp.src(paths.config)
|
// Get the last commit.
|
||||||
.pipe(through(function(file) {
|
exec('git log -1 --pretty=format:"%H"', function (err, commit, stderr) {
|
||||||
// Convert the contents of the file into a TypeScript class.
|
if (err) {
|
||||||
// Disable the rule variable-name in the file.
|
console.error('An error occurred while getting the last commit: ' + err);
|
||||||
var config = JSON.parse(file.contents.toString()),
|
} else if (stderr) {
|
||||||
contents = license + '// tslint:disable: variable-name\n' + 'export class CoreConfigConstants {\n';
|
console.error('An error occurred while getting the last commit: ' + stderr);
|
||||||
|
}
|
||||||
|
|
||||||
for (var key in config) {
|
gulp.src(paths.config)
|
||||||
var value = config[key];
|
.pipe(through(function(file) {
|
||||||
if (typeof value == 'string') {
|
// Convert the contents of the file into a TypeScript class.
|
||||||
// Wrap the string in ' .
|
// Disable the rule variable-name in the file.
|
||||||
value = "'" + value + "'";
|
var config = JSON.parse(file.contents.toString()),
|
||||||
} else if (typeof value != 'number' && typeof value != 'boolean') {
|
contents = license + '// tslint:disable: variable-name\n' + 'export class CoreConfigConstants {\n',
|
||||||
// Stringify with 4 spaces of indentation, and then add 4 more spaces in each line.
|
that = this;
|
||||||
value = JSON.stringify(value, null, 4).replace(/^(?: )/gm, ' ').replace(/^(?:})/gm, ' }');
|
|
||||||
// Replace " by ' in values.
|
|
||||||
value = value.replace(/: "([^"]*)"/g, ": '$1'");
|
|
||||||
|
|
||||||
// Check if the keys have "-" in it.
|
for (var key in config) {
|
||||||
var matches = value.match(/"([^"]*\-[^"]*)":/g);
|
var value = config[key];
|
||||||
if (matches) {
|
if (typeof value == 'string') {
|
||||||
// Replace " by ' in keys. We cannot remove them because keys have chars like '-'.
|
// Wrap the string in ' and scape them.
|
||||||
value = value.replace(/"([^"]*)":/g, "'$1':");
|
value = "'" + value.replace(/([^\\])'/g, "$1\\'") + "'";
|
||||||
} else {
|
} else if (typeof value != 'number' && typeof value != 'boolean') {
|
||||||
// Remove ' in keys.
|
// Stringify with 4 spaces of indentation, and then add 4 more spaces in each line.
|
||||||
value = value.replace(/"([^"]*)":/g, "$1:");
|
value = JSON.stringify(value, null, 4).replace(/^(?: )/gm, ' ').replace(/^(?:})/gm, ' }');
|
||||||
|
// Replace " by ' in values.
|
||||||
|
value = value.replace(/: "([^"]*)"/g, ": '$1'");
|
||||||
|
|
||||||
|
// Check if the keys have "-" in it.
|
||||||
|
var matches = value.match(/"([^"]*\-[^"]*)":/g);
|
||||||
|
if (matches) {
|
||||||
|
// Replace " by ' in keys. We cannot remove them because keys have chars like '-'.
|
||||||
|
value = value.replace(/"([^"]*)":/g, "'$1':");
|
||||||
|
} else {
|
||||||
|
// Remove ' in keys.
|
||||||
|
value = value.replace(/"([^"]*)":/g, "$1:");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add type any to the key.
|
||||||
|
key = key + ': any';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add type any to the key.
|
// If key has quotation marks, remove them.
|
||||||
key = key + ': any';
|
if (key[0] == '"') {
|
||||||
|
key = key.substr(1, key.length - 2);
|
||||||
|
}
|
||||||
|
contents += ' static ' + key + ' = ' + value + ';\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
// If key has quotation marks, remove them.
|
// Add compilation info.
|
||||||
if (key[0] == '"') {
|
contents += ' static compilationtime = ' + Date.now() + ';\n';
|
||||||
key = key.substr(1, key.length - 2);
|
contents += ' static lastcommit = \'' + commit + '\';\n';
|
||||||
}
|
|
||||||
contents += ' static ' + key + ' = ' + value + ';\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add compilation time.
|
contents += '}\n';
|
||||||
contents += ' static compilationtime = ' + Date.now() + ';\n';
|
|
||||||
|
|
||||||
contents += '}\n';
|
file.contents = new Buffer(contents);
|
||||||
|
this.emit('data', file);
|
||||||
|
}))
|
||||||
|
.pipe(rename('configconstants.ts'))
|
||||||
|
.pipe(gulp.dest(paths.src))
|
||||||
|
.on('end', done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
file.contents = new Buffer(contents);
|
gulp.task('default', gulp.parallel('lang', 'config'));
|
||||||
this.emit('data', file);
|
|
||||||
}))
|
gulp.task('watch', function() {
|
||||||
.pipe(rename('configconstants.ts'))
|
var langsPaths = paths.lang.map(function(path) {
|
||||||
.pipe(gulp.dest(paths.src))
|
return path + 'en.json';
|
||||||
.on('end', done);
|
});
|
||||||
|
gulp.watch(langsPaths, { interval: 500 }, gulp.parallel('lang'));
|
||||||
|
gulp.watch(paths.config, { interval: 500 }, gulp.parallel('config'));
|
||||||
});
|
});
|
||||||
|
|
||||||
var templatesSrc = [
|
var templatesSrc = [
|
||||||
|
@ -306,7 +284,7 @@ var templatesSrc = [
|
||||||
gulp.task('copy-component-templates', function(done) {
|
gulp.task('copy-component-templates', function(done) {
|
||||||
deleteFolderRecursive(templatesDest);
|
deleteFolderRecursive(templatesDest);
|
||||||
|
|
||||||
gulp.src(templatesSrc)
|
gulp.src(templatesSrc, { allowEmpty: true })
|
||||||
.pipe(flatten())
|
.pipe(flatten())
|
||||||
.pipe(gulp.dest(templatesDest))
|
.pipe(gulp.dest(templatesDest))
|
||||||
.on('end', done);
|
.on('end', done);
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// This hook copies Android splash screen files from dev directories into the appropriate platform specific location.
|
||||||
|
// The code was extracted from here: http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/
|
||||||
|
|
||||||
|
var filesToCopy = [{
|
||||||
|
'resources/android/splash/drawable-land-hdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-hdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-land-ldpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-ldpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-land-mdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-mdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-land-xhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-xhdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-land-xxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-xxhdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-land-xxxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-xxxhdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-port-hdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-hdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-port-ldpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-ldpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-port-mdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-mdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-port-xhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-xhdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-port-xxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-xxhdpi/screen.png'
|
||||||
|
}, {
|
||||||
|
'resources/android/splash/drawable-port-xxxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-xxxhdpi/screen.png'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
// no need to configure below
|
||||||
|
var rootDir = process.argv[2];
|
||||||
|
|
||||||
|
filesToCopy.forEach(function(obj) {
|
||||||
|
Object.keys(obj).forEach(function(key) {
|
||||||
|
var val = obj[key];
|
||||||
|
var srcFile = path.join(rootDir, key);
|
||||||
|
var destFile = path.join(rootDir, val);
|
||||||
|
var destDir = path.dirname(destFile);
|
||||||
|
if (fs.existsSync(srcFile) && fs.existsSync(destDir)) {
|
||||||
|
fs.createReadStream(srcFile).pipe(fs.createWriteStream(destFile));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,10 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "moodlemobile",
|
"name": "moodlemobile",
|
||||||
"app_id": "com.moodle.moodlemobile",
|
|
||||||
"type": "ionic-angular",
|
|
||||||
"integrations": {
|
"integrations": {
|
||||||
"cordova": {},
|
"cordova": {},
|
||||||
"gulp": {}
|
"gulp": {}
|
||||||
},
|
},
|
||||||
"watchPatterns": []
|
"type": "ionic-angular",
|
||||||
|
"watchPatterns": [],
|
||||||
|
"pro_id": "com.moodle.moodlemobile",
|
||||||
|
"id": "com.moodle.moodlemobile"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
157
package.json
157
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "moodlemobile",
|
"name": "moodlemobile",
|
||||||
"version": "3.5.1",
|
"version": "3.6.0",
|
||||||
"description": "The official app for Moodle.",
|
"description": "The official app for Moodle.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Moodle Pty Ltd.",
|
"name": "Moodle Pty Ltd.",
|
||||||
|
@ -48,64 +48,90 @@
|
||||||
"@angular/http": "5.2.10",
|
"@angular/http": "5.2.10",
|
||||||
"@angular/platform-browser": "5.2.10",
|
"@angular/platform-browser": "5.2.10",
|
||||||
"@angular/platform-browser-dynamic": "5.2.10",
|
"@angular/platform-browser-dynamic": "5.2.10",
|
||||||
"@ionic-native/badge": "^4.5.3",
|
"@ionic-native/badge": "4.17.0",
|
||||||
"@ionic-native/camera": "^4.5.2",
|
"@ionic-native/camera": "4.17.0",
|
||||||
"@ionic-native/clipboard": "^4.3.2",
|
"@ionic-native/clipboard": "4.17.0",
|
||||||
"@ionic-native/core": "4.3.0",
|
"@ionic-native/core": "4.11.0",
|
||||||
"@ionic-native/device": "^4.5.3",
|
"@ionic-native/device": "4.17.0",
|
||||||
"@ionic-native/file": "^4.3.3",
|
"@ionic-native/file": "4.17.0",
|
||||||
"@ionic-native/file-opener": "^4.7.0",
|
"@ionic-native/file-opener": "4.17.0",
|
||||||
"@ionic-native/file-transfer": "^4.3.3",
|
"@ionic-native/file-transfer": "4.17.0",
|
||||||
"@ionic-native/globalization": "^4.3.2",
|
"@ionic-native/globalization": "4.17.0",
|
||||||
"@ionic-native/in-app-browser": "^4.3.3",
|
"@ionic-native/in-app-browser": "4.17.0",
|
||||||
"@ionic-native/keyboard": "^4.3.2",
|
"@ionic-native/keyboard": "4.17.0",
|
||||||
"@ionic-native/local-notifications": "4.5.2",
|
"@ionic-native/local-notifications": "4.5.2",
|
||||||
"@ionic-native/media-capture": "^4.5.2",
|
"@ionic-native/media-capture": "4.17.0",
|
||||||
"@ionic-native/network": "^4.3.2",
|
"@ionic-native/network": "4.17.0",
|
||||||
"@ionic-native/push": "^4.5.3",
|
"@ionic-native/push": "4.17.0",
|
||||||
"@ionic-native/splash-screen": "4.3.0",
|
"@ionic-native/screen-orientation": "4.17.0",
|
||||||
"@ionic-native/sqlite": "^4.3.2",
|
"@ionic-native/splash-screen": "4.17.0",
|
||||||
"@ionic-native/status-bar": "4.3.0",
|
"@ionic-native/sqlite": "4.17.0",
|
||||||
"@ionic-native/web-intent": "^4.7.0",
|
"@ionic-native/status-bar": "4.17.0",
|
||||||
"@ionic-native/zip": "^4.3.3",
|
"@ionic-native/web-intent": "4.17.0",
|
||||||
"@ngx-translate/core": "^8.0.0",
|
"@ionic-native/zip": "4.17.0",
|
||||||
"@ngx-translate/http-loader": "^2.0.0",
|
"@ngx-translate/core": "8.0.0",
|
||||||
|
"@ngx-translate/http-loader": "2.0.1",
|
||||||
"@types/cordova": "0.0.34",
|
"@types/cordova": "0.0.34",
|
||||||
"@types/cordova-plugin-file-transfer": "0.0.3",
|
"@types/cordova-plugin-file-transfer": "0.0.3",
|
||||||
"@types/cordova-plugin-globalization": "0.0.3",
|
"@types/cordova-plugin-globalization": "0.0.3",
|
||||||
"@types/cordova-plugin-network-information": "0.0.3",
|
"@types/cordova-plugin-network-information": "0.0.3",
|
||||||
"@types/node": "^8.0.47",
|
"@types/node": "8.10.19",
|
||||||
"@types/promise.prototype.finally": "^2.0.2",
|
"@types/promise.prototype.finally": "2.0.2",
|
||||||
"chart.js": "^2.7.2",
|
"chart.js": "2.7.2",
|
||||||
"cordova-android": "7.0.0",
|
"com-darryncampbell-cordova-plugin-intent": "1.1.1",
|
||||||
"cordova-ios": "4.5.4",
|
"cordova-android": "7.1.2",
|
||||||
|
"cordova-android-support-gradle-release": "2.0.1",
|
||||||
|
"cordova-clipboard": "1.2.1",
|
||||||
|
"cordova-ios": "4.5.5",
|
||||||
|
"cordova-plugin-app-event": "1.2.1",
|
||||||
|
"cordova-plugin-badge": "0.8.8",
|
||||||
|
"cordova-plugin-camera": "4.0.3",
|
||||||
|
"cordova-plugin-customurlscheme": "4.3.0",
|
||||||
|
"cordova-plugin-device": "2.0.2",
|
||||||
|
"cordova-plugin-file": "6.0.1",
|
||||||
|
"cordova-plugin-file-opener2": "2.0.19",
|
||||||
|
"cordova-plugin-file-transfer": "1.7.1",
|
||||||
|
"cordova-plugin-globalization": "1.11.0",
|
||||||
|
"cordova-plugin-inappbrowser": "3.0.0",
|
||||||
|
"cordova-plugin-ionic-keyboard": "2.1.3",
|
||||||
|
"cordova-plugin-local-notifications-mm": "1.0.13",
|
||||||
|
"cordova-plugin-media-capture": "3.0.2",
|
||||||
|
"cordova-plugin-network-information": "2.0.1",
|
||||||
|
"cordova-plugin-screen-orientation": "3.0.1",
|
||||||
|
"cordova-plugin-splashscreen": "5.0.2",
|
||||||
|
"cordova-plugin-statusbar": "2.4.2",
|
||||||
|
"cordova-plugin-whitelist": "1.3.3",
|
||||||
|
"cordova-plugin-zip": "3.1.0",
|
||||||
|
"cordova-sqlite-storage": "2.6.0",
|
||||||
|
"es6-promise-plugin": "4.2.2",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"ionic-angular": "^3.9.2",
|
"ionic-angular": "3.9.2",
|
||||||
"ionicons": "3.0.0",
|
"ionicons": "3.0.0",
|
||||||
"jszip": "^3.1.4",
|
"jszip": "3.1.5",
|
||||||
"moment": "^2.19.1",
|
"moment": "2.22.2",
|
||||||
"promise.prototype.finally": "^3.0.1",
|
"nl.kingsquare.cordova.background-audio": "1.0.1",
|
||||||
|
"phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle",
|
||||||
|
"promise.prototype.finally": "3.1.0",
|
||||||
"rxjs": "5.5.11",
|
"rxjs": "5.5.11",
|
||||||
"sw-toolbox": "3.6.0",
|
"sw-toolbox": "3.6.0",
|
||||||
"ts-md5": "^1.2.2",
|
"ts-md5": "1.2.4",
|
||||||
|
"web-animations-js": "2.3.1",
|
||||||
"zone.js": "0.8.26"
|
"zone.js": "0.8.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ionic/app-scripts": "3.1.9",
|
"@ionic/app-scripts": "3.1.9",
|
||||||
"electron-rebuild": "^1.8.1",
|
"electron-rebuild": "1.8.1",
|
||||||
"gulp": "^3.9.1",
|
"electron-builder-lib": "20.23.1",
|
||||||
"gulp-clip-empty-files": "^0.1.2",
|
"gulp": "4.0.0",
|
||||||
"gulp-flatten": "^0.4.0",
|
"gulp-clip-empty-files": "0.1.2",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-flatten": "0.4.0",
|
||||||
"gulp-slash": "^1.1.3",
|
"gulp-rename": "1.3.0",
|
||||||
"node-loader": "^0.6.0",
|
"gulp-slash": "1.1.3",
|
||||||
"through": "^2.3.8",
|
"gulp-util": "3.0.8",
|
||||||
"typescript": "~2.6.2",
|
"node-loader": "0.6.0",
|
||||||
"webpack-merge": "^4.1.2"
|
"through": "2.3.8",
|
||||||
},
|
"typescript": "2.6.2",
|
||||||
"optionalDependencies": {
|
"webpack-merge": "4.1.2"
|
||||||
"electron-windows-notifications": "^2.1.1",
|
|
||||||
"electron-builder-squirrel-windows": "^20.19.0"
|
|
||||||
},
|
},
|
||||||
"browser": {
|
"browser": {
|
||||||
"electron": false
|
"electron": false
|
||||||
|
@ -114,7 +140,39 @@
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"android",
|
"android",
|
||||||
"ios"
|
"ios"
|
||||||
]
|
],
|
||||||
|
"plugins": {
|
||||||
|
"com-darryncampbell-cordova-plugin-intent": {},
|
||||||
|
"cordova-android-support-gradle-release": {
|
||||||
|
"ANDROID_SUPPORT_VERSION": "27.1.0"
|
||||||
|
},
|
||||||
|
"cordova-clipboard": {},
|
||||||
|
"cordova-plugin-badge": {},
|
||||||
|
"cordova-plugin-camera": {},
|
||||||
|
"cordova-plugin-customurlscheme": {
|
||||||
|
"URL_SCHEME": "moodlemobile"
|
||||||
|
},
|
||||||
|
"cordova-plugin-device": {},
|
||||||
|
"cordova-plugin-file": {},
|
||||||
|
"cordova-plugin-file-opener2": {},
|
||||||
|
"cordova-plugin-file-transfer": {},
|
||||||
|
"cordova-plugin-globalization": {},
|
||||||
|
"cordova-plugin-inappbrowser": {},
|
||||||
|
"cordova-plugin-ionic-keyboard": {},
|
||||||
|
"cordova-plugin-local-notifications-mm": {},
|
||||||
|
"cordova-plugin-media-capture": {},
|
||||||
|
"cordova-plugin-network-information": {},
|
||||||
|
"cordova-plugin-screen-orientation": {},
|
||||||
|
"cordova-plugin-splashscreen": {},
|
||||||
|
"cordova-plugin-statusbar": {},
|
||||||
|
"cordova-plugin-whitelist": {},
|
||||||
|
"cordova-plugin-zip": {},
|
||||||
|
"cordova-sqlite-storage": {},
|
||||||
|
"nl.kingsquare.cordova.background-audio": {},
|
||||||
|
"phonegap-plugin-push": {
|
||||||
|
"SENDER_ID": "694767596569"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"main": "desktop/electron.js",
|
"main": "desktop/electron.js",
|
||||||
"build": {
|
"build": {
|
||||||
|
@ -162,10 +220,11 @@
|
||||||
"icon": "resources/desktop/icon.ico"
|
"icon": "resources/desktop/icon.ico"
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"category": "Education"
|
"category": "Education",
|
||||||
|
"target": "AppImage"
|
||||||
},
|
},
|
||||||
"snap": {
|
"snap": {
|
||||||
"confinement": "classic"
|
"confinement": "classic"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Compile AOT.
|
||||||
|
if [ $TRAVIS_BRANCH == 'integration' ] || [ $TRAVIS_BRANCH == 'master' ] || [ -z $TRAVIS_BRANCH ] ; then
|
||||||
|
cd scripts
|
||||||
|
./build_lang.sh
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
if [ $TRAVIS_BRANCH == 'master' ] && [ ! -z $GIT_TOKEN ] ; then
|
||||||
|
git remote set-url origin https://$GIT_TOKEN@github.com/$TRAVIS_REPO_SLUG.git
|
||||||
|
git fetch -q origin
|
||||||
|
git add src/assets/lang
|
||||||
|
git add */en.json
|
||||||
|
git commit -m 'Update lang files [ci skip]'
|
||||||
|
git push origin HEAD:$TRAVIS_BRANCH
|
||||||
|
|
||||||
|
version=`grep versionname src/config.json| cut -d: -f2|cut -d'"' -f2`
|
||||||
|
date=`date +%Y%m%d`'00'
|
||||||
|
|
||||||
|
pushd ../../moodle-local_moodlemobileapp
|
||||||
|
sed -ie "s/release[ ]*=[ ]*'[^']*';/release = '$version';/1" version.php
|
||||||
|
sed -ie "s/version[ ]*=[ ]*[0-9]*;/version = $date;/1" version.php
|
||||||
|
rm version.phpe
|
||||||
|
git remote set-url origin https://$GIT_TOKEN@github.com/moodlehq/moodle-local_moodlemobileapp.git
|
||||||
|
git fetch -q origin
|
||||||
|
git add .
|
||||||
|
git commit -m "Update lang files from $version"
|
||||||
|
git push
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -ie $'s~throw new Error("No ResourceLoader.*~url = "templates/" + url;\\\nvar resolve;\\\nvar reject;\\\nvar promise = new Promise(function (res, rej) {\\\nresolve = res;\\\nreject = rej;\\\n});\\\nvar xhr = new XMLHttpRequest();\\\nxhr.open("GET", url, true);\\\nxhr.responseType = "text";\\\nxhr.onload = function () {\\\nvar response = xhr.response || xhr.responseText;\\\nvar status = xhr.status === 1223 ? 204 : xhr.status;\\\nif (status === 0) {\\\nstatus = response ? 200 : 0;\\\n}\\\nif (200 <= status \&\& status <= 300) {\\\nresolve(response);\\\n}\\\nelse {\\\nreject("Failed to load " + url);\\\n}\\\n};\\\nxhr.onerror = function () { reject("Failed to load " + url); };\\\nxhr.send();\\\nreturn promise;\\\n~g' node_modules/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js
|
||||||
|
sed -ie "s/context\.isProd || hasArg('--minifyJs')/hasArg('--minifyJs')/g" node_modules/@ionic/app-scripts/dist/util/config.js
|
||||||
|
sed -ie "s/context\.isProd || hasArg('--optimizeJs')/hasArg('--optimizeJs')/g" node_modules/@ionic/app-scripts/dist/util/config.js
|
||||||
|
npm run ionic:build -- --prod
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy to PGB git (only on a configured travis build).
|
||||||
|
if [ ! -z $GIT_ORG ] && [ ! -z $GIT_TOKEN ] ; then
|
||||||
|
gitfolder=${PWD##*/}
|
||||||
|
cd ..
|
||||||
|
git clone --depth 1 --no-single-branch https://github.com/$GIT_ORG/moodlemobile-phonegapbuild.git pgb
|
||||||
|
cd pgb
|
||||||
|
git checkout $TRAVIS_BRANCH
|
||||||
|
rm -Rf assets build index.html templates
|
||||||
|
cp -Rf ../$gitfolder/www/* ./
|
||||||
|
rm -Rf assets/countries assets/mimetypes
|
||||||
|
git add .
|
||||||
|
git commit -m "Travis build: $TRAVIS_BUILD_NUMBER"
|
||||||
|
git push https://$GIT_TOKEN@github.com/$GIT_ORG/moodlemobile-phonegapbuild.git
|
||||||
|
fi
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/bin/bash
|
||||||
|
source "functions.sh"
|
||||||
|
forceLang=$1
|
||||||
|
|
||||||
|
print_title 'Getting languages'
|
||||||
|
git clone --depth 1 --no-single-branch https://git.in.moodle.com/moodle/moodle-langpacks.git $LANGPACKSFOLDER
|
||||||
|
pushd $LANGPACKSFOLDER
|
||||||
|
BRANCHES=($(git branch -r --format="%(refname:lstrip=3)" --sort="refname" | grep MOODLE_))
|
||||||
|
BRANCH=${BRANCHES[${#BRANCHES[@]}-1]}
|
||||||
|
git checkout $BRANCH
|
||||||
|
git pull
|
||||||
|
popd
|
||||||
|
|
||||||
|
print_title 'Getting local mobile langs'
|
||||||
|
git clone --depth 1 https://github.com/moodlehq/moodle-local_moodlemobileapp.git ../../moodle-local_moodlemobileapp
|
||||||
|
|
||||||
|
if [ -z $forceLang ]; then
|
||||||
|
php -f moodle_to_json.php
|
||||||
|
else
|
||||||
|
php -f moodle_to_json.php "$forceLang"
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_ok 'All done!'
|
|
@ -0,0 +1,266 @@
|
||||||
|
#!/bin/bash
|
||||||
|
source "functions.sh"
|
||||||
|
|
||||||
|
#Saves or updates a key on langindex_old.json
|
||||||
|
function save_key {
|
||||||
|
key=$1
|
||||||
|
found=$2
|
||||||
|
|
||||||
|
print_ok "$key=$found"
|
||||||
|
echo "{\"$key\": \"$found\"}" > langindex_old.json
|
||||||
|
jq -s '.[0] + .[1]' langindex.json langindex_old.json > langindex_new.json
|
||||||
|
mv langindex_new.json langindex.json
|
||||||
|
}
|
||||||
|
|
||||||
|
#Removes a key on langindex_old.json
|
||||||
|
function remove_key {
|
||||||
|
key=$1
|
||||||
|
found=$2
|
||||||
|
|
||||||
|
print_ok "$key=$found"
|
||||||
|
echo "{\"$key\": \"$found\"}" > langindex_old.json
|
||||||
|
jq -s '.[0] - .[1]' langindex.json langindex_old.json > langindex_new.json
|
||||||
|
mv langindex_new.json langindex.json
|
||||||
|
}
|
||||||
|
|
||||||
|
#Check if and i exists in php file
|
||||||
|
function exists_in_file {
|
||||||
|
file=$1
|
||||||
|
id=$2
|
||||||
|
|
||||||
|
file=`echo $file | sed s/^mod_workshop_assessment/workshopform/1`
|
||||||
|
file=`echo $file | sed s/^mod_assign_/assign/1`
|
||||||
|
file=`echo $file | sed s/^mod_//1`
|
||||||
|
|
||||||
|
completeFile="$LANGPACKSFOLDER/en/$file.php"
|
||||||
|
if [ -f "$completeFile" ]; then
|
||||||
|
coincidence=`grep "string\[\'$id\'\]" $completeFile`
|
||||||
|
if [ ! -z "$coincidence" ]; then
|
||||||
|
found=$file
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
found=0
|
||||||
|
}
|
||||||
|
|
||||||
|
#Checks if a key exists on the original local_moodlemobileapp.php
|
||||||
|
function exists_in_mobile {
|
||||||
|
file='local_moodlemobileapp'
|
||||||
|
exists_in_file $file $key
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_match {
|
||||||
|
match=$1
|
||||||
|
filematch=""
|
||||||
|
|
||||||
|
coincidence=`grep "$match" $LANGPACKSFOLDER/en/*.php | wc -l`
|
||||||
|
if [ $coincidence -eq 1 ]; then
|
||||||
|
filematch=`grep "$match" $LANGPACKSFOLDER/en/*.php | cut -d'/' -f5 | cut -d'.' -f1`
|
||||||
|
exists_in_file $filematch $plainid
|
||||||
|
elif [ $coincidence -gt 0 ] && [ "$#" -gt 1 ]; then
|
||||||
|
print_message $2
|
||||||
|
tput setaf 6
|
||||||
|
grep "$match" $LANGPACKSFOLDER/en/*.php
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#Find if the id or the value can be found on files to help providing a solution.
|
||||||
|
function find_matches {
|
||||||
|
do_match "string\[\'$plainid\'\] = \'$value\'" "Found EXACT match for $key in the following paths"
|
||||||
|
if [ $coincidence -gt 0 ]; then
|
||||||
|
case=1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
do_match " = \'$value\'" "Found some string VALUES for $key in the following paths"
|
||||||
|
if [ $coincidence -gt 0 ]; then
|
||||||
|
case=2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
do_match "string\[\'$plainid\'\]" "Found some string KEYS for $key in the following paths, value $value"
|
||||||
|
if [ $coincidence -gt 0 ]; then
|
||||||
|
case=3
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_message "No match found for $key add it to local_moodlemobileapp"
|
||||||
|
save_key $key "local_moodlemobileapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_single_matches {
|
||||||
|
do_match "string\[\'$plainid\'\] = \'$value\'"
|
||||||
|
if [ ! -z $filematch ] && [ $found != 0 ]; then
|
||||||
|
case=1
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
do_match " = \'$value\'"
|
||||||
|
if [ ! -z $filematch ] && [ $filematch != 'local_moodlemobileapp' ]; then
|
||||||
|
case=2
|
||||||
|
print_message "Found some string VALUES for $key in the following paths $filematch"
|
||||||
|
tput setaf 6
|
||||||
|
grep "$match" $LANGPACKSFOLDER/en/*.php
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
do_match "string\[\'$plainid\'\]"
|
||||||
|
if [ ! -z $filematch ] && [ $found != 0 ]; then
|
||||||
|
case=3
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#Tries to gues the file where the id will be found.
|
||||||
|
function guess_file {
|
||||||
|
key=$1
|
||||||
|
value=$2
|
||||||
|
|
||||||
|
type=`echo $key | cut -d'.' -f1`
|
||||||
|
component=`echo $key | cut -d'.' -f2`
|
||||||
|
plainid=`echo $key | cut -d'.' -f3-`
|
||||||
|
|
||||||
|
if [ -z "$plainid" ]; then
|
||||||
|
plainid=$component
|
||||||
|
component='moodle'
|
||||||
|
fi
|
||||||
|
|
||||||
|
exists_in_file $component $plainid
|
||||||
|
|
||||||
|
if [ $found == 0 ]; then
|
||||||
|
tempid=`echo $plainid | sed s/^mod_//1`
|
||||||
|
if [ $component == 'moodle' ] && [ "$tempid" != "$plainid" ]; then
|
||||||
|
exists_in_file $plainid pluginname
|
||||||
|
|
||||||
|
if [ $found != 0 ]; then
|
||||||
|
found=$found/pluginname
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Not found in file, try in local_moodlemobileapp
|
||||||
|
if [ $found == 0 ]; then
|
||||||
|
exists_in_mobile
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Still not found, if only found in one file, use it.
|
||||||
|
if [ $found == 0 ]; then
|
||||||
|
find_single_matches
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Last fallback.
|
||||||
|
if [ $found == 0 ]; then
|
||||||
|
exists_in_file 'moodle' $plainid
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $found == 0 ]; then
|
||||||
|
find_matches
|
||||||
|
else
|
||||||
|
save_key $key $found
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#Finds if there's a better file where to get the id from.
|
||||||
|
function find_better_file {
|
||||||
|
key=$1
|
||||||
|
value=$2
|
||||||
|
current=$3
|
||||||
|
|
||||||
|
type=`echo $key | cut -d'.' -f1`
|
||||||
|
component=`echo $key | cut -d'.' -f2`
|
||||||
|
plainid=`echo $key | cut -d'.' -f3-`
|
||||||
|
|
||||||
|
if [ -z "$plainid" ]; then
|
||||||
|
plainid=$component
|
||||||
|
component='moodle'
|
||||||
|
fi
|
||||||
|
|
||||||
|
currentFile=`echo $current | cut -d'/' -f1`
|
||||||
|
currentStr=`echo $current | cut -d'/' -f2-`
|
||||||
|
if [ $currentFile == $current ]; then
|
||||||
|
currentStr=$plainid
|
||||||
|
fi
|
||||||
|
|
||||||
|
exists_in_file $component $plainid
|
||||||
|
if [ $found != 0 ] && [ $currentStr == $plainid ]; then
|
||||||
|
if [ $found != $currentFile ]; then
|
||||||
|
print_ok "Key '$key' found in component, no need to replace old '$current'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Still not found, if only found in one file, use it.
|
||||||
|
if [ $found == 0 ]; then
|
||||||
|
find_single_matches
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $found != 0 ] && [ $found != $currentFile ] && [ $case -lt 3 ]; then
|
||||||
|
print_message "Indexed string '$key' found in '$found' better than '$current'"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $currentFile == 'local_moodlemobileapp' ]; then
|
||||||
|
exists_in_mobile
|
||||||
|
else
|
||||||
|
exists_in_file $currentFile $currentStr
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $found == 0 ]; then
|
||||||
|
print_error "Indexed string '$key' not found on current place '$current'"
|
||||||
|
if [ $currentFile != 'local_moodlemobileapp' ]; then
|
||||||
|
print_error "Execute this on AMOS
|
||||||
|
CPY [$currentStr,$currentFile],[$key,local_moodlemobileapp]"
|
||||||
|
save_key $key "local_moodlemobileapp"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#Parses the file.
|
||||||
|
function parse_file {
|
||||||
|
findbetter=$2
|
||||||
|
keys=`jq -r 'keys[]' $1`
|
||||||
|
for key in $keys; do
|
||||||
|
# Check if already parsed.
|
||||||
|
exec="jq -r .\"$key\" langindex.json"
|
||||||
|
found=`$exec`
|
||||||
|
|
||||||
|
exec="jq -r .\"$key\" $1"
|
||||||
|
value=`$exec`
|
||||||
|
if [ -z "$found" ] || [ "$found" == 'null' ]; then
|
||||||
|
guess_file $key "$value"
|
||||||
|
elif [ ! -z "$findbetter" ]; then
|
||||||
|
find_better_file "$key" "$value" "$found"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
print_title 'Generating language from code...'
|
||||||
|
gulp lang
|
||||||
|
|
||||||
|
print_title 'Getting languages'
|
||||||
|
git clone https://git.in.moodle.com/moodle/moodle-langpacks.git $LANGPACKSFOLDER
|
||||||
|
pushd $LANGPACKSFOLDER
|
||||||
|
BRANCHES=($(git branch -r --format="%(refname:lstrip=3)" --sort="refname" | grep MOODLE_))
|
||||||
|
BRANCH=${BRANCHES[${#BRANCHES[@]}-1]}
|
||||||
|
git checkout $BRANCH
|
||||||
|
git pull
|
||||||
|
popd
|
||||||
|
|
||||||
|
print_title 'Processing file'
|
||||||
|
#Create langindex.json if not exists.
|
||||||
|
if [ ! -f 'langindex.json' ]; then
|
||||||
|
echo "{}" > langindex.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
findbetter=$1
|
||||||
|
parse_file '../src/assets/lang/en.json' $findbetter
|
||||||
|
|
||||||
|
echo
|
||||||
|
|
||||||
|
jq -S --indent 2 -s '.[0]' langindex.json > langindex_new.json
|
||||||
|
mv langindex_new.json langindex.json
|
||||||
|
rm langindex_old.json
|
||||||
|
|
||||||
|
print_ok 'All done!'
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
LANGPACKSFOLDER='../../moodle-langpacks'
|
||||||
|
|
||||||
|
function check_success {
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
print_error "$1"
|
||||||
|
elif [ "$#" -gt 1 ]; then
|
||||||
|
print_ok "$2"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_success {
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
print_message "$1"
|
||||||
|
$3=0
|
||||||
|
else
|
||||||
|
print_ok "$2"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_error {
|
||||||
|
tput setaf 1; echo " ERROR: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_ok {
|
||||||
|
tput setaf 2; echo " OK: $1"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_message {
|
||||||
|
tput setaf 3; echo "-------- $1"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
function print_title {
|
||||||
|
stepnumber=$(($stepnumber + 1))
|
||||||
|
echo
|
||||||
|
tput setaf 5; echo "$stepnumber $1"
|
||||||
|
tput setaf 5; echo '=================='
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,388 @@
|
||||||
|
<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script for converting moodle strings to json.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check we are in CLI.
|
||||||
|
if (isset($_SERVER['REMOTE_ADDR'])) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
define('MOODLE_INTERNAL', 1);
|
||||||
|
define('LANGPACKSFOLDER', '../../moodle-langpacks');
|
||||||
|
define('ASSETSPATH', '../src/assets/lang/');
|
||||||
|
define('CONFIG', '../src/config.json');
|
||||||
|
|
||||||
|
$config = file_get_contents(CONFIG);
|
||||||
|
$config = (array) json_decode($config);
|
||||||
|
$config_langs = array_keys(get_object_vars($config['languages']));
|
||||||
|
|
||||||
|
// Set languages to do. If script is called using a language it will be used as unique.
|
||||||
|
if (isset($argv[1]) && !empty($argv[1])) {
|
||||||
|
$forcedetect = false;
|
||||||
|
$languages = explode(',', $argv[1]);
|
||||||
|
} else {
|
||||||
|
$forcedetect = true;
|
||||||
|
$languages = $config_langs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the index file, just once.
|
||||||
|
$keys = file_get_contents('langindex.json');
|
||||||
|
$keys = (array) json_decode($keys);
|
||||||
|
|
||||||
|
foreach ($keys as $key => $value) {
|
||||||
|
$map = new StdClass();
|
||||||
|
if ($value == 'local_moodlemobileapp') {
|
||||||
|
$map->file = $value;
|
||||||
|
$map->string = $key;
|
||||||
|
} else {
|
||||||
|
$exp = explode('/', $value, 2);
|
||||||
|
$map->file = $exp[0];
|
||||||
|
if (count($exp) == 2) {
|
||||||
|
$map->string = $exp[1];
|
||||||
|
} else {
|
||||||
|
$exp = explode('.', $key, 3);
|
||||||
|
|
||||||
|
if (count($exp) == 3) {
|
||||||
|
$map->string = $exp[2];
|
||||||
|
} else {
|
||||||
|
$map->string = $exp[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$keys[$key] = $map;
|
||||||
|
}
|
||||||
|
$total = count ($keys);
|
||||||
|
|
||||||
|
echo "Total strings to translate $total\n";
|
||||||
|
|
||||||
|
$add_langs = array();
|
||||||
|
// Process the languages.
|
||||||
|
foreach ($languages as $lang) {
|
||||||
|
$ok = build_lang($lang, $keys, $total);
|
||||||
|
if ($ok) {
|
||||||
|
$add_langs[$lang] = $lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($forcedetect) {
|
||||||
|
echo "\n\n\n";
|
||||||
|
|
||||||
|
$all_languages = glob(LANGPACKSFOLDER.'/*' , GLOB_ONLYDIR);
|
||||||
|
function get_lang_from_dir($dir) {
|
||||||
|
return str_replace('_', '-', explode('/', $dir)[3]);
|
||||||
|
}
|
||||||
|
$all_languages = array_map('get_lang_from_dir', $all_languages);
|
||||||
|
$detect_lang = array_diff($all_languages, $languages);
|
||||||
|
$new_langs = array();
|
||||||
|
foreach ($detect_lang as $lang) {
|
||||||
|
$new = detect_lang($lang, $keys, $total);
|
||||||
|
if ($new) {
|
||||||
|
$new_langs[$lang] = $lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($new_langs)) {
|
||||||
|
echo "\n\n\nThe following languages are going to be added\n\n\n";
|
||||||
|
foreach ($new_langs as $lang) {
|
||||||
|
$ok = build_lang($lang, $keys, $total);
|
||||||
|
if ($ok) {
|
||||||
|
$add_langs[$lang] = $lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_langs_to_config($add_langs, $config);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
add_langs_to_config($add_langs, $config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_langs_to_config($langs, $config) {
|
||||||
|
$changed = false;
|
||||||
|
$config_langs = get_object_vars($config['languages']);
|
||||||
|
foreach ($langs as $lang) {
|
||||||
|
if (!isset($config_langs[$lang])) {
|
||||||
|
$langfoldername = str_replace('-', '_', $lang);
|
||||||
|
|
||||||
|
$string = [];
|
||||||
|
include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php');
|
||||||
|
$config['languages']->$lang = $string['thislanguage'];
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($changed) {
|
||||||
|
// Sort languages by key.
|
||||||
|
$config['languages'] = json_decode( json_encode( $config['languages'] ), true );
|
||||||
|
ksort($config['languages']);
|
||||||
|
$config['languages'] = json_decode( json_encode( $config['languages'] ), false );
|
||||||
|
file_put_contents(CONFIG, json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_lang($lang, $keys, $total) {
|
||||||
|
$local = 0;
|
||||||
|
$langFile = false;
|
||||||
|
$translations = [];
|
||||||
|
$langfoldername = str_replace('-', '_', $lang);
|
||||||
|
|
||||||
|
if (!is_dir(LANGPACKSFOLDER.'/'.$langfoldername) || !is_file(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php')) {
|
||||||
|
echo "Cannot translate $langfoldername, folder not found";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = [];
|
||||||
|
include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php');
|
||||||
|
$parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : "";
|
||||||
|
|
||||||
|
echo "Processing $lang";
|
||||||
|
if ($parent != "" && $parent != $lang) {
|
||||||
|
echo "($parent)";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add the translation to the array.
|
||||||
|
foreach ($keys as $key => $value) {
|
||||||
|
$file = LANGPACKSFOLDER.'/'.$langfoldername.'/'.$value->file.'.php';
|
||||||
|
// Apply translations.
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = [];
|
||||||
|
include($file);
|
||||||
|
|
||||||
|
if (!isset($string[$value->string])) {
|
||||||
|
// Not yet translated. Do not override.
|
||||||
|
if (!$langFile) {
|
||||||
|
// Load lang fils just once.
|
||||||
|
$langFile = file_get_contents(ASSETSPATH.$lang.'.json');
|
||||||
|
$langFile = (array) json_decode($langFile);
|
||||||
|
}
|
||||||
|
if (is_array($langFile) && isset($langFile[$key])) {
|
||||||
|
$translations[$key] = $langFile[$key];
|
||||||
|
$local++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$text = $string[$value->string];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value->file != 'local_moodlemobileapp') {
|
||||||
|
$text = str_replace('$a->', '$a.', $text);
|
||||||
|
$text = str_replace('{$a', '{{$a', $text);
|
||||||
|
$text = str_replace('}', '}}', $text);
|
||||||
|
// Prevent double.
|
||||||
|
$text = str_replace(array('{{{', '}}}'), array('{{', '}}'), $text);
|
||||||
|
} else {
|
||||||
|
$local++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations[$key] = html_entity_decode($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort and save.
|
||||||
|
ksort($translations);
|
||||||
|
file_put_contents(ASSETSPATH.$lang.'.json', str_replace('\/', '/', json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)));
|
||||||
|
|
||||||
|
$success = count($translations);
|
||||||
|
$percentage = floor($success/$total *100);
|
||||||
|
echo "\t\t$success of $total -> $percentage% ($local local)\n";
|
||||||
|
|
||||||
|
if ($lang == 'en') {
|
||||||
|
generate_local_moodlemobileapp($keys, $translations);
|
||||||
|
override_component_lang_files($keys, $translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function detect_lang($lang, $keys, $total) {
|
||||||
|
$success = 0;
|
||||||
|
$local = 0;
|
||||||
|
$langfoldername = str_replace('-', '_', $lang);
|
||||||
|
|
||||||
|
if (!is_dir(LANGPACKSFOLDER.'/'.$langfoldername) || !is_file(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php')) {
|
||||||
|
echo "Cannot translate $langfoldername, folder not found";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = [];
|
||||||
|
include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php');
|
||||||
|
$parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : "";
|
||||||
|
if (!isset($string['thislanguage'])) {
|
||||||
|
echo "Cannot translate $langfoldername, name not found";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Checking $lang";
|
||||||
|
if ($parent != "" && $parent != $lang) {
|
||||||
|
echo "($parent)";
|
||||||
|
}
|
||||||
|
$langname = $string['thislanguage'];
|
||||||
|
echo " ".$langname." -D";
|
||||||
|
|
||||||
|
// Add the translation to the array.
|
||||||
|
foreach ($keys as $key => $value) {
|
||||||
|
$file = LANGPACKSFOLDER.'/'.$langfoldername.'/'.$value->file.'.php';
|
||||||
|
// Apply translations.
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = [];
|
||||||
|
include($file);
|
||||||
|
|
||||||
|
if (!isset($string[$value->string])) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$text = $string[$value->string];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value->file == 'local_moodlemobileapp') {
|
||||||
|
$local++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$success++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$percentage = floor($success/$total *100);
|
||||||
|
echo "\t\t$success of $total -> $percentage% ($local local)";
|
||||||
|
if (($percentage > 75 && $local > 50) || ($percentage > 50 && $local > 75)) {
|
||||||
|
echo " \t DETECTED\n";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
echo "\n";
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_key($key, $value, $path) {
|
||||||
|
$filePath = $path . '/en.json';
|
||||||
|
|
||||||
|
$file = file_get_contents($filePath);
|
||||||
|
$file = (array) json_decode($file);
|
||||||
|
$value = html_entity_decode($value);
|
||||||
|
if ($file[$key] != $value) {
|
||||||
|
$file[$key] = $value;
|
||||||
|
ksort($file);
|
||||||
|
file_put_contents($filePath, str_replace('\/', '/', json_encode($file, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function override_component_lang_files($keys, $translations) {
|
||||||
|
echo "Override component lang files.\n";
|
||||||
|
foreach ($translations as $key => $value) {
|
||||||
|
$path = '../src/';
|
||||||
|
$exp = explode('.', $key, 3);
|
||||||
|
|
||||||
|
$type = $exp[0];
|
||||||
|
if (count($exp) == 3) {
|
||||||
|
$component = $exp[1];
|
||||||
|
$plainid = $exp[2];
|
||||||
|
} else {
|
||||||
|
$component = 'moodle';
|
||||||
|
$plainid = $exp[1];
|
||||||
|
}
|
||||||
|
switch($type) {
|
||||||
|
case 'core':
|
||||||
|
case 'addon':
|
||||||
|
switch($component) {
|
||||||
|
case 'moodle':
|
||||||
|
$path .= 'lang';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$path .= $type.'/'.str_replace('_', '/', $component).'/lang';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'assets':
|
||||||
|
$path .= $type.'/'.$component;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_file($path.'/en.json')) {
|
||||||
|
save_key($plainid, $value, $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates local moodle mobile app file to update languages in AMOS.
|
||||||
|
*
|
||||||
|
* @param [array] $keys Translation keys.
|
||||||
|
* @param [array] $translations English translations.
|
||||||
|
*/
|
||||||
|
function generate_local_moodlemobileapp($keys, $translations) {
|
||||||
|
echo "Generate local_moodlemobileapp.\n";
|
||||||
|
$string = '<?php
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version details.
|
||||||
|
*
|
||||||
|
* @package local
|
||||||
|
* @subpackage moodlemobileapp
|
||||||
|
* @copyright 2014 Juan Leyva <juanleyvadelgado@gmail.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
$string[\'appstoredescription\'] = \'NOTE: This official Moodle Mobile app will ONLY work with Moodle sites that have been set up to allow it. Please talk to your Moodle administrator if you have any problems connecting.
|
||||||
|
|
||||||
|
If your Moodle site has been configured correctly, you can use this app to:
|
||||||
|
|
||||||
|
- browse the content of your courses, even when offline
|
||||||
|
- receive instant notifications of messages and other events
|
||||||
|
- quickly find and contact other people in your courses
|
||||||
|
- upload images, audio, videos and other files from your mobile device
|
||||||
|
- view your course grades
|
||||||
|
- and more!
|
||||||
|
|
||||||
|
Please see http://docs.moodle.org/en/Mobile_app for all the latest information.
|
||||||
|
|
||||||
|
We’d really appreciate any good reviews about the functionality so far, and your suggestions on what else you want this app to do!
|
||||||
|
|
||||||
|
The app requires the following permissions:
|
||||||
|
Record audio - For recording audio to upload to Moodle
|
||||||
|
Read and modify the contents of your SD card - Contents are downloaded to the SD Card so you can see them offline
|
||||||
|
Network access - To be able to connect with your Moodle site and check if you are connected or not to switch to offline mode
|
||||||
|
Run at startup - So you receive local notifications even when the app is running in the background
|
||||||
|
Prevent phone from sleeping - So you can receive push notifications anytime\';'."\n";
|
||||||
|
foreach ($keys as $key => $value) {
|
||||||
|
if (isset($translations[$key]) && $value->file == 'local_moodlemobileapp') {
|
||||||
|
$string .= '$string[\''.$key.'\'] = \''.str_replace("'", "\'", $translations[$key]).'\';'."\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$string .= '$string[\'pluginname\'] = \'Moodle Mobile language strings\';'."\n";
|
||||||
|
|
||||||
|
file_put_contents('../../moodle-local_moodlemobileapp/lang/en/local_moodlemobileapp.php', $string."\n");
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"badges": "شارات",
|
|
||||||
"expired": "عذراً، تم إغلاق هذا النشاط في {{$a}} وهو غير متوفر الآن."
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Елементи на значката",
|
|
||||||
"badges": "Значки",
|
|
||||||
"contact": "Контакт",
|
|
||||||
"expired": "За съжаление тази дейност е затворена от {{$a}} и вече не е достъпна",
|
|
||||||
"expirydate": "Дата на изтичане",
|
|
||||||
"issuancedetails": "Срок на значката",
|
|
||||||
"issuerdetails": "Данни за връчващия",
|
|
||||||
"issuername": "Име на връчващия",
|
|
||||||
"nobadges": "Няма налични значки."
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detalls de la insígnia",
|
|
||||||
"badges": "Insígnies",
|
|
||||||
"contact": "Contacte",
|
|
||||||
"dateawarded": "Data publicada",
|
|
||||||
"expired": "Aquesta activitat es va tancar el dia {{$a}} i ja no està disponible.",
|
|
||||||
"expirydate": "Data d'expiració",
|
|
||||||
"issuancedetails": "Expiració de la insígnia",
|
|
||||||
"issuerdetails": "Detalls de l'atorgador",
|
|
||||||
"issuername": "Nom de l'atorgador",
|
|
||||||
"nobadges": "No hi ha insígnies disponibles.",
|
|
||||||
"recipientdetails": "Detalls del receptor"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detaily odznaku",
|
|
||||||
"badges": "Odznaky",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Datum udělení",
|
|
||||||
"expired": "Je nám líto, tato činnost byla uzavřena {{$a}} a není nadále dostupná",
|
|
||||||
"expirydate": "Datum vypršení platnosti",
|
|
||||||
"issuancedetails": "Vypršení platnosti odznaku",
|
|
||||||
"issuerdetails": "Podrobnosti o vydavateli",
|
|
||||||
"issuername": "Jméno vydavatele",
|
|
||||||
"nobadges": "Žádné odznaky nejsou k dispozici.",
|
|
||||||
"recipientdetails": "Podrobnosti o příjemci"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Badgedetaljer",
|
|
||||||
"badges": "Badges",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Udstedelsesdato",
|
|
||||||
"expired": "Beklager, denne aktivitet er lukket d. {{$a}} og er ikke længere tilgængelig",
|
|
||||||
"expirydate": "Udløbsdato",
|
|
||||||
"issuancedetails": "Badge-udløb",
|
|
||||||
"issuerdetails": "Udstederdata",
|
|
||||||
"issuername": "Udsteders navn",
|
|
||||||
"nobadges": "Der er ingen tilgængelige badges.",
|
|
||||||
"recipientdetails": "Modtagerdata"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Grundeinstellungen",
|
|
||||||
"badges": "Auszeichnungen",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Verleihdatum",
|
|
||||||
"expired": "Diese Abstimmung ist seit {{$a}} beendet. Eine Auswahl ist nicht mehr möglich.",
|
|
||||||
"expirydate": "Ablaufdatum",
|
|
||||||
"issuancedetails": "Ablauf festlegen",
|
|
||||||
"issuerdetails": "Verleiher",
|
|
||||||
"issuername": "Verleiher",
|
|
||||||
"nobadges": "Keine Auszeichnungen verfügbar",
|
|
||||||
"recipientdetails": "Empfängerdetails"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Grundeinstellungen",
|
|
||||||
"badges": "Auszeichnungen",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Verleihdatum",
|
|
||||||
"expired": "Diese Abstimmung ist seit {{$a}} beendet. Eine Auswahl ist nicht mehr möglich.",
|
|
||||||
"expirydate": "Ablaufdatum",
|
|
||||||
"issuancedetails": "Ablauf festlegen",
|
|
||||||
"issuerdetails": "Verleiher",
|
|
||||||
"issuername": "Verleiher",
|
|
||||||
"nobadges": "Keine Auszeichnungen verfügbar",
|
|
||||||
"recipientdetails": "Empfängerdetails"
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"badges": "Βραβεία",
|
|
||||||
"expired": "Η δραστηριότητα αυτή έκλεισε στις {{$a}} και δεν είναι πλέον διαθέσιμη"
|
|
||||||
}
|
|
|
@ -1,13 +1,29 @@
|
||||||
{
|
{
|
||||||
|
"alignment": "Competency",
|
||||||
"badgedetails": "Badge details",
|
"badgedetails": "Badge details",
|
||||||
"badges": "Badges",
|
"badges": "Badges",
|
||||||
|
"bendorsement": "Endorsement",
|
||||||
|
"claimcomment": "Endorsement comment",
|
||||||
|
"claimid": "Claim URL",
|
||||||
"contact": "Contact",
|
"contact": "Contact",
|
||||||
"dateawarded": "Date issued",
|
"dateawarded": "Date issued",
|
||||||
"expired": "Expired",
|
"expired": "Expired",
|
||||||
"expirydate": "Expiry date",
|
"expirydate": "Expiry date",
|
||||||
|
"imageauthoremail": "Image author's email",
|
||||||
|
"imageauthorname": "Image author's name",
|
||||||
|
"imageauthorurl": "Image author's URL",
|
||||||
|
"imagecaption": "Image caption",
|
||||||
"issuancedetails": "Badge expiry",
|
"issuancedetails": "Badge expiry",
|
||||||
"issuerdetails": "Issuer details",
|
"issuerdetails": "Issuer details",
|
||||||
|
"issueremail": "Email",
|
||||||
"issuername": "Issuer name",
|
"issuername": "Issuer name",
|
||||||
|
"issuerurl": "Issuer URL",
|
||||||
|
"language": "Language",
|
||||||
|
"noalignment": "This badge does not have any competencies specified.",
|
||||||
"nobadges": "There are no badges available.",
|
"nobadges": "There are no badges available.",
|
||||||
"recipientdetails": "Recipient details"
|
"norelated": "This badge does not have any related badges.",
|
||||||
}
|
"recipientdetails": "Recipient details",
|
||||||
|
"relatedbages": "Related badges",
|
||||||
|
"version": "Version",
|
||||||
|
"warnexpired": "(This badge has expired!)"
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detalles de insignia",
|
|
||||||
"badges": "Insignias",
|
|
||||||
"contact": "Contacto",
|
|
||||||
"dateawarded": "Fecha de emisión",
|
|
||||||
"expired": "Lo sentimos, esta actividad se cerró el {{$a}} y ya no está disponible",
|
|
||||||
"expirydate": "Fecha de caducidad",
|
|
||||||
"issuancedetails": "Caducidad de insignia",
|
|
||||||
"issuerdetails": "Detalles del emisor",
|
|
||||||
"issuername": "Nombre del emisor",
|
|
||||||
"nobadges": "No hay insignias disponibles.",
|
|
||||||
"recipientdetails": "Detalles de receptores"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detalles de la insignia",
|
|
||||||
"badges": "Insignias",
|
|
||||||
"contact": "Contacto",
|
|
||||||
"dateawarded": "Fecha de la emisión",
|
|
||||||
"expired": "Lo sentimos, esta actividad se cerró el {{$a}} y ya no está disponible",
|
|
||||||
"expirydate": "Fecha de expiración",
|
|
||||||
"issuancedetails": "Caducidad de la insignia",
|
|
||||||
"issuerdetails": "Detalles del emisor",
|
|
||||||
"issuername": "Nombre del emisor",
|
|
||||||
"nobadges": "No hay insignias disponibles",
|
|
||||||
"recipientdetails": "Detalles del destinatario"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Dominaren xehetasunak",
|
|
||||||
"badges": "Dominak",
|
|
||||||
"contact": "Kontaktua",
|
|
||||||
"dateawarded": "Emate-data",
|
|
||||||
"expired": "Sentitzen dugu, jarduera hau {{$a}}(e)an itxi zen eta dagoeneko ez dago eskuragarri.",
|
|
||||||
"expirydate": "Epemugaren data",
|
|
||||||
"issuancedetails": "Dominaren iraungitzea",
|
|
||||||
"issuerdetails": "Emailearen xehetasunak",
|
|
||||||
"issuername": "Emailearen izena",
|
|
||||||
"nobadges": "Ez dago dominarik eskura.",
|
|
||||||
"recipientdetails": "Jasotzailearen zehaztasunak"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "مشخصات مدال",
|
|
||||||
"badges": "مدالها",
|
|
||||||
"contact": "تماس",
|
|
||||||
"dateawarded": "تاریخ صدور",
|
|
||||||
"expired": "با عرض پوزش، این فعالیت در {{$a}} بسته شد و دیگر در دسترس نیست",
|
|
||||||
"expirydate": "تاریخ انقضا",
|
|
||||||
"issuancedetails": "انقضای مدال",
|
|
||||||
"issuerdetails": "مشخصات صادرکننده",
|
|
||||||
"issuername": "نام صادرکننده",
|
|
||||||
"nobadges": "مدالی موجود نیست.",
|
|
||||||
"recipientdetails": "مشخصات دریافتکننده"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Osaamismerkin tiedot",
|
|
||||||
"badges": "Osaamismerkit",
|
|
||||||
"contact": "Yhteystieto",
|
|
||||||
"dateawarded": "Myöntämispäivä",
|
|
||||||
"expired": "Tämä aktiviteeti on suljettu {{$a}} eikä ole enää käytettävissä.",
|
|
||||||
"expirydate": "Vanhenemispäivä",
|
|
||||||
"issuancedetails": "Osaamismerkin vanhentuminen",
|
|
||||||
"issuerdetails": "Osaamismerkin myöntäjän tiedot",
|
|
||||||
"issuername": "Osaamismerkin myöntäjän nimi",
|
|
||||||
"nobadges": "Yhtään osaamismerkkiä ei ole tarjolla",
|
|
||||||
"recipientdetails": "Vastaanottajan tiedot"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Description du badge",
|
|
||||||
"badges": "Badges",
|
|
||||||
"contact": "Contact",
|
|
||||||
"dateawarded": "Date de remise",
|
|
||||||
"expired": "Désolé, cette activité s'est terminée le {{$a}} et n'est plus disponible",
|
|
||||||
"expirydate": "Date d'échéance",
|
|
||||||
"issuancedetails": "Échéance du badge",
|
|
||||||
"issuerdetails": "Détail de l'émetteur",
|
|
||||||
"issuername": "Nom de l'émetteur",
|
|
||||||
"nobadges": "Il n'y a pas de badge disponible.",
|
|
||||||
"recipientdetails": "Infos détenteur"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "פרטי ההישג",
|
|
||||||
"badges": "הישגים",
|
|
||||||
"contact": "ליצירת קשר",
|
|
||||||
"dateawarded": "תאריך הקבלה",
|
|
||||||
"expired": "מצטערים, פעילות זו נסגרה על {{$a}} והיא איננה זמינה יותר",
|
|
||||||
"expirydate": "תאריך תפוגה",
|
|
||||||
"issuancedetails": "מועד תפוגת ההישג",
|
|
||||||
"issuerdetails": "פרטי הגורם אשר העניק את ההישג",
|
|
||||||
"issuername": "שם מעניק ההישג",
|
|
||||||
"nobadges": "אין הישגים זמינים.",
|
|
||||||
"recipientdetails": "פרטי המכותב"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detalji značke",
|
|
||||||
"badges": "Značke",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Datum izdavanja",
|
|
||||||
"expired": "Nažalost, ova aktivnost je zatvorena od {{$a}} i nije više dostupna",
|
|
||||||
"expirydate": "Datum isteka",
|
|
||||||
"issuancedetails": "Istek značke",
|
|
||||||
"issuerdetails": "Detalji o izdavaču",
|
|
||||||
"issuername": "Ime izdavača",
|
|
||||||
"nobadges": "Nema dostupnih značaka.",
|
|
||||||
"recipientdetails": "Podaci o dobitniku"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Részletek",
|
|
||||||
"badges": "Kitűzők",
|
|
||||||
"contact": "Kapcsolat",
|
|
||||||
"dateawarded": "Kiadás dátuma",
|
|
||||||
"expired": "Ez a tevékenység {{$a}} időpontban lezárult és már nem érhető el",
|
|
||||||
"expirydate": "Lejárat időpontja",
|
|
||||||
"issuancedetails": "A kitűző lejárata",
|
|
||||||
"issuerdetails": "Az adományozó adatai",
|
|
||||||
"issuername": "Az adományozó neve",
|
|
||||||
"nobadges": "Nincs elérhető kitűző.",
|
|
||||||
"recipientdetails": "A megjutalmazott adatai"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Dettagli badge",
|
|
||||||
"badges": "Badge",
|
|
||||||
"contact": "Contatto",
|
|
||||||
"dateawarded": "Data di rilascio",
|
|
||||||
"expired": "Spiacente, questa attività è stata chiusa il {{$a}} e non è più disponibile",
|
|
||||||
"expirydate": "Data di scadenza",
|
|
||||||
"issuancedetails": "Scadenza badge",
|
|
||||||
"issuerdetails": "Dettagli di chi rilascia il badge",
|
|
||||||
"issuername": "Nome di chi rilascia il badge",
|
|
||||||
"nobadges": "Non sono presenti badge.",
|
|
||||||
"recipientdetails": "Dettagli destinatario"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "バッジ詳細",
|
|
||||||
"badges": "バッジ",
|
|
||||||
"contact": "連絡先",
|
|
||||||
"dateawarded": "発効日",
|
|
||||||
"expired": "申し訳ございません、この活動は {{$a}} に終了しているため、これ以上利用することはできません。",
|
|
||||||
"expirydate": "有効期限",
|
|
||||||
"issuancedetails": "バッジ有効期限",
|
|
||||||
"issuerdetails": "発行者詳細",
|
|
||||||
"issuername": "発行者名",
|
|
||||||
"nobadges": "利用できるバッジはありません。",
|
|
||||||
"recipientdetails": "取得者詳細"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "뱃지 세부사항",
|
|
||||||
"badges": "뱃지",
|
|
||||||
"contact": "연락처",
|
|
||||||
"dateawarded": "발행일",
|
|
||||||
"expired": "죄송합니다. 이 활동은 {{$a}} 에 종료되어서 더 이상 사용할 수 없습니다.",
|
|
||||||
"expirydate": "만료일",
|
|
||||||
"issuancedetails": "뱃지 만료기한",
|
|
||||||
"issuerdetails": "발행자 세부정보",
|
|
||||||
"issuername": "발행자 이름",
|
|
||||||
"nobadges": "사용가능한 뱃지가 없습니다.",
|
|
||||||
"recipientdetails": "수신자 세부사항"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Pasiekimo detalės",
|
|
||||||
"badges": "Pasiekimai",
|
|
||||||
"contact": "Kontaktas",
|
|
||||||
"dateawarded": "Suteikimo data",
|
|
||||||
"expired": "Atsiprašome, veikla uždaryta {{$a}} ir nebegalima",
|
|
||||||
"expirydate": "Galiojimo laikas",
|
|
||||||
"issuancedetails": "Pasiekimo galiojimas",
|
|
||||||
"issuerdetails": "Suteikėjo detalesnė informacija",
|
|
||||||
"issuername": "Suteikėjo vardas",
|
|
||||||
"nobadges": "Nėra sukurtų pasiekimų.",
|
|
||||||
"recipientdetails": "Informacija apie gavėją"
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"expired": "संपलेला"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Badgedetails",
|
|
||||||
"badges": "Badges",
|
|
||||||
"contact": "Contact",
|
|
||||||
"dateawarded": "Uitgavedatum",
|
|
||||||
"expired": "Sorry, deze activiteit is afgesloten op {{$a}} en is niet meer beschikbaar",
|
|
||||||
"expirydate": "Vervaldatum",
|
|
||||||
"issuancedetails": "Badge verloopt",
|
|
||||||
"issuerdetails": "Details uitgever",
|
|
||||||
"issuername": "Naam uitgever",
|
|
||||||
"nobadges": "Er zijn geen badges beschikbaar.",
|
|
||||||
"recipientdetails": "Details ontvanger"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Utmerkelsesdetaljer",
|
|
||||||
"badges": "Utmerkelser",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Dato tildelt",
|
|
||||||
"expired": "Beklager, denne aktiviteten ble stengt {{$a}} og er ikke tilgjengelig lenger.",
|
|
||||||
"expirydate": "Utløpsdato",
|
|
||||||
"issuancedetails": "Utløpsdato på utmerkelse",
|
|
||||||
"issuerdetails": "Utstederdetaljer",
|
|
||||||
"issuername": "Navn på utsteder",
|
|
||||||
"nobadges": "Det er ingen tilgjengelige utmerkelser.",
|
|
||||||
"recipientdetails": "Mottakerdetaljer"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Szczegóły odznaki",
|
|
||||||
"badges": "Odznaki",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Data wydania",
|
|
||||||
"expired": "Niestety ta aktywność została zamknięta {{$a}} i nie jest już dostępna.",
|
|
||||||
"expirydate": "Data ważności",
|
|
||||||
"issuancedetails": "Wygaśnięcie odznaki",
|
|
||||||
"issuerdetails": "Dane wystawcy",
|
|
||||||
"issuername": "Nazwa wydawcy",
|
|
||||||
"nobadges": "Brak dostępnych odznak",
|
|
||||||
"recipientdetails": "Dane odbiorcy"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detalhes do emblema",
|
|
||||||
"badges": "Emblemas",
|
|
||||||
"contact": "Contato",
|
|
||||||
"dateawarded": "Data de emissão",
|
|
||||||
"expired": "Esta atividade está encerrada desde {{$a}}",
|
|
||||||
"expirydate": "Data de validade",
|
|
||||||
"issuancedetails": "Expiração do emblema",
|
|
||||||
"issuerdetails": "Detalhes do emissor",
|
|
||||||
"issuername": "Nome do emissor",
|
|
||||||
"nobadges": "Não há emblemas disponíveis.",
|
|
||||||
"recipientdetails": "Detalhes do usuário a receber o emblema"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detalhes da Medalha",
|
|
||||||
"badges": "Medalhas",
|
|
||||||
"contact": "Contacto",
|
|
||||||
"dateawarded": "Data de emissão",
|
|
||||||
"expired": "Esta atividade terminou em {{$a}} e já não está disponível",
|
|
||||||
"expirydate": "Data de validade",
|
|
||||||
"issuancedetails": "Data de validade da Medalha",
|
|
||||||
"issuerdetails": "Detalhes do emissor",
|
|
||||||
"issuername": "Nome do emissor",
|
|
||||||
"nobadges": "Não existem Medalhas disponíveis.",
|
|
||||||
"recipientdetails": "Detalhes do condecorado"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detalii ecuson",
|
|
||||||
"badges": "Ecusoane",
|
|
||||||
"contact": "Contact",
|
|
||||||
"dateawarded": "Data emiterii",
|
|
||||||
"expired": "Ne pare rău, această activitate s-a închis la {{$a}} şi nu mai este disponibilă",
|
|
||||||
"expirydate": "Dată de expirare",
|
|
||||||
"issuancedetails": "Expirare ecuson",
|
|
||||||
"issuerdetails": "Detalii emitent",
|
|
||||||
"issuername": "Nume emitent",
|
|
||||||
"nobadges": "Nu există ecusoane disponibile",
|
|
||||||
"recipientdetails": "Detalii recipient"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Подробнее о значке",
|
|
||||||
"badges": "Значки",
|
|
||||||
"contact": "Контакты",
|
|
||||||
"dateawarded": "Дата выдачи",
|
|
||||||
"expired": "Извините, этот элемент курса закрыт {{$a}} и более недоступен",
|
|
||||||
"expirydate": "Дата окончания срока действия",
|
|
||||||
"issuancedetails": "Срок действия значка",
|
|
||||||
"issuerdetails": "Сведения об эмитенте",
|
|
||||||
"issuername": "Наименование эмитента",
|
|
||||||
"nobadges": "Нет доступных значков.",
|
|
||||||
"recipientdetails": "Сведения о получателе"
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Подаци о беџу",
|
|
||||||
"badges": "Беџеви",
|
|
||||||
"contact": "Контакт",
|
|
||||||
"dateawarded": "Датум издавања",
|
|
||||||
"expirydate": "Датум истека",
|
|
||||||
"issuancedetails": "Беџ истиче",
|
|
||||||
"issuerdetails": "Подаци о издавачу",
|
|
||||||
"issuername": "Име/назив издавача беџа",
|
|
||||||
"nobadges": "Нема доступних беџева",
|
|
||||||
"recipientdetails": "Детаљи о примаоцу"
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Podaci o bedžu",
|
|
||||||
"badges": "Bedževi",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Datum izdavanja",
|
|
||||||
"expirydate": "Datum isteka",
|
|
||||||
"issuancedetails": "Bedž ističe",
|
|
||||||
"issuerdetails": "Podaci o izdavaču",
|
|
||||||
"issuername": "Ime/naziv izdavača bedža",
|
|
||||||
"nobadges": "Nema dostupnih bedževa",
|
|
||||||
"recipientdetails": "Detalji o primaocu"
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Detaljer för märke",
|
|
||||||
"badges": "Märken",
|
|
||||||
"contact": "Kontakt",
|
|
||||||
"dateawarded": "Utfärdandedatum",
|
|
||||||
"expired": "Den här aktiviteten är stängd på {{$a}} och den är inte längre tillgänglig.",
|
|
||||||
"expirydate": "Förfallodatum",
|
|
||||||
"issuancedetails": "Förfallande av märke",
|
|
||||||
"issuerdetails": "Utfärdarens detaljer",
|
|
||||||
"issuername": "Utfärdarens namn",
|
|
||||||
"nobadges": "Det finns inga märken tillgängliga."
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"badges": "Бейҷҳо",
|
|
||||||
"expired": "Бубахшед,ин фаъолият маҳкам карда шудааст {{$a}} ва акнун дастрас нест"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Nişan ayrıntıları",
|
|
||||||
"badges": "Nişanlar",
|
|
||||||
"contact": "İletişim",
|
|
||||||
"dateawarded": "Verilen tarih",
|
|
||||||
"expired": "Üzgünüz, bu etkinlik {{$a}} tarihinde kapandı ve bu etkinliğe artık ulaşılamaz",
|
|
||||||
"expirydate": "Bitiş Tarihi",
|
|
||||||
"issuancedetails": "Rozet sona erme",
|
|
||||||
"issuerdetails": "çıkaran ayrıntıları",
|
|
||||||
"issuername": "Çıkaranın adı",
|
|
||||||
"nobadges": "Uygun nişan bulunmuyor.",
|
|
||||||
"recipientdetails": "Alıcı bilgileri"
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "Детальніше про відзнаку",
|
|
||||||
"badges": "Відзнаки",
|
|
||||||
"contact": "Контакт",
|
|
||||||
"dateawarded": "Дата отримання",
|
|
||||||
"expired": "На жаль, ця діяльність закрита для {{$a}} та більше недоступна",
|
|
||||||
"expirydate": "Дата завершення",
|
|
||||||
"issuancedetails": "Відзнака не актуальна",
|
|
||||||
"issuerdetails": "Деталі присудження",
|
|
||||||
"issuername": "Ім’я видавця",
|
|
||||||
"nobadges": "Немає доступних відзнак.",
|
|
||||||
"recipientdetails": "Деталі отримувача"
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "勋章详情",
|
|
||||||
"badges": "勋章",
|
|
||||||
"contact": "联系",
|
|
||||||
"dateawarded": "授予日期",
|
|
||||||
"expirydate": "过期时间",
|
|
||||||
"issuancedetails": "有效期",
|
|
||||||
"issuerdetails": "授勋机构详情",
|
|
||||||
"issuername": "授勋机构名称",
|
|
||||||
"nobadges": "没有可用的勋章",
|
|
||||||
"recipientdetails": "获得者详情"
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"badgedetails": "獎章細節",
|
|
||||||
"badges": "獎章",
|
|
||||||
"contact": "聯絡",
|
|
||||||
"dateawarded": "頒發的日期",
|
|
||||||
"expirydate": "失效日期",
|
|
||||||
"issuancedetails": "獎章到期",
|
|
||||||
"issuerdetails": "頒授者細節",
|
|
||||||
"issuername": "頒授者的姓名",
|
|
||||||
"nobadges": "這裡沒有可用的獎章",
|
|
||||||
"recipientdetails": "收件者細節"
|
|
||||||
}
|
|
|
@ -19,7 +19,7 @@
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<ion-item-group *ngIf="user.fullname">
|
<ion-item-group *ngIf="user.fullname">
|
||||||
<ion-item-divider color="light">
|
<ion-item-divider>
|
||||||
<h2>{{ 'addon.badges.recipientdetails' | translate}}</h2>
|
<h2>{{ 'addon.badges.recipientdetails' | translate}}</h2>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item text-wrap>
|
<ion-item text-wrap>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item-divider color="light">
|
<ion-item-divider>
|
||||||
<h2>{{ 'addon.badges.issuerdetails' | translate}}</h2>
|
<h2>{{ 'addon.badges.issuerdetails' | translate}}</h2>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item text-wrap *ngIf="badge.issuername">
|
<ion-item text-wrap *ngIf="badge.issuername">
|
||||||
|
@ -42,19 +42,27 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item text-wrap *ngIf="badge.issuercontact">
|
<ion-item text-wrap *ngIf="badge.issuercontact">
|
||||||
<h2>{{ 'addon.badges.contact' | translate}}</h2>
|
<h2>{{ 'addon.badges.contact' | translate}}</h2>
|
||||||
<p>
|
<p><a href="mailto:{{badge.issuercontact}}" core-link auto-login="no">
|
||||||
<core-format-text clean="true" [text]="badge.issuercontact"></core-format-text>
|
<core-format-text [text]="badge.issuercontact"></core-format-text>
|
||||||
</p>
|
</a></p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item-divider color="light">
|
<ion-item-divider>
|
||||||
<h2>{{ 'addon.badges.badgedetails' | translate}}</h2>
|
<h2>{{ 'addon.badges.badgedetails' | translate}}</h2>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item text-wrap *ngIf="badge.name">
|
<ion-item text-wrap *ngIf="badge.name">
|
||||||
<h2>{{ 'core.name' | translate}}</h2>
|
<h2>{{ 'core.name' | translate}}</h2>
|
||||||
<p>{{badge.name}}</p>
|
<p>{{ badge.name }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.version">
|
||||||
|
<h2>{{ 'addon.badges.version' | translate}}</h2>
|
||||||
|
<p>{{ badge.version }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.language">
|
||||||
|
<h2>{{ 'addon.badges.language' | translate}}</h2>
|
||||||
|
<p>{{ badge.language }}</p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item text-wrap *ngIf="badge.description">
|
<ion-item text-wrap *ngIf="badge.description">
|
||||||
<h2>{{ 'core.description' | translate}}</h2>
|
<h2>{{ 'core.description' | translate}}</h2>
|
||||||
|
@ -62,25 +70,117 @@
|
||||||
<core-format-text clean="true" [text]="badge.description"></core-format-text>
|
<core-format-text clean="true" [text]="badge.description"></core-format-text>
|
||||||
</p>
|
</p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.imageauthorname">
|
||||||
|
<h2>{{ 'addon.badges.imageauthorname' | translate}}</h2>
|
||||||
|
<p>{{ badge.imageauthorname }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.imageauthoremail">
|
||||||
|
<h2>{{ 'addon.badges.imageauthoremail' | translate}}</h2>
|
||||||
|
<p><a href="mailto:{{badge.imageauthoremail}}" core-link auto-login="no">
|
||||||
|
<core-format-text [text]="badge.imageauthoremail"></core-format-text>
|
||||||
|
</a></p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.imageauthorurl">
|
||||||
|
<h2>{{ 'addon.badges.imageauthorurl' | translate}}</h2>
|
||||||
|
<p><a [href]="badge.imageauthorurl" core-link auto-login="no">
|
||||||
|
<core-format-text [text]="badge.imageauthorurl"></core-format-text>
|
||||||
|
</a></p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.imagecaption">
|
||||||
|
<h2>{{ 'addon.badges.imagecaption' | translate}}</h2>
|
||||||
|
<p><core-format-text [text]="badge.imagecaption"></core-format-text></p>
|
||||||
|
</ion-item>
|
||||||
<ion-item text-wrap *ngIf="course.fullname">
|
<ion-item text-wrap *ngIf="course.fullname">
|
||||||
<h2>{{ 'core.course' | translate}}</h2>
|
<h2>{{ 'core.course' | translate}}</h2>
|
||||||
<p>
|
<p>
|
||||||
<core-format-text [text]="course.fullname"></core-format-text>
|
<core-format-text [text]="course.fullname"></core-format-text>
|
||||||
</p>
|
</p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<!-- Criteria (not yet avalaible) -->
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item-divider color="light">
|
<ion-item-divider>
|
||||||
<h2>{{ 'addon.badges.issuancedetails' | translate}}</h2>
|
<h2>{{ 'addon.badges.issuancedetails' | translate}}</h2>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item text-wrap *ngIf="badge.dateissued">
|
<ion-item text-wrap *ngIf="badge.dateissued">
|
||||||
<h2>{{ 'addon.badges.dateawarded' | translate}}</h2>
|
<h2>{{ 'addon.badges.dateawarded' | translate}}</h2>
|
||||||
<p>{{badge.dateissued | coreToLocaleString }}</p>
|
<p>{{badge.dateissued * 1000 | coreFormatDate }}</p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item text-wrap *ngIf="badge.dateexpire">
|
<ion-item text-wrap *ngIf="badge.dateexpire">
|
||||||
<h2>{{ 'addon.badges.expirydate' | translate}}</h2>
|
<h2>{{ 'addon.badges.expirydate' | translate}}</h2>
|
||||||
<p>{{badge.dateexpire | coreToLocaleString }}</p>
|
<p>
|
||||||
|
{{ badge.dateexpire * 1000 | coreFormatDate }}
|
||||||
|
<span class="text-danger" *ngIf="currentTime >= badge.dateexpire">
|
||||||
|
{{ 'addon.badges.warnexpired' | translate }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</ion-item>
|
||||||
|
<!-- Evidence (not yet avalaible) -->
|
||||||
|
</ion-item-group>
|
||||||
|
|
||||||
|
<!-- Endorsement -->
|
||||||
|
<ion-item-group *ngIf="badge.endorsement">
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.badges.bendorsement' | translate}}</h2>
|
||||||
|
</ion-item-divider>
|
||||||
|
<ion-item text-wrap *ngIf="badge.endorsement.issuername">
|
||||||
|
<h2>{{ 'addon.badges.issuername' | translate}}</h2>
|
||||||
|
<p>{{ badge.endorsement.issuername }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.endorsement.issueremail">
|
||||||
|
<h2>{{ 'addon.badges.issueremail' | translate}}</h2>
|
||||||
|
<p><a href="mailto:{{badge.endorsement.issueremail}}" core-link auto-login="no">
|
||||||
|
<core-format-text [text]="badge.endorsement.issueremail"></core-format-text>
|
||||||
|
</a></p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.endorsement.issuerurl">
|
||||||
|
<h2>{{ 'addon.badges.issuerurl' | translate}}</h2>
|
||||||
|
<p><a [href]="badge.endorsement.issuerurl" core-link auto-login="no">
|
||||||
|
<core-format-text [text]="badge.endorsement.issuerurl"></core-format-text>
|
||||||
|
</a></p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.endorsement.dateissued">
|
||||||
|
<h2>{{ 'addon.badges.dateawarded' | translate}}</h2>
|
||||||
|
<p>{{ badge.endorsement.dateissued * 1000 | coreFormatDate }}</p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.endorsement.claimid">
|
||||||
|
<h2>{{ 'addon.badges.claimid' | translate}}</h2>
|
||||||
|
<p><a [href]="badge.endorsement.claimid" core-link auto-login="no">
|
||||||
|
<core-format-text [text]="badge.endorsement.claimid"></core-format-text>
|
||||||
|
</a></p>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.endorsement.claimcomment">
|
||||||
|
<h2>{{ 'addon.badges.claimcomment' | translate}}</h2>
|
||||||
|
<p>
|
||||||
|
<core-format-text [text]="badge.endorsement.claimcomment"></core-format-text>
|
||||||
|
</p>
|
||||||
|
</ion-item>
|
||||||
|
</ion-item-group>
|
||||||
|
|
||||||
|
<!-- Related badges -->
|
||||||
|
<ion-item-group *ngIf="badge.relatedbadges">
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.badges.relatedbages' | translate}}</h2>
|
||||||
|
</ion-item-divider>
|
||||||
|
<ion-item text-wrap *ngFor="let relatedBadge of badge.relatedbadges">
|
||||||
|
<h2><core-format-text [text]="relatedBadge.name"></core-format-text></h2>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="badge.relatedbadges.length == 0">
|
||||||
|
<h2>{{ 'addon.badges.norelated' | translate}}</h2>
|
||||||
|
</ion-item>
|
||||||
|
</ion-item-group>
|
||||||
|
|
||||||
|
<!-- Competencies alignment -->
|
||||||
|
<ion-item-group *ngIf="badge.competencies">
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.badges.alignment' | translate}}</h2>
|
||||||
|
</ion-item-divider>
|
||||||
|
<a ion-item text-wrap *ngFor="let competency of badge.competencies" [href]="competency.targeturl" core-link auto-login="no">
|
||||||
|
<h2><core-format-text [text]="competency.targetname"></core-format-text></h2>
|
||||||
|
</a>
|
||||||
|
<ion-item text-wrap *ngIf="badge.competencies.length == 0">
|
||||||
|
<h2>{{ 'addon.badges.noalignment' | translate}}</h2>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
|
|
||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, ViewChild } from '@angular/core';
|
||||||
import { IonicPage, Content, NavParams } from 'ionic-angular';
|
import { IonicPage, Content, NavParams } from 'ionic-angular';
|
||||||
import { AddonBadgesProvider } from '../../providers/badges';
|
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreUserProvider } from '@core/user/providers/user';
|
import { CoreUserProvider } from '@core/user/providers/user';
|
||||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { AddonBadgesProvider } from '../../providers/badges';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of calendar events.
|
* Page that displays the list of calendar events.
|
||||||
|
@ -32,9 +32,10 @@ import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
export class AddonBadgesIssuedBadgePage {
|
export class AddonBadgesIssuedBadgePage {
|
||||||
@ViewChild(Content) content: Content;
|
@ViewChild(Content) content: Content;
|
||||||
|
|
||||||
courseId: number;
|
protected badgeHash: string;
|
||||||
userId: number;
|
protected userId: number;
|
||||||
badgeHash: string;
|
protected courseId: number;
|
||||||
|
|
||||||
user: any = {};
|
user: any = {};
|
||||||
course: any = {};
|
course: any = {};
|
||||||
badge: any = {};
|
badge: any = {};
|
||||||
|
@ -70,29 +71,29 @@ export class AddonBadgesIssuedBadgePage {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
this.currentTime = this.timeUtils.timestamp();
|
this.currentTime = this.timeUtils.timestamp();
|
||||||
let promise = this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => {
|
promises.push(this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
});
|
}));
|
||||||
promises.push(promise);
|
|
||||||
|
|
||||||
promise = this.badgesProvider.getUserBadges(this.courseId, this.userId).then((badges) => {
|
promises.push(this.badgesProvider.getUserBadges(this.courseId, this.userId).then((badges) => {
|
||||||
badges.forEach((badge) => {
|
const badge = badges.find((badge) => {
|
||||||
if (this.badgeHash == badge.uniquehash) {
|
return this.badgeHash == badge.uniquehash;
|
||||||
this.badge = badge;
|
|
||||||
if (badge.courseid) {
|
|
||||||
return this.coursesProvider.getUserCourse(badge.courseid, true).then((course) => {
|
|
||||||
this.course = course;
|
|
||||||
}).catch(() => {
|
|
||||||
// Maybe an old deleted course.
|
|
||||||
this.course = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (badge) {
|
||||||
|
this.badge = badge;
|
||||||
|
if (badge.courseid) {
|
||||||
|
return this.coursesProvider.getUserCourse(badge.courseid, true).then((course) => {
|
||||||
|
this.course = course;
|
||||||
|
}).catch(() => {
|
||||||
|
// Maybe an old deleted course.
|
||||||
|
this.course = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}).catch((message) => {
|
}).catch((message) => {
|
||||||
this.domUtils.showErrorModalDefault(message, 'Error getting badge data.');
|
this.domUtils.showErrorModalDefault(message, 'Error getting badge data.');
|
||||||
});
|
}));
|
||||||
promises.push(promise);
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<img [src]="badge.badgeurl" [alt]="badge.name" item-start core-external-content>
|
<img [src]="badge.badgeurl" [alt]="badge.name" item-start core-external-content>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
<h2><core-format-text [text]="badge.name"></core-format-text></h2>
|
<h2><core-format-text [text]="badge.name"></core-format-text></h2>
|
||||||
<p>{{ badge.dateissued | coreToLocaleString }}</p>
|
<p>{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}</p>
|
||||||
<ion-badge item-end color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
|
<ion-badge item-end color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
|
||||||
{{ 'addon.badges.expired' | translate }}
|
{{ 'addon.badges.expired' | translate }}
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { AddonBlockActivityModulesComponentsModule } from './components/components.module';
|
||||||
|
import { CoreBlockDelegate } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockActivityModulesHandler } from './providers/block-handler';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
AddonBlockActivityModulesComponentsModule,
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonBlockActivityModulesHandler
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockActivityModulesModule {
|
||||||
|
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockActivityModulesHandler) {
|
||||||
|
blockDelegate.registerHandler(blockHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
ion-app.app-root.md addon-block-activitymodules {
|
||||||
|
.core-module-icon {
|
||||||
|
margin-top: $label-md-margin-top;
|
||||||
|
margin-bottom: $label-md-margin-bottom;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-app.app-root.ios addon-block-activitymodules {
|
||||||
|
.core-module-icon {
|
||||||
|
margin-top: $label-ios-margin-top;
|
||||||
|
margin-bottom: $label-ios-margin-bottom;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-app.app-root.wp addon-block-activitymodules {
|
||||||
|
.core-module-icon {
|
||||||
|
margin-top: $item-wp-padding-top;
|
||||||
|
margin-bottom: $item-wp-padding-bottom;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
// (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, OnInit, Injector, Input } from '@angular/core';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
|
||||||
|
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
|
||||||
|
import { CoreConstants } from '@core/constants';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render an "activity modules" block.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-block-activitymodules',
|
||||||
|
templateUrl: 'addon-block-activitymodules.html'
|
||||||
|
})
|
||||||
|
export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent implements OnInit {
|
||||||
|
@Input() block: any; // The block to render.
|
||||||
|
@Input() contextLevel: string; // The context where the block will be used.
|
||||||
|
@Input() instanceId: number; // The instance ID associated with the context level.
|
||||||
|
|
||||||
|
entries: any[] = [];
|
||||||
|
|
||||||
|
protected fetchContentDefaultError = 'Error getting activity modules data.';
|
||||||
|
|
||||||
|
constructor(injector: Injector, protected utils: CoreUtilsProvider, protected courseProvider: CoreCourseProvider,
|
||||||
|
protected translate: TranslateService, protected moduleDelegate: CoreCourseModuleDelegate) {
|
||||||
|
|
||||||
|
super(injector, 'AddonBlockActivityModulesComponent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
return this.courseProvider.invalidateSections(this.instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the data to render the block.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(): Promise<any> {
|
||||||
|
return this.courseProvider.getSections(this.instanceId, false, true).then((sections) => {
|
||||||
|
|
||||||
|
this.entries = [];
|
||||||
|
|
||||||
|
const archetypes = {},
|
||||||
|
modIcons = {};
|
||||||
|
let modFullNames = {};
|
||||||
|
|
||||||
|
sections.forEach((section) => {
|
||||||
|
if (!section.modules) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.modules.forEach((mod) => {
|
||||||
|
if (mod.uservisible === false || !this.courseProvider.moduleHasView(mod) ||
|
||||||
|
typeof modFullNames[mod.modname] != 'undefined') {
|
||||||
|
// Ignore this module.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the archetype of the module type.
|
||||||
|
if (typeof archetypes[mod.modname] == 'undefined') {
|
||||||
|
archetypes[mod.modname] = this.moduleDelegate.supportsFeature(mod.modname,
|
||||||
|
CoreConstants.FEATURE_MOD_ARCHETYPE, CoreConstants.MOD_ARCHETYPE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the full name of the module type.
|
||||||
|
if (archetypes[mod.modname] == CoreConstants.MOD_ARCHETYPE_RESOURCE) {
|
||||||
|
// All resources are gathered in a single "Resources" option.
|
||||||
|
if (!modFullNames['resources']) {
|
||||||
|
modFullNames['resources'] = this.translate.instant('core.resources');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
modFullNames[mod.modname] = mod.modplural;
|
||||||
|
}
|
||||||
|
modIcons[mod.modname] = mod.modicon;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort the modnames alphabetically.
|
||||||
|
modFullNames = this.utils.sortValues(modFullNames);
|
||||||
|
|
||||||
|
for (const modName in modFullNames) {
|
||||||
|
let icon;
|
||||||
|
|
||||||
|
if (modName === 'resources') {
|
||||||
|
icon = this.courseProvider.getModuleIconSrc('page', modIcons['page']);
|
||||||
|
} else {
|
||||||
|
icon = this.moduleDelegate.getModuleIconSrc(modName, modIcons[modName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.entries.push({
|
||||||
|
icon: icon,
|
||||||
|
name: modFullNames[modName],
|
||||||
|
modName: modName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.block_activitymodules.pluginname' | translate }}</h2>
|
||||||
|
</ion-item-divider>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<a ion-item text-wrap *ngFor="let entry of entries" class="item-media" detail-none [navPush]="'CoreCourseListModTypePage'" [navParams]="{title: entry.name, courseId: instanceId, modName: entry.modName}">
|
||||||
|
<img item-start [src]="entry.icon" alt="" role="presentation" class="core-module-icon">
|
||||||
|
<core-format-text [text]="entry.name"></core-format-text>
|
||||||
|
</a>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AddonBlockActivityModulesComponent } from './activitymodules/activitymodules';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonBlockActivityModulesComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCourseComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonBlockActivityModulesComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonBlockActivityModulesComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockActivityModulesComponentsModule {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"pluginname": "Activities"
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 { Injectable, Injector } from '@angular/core';
|
||||||
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockActivityModulesComponent } from '../components/activitymodules/activitymodules';
|
||||||
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonBlockActivityModulesHandler extends CoreBlockBaseHandler {
|
||||||
|
name = 'AddonBlockActivityModules';
|
||||||
|
blockName = 'activity_modules';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param {Injector} injector Injector.
|
||||||
|
* @param {any} block The block to render.
|
||||||
|
* @param {string} contextLevel The context where the block will be used.
|
||||||
|
* @param {number} instanceId The instance ID associated with the context level.
|
||||||
|
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'addon.block_activitymodules.pluginname',
|
||||||
|
class: 'addon-block-activitymodules',
|
||||||
|
component: AddonBlockActivityModulesComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreCoursesComponentsModule } from '@core/courses/components/components.module';
|
||||||
|
import { AddonBlockMyOverviewComponent } from './myoverview/myoverview';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonBlockMyOverviewComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCoursesComponentsModule,
|
||||||
|
CoreCourseComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonBlockMyOverviewComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonBlockMyOverviewComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockMyOverviewComponentsModule {}
|
|
@ -0,0 +1,44 @@
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.block_myoverview.pluginname' | translate }}</h2>
|
||||||
|
<!-- Download all courses. -->
|
||||||
|
<div *ngIf="downloadEnabled && courses[selectedFilter] && courses[selectedFilter].length > 1 && !showFilter" class="core-button-spinner" item-end>
|
||||||
|
<button *ngIf="prefetchCoursesData[selectedFilter].icon && prefetchCoursesData[selectedFilter].icon != 'spinner'" ion-button icon-only clear color="dark" (click)="prefetchCourses()">
|
||||||
|
<core-icon [name]="prefetchCoursesData[selectedFilter].icon"></core-icon>
|
||||||
|
</button>
|
||||||
|
<ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[selectedFilter].badge">{{prefetchCoursesData[selectedFilter].badge}}</ion-badge>
|
||||||
|
<ion-spinner *ngIf="!prefetchCoursesData[selectedFilter].icon || prefetchCoursesData[selectedFilter].icon == 'spinner'"></ion-spinner>
|
||||||
|
</div>
|
||||||
|
<core-context-menu item-end>
|
||||||
|
<core-context-menu-item *ngIf="loaded && showFilterSwitchButton()" [priority]="1000" [content]="'core.courses.filtermycourses' | translate" (action)="switchFilter()" iconAction="funnel"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="loaded && showSortFilter" [priority]="900" content="{{('core.sortby' | translate) + ' ' + ('addon.block_myoverview.title' | translate)}}" (action)="switchSort('fullname')" [iconAction]="sort == 'fullname' ? 'radio-button-on' : 'radio-button-off'"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="loaded && showSortFilter" [priority]="800" content="{{('core.sortby' | translate) + ' ' + ('addon.block_myoverview.lastaccessed' | translate)}}" (action)="switchSort('lastaccess')" [iconAction]="sort == 'lastaccess' ? 'radio-button-on' : 'radio-button-off'"></core-context-menu-item>
|
||||||
|
</core-context-menu>
|
||||||
|
</ion-item-divider>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<div padding [hidden]="showFilter || !showSelectorFilter" class="safe-padding-horizontal">
|
||||||
|
<!-- "Time" selector. -->
|
||||||
|
<ion-select text-start [title]="'core.show' | translate" [(ngModel)]="selectedFilter" (ngModelChange)="selectedChanged()" interface="popover" class="core-button-select">
|
||||||
|
<ion-option value="all">{{ 'addon.block_myoverview.all' | translate }}</ion-option>∫
|
||||||
|
<ion-option value="inprogress">{{ 'addon.block_myoverview.inprogress' | translate }}</ion-option>
|
||||||
|
<ion-option value="future">{{ 'addon.block_myoverview.future' | translate }}</ion-option>
|
||||||
|
<ion-option value="past">{{ 'addon.block_myoverview.past' | translate }}</ion-option>
|
||||||
|
<ion-option value="favourite" *ngIf="showFavourite">{{ 'addon.block_myoverview.favourites' | translate }}</ion-option>
|
||||||
|
<ion-option value="hidden" *ngIf="showHidden">{{ 'addon.block_myoverview.hiddencourses' | translate }}</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</div>
|
||||||
|
<core-empty-box *ngIf="courses[selectedFilter].length == 0" image="assets/img/icons/courses.svg" [message]="'addon.block_myoverview.nocourses' | translate"></core-empty-box>
|
||||||
|
|
||||||
|
<!-- Filter courses. -->
|
||||||
|
<ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="courses.filter" (ionInput)="filterChanged($event)" (ionCancel)="filterChanged()" [placeholder]="'core.courses.filtermycourses' | translate">
|
||||||
|
</ion-searchbar>
|
||||||
|
<!-- List of courses. -->
|
||||||
|
<div class="safe-area-page">
|
||||||
|
<ion-grid no-padding>
|
||||||
|
<ion-row no-padding>
|
||||||
|
<ion-col *ngFor="let course of filteredCourses" no-padding col-12 col-sm-6 col-md-6 col-lg-4 col-xl-4 align-self-stretch>
|
||||||
|
<core-courses-course-progress [course]="course" class="core-courseoverview" showAll="true" [showDownload]="downloadEnabled"></core-courses-course-progress>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
</div>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,341 @@
|
||||||
|
// (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, OnInit, Input, OnDestroy, ViewChild, Injector } from '@angular/core';
|
||||||
|
import { Searchbar } from 'ionic-angular';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
||||||
|
import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
|
||||||
|
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render a my overview block.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-block-myoverview',
|
||||||
|
templateUrl: 'addon-block-myoverview.html'
|
||||||
|
})
|
||||||
|
export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
|
||||||
|
@ViewChild('searchbar') searchbar: Searchbar;
|
||||||
|
@Input() downloadEnabled: boolean;
|
||||||
|
|
||||||
|
courses = {
|
||||||
|
filter: '',
|
||||||
|
all: [],
|
||||||
|
past: [],
|
||||||
|
inprogress: [],
|
||||||
|
future: [],
|
||||||
|
favourite: [],
|
||||||
|
hidden: []
|
||||||
|
};
|
||||||
|
selectedFilter = 'inprogress';
|
||||||
|
sort = 'fullname';
|
||||||
|
currentSite: any;
|
||||||
|
filteredCourses: any[];
|
||||||
|
prefetchCoursesData = {
|
||||||
|
all: {},
|
||||||
|
inprogress: {},
|
||||||
|
past: {},
|
||||||
|
future: {},
|
||||||
|
favourite: {},
|
||||||
|
hidden: {}
|
||||||
|
};
|
||||||
|
showFilter = false;
|
||||||
|
showFavourite = false;
|
||||||
|
showHidden = false;
|
||||||
|
showSelectorFilter = false;
|
||||||
|
showSortFilter = false;
|
||||||
|
|
||||||
|
protected prefetchIconsInitialized = false;
|
||||||
|
protected isDestroyed;
|
||||||
|
protected downloadButtonObserver;
|
||||||
|
protected coursesObserver;
|
||||||
|
protected courseIds = [];
|
||||||
|
protected fetchContentDefaultError = 'Error getting my overview data.';
|
||||||
|
|
||||||
|
constructor(injector: Injector, private coursesProvider: CoreCoursesProvider,
|
||||||
|
private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider,
|
||||||
|
private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider,
|
||||||
|
private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider,
|
||||||
|
private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider) {
|
||||||
|
|
||||||
|
super(injector, 'AddonBlockMyOverviewComponent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Refresh the enabled flags if enabled.
|
||||||
|
this.downloadButtonObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED,
|
||||||
|
(data) => {
|
||||||
|
const wasEnabled = this.downloadEnabled;
|
||||||
|
|
||||||
|
this.downloadEnabled = data.enabled;
|
||||||
|
|
||||||
|
if (!wasEnabled && this.downloadEnabled && this.loaded) {
|
||||||
|
// Download all courses is enabled now, initialize it.
|
||||||
|
this.initPrefetchCoursesIcons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
|
||||||
|
this.refreshContent();
|
||||||
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
this.currentSite = this.sitesProvider.getCurrentSite();
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewSort', this.sort).then((value) => {
|
||||||
|
this.sort = value;
|
||||||
|
}));
|
||||||
|
promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => {
|
||||||
|
this.selectedFilter = typeof this.courses[value] == 'undefined' ? 'inprogress' : value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
Promise.all(promises).finally(() => {
|
||||||
|
super.ngOnInit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.coursesProvider.invalidateUserCourses().finally(() => {
|
||||||
|
// Invalidate course completion data.
|
||||||
|
promises.push(this.coursesProvider.invalidateUserCourses().finally(() => {
|
||||||
|
// Invalidate course completion data.
|
||||||
|
return this.utils.allPromises(this.courseIds.map((courseId) => {
|
||||||
|
return this.courseCompletionProvider.invalidateCourseCompletion(courseId);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions());
|
||||||
|
if (this.courseIds.length > 0) {
|
||||||
|
promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds.join(',')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises).finally(() => {
|
||||||
|
this.prefetchIconsInitialized = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the courses for my overview.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(): Promise<any> {
|
||||||
|
return this.coursesHelper.getUserCoursesWithOptions(this.sort).then((courses) => {
|
||||||
|
this.courseIds = courses.map((course) => {
|
||||||
|
return course.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showSortFilter = courses.length > 0 && typeof courses[0].lastaccess != 'undefined';
|
||||||
|
|
||||||
|
this.initCourseFilters(courses);
|
||||||
|
|
||||||
|
this.courses.filter = '';
|
||||||
|
this.showFilter = false;
|
||||||
|
this.showSelectorFilter = courses.length > 0 && (this.courses.past.length > 0 || this.courses.future.length > 0 ||
|
||||||
|
typeof courses[0].enddate != 'undefined');
|
||||||
|
this.showHidden = this.showSelectorFilter && typeof courses[0].hidden != 'undefined';
|
||||||
|
this.showFavourite = this.showSelectorFilter && typeof courses[0].isfavourite != 'undefined';
|
||||||
|
if (!this.showSelectorFilter) {
|
||||||
|
// No selector, show all.
|
||||||
|
this.selectedFilter = 'all';
|
||||||
|
}
|
||||||
|
this.filteredCourses = this.courses[this.selectedFilter];
|
||||||
|
|
||||||
|
this.initPrefetchCoursesIcons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filter has changed.
|
||||||
|
*
|
||||||
|
* @param {any} Received Event.
|
||||||
|
*/
|
||||||
|
filterChanged(event: any): void {
|
||||||
|
const newValue = event.target.value && event.target.value.trim().toLowerCase();
|
||||||
|
if (!newValue || !this.courses['all']) {
|
||||||
|
this.filteredCourses = this.courses['all'];
|
||||||
|
} else {
|
||||||
|
// Use displayname if avalaible, or fullname if not.
|
||||||
|
if (this.courses['all'].length > 0 &&
|
||||||
|
typeof this.courses['all'][0].displayname != 'undefined') {
|
||||||
|
this.filteredCourses = this.courses['all'].filter((course) => {
|
||||||
|
return course.displayname.toLowerCase().indexOf(newValue) > -1;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.filteredCourses = this.courses['all'].filter((course) => {
|
||||||
|
return course.fullname.toLowerCase().indexOf(newValue) > -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the prefetch icon for selected courses.
|
||||||
|
*/
|
||||||
|
protected initPrefetchCoursesIcons(): void {
|
||||||
|
if (this.prefetchIconsInitialized || !this.downloadEnabled) {
|
||||||
|
// Already initialized.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prefetchIconsInitialized = true;
|
||||||
|
|
||||||
|
Object.keys(this.prefetchCoursesData).forEach((filter) => {
|
||||||
|
this.courseHelper.initPrefetchCoursesIcons(this.courses[filter], this.prefetchCoursesData[filter]).then((prefetch) => {
|
||||||
|
this.prefetchCoursesData[filter] = prefetch;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch all the shown courses.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
prefetchCourses(): Promise<any> {
|
||||||
|
const selected = this.selectedFilter,
|
||||||
|
initialIcon = this.prefetchCoursesData[selected].icon;
|
||||||
|
|
||||||
|
return this.courseHelper.prefetchCourses(this.courses[selected], this.prefetchCoursesData[selected]).catch((error) => {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||||
|
this.prefetchCoursesData[selected].icon = initialIcon;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The selected courses filter have changed.
|
||||||
|
*/
|
||||||
|
selectedChanged(): void {
|
||||||
|
this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter);
|
||||||
|
this.filteredCourses = this.courses[this.selectedFilter];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init courses filters.
|
||||||
|
*
|
||||||
|
* @param {any[]} courses Courses to filter.
|
||||||
|
*/
|
||||||
|
initCourseFilters(courses: any[]): void {
|
||||||
|
if (this.showSortFilter) {
|
||||||
|
if (this.sort == 'lastaccess') {
|
||||||
|
courses.sort((a, b) => {
|
||||||
|
return b.lastaccess - a.lastaccess;
|
||||||
|
});
|
||||||
|
} else if (this.sort == 'fullname') {
|
||||||
|
courses.sort((a, b) => {
|
||||||
|
const compareA = a.fullname.toLowerCase(),
|
||||||
|
compareB = b.fullname.toLowerCase();
|
||||||
|
|
||||||
|
return compareA.localeCompare(compareB);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.courses.all = [];
|
||||||
|
this.courses.past = [];
|
||||||
|
this.courses.inprogress = [];
|
||||||
|
this.courses.future = [];
|
||||||
|
this.courses.favourite = [];
|
||||||
|
this.courses.hidden = [];
|
||||||
|
|
||||||
|
const today = this.timeUtils.timestamp();
|
||||||
|
courses.forEach((course) => {
|
||||||
|
if (course.hidden) {
|
||||||
|
this.courses.hidden.push(course);
|
||||||
|
} else {
|
||||||
|
this.courses.all.push(course);
|
||||||
|
|
||||||
|
if ((course.enddate && course.enddate < today) || course.completed) {
|
||||||
|
// Courses that have already ended.
|
||||||
|
this.courses.past.push(course);
|
||||||
|
} else if (course.startdate > today) {
|
||||||
|
// Courses that have not started yet.
|
||||||
|
this.courses.future.push(course);
|
||||||
|
} else {
|
||||||
|
// Courses still in progress.
|
||||||
|
this.courses.inprogress.push(course);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (course.isfavourite) {
|
||||||
|
this.courses.favourite.push(course);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filteredCourses = this.courses[this.selectedFilter];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The selected courses sort filter have changed.
|
||||||
|
*
|
||||||
|
* @param {string} sort New sorting.
|
||||||
|
*/
|
||||||
|
switchSort(sort: string): void {
|
||||||
|
this.sort = sort;
|
||||||
|
this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewSort', this.sort);
|
||||||
|
this.initCourseFilters(this.courses.all.concat(this.courses.hidden));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide the filter.
|
||||||
|
*/
|
||||||
|
switchFilter(): void {
|
||||||
|
this.showFilter = !this.showFilter;
|
||||||
|
this.courses.filter = '';
|
||||||
|
this.filteredCourses = this.courses[this.showFilter ? 'all' : this.selectedFilter];
|
||||||
|
if (this.showFilter) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.searchbar.setFocus();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If switch button that enables the filter input is shown or not.
|
||||||
|
*
|
||||||
|
* @return {boolean} If switch button that enables the filter input is shown or not.
|
||||||
|
*/
|
||||||
|
showFilterSwitchButton(): boolean {
|
||||||
|
return this.loaded && this.courses['all'] && this.courses['all'].length > 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.coursesObserver && this.coursesObserver.off();
|
||||||
|
this.downloadButtonObserver && this.downloadButtonObserver.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"all": "All",
|
||||||
|
"future": "Future",
|
||||||
|
"inprogress": "In progress",
|
||||||
|
"favourites" : "Starred",
|
||||||
|
"hiddencourses": "Hidden",
|
||||||
|
"lastaccessed": "Last accessed",
|
||||||
|
"morecourses": "More courses",
|
||||||
|
"nocourses": "No courses",
|
||||||
|
"past": "Past",
|
||||||
|
"pluginname": "Course overview",
|
||||||
|
"title": "Title"
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreBlockDelegate } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockMyOverviewComponentsModule } from './components/components.module';
|
||||||
|
import { AddonBlockMyOverviewHandler } from './providers/block-handler';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
AddonBlockMyOverviewComponentsModule,
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonBlockMyOverviewHandler
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockMyOverviewModule {
|
||||||
|
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockMyOverviewHandler) {
|
||||||
|
blockDelegate.registerHandler(blockHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// (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 { Injectable, Injector } from '@angular/core';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { AddonBlockMyOverviewComponent } from '../components/myoverview/myoverview';
|
||||||
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonBlockMyOverviewHandler extends CoreBlockBaseHandler {
|
||||||
|
name = 'AddonBlockMyOverview';
|
||||||
|
blockName = 'myoverview';
|
||||||
|
|
||||||
|
constructor(private coursesProvider: CoreCoursesProvider, private sitesProvider: CoreSitesProvider) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return {boolean} Whether or not the handler is enabled on a site level.
|
||||||
|
*/
|
||||||
|
isEnabled(): boolean | Promise<boolean> {
|
||||||
|
return this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.6') ||
|
||||||
|
!this.coursesProvider.isMyCoursesDisabledInSite();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param {Injector} injector Injector.
|
||||||
|
* @param {any} block The block to render.
|
||||||
|
* @param {string} contextLevel The context where the block will be used.
|
||||||
|
* @param {number} instanceId The instance ID associated with the context level.
|
||||||
|
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'addon.block_myoverview.pluginname',
|
||||||
|
class: 'addon-block-myoverview',
|
||||||
|
component: AddonBlockMyOverviewComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AddonBlockRecentlyAccessedCoursesComponent } from './recentlyaccessedcourses/recentlyaccessedcourses';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCoursesComponentsModule } from '@core/courses/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonBlockRecentlyAccessedCoursesComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCoursesComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonBlockRecentlyAccessedCoursesComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonBlockRecentlyAccessedCoursesComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockRecentlyAccessedCoursesComponentsModule {}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.block_recentlyaccessedcourses.pluginname' | translate }}</h2>
|
||||||
|
<div *ngIf="downloadEnabled && courses && courses.length > 1" class="core-button-spinner" item-end>
|
||||||
|
<button *ngIf="prefetchCoursesData.icon && prefetchCoursesData.icon != 'spinner'" ion-button icon-only clear color="dark" (click)="prefetchCourses()">
|
||||||
|
<core-icon [name]="prefetchCoursesData.icon"></core-icon>
|
||||||
|
</button>
|
||||||
|
<ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge">{{prefetchCoursesData.badge}}</ion-badge>
|
||||||
|
<ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.icon == 'spinner'"></ion-spinner>
|
||||||
|
</div>
|
||||||
|
</ion-item-divider>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" [message]="'addon.block_recentlyaccessedcourses.nocourses' | translate"></core-empty-box>
|
||||||
|
<!-- List of courses. -->
|
||||||
|
<div class="safe-area-page">
|
||||||
|
<div class="core-horizontal-scroll">
|
||||||
|
<ng-container *ngFor="let course of courses">
|
||||||
|
<core-courses-course-progress [course]="course" class="core-recentlyaccessedcourses" [showDownload]="downloadEnabled"></core-courses-course-progress>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,160 @@
|
||||||
|
// (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, OnInit, OnDestroy, Injector, Input } from '@angular/core';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
||||||
|
import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
|
||||||
|
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render a recent courses block.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-block-recentlyaccessedcourses',
|
||||||
|
templateUrl: 'addon-block-recentlyaccessedcourses.html'
|
||||||
|
})
|
||||||
|
export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() downloadEnabled: boolean;
|
||||||
|
|
||||||
|
courses = [];
|
||||||
|
prefetchCoursesData = {
|
||||||
|
icon: '',
|
||||||
|
badge: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
protected prefetchIconsInitialized = false;
|
||||||
|
protected isDestroyed;
|
||||||
|
protected downloadButtonObserver;
|
||||||
|
protected coursesObserver;
|
||||||
|
protected courseIds = [];
|
||||||
|
protected fetchContentDefaultError = 'Error getting recent courses data.';
|
||||||
|
|
||||||
|
constructor(injector: Injector, private coursesProvider: CoreCoursesProvider,
|
||||||
|
private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider,
|
||||||
|
private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider,
|
||||||
|
private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider,
|
||||||
|
private sitesProvider: CoreSitesProvider) {
|
||||||
|
|
||||||
|
super(injector, 'AddonBlockRecentlyAccessedCoursesComponent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Refresh the enabled flags if enabled.
|
||||||
|
this.downloadButtonObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED,
|
||||||
|
(data) => {
|
||||||
|
const wasEnabled = this.downloadEnabled;
|
||||||
|
|
||||||
|
this.downloadEnabled = data.enabled;
|
||||||
|
|
||||||
|
if (!wasEnabled && this.downloadEnabled && this.loaded) {
|
||||||
|
// Download all courses is enabled now, initialize it.
|
||||||
|
this.initPrefetchCoursesIcons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
|
||||||
|
this.refreshContent();
|
||||||
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.coursesProvider.invalidateUserCourses().finally(() => {
|
||||||
|
// Invalidate course completion data.
|
||||||
|
return this.utils.allPromises(this.courseIds.map((courseId) => {
|
||||||
|
return this.courseCompletionProvider.invalidateCourseCompletion(courseId);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions());
|
||||||
|
if (this.courseIds.length > 0) {
|
||||||
|
promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds.join(',')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises).finally(() => {
|
||||||
|
this.prefetchIconsInitialized = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the courses for recent courses.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(): Promise<any> {
|
||||||
|
return this.coursesHelper.getUserCoursesWithOptions('lastaccess', 10).then((courses) => {
|
||||||
|
this.courses = courses;
|
||||||
|
|
||||||
|
this.initPrefetchCoursesIcons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the prefetch icon for selected courses.
|
||||||
|
*/
|
||||||
|
protected initPrefetchCoursesIcons(): void {
|
||||||
|
if (this.prefetchIconsInitialized || !this.downloadEnabled) {
|
||||||
|
// Already initialized.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prefetchIconsInitialized = true;
|
||||||
|
|
||||||
|
this.courseHelper.initPrefetchCoursesIcons(this.courses, this.prefetchCoursesData).then((prefetch) => {
|
||||||
|
this.prefetchCoursesData = prefetch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch all the shown courses.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
prefetchCourses(): Promise<any> {
|
||||||
|
const initialIcon = this.prefetchCoursesData.icon;
|
||||||
|
|
||||||
|
return this.courseHelper.prefetchCourses(this.courses, this.prefetchCoursesData).catch((error) => {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||||
|
this.prefetchCoursesData.icon = initialIcon;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.coursesObserver && this.coursesObserver.off();
|
||||||
|
this.downloadButtonObserver && this.downloadButtonObserver.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"nocourses": "No recent courses",
|
||||||
|
"pluginname": "Recently accessed courses"
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 { Injectable, Injector } from '@angular/core';
|
||||||
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockRecentlyAccessedCoursesComponent } from '../components/recentlyaccessedcourses/recentlyaccessedcourses';
|
||||||
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonBlockRecentlyAccessedCoursesHandler extends CoreBlockBaseHandler {
|
||||||
|
name = 'AddonBlockRecentlyAccessedCourses';
|
||||||
|
blockName = 'recentlyaccessedcourses';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param {Injector} injector Injector.
|
||||||
|
* @param {any} block The block to render.
|
||||||
|
* @param {string} contextLevel The context where the block will be used.
|
||||||
|
* @param {number} instanceId The instance ID associated with the context level.
|
||||||
|
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'addon.block_recentlyaccessedcourses.pluginname',
|
||||||
|
class: 'addon-block-recentlyaccessedcourses',
|
||||||
|
component: AddonBlockRecentlyAccessedCoursesComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreBlockDelegate } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockRecentlyAccessedCoursesComponentsModule } from './components/components.module';
|
||||||
|
import { AddonBlockRecentlyAccessedCoursesHandler } from './providers/block-handler';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CoreComponentsModule,
|
||||||
|
AddonBlockRecentlyAccessedCoursesComponentsModule,
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonBlockRecentlyAccessedCoursesHandler
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockRecentlyAccessedCoursesModule {
|
||||||
|
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockRecentlyAccessedCoursesHandler) {
|
||||||
|
blockDelegate.registerHandler(blockHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AddonBlockRecentlyAccessedItemsComponent } from './recentlyaccesseditems/recentlyaccesseditems';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonBlockRecentlyAccessedItemsComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCourseComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonBlockRecentlyAccessedItemsComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonBlockRecentlyAccessedItemsComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockRecentlyAccessedItemsComponentsModule {}
|
|
@ -0,0 +1,19 @@
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.block_recentlyaccesseditems.pluginname' | translate }}</h2>
|
||||||
|
</ion-item-divider>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<div class="core-horizontal-scroll" *ngIf="items && items.length > 0">
|
||||||
|
<div *ngFor="let item of items">
|
||||||
|
<ion-card>
|
||||||
|
<a ion-item text-wrap detail-none class="core-course-module-handler item-media" (click)="action($event, item)" [title]="item.name">
|
||||||
|
<img item-start [src]="item.iconUrl" alt="" role="presentation" *ngIf="item.iconUrl" class="core-module-icon">
|
||||||
|
<h2><core-format-text [text]="item.name"></core-format-text></h2>
|
||||||
|
<p><core-format-text [text]="item.coursename"></core-format-text></p>
|
||||||
|
</a>
|
||||||
|
</ion-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<core-empty-box *ngIf="items.length <= 0" image="assets/img/icons/activities.svg" [message]="'addon.block_recentlyaccesseditems.noitems' | translate"></core-empty-box>
|
||||||
|
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,3 @@
|
||||||
|
ion-app.app-root addon-block-recentlyaccesseditems .core-horizontal-scroll > div {
|
||||||
|
@include horizontal_scroll_item(80%, 250px, 300px);
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
// (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, OnInit, Injector, Optional } from '@angular/core';
|
||||||
|
import { NavController } from 'ionic-angular';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
|
||||||
|
import { AddonBlockRecentlyAccessedItemsProvider } from '../../providers/recentlyaccesseditems';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render a recently accessed items block.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-block-recentlyaccesseditems',
|
||||||
|
templateUrl: 'addon-block-recentlyaccesseditems.html'
|
||||||
|
})
|
||||||
|
export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseComponent implements OnInit {
|
||||||
|
items = [];
|
||||||
|
|
||||||
|
protected fetchContentDefaultError = 'Error getting recently accessed items data.';
|
||||||
|
|
||||||
|
constructor(injector: Injector, @Optional() private navCtrl: NavController,
|
||||||
|
private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider,
|
||||||
|
private recentItemsProvider: AddonBlockRecentlyAccessedItemsProvider,
|
||||||
|
private contentLinksHelper: CoreContentLinksHelperProvider) {
|
||||||
|
|
||||||
|
super(injector, 'AddonBlockRecentlyAccessedItemsComponent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
return this.recentItemsProvider.invalidateRecentItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the data to render the block.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(): Promise<any> {
|
||||||
|
return this.recentItemsProvider.getRecentItems().then((items) => {
|
||||||
|
this.items = items;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event clicked.
|
||||||
|
*
|
||||||
|
* @param {Event} e Click event.
|
||||||
|
* @param {any} item Activity item info.
|
||||||
|
*/
|
||||||
|
action(e: Event, item: any): void {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const url = this.textUtils.decodeHTMLEntities(item.viewurl);
|
||||||
|
const modal = this.domUtils.showModalLoading();
|
||||||
|
this.contentLinksHelper.handleLink(url, undefined, this.navCtrl).then((treated) => {
|
||||||
|
if (!treated) {
|
||||||
|
return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(url);
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
modal.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"noitems": "No recent items",
|
||||||
|
"pluginname": "Recently accessed items"
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 { Injectable, Injector } from '@angular/core';
|
||||||
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockRecentlyAccessedItemsComponent } from '../components/recentlyaccesseditems/recentlyaccesseditems';
|
||||||
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonBlockRecentlyAccessedItemsHandler extends CoreBlockBaseHandler {
|
||||||
|
name = 'AddonBlockRecentlyAccessedItems';
|
||||||
|
blockName = 'recentlyaccesseditems';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param {Injector} injector Injector.
|
||||||
|
* @param {any} block The block to render.
|
||||||
|
* @param {string} contextLevel The context where the block will be used.
|
||||||
|
* @param {number} instanceId The instance ID associated with the context level.
|
||||||
|
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'addon.block_recentlyaccesseditems.pluginname',
|
||||||
|
class: 'addon-block-recentlyaccesseditems',
|
||||||
|
component: AddonBlockRecentlyAccessedItemsComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features regarding recently accessed items.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonBlockRecentlyAccessedItemsProvider {
|
||||||
|
protected ROOT_CACHE_KEY = 'AddonBlockRecentlyAccessedItems:';
|
||||||
|
|
||||||
|
constructor(private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider,
|
||||||
|
private domUtils: CoreDomUtilsProvider) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for get last accessed items value WS call.
|
||||||
|
*
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getRecentItemsCacheKey(): string {
|
||||||
|
return this.ROOT_CACHE_KEY + ':recentitems';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last accessed items.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved when the info is retrieved.
|
||||||
|
*/
|
||||||
|
getRecentItems(siteId?: string): Promise<any[]> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getRecentItemsCacheKey()
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets).then((items) => {
|
||||||
|
return items.map((item) => {
|
||||||
|
const modicon = item.icon && this.domUtils.getHTMLElementAttribute(item.icon, 'src');
|
||||||
|
item.iconUrl = this.courseProvider.getModuleIconSrc(item.modname, modicon);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates get last accessed items WS call.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID to invalidate. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateRecentItems(siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKey(this.getRecentItemsCacheKey());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AddonBlockRecentlyAccessedItemsComponentsModule } from './components/components.module';
|
||||||
|
import { CoreBlockDelegate } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockRecentlyAccessedItemsHandler } from './providers/block-handler';
|
||||||
|
import { AddonBlockRecentlyAccessedItemsProvider } from './providers/recentlyaccesseditems';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
AddonBlockRecentlyAccessedItemsComponentsModule,
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonBlockRecentlyAccessedItemsHandler,
|
||||||
|
AddonBlockRecentlyAccessedItemsProvider
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockRecentlyAccessedItemsModule {
|
||||||
|
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockRecentlyAccessedItemsHandler) {
|
||||||
|
blockDelegate.registerHandler(blockHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AddonBlockSiteMainMenuComponent } from './sitemainmenu/sitemainmenu';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonBlockSiteMainMenuComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCourseComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonBlockSiteMainMenuComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonBlockSiteMainMenuComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockSiteMainMenuComponentsModule {}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.block_sitemainmenu.pluginname' | translate }}</h2>
|
||||||
|
</ion-item-divider>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<ion-item text-wrap *ngIf="block.summary">
|
||||||
|
<core-format-text [text]="block.summary"></core-format-text>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<core-course-module *ngFor="let module of block.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="true" [section]="block"></core-course-module>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,114 @@
|
||||||
|
// (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, OnInit, Injector } from '@angular/core';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreSiteHomeProvider } from '@core/sitehome/providers/sitehome';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||||
|
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render a site main menu block.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-block-sitemainmenu',
|
||||||
|
templateUrl: 'addon-block-sitemainmenu.html'
|
||||||
|
})
|
||||||
|
export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent implements OnInit {
|
||||||
|
block: any;
|
||||||
|
siteHomeId: number;
|
||||||
|
|
||||||
|
protected fetchContentDefaultError = 'Error getting main menu data.';
|
||||||
|
|
||||||
|
constructor(injector: Injector, protected sitesProvider: CoreSitesProvider, protected courseProvider: CoreCourseProvider,
|
||||||
|
protected courseHelper: CoreCourseHelperProvider, protected siteHomeProvider: CoreSiteHomeProvider,
|
||||||
|
protected prefetchDelegate: CoreCourseModulePrefetchDelegate) {
|
||||||
|
|
||||||
|
super(injector, 'AddonBlockSiteMainMenuComponent');
|
||||||
|
|
||||||
|
this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.courseProvider.invalidateSections(this.siteHomeId));
|
||||||
|
promises.push(this.siteHomeProvider.invalidateNewsForum(this.siteHomeId));
|
||||||
|
|
||||||
|
if (this.block && this.block.modules) {
|
||||||
|
// Invalidate modules prefetch data.
|
||||||
|
promises.push(this.prefetchDelegate.invalidateModules(this.block.modules, this.siteHomeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the data to render the block.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(): Promise<any> {
|
||||||
|
return this.courseProvider.getSections(this.siteHomeId, false, true).then((sections) => {
|
||||||
|
this.block = sections[0];
|
||||||
|
|
||||||
|
if (this.block) {
|
||||||
|
this.block.hasContent = this.courseHelper.sectionHasContent(this.block);
|
||||||
|
this.courseHelper.addHandlerDataForModules([this.block], this.siteHomeId);
|
||||||
|
|
||||||
|
// Check if Site Home displays announcements. If so, remove it from the main menu block.
|
||||||
|
const currentSite = this.sitesProvider.getCurrentSite(),
|
||||||
|
config = currentSite ? currentSite.getStoredConfig() || {} : {};
|
||||||
|
let hasNewsItem = false;
|
||||||
|
|
||||||
|
if (config.frontpageloggedin) {
|
||||||
|
const items = config.frontpageloggedin.split(',');
|
||||||
|
|
||||||
|
hasNewsItem = items.find((item) => { return item == '0'; });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNewsItem && this.block.modules) {
|
||||||
|
// Remove forum activity (news one only) from the main menu block to prevent duplicates.
|
||||||
|
return this.siteHomeProvider.getNewsForum(this.siteHomeId).then((forum) => {
|
||||||
|
// Search the module that belongs to site news.
|
||||||
|
for (let i = 0; i < this.block.modules.length; i++) {
|
||||||
|
const module = this.block.modules[i];
|
||||||
|
|
||||||
|
if (module.modname == 'forum' && module.instance == forum.id) {
|
||||||
|
this.block.modules.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"pluginname": "Main menu"
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 { Injectable, Injector } from '@angular/core';
|
||||||
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockSiteMainMenuComponent } from '../components/sitemainmenu/sitemainmenu';
|
||||||
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonBlockSiteMainMenuHandler extends CoreBlockBaseHandler {
|
||||||
|
name = 'AddonBlockSiteMainMenu';
|
||||||
|
blockName = 'site_main_menu';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param {Injector} injector Injector.
|
||||||
|
* @param {any} block The block to render.
|
||||||
|
* @param {string} contextLevel The context where the block will be used.
|
||||||
|
* @param {number} instanceId The instance ID associated with the context level.
|
||||||
|
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'addon.block_sitemainmenu.pluginname',
|
||||||
|
class: 'addon-block-sitemainmenu',
|
||||||
|
component: AddonBlockSiteMainMenuComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { AddonBlockSiteMainMenuComponentsModule } from './components/components.module';
|
||||||
|
import { CoreBlockDelegate } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockSiteMainMenuHandler } from './providers/block-handler';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
AddonBlockSiteMainMenuComponentsModule,
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonBlockSiteMainMenuHandler
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockSiteMainMenuModule {
|
||||||
|
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockSiteMainMenuHandler) {
|
||||||
|
blockDelegate.registerHandler(blockHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AddonBlockStarredCoursesComponent } from './starredcourses/starredcourses';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCoursesComponentsModule } from '@core/courses/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonBlockStarredCoursesComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCoursesComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonBlockStarredCoursesComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonBlockStarredCoursesComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockStarredCoursesComponentsModule {}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<ion-item-divider>
|
||||||
|
<h2>{{ 'addon.block_starredcourses.pluginname' | translate }}</h2>
|
||||||
|
<div *ngIf="downloadEnabled && courses && courses.length > 1" class="core-button-spinner" item-end>
|
||||||
|
<button *ngIf="prefetchCoursesData.icon && prefetchCoursesData.icon != 'spinner'" ion-button icon-only clear color="dark" (click)="prefetchCourses()">
|
||||||
|
<core-icon [name]="prefetchCoursesData.icon"></core-icon>
|
||||||
|
</button>
|
||||||
|
<ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge">{{prefetchCoursesData.badge}}</ion-badge>
|
||||||
|
<ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.icon == 'spinner'"></ion-spinner>
|
||||||
|
</div>
|
||||||
|
</ion-item-divider>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" [message]="'addon.block_starredcourses.nocourses' | translate"></core-empty-box>
|
||||||
|
<!-- List of courses. -->
|
||||||
|
<div class="safe-area-page">
|
||||||
|
<div class="core-horizontal-scroll">
|
||||||
|
<ng-container *ngFor="let course of courses">
|
||||||
|
<core-courses-course-progress [course]="course" class="core-block_starredcourses" [showDownload]="downloadEnabled"></core-courses-course-progress>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,160 @@
|
||||||
|
// (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, OnInit, OnDestroy, Injector, Input } from '@angular/core';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
|
import { CoreCoursesHelperProvider } from '@core/courses/providers/helper';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
||||||
|
import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
|
||||||
|
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render a starred courses block.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-block-starredcourses',
|
||||||
|
templateUrl: 'addon-block-starredcourses.html'
|
||||||
|
})
|
||||||
|
export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() downloadEnabled: boolean;
|
||||||
|
|
||||||
|
courses = [];
|
||||||
|
prefetchCoursesData = {
|
||||||
|
icon: '',
|
||||||
|
badge: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
protected prefetchIconsInitialized = false;
|
||||||
|
protected isDestroyed;
|
||||||
|
protected downloadButtonObserver;
|
||||||
|
protected coursesObserver;
|
||||||
|
protected courseIds = [];
|
||||||
|
protected fetchContentDefaultError = 'Error getting starred courses data.';
|
||||||
|
|
||||||
|
constructor(injector: Injector, private coursesProvider: CoreCoursesProvider,
|
||||||
|
private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider,
|
||||||
|
private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider,
|
||||||
|
private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider,
|
||||||
|
private sitesProvider: CoreSitesProvider) {
|
||||||
|
|
||||||
|
super(injector, 'AddonBlockStarredCoursesComponent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Refresh the enabled flags if enabled.
|
||||||
|
this.downloadButtonObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED,
|
||||||
|
(data) => {
|
||||||
|
const wasEnabled = this.downloadEnabled;
|
||||||
|
|
||||||
|
this.downloadEnabled = data.enabled;
|
||||||
|
|
||||||
|
if (!wasEnabled && this.downloadEnabled && this.loaded) {
|
||||||
|
// Download all courses is enabled now, initialize it.
|
||||||
|
this.initPrefetchCoursesIcons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
|
||||||
|
this.refreshContent();
|
||||||
|
}, this.sitesProvider.getCurrentSiteId());
|
||||||
|
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.coursesProvider.invalidateUserCourses().finally(() => {
|
||||||
|
// Invalidate course completion data.
|
||||||
|
return this.utils.allPromises(this.courseIds.map((courseId) => {
|
||||||
|
return this.courseCompletionProvider.invalidateCourseCompletion(courseId);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions());
|
||||||
|
if (this.courseIds.length > 0) {
|
||||||
|
promises.push(this.coursesProvider.invalidateCoursesByField('ids', this.courseIds.join(',')));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises).finally(() => {
|
||||||
|
this.prefetchIconsInitialized = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the courses.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(): Promise<any> {
|
||||||
|
return this.coursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite').then((courses) => {
|
||||||
|
this.courses = courses;
|
||||||
|
|
||||||
|
this.initPrefetchCoursesIcons();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the prefetch icon for selected courses.
|
||||||
|
*/
|
||||||
|
protected initPrefetchCoursesIcons(): void {
|
||||||
|
if (this.prefetchIconsInitialized || !this.downloadEnabled) {
|
||||||
|
// Already initialized.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prefetchIconsInitialized = true;
|
||||||
|
|
||||||
|
this.courseHelper.initPrefetchCoursesIcons(this.courses, this.prefetchCoursesData).then((prefetch) => {
|
||||||
|
this.prefetchCoursesData = prefetch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch all the shown courses.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
prefetchCourses(): Promise<any> {
|
||||||
|
const initialIcon = this.prefetchCoursesData.icon;
|
||||||
|
|
||||||
|
return this.courseHelper.prefetchCourses(this.courses, this.prefetchCoursesData).catch((error) => {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||||
|
this.prefetchCoursesData.icon = initialIcon;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.coursesObserver && this.coursesObserver.off();
|
||||||
|
this.downloadButtonObserver && this.downloadButtonObserver.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"nocourses": "No starred courses",
|
||||||
|
"pluginname": "Starred courses"
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 { Injectable, Injector } from '@angular/core';
|
||||||
|
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockStarredCoursesComponent } from '../components/starredcourses/starredcourses';
|
||||||
|
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonBlockStarredCoursesHandler extends CoreBlockBaseHandler {
|
||||||
|
name = 'AddonBlockStarredCourses';
|
||||||
|
blockName = 'starredcourses';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param {Injector} injector Injector.
|
||||||
|
* @param {any} block The block to render.
|
||||||
|
* @param {string} contextLevel The context where the block will be used.
|
||||||
|
* @param {number} instanceId The instance ID associated with the context level.
|
||||||
|
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
|
||||||
|
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'addon.starredcourses.pluginname',
|
||||||
|
class: 'addon-block-starredcourses',
|
||||||
|
component: AddonBlockStarredCoursesComponent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreBlockDelegate } from '@core/block/providers/delegate';
|
||||||
|
import { AddonBlockStarredCoursesComponentsModule } from './components/components.module';
|
||||||
|
import { AddonBlockStarredCoursesHandler } from './providers/block-handler';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
IonicModule,
|
||||||
|
CoreComponentsModule,
|
||||||
|
AddonBlockStarredCoursesComponentsModule,
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonBlockStarredCoursesHandler
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockStarredCoursesModule {
|
||||||
|
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockStarredCoursesHandler) {
|
||||||
|
blockDelegate.registerHandler(blockHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
|
import { CoreCoursesComponentsModule } from '@core/courses/components/components.module';
|
||||||
|
import { AddonBlockTimelineComponent } from './timeline/timeline';
|
||||||
|
import { AddonBlockTimelineEventsComponent } from './events/events';
|
||||||
|
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonBlockTimelineComponent,
|
||||||
|
AddonBlockTimelineEventsComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule,
|
||||||
|
CoreCoursesComponentsModule,
|
||||||
|
CoreCourseComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonBlockTimelineComponent,
|
||||||
|
AddonBlockTimelineEventsComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonBlockTimelineComponent,
|
||||||
|
AddonBlockTimelineEventsComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonBlockTimelineComponentsModule {}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<ion-item-group *ngFor="let dayEvents of filteredEvents">
|
||||||
|
<ion-item-divider [color]="dayEvents.color">
|
||||||
|
<h2>{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedayshort" }}</h2>
|
||||||
|
</ion-item-divider>
|
||||||
|
<ng-container *ngFor="let event of dayEvents.events">
|
||||||
|
<a ion-item text-wrap detail-none class="core-course-module-handler item-media" (click)="action($event, event.url)" [title]="event.name">
|
||||||
|
<img item-start [src]="event.iconUrl" core-external-content alt="" role="presentation" *ngIf="event.iconUrl" class="core-module-icon">
|
||||||
|
<h2><core-format-text [text]="event.name"></core-format-text></h2>
|
||||||
|
<p *ngIf="showCourse">
|
||||||
|
<core-format-text [text]="event.course.fullnamedisplay"></core-format-text>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button ion-button clear class="hidden-tablet" (click)="action($event, event.action.url)" [title]="event.action.name" [disabled]="!event.action.actionable" *ngIf="event.action">
|
||||||
|
{{event.action.name}}
|
||||||
|
<ion-badge item-end margin-start *ngIf="event.action.showitemcount">{{event.action.itemcount}}</ion-badge>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ion-badge color="light" item-end>{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</ion-badge>
|
||||||
|
|
||||||
|
<button ion-button clear item-end class="hidden-phone" (click)="action($event, event.action.url)" [title]="event.action.name" [disabled]="!event.action.actionable" *ngIf="event.action">
|
||||||
|
{{event.action.name}}
|
||||||
|
<ion-badge item-end margin-start *ngIf="event.action.showitemcount">{{event.action.itemcount}}</ion-badge>
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</ion-item-group>
|
||||||
|
|
||||||
|
<div padding text-center *ngIf="canLoadMore && !empty">
|
||||||
|
<!-- Button and spinner to show more attempts. -->
|
||||||
|
<button ion-button block (click)="loadMoreEvents()" color="light" *ngIf="!loadingMore">
|
||||||
|
{{ 'core.loadmore' | translate }}
|
||||||
|
</button>
|
||||||
|
<ion-spinner *ngIf="loadingMore"></ion-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<core-empty-box *ngIf="empty" image="assets/img/icons/activities.svg" [message]="'addon.block_timeline.noevents' | translate" [inline]="!showCourse"></core-empty-box>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue