mirror of
https://github.com/moodle/moodle.git
synced 2025-08-03 16:13:28 +02:00
MDL-63058 block_myoverview: starring and storing preferences
This commit is contained in:
parent
b81722e22f
commit
3cfff88516
39 changed files with 1403 additions and 101 deletions
|
@ -1 +1 @@
|
|||
define(["core/ajax"],function(a){var b=function(b){var c={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:b},d=a.call([c])[0];return d};return{getEnrolledCoursesByTimeline:b}});
|
||||
define(["core/ajax","core/notification"],function(a,b){var c=function(b){var c={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:b},d=a.call([c])[0];return d},d=function(b){var c={methodname:"core_course_set_favourite_courses",args:b},d=a.call([c])[0];return d},e=function(c){var d={methodname:"core_user_update_user_preferences",args:c};a.call([d])[0].fail(b.exception)};return{getEnrolledCoursesByTimeline:c,setFavouriteCourses:d,updateUserPreferences:e}});
|
2
blocks/myoverview/amd/build/view.min.js
vendored
2
blocks/myoverview/amd/build/view.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/notification","block_myoverview/repository","core/paged_content_factory","core/templates"],function(a,b,c,d,e){var f={COURSES_CARDS:"block_myoverview/view-cards",COURSES_LIST:"block_myoverview/view-list",COURSES_SUMMARY:"block_myoverview/view-summary",NOCOURSES:"block_myoverview/no-courses"},g=[12,24],h=[],i=function(a){var b={};return b.display=a.attr("data-display"),b.grouping=a.attr("data-grouping"),b.sort=a.attr("data-sort"),b},j={ignoreControlWhileLoading:!0,controlPlacementBottom:!0},k=function(a,b,d){return c.getEnrolledCoursesByTimeline({offset:d*b,limit:b,classification:a.grouping,sort:a.sort})},l=function(a,b,c){var d="";if(d="cards"==c.display?f.COURSES_CARDS:"list"==c.display?f.COURSES_LIST:f.COURSES_SUMMARY,b.courses.length)return e.render(d,{courses:b.courses});var g=a.attr("data-nocoursesimg");return e.render(f.NOCOURSES,{nocoursesimg:g})},m=function(c,f){c=a(c);var m=i(c),n=d.createWithLimit(g,function(a,d){var e=[];return a.forEach(function(a){var f=a.pageNumber-1,g=k(m,a.limit,f).then(function(b){return b.courses.length<a.limit&&d.allItemsLoaded(a.pageNumber),h=b,l(c,b,m)})["catch"](b.exception);e.push(g)}),e},j);n.then(function(a,b){return e.replaceNodeContents(f,a,b)})["catch"](b.exception)},n=function(a,c){var d=i(a);l(a,h,d).then(function(a,b){return e.replaceNodeContents(c,a,b)})["catch"](b.exception)};return{init:m,reset:n}});
|
||||
define(["jquery","block_myoverview/repository","core/paged_content_factory","core/custom_interaction_events","core/notification","core/templates"],function(a,b,c,d,e,f){var g={ACTION_ADD_FAVOURITE:'[data-action="add-favourite"]',ACTION_REMOVE_FAVOURITE:'[data-action="remove-favourite"]',FAVOURITE_ICON:'[data-region="favourite-icon"]',ICON_IS_FAVOURITE:'[data-region="is-favourite"]',ICON_NOT_FAVOURITE:'[data-region="not-favourite"]',PAGED_CONTENT_CONTAINER:'[data-region="page-container"]'},h={COURSES_CARDS:"block_myoverview/view-cards",COURSES_LIST:"block_myoverview/view-list",COURSES_SUMMARY:"block_myoverview/view-summary",NOCOURSES:"block_myoverview/no-courses"},i=[12,24,48],j=[],k=function(a){var b={};return b.display=a.attr("data-display"),b.grouping=a.attr("data-grouping"),b.sort=a.attr("data-sort"),b},l={ignoreControlWhileLoading:!0,controlPlacementBottom:!0},m=function(a,c,d){return b.getEnrolledCoursesByTimeline({offset:d*c,limit:c,classification:a.grouping,sort:a.sort})},n=function(a,b){return a.find(g.FAVOURITE_ICON+'[data-course-id="'+b+'"]')},o=function(a,b){return a.find('[data-region="paged-content-page"][data-page="'+b+'"]')},p=function(a){return a.attr("data-course-id")},q=function(a,b){var c=n(a,b),d=c.find(g.ICON_IS_FAVOURITE);d.addClass("hidden"),d.attr("aria-hidden",!0);var e=c.find(g.ICON_NOT_FAVOURITE);e.removeClass("hidden"),e.attr("aria-hidden",!1)},r=function(a,b){var c=n(a,b),d=c.find(g.ICON_IS_FAVOURITE);d.removeClass("hidden"),d.attr("aria-hidden",!1);var e=c.find(g.ICON_NOT_FAVOURITE);e.addClass("hidden"),e.attr("aria-hidden",!0)},s=function(a,b){return a.find('[data-action="add-favourite"][data-course-id="'+b+'"]')},t=function(a,b){return a.find('[data-action="remove-favourite"][data-course-id="'+b+'"]')},u=function(a,b){var c=t(a,b),d=s(a,b);w(b,!0).then(function(f){f?(c.removeClass("hidden"),d.addClass("hidden"),r(a,b)):e.alert("Starring course failed","Could not change favourite state")})["catch"](e.exception)},v=function(a,b){var c=t(a,b),d=s(a,b);w(b,!1).then(function(f){f?(c.addClass("hidden"),d.removeClass("hidden"),q(a,b)):e.alert("Starring course failed","Could not change favourite state")})["catch"](e.exception)},w=function(a,c){return b.setFavouriteCourses({courses:[{id:a,favourite:c}]}).then(function(b){return 0==b.warnings.length&&(j.forEach(function(b){b.courses.forEach(function(d,e){d.id==a&&(b.courses[e].isfavourite=c)})}),!0)})["catch"](e.exception)},x=function(a,b){var c=k(a),d="";if(d="cards"==c.display?h.COURSES_CARDS:"list"==c.display?h.COURSES_LIST:h.COURSES_SUMMARY,b.courses.length)return f.render(d,{courses:b.courses});var e=a.attr("data-nocoursesimg");return f.render(h.NOCOURSES,{nocoursesimg:e})},y=function(b,d){b=a(b),b.attr("data-init")||(z(b),b.attr("data-init",!0));var g=k(b),h=c.createWithLimit(i,function(a,c){var d=[];return a.forEach(function(a){var f=a.pageNumber,h=a.pageNumber-1,i=m(g,a.limit,h).then(function(d){return d.courses.length<a.limit&&c.allItemsLoaded(a.pageNumber),j[f]=d,x(b,d)})["catch"](e.exception);d.push(i)}),d},l);h.then(function(a,b){return f.replaceNodeContents(d,a,b)})["catch"](e.exception)},z=function(b){d.define(b,[d.events.activate]),b.on(d.events.activate,g.ACTION_ADD_FAVOURITE,function(c,d){var e=a(c.target).closest(g.ACTION_ADD_FAVOURITE),f=p(e);u(b,f),d.originalEvent.preventDefault()}),b.on(d.events.activate,g.ACTION_REMOVE_FAVOURITE,function(c,d){var e=a(c.target).closest(g.ACTION_REMOVE_FAVOURITE),f=p(e);v(b,f),d.originalEvent.preventDefault()}),b.on(d.events.activate,g.FAVOURITE_ICON,function(a,b){b.originalEvent.preventDefault()})},A=function(a,b){j.length>0?j.forEach(function(b,c){var d=o(a,c);x(a,b).then(function(a,b){return f.replaceNodeContents(d,a,b)})}):y(a,b)};return{init:y,reset:A}});
|
2
blocks/myoverview/amd/build/view_nav.min.js
vendored
2
blocks/myoverview/amd/build/view_nav.min.js
vendored
|
@ -1 +1 @@
|
|||
define(["jquery","core/custom_interaction_events","block_myoverview/view"],function(a,b,c){var d={FILTERS:'[data-region="filter"]',FILTER_OPTION:"[data-filter]",DISPLAY_OPTION:"[data-display-option]"},e=function(e,f,g){var h=e.find(d.FILTERS);b.define(h,[b.events.activate]),h.on(b.events.activate,d.FILTER_OPTION,function(b,d){var e=a(b.target);if(!e.hasClass("active")){var h="data-"+e.attr("data-filter");f.attr(h,e.attr("data-value")),c.init(f,g),d.originalEvent.preventDefault()}}),b.define(h,[b.events.activate]),h.on(b.events.activate,d.DISPLAY_OPTION,function(b,d){var e=a(b.target);e.hasClass("active")||(f.attr("data-display",e.attr("data-value")),c.reset(f,g),d.originalEvent.preventDefault())})},f=function(b,c,d){b=a(b),e(b,c,d)};return{init:f}});
|
||||
define(["jquery","core/custom_interaction_events","block_myoverview/repository","block_myoverview/view"],function(a,b,c,d){var e={FILTERS:'[data-region="filter"]',FILTER_OPTION:"[data-filter]",DISPLAY_OPTION:"[data-display-option]"},f=function(a,b){var d=null;d="display"==a?"block_myoverview_user_view_preference":"sort"==a?"block_myoverview_user_sort_preference":"block_myoverview_user_grouping_preference",c.updateUserPreferences({preferences:[{type:d,value:b}]})},g=function(c,g,h){var i=c.find(e.FILTERS);b.define(i,[b.events.activate]),i.on(b.events.activate,e.FILTER_OPTION,function(b,c){var e=a(b.target);if(!e.hasClass("active")){var i=e.attr("data-filter"),j="data-"+i,k=e.attr("data-value"),l=e.attr("data-pref");g.attr(j,k),f(i,l),d.init(g,h),c.originalEvent.preventDefault()}}),b.define(i,[b.events.activate]),i.on(b.events.activate,e.DISPLAY_OPTION,function(b,c){var e=a(b.target);if(!e.hasClass("active")){var i=e.attr("data-display-option"),j=e.attr("data-value"),k=e.attr("data-pref");f(i,k),g.attr("data-display",j),d.reset(g,h),c.originalEvent.preventDefault()}})},h=function(b,c,d){b=a(b),g(b,c,d)};return{init:h}});
|
|
@ -20,7 +20,7 @@
|
|||
* @copyright 2018 Bas Brands <base@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['core/ajax'], function(Ajax) {
|
||||
define(['core/ajax', 'core/notification'], function(Ajax, Notification) {
|
||||
|
||||
/**
|
||||
* Retrieve a list of enrolled courses.
|
||||
|
@ -47,7 +47,55 @@ define(['core/ajax'], function(Ajax) {
|
|||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the favourite state on a list of courses.
|
||||
*
|
||||
* Valid args are:
|
||||
* Array courses list of course id numbers.
|
||||
*
|
||||
* @param {Object} args Arguments send to the webservice.
|
||||
* @return {Promise} Resolve with warnings.
|
||||
*/
|
||||
var setFavouriteCourses = function(args) {
|
||||
|
||||
var request = {
|
||||
methodname: 'core_course_set_favourite_courses',
|
||||
args: args
|
||||
};
|
||||
|
||||
var promise = Ajax.call([request])[0];
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the user preferences.
|
||||
*
|
||||
* @param {Object} args Arguments send to the webservice.
|
||||
*
|
||||
* Sample args:
|
||||
* {
|
||||
* preferences: [
|
||||
* {
|
||||
* type: 'block_example_user_sort_preference'
|
||||
* value: 'title'
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
var updateUserPreferences = function(args) {
|
||||
var request = {
|
||||
methodname: 'core_user_update_user_preferences',
|
||||
args: args
|
||||
};
|
||||
|
||||
Ajax.call([request])[0]
|
||||
.fail(Notification.exception);
|
||||
};
|
||||
|
||||
return {
|
||||
getEnrolledCoursesByTimeline: getEnrolledCoursesByTimeline
|
||||
getEnrolledCoursesByTimeline: getEnrolledCoursesByTimeline,
|
||||
setFavouriteCourses: setFavouriteCourses,
|
||||
updateUserPreferences: updateUserPreferences
|
||||
};
|
||||
});
|
||||
|
|
|
@ -24,19 +24,31 @@
|
|||
define(
|
||||
[
|
||||
'jquery',
|
||||
'core/notification',
|
||||
'block_myoverview/repository',
|
||||
'core/paged_content_factory',
|
||||
'core/custom_interaction_events',
|
||||
'core/notification',
|
||||
'core/templates',
|
||||
],
|
||||
function(
|
||||
$,
|
||||
Notification,
|
||||
Repository,
|
||||
PagedContentFactory,
|
||||
CustomEvents,
|
||||
Notification,
|
||||
Templates
|
||||
) {
|
||||
|
||||
var SELECTORS = {
|
||||
ACTION_ADD_FAVOURITE: '[data-action="add-favourite"]',
|
||||
ACTION_REMOVE_FAVOURITE: '[data-action="remove-favourite"]',
|
||||
FAVOURITE_ICON: '[data-region="favourite-icon"]',
|
||||
ICON_IS_FAVOURITE: '[data-region="is-favourite"]',
|
||||
ICON_NOT_FAVOURITE: '[data-region="not-favourite"]',
|
||||
PAGED_CONTENT_CONTAINER: '[data-region="page-container"]'
|
||||
|
||||
};
|
||||
|
||||
var TEMPLATES = {
|
||||
COURSES_CARDS: 'block_myoverview/view-cards',
|
||||
COURSES_LIST: 'block_myoverview/view-list',
|
||||
|
@ -44,9 +56,9 @@ function(
|
|||
NOCOURSES: 'block_myoverview/no-courses'
|
||||
};
|
||||
|
||||
var NUMCOURSES_PERPAGE = [12, 24];
|
||||
var NUMCOURSES_PERPAGE = [12, 24, 48];
|
||||
|
||||
var currentCourseList = [];
|
||||
var loadedPages = [];
|
||||
|
||||
/**
|
||||
* Get filter values from DOM.
|
||||
|
@ -62,7 +74,7 @@ function(
|
|||
return filters;
|
||||
};
|
||||
|
||||
// We want the paged content controls below the paged content area
|
||||
// We want the paged content controls below the paged content area.
|
||||
// and the controls should be ignored while data is loading.
|
||||
var DEFAULT_PAGED_CONTENT_CONFIG = {
|
||||
ignoreControlWhileLoading: true,
|
||||
|
@ -75,9 +87,10 @@ function(
|
|||
* @param {object} filters The filters for this view.
|
||||
* @param {int} limit The number of courses to show.
|
||||
* @param {int} pageNumber The pagenumber to view.
|
||||
* @return {promise} Resolved with an array of courses.
|
||||
* @return {promise|Array} Resolved with an array of courses.
|
||||
*/
|
||||
var getMyCourses = function(filters, limit, pageNumber) {
|
||||
|
||||
return Repository.getEnrolledCoursesByTimeline({
|
||||
offset: pageNumber * limit,
|
||||
limit: limit,
|
||||
|
@ -86,15 +99,178 @@ function(
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the container element for the favourite icon.
|
||||
*
|
||||
* @param {Object} root The course overview container
|
||||
* @param {Number} courseId Course id number
|
||||
* @return {Object} The favourite icon container
|
||||
*/
|
||||
var getFavouriteIconContainer = function(root, courseId) {
|
||||
return root.find(SELECTORS.FAVOURITE_ICON + '[data-course-id="' + courseId + '"]');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the paged content container element.
|
||||
*
|
||||
* @param {Object} root The course overview container
|
||||
* @param {Number} index Rendered page index.
|
||||
* @return {Object} The rendered paged container.
|
||||
*/
|
||||
var getPagedContentContainer = function(root, index) {
|
||||
return root.find('[data-region="paged-content-page"][data-page="' + index + '"]');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the course id from a favourite element.
|
||||
*
|
||||
* @param {Object} root The favourite icon container element.
|
||||
* @return {Number} Course id.
|
||||
*/
|
||||
var getFavouriteCourseId = function(root) {
|
||||
return root.attr('data-course-id');
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the favourite icon.
|
||||
*
|
||||
* @param {Object} root The favourite icon container element.
|
||||
* @param {Number} courseId Course id number.
|
||||
*/
|
||||
var hideFavouriteIcon = function(root, courseId) {
|
||||
var iconContainer = getFavouriteIconContainer(root, courseId);
|
||||
var isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE);
|
||||
isFavouriteIcon.addClass('hidden');
|
||||
isFavouriteIcon.attr('aria-hidden', true);
|
||||
var notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE);
|
||||
notFavourteIcon.removeClass('hidden');
|
||||
notFavourteIcon.attr('aria-hidden', false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the favourite icon.
|
||||
*
|
||||
* @param {Object} root The course overview container.
|
||||
* @param {Number} courseId Course id number.
|
||||
*/
|
||||
var showFavouriteIcon = function(root, courseId) {
|
||||
var iconContainer = getFavouriteIconContainer(root, courseId);
|
||||
var isFavouriteIcon = iconContainer.find(SELECTORS.ICON_IS_FAVOURITE);
|
||||
isFavouriteIcon.removeClass('hidden');
|
||||
isFavouriteIcon.attr('aria-hidden', false);
|
||||
var notFavourteIcon = iconContainer.find(SELECTORS.ICON_NOT_FAVOURITE);
|
||||
notFavourteIcon.addClass('hidden');
|
||||
notFavourteIcon.attr('aria-hidden', true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the action menu item
|
||||
*
|
||||
* @param {Object} root root The course overview container
|
||||
* @param {Number} courseId Course id.
|
||||
* @return {Object} The add to favourite menu item.
|
||||
*/
|
||||
var getAddFavouriteMenuItem = function(root, courseId) {
|
||||
return root.find('[data-action="add-favourite"][data-course-id="' + courseId + '"]');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the action menu item
|
||||
*
|
||||
* @param {Object} root root The course overview container
|
||||
* @param {Number} courseId Course id.
|
||||
* @return {Object} The remove from favourites menu item.
|
||||
*/
|
||||
var getRemoveFavouriteMenuItem = function(root, courseId) {
|
||||
return root.find('[data-action="remove-favourite"][data-course-id="' + courseId + '"]');
|
||||
};
|
||||
|
||||
/**
|
||||
* Add course to favourites
|
||||
*
|
||||
* @param {Object} root The course overview container
|
||||
* @param {Number} courseId Course id number
|
||||
*/
|
||||
var addToFavourites = function(root, courseId) {
|
||||
var removeAction = getRemoveFavouriteMenuItem(root, courseId);
|
||||
var addAction = getAddFavouriteMenuItem(root, courseId);
|
||||
|
||||
setCourseFavouriteState(courseId, true).then(function(success) {
|
||||
if (success) {
|
||||
removeAction.removeClass('hidden');
|
||||
addAction.addClass('hidden');
|
||||
showFavouriteIcon(root, courseId);
|
||||
} else {
|
||||
Notification.alert('Starring course failed', 'Could not change favourite state');
|
||||
}
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove course from favourites
|
||||
*
|
||||
* @param {Object} root The course overview container
|
||||
* @param {Number} courseId Course id number
|
||||
*/
|
||||
var removeFromFavourites = function(root, courseId) {
|
||||
var removeAction = getRemoveFavouriteMenuItem(root, courseId);
|
||||
var addAction = getAddFavouriteMenuItem(root, courseId);
|
||||
|
||||
setCourseFavouriteState(courseId, false).then(function(success) {
|
||||
if (success) {
|
||||
removeAction.addClass('hidden');
|
||||
addAction.removeClass('hidden');
|
||||
hideFavouriteIcon(root, courseId);
|
||||
} else {
|
||||
Notification.alert('Starring course failed', 'Could not change favourite state');
|
||||
}
|
||||
return;
|
||||
}).catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the courses favourite status and push to repository
|
||||
*
|
||||
* @param {Number} courseId Course id to favourite.
|
||||
* @param {Bool} status new favourite status.
|
||||
* @return {Promise} Repository promise.
|
||||
*/
|
||||
var setCourseFavouriteState = function(courseId, status) {
|
||||
|
||||
return Repository.setFavouriteCourses({
|
||||
courses: [
|
||||
{
|
||||
'id': courseId,
|
||||
'favourite': status
|
||||
}
|
||||
]
|
||||
}).then(function(result) {
|
||||
if (result.warnings.length == 0) {
|
||||
loadedPages.forEach(function(courseList) {
|
||||
courseList.courses.forEach(function(course, index) {
|
||||
if (course.id == courseId) {
|
||||
courseList.courses[index].isfavourite = status;
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the dashboard courses.
|
||||
*
|
||||
* @param {object} root The root element for the courses view.
|
||||
* @param {array} coursesData containing array of returned courses.
|
||||
* @param {object} filters The filters for this view.
|
||||
* @return {promise} jQuery promise resolved after rendering is complete.
|
||||
*/
|
||||
var renderCourses = function(root, coursesData, filters) {
|
||||
var renderCourses = function(root, coursesData) {
|
||||
|
||||
var filters = getFilterValues(root);
|
||||
|
||||
var currentTemplate = '';
|
||||
if (filters.display == 'cards') {
|
||||
|
@ -127,6 +303,11 @@ function(
|
|||
|
||||
root = $(root);
|
||||
|
||||
if (!root.attr('data-init')) {
|
||||
registerEventListeners(root);
|
||||
root.attr('data-init', true);
|
||||
}
|
||||
|
||||
var filters = getFilterValues(root);
|
||||
|
||||
var pagedContentPromise = PagedContentFactory.createWithLimit(
|
||||
|
@ -135,6 +316,7 @@ function(
|
|||
var promises = [];
|
||||
|
||||
pagesData.forEach(function(pageData) {
|
||||
var currentPage = pageData.pageNumber;
|
||||
var pageNumber = pageData.pageNumber - 1;
|
||||
|
||||
var pagePromise = getMyCourses(
|
||||
|
@ -145,8 +327,8 @@ function(
|
|||
if (coursesData.courses.length < pageData.limit) {
|
||||
actions.allItemsLoaded(pageData.pageNumber);
|
||||
}
|
||||
currentCourseList = coursesData;
|
||||
return renderCourses(root, coursesData, filters);
|
||||
loadedPages[currentPage] = coursesData;
|
||||
return renderCourses(root, coursesData);
|
||||
})
|
||||
.catch(Notification.exception);
|
||||
|
||||
|
@ -163,6 +345,35 @@ function(
|
|||
}).catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen to, and handle events for the myoverview block.
|
||||
*
|
||||
* @param {Object} root The myoverview block container element.
|
||||
*/
|
||||
var registerEventListeners = function(root) {
|
||||
CustomEvents.define(root, [
|
||||
CustomEvents.events.activate
|
||||
]);
|
||||
|
||||
root.on(CustomEvents.events.activate, SELECTORS.ACTION_ADD_FAVOURITE, function(e, data) {
|
||||
var favourite = $(e.target).closest(SELECTORS.ACTION_ADD_FAVOURITE);
|
||||
var courseId = getFavouriteCourseId(favourite);
|
||||
addToFavourites(root, courseId);
|
||||
data.originalEvent.preventDefault();
|
||||
});
|
||||
|
||||
root.on(CustomEvents.events.activate, SELECTORS.ACTION_REMOVE_FAVOURITE, function(e, data) {
|
||||
var favourite = $(e.target).closest(SELECTORS.ACTION_REMOVE_FAVOURITE);
|
||||
var courseId = getFavouriteCourseId(favourite);
|
||||
removeFromFavourites(root, courseId);
|
||||
data.originalEvent.preventDefault();
|
||||
});
|
||||
|
||||
root.on(CustomEvents.events.activate, SELECTORS.FAVOURITE_ICON, function(e, data) {
|
||||
data.originalEvent.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the courses views to their original
|
||||
* state on first page load.
|
||||
|
@ -170,15 +381,22 @@ function(
|
|||
* This is called when configuration has changed for the event lists
|
||||
* to cause them to reload their data.
|
||||
*
|
||||
* @param {object} root The root element for the timeline view.
|
||||
* @param {object} content The content element for the timeline view.
|
||||
* @param {Object} root The root element for the timeline view.
|
||||
* @param {Object} content The content element for the timeline view.
|
||||
*/
|
||||
var reset = function(root, content) {
|
||||
var filters = getFilterValues(root);
|
||||
renderCourses(root, currentCourseList, filters)
|
||||
.then(function(html, js) {
|
||||
return Templates.replaceNodeContents(content, html, js);
|
||||
}).catch(Notification.exception);
|
||||
|
||||
if (loadedPages.length > 0) {
|
||||
loadedPages.forEach(function(courseList, index) {
|
||||
var pagedContentPage = getPagedContentContainer(root, index);
|
||||
renderCourses(root, courseList).then(function(html, js) {
|
||||
return Templates.replaceNodeContents(pagedContentPage, html, js);
|
||||
}).catch(Notification.exception);
|
||||
});
|
||||
} else {
|
||||
init(root, content);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -25,11 +25,13 @@ define(
|
|||
[
|
||||
'jquery',
|
||||
'core/custom_interaction_events',
|
||||
'block_myoverview/repository',
|
||||
'block_myoverview/view'
|
||||
],
|
||||
function(
|
||||
$,
|
||||
CustomEvents,
|
||||
Repository,
|
||||
View
|
||||
) {
|
||||
|
||||
|
@ -39,6 +41,32 @@ function(
|
|||
DISPLAY_OPTION: '[data-display-option]'
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the user preference for the block.
|
||||
*
|
||||
* @param {String} filter The type of filter: display/sort/grouping.
|
||||
* @param {String} value The current preferred value.
|
||||
*/
|
||||
var updatePreferences = function(filter, value) {
|
||||
var type = null;
|
||||
if (filter == 'display') {
|
||||
type = 'block_myoverview_user_view_preference';
|
||||
} else if (filter == 'sort') {
|
||||
type = 'block_myoverview_user_sort_preference';
|
||||
} else {
|
||||
type = 'block_myoverview_user_grouping_preference';
|
||||
}
|
||||
|
||||
Repository.updateUserPreferences({
|
||||
preferences: [
|
||||
{
|
||||
type: type,
|
||||
value: value
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Event listener for the Display filter (cards, list).
|
||||
*
|
||||
|
@ -62,8 +90,14 @@ function(
|
|||
return;
|
||||
}
|
||||
|
||||
var attributename = 'data-' + option.attr('data-filter');
|
||||
viewRoot.attr(attributename, option.attr('data-value'));
|
||||
var filter = option.attr('data-filter');
|
||||
var attributename = 'data-' + filter;
|
||||
var value = option.attr('data-value');
|
||||
var pref = option.attr('data-pref');
|
||||
|
||||
viewRoot.attr(attributename, value);
|
||||
|
||||
updatePreferences(filter, pref);
|
||||
|
||||
// Reset the views.
|
||||
View.init(viewRoot, viewContent);
|
||||
|
@ -83,7 +117,12 @@ function(
|
|||
return;
|
||||
}
|
||||
|
||||
viewRoot.attr('data-display', option.attr('data-value'));
|
||||
var filter = option.attr('data-display-option');
|
||||
var value = option.attr('data-value');
|
||||
var pref = option.attr('data-pref');
|
||||
|
||||
updatePreferences(filter, pref);
|
||||
viewRoot.attr('data-display', value);
|
||||
View.reset(viewRoot, viewContent);
|
||||
data.originalEvent.preventDefault();
|
||||
}
|
||||
|
|
|
@ -49,8 +49,11 @@ class block_myoverview extends block_base {
|
|||
if (isset($this->content)) {
|
||||
return $this->content;
|
||||
}
|
||||
$group = get_user_preferences('block_myoverview_user_grouping_preference');
|
||||
$sort = get_user_preferences('block_myoverview_user_sort_preference');
|
||||
$view = get_user_preferences('block_myoverview_user_view_preference');
|
||||
|
||||
$renderable = new \block_myoverview\output\main();
|
||||
$renderable = new \block_myoverview\output\main($group, $sort, $view);
|
||||
$renderer = $this->page->get_renderer('block_myoverview');
|
||||
|
||||
$this->content = new stdClass();
|
||||
|
|
|
@ -28,7 +28,7 @@ use renderable;
|
|||
use renderer_base;
|
||||
use templatable;
|
||||
|
||||
require_once($CFG->libdir . '/completionlib.php');
|
||||
require_once($CFG->dirroot . '/blocks/myoverview/lib.php');
|
||||
|
||||
/**
|
||||
* Class containing data for my overview block.
|
||||
|
@ -37,18 +37,75 @@ require_once($CFG->libdir . '/completionlib.php');
|
|||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class main implements renderable, templatable {
|
||||
|
||||
/**
|
||||
* Store the grouping preference
|
||||
*
|
||||
* @var string String matching the grouping constants defined in myoverview/lib.php
|
||||
*/
|
||||
private $grouping;
|
||||
|
||||
/**
|
||||
* Store the sort preference
|
||||
*
|
||||
* @var string String matching the sort constants defined in myoverview/lib.php
|
||||
*/
|
||||
private $sort;
|
||||
|
||||
/**
|
||||
* Store the view preference
|
||||
*
|
||||
* @var string String matching the view/display constants defined in myoverview/lib.php
|
||||
*/
|
||||
private $view;
|
||||
|
||||
/**
|
||||
* main constructor.
|
||||
* Initialize the user preferences
|
||||
*
|
||||
* @param string $grouping Grouping user preference
|
||||
* @param string $sort Sort user preference
|
||||
* @param string $view Display user preference
|
||||
*/
|
||||
public function __construct($grouping, $sort, $view) {
|
||||
$this->grouping = $grouping ? $grouping : BLOCK_MYOVERVIEW_GROUPING_ALL;
|
||||
$this->sort = $sort ? $sort : BLOCK_MYOVERVIEW_SORTING_TITLE;
|
||||
$this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user preferences as an array to figure out what has been selected
|
||||
*
|
||||
* @return array $preferences Array with the pref as key and value set to true
|
||||
*/
|
||||
public function get_preferences_as_booleans() {
|
||||
$preferences = [];
|
||||
$preferences[$this->view] = true;
|
||||
$preferences[$this->sort] = true;
|
||||
$preferences[$this->grouping] = true;
|
||||
|
||||
return $preferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template.
|
||||
*
|
||||
* @param \renderer_base $output
|
||||
* @return stdClass
|
||||
* @return array Context variables for the template
|
||||
*/
|
||||
public function export_for_template(renderer_base $output) {
|
||||
|
||||
$nocoursesurl = $output->image_url('courses', 'block_myoverview')->out();
|
||||
|
||||
return (object) [
|
||||
'nocoursesimg' => $nocoursesurl
|
||||
$defaultvariables = [
|
||||
'nocoursesimg' => $nocoursesurl,
|
||||
'grouping' => $this->grouping,
|
||||
'sort' => $this->sort == BLOCK_MYOVERVIEW_SORTING_TITLE ? 'fullname' : 'ul.timeaccess desc',
|
||||
'view' => $this->view
|
||||
];
|
||||
|
||||
$preferences = $this->get_preferences_as_booleans();
|
||||
return array_merge($defaultvariables, $preferences);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,9 @@
|
|||
|
||||
namespace block_myoverview\privacy;
|
||||
|
||||
use core_privacy\local\request\user_preference_provider;
|
||||
use core_privacy\local\metadata\collection;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
|
@ -32,15 +35,48 @@ defined('MOODLE_INTERNAL') || die();
|
|||
* @copyright 2018 Zig Tan <zig@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class provider implements \core_privacy\local\metadata\null_provider {
|
||||
class provider implements \core_privacy\local\metadata\provider, user_preference_provider {
|
||||
|
||||
/**
|
||||
* Get the language string identifier with the component's language
|
||||
* file to explain why this plugin stores no data.
|
||||
* Returns meta-data information about the myoverview block.
|
||||
*
|
||||
* @return string
|
||||
* @param \core_privacy\local\metadata\collection $collection A collection of meta-data.
|
||||
* @return \core_privacy\local\metadata\collection Return the collection of meta-data.
|
||||
*/
|
||||
public static function get_reason() : string {
|
||||
return 'privacy:metadata';
|
||||
public static function get_metadata(collection $collection) : collection {
|
||||
$collection->add_user_preference('block_myoverview_user_sort_preference', 'privacy:metadata:overviewsortpreference');
|
||||
$collection->add_user_preference('block_myoverview_user_view_preference', 'privacy:metadata:overviewviewpreference');
|
||||
$collection->add_user_preference('block_myoverview_user_grouping_preference',
|
||||
'privacy:metadata:overviewgroupingpreference');
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Export all user preferences for the myoverview block
|
||||
*
|
||||
* @param int $userid The userid of the user whose data is to be exported.
|
||||
*/
|
||||
public static function export_user_preferences(int $userid) {
|
||||
$preference = get_user_preferences('block_myoverview_user_sort_preference', null, $userid);
|
||||
if (isset($preference)) {
|
||||
\core_privacy\local\request\writer::export_user_preference('block_myoverview',
|
||||
'block_myoverview_user_sort_preference', get_string($preference, 'block_myoverview'),
|
||||
get_string('privacy:metadata:overviewsortpreference', 'block_myoverview'));
|
||||
}
|
||||
|
||||
$preference = get_user_preferences('block_myoverview_user_view_preference', null, $userid);
|
||||
if (isset($preference)) {
|
||||
\core_privacy\local\request\writer::export_user_preference('block_myoverview',
|
||||
'block_myoverview_user_view_preference',
|
||||
get_string($preference, 'block_myoverview'),
|
||||
get_string('privacy:metadata:overviewviewpreference', 'block_myoverview'));
|
||||
}
|
||||
|
||||
$preference = get_user_preferences('block_myoverview_user_grouping_preference', null, $userid);
|
||||
if (isset($preference)) {
|
||||
\core_privacy\local\request\writer::export_user_preference('block_myoverview',
|
||||
'block_myoverview_user_grouping_preference',
|
||||
get_string($preference, 'block_myoverview'),
|
||||
get_string('privacy:metadata:overviewgroupingpreference', 'block_myoverview'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,14 +23,19 @@
|
|||
*/
|
||||
|
||||
$string['all'] = 'All';
|
||||
$string['addtofavourites'] = 'Star this course';
|
||||
$string['aria:addtofavourites'] = 'Star for';
|
||||
$string['aria:allcourses'] = 'All courses';
|
||||
$string['aria:card'] = 'Switch to card view';
|
||||
$string['aria:controls'] = 'Course overview controls';
|
||||
$string['aria:courseactions'] = 'Actions for current course';
|
||||
$string['aria:courseimage'] = 'Course image:';
|
||||
$string['aria:coursename'] = 'Course name:';
|
||||
$string['aria:coursesummary'] = 'Course summary text:';
|
||||
$string['aria:courseprogress'] = 'Course progress:';
|
||||
$string['aria:displaydropdown'] = 'Display dropdown';
|
||||
$string['aria:favourite'] = 'Course is starred';
|
||||
$string['aria:favourites'] = 'Show starred courses';
|
||||
$string['aria:future'] = 'Show future courses';
|
||||
$string['aria:groupingdropdown'] = 'Grouping dropdown';
|
||||
$string['aria:inprogress'] = 'Show in courses in progress';
|
||||
|
@ -38,32 +43,44 @@ $string['aria:lastaccessed'] = 'Sort courses by last accessed date';
|
|||
$string['aria:list'] = 'Switch to list view';
|
||||
$string['aria:title'] = 'Sort courses by title';
|
||||
$string['aria:past'] = 'Show past courses';
|
||||
$string['aria:removefromfavourites'] = 'Remove star for';
|
||||
$string['aria:summary'] = 'Switch to summary view';
|
||||
$string['aria:sortingdropdown'] = 'Sorting dropdown';
|
||||
$string['card'] = 'Card';
|
||||
$string['courseprogress'] = 'Course progress:';
|
||||
$string['complete'] = 'Complete';
|
||||
$string['favorite'] = 'Favorite';
|
||||
$string['favourite'] = 'Starred course';
|
||||
$string['favourites'] = 'Starred';
|
||||
$string['future'] = 'Future';
|
||||
$string['future:aria'] = 'View future courses';
|
||||
$string['hidden'] = 'Hidden';
|
||||
$string['inprogress'] = 'In progress';
|
||||
$string['inprogress:aria'] = 'View in progress courses';
|
||||
$string['notfavourite'] = 'Not starred';
|
||||
$string['lastaccessed'] = 'Last accessed';
|
||||
$string['lastaccessed:aria'] = 'Sort course by lastaccessed';
|
||||
$string['list'] = 'List';
|
||||
$string['morecourses'] = 'More courses';
|
||||
$string['myoverview:addinstance'] = 'Add a new course overview block';
|
||||
$string['myoverview:myaddinstance'] = 'Add a new course overview block to Dashboard';
|
||||
$string['nocoursesfuture'] = 'No future courses';
|
||||
$string['nocoursesinprogress'] = 'No in progress courses';
|
||||
$string['nocourses'] = 'No courses';
|
||||
$string['nocoursespast'] = 'No past courses';
|
||||
$string['past'] = 'Past';
|
||||
$string['pluginname'] = 'Course overview';
|
||||
$string['privacy:metadata'] = 'The myoverview block does not store any personal data.';
|
||||
$string['privacy:metadata:overviewsortpreference'] = 'The myoverview block sort preference.';
|
||||
$string['privacy:metadata:overviewviewpreference'] = 'The myoverview block view preference.';
|
||||
$string['privacy:metadata:overviewgroupingpreference'] = 'The myoverview block grouping preference.';
|
||||
$string['removefromfavourites'] = 'Unstar this course';
|
||||
$string['summary'] = 'Summary';
|
||||
$string['title'] = 'Title';
|
||||
$string['viewcoursename'] = 'View course {$a}';
|
||||
$string['viewcourse'] = 'View course';
|
||||
|
||||
// Deprecated since Moodle 3.6.
|
||||
$string['defaulttab'] = 'Default tab';
|
||||
$string['defaulttab_desc'] = 'The tab that will be displayed when a user first views their course overview. When returning to their course overview, the user\'s active tab is remembered.';
|
||||
$string['morecourses'] = 'More courses';
|
||||
$string['nocoursesinprogress'] = 'No in progress courses';
|
||||
$string['nocoursesfuture'] = 'No future courses';
|
||||
$string['nocoursespast'] = 'No past courses';
|
||||
$string['noevents'] = 'No upcoming activities due';
|
||||
$string['next30days'] = 'Next 30 days';
|
||||
$string['next7days'] = 'Next 7 days';
|
||||
$string['recentlyoverdue'] = 'Recently overdue';
|
||||
$string['sortbycourses'] = 'Sort by courses';
|
||||
$string['sortbydates'] = 'Sort by dates';
|
||||
$string['timeline'] = 'Timeline';
|
||||
$string['viewcoursename'] = 'View course {$a}';
|
||||
$string['privacy:metadata:overviewlasttab'] = 'This stores the last tab selected by the user on the overview block.';
|
||||
|
||||
|
|
15
blocks/myoverview/lang/en/deprecated.txt
Normal file
15
blocks/myoverview/lang/en/deprecated.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
defaulttab,block_myoverview
|
||||
defaulttab_desc,block_myoverview
|
||||
morecourses,block_myoverview
|
||||
nocoursesinprogress,block_myoverview
|
||||
nocoursesfuture,block_myoverview
|
||||
nocoursespast,block_myoverview
|
||||
noevents,block_myoverview
|
||||
next30days,block_myoverview
|
||||
next7days,block_myoverview
|
||||
recentlyoverdue,block_myoverview
|
||||
sortbycourses,block_myoverview
|
||||
sortbydates,block_myoverview
|
||||
timeline,block_myoverview
|
||||
viewcoursename,block_myoverview
|
||||
privacy:metadata:overviewlasttab,block_myoverview
|
88
blocks/myoverview/lib.php
Normal file
88
blocks/myoverview/lib.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Library functions for overview.
|
||||
*
|
||||
* @package block_myoverview
|
||||
* @copyright 2018 Peter Dias
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Constants for the user preferences grouping options
|
||||
*/
|
||||
define('BLOCK_MYOVERVIEW_GROUPING_ALL', 'all');
|
||||
define('BLOCK_MYOVERVIEW_GROUPING_INPROGRESS', 'inprogress');
|
||||
define('BLOCK_MYOVERVIEW_GROUPING_FUTURE', 'future');
|
||||
define('BLOCK_MYOVERVIEW_GROUPING_PAST', 'past');
|
||||
define('BLOCK_MYOVERVIEW_GROUPING_FAVOURITES', 'favourites');
|
||||
|
||||
/**
|
||||
* Constants for the user preferences sorting options
|
||||
* timeline
|
||||
*/
|
||||
define('BLOCK_MYOVERVIEW_SORTING_TITLE', 'title');
|
||||
define('BLOCK_MYOVERVIEW_SORTING_LASTACCESSED', 'lastaccessed');
|
||||
|
||||
/**
|
||||
* Constants for the user preferences view options
|
||||
*/
|
||||
define('BLOCK_MYOVERVIEW_VIEW_CARD', 'cards');
|
||||
define('BLOCK_MYOVERVIEW_VIEW_LIST', 'list');
|
||||
define('BLOCK_MYOVERVIEW_VIEW_SUMMARY', 'summary');
|
||||
|
||||
/**
|
||||
* Get the current user preferences that are available
|
||||
*
|
||||
* @return mixed Array representing current options along with defaults
|
||||
*/
|
||||
function block_myoverview_user_preferences() {
|
||||
$preferences['block_myoverview_user_grouping_preference'] = array(
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
'default' => BLOCK_MYOVERVIEW_GROUPING_ALL,
|
||||
'type' => PARAM_ALPHA,
|
||||
'choices' => array(
|
||||
BLOCK_MYOVERVIEW_GROUPING_ALL,
|
||||
BLOCK_MYOVERVIEW_GROUPING_INPROGRESS,
|
||||
BLOCK_MYOVERVIEW_GROUPING_FUTURE,
|
||||
BLOCK_MYOVERVIEW_GROUPING_PAST,
|
||||
BLOCK_MYOVERVIEW_GROUPING_FAVOURITES
|
||||
)
|
||||
);
|
||||
$preferences['block_myoverview_user_sort_preference'] = array(
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
'default' => BLOCK_MYOVERVIEW_SORTING_TITLE,
|
||||
'type' => PARAM_ALPHA,
|
||||
'choices' => array(
|
||||
BLOCK_MYOVERVIEW_SORTING_TITLE,
|
||||
BLOCK_MYOVERVIEW_SORTING_LASTACCESSED
|
||||
)
|
||||
);
|
||||
$preferences['block_myoverview_user_view_preference'] = array(
|
||||
'null' => NULL_NOT_ALLOWED,
|
||||
'default' => BLOCK_MYOVERVIEW_VIEW_CARD,
|
||||
'type' => PARAM_ALPHA,
|
||||
'choices' => array(
|
||||
BLOCK_MYOVERVIEW_VIEW_CARD,
|
||||
BLOCK_MYOVERVIEW_VIEW_LIST,
|
||||
BLOCK_MYOVERVIEW_VIEW_SUMMARY
|
||||
)
|
||||
);
|
||||
return $preferences;
|
||||
}
|
61
blocks/myoverview/templates/course-action-menu.mustache
Normal file
61
blocks/myoverview/templates/course-action-menu.mustache
Normal file
|
@ -0,0 +1,61 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_myoverview/course-action-menu
|
||||
|
||||
This template renders action menu for each course.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"isfavourite": true
|
||||
}
|
||||
}}
|
||||
<div class="ml-auto dropdown">
|
||||
<button class="btn btn-link btn-icon icon-size-3 coursemenubtn"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
{{#pix}} i/moremenu, core {{/pix}}
|
||||
<span class="sr-only">
|
||||
{{#str}} aria:courseactions, block_myoverview {{/str}} {{{fullname}}}
|
||||
</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item {{#isfavourite}}hidden{{/isfavourite}}" href="#"
|
||||
data-action="add-favourite"
|
||||
data-course-id="{{id}}"
|
||||
aria-controls="favorite-icon-{{ id }}"
|
||||
>
|
||||
{{#pix}} i/star, core, {{#str}} favourite, block_myoverview {{/str}} {{/pix}}
|
||||
{{#str}} addtofavourites, block_myoverview {{/str}}
|
||||
<div class="sr-only">
|
||||
{{#str}} aria:addtofavourites, block_myoverview {{/str}} {{{fullname}}}
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item {{^isfavourite}}hidden{{/isfavourite}}" href="#"
|
||||
data-action="remove-favourite"
|
||||
data-course-id="{{id}}"
|
||||
aria-controls="favorite-icon-{{ id }}"
|
||||
>
|
||||
{{#str}} removefromfavourites, block_myoverview {{/str}}
|
||||
<div class="sr-only">
|
||||
{{#str}} aria:removefromfavourites, block_myoverview {{/str}} {{{fullname}}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
|
@ -21,14 +21,17 @@
|
|||
|
||||
Example context (json):
|
||||
{
|
||||
"nocoursesimg": "https://moodlesite/theme/image.php/boost/block_myoverview/1535727318/courses"
|
||||
"nocoursesimg": "https://moodlesite/theme/image.php/boost/block_myoverview/1535727318/courses",
|
||||
"grouping": "all",
|
||||
"sort": "fullname",
|
||||
"view": "card"
|
||||
}
|
||||
}}
|
||||
<div id="courses-view-{{uniqid}}"
|
||||
data-region="courses-view"
|
||||
data-display="cards"
|
||||
data-grouping="all"
|
||||
data-sort="fullname"
|
||||
data-display="{{view}}"
|
||||
data-grouping="{{grouping}}"
|
||||
data-sort="{{sort}}"
|
||||
data-nocoursesimg="{{nocoursesimg}}">
|
||||
<div data-region="course-view-content">
|
||||
<div data-region="courses-loading-placeholder">
|
||||
|
@ -40,4 +43,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
49
blocks/myoverview/templates/favourite-icon.mustache
Normal file
49
blocks/myoverview/templates/favourite-icon.mustache
Normal file
|
@ -0,0 +1,49 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_myoverview/favourite-icon
|
||||
|
||||
This template renders the favourite icon for a course.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"id": 3,
|
||||
"isfavourite": true
|
||||
}
|
||||
}}
|
||||
<div id="favorite-icon-{{ id }}"
|
||||
data-region="favourite-icon"
|
||||
data-course-id="{{id}}"
|
||||
>
|
||||
<div class="btn btn-link favouritebtn p-2">
|
||||
<span
|
||||
{{^isfavourite}}class="hidden"{{/isfavourite}}
|
||||
data-region="is-favourite"
|
||||
aria-hidden="{{^isfavourite}}true{{/isfavourite}}{{#isfavourite}}false{{/isfavourite}}"
|
||||
>
|
||||
{{#pix}} i/star, core, {{#str}} favourite, block_myoverview {{/str}} {{/pix}}
|
||||
<span class="sr-only">{{#str}} aria:favourite, block_myoverview {{/str}}</span>
|
||||
</span>
|
||||
<span
|
||||
{{#isfavourite}}class="hidden"{{/isfavourite}}
|
||||
data-region="not-favourite"
|
||||
aria-hidden="{{^isfavourite}}false{{/isfavourite}}{{#isfavourite}}true{{/isfavourite}}"
|
||||
>
|
||||
{{#pix}} i/empty, core, {{#str}} notfavourite, block_myoverview {{/str}} {{/pix}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
|
@ -20,28 +20,37 @@
|
|||
This template renders display dropdown.
|
||||
|
||||
Example context (json):
|
||||
{}
|
||||
{
|
||||
"cards": true,
|
||||
"list": false,
|
||||
"summary": false
|
||||
}
|
||||
}}
|
||||
<div class="dropdown">
|
||||
<button id="displaydropdown" type="button" class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
aria-label="{{#str}} aria:displaydropdown, block_myoverview {{/str}}">
|
||||
<span class="d-sm-inline-block">{{#pix}} a/view_icon_active {{/pix}}</span>
|
||||
{{#pix}} a/view_icon_active {{/pix}}
|
||||
<span class="d-sm-inline-block">
|
||||
{{#cards}}{{#str}} card, block_myoverview {{/str}}{{/cards}}
|
||||
{{#list}}{{#str}} list, block_myoverview {{/str}}{{/list}}
|
||||
{{#summary}}{{#str}} summary, block_myoverview {{/str}}{{/summary}}
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" data-show-active-item aria-labelledby="displaydropdown">
|
||||
<li>
|
||||
<a class="dropdown-item active" href="#" data-display-option="display" data-value="cards" aria-label="{{#str}} aria:card, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#cards}}active{{/cards}}" href="#" data-display-option="display" data-value="cards" data-pref="cards" aria-label="{{#str}} aria:card, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} card, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-display-option="display" data-value="list" aria-label="{{#str}} aria:list, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#list}}active{{/list}}" href="#" data-display-option="display" data-value="list" data-pref="list" aria-label="{{#str}} aria:list, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} list, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-display-option="display" data-value="summary" aria-label="{{#str}} aria:summary, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#summary}}active{{/summary}}" href="#" data-display-option="display" data-value="summary" data-pref="summary" aria-label="{{#str}} aria:summary, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} summary, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,32 +20,49 @@
|
|||
This template renders grouping dropdown.
|
||||
|
||||
Example context (json):
|
||||
{}
|
||||
{
|
||||
"all": true,
|
||||
"inprogress": false,
|
||||
"future": false,
|
||||
"past": false
|
||||
}
|
||||
}}
|
||||
<div class="dropdown">
|
||||
<button id="groupingdropdown" type="button" class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{{#str}} aria:groupingdropdown, block_myoverview {{/str}}">
|
||||
<span class="d-sm-inline-block">{{#str}} all, block_myoverview {{/str}}</span>
|
||||
{{#pix}} i/filter {{/pix}}
|
||||
<span class="d-sm-inline-block" data-active-item-text>
|
||||
{{#all}}{{#str}} all, block_myoverview {{/str}}{{/all}}
|
||||
{{#inprogress}}{{#str}} inprogress, block_myoverview {{/str}}{{/inprogress}}
|
||||
{{#future}}{{#str}} future, block_myoverview {{/str}}{{/future}}
|
||||
{{#past}}{{#str}} past, block_myoverview {{/str}}{{/past}}
|
||||
{{#favourites}}{{#str}} favourites, block_myoverview {{/str}}{{/favourites}}
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" data-show-active-item aria-labelledby="groupingdropdown">
|
||||
<ul class="dropdown-menu" data-show-active-item data-active-item-text aria-labelledby="groupingdropdown">
|
||||
<li>
|
||||
<a class="dropdown-item active" href="#" data-filter="grouping" data-value="all" aria-label="{{#str}} aria:allcourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#all}}active{{/all}}" href="#" data-filter="grouping" data-value="all" data-pref="all" aria-label="{{#str}} aria:allcourses, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} all, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="inprogress" aria-label="{{#str}} aria:inprogress, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#inprogress}}active{{/inprogress}}" href="#" data-filter="grouping" data-value="inprogress" data-pref="inprogress" aria-label="{{#str}} aria:inprogress, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} inprogress, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="future" aria-label="{{#str}} aria:future, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#future}}active{{/future}}" href="#" data-filter="grouping" data-value="future" data-pref="future" aria-label="{{#str}} aria:future, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} future, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-filter="grouping" data-value="past" aria-label="{{#str}} aria:past, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#past}}active{{/past}}" href="#" data-filter="grouping" data-value="past" data-pref="past" aria-label="{{#str}} aria:past, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} past, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item {{#favourites}}active{{/favourites}}" href="#" data-filter="grouping" data-value="favourites" data-pref="favourites" aria-label="{{#str}} aria:favourites, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} favourites, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -20,22 +20,28 @@
|
|||
This template renders sorting dropdown.
|
||||
|
||||
Example context (json):
|
||||
{}
|
||||
{
|
||||
"title": false,
|
||||
"lastaccessed": true
|
||||
}
|
||||
}}
|
||||
|
||||
<div class="dropdown mr-1 ml-auto">
|
||||
{{#str}} sortby, core {{/str}}
|
||||
<button id="sortingdropdown" type="button" class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{{#str}} aria:sortingdropdown, block_myoverview {{/str}}">
|
||||
<span class="d-sm-inline-block">{{#str}} title, block_myoverview {{/str}}</span>
|
||||
<span data-active-item-text class="d-sm-inline-block d-none">
|
||||
{{#title}}{{#str}} title, block_myoverview {{/str}}{{/title}}
|
||||
{{#lastaccessed}}{{#str}} lastaccessed, block_myoverview {{/str}}{{/lastaccessed}}
|
||||
</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" data-show-active-item aria-labelledby="sortingdropdown">
|
||||
<li>
|
||||
<a class="dropdown-item active" href="#" data-filter="sort" data-value="fullname" aria-label="{{#str}} aria:title, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#title}}active{{/title}}" href="#" data-filter="sort" data-pref="title" data-value="fullname" aria-label="{{#str}} aria:title, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} title, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-filter="sort" data-value="ul.timeaccess desc" aria-label="{{#str}} aria:lastaccessed, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
<a class="dropdown-item {{#lastaccessed}}active{{/lastaccessed}}" href="#" data-filter="sort" data-pref="lastaccessed" data-value="ul.timeaccess desc" aria-label="{{#str}} aria:lastaccessed, block_myoverview {{/str}}" aria-controls="courses-view-{{uniqid}}">
|
||||
{{#str}} lastaccessed, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -37,19 +37,21 @@
|
|||
<div class="row card-deck" role="list">
|
||||
{{#courses}}
|
||||
<div class="card course-card" role="listitem">
|
||||
<a href="{{viewurl}}" tabindex="-1" role="presentation">
|
||||
<a href="{{viewurl}}" tabindex="-1">
|
||||
<div class="card-img-top myoverviewimg" style='background-image: url("{{{courseimage}}}");'>
|
||||
<span class="sr-only">{{#str}}aria:courseimage, block_myoverview{{/str}}</span>
|
||||
{{> block_myoverview/favourite-icon }}
|
||||
</div>
|
||||
</a>
|
||||
<div class="card-body course-info-container" id="course-info-container-{{id}}">
|
||||
<div class="d-flex">
|
||||
<div class="card-title">
|
||||
<div class="card-body pr-1 course-info-container" id="course-info-container-{{id}}">
|
||||
<div class="d-flex align-items-start">
|
||||
<div class="card-title mr-2">
|
||||
<a href="{{viewurl}}">
|
||||
<span class="sr-only">{{#str}}aria:coursename, block_myoverview{{/str}}</span>
|
||||
{{#shortentext}}140, {{{fullname}}} {{/shortentext}}
|
||||
{{#shortentext}}40, {{{fullname}}} {{/shortentext}}
|
||||
</a>
|
||||
</div>
|
||||
{{> block_myoverview/course-action-menu }}
|
||||
</div>
|
||||
</div>
|
||||
{{#hasprogress}}
|
||||
|
|
|
@ -34,21 +34,27 @@
|
|||
}
|
||||
}}
|
||||
|
||||
<ul class="list-group" role="list">
|
||||
<ul class="list-group">
|
||||
{{#courses}}
|
||||
<li class="list-group-item course-listitem" role="listitem">
|
||||
<li class="list-group-item course-listitem">
|
||||
<div class="row-fluid">
|
||||
<div class="{{#hasprogress}}col-6 span6{{/hasprogress}}{{^hasprogress}}col-12 span12{{/hasprogress}}">
|
||||
<a href="{{viewurl}}">
|
||||
<span class="sr-only">{{#str}}aria:coursename, block_myoverview{{/str}}</span>
|
||||
{{{fullname}}}
|
||||
</a>
|
||||
<div class="{{#hasprogress}}col-6 span6{{/hasprogress}}{{^hasprogress}}col-11 span11{{/hasprogress}} p-l-0">
|
||||
<div class="d-flex align-items-center">
|
||||
{{> block_myoverview/favourite-icon }}
|
||||
<a href="{{viewurl}}">
|
||||
<span class="sr-only">{{#str}}aria:coursename, block_myoverview{{/str}}</span>
|
||||
{{{fullname}}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{#hasprogress}}
|
||||
<div class="col-6 span6">
|
||||
<div class="col-5 span5">
|
||||
{{> block_myoverview/progress-bar}}
|
||||
</div>
|
||||
{{/hasprogress}}
|
||||
<div class="col-1 span1 p-0 d-flex">
|
||||
{{> block_myoverview/course-action-menu }}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{/courses}}
|
||||
|
|
|
@ -38,14 +38,21 @@
|
|||
{{#courses}}
|
||||
<div class="course-summaryitem m-b-1 p-2" role="listitem">
|
||||
<div class="row-fluid d-flex">
|
||||
<a href="{{viewurl}}" class="col-sm-4 col-xl-3 span4" tabindex="-1" role="presentation">
|
||||
<a href="{{viewurl}}" class="col-sm-4 col-xl-3 span4 position-relative" tabindex="-1">
|
||||
<div class="position-absolute">
|
||||
{{> block_myoverview/favourite-icon }}
|
||||
</div>
|
||||
<img src="{{{courseimage}}}" class="summaryimage img-fluid" alt="{{#str}}aria:courseimage, block_myoverview{{/str}}">
|
||||
|
||||
</a>
|
||||
<div class="col-sm-8 col-xl-9 span8 align-self-stretch d-flex flex-column">
|
||||
<a href="{{viewurl}}">
|
||||
<span class="sr-only">{{#str}}aria:coursename, block_myoverview{{/str}}</span>
|
||||
<h4>{{{fullname}}}</h4>
|
||||
</a>
|
||||
<div class="d-flex">
|
||||
<a href="{{viewurl}}">
|
||||
<span class="sr-only">{{#str}}aria:coursename, block_myoverview{{/str}}</span>
|
||||
<h4>{{{fullname}}}</h4>
|
||||
</a>
|
||||
{{> block_myoverview/course-action-menu }}
|
||||
</div>
|
||||
<div class="summary">
|
||||
<span class="sr-only">{{#str}}aria:coursesummary, block_myoverview{{/str}}</span>
|
||||
{{{summary}}}
|
||||
|
|
|
@ -65,4 +65,96 @@ Feature: The my overview block allows users to easily access their courses
|
|||
Then I should see "Course 3" in the "Course overview" "block"
|
||||
Then I should see "Course 4" in the "Course overview" "block"
|
||||
Then I should see "Course 5" in the "Course overview" "block"
|
||||
And I log out
|
||||
And I log out
|
||||
|
||||
Scenario: View inprogress courses - test persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
And I click on "In progress" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "In progress" in the "Course overview" "block"
|
||||
Then I should see "Course 2" in the "Course overview" "block"
|
||||
Then I should see "Course 3" in the "Course overview" "block"
|
||||
Then I should see "Course 4" in the "Course overview" "block"
|
||||
And I should not see "Course 1" in the "Course overview" "block"
|
||||
And I should not see "Course 5" in the "Course overview" "block"
|
||||
And I log out
|
||||
|
||||
Scenario: View all courses - w/ persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "All" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "All" in the "Course overview" "block"
|
||||
Then I should see "Course 1" in the "Course overview" "block"
|
||||
Then I should see "Course 2" in the "Course overview" "block"
|
||||
Then I should see "Course 3" in the "Course overview" "block"
|
||||
Then I should see "Course 4" in the "Course overview" "block"
|
||||
Then I should see "Course 5" in the "Course overview" "block"
|
||||
And I log out
|
||||
|
||||
Scenario: View past courses - w/ persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "Past" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "Past" in the "Course overview" "block"
|
||||
Then I should see "Course 1" in the "Course overview" "block"
|
||||
And I should not see "Course 2" in the "Course overview" "block"
|
||||
And I should not see "Course 3" in the "Course overview" "block"
|
||||
And I should not see "Course 4" in the "Course overview" "block"
|
||||
And I should not see "Course 5" in the "Course overview" "block"
|
||||
And I log out
|
||||
|
||||
Scenario: View future courses - w/ persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "All" "button" in the "Course overview" "block"
|
||||
When I click on "Future" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "Future" in the "Course overview" "block"
|
||||
Then I should see "Course 5" in the "Course overview" "block"
|
||||
And I should not see "Course 1" in the "Course overview" "block"
|
||||
And I should not see "Course 2" in the "Course overview" "block"
|
||||
And I should not see "Course 3" in the "Course overview" "block"
|
||||
And I should not see "Course 4" in the "Course overview" "block"
|
||||
And I log out
|
||||
|
||||
Scenario: List display persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "Display dropdown" "button" in the "Course overview" "block"
|
||||
And I click on "List" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "List" in the "Course overview" "block"
|
||||
And "[data-display='list']" "css_element" in the "Course overview" "block" should be visible
|
||||
|
||||
Scenario: Cards display persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "Display dropdown" "button" in the "Course overview" "block"
|
||||
And I click on "Card" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "Card" in the "Course overview" "block"
|
||||
And "[data-display='cards']" "css_element" in the "Course overview" "block" should be visible
|
||||
|
||||
Scenario: Summary display persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "Display dropdown" "button" in the "Course overview" "block"
|
||||
And I click on "Summary" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "Summary" in the "Course overview" "block"
|
||||
And "[data-display='summary']" "css_element" in the "Course overview" "block" should be visible
|
||||
|
||||
Scenario: Title sort persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "sortingdropdown" "button" in the "Course overview" "block"
|
||||
And I click on "Title" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "Title" in the "Course overview" "block"
|
||||
And "[data-sort='fullname']" "css_element" in the "Course overview" "block" should be visible
|
||||
|
||||
Scenario: Last accessed sort persistence
|
||||
Given I log in as "student1"
|
||||
And I click on "sortingdropdown" "button" in the "Course overview" "block"
|
||||
And I click on "Last accessed" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then I should see "Last accessed" in the "Course overview" "block"
|
||||
And "[data-sort='ul.timeaccess desc']" "css_element" in the "Course overview" "block" should be visible
|
|
@ -0,0 +1,51 @@
|
|||
@block @block_myoverview @javascript
|
||||
Feature: The my overview block allows users to favourite their courses
|
||||
In order to enable the my overview block in a course
|
||||
As a student
|
||||
I can add the my overview block to my dashboard
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email | idnumber |
|
||||
| student1 | Student | X | student1@example.com | S1 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
| Course 2 | C2 | 0 |
|
||||
| Course 3 | C3 | 0 |
|
||||
| Course 4 | C4 | 0 |
|
||||
| Course 5 | C5 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| student1 | C2 | student |
|
||||
| student1 | C3 | student |
|
||||
| student1 | C4 | student |
|
||||
| student1 | C5 | student |
|
||||
|
||||
Scenario: Favourite a course on a course card
|
||||
Given I log in as "student1"
|
||||
When I click on ".coursemenubtn" "css_element" in the "//div[@class='card course-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card course-card' and contains(.,'Course 2')]" "xpath_element"
|
||||
And I reload the page
|
||||
Then "//div[@class='card course-card' and contains(.,'Course 2')]//span[@data-region='is-favourite' and @aria-hidden='false']" "xpath_element" should exist
|
||||
And "//div[@class='card course-card' and contains(.,'Course 2')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should not exist
|
||||
And "//div[@class='card course-card' and contains(.,'Course 2')]//span[@data-region='not-favourite' and @aria-hidden='true']" "xpath_element" should exist
|
||||
And "//div[@class='card course-card' and contains(.,'Course 1')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist
|
||||
And "//div[@class='card course-card' and contains(.,'Course 3')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist
|
||||
And I log out
|
||||
|
||||
Scenario: Star a course and switch display
|
||||
Given I log in as "student1"
|
||||
When I click on ".coursemenubtn" "css_element" in the "//div[@class='card course-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I click on "Star this course" "link" in the "//div[@class='card course-card' and contains(.,'Course 5')]" "xpath_element"
|
||||
And I reload the page
|
||||
And I click on "Display dropdown" "button" in the "Course overview" "block"
|
||||
And I click on "List" "link" in the "Course overview" "block"
|
||||
And I reload the page
|
||||
Then "//li[@class='list-group-item course-listitem' and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='false']" "xpath_element" should exist
|
||||
And "//li[@class='list-group-item course-listitem' and contains(.,'Course 5')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should not exist
|
||||
And "//li[@class='list-group-item course-listitem' and contains(.,'Course 5')]//span[@data-region='not-favourite' and @aria-hidden='true']" "xpath_element" should exist
|
||||
And "//li[@class='list-group-item course-listitem' and contains(.,'Course 1')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist
|
||||
And "//li[@class='list-group-item course-listitem' and contains(.,'Course 3')]//span[@data-region='is-favourite' and @aria-hidden='true']" "xpath_element" should exist
|
||||
And I log out
|
81
blocks/myoverview/tests/privacy_test.php
Normal file
81
blocks/myoverview/tests/privacy_test.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?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/>.
|
||||
/**
|
||||
* Unit tests for the block_myoverview implementation of the privacy API.
|
||||
*
|
||||
* @package block_myoverview
|
||||
* @category test
|
||||
* @copyright 2018 Peter Dias <peter@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use \core_privacy\local\request\writer;
|
||||
use \block_myoverview\privacy\provider;
|
||||
/**
|
||||
* Unit tests for the block_myoverview implementation of the privacy API.
|
||||
*
|
||||
* @copyright 2018 Peter Dias <peter@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class block_myoverview_privacy_testcase extends \core_privacy\tests\provider_testcase {
|
||||
/**
|
||||
* Ensure that export_user_preferences returns no data if the user has not visited the myoverview block.
|
||||
*/
|
||||
public function test_export_user_preferences_no_pref() {
|
||||
$this->resetAfterTest();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
provider::export_user_preferences($user->id);
|
||||
$writer = writer::with_context(\context_system::instance());
|
||||
$this->assertFalse($writer->has_any_data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the export_user_preferences given different inputs
|
||||
*
|
||||
* @param string $type The name of the user preference to get/set
|
||||
* @param string $value The value you are storing
|
||||
*
|
||||
* @dataProvider user_preference_provider
|
||||
*/
|
||||
public function test_export_user_preferences($type, $value) {
|
||||
$this->resetAfterTest();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
set_user_preference($type, $value, $user);
|
||||
provider::export_user_preferences($user->id);
|
||||
$writer = writer::with_context(\context_system::instance());
|
||||
$blockpreferences = $writer->get_user_preferences('block_myoverview');
|
||||
$this->assertEquals(get_string($value, 'block_myoverview'), $blockpreferences->{$type}->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of valid user preferences for the myoverview block.
|
||||
*
|
||||
* @return array Array of valid user preferences.
|
||||
*/
|
||||
public function user_preference_provider() {
|
||||
return array(
|
||||
array('block_myoverview_user_sort_preference', 'lastaccessed'),
|
||||
array('block_myoverview_user_sort_preference', 'title'),
|
||||
array('block_myoverview_user_grouping_preference', 'all'),
|
||||
array('block_myoverview_user_grouping_preference', 'inprogress'),
|
||||
array('block_myoverview_user_grouping_preference', 'future'),
|
||||
array('block_myoverview_user_grouping_preference', 'past'),
|
||||
array('block_myoverview_user_view_preference', 'cards'),
|
||||
array('block_myoverview_user_view_preference', 'list'),
|
||||
array('block_myoverview_user_view_preference', 'summary')
|
||||
);
|
||||
}
|
||||
}
|
|
@ -35,12 +35,26 @@ use moodle_url;
|
|||
*/
|
||||
class course_summary_exporter extends \core\external\exporter {
|
||||
|
||||
/**
|
||||
* Constructor - saves the persistent object, and the related objects.
|
||||
*
|
||||
* @param mixed $data - Either an stdClass or an array of values.
|
||||
* @param array $related - An optional list of pre-loaded objects related to this object.
|
||||
*/
|
||||
public function __construct($data, $related = array()) {
|
||||
if (!array_key_exists('isfavourite', $related)) {
|
||||
$related['isfavourite'] = false;
|
||||
}
|
||||
parent::__construct($data, $related);
|
||||
}
|
||||
|
||||
protected static function define_related() {
|
||||
// We cache the context so it does not need to be retrieved from the course.
|
||||
return array('context' => '\\context');
|
||||
return array('context' => '\\context', 'isfavourite' => 'bool?');
|
||||
}
|
||||
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
|
||||
$courseimage = self::get_course_image($this->data);
|
||||
if (!$courseimage) {
|
||||
$courseimage = self::get_course_pattern($this->data);
|
||||
|
@ -56,7 +70,8 @@ class course_summary_exporter extends \core\external\exporter {
|
|||
'viewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->id)))->out(false),
|
||||
'courseimage' => $courseimage,
|
||||
'progress' => $progress,
|
||||
'hasprogress' => $hasprogress
|
||||
'hasprogress' => $hasprogress,
|
||||
'isfavourite' => $this->related['isfavourite']
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -119,6 +134,9 @@ class course_summary_exporter extends \core\external\exporter {
|
|||
),
|
||||
'hasprogress' => array(
|
||||
'type' => PARAM_BOOL
|
||||
),
|
||||
'isfavourite' => array(
|
||||
'type' => PARAM_BOOL
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3663,6 +3663,8 @@ class core_course_external extends external_api {
|
|||
break;
|
||||
case COURSE_TIMELINE_FUTURE:
|
||||
break;
|
||||
case COURSE_FAVOURITES:
|
||||
break;
|
||||
default:
|
||||
throw new invalid_parameter_exception('Invalid classification');
|
||||
}
|
||||
|
@ -3672,17 +3674,42 @@ class core_course_external extends external_api {
|
|||
$requiredproperties = course_summary_exporter::define_properties();
|
||||
$fields = join(',', array_keys($requiredproperties));
|
||||
$courses = course_get_enrolled_courses_for_logged_in_user(0, $offset, $sort, $fields);
|
||||
list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification(
|
||||
$courses,
|
||||
$classification,
|
||||
$limit
|
||||
);
|
||||
|
||||
$favouritecourseids = [];
|
||||
$ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
|
||||
$favourites = $ufservice->find_favourites_by_type('core_course', 'courses');
|
||||
|
||||
if ($favourites) {
|
||||
$favouritecourseids = array_map(
|
||||
function($favourite) {
|
||||
return $favourite->itemid;
|
||||
}, $favourites);
|
||||
}
|
||||
|
||||
if ($classification == COURSE_FAVOURITES) {
|
||||
list($filteredcourses, $processedcount) = course_filter_courses_by_favourites(
|
||||
$courses,
|
||||
$favouritecourseids,
|
||||
$limit
|
||||
);
|
||||
|
||||
} else {
|
||||
list($filteredcourses, $processedcount) = course_filter_courses_by_timeline_classification(
|
||||
$courses,
|
||||
$classification,
|
||||
$limit
|
||||
);
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$formattedcourses = array_map(function($course) use ($renderer) {
|
||||
$formattedcourses = array_map(function($course) use ($renderer, $favouritecourseids) {
|
||||
context_helper::preload_from_record($course);
|
||||
$context = context_course::instance($course->id);
|
||||
$exporter = new course_summary_exporter($course, ['context' => $context]);
|
||||
$isfavourite = false;
|
||||
if (in_array($course->id, $favouritecourseids)) {
|
||||
$isfavourite = true;
|
||||
}
|
||||
$exporter = new course_summary_exporter($course, ['context' => $context, 'isfavourite' => $isfavourite]);
|
||||
return $exporter->export($renderer);
|
||||
}, $filteredcourses);
|
||||
|
||||
|
@ -3705,4 +3732,114 @@ class core_course_external extends external_api {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function set_favourite_courses_parameters() {
|
||||
return new external_function_parameters(
|
||||
array(
|
||||
'courses' => new external_multiple_structure(
|
||||
new external_single_structure(
|
||||
array(
|
||||
'id' => new external_value(PARAM_INT, 'course ID'),
|
||||
'favourite' => new external_value(PARAM_BOOL, 'favourite status')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the course favourite status for an array of courses.
|
||||
*
|
||||
* @param array $courses List with course id's and favourite status.
|
||||
* @return array Array with an array of favourite courses.
|
||||
*/
|
||||
public static function set_favourite_courses(
|
||||
array $courses
|
||||
) {
|
||||
global $USER;
|
||||
|
||||
$params = self::validate_parameters(self::set_favourite_courses_parameters(),
|
||||
array(
|
||||
'courses' => $courses
|
||||
)
|
||||
);
|
||||
|
||||
$warnings = [];
|
||||
|
||||
$ufservice = \core_favourites\service_factory::get_service_for_user_context(\context_user::instance($USER->id));
|
||||
|
||||
foreach ($params['courses'] as $course) {
|
||||
|
||||
$warning = [];
|
||||
|
||||
$favouriteexists = $ufservice->favourite_exists('core_course', 'courses', $course['id'], \context_system::instance());
|
||||
|
||||
if ($course['favourite']) {
|
||||
if (!$favouriteexists) {
|
||||
try {
|
||||
$ufservice->create_favourite('core_course', 'courses', $course['id'], \context_system::instance());
|
||||
} catch (Exception $e) {
|
||||
$warning['courseid'] = $course['id'];
|
||||
if ($e instanceof moodle_exception) {
|
||||
$warning['warningcode'] = $e->errorcode;
|
||||
} else {
|
||||
$warning['warningcode'] = $e->getCode();
|
||||
}
|
||||
$warning['message'] = $e->getMessage();
|
||||
$warnings[] = $warning;
|
||||
$warnings[] = $warning;
|
||||
}
|
||||
} else {
|
||||
$warning['courseid'] = $course['id'];
|
||||
$warning['warningcode'] = 'coursealreadyfavourited';
|
||||
$warning['message'] = 'Course already favourited';
|
||||
$warnings[] = $warning;
|
||||
}
|
||||
} else {
|
||||
if ($favouriteexists) {
|
||||
try {
|
||||
$ufservice->delete_favourite('core_course', 'courses', $course['id'], \context_system::instance());
|
||||
} catch (Exception $e) {
|
||||
$warning['courseid'] = $course['id'];
|
||||
if ($e instanceof moodle_exception) {
|
||||
$warning['warningcode'] = $e->errorcode;
|
||||
} else {
|
||||
$warning['warningcode'] = $e->getCode();
|
||||
}
|
||||
$warning['message'] = $e->getMessage();
|
||||
$warnings[] = $warning;
|
||||
$warnings[] = $warning;
|
||||
}
|
||||
} else {
|
||||
$warning['courseid'] = $course['id'];
|
||||
$warning['warningcode'] = 'cannotdeletefavourite';
|
||||
$warning['message'] = 'Could not delete favourite status for course';
|
||||
$warnings[] = $warning;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'warnings' => $warnings
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value
|
||||
*
|
||||
* @return external_description
|
||||
*/
|
||||
public static function set_favourite_courses_returns() {
|
||||
return new external_single_structure(
|
||||
array(
|
||||
'warnings' => new external_warnings()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ define('COURSE_TIMELINE_ALL', 'all');
|
|||
define('COURSE_TIMELINE_PAST', 'past');
|
||||
define('COURSE_TIMELINE_INPROGRESS', 'inprogress');
|
||||
define('COURSE_TIMELINE_FUTURE', 'future');
|
||||
define('COURSE_FAVOURITES', 'favourites');
|
||||
define('COURSE_DB_QUERY_LIMIT', 1000);
|
||||
|
||||
function make_log_url($module, $url) {
|
||||
|
@ -4271,6 +4272,51 @@ function course_filter_courses_by_timeline_classification(
|
|||
return [$filteredcourses, $numberofcoursesprocessed];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the given $courses for any that match the given $classification up to the specified
|
||||
* $limit.
|
||||
*
|
||||
* This function will return the subset of courses that are favourites as well as the
|
||||
* number of courses it had to process to build that subset.
|
||||
*
|
||||
* It is recommended that for larger sets of courses this function is given a Generator that loads
|
||||
* the courses from the database in chunks.
|
||||
*
|
||||
* @param array|Traversable $courses List of courses to process
|
||||
* @param array $favouritecourseids Array of favourite courses.
|
||||
* @param int $limit Limit the number of results to this amount
|
||||
* @return array First value is the filtered courses, second value is the number of courses processed
|
||||
*/
|
||||
function course_filter_courses_by_favourites(
|
||||
$courses,
|
||||
$favouritecourseids,
|
||||
int $limit = 0
|
||||
) : array {
|
||||
|
||||
$filteredcourses = [];
|
||||
$numberofcoursesprocessed = 0;
|
||||
$filtermatches = 0;
|
||||
|
||||
foreach ($courses as $course) {
|
||||
$numberofcoursesprocessed++;
|
||||
|
||||
if (in_array($course->id, $favouritecourseids)) {
|
||||
$filteredcourses[] = $course;
|
||||
$filtermatches++;
|
||||
}
|
||||
|
||||
if ($limit && $filtermatches >= $limit) {
|
||||
// We've found the number of requested courses. No need to continue searching.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the number of filtered courses as well as the number of courses that were searched
|
||||
// in order to find the matching courses. This allows the calling code to do some kind of
|
||||
// pagination.
|
||||
return [$filteredcourses, $numberofcoursesprocessed];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check module updates since a given time.
|
||||
* This function checks for updates in the module config, file areas, completion, grades, comments and ratings.
|
||||
|
|
|
@ -255,6 +255,7 @@ class icon_system_fontawesome extends icon_system_font {
|
|||
'core:i/menubars' => 'fa-bars',
|
||||
'core:i/mnethost' => 'fa-external-link',
|
||||
'core:i/moodle_host' => 'fa-graduation-cap',
|
||||
'core:i/moremenu' => 'fa-ellipsis-h',
|
||||
'core:i/move_2d' => 'fa-arrows',
|
||||
'core:i/navigationitem' => 'fa-fw',
|
||||
'core:i/ne_red_mark' => 'fa-remove',
|
||||
|
@ -296,6 +297,7 @@ class icon_system_fontawesome extends icon_system_font {
|
|||
'core:i/settings' => 'fa-cog',
|
||||
'core:i/show' => 'fa-eye-slash',
|
||||
'core:i/siteevent' => 'fa-globe',
|
||||
'core:i/star' => 'fa-star',
|
||||
'core:i/star-rating' => 'fa-star',
|
||||
'core:i/stats' => 'fa-line-chart',
|
||||
'core:i/switch' => 'fa-exchange',
|
||||
|
|
|
@ -2241,6 +2241,14 @@ $functions = array(
|
|||
'description' => 'Returns the filters available in the given contexts.',
|
||||
'type' => 'read',
|
||||
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
|
||||
),
|
||||
'core_course_set_favourite_courses' => array(
|
||||
'classname' => 'core_course_external',
|
||||
'methodname' => 'set_favourite_courses',
|
||||
'classpath' => 'course/externallib.php',
|
||||
'description' => 'Add a list of courses to the list of favourite courses.',
|
||||
'type' => 'read',
|
||||
'ajax' => true
|
||||
)
|
||||
);
|
||||
|
||||
|
|
BIN
pix/i/moremenu.png
Normal file
BIN
pix/i/moremenu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 305 B |
6
pix/i/moremenu.svg
Normal file
6
pix/i/moremenu.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12pt" height="12pt" viewBox="0 0 12 12" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(60%,60%,60%);fill-opacity:1;" d="M 3.855469 4.929688 L 3.855469 6.214844 C 3.855469 6.394531 3.792969 6.542969 3.667969 6.667969 C 3.542969 6.792969 3.394531 6.855469 3.214844 6.855469 L 1.929688 6.855469 C 1.75 6.855469 1.597656 6.792969 1.472656 6.667969 C 1.347656 6.542969 1.285156 6.394531 1.285156 6.214844 L 1.285156 4.929688 C 1.285156 4.75 1.347656 4.597656 1.472656 4.472656 C 1.597656 4.347656 1.75 4.285156 1.929688 4.285156 L 3.214844 4.285156 C 3.394531 4.285156 3.542969 4.347656 3.667969 4.472656 C 3.792969 4.597656 3.855469 4.75 3.855469 4.929688 Z M 7.285156 4.929688 L 7.285156 6.214844 C 7.285156 6.394531 7.222656 6.542969 7.097656 6.667969 C 6.972656 6.792969 6.820312 6.855469 6.644531 6.855469 L 5.355469 6.855469 C 5.179688 6.855469 5.027344 6.792969 4.902344 6.667969 C 4.777344 6.542969 4.714844 6.394531 4.714844 6.214844 L 4.714844 4.929688 C 4.714844 4.75 4.777344 4.597656 4.902344 4.472656 C 5.027344 4.347656 5.179688 4.285156 5.355469 4.285156 L 6.644531 4.285156 C 6.820312 4.285156 6.972656 4.347656 7.097656 4.472656 C 7.222656 4.597656 7.285156 4.75 7.285156 4.929688 Z M 10.714844 4.929688 L 10.714844 6.214844 C 10.714844 6.394531 10.652344 6.542969 10.527344 6.667969 C 10.402344 6.792969 10.25 6.855469 10.070312 6.855469 L 8.785156 6.855469 C 8.605469 6.855469 8.457031 6.792969 8.332031 6.667969 C 8.207031 6.542969 8.144531 6.394531 8.144531 6.214844 L 8.144531 4.929688 C 8.144531 4.75 8.207031 4.597656 8.332031 4.472656 C 8.457031 4.347656 8.605469 4.285156 8.785156 4.285156 L 10.070312 4.285156 C 10.25 4.285156 10.402344 4.347656 10.527344 4.472656 C 10.652344 4.597656 10.714844 4.75 10.714844 4.929688 Z M 10.714844 4.929688 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
BIN
pix/i/star.png
Normal file
BIN
pix/i/star.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 392 B |
6
pix/i/star.svg
Normal file
6
pix/i/star.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16pt" height="16pt" viewBox="0 0 16 16" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(60%,60%,60%);fill-opacity:1;" d="M 15.429688 5.777344 C 15.429688 5.90625 15.351562 6.050781 15.195312 6.207031 L 11.957031 9.367188 L 12.722656 13.832031 C 12.730469 13.871094 12.730469 13.929688 12.730469 14.007812 C 12.730469 14.132812 12.699219 14.238281 12.636719 14.324219 C 12.574219 14.414062 12.484375 14.457031 12.367188 14.457031 C 12.253906 14.457031 12.132812 14.417969 12.007812 14.347656 L 8 12.242188 L 3.992188 14.347656 C 3.859375 14.417969 3.742188 14.457031 3.632812 14.457031 C 3.507812 14.457031 3.414062 14.414062 3.351562 14.324219 C 3.289062 14.238281 3.257812 14.132812 3.257812 14.007812 C 3.257812 13.972656 3.265625 13.914062 3.277344 13.832031 L 4.042969 9.367188 L 0.792969 6.207031 C 0.644531 6.042969 0.570312 5.902344 0.570312 5.777344 C 0.570312 5.554688 0.738281 5.417969 1.070312 5.367188 L 5.554688 4.714844 L 7.5625 0.652344 C 7.675781 0.40625 7.820312 0.285156 8 0.285156 C 8.179688 0.285156 8.324219 0.40625 8.4375 0.652344 L 10.445312 4.714844 L 14.929688 5.367188 C 15.261719 5.417969 15.429688 5.554688 15.429688 5.777344 Z M 15.429688 5.777344 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -106,6 +106,31 @@ $card-gutter : $card-deck-margin * 2;
|
|||
.summary img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
a,
|
||||
.btn-link {
|
||||
color: inherit;
|
||||
}
|
||||
.btn.btn-link.btn-icon {
|
||||
height: $icon-width;
|
||||
width: $icon-width;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
|
||||
@include hover-focus {
|
||||
background-color: $gray-200;
|
||||
}
|
||||
|
||||
@each $size, $length in $iconsizes {
|
||||
&.icon-size-#{$size} {
|
||||
height: ($length + 20px) !important;
|
||||
width: ($length + 20px) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include media-breakpoint-down(sm) {
|
||||
.summaryimage {
|
||||
max-height: 7rem;
|
||||
|
@ -165,6 +190,11 @@ body.drawer-open-left #region-main.has-blocks .block_myoverview {
|
|||
flex-basis: calc(33.33% - #{$card-gutter});
|
||||
}
|
||||
}
|
||||
@media (min-width: 1400px) {
|
||||
.course-card {
|
||||
flex-basis: calc(25% - #{$card-gutter});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show expand collapse with font-awesome.
|
||||
|
|
|
@ -11162,6 +11162,40 @@ div.editor_atto_toolbar button .icon {
|
|||
.block_myoverview .summary img {
|
||||
max-width: 100%; }
|
||||
|
||||
.block_myoverview .icon {
|
||||
margin-right: 0; }
|
||||
|
||||
.block_myoverview a,
|
||||
.block_myoverview .btn-link {
|
||||
color: inherit; }
|
||||
|
||||
.block_myoverview .btn.btn-link.btn-icon, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.action, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.action, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.addcriterion, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.addcriterion {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0; }
|
||||
.block_myoverview .btn.btn-link.btn-icon:hover, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.action:hover, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.action:hover, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon:hover, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon:hover, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.addcriterion:hover, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.addcriterion:hover, .block_myoverview .btn.btn-link.btn-icon:focus, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.action:focus, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.action:focus, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon:focus, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon:focus, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.addcriterion:focus, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.addcriterion:focus {
|
||||
background-color: #e9ecef; }
|
||||
.block_myoverview .btn.btn-link.btn-icon.icon-size-0, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.icon-size-0.action, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.icon-size-0.action, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon.icon-size-0, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon.icon-size-0, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.icon-size-0.addcriterion, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.icon-size-0.addcriterion {
|
||||
height: 20px !important;
|
||||
width: 20px !important; }
|
||||
.block_myoverview .btn.btn-link.btn-icon.icon-size-1, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.icon-size-1.action, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.icon-size-1.action, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon.icon-size-1, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon.icon-size-1, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.icon-size-1.addcriterion, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.icon-size-1.addcriterion {
|
||||
height: 24px !important;
|
||||
width: 24px !important; }
|
||||
.block_myoverview .btn.btn-link.btn-icon.icon-size-2, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.icon-size-2.action, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.icon-size-2.action, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon.icon-size-2, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon.icon-size-2, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.icon-size-2.addcriterion, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.icon-size-2.addcriterion {
|
||||
height: 28px !important;
|
||||
width: 28px !important; }
|
||||
.block_myoverview .btn.btn-link.btn-icon.icon-size-3, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.icon-size-3.action, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.icon-size-3.action, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon.icon-size-3, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon.icon-size-3, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.icon-size-3.addcriterion, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.icon-size-3.addcriterion {
|
||||
height: 36px !important;
|
||||
width: 36px !important; }
|
||||
.block_myoverview .btn.btn-link.btn-icon.icon-size-4, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.icon-size-4.action, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.icon-size-4.action, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon.icon-size-4, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon.icon-size-4, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.icon-size-4.addcriterion, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.icon-size-4.addcriterion {
|
||||
height: 44px !important;
|
||||
width: 44px !important; }
|
||||
.block_myoverview .btn.btn-link.btn-icon.icon-size-5, .block_myoverview #page-grade-grading-manage .actions .btn-link.btn-icon.icon-size-5.action, #page-grade-grading-manage .actions .block_myoverview .btn-link.btn-icon.icon-size-5.action, .block_myoverview #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel input.btn-link.btn-icon.icon-size-5, #rubric-rubric.gradingform_rubric #rubric-criteria .criterion .addlevel .block_myoverview input.btn-link.btn-icon.icon-size-5, .block_myoverview #rubric-rubric.gradingform_rubric .btn-link.btn-icon.icon-size-5.addcriterion, #rubric-rubric.gradingform_rubric .block_myoverview .btn-link.btn-icon.icon-size-5.addcriterion {
|
||||
height: 68px !important;
|
||||
width: 68px !important; }
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.block_myoverview .summaryimage {
|
||||
max-height: 7rem; } }
|
||||
|
@ -11202,6 +11236,10 @@ body.drawer-open-left #region-main.has-blocks .block_myoverview .course-card {
|
|||
body.drawer-open-left #region-main.has-blocks .block_myoverview .course-card {
|
||||
flex-basis: calc(33.33% - 0.5rem); } }
|
||||
|
||||
@media (min-width: 1400px) {
|
||||
body.drawer-open-left #region-main.has-blocks .block_myoverview .course-card {
|
||||
flex-basis: calc(25% - 0.5rem); } }
|
||||
|
||||
.block_settings .block_tree [aria-expanded="true"],
|
||||
.block_settings .block_tree [aria-expanded="true"].emptybranch,
|
||||
.block_settings .block_tree [aria-expanded="false"],
|
||||
|
|
|
@ -346,6 +346,12 @@
|
|||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.favouritebtn {
|
||||
box-sizing: content-box;
|
||||
* {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
}
|
||||
.empty-placeholder-image-lg {
|
||||
height: 125px;
|
||||
}
|
||||
|
@ -396,6 +402,24 @@
|
|||
.summary img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.favouritebtn {
|
||||
cursor: pointer;
|
||||
margin-left: 0;
|
||||
}
|
||||
.coursemenubtn {
|
||||
border-radius: 50%;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
padding: 8px 0;
|
||||
&:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
img.icon {
|
||||
padding-right: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.summaryimage {
|
||||
|
|
|
@ -337,6 +337,10 @@
|
|||
align-self: stretch;
|
||||
}
|
||||
|
||||
.align-items-start {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
|
|
@ -16541,6 +16541,12 @@ body {
|
|||
.block_myoverview *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.block_myoverview .favouritebtn {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.block_myoverview .favouritebtn * {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.block_myoverview .empty-placeholder-image-lg {
|
||||
height: 125px;
|
||||
}
|
||||
|
@ -16607,6 +16613,24 @@ body {
|
|||
.block_myoverview .summary img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.block_myoverview .favouritebtn {
|
||||
cursor: pointer;
|
||||
margin-left: 0;
|
||||
}
|
||||
.block_myoverview .coursemenubtn {
|
||||
border-radius: 50%;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.block_myoverview .coursemenubtn:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.block_myoverview .coursemenubtn img.icon {
|
||||
padding-right: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.block_myoverview .summaryimage {
|
||||
max-height: 7rem;
|
||||
|
@ -22180,6 +22204,9 @@ ul.indented-list {
|
|||
.align-self-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
.align-items-start {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
{{!
|
||||
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/>.
|
||||
}}
|
||||
{{!
|
||||
@template block_myoverview/course-action-menu
|
||||
|
||||
This template renders action menu for each course.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"isfavourite": true
|
||||
}
|
||||
}}
|
||||
<div class="ml-auto dropdown">
|
||||
<button class="btn btn-link btn-icon icon-size-3 coursemenubtn"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-label="{{#str}} aria:courseactions, block_myoverview {{/str}}">
|
||||
{{#pix}} i/moremenu, core {{/pix}}
|
||||
</button>
|
||||
<div class="dropdown-menu pull-right">
|
||||
<ul class="list-unstyled">
|
||||
<li class="{{#isfavourite}}hidden{{/isfavourite}}" data-action="add-favourite" data-course-id="{{id}}">
|
||||
<a class="dropdown-item p-a-1" href="#">
|
||||
{{#pix}} i/star, core, {{#str}} starred, block_myoverview {{/str}} {{/pix}}
|
||||
{{#str}} addtofavourites, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{^isfavourite}}hidden{{/isfavourite}}" data-action="remove-favourite" data-course-id="{{id}}">
|
||||
<a class="dropdown-item p-a-1" href="#">
|
||||
{{#str}} removefromfavourites, block_myoverview {{/str}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue