first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,10 @@
/**
* A javascript module to retrieve calendar events from the server.
*
* @module block_timeline/calendar_events_repository
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("block_timeline/calendar_events_repository",["jquery","core/ajax","core/notification"],(function($,Ajax,Notification){return{queryByTime:function(args){args.hasOwnProperty("limit")||(args.limit=20),args.limitnum=args.limit,delete args.limit,args.hasOwnProperty("starttime")&&(args.timesortfrom=args.starttime,delete args.starttime),args.hasOwnProperty("endtime")&&(args.timesortto=args.endtime,delete args.endtime),args.limittononsuspendedevents=!0;var request={methodname:"core_calendar_get_action_events_by_timesort",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},queryByCourse:function(args){args.hasOwnProperty("limit")||(args.limit=20),args.limitnum=args.limit,delete args.limit,args.hasOwnProperty("starttime")&&(args.timesortfrom=args.starttime,delete args.starttime),args.hasOwnProperty("endtime")&&(args.timesortto=args.endtime,delete args.endtime);var request={methodname:"core_calendar_get_action_events_by_course",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},queryByCourses:function(args){args.hasOwnProperty("limit")||(args.limit=10),args.limitnum=args.limit,delete args.limit,args.hasOwnProperty("starttime")&&(args.timesortfrom=args.starttime,delete args.starttime),args.hasOwnProperty("endtime")&&(args.timesortto=args.endtime,delete args.endtime);var request={methodname:"core_calendar_get_action_events_by_courses",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise}}}));
//# sourceMappingURL=calendar_events_repository.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
/**
* Javascript to initialise the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("block_timeline/main",["jquery","block_timeline/view_nav","block_timeline/view"],(function($,ViewNav,View){var SELECTORS_TIMELINE_VIEW='[data-region="timeline-view"]';return{init:function(root){var viewRoot=(root=$(root)).find(SELECTORS_TIMELINE_VIEW);ViewNav.init(root,viewRoot),View.init(viewRoot)}}}));
//# sourceMappingURL=main.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Javascript to initialise the timeline block.\n *\n * @copyright 2018 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'block_timeline/view_nav',\n 'block_timeline/view'\n],\nfunction(\n $,\n ViewNav,\n View\n) {\n\n var SELECTORS = {\n TIMELINE_VIEW: '[data-region=\"timeline-view\"]'\n };\n\n /**\n * Initialise all of the modules for the timeline block.\n *\n * @param {object} root The root element for the timeline block.\n */\n var init = function(root) {\n root = $(root);\n var viewRoot = root.find(SELECTORS.TIMELINE_VIEW);\n\n // Initialise the timeline navigation elements.\n ViewNav.init(root, viewRoot);\n // Initialise the timeline view modules.\n View.init(viewRoot);\n };\n\n return {\n init: init\n };\n});\n"],"names":["define","$","ViewNav","View","SELECTORS","init","root","viewRoot","find"],"mappings":";;;;;;AAsBAA,6BACA,CACI,SACA,0BACA,wBAEJ,SACIC,EACAC,QACAC,UAGIC,wBACe,sCAkBZ,CACHC,KAXO,SAASC,UAEZC,UADJD,KAAOL,EAAEK,OACWE,KAAKJ,yBAGzBF,QAAQG,KAAKC,KAAMC,UAEnBJ,KAAKE,KAAKE"}
+9
View File
@@ -0,0 +1,9 @@
/**
* Manage the timeline view for the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("block_timeline/view",["jquery","block_timeline/view_dates","block_timeline/view_courses"],(function($,ViewDates,ViewCourses){var SELECTORS_TIMELINE_DATES_VIEW='[data-region="view-dates"]',SELECTORS_TIMELINE_COURSES_VIEW='[data-region="view-courses"]';return{init:function(root){var datesViewRoot=(root=$(root)).find(SELECTORS_TIMELINE_DATES_VIEW),coursesViewRoot=root.find(SELECTORS_TIMELINE_COURSES_VIEW);ViewDates.init(datesViewRoot),ViewCourses.init(coursesViewRoot)},reset:function(root){var datesViewRoot=root.find(SELECTORS_TIMELINE_DATES_VIEW),coursesViewRoot=root.find(SELECTORS_TIMELINE_COURSES_VIEW);ViewDates.reset(datesViewRoot),ViewCourses.reset(coursesViewRoot)},shown:function(root){var datesViewRoot=root.find(SELECTORS_TIMELINE_DATES_VIEW),coursesViewRoot=root.find(SELECTORS_TIMELINE_COURSES_VIEW);datesViewRoot.hasClass("active")?ViewDates.shown(datesViewRoot):ViewCourses.shown(coursesViewRoot)}}}));
//# sourceMappingURL=view.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"view.min.js","sources":["../src/view.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Manage the timeline view for the timeline block.\n *\n * @copyright 2018 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(\n[\n 'jquery',\n 'block_timeline/view_dates',\n 'block_timeline/view_courses',\n],\nfunction(\n $,\n ViewDates,\n ViewCourses\n) {\n\n var SELECTORS = {\n TIMELINE_DATES_VIEW: '[data-region=\"view-dates\"]',\n TIMELINE_COURSES_VIEW: '[data-region=\"view-courses\"]',\n };\n\n /**\n * Intialise the timeline dates and courses views on page load.\n * This function should only be called once per page load because\n * it can cause event listeners to be added to the page.\n *\n * @param {object} root The root element for the timeline view.\n */\n var init = function(root) {\n root = $(root);\n var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);\n var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);\n\n ViewDates.init(datesViewRoot);\n ViewCourses.init(coursesViewRoot);\n };\n\n /**\n * Reset the timeline dates and courses views to their original\n * state on first page load.\n *\n * This is called when configuration has changed for the event lists\n * to cause them to reload their data.\n *\n * @param {object} root The root element for the timeline view.\n */\n var reset = function(root) {\n var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);\n var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);\n ViewDates.reset(datesViewRoot);\n ViewCourses.reset(coursesViewRoot);\n };\n\n /**\n * Tell the timeline dates or courses view that it has been displayed.\n *\n * This is called each time one of the views is displayed and is used to\n * lazy load the data within it on first load.\n *\n * @param {object} root The root element for the timeline view.\n */\n var shown = function(root) {\n var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);\n var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);\n\n if (datesViewRoot.hasClass('active')) {\n ViewDates.shown(datesViewRoot);\n } else {\n ViewCourses.shown(coursesViewRoot);\n }\n };\n\n return {\n init: init,\n reset: reset,\n shown: shown,\n };\n});\n"],"names":["define","$","ViewDates","ViewCourses","SELECTORS","init","root","datesViewRoot","find","coursesViewRoot","reset","shown","hasClass"],"mappings":";;;;;;AAsBAA,6BACA,CACI,SACA,4BACA,gCAEJ,SACIC,EACAC,UACAC,iBAGIC,8BACqB,6BADrBA,gCAEuB,qCAsDpB,CACHC,KA7CO,SAASC,UAEZC,eADJD,KAAOL,EAAEK,OACgBE,KAAKJ,+BAC1BK,gBAAkBH,KAAKE,KAAKJ,iCAEhCF,UAAUG,KAAKE,eACfJ,YAAYE,KAAKI,kBAwCjBC,MA5BQ,SAASJ,UACbC,cAAgBD,KAAKE,KAAKJ,+BAC1BK,gBAAkBH,KAAKE,KAAKJ,iCAChCF,UAAUQ,MAAMH,eAChBJ,YAAYO,MAAMD,kBAyBlBE,MAdQ,SAASL,UACbC,cAAgBD,KAAKE,KAAKJ,+BAC1BK,gBAAkBH,KAAKE,KAAKJ,iCAE5BG,cAAcK,SAAS,UACvBV,UAAUS,MAAMJ,eAEhBJ,YAAYQ,MAAMF"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
/**
* Manage the timeline dates view for the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("block_timeline/view_dates",["jquery","block_timeline/event_list","core/pubsub","core/paged_content_events"],(function($,EventList,PubSub,PagedContentEvents){var SELECTORS_EVENT_LIST_CONTAINER='[data-region="event-list-container"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',load=function(root){if(!root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length){var eventListContainer=root.find(SELECTORS_EVENT_LIST_CONTAINER),namespace=$(eventListContainer).attr("id")+"user_block_timeline"+Math.random();!function(root,namespace){var event=namespace+PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT;PubSub.subscribe(event,(function(limit){$(root).data("limit",limit)}))}(root,namespace);var config={persistentLimitKey:"block_timeline_user_limit_preference",eventNamespace:namespace};EventList.init(eventListContainer,config)}};return{init:function(root){(root=$(root)).hasClass("active")&&!root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length&&(load(root),root.attr("data-seen",!0))},reset:function(root){root.removeAttr("data-seen"),root.hasClass("active")&&(load(root),root.attr("data-seen",!0))},shown:function(root){root.attr("data-seen")||(load(root),root.attr("data-seen",!0))}}}));
//# sourceMappingURL=view_dates.min.js.map
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
define("block_timeline/view_nav",["exports","jquery","core/custom_interaction_events","block_timeline/view","core/notification","core/utils","core_user/repository"],(function(_exports,_jquery,CustomEvents,View,Notification,Utils,UserRepository){var obj;
/**
* Manage the timeline view navigation for the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj},CustomEvents=_interopRequireWildcard(CustomEvents),View=_interopRequireWildcard(View),Notification=_interopRequireWildcard(Notification),Utils=_interopRequireWildcard(Utils),UserRepository=_interopRequireWildcard(UserRepository);const SELECTORS_TIMELINE_DAY_FILTER='[data-region="day-filter"]',SELECTORS_TIMELINE_DAY_FILTER_OPTION="[data-from]",SELECTORS_TIMELINE_VIEW_SELECTOR='[data-region="view-selector"]',SELECTORS_DATA_DAYS_OFFSET="[data-days-offset]",SELECTORS_TIMELINE_SEARCH_INPUT='[data-action="search"]',SELECTORS_TIMELINE_SEARCH_CLEAR_ICON='[data-action="clearsearch"]',SELECTORS_NO_COURSES_EMPTY_MESSAGE='[data-region="no-courses-empty-message"]',activeSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.removeClass("d-none"),View.reset(timelineViewRoot)},clearSearchState=(clearSearchIcon,timelineViewRoot)=>{clearSearchIcon.addClass("d-none"),View.reset(timelineViewRoot)};_exports.init=function(root,timelineViewRoot){(function(root,timelineViewRoot){const viewSelector=root.find(SELECTORS_TIMELINE_VIEW_SELECTOR);viewSelector.on("shown shown.bs.tab",(function(e){View.shown(timelineViewRoot),(0,_jquery.default)(e.target).removeClass("active")})),CustomEvents.define(viewSelector,[CustomEvents.events.activate]),viewSelector.on(CustomEvents.events.activate,"[data-toggle='tab']",(function(e){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_sort_preference",filtername).catch(Notification.exception)}))})(root=(0,_jquery.default)(root),timelineViewRoot),root.find(SELECTORS_NO_COURSES_EMPTY_MESSAGE).length||(function(root,timelineViewRoot){const timelineDaySelectorContainer=root.find(SELECTORS_TIMELINE_DAY_FILTER);CustomEvents.define(timelineDaySelectorContainer,[CustomEvents.events.activate]),timelineDaySelectorContainer.on(CustomEvents.events.activate,SELECTORS_TIMELINE_DAY_FILTER_OPTION,(function(e,data){var filtername=(0,_jquery.default)(e.currentTarget).data("filtername");UserRepository.setUserPreference("block_timeline_user_filter_preference",filtername).catch(Notification.exception);var option=(0,_jquery.default)(e.target).closest(SELECTORS_TIMELINE_DAY_FILTER_OPTION);if("true"!=option.attr("aria-current")){var daysOffset=option.attr("data-from"),daysLimit=option.attr("data-to"),elementsWithDaysOffset=root.find(SELECTORS_DATA_DAYS_OFFSET);elementsWithDaysOffset.attr("data-days-offset",daysOffset),null!=daysLimit?elementsWithDaysOffset.attr("data-days-limit",daysLimit):elementsWithDaysOffset.removeAttr("data-days-limit"),"overdue"===option.attr("data-filtername")?elementsWithDaysOffset.attr("data-filter-overdue",!0):elementsWithDaysOffset.removeAttr("data-filter-overdue"),View.reset(timelineViewRoot),data.originalEvent.preventDefault()}}))}(root,timelineViewRoot),((root,timelineViewRoot)=>{const searchInput=root.find(SELECTORS_TIMELINE_SEARCH_INPUT),clearSearchIcon=root.find(SELECTORS_TIMELINE_SEARCH_CLEAR_ICON);searchInput.on("input",Utils.debounce((()=>{""!==searchInput.val()?activeSearchState(clearSearchIcon,timelineViewRoot):clearSearchState(clearSearchIcon,timelineViewRoot)}),1e3)),clearSearchIcon.on("click",(()=>{searchInput.val(""),clearSearchState(clearSearchIcon,timelineViewRoot),searchInput.focus()}))})(root,timelineViewRoot))}}));
//# sourceMappingURL=view_nav.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,168 @@
// 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/>.
/**
* A javascript module to retrieve calendar events from the server.
*
* @module block_timeline/calendar_events_repository
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
var DEFAULT_LIMIT = 20;
/**
* Retrieve a list of calendar events for the logged in user for the
* given course.
*
* Valid args are:
* int courseid Only get events for this course
* int starttime Only get events after this time
* int endtime Only get events before this time
* int limit Limit the number of results returned
* int aftereventid Offset the result set from the given id
*
* @method queryByCourse
* @param {object} args The request arguments
* @return {promise} Resolved with an array of the calendar events
*/
var queryByCourse = function(args) {
if (!args.hasOwnProperty('limit')) {
args.limit = DEFAULT_LIMIT;
}
args.limitnum = args.limit;
delete args.limit;
if (args.hasOwnProperty('starttime')) {
args.timesortfrom = args.starttime;
delete args.starttime;
}
if (args.hasOwnProperty('endtime')) {
args.timesortto = args.endtime;
delete args.endtime;
}
var request = {
methodname: 'core_calendar_get_action_events_by_course',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Retrieve a list of calendar events for the given courses for the
* logged in user.
*
* Valid args are:
* array courseids Get events for these courses
* int starttime Only get events after this time
* int endtime Only get events before this time
* int limit Limit the number of results returned
*
* @method queryByCourses
* @param {object} args The request arguments
* @return {promise} Resolved with an array of the calendar events
*/
var queryByCourses = function(args) {
if (!args.hasOwnProperty('limit')) {
// This is intentionally smaller than the default limit.
args.limit = 10;
}
args.limitnum = args.limit;
delete args.limit;
if (args.hasOwnProperty('starttime')) {
args.timesortfrom = args.starttime;
delete args.starttime;
}
if (args.hasOwnProperty('endtime')) {
args.timesortto = args.endtime;
delete args.endtime;
}
var request = {
methodname: 'core_calendar_get_action_events_by_courses',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
/**
* Retrieve a list of calendar events for the logged in user after the given
* time.
*
* Valid args are:
* int starttime Only get events after this time
* int endtime Only get events before this time
* int limit Limit the number of results returned
* int aftereventid Offset the result set from the given id
*
* @method queryByTime
* @param {object} args The request arguments
* @return {promise} Resolved with an array of the calendar events
*/
var queryByTime = function(args) {
if (!args.hasOwnProperty('limit')) {
args.limit = DEFAULT_LIMIT;
}
args.limitnum = args.limit;
delete args.limit;
if (args.hasOwnProperty('starttime')) {
args.timesortfrom = args.starttime;
delete args.starttime;
}
if (args.hasOwnProperty('endtime')) {
args.timesortto = args.endtime;
delete args.endtime;
}
// Don't show events related to courses that the user is suspended in.
args.limittononsuspendedevents = true;
var request = {
methodname: 'core_calendar_get_action_events_by_timesort',
args: args
};
var promise = Ajax.call([request])[0];
promise.fail(Notification.exception);
return promise;
};
return {
queryByTime: queryByTime,
queryByCourse: queryByCourse,
queryByCourses: queryByCourses,
};
});
+544
View File
@@ -0,0 +1,544 @@
// 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/>.
/**
* Javascript to load and render the list of calendar events for a
* given day range.
*
* @module block_timeline/event_list
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/notification',
'core/templates',
'core/str',
'core/user_date',
'block_timeline/calendar_events_repository',
'core/pending'
],
function(
$,
Notification,
Templates,
Str,
UserDate,
CalendarEventsRepository,
Pending
) {
var SECONDS_IN_DAY = 60 * 60 * 24;
var courseview = false;
var SELECTORS = {
EMPTY_MESSAGE: '[data-region="no-events-empty-message"]',
ROOT: '[data-region="event-list-container"]',
EVENT_LIST_CONTENT: '[data-region="event-list-content"]',
EVENT_LIST_WRAPPER: '[data-region="event-list-wrapper"]',
EVENT_LIST_LOADING_PLACEHOLDER: '[data-region="event-list-loading-placeholder"]',
TIMELINE_BLOCK: '[data-region="timeline"]',
TIMELINE_SEARCH: '[data-action="search"]',
MORE_ACTIVITIES_BUTTON: '[data-action="more-events"]',
MORE_ACTIVITIES_BUTTON_CONTAINER: '[data-region="more-events-button-container"]'
};
var TEMPLATES = {
EVENT_LIST_CONTENT: 'block_timeline/event-list-content',
MORE_ACTIVITIES_BUTTON: 'block_timeline/event-list-loadmore',
LOADING_ICON: 'core/loading'
};
/** @property {number} The total items will be shown on the first load. */
const DEFAULT_LAZY_LOADING_ITEMS_FIRST_LOAD = 5;
/** @property {number} The total items will be shown when click on the Show more activities button. */
const DEFAULT_LAZY_LOADING_ITEMS_OTHER_LOAD = 10;
/**
* Hide the content area and display the empty content message.
*
* @param {object} root The container element
*/
var hideContent = function(root) {
root.find(SELECTORS.EVENT_LIST_CONTENT).addClass('hidden');
root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');
};
/**
* Show the content area and hide the empty content message.
*
* @param {object} root The container element
*/
var showContent = function(root) {
root.find(SELECTORS.EVENT_LIST_CONTENT).removeClass('hidden');
root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');
};
/**
* Empty the content area.
*
* @param {object} root The container element
*/
var emptyContent = function(root) {
root.find(SELECTORS.EVENT_LIST_CONTENT).empty();
};
/**
* Construct the template context from a list of calendar events. The events
* are grouped by which day they are on. The day is calculated from the user's
* midnight timestamp to ensure that the calculation is timezone agnostic.
*
* The return data structure will look like:
* {
* eventsbyday: [
* {
* dayTimestamp: 1533744000,
* events: [
* { ...event 1 data... },
* { ...event 2 data... }
* ]
* },
* {
* dayTimestamp: 1533830400,
* events: [
* { ...event 3 data... },
* { ...event 4 data... }
* ]
* }
* ]
* }
*
* Each day timestamp is the day's midnight in the user's timezone.
*
* @param {array} calendarEvents List of calendar events
* @return {object}
*/
var buildTemplateContext = function(calendarEvents) {
var eventsByDay = {};
var templateContext = {
courseview,
eventsbyday: []
};
calendarEvents.forEach(function(calendarEvent) {
var dayTimestamp = calendarEvent.timeusermidnight;
if (eventsByDay[dayTimestamp]) {
eventsByDay[dayTimestamp].push(calendarEvent);
} else {
eventsByDay[dayTimestamp] = [calendarEvent];
}
});
Object.keys(eventsByDay).forEach(function(dayTimestamp) {
var events = eventsByDay[dayTimestamp];
templateContext.eventsbyday.push({
dayTimestamp: dayTimestamp,
events: events
});
});
return templateContext;
};
/**
* Render the HTML for the given calendar events.
*
* @param {array} calendarEvents A list of calendar events
* @return {promise} Resolved with HTML and JS strings.
*/
var render = function(calendarEvents) {
var templateContext = buildTemplateContext(calendarEvents);
var templateName = TEMPLATES.EVENT_LIST_CONTENT;
return Templates.render(templateName, templateContext);
};
/**
* Retrieve a list of calendar events from the server for the given
* constraints.
*
* @param {Number} midnight The user's midnight time in unix timestamp.
* @param {Number} limit Limit the result set to this number of items
* @param {Number} daysOffset How many days (from midnight) to offset the results from
* @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to
* @param {int|false} lastId The ID of the last seen event (if any)
* @param {int|undefined} courseId Course ID to restrict events to
* @param {string|undefined} searchValue Search value
* @return {Promise} A jquery promise
*/
var load = function(midnight, limit, daysOffset, daysLimit, lastId, courseId, searchValue) {
var startTime = midnight + (daysOffset * SECONDS_IN_DAY);
var endTime = daysLimit != undefined ? midnight + (daysLimit * SECONDS_IN_DAY) : false;
var args = {
starttime: startTime,
limit: limit,
};
if (lastId) {
args.aftereventid = lastId;
}
if (endTime) {
args.endtime = endTime;
}
if (searchValue) {
args.searchvalue = searchValue;
}
if (courseId) {
// If we have a course id then we only want events from that course.
args.courseid = courseId;
return CalendarEventsRepository.queryByCourse(args);
} else {
// Otherwise we want events from any course.
return CalendarEventsRepository.queryByTime(args);
}
};
/**
* Create a lazy-loading region for the calendar events in the given root element.
*
* @param {object} root The event list container element.
* @param {object} additionalConfig Additional config options to pass to pagedContentFactory.
*/
var init = function(root, additionalConfig = {}) {
const pendingPromise = new Pending('block/timeline:event-init');
root = $(root);
courseview = !!additionalConfig.courseview;
// Create a promise that will be resolved once the first set of page
// data has been loaded. This ensures that the loading placeholder isn't
// hidden until we have all of the data back to prevent the page elements
// jumping around.
var firstLoad = $.Deferred();
var eventListContent = root.find(SELECTORS.EVENT_LIST_CONTENT);
var loadingPlaceholder = root.find(SELECTORS.EVENT_LIST_LOADING_PLACEHOLDER);
var courseId = root.attr('data-course-id');
var daysOffset = parseInt(root.attr('data-days-offset'), 10);
var daysLimit = root.attr('data-days-limit');
var midnight = parseInt(root.attr('data-midnight'), 10);
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
// Make sure the content area and loading placeholder is visible.
// This is because the init function can be called to re-initialise
// an existing event list area.
emptyContent(root);
showContent(root);
loadingPlaceholder.removeClass('hidden');
// Days limit isn't mandatory.
if (daysLimit != undefined) {
daysLimit = parseInt(daysLimit, 10);
}
// Create the lazy loading content element.
return createLazyLoadingContent(root, firstLoad,
DEFAULT_LAZY_LOADING_ITEMS_FIRST_LOAD, midnight, 0, courseId, daysOffset, daysLimit, searchValue)
.then(function(html, js) {
firstLoad.then(function(data) {
if (!data.hasContent) {
loadingPlaceholder.addClass('hidden');
// If we didn't get any data then show the empty data message.
return hideContent(root);
}
html = $(html);
// Hide the content for now.
html.addClass('hidden');
// Replace existing elements with the newly created lazy-loading region.
Templates.replaceNodeContents(eventListContent, html, js);
// Prevent changing page elements too much by only showing the content
// once we've loaded some data for the first time. This allows our
// fancy loading placeholder to shine.
html.removeClass('hidden');
loadingPlaceholder.addClass('hidden');
if (!data.loadedAll) {
Templates.render(TEMPLATES.MORE_ACTIVITIES_BUTTON, {courseview}).then(function(html) {
eventListContent.append(html);
setLastTimestamp(root, data.lastTimeStamp);
// Init the event handler.
initEventListener(root);
return html;
}).catch(function() {
return false;
});
}
return data;
})
.catch(function() {
return false;
});
return html;
}).then(() => {
return pendingPromise.resolve();
})
.catch(Notification.exception);
};
/**
* Create a lazy-loading content element for showing the event list for the initial load.
*
* @param {object} root The event list container element.
* @param {object} firstLoad A jQuery promise to be resolved after the first set of data is loaded.
* @param {int} itemLimit Limit the number of items.
* @param {Number} midnight The user's midnight time in unix timestamp.
* @param {int} lastId The last event ID for each loaded page. Page number is key, id is value.
* @param {int|undefined} courseId Course ID to restrict events to.
* @param {Number} daysOffset How many days (from midnight) to offset the results from.
* @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to.
* @param {string|undefined} searchValue Search value.
* @return {object} jQuery promise resolved with calendar events.
*/
const createLazyLoadingContent = (root, firstLoad, itemLimit, midnight, lastId,
courseId, daysOffset, daysLimit, searchValue) => {
return loadEventsForLazyLoading(
root,
itemLimit,
midnight,
lastId,
courseId,
daysOffset,
daysLimit,
searchValue
).then(data => {
if (data.calendarEvents.length) {
const lastEventId = data.calendarEvents.at(-1).id;
const lastTimeStamp = data.calendarEvents.at(-1).timeusermidnight;
firstLoad.resolve({
hasContent: true,
lastId: lastEventId,
lastTimeStamp: lastTimeStamp,
loadedAll: data.loadedAll
});
return render(data.calendarEvents, midnight);
} else {
firstLoad.resolve({
hasContent: false,
lastId: 0,
lastTimeStamp: 0,
loadedAll: true
});
return data.calendarEvents;
}
}).catch(Notification.exception);
};
/**
* Handle the request from the lazy-loading region.
* Uses the given data like course id, offset... to request the events from the server.
*
* @param {object} root The event list container element.
* @param {int} itemLimit Limit the number of items.
* @param {Number} midnight The user's midnight time in unix timestamp.
* @param {int} lastId The last event ID for each loaded page.
* @param {int|undefined} courseId Course ID to restrict events to.
* @param {Number} daysOffset How many days (from midnight) to offset the results from.
* @param {int|undefined} daysLimit How many dates (from midnight) to limit the result to.
* @param {string|undefined} searchValue Search value.
* @return {object} jQuery promise resolved with calendar events.
*/
const loadEventsForLazyLoading = (root, itemLimit, midnight, lastId, courseId, daysOffset, daysLimit, searchValue) => {
// Load one more than the given limit so that we can tell if there
// is more content to load after this.
const eventsPromise = load(midnight, itemLimit + 1, daysOffset, daysLimit, lastId, courseId, searchValue);
let calendarEvents = [];
let loadedAll = true;
return eventsPromise.then(result => {
if (!result.events.length) {
return {calendarEvents, loadedAll};
}
// Determine if the overdue filter is applied.
const overdueFilter = document.querySelector("[data-filtername='overdue']");
const filterByOverdue = (overdueFilter && overdueFilter.getAttribute('aria-current'));
calendarEvents = result.events.filter(event => {
if (event.eventtype == 'open' || event.eventtype == 'opensubmission') {
const dayTimestamp = UserDate.getUserMidnightForTimestamp(event.timesort, midnight);
return dayTimestamp > midnight;
}
// When filtering by overdue, we fetch all events due today, in case any have elapsed already and are overdue.
// This means if filtering by overdue, some events fetched might not be required (eg if due later today).
return (!filterByOverdue || event.overdue);
});
loadedAll = calendarEvents.length <= itemLimit;
if (!loadedAll) {
// Remove the last element from the array because it isn't
// needed in this result set.
calendarEvents.pop();
}
if (calendarEvents.length) {
const lastEventId = calendarEvents.at(-1).id;
setOffset(root, lastEventId);
}
return {calendarEvents, loadedAll};
});
};
/**
* Load new events and append to current list.
*
* @param {object} root The event list container element.
*/
const loadMoreEvents = root => {
const midnight = parseInt(root.attr('data-midnight'), 10);
const courseId = root.attr('data-course-id');
const daysOffset = parseInt(root.attr('data-days-offset'), 10);
const daysLimit = root.attr('data-days-limit');
const lastId = getOffset(root);
const eventListWrapper = root.find(SELECTORS.EVENT_LIST_WRAPPER);
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
const eventsPromise = loadEventsForLazyLoading(
root,
DEFAULT_LAZY_LOADING_ITEMS_OTHER_LOAD,
midnight,
lastId,
courseId,
daysOffset,
daysLimit,
searchValue
);
eventsPromise.then(data => {
if (data.calendarEvents.length) {
const renderPromise = render(data.calendarEvents);
const lastTimestamp = getLastTimestamp(root);
renderPromise.then((html, js) => {
html = $(html);
// Remove the date heading if it has the same value as the previous one.
html.find(`[data-timestamp="${lastTimestamp}"]`).remove();
Templates.appendNodeContents(eventListWrapper, html.html(), js);
if (!data.loadedAll) {
Templates.render(TEMPLATES.MORE_ACTIVITIES_BUTTON, {}).then(html => {
eventListWrapper.append(html);
setLastTimestamp(root, data.calendarEvents.at(-1).timeusermidnight);
// Init the event handler.
initEventListener(root);
return html;
}).catch(() => {
return false;
});
}
return html;
}).catch(Notification.exception);
}
return data;
}).then(() => {
return disableMoreActivitiesButtonLoading(root);
}).catch(Notification.exception);
};
/**
* Return the offset value for lazy loading fetching.
*
* @param {object} element The event list container element.
* @return {Number} Offset value.
*/
const getOffset = element => {
return parseInt(element.attr('data-lazyload-offset'), 10);
};
/**
* Set the offset value for lazy loading fetching.
*
* @param {object} element The event list container element.
* @param {Number} offset Offset value.
*/
const setOffset = (element, offset) => {
element.attr('data-lazyload-offset', offset);
};
/**
* Return the timestamp value for lazy loading fetching.
*
* @param {object} element The event list container element.
* @return {Number} Timestamp value.
*/
const getLastTimestamp = element => {
return parseInt(element.attr('data-timestamp'), 10);
};
/**
* Set the timestamp value for lazy loading fetching.
*
* @param {object} element The event list container element.
* @param {Number} timestamp Timestamp value.
*/
const setLastTimestamp = (element, timestamp) => {
element.attr('data-timestamp', timestamp);
};
/**
* Add the "Show more activities" button and remove and loading spinner.
*
* @param {object} root The event list container element.
*/
const enableMoreActivitiesButtonLoading = root => {
const loadMoreButton = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON);
loadMoreButton.prop('disabled', true);
Templates.render(TEMPLATES.LOADING_ICON, {}).then(html => {
loadMoreButton.append(html);
return html;
}).catch(() => {
// It's not important if this false so just do so silently.
return false;
});
};
/**
* Remove the "Show more activities" button and remove and loading spinner.
*
* @param {object} root The event list container element.
*/
const disableMoreActivitiesButtonLoading = root => {
const loadMoreButtonContainer = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON_CONTAINER);
loadMoreButtonContainer.remove();
};
/**
* Event initialise.
*
* @param {object} root The event list container element.
*/
const initEventListener = root => {
const loadMoreButton = root.find(SELECTORS.MORE_ACTIVITIES_BUTTON);
loadMoreButton.on('click', () => {
enableMoreActivitiesButtonLoading(root);
loadMoreEvents(root);
});
};
return {
init: init,
rootSelector: SELECTORS.ROOT,
};
});
+57
View File
@@ -0,0 +1,57 @@
// 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/>.
/**
* Javascript to initialise the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'block_timeline/view_nav',
'block_timeline/view'
],
function(
$,
ViewNav,
View
) {
var SELECTORS = {
TIMELINE_VIEW: '[data-region="timeline-view"]'
};
/**
* Initialise all of the modules for the timeline block.
*
* @param {object} root The root element for the timeline block.
*/
var init = function(root) {
root = $(root);
var viewRoot = root.find(SELECTORS.TIMELINE_VIEW);
// Initialise the timeline navigation elements.
ViewNav.init(root, viewRoot);
// Initialise the timeline view modules.
View.init(viewRoot);
};
return {
init: init
};
});
+96
View File
@@ -0,0 +1,96 @@
// 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/>.
/**
* Manage the timeline view for the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'block_timeline/view_dates',
'block_timeline/view_courses',
],
function(
$,
ViewDates,
ViewCourses
) {
var SELECTORS = {
TIMELINE_DATES_VIEW: '[data-region="view-dates"]',
TIMELINE_COURSES_VIEW: '[data-region="view-courses"]',
};
/**
* Intialise the timeline dates and courses views on page load.
* This function should only be called once per page load because
* it can cause event listeners to be added to the page.
*
* @param {object} root The root element for the timeline view.
*/
var init = function(root) {
root = $(root);
var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);
var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);
ViewDates.init(datesViewRoot);
ViewCourses.init(coursesViewRoot);
};
/**
* Reset the timeline dates and courses views to their original
* state on first page load.
*
* 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.
*/
var reset = function(root) {
var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);
var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);
ViewDates.reset(datesViewRoot);
ViewCourses.reset(coursesViewRoot);
};
/**
* Tell the timeline dates or courses view that it has been displayed.
*
* This is called each time one of the views is displayed and is used to
* lazy load the data within it on first load.
*
* @param {object} root The root element for the timeline view.
*/
var shown = function(root) {
var datesViewRoot = root.find(SELECTORS.TIMELINE_DATES_VIEW);
var coursesViewRoot = root.find(SELECTORS.TIMELINE_COURSES_VIEW);
if (datesViewRoot.hasClass('active')) {
ViewDates.shown(datesViewRoot);
} else {
ViewCourses.shown(coursesViewRoot);
}
};
return {
init: init,
reset: reset,
shown: shown,
};
});
+582
View File
@@ -0,0 +1,582 @@
// 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/>.
/**
* Manage the timeline courses view for the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/notification',
'core/custom_interaction_events',
'core/templates',
'block_timeline/event_list',
'core_course/repository',
'block_timeline/calendar_events_repository',
'core/pending'
],
function(
$,
Notification,
CustomEvents,
Templates,
EventList,
CourseRepository,
EventsRepository,
Pending
) {
var SELECTORS = {
MORE_COURSES_BUTTON: '[data-action="more-courses"]',
MORE_COURSES_BUTTON_CONTAINER: '[data-region="more-courses-button-container"]',
NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]',
NO_COURSES_WITH_EVENTS_MESSAGE: '[data-region="no-events-empty-message"]',
COURSES_LIST: '[data-region="courses-list"]',
COURSE_ITEMS_LOADING_PLACEHOLDER: '[data-region="course-items-loading-placeholder"]',
COURSE_EVENTS_CONTAINER: '[data-region="course-events-container"]',
COURSE_NAME: '[data-region="course-name"]',
LOADING_ICON: '.loading-icon',
TIMELINE_BLOCK: '[data-region="timeline"]',
TIMELINE_SEARCH: '[data-action="search"]'
};
var TEMPLATES = {
COURSE_ITEMS: 'block_timeline/course-items',
LOADING_ICON: 'core/loading'
};
var COURSE_CLASSIFICATION = 'all';
var COURSE_SORT = 'fullname asc';
var COURSE_EVENT_LIMIT = 5;
var COURSE_LIMIT = 2;
var SECONDS_IN_DAY = 60 * 60 * 24;
const additionalConfig = {courseview: true};
/**
* Hide the loading placeholder elements.
*
* @param {object} root The rool element.
*/
var hideLoadingPlaceholder = function(root) {
root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).addClass('hidden');
};
/**
* Show the loading placeholder elements.
*
* @param {object} root The rool element.
*/
const showLoadingPlaceholder = function(root) {
root.find(SELECTORS.COURSE_ITEMS_LOADING_PLACEHOLDER).removeClass('hidden');
};
/**
* Hide the "more courses" button.
*
* @param {object} root The rool element.
*/
var hideMoreCoursesButton = function(root) {
root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).addClass('hidden');
};
/**
* Show the "more courses" button.
*
* @param {object} root The rool element.
*/
var showMoreCoursesButton = function(root) {
root.find(SELECTORS.MORE_COURSES_BUTTON_CONTAINER).removeClass('hidden');
};
/**
* Disable the "more courses" button and show the loading spinner.
*
* @param {object} root The rool element.
*/
var enableMoreCoursesButtonLoading = function(root) {
var button = root.find(SELECTORS.MORE_COURSES_BUTTON);
button.prop('disabled', true);
Templates.render(TEMPLATES.LOADING_ICON, {})
.then(function(html) {
button.append(html);
return html;
})
.catch(function() {
// It's not important if this false so just do so silently.
return false;
});
};
/**
* Enable the "more courses" button and remove the loading spinner.
*
* @param {object} root The rool element.
*/
var disableMoreCoursesButtonLoading = function(root) {
var button = root.find(SELECTORS.MORE_COURSES_BUTTON);
button.prop('disabled', false);
button.find(SELECTORS.LOADING_ICON).remove();
};
/**
* Display the message for when courses have no events available (within the current filtering).
*
* @param {object} root The rool element.
*/
const showNoCoursesWithEventsMessage = function(root) {
// Remove any course list contents, since we will display the no events message.
const container = root.find(SELECTORS.COURSES_LIST);
Templates.replaceNodeContents(container, '', '');
root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).removeClass('hidden');
};
/**
* Hide the message for when courses have no events available (within the current filtering).
*
* @param {object} root The rool element.
*/
const hideNoCoursesWithEventsMessage = function(root) {
root.find(SELECTORS.NO_COURSES_WITH_EVENTS_MESSAGE).addClass('hidden');
};
/**
* Render the course items HTML to the page.
*
* @param {object} root The rool element.
* @param {string} html The course items HTML to render.
* @param {boolean} append Whether the HTML should be appended (eg pressed "show more courses").
* Defaults to false - replaces the existing content (eg when modifying filter values).
*/
var renderCourseItemsHTML = function(root, html, append = false) {
var container = root.find(SELECTORS.COURSES_LIST);
if (append) {
Templates.appendNodeContents(container, html, '');
} else {
Templates.replaceNodeContents(container, html, '');
}
};
/**
* Return the offset value for fetching courses.
*
* @param {object} root The rool element.
* @return {Number}
*/
var getOffset = function(root) {
return parseInt(root.attr('data-offset'), 10);
};
/**
* Set the offset value for fetching courses.
*
* @param {object} root The rool element.
* @param {Number} offset Offset value.
*/
var setOffset = function(root, offset) {
root.attr('data-offset', offset);
};
/**
* Return the limit value for fetching courses.
*
* @param {object} root The rool element.
* @return {Number}
*/
var getLimit = function(root) {
return parseInt(root.attr('data-limit'), 10);
};
/**
* Return the days offset value for fetching events.
*
* @param {object} root The rool element.
* @return {Number}
*/
var getDaysOffset = function(root) {
return parseInt(root.attr('data-days-offset'), 10);
};
/**
* Return the days limit value for fetching events. The days
* limit is optional so undefined will be returned if it isn't
* set.
*
* @param {object} root The rool element.
* @return {int|undefined}
*/
var getDaysLimit = function(root) {
var daysLimit = root.attr('data-days-limit');
return daysLimit != undefined ? parseInt(daysLimit, 10) : undefined;
};
/**
* Return the timestamp for the user's midnight.
*
* @param {object} root The rool element.
* @return {Number}
*/
var getMidnight = function(root) {
return parseInt(root.attr('data-midnight'), 10);
};
/**
* Return the start time for fetching events. This is calculated
* based on the user's midnight value so that timezones are
* preserved.
*
* @param {object} root The rool element.
* @return {Number}
*/
var getStartTime = function(root) {
var midnight = getMidnight(root);
var daysOffset = getDaysOffset(root);
return midnight + (daysOffset * SECONDS_IN_DAY);
};
/**
* Return the end time for fetching events. This is calculated
* based on the user's midnight value so that timezones are
* preserved, unless filtering by overdue, where the current UNIX timestamp is used.
*
* @param {object} root The rool element.
* @return {Number}
*/
var getEndTime = function(root) {
let endTime = null;
if (root.attr('data-filter-overdue')) {
// If filtering by overdue, end time will be the current timestamp in seconds.
endTime = Math.floor(Date.now() / 1000);
} else {
const midnight = getMidnight(root);
const daysLimit = getDaysLimit(root);
if (daysLimit != undefined) {
endTime = midnight + (daysLimit * SECONDS_IN_DAY);
}
}
return endTime;
};
/**
* Get a list of events for the given course ids. Returns a promise that will
* be resolved with the events.
*
* @param {array} courseIds The list of course ids to fetch events for.
* @param {Number} startTime Timestamp to fetch events from.
* @param {Number} limit Limit to the number of events (this applies per course, not total)
* @param {Number} endTime Timestamp to fetch events to.
* @param {string|undefined} searchValue Search value
* @return {object} jQuery promise.
*/
var getEventsForCourseIds = function(courseIds, startTime, limit, endTime, searchValue) {
var args = {
courseids: courseIds,
starttime: startTime,
limit: limit
};
if (endTime) {
args.endtime = endTime;
}
if (searchValue) {
args.searchvalue = searchValue;
}
return EventsRepository.queryByCourses(args);
};
/**
* Get the last time the events were reloaded.
*
* @param {object} root The rool element.
* @return {Number}
*/
var getEventReloadTime = function(root) {
return root.data('last-event-load-time');
};
/**
* Set the last time the events were reloaded.
*
* @param {object} root The rool element.
* @param {Number} time Timestamp in milliseconds.
*/
var setEventReloadTime = function(root, time) {
root.data('last-event-load-time', time);
};
/**
* Check if events have begun reloading since the given
* time.
*
* @param {object} root The rool element.
* @param {Number} time Timestamp in milliseconds.
* @return {bool}
*/
var hasReloadedEventsSince = function(root, time) {
return getEventReloadTime(root) > time;
};
/**
* Send a request to the server to load the events for the courses.
*
* @param {array} courses List of course objects.
* @param {Number} startTime Timestamp to load events after.
* @param {int|undefined} endTime Timestamp to load events up until.
* @param {string|undefined} searchValue Search value
* @return {object} jQuery promise resolved with the events.
*/
var loadEventsForCourses = function(courses, startTime, endTime, searchValue) {
var courseIds = courses.map(function(course) {
return course.id;
});
return getEventsForCourseIds(courseIds, startTime, COURSE_EVENT_LIMIT + 1, endTime, searchValue);
};
/**
* Render the courses in the DOM once the server has returned the courses.
*
* @param {array} courses List of course objects.
* @param {object} root The root element
* @param {Number} midnight The midnight timestamp in the user's timezone.
* @param {Number} daysOffset Number of days from today to offset the events.
* @param {Number} daysLimit Number of days from today to limit the events to.
* @param {boolean} append Whether new content should be appended instead of replaced (eg "show more courses").
* @return {object} jQuery promise resolved after rendering is complete.
*/
var updateDisplayFromCourses = function(courses, root, midnight, daysOffset, daysLimit, append) {
// Render the courses template.
return Templates.render(TEMPLATES.COURSE_ITEMS, {
courses: courses,
midnight: midnight,
hasdaysoffset: true,
hasdayslimit: daysLimit != undefined,
daysoffset: daysOffset,
dayslimit: daysLimit,
nodayslimit: daysLimit == undefined,
courseview: true,
hascourses: true
}).then(function(html) {
hideLoadingPlaceholder(root);
if (html) {
// Template rendering is complete and we have the HTML so we can
// add it to the DOM.
renderCourseItemsHTML(root, html, append);
}
return html;
})
.then(function(html) {
if (courses.length < COURSE_LIMIT) {
// We know there aren't any more courses because we got back less
// than we asked for so hide the button to request more.
hideMoreCoursesButton(root);
} else {
// Make sure the button is visible if there are more courses to load.
showMoreCoursesButton(root);
}
return html;
})
.catch(function() {
hideLoadingPlaceholder(root);
});
};
/**
* Find all of the visible course blocks and initialise the event
* list module to being loading the events for the course block.
*
* @param {object} root The root element for the timeline courses view.
* @param {boolean} append Whether content should be appended instead of replaced (eg "show more courses"). False by default.
* @return {object} jQuery promise resolved with courses and events.
*/
var loadMoreCourses = function(root, append = false) {
const pendingPromise = new Pending('block/timeline:load-more-courses');
var offset = getOffset(root);
var limit = getLimit(root);
const startTime = getStartTime(root);
const endTime = getEndTime(root);
const searchValue = root.closest(SELECTORS.TIMELINE_BLOCK).find(SELECTORS.TIMELINE_SEARCH).val();
// Start loading the next set of courses.
// Fetch up to limit number of courses with at least one action event in the time filtering specified.
// Courses without events will also be fetched, but hidden in case they have events in other timespans.
return CourseRepository.getEnrolledCoursesWithEventsByTimelineClassification(
COURSE_CLASSIFICATION,
limit,
offset,
COURSE_SORT,
searchValue,
startTime,
endTime
).then(function(result) {
var startEventLoadingTime = Date.now();
var courses = result.courses;
var nextOffset = result.nextoffset;
var daysOffset = getDaysOffset(root);
var daysLimit = getDaysLimit(root);
var midnight = getMidnight(root);
const moreCoursesAvailable = result.morecoursesavailable;
// Record the next offset if we want to request more courses.
setOffset(root, nextOffset);
// Load the events for these courses.
var eventsPromise = loadEventsForCourses(courses, startTime, endTime, searchValue);
// Render the courses in the DOM.
var renderPromise = updateDisplayFromCourses(courses, root, midnight, daysOffset, daysLimit, append);
return $.when(eventsPromise, renderPromise)
.then(function(eventsByCourse) {
if (hasReloadedEventsSince(root, startEventLoadingTime)) {
// All of the events are being reloaded so ignore our results.
return eventsByCourse;
}
if (courses.length > 0) {
// Render the events in the correct course event list.
courses.forEach(function(course) {
const courseId = course.id;
const containerSelector = '[data-region="course-events-container"][data-course-id="' + courseId + '"]';
const courseEventsContainer = root.find(containerSelector);
const eventListRoot = courseEventsContainer.find(EventList.rootSelector);
EventList.init(eventListRoot, additionalConfig);
});
if (!moreCoursesAvailable) {
// If no more courses with events matching the current filtering exist, hide the more courses button.
hideMoreCoursesButton(root);
} else {
// If more courses exist with events matching the current filtering, show the more courses button.
showMoreCoursesButton(root);
}
} else {
// No more courses to load, hide the more courses button.
hideMoreCoursesButton(root);
// A zero offset means this was not loading "more courses", so we need to display the no results message.
if (offset == 0) {
showNoCoursesWithEventsMessage(root);
}
}
return eventsByCourse;
});
}).then(() => {
return pendingPromise.resolve();
}).catch(Notification.exception);
};
/**
* Add event listeners to load more courses for the courses view.
*
* @param {object} root The root element for the timeline courses view.
*/
var registerEventListeners = function(root) {
CustomEvents.define(root, [CustomEvents.events.activate]);
// Show more courses and load their events when the user clicks the "more courses" button.
root.on(CustomEvents.events.activate, SELECTORS.MORE_COURSES_BUTTON, function(e, data) {
enableMoreCoursesButtonLoading(root);
loadMoreCourses(root, true)
.then(function() {
disableMoreCoursesButtonLoading(root);
return;
})
.catch(function() {
disableMoreCoursesButtonLoading(root);
});
if (data) {
data.originalEvent.preventDefault();
data.originalEvent.stopPropagation();
}
e.stopPropagation();
});
};
/**
* Initialise the timeline courses view. Begin loading the events
* if this view is active. Add the relevant event listeners.
*
* This function should only be called once per page load because it
* is adding event listeners to the page.
*
* @param {object} root The root element for the timeline courses view.
*/
var init = function(root) {
root = $(root);
// Only need to handle course loading if the user is actively enrolled in a course.
if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
setEventReloadTime(root, Date.now());
if (root.hasClass('active')) {
// Only load if this is active otherwise it will be lazy loaded later.
loadMoreCourses(root);
root.attr('data-seen', true);
}
registerEventListeners(root);
}
};
/**
* Reset the element back to it's initial state. Begin loading the events again
* if this view is active.
*
* @param {object} root The root element for the timeline courses view.
*/
var reset = function(root) {
setOffset(root, 0);
showLoadingPlaceholder(root);
hideNoCoursesWithEventsMessage(root);
root.removeAttr('data-seen');
if (root.hasClass('active')) {
shown(root);
}
};
/**
* Begin loading the events unless we know there are no actively enrolled courses.
*
* @param {object} root The root element for the timeline courses view.
*/
var shown = function(root) {
if (!root.attr('data-seen') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
loadMoreCourses(root);
root.attr('data-seen', true);
}
};
return {
init: init,
reset: reset,
shown: shown
};
});
+122
View File
@@ -0,0 +1,122 @@
// 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/>.
/**
* Manage the timeline dates view for the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'block_timeline/event_list',
'core/pubsub',
'core/paged_content_events'
],
function(
$,
EventList,
PubSub,
PagedContentEvents
) {
var SELECTORS = {
EVENT_LIST_CONTAINER: '[data-region="event-list-container"]',
NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]',
};
/**
* Setup the listeners for the timeline block
*
* @param {string} root view dates container
* @param {string} namespace The namespace for the paged content
*/
var registerEventListeners = function(root, namespace) {
var event = namespace + PagedContentEvents.SET_ITEMS_PER_PAGE_LIMIT;
PubSub.subscribe(event, function(limit) {
$(root).data('limit', limit);
});
};
/**
* Initialise the event list and being loading the events.
*
* @param {object} root The root element for the timeline dates view.
*/
var load = function(root) {
if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
var eventListContainer = root.find(SELECTORS.EVENT_LIST_CONTAINER);
var namespace = $(eventListContainer).attr('id') + "user_block_timeline" + Math.random();
registerEventListeners(root, namespace);
var config = {
persistentLimitKey: "block_timeline_user_limit_preference",
eventNamespace: namespace
};
EventList.init(eventListContainer, config);
}
};
/**
* Initialise the timeline dates view. Begin loading the events
* if this view is active.
*
* @param {object} root The root element for the timeline courses view.
*/
var init = function(root) {
root = $(root);
// Only need to handle events loading if the user is actively enrolled in a course and this view is active.
if (root.hasClass('active') && !root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
load(root);
root.attr('data-seen', true);
}
};
/**
* Reset the view back to it's initial state. If this view is active then
* beging loading the events.
*
* @param {object} root The root element for the timeline courses view.
*/
var reset = function(root) {
root.removeAttr('data-seen');
if (root.hasClass('active')) {
load(root);
root.attr('data-seen', true);
}
};
/**
* Load the events if this is the first time the view is displayed.
*
* @param {object} root The root element for the timeline courses view.
*/
var shown = function(root) {
if (!root.attr('data-seen')) {
load(root);
root.attr('data-seen', true);
}
};
return {
init: init,
reset: reset,
shown: shown
};
});
+189
View File
@@ -0,0 +1,189 @@
// 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/>.
/**
* Manage the timeline view navigation for the timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as CustomEvents from 'core/custom_interaction_events';
import * as View from 'block_timeline/view';
import * as Notification from 'core/notification';
import * as Utils from 'core/utils';
import * as UserRepository from 'core_user/repository';
const SELECTORS = {
TIMELINE_DAY_FILTER: '[data-region="day-filter"]',
TIMELINE_DAY_FILTER_OPTION: '[data-from]',
TIMELINE_VIEW_SELECTOR: '[data-region="view-selector"]',
DATA_DAYS_OFFSET: '[data-days-offset]',
DATA_DAYS_LIMIT: '[data-days-limit]',
TIMELINE_SEARCH_INPUT: '[data-action="search"]',
TIMELINE_SEARCH_CLEAR_ICON: '[data-action="clearsearch"]',
NO_COURSES_EMPTY_MESSAGE: '[data-region="no-courses-empty-message"]',
};
/**
* Event listener for the day selector ("Next 7 days", "Next 30 days", etc).
*
* @param {object} root The root element for the timeline block
* @param {object} timelineViewRoot The root element for the timeline view
*/
const registerTimelineDaySelector = function(root, timelineViewRoot) {
const timelineDaySelectorContainer = root.find(SELECTORS.TIMELINE_DAY_FILTER);
CustomEvents.define(timelineDaySelectorContainer, [CustomEvents.events.activate]);
timelineDaySelectorContainer.on(
CustomEvents.events.activate,
SELECTORS.TIMELINE_DAY_FILTER_OPTION,
function(e, data) {
// Update the user preference
var filtername = $(e.currentTarget).data('filtername');
var type = 'block_timeline_user_filter_preference';
UserRepository.setUserPreference(type, filtername)
.catch(Notification.exception);
var option = $(e.target).closest(SELECTORS.TIMELINE_DAY_FILTER_OPTION);
if (option.attr('aria-current') == 'true') {
// If it's already active then we don't need to do anything.
return;
}
var daysOffset = option.attr('data-from');
var daysLimit = option.attr('data-to');
var elementsWithDaysOffset = root.find(SELECTORS.DATA_DAYS_OFFSET);
elementsWithDaysOffset.attr('data-days-offset', daysOffset);
if (daysLimit != undefined) {
elementsWithDaysOffset.attr('data-days-limit', daysLimit);
} else {
elementsWithDaysOffset.removeAttr('data-days-limit');
}
if (option.attr('data-filtername') === 'overdue') {
elementsWithDaysOffset.attr('data-filter-overdue', true);
} else {
elementsWithDaysOffset.removeAttr('data-filter-overdue');
}
// Reset the views to reinitialise the event lists now that we've
// updated the day limits.
View.reset(timelineViewRoot);
data.originalEvent.preventDefault();
}
);
};
/**
* Event listener for the "sort" button in the timeline navigation that allows for
* changing between the timeline dates and courses views.
*
* On a view change we tell the timeline view module that the view has been shown
* so that it can handle how to display the appropriate view.
*
* @param {object} root The root element for the timeline block
* @param {object} timelineViewRoot The root element for the timeline view
*/
const registerViewSelector = function(root, timelineViewRoot) {
const viewSelector = root.find(SELECTORS.TIMELINE_VIEW_SELECTOR);
// Listen for when the user changes tab so that we can show the first set of courses
// and load their events when they request the sort by courses view for the first time.
viewSelector.on('shown shown.bs.tab', function(e) {
View.shown(timelineViewRoot);
$(e.target).removeClass('active');
});
// Event selector for user_sort
CustomEvents.define(viewSelector, [CustomEvents.events.activate]);
viewSelector.on(CustomEvents.events.activate, "[data-toggle='tab']", function(e) {
var filtername = $(e.currentTarget).data('filtername');
var type = 'block_timeline_user_sort_preference';
UserRepository.setUserPreference(type, filtername)
.catch(Notification.exception);
});
};
/**
* Event listener for the "search" input field in the timeline navigation that allows for
* searching the activity name, course name and activity type.
*
* @param {object} root The root element for the timeline block
* @param {object} timelineViewRoot The root element for the timeline view
*/
const registerSearch = (root, timelineViewRoot) => {
const searchInput = root.find(SELECTORS.TIMELINE_SEARCH_INPUT);
const clearSearchIcon = root.find(SELECTORS.TIMELINE_SEARCH_CLEAR_ICON);
searchInput.on('input', Utils.debounce(() => {
if (searchInput.val() !== '') {
activeSearchState(clearSearchIcon, timelineViewRoot);
} else {
clearSearchState(clearSearchIcon, timelineViewRoot);
}
}, 1000));
clearSearchIcon.on('click', () => {
searchInput.val('');
clearSearchState(clearSearchIcon, timelineViewRoot);
searchInput.focus();
});
};
/**
* Show the clear search icon.
*
* @param {object} clearSearchIcon Clear search icon element.
* @param {object} timelineViewRoot The root element for the timeline view
*/
const activeSearchState = (clearSearchIcon, timelineViewRoot) => {
clearSearchIcon.removeClass('d-none');
View.reset(timelineViewRoot);
};
/**
* Hide the clear search icon.
*
* @param {object} clearSearchIcon Clear search icon element.
* @param {object} timelineViewRoot The root element for the timeline view
*/
const clearSearchState = (clearSearchIcon, timelineViewRoot) => {
clearSearchIcon.addClass('d-none');
View.reset(timelineViewRoot);
};
/**
* Initialise the timeline view navigation by adding event listeners to
* the navigation elements.
*
* @param {jQuery|HTMLElement} root The root element for the timeline block
* @param {object} timelineViewRoot The root element for the timeline view
*/
export const init = function(root, timelineViewRoot) {
root = $(root);
registerViewSelector(root, timelineViewRoot);
// Only need to handle filtering if the user is actively enrolled in a course.
if (!root.find(SELECTORS.NO_COURSES_EMPTY_MESSAGE).length) {
registerTimelineDaySelector(root, timelineViewRoot);
registerSearch(root, timelineViewRoot);
}
};
+76
View File
@@ -0,0 +1,76 @@
<?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/>.
/**
* Contains the class for the Timeline block.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Timeline block class.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class block_timeline extends block_base {
/**
* Init.
*/
public function init() {
$this->title = get_string('pluginname', 'block_timeline');
}
/**
* Returns the contents.
*
* @return stdClass contents of block
*/
public function get_content() {
if (isset($this->content)) {
return $this->content;
}
$sort = get_user_preferences('block_timeline_user_sort_preference');
$filter = get_user_preferences('block_timeline_user_filter_preference');
$limit = get_user_preferences('block_timeline_user_limit_preference');
$renderable = new \block_timeline\output\main($sort, $filter, $limit);
$renderer = $this->page->get_renderer('block_timeline');
$this->content = (object) [
'text' => $renderer->render($renderable),
'footer' => ''
];
return $this->content;
}
/**
* Locations where block can be displayed.
*
* @return array
*/
public function applicable_formats() {
return array('my' => true);
}
}
+191
View File
@@ -0,0 +1,191 @@
<?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/>.
/**
* Class containing data for timeline block.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_timeline\output;
defined('MOODLE_INTERNAL') || die();
use renderable;
use renderer_base;
use templatable;
use core_course\external\course_summary_exporter;
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/blocks/timeline/lib.php');
require_once($CFG->libdir . '/completionlib.php');
/**
* Class containing data for timeline block.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class main implements renderable, templatable {
/** Number of courses to load per page */
const COURSES_PER_PAGE = 2;
/**
* @var string The current filter preference
*/
public $filter;
/**
* @var string The current sort/order preference
*/
public $order;
/**
* @var string The current limit preference
*/
public $limit;
/** @var int Number of timeline instances displayed. */
protected static $timelineinstances = 0;
/** @var int This timeline instance's ID. */
protected $timelineinstanceid = 0;
/**
* main constructor.
*
* @param string $order Constant sort value from ../timeline/lib.php
* @param string $filter Constant filter value from ../timeline/lib.php
* @param string $limit Constant limit value from ../timeline/lib.php
*/
public function __construct($order, $filter, $limit) {
$this->order = $order ? $order : BLOCK_TIMELINE_SORT_BY_DATES;
$this->filter = $filter ? $filter : BLOCK_TIMELINE_FILTER_BY_7_DAYS;
$this->limit = $limit ? $limit : BLOCK_TIMELINE_ACTIVITIES_LIMIT_DEFAULT;
// Increment the timeline instances count on initialisation.
self::$timelineinstances++;
// Assign this instance an ID based on the latest timeline instances count.
$this->timelineinstanceid = self::$timelineinstances;
}
/**
* Test the available filters with the current user preference and return an array with
* bool flags corresponding to which is active
*
* @return array
*/
protected function get_filters_as_booleans() {
$filters = [
BLOCK_TIMELINE_FILTER_BY_NONE => false,
BLOCK_TIMELINE_FILTER_BY_OVERDUE => false,
BLOCK_TIMELINE_FILTER_BY_7_DAYS => false,
BLOCK_TIMELINE_FILTER_BY_30_DAYS => false,
BLOCK_TIMELINE_FILTER_BY_3_MONTHS => false,
BLOCK_TIMELINE_FILTER_BY_6_MONTHS => false
];
// Set the selected filter to true.
$filters[$this->filter] = true;
return $filters;
}
/**
* Get the offset/limit values corresponding to $this->filter
* which are used to send through to the context as default values
*
* @return array
*/
private function get_filter_offsets() {
$limit = '';
if (in_array($this->filter, [BLOCK_TIMELINE_FILTER_BY_NONE, BLOCK_TIMELINE_FILTER_BY_OVERDUE])) {
$offset = -14;
if ($this->filter == BLOCK_TIMELINE_FILTER_BY_OVERDUE) {
$limit = 1;
}
} else {
$offset = 0;
$limit = 7;
switch($this->filter) {
case BLOCK_TIMELINE_FILTER_BY_30_DAYS:
$limit = 30;
break;
case BLOCK_TIMELINE_FILTER_BY_3_MONTHS:
$limit = 90;
break;
case BLOCK_TIMELINE_FILTER_BY_6_MONTHS:
$limit = 180;
break;
}
}
return [
'daysoffset' => $offset,
'dayslimit' => $limit
];
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output) {
$nocoursesurl = $output->image_url('courses', 'block_timeline')->out();
$noeventsurl = $output->image_url('activities', 'block_timeline')->out();
$requiredproperties = course_summary_exporter::define_properties();
$fields = join(',', array_keys($requiredproperties));
$courses = course_get_enrolled_courses_for_logged_in_user(0, 0, null, $fields);
list($inprogresscourses, $processedcount) = course_filter_courses_by_timeline_classification(
$courses,
COURSE_TIMELINE_INPROGRESS,
self::COURSES_PER_PAGE
);
$formattedcourses = array_map(function($course) use ($output) {
\context_helper::preload_from_record($course);
$context = \context_course::instance($course->id);
$exporter = new course_summary_exporter($course, ['context' => $context]);
return $exporter->export($output);
}, $inprogresscourses);
$filters = $this->get_filters_as_booleans();
$offsets = $this->get_filter_offsets();
$contextvariables = [
'timelineinstanceid' => $this->timelineinstanceid,
'midnight' => usergetmidnight(time()),
'coursepages' => [$formattedcourses],
'urls' => [
'nocourses' => $nocoursesurl,
'noevents' => $noeventsurl
],
'sorttimelinedates' => $this->order == BLOCK_TIMELINE_SORT_BY_DATES,
'sorttimelinecourses' => $this->order == BLOCK_TIMELINE_SORT_BY_COURSES,
'selectedfilter' => $this->filter,
'hasdaysoffset' => true,
'hasdayslimit' => $offsets['dayslimit'] !== '' ,
'nodayslimit' => $offsets['dayslimit'] === '' ,
'limit' => $this->limit,
'hascourses' => !empty($formattedcourses),
];
return array_merge($contextvariables, $filters, $offsets);
}
}
@@ -0,0 +1,48 @@
<?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/>.
/**
* Timeline block rendrer.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_timeline\output;
defined('MOODLE_INTERNAL') || die;
use plugin_renderer_base;
use renderable;
/**
* Timeline block renderer.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Return the main content for the block timeline.
*
* @param main $main The main renderable
* @return string HTML string
*/
public function render_main(main $main) {
return $this->render_from_template('block_timeline/main', $main->export_for_template($this));
}
}
@@ -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/>.
/**
* Privacy Subsystem implementation for block_timeline.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_timeline\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
/**
* Privacy Subsystem for block_timeline.
*
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\user_preference_provider {
/**
* Returns meta-data information about the myoverview block.
*
* @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_metadata(collection $collection): collection {
$collection->add_user_preference('block_timeline_user_sort_preference', 'privacy:metadata:timelinesortpreference');
$collection->add_user_preference('block_timeline_user_filter_preference', 'privacy:metadata:timelinefilterpreference');
$collection->add_user_preference('block_timeline_user_limit_preference', 'privacy:metadata:timelinelimitpreference');
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_timeline_user_sort_preference', null, $userid);
if (isset($preference)) {
\core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_sort_preference',
get_string($preference, 'block_timeline'),
get_string('privacy:metadata:timelinesortpreference', 'block_timeline')
);
}
$preference = get_user_preferences('block_timeline_user_filter_preference', null, $userid);
if (isset($preference)) {
\core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_filter_preference',
get_string($preference, 'block_timeline'),
get_string('privacy:metadata:timelinefilterpreference', 'block_timeline')
);
}
$preference = get_user_preferences('block_timeline_user_limit_preference', null, $userid);
if (isset($preference)) {
\core_privacy\local\request\writer::export_user_preference('block_timeline', 'block_timeline_user_limit_preference',
$preference,
get_string('privacy:metadata:timelinelimitpreference', 'block_timeline')
);
}
}
}
+38
View File
@@ -0,0 +1,38 @@
<?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/>.
/**
* Capabilities for the timeline block.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'block/timeline:myaddinstance' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array(
'user' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/my:manageblocks'
)
);
+108
View File
@@ -0,0 +1,108 @@
<?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/>.
/**
* Timeline block installation.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Add the timeline block to the dashboard for all users by default
* when it is installed.
*/
function xmldb_block_timeline_install() {
global $DB;
if ($DB->count_records('block_instances') < 1) {
// Only add the timeline block if it's being installed on an existing site.
// For new sites it will be added by blocks_add_default_system_blocks().
return;
}
if ($defaultmypage = $DB->get_record('my_pages', array('userid' => null, 'name' => '__default', 'private' => 1))) {
$subpagepattern = $defaultmypage->id;
} else {
$subpagepattern = null;
}
$page = new moodle_page();
$systemcontext = context_system::instance();
$page->set_context($systemcontext);
// Add the block to the default /my.
$page->blocks->add_region(BLOCK_POS_RIGHT);
$page->blocks->add_block('timeline', BLOCK_POS_RIGHT, 0, false, 'my-index', $subpagepattern);
// Now we need to find all users that have viewed their dashboard because it'll have
// made duplicates of the default block_instances for them so they won't see the new
// timeline block without the admin resetting all of the dashboards.
//
// Instead we'll just add the timeline block to their dashboards here. We will only
// add the timeline block if they still have the myoverview block.
$sql = "SELECT parentcontextid, subpagepattern
FROM {block_instances}
WHERE pagetypepattern = 'my-index'
AND blockname = 'myoverview'
AND parentcontextid != ?";
$params = [$systemcontext->id];
$existingrecords = $DB->get_recordset_sql($sql, $params);
$blockinstances = [];
$seencontexts = [];
$now = time();
foreach ($existingrecords as $existingrecord) {
$parentcontextid = $existingrecord->parentcontextid;
if (isset($seencontexts[$parentcontextid])) {
// If we've seen this context already then skip it because we don't want
// to add duplicate timeline blocks to the same context. This happens
// if something funny is going on with the subpagepattern.
continue;
} else {
$seencontexts[$parentcontextid] = true;
}
$blockinstances[] = [
'blockname' => 'timeline',
'parentcontextid' => $parentcontextid,
'showinsubcontexts' => false,
'pagetypepattern' => 'my-index',
'subpagepattern' => $existingrecord->subpagepattern,
'defaultregion' => BLOCK_POS_RIGHT,
'defaultweight' => 0,
'configdata' => '',
'timecreated' => $now,
'timemodified' => $now,
];
if (count($blockinstances) >= 1000) {
// Insert after every 1000 records so that the memory usage doesn't
// get out of control.
$DB->insert_records('block_instances', $blockinstances);
$blockinstances = [];
}
}
$existingrecords->close();
if (!empty($blockinstances)) {
// Insert what ever is left over.
$DB->insert_records('block_instances', $blockinstances);
}
}
+59
View File
@@ -0,0 +1,59 @@
<?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/>.
/**
* This file keeps track of upgrades to the timeline block
*
* Sometimes, changes between versions involve alterations to database structures
* and other major things that may break installations.
*
* The upgrade function in this file will attempt to perform all the necessary
* actions to upgrade your older installation to the current version.
*
* If there's something it cannot do itself, it will tell you what you need to do.
*
* The commands in here will all be database-neutral, using the methods of
* database_manager class
*
* Please do not forget to use upgrade_set_timeout()
* before any action that may take longer time to finish.
*
* @package block_timeline
* @copyright 2022 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
*
* Upgrade the timeline block
* @param int $oldversion
* @param object $block
*/
function xmldb_block_timeline_upgrade($oldversion, $block) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
@@ -0,0 +1,50 @@
<?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/>.
/**
* Lang strings for the timeline block.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['ariadayfilter'] = 'Filter timeline by date';
$string['ariadayfilteroption'] = '{$a} filter option';
$string['ariaeventlistitem'] = '{$a->name} activity in {$a->course} is due on {$a->date}';
$string['ariaviewselector'] = 'Sort timeline items';
$string['ariaviewselectoroption'] = '{$a} sort option';
$string['duedate'] = 'Due date';
$string['moreactivities'] = 'Show more activities';
$string['morecourses'] = 'Show more courses';
$string['timeline:myaddinstance'] = 'Add a new timeline block to Dashboard';
$string['nocoursesinprogress'] = 'No in-progress courses';
$string['noevents'] = 'No activities require action';
$string['next30days'] = 'Next 30 days';
$string['next7days'] = 'Next 7 days';
$string['next3months'] = 'Next 3 months';
$string['next6months'] = 'Next 6 months';
$string['overdue'] = 'Overdue';
$string['all'] = 'All';
$string['pluginname'] = 'Timeline';
$string['searchevents'] = 'Search by activity type or name';
$string['sortbycourses'] = 'Sort by courses';
$string['sortbydates'] = 'Sort by dates';
$string['timeline'] = 'Timeline';
$string['viewcourse'] = 'View course';
$string['privacy:metadata:timelinesortpreference'] = 'The user sort preference for the timeline block.';
$string['privacy:metadata:timelinefilterpreference'] = 'The user day filter preference for the timeline block.';
$string['privacy:metadata:timelinelimitpreference'] = 'The user page limit preference for the timeline block.';
+82
View File
@@ -0,0 +1,82 @@
<?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 timeline
*
* @package block_timeline
* @copyright 2018 Peter Dias
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Define constants to store the SORT user preference
*/
define('BLOCK_TIMELINE_SORT_BY_DATES', 'sortbydates');
define('BLOCK_TIMELINE_SORT_BY_COURSES', 'sortbycourses');
/**
* Define constants to store the FILTER user preference
*/
define('BLOCK_TIMELINE_FILTER_BY_NONE', 'all');
define('BLOCK_TIMELINE_FILTER_BY_OVERDUE', 'overdue');
define('BLOCK_TIMELINE_FILTER_BY_7_DAYS', 'next7days');
define('BLOCK_TIMELINE_FILTER_BY_30_DAYS', 'next30days');
define('BLOCK_TIMELINE_FILTER_BY_3_MONTHS', 'next3months');
define('BLOCK_TIMELINE_FILTER_BY_6_MONTHS', 'next6months');
define('BLOCK_TIMELINE_ACTIVITIES_LIMIT_DEFAULT', 5);
/**
* Returns the name of the user preferences as well as the details this plugin uses.
*
* @uses core_user::is_current_user
*
* @return array[]
*/
function block_timeline_user_preferences(): array {
$preferences['block_timeline_user_sort_preference'] = array(
'null' => NULL_NOT_ALLOWED,
'default' => BLOCK_TIMELINE_SORT_BY_DATES,
'type' => PARAM_ALPHA,
'choices' => array(BLOCK_TIMELINE_SORT_BY_DATES, BLOCK_TIMELINE_SORT_BY_COURSES),
'permissioncallback' => [core_user::class, 'is_current_user'],
);
$preferences['block_timeline_user_filter_preference'] = array(
'null' => NULL_NOT_ALLOWED,
'default' => BLOCK_TIMELINE_FILTER_BY_30_DAYS,
'type' => PARAM_ALPHANUM,
'choices' => array(
BLOCK_TIMELINE_FILTER_BY_NONE,
BLOCK_TIMELINE_FILTER_BY_OVERDUE,
BLOCK_TIMELINE_FILTER_BY_7_DAYS,
BLOCK_TIMELINE_FILTER_BY_30_DAYS,
BLOCK_TIMELINE_FILTER_BY_3_MONTHS,
BLOCK_TIMELINE_FILTER_BY_6_MONTHS
),
'permissioncallback' => [core_user::class, 'is_current_user'],
);
$preferences['block_timeline_user_limit_preference'] = array(
'null' => NULL_NOT_ALLOWED,
'default' => BLOCK_TIMELINE_ACTIVITIES_LIMIT_DEFAULT,
'type' => PARAM_INT,
'permissioncallback' => [core_user::class, 'is_current_user'],
);
return $preferences;
}
+41
View File
@@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="157 -1509 148 125" preserveAspectRatio="xMinYMid meet">
<defs>
<style>
.cls-1 {
clip-path: url(#clip-Activities);
}
.cls-2 {
fill: #eee;
}
.cls-3 {
fill: #c4c8cc;
}
.cls-4 {
fill: #fff;
}
</style>
<clipPath id="clip-Activities">
<rect x="157" y="-1509" width="148" height="125"/>
</clipPath>
</defs>
<g id="Activities" class="cls-1">
<g id="Group_42" data-name="Group 42" transform="translate(-268 -1985)">
<ellipse id="Ellipse_37" data-name="Ellipse 37" class="cls-2" cx="74" cy="14.785" rx="74" ry="14.785" transform="translate(425 571.43)"/>
<rect id="Rectangle_80" data-name="Rectangle 80" class="cls-3" width="94.182" height="110.215" transform="translate(451.909 476)"/>
<g id="Group_41" data-name="Group 41" transform="translate(467.043 493)">
<rect id="Rectangle_81" data-name="Rectangle 81" class="cls-4" width="44.456" height="5.625" transform="translate(21.16 0.549)"/>
<rect id="Rectangle_82" data-name="Rectangle 82" class="cls-4" width="33.342" height="5.625" transform="translate(21.16 11.652)"/>
<rect id="Rectangle_83" data-name="Rectangle 83" class="cls-4" width="44.456" height="5.625" transform="translate(21.16 30.772)"/>
<rect id="Rectangle_84" data-name="Rectangle 84" class="cls-4" width="33.342" height="5.625" transform="translate(21.16 41.875)"/>
<rect id="Rectangle_85" data-name="Rectangle 85" class="cls-4" width="44.456" height="5.625" transform="translate(21.16 61.291)"/>
<rect id="Rectangle_86" data-name="Rectangle 86" class="cls-4" width="33.342" height="5.625" transform="translate(21.16 72.393)"/>
<ellipse id="Ellipse_38" data-name="Ellipse 38" class="cls-4" cx="7.007" cy="7" rx="7.007" ry="7" transform="translate(0 0)"/>
<ellipse id="Ellipse_39" data-name="Ellipse 39" class="cls-4" cx="7.007" cy="7" rx="7.007" ry="7" transform="translate(0 31)"/>
<ellipse id="Ellipse_40" data-name="Ellipse 40" class="cls-4" cx="7.007" cy="7" rx="7.007" ry="7" transform="translate(0 61)"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+52
View File
@@ -0,0 +1,52 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="157 -1305 148 125" preserveAspectRatio="xMinYMid meet">
<defs>
<style>
.cls-1 {
clip-path: url(#clip-Courses);
}
.cls-2 {
fill: #eee;
}
.cls-3 {
fill: #c4c8cc;
}
.cls-4 {
fill: #fff;
}
</style>
<clipPath id="clip-Courses">
<rect x="157" y="-1305" width="148" height="125"/>
</clipPath>
</defs>
<g id="Courses" class="cls-1">
<g id="Group_44" data-name="Group 44" transform="translate(-268 -1781)">
<ellipse id="Ellipse_41" data-name="Ellipse 41" class="cls-2" cx="74" cy="14.785" rx="74" ry="14.785" transform="translate(425 571.43)"/>
<rect id="Rectangle_87" data-name="Rectangle 87" class="cls-3" width="95.097" height="110.215" transform="translate(451.909 476)"/>
<g id="Group_43" data-name="Group 43" transform="translate(464.04 494)">
<rect id="Rectangle_88" data-name="Rectangle 88" class="cls-4" width="31.043" height="34" transform="translate(0)"/>
<rect id="Rectangle_89" data-name="Rectangle 89" class="cls-4" width="31.043" height="34" transform="translate(0 42)"/>
<rect id="Rectangle_90" data-name="Rectangle 90" class="cls-4" width="31.067" height="34" transform="translate(39.005)"/>
<rect id="Rectangle_91" data-name="Rectangle 91" class="cls-4" width="31.067" height="34" transform="translate(39.005 42)"/>
<rect id="Rectangle_92" data-name="Rectangle 92" class="cls-3" width="23.023" height="3.18" transform="translate(3.081 16.549)"/>
<rect id="Rectangle_93" data-name="Rectangle 93" class="cls-3" width="23.023" height="3.18" transform="translate(3.081 58.549)"/>
<rect id="Rectangle_94" data-name="Rectangle 94" class="cls-3" width="23.023" height="3.18" transform="translate(43.122 16.549)"/>
<rect id="Rectangle_95" data-name="Rectangle 95" class="cls-3" width="23.023" height="3.18" transform="translate(43.122 58.549)"/>
<rect id="Rectangle_96" data-name="Rectangle 96" class="cls-3" width="14.014" height="3.18" transform="translate(3.081 21.825)"/>
<rect id="Rectangle_97" data-name="Rectangle 97" class="cls-3" width="18.845" height="3.18" transform="translate(3.081 26.825)"/>
<rect id="Rectangle_98" data-name="Rectangle 98" class="cls-3" width="14.014" height="3.18" transform="translate(3.081 63.825)"/>
<rect id="Rectangle_99" data-name="Rectangle 99" class="cls-3" width="18.845" height="3.18" transform="translate(3.081 68.825)"/>
<rect id="Rectangle_100" data-name="Rectangle 100" class="cls-3" width="14.014" height="3.18" transform="translate(43.122 21.825)"/>
<rect id="Rectangle_101" data-name="Rectangle 101" class="cls-3" width="18.845" height="3.18" transform="translate(43.122 26.825)"/>
<rect id="Rectangle_102" data-name="Rectangle 102" class="cls-3" width="14.014" height="3.18" transform="translate(43.122 63.825)"/>
<rect id="Rectangle_103" data-name="Rectangle 103" class="cls-3" width="18.845" height="3.18" transform="translate(43.122 68.825)"/>
<ellipse id="Ellipse_42" data-name="Ellipse 42" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(3.003 3.55)"/>
<ellipse id="Ellipse_43" data-name="Ellipse 43" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(3.003 45.55)"/>
<ellipse id="Ellipse_44" data-name="Ellipse 44" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(43.044 3.55)"/>
<ellipse id="Ellipse_45" data-name="Ellipse 45" class="cls-3" cx="5.658" cy="5.652" rx="5.658" ry="5.652" transform="translate(43.044 45.55)"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

+32
View File
@@ -0,0 +1,32 @@
#block-region-side-pre .block_timeline .nav-search {
flex: 0 0 100%;
max-width: 100%;
}
.block_timeline .input-group.searchbar {
width: 100%;
}
#block-region-side-pre .block_timeline h6.event-action {
flex-basis: 100%;
}
#block-region-side-pre .block_timeline .event-name-container {
flex-basis: 50%;
}
#block-region-side-pre .block_timeline h6.event-action a.btn {
width: auto;
}
.block_timeline .timeline-action-button {
margin-left: auto;
}
@media (max-width: 480px) {
.block_timeline .timeline-name {
width: 100%;
}
.block_timeline .timeline-action-button {
margin-left: 0;
}
}
@@ -0,0 +1,39 @@
{{!
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_timeline/course-item-loading-placeholder
This template renders the each course block containing a summary and calendar events.
Example context (json):
{}
}}
<li class="list-group-item mt-3 p-0 px-2 border-0">
<div class="w-50 bg-pulse-grey mt-1 mb-2" style="height: 20px"></div>
<div>
<ul class="pl-0 list-group list-group-flush">
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
</ul>
<div class="pt-3 pb-2 d-flex justify-content-between">
<div class="w-25 bg-pulse-grey" style="height: 35px"></div>
</div>
</div>
</li>
@@ -0,0 +1,36 @@
{{!
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_timeline/course-item
This template renders the each course block containing a summary and calendar events.
Example context (json):
{
"shortname": "course 3",
"viewurl": "https://www.google.com",
"summary": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout."
}
}}
<li class="list-group-item mt-3 p-0 border-0">
<div data-region="course-events-container" id="course-events-container-{{id}}" data-course-id="{{id}}" class="px-2">
<h4 class="h5 font-weight-bold">{{{fullnamedisplay}}}</h4>
{{< block_timeline/event-list }}
{{$courseid}}{{id}}{{/courseid}}
{{/ block_timeline/event-list }}
</div>
</li>
@@ -0,0 +1,31 @@
{{!
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_timeline/course-items
This template renders the each course block containing a summary and calendar events.
Example context (json):
{
"shortname": "course 3",
"viewurl": "https://www.google.com",
"summary": "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout."
}
}}
{{#courses}}
{{> block_timeline/course-item }}
{{/courses}}
@@ -0,0 +1,73 @@
{{!
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_timeline/event-list-content
This template renders a group of event list items for the timeline block.
Example context (json):
{
"events": [
{
"name": "Assignment due 1",
"url": "https://www.google.com",
"timesort": 1490320388,
"course": {
"fullnamedisplay": "Course 1"
},
"action": {
"name": "Submit assignment",
"url": "https://www.google.com",
"itemcount": 1,
"actionable": true
},
"icon": {
"key": "icon",
"component": "mod_assign",
"alttext": "Assignment icon"
}
},
{
"name": "Assignment due 2",
"url": "https://www.google.com",
"timesort": 1490320388,
"course": {
"fullnamedisplay": "Course 1"
},
"action": {
"name": "Submit assignment",
"url": "https://www.google.com",
"itemcount": 1,
"actionable": true
},
"icon": {
"key": "icon",
"component": "mod_assign",
"alttext": "Assignment icon"
}
}
]
}
}}
<div class="pb-2" data-region="event-list-wrapper">
{{#eventsbyday}}
<div class="mt-3" data-region="event-list-content-date" data-timestamp="{{dayTimestamp}}">
<h5 class="h6 d-inline {{^courseview}}font-weight-bold px-2{{/courseview}}">{{#userdate}} {{dayTimestamp}}, {{#str}} strftimedaydate, core_langconfig {{/str}} {{/userdate}}</h5>
</div>
{{> block_timeline/event-list-items }}
{{/eventsbyday}}
</div>
@@ -0,0 +1,104 @@
{{!
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_timeline/event-list-item
This template renders an event list item for the timeline block.
Example context (json):
{
"name": "Assignment 1 is due",
"activityname": "Assignment",
"activitystr": "Assignment is due",
"courseview": true,
"url": "https://www.google.com",
"timesort": 1490320388,
"course": {
"fullnamedisplay": "Course 1"
},
"action": {
"name": "Submit assignment",
"url": "https://www.google.com",
"itemcount": 1,
"showitemcount": true,
"actionable": true
},
"icon": {
"key": "icon",
"component": "mod_assign",
"alttext": "Assignment icon",
"iconurl": "#"
},
"overdue": false,
"purpose": "assessment"
}
}}
<div class="list-group-item timeline-event-list-item flex-column pt-2 pb-0 border-0 {{#courseview}}px-0{{/courseview}}{{^courseview}}px-2{{/courseview}}"
data-region="event-list-item">
<div class="d-flex flex-wrap pb-1">
<div class="d-flex mr-auto pb-1 mw-100 timeline-name">
<small class="text-right text-nowrap align-self-center ml-1">
{{#userdate}} {{timesort}}, {{#str}} strftimetime24, core_langconfig {{/str}} {{/userdate}}
</small>
<div class="activityiconcontainer small courseicon align-self-top align-self-center mx-3 mb-1 mb-sm-0 text-nowrap">
{{#icon}}
{{#iconurl}}
<img alt="{{alttext}}" title="{{alttext}}" src="{{{ iconurl }}}" class="icon {{iconclass}}">
{{/iconurl}}
{{^iconurl}}
{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}
{{/iconurl}}
{{/icon}}
</div>
<div class="event-name-container flex-grow-1 line-height-3 nowrap text-truncate">
<div class="d-flex">
<h6 class="event-name mb-0 pb-1 text-truncate">
{{#overdue}}<span class="badge rounded-pill bg-danger text-white ml-1 float-right">{{#str}} overdue, block_timeline {{/str}}</span>{{/overdue}}
<a href="{{url}}"
title="{{name}}"
aria-label='{{#cleanstr}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{activityname}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/cleanstr}}'>
{{{activityname}}}</a>
</h6>
</div>
<small class="mb-0">
{{#courseview}}
{{activitystr}}
{{/courseview}}
{{^courseview}}
{{activitystr}}{{#course.fullnamedisplay}} &middot; {{{course.fullnamedisplay}}}{{/course.fullnamedisplay}}
{{/courseview}}
</small>
</div>
</div>
{{#action.actionable}}
<div class="d-flex timeline-action-button">
<h6 class="event-action">
<a class="list-group-item-action btn btn-outline-secondary btn-sm text-nowrap"
href="{{action.url}}"
aria-label="{{action.name}}"
title="{{action.name}}">
{{{action.name}}}
{{#action.showitemcount}}
<span class="badge bg-secondary text-dark">{{action.itemcount}}</span>
{{/action.showitemcount}}
</a>
</h6>
</div>
{{/action.actionable}}
</div>
<div class="pt-2 border-bottom"></div>
</div>
@@ -0,0 +1,70 @@
{{!
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_timeline/event-list-items
This template renders a group of event list items for the timeline block.
Example context (json):
{
"events": [
{
"name": "Assignment due 1",
"url": "https://www.google.com",
"timesort": 1490320388,
"course": {
"fullnamedisplay": "Course 1"
},
"action": {
"name": "Submit assignment",
"url": "https://www.google.com",
"itemcount": 1,
"actionable": true
},
"icon": {
"key": "icon",
"component": "mod_assign",
"alttext": "Assignment icon"
}
},
{
"name": "Assignment due 2",
"url": "https://www.google.com",
"timesort": 1490320388,
"course": {
"fullnamedisplay": "Course 1"
},
"action": {
"name": "Submit assignment",
"url": "https://www.google.com",
"itemcount": 1,
"actionable": true
},
"icon": {
"key": "icon",
"component": "mod_assign",
"alttext": "Assignment icon"
}
}
]
}
}}
<div class="list-group list-group-flush">
{{#events}}
{{> block_timeline/event-list-item }}
{{/events}}
</div>
@@ -0,0 +1,31 @@
{{!
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_timeline/event-list-loadmore
This template renders a show more activities button.
Example context (json):
{
"courseview": false
}
}}
<div class="pt-1 pb-2 {{^courseview}}pl-2{{/courseview}}" data-region="more-events-button-container">
<button type="button" class="btn btn-secondary btn-sm" data-action="more-events">
{{#str}} moreactivities, block_timeline {{/str}}
</button>
</div>
@@ -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_timeline/event-list
This template renders a list of events for the timeline block.
Example context (json):
{
}
}}
<div data-region="event-list-container"
data-days-offset="{{$daysoffset}}{{#hasdaysoffset}}{{daysoffset}}{{/hasdaysoffset}}{{^hasdaysoffset}}0{{/hasdaysoffset}}{{/daysoffset}}"
{{^nodayslimit}}data-days-limit="{{$dayslimit}}{{#hasdayslimit}}{{dayslimit}}{{/hasdayslimit}}{{^hasdayslimit}}30{{/hasdayslimit}}{{/dayslimit}}"{{/nodayslimit}}
data-course-id="{{$courseid}}{{/courseid}}"
data-midnight="{{midnight}}"
>
{{#hascourses}}
<div data-region="event-list-loading-placeholder">
<ul class="pl-0 list-group list-group-flush">
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
{{> block_timeline/placeholder-event-list-item }}
</ul>
<div class="pt-3 pb-2 d-flex justify-content-between">
<div class="w-25 bg-pulse-grey" style="height: 35px"></div>
</div>
</div>
{{/hascourses}}
<div data-region="event-list-content"></div>
{{> block_timeline/no-events }}
{{> block_timeline/no-courses }}
</div>
+58
View File
@@ -0,0 +1,58 @@
{{!
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_timeline/main
This template renders the main content area for the timeline block.
Example context (json):
{}
}}
<div id="block-timeline-{{uniqid}}-{{timelineinstanceid}}" class="block-timeline" data-region="timeline">
<div class="p-0 px-2">
<div class="row no-gutters">
<div class="mr-2 mb-1">
{{> block_timeline/nav-day-filter }}
</div>
<div class="mr-auto mb-1">
{{> block_timeline/nav-view-selector }}
</div>
<div class="col-md-6 col-sm-8 col-12 mb-1 d-flex justify-content-end nav-search">
{{> block_timeline/nav-search }}
</div>
</div>
<div class="pb-3 px-2 border-bottom"></div>
</div>
<div class="p-0">
{{> block_timeline/view }}
</div>
</div>
{{#js}}
require(
[
'jquery',
'block_timeline/main',
],
function(
$,
Main
) {
var root = $('#block-timeline-{{uniqid}}-{{timelineinstanceid}}');
Main.init(root);
});
{{/js}}
@@ -0,0 +1,113 @@
{{!
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_timeline/nav-day-filter
This template renders the day range selector for the timeline view.
Example context (json):
{}
}}
<div data-region="day-filter" class="dropdown mb-1">
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
aria-label="{{#str}} ariadayfilter, block_timeline {{/str}}" aria-controls="menudayfilter"
title="{{#str}} ariadayfilter, block_timeline{{/str}}" aria-describedby="timeline-day-filter-current-selection">
<span id="timeline-day-filter-current-selection" data-active-item-text>
{{#all}} {{#str}} all, core {{/str}} {{/all}}
{{#overdue}} {{#str}} overdue, block_timeline {{/str}} {{/overdue}}
{{#next7days}} {{#str}}next7days, block_timeline {{/str}} {{/next7days}}
{{#next30days}} {{#str}}next30days, block_timeline {{/str}} {{/next30days}}
{{#next3months}} {{#str}}next3months, block_timeline {{/str}} {{/next3months}}
{{#next6months}} {{#str}}next6months, block_timeline {{/str}} {{/next6months}}
</span>
</button>
<div id="menudayfilter" role="menu" class="dropdown-menu" data-show-active-item data-skip-active-class="true">
<a
class="dropdown-item"
href="#"
data-from="-14"
data-filtername="all"
{{#all}}aria-current="true"{{/all}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} all, core {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} all, core {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="-14"
data-to="1"
data-filtername="overdue"
{{#overdue}}aria-current="true"{{/overdue}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} overdue, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} overdue, block_timeline {{/str}}
</a>
<div class="dropdown-divider" role="separator"></div>
<h6 class="dropdown-header">{{#str}} duedate, block_timeline {{/str}}</h6>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="7"
data-filtername="next7days"
{{#next7days}}aria-current="true"{{/next7days}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next7days, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="30"
data-filtername="next30days"
{{#next30days}}aria-current="true"{{/next30days}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next30days, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="90"
data-filtername="next3months"
{{#next3months}}aria-current="true"{{/next3months}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next3months, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#"
data-from="0"
data-to="180"
data-filtername="next6months"
{{#next6months}}aria-current="true"{{/next6months}}
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} next6months, block_timeline {{/str}}
</a>
</div>
</div>
@@ -0,0 +1,34 @@
{{!
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_timeline/nav-search
This template renders the main content area for the timeline block.
Example context (json):
{}
}}
<div class="w-100">
{{< core/search_input_auto }}
{{$label}}{{#str}}
searchevents, block_timeline
{{/str}}{{/label}}
{{$placeholder}}{{#str}}
searchevents, block_timeline
{{/str}}{{/placeholder}}
{{/ core/search_input_auto }}
</div>
@@ -0,0 +1,58 @@
{{!
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_timeline/nav-view-selector
This template renders the timeline sort selector.
Example context (json):
{}
}}
<div data-region="view-selector" class="btn-group mb-1">
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
aria-label="{{#str}} ariaviewselector, block_timeline{{/str}}" aria-controls="menusortby"
title="{{#str}} ariaviewselector, block_timeline{{/str}}" aria-describedby="timeline-view-selector-current-selection">
<span id="timeline-view-selector-current-selection" data-active-item-text>
{{#sorttimelinecourses}}{{#str}} sortbycourses, block_timeline{{/str}}{{/sorttimelinecourses}}
{{#sorttimelinedates}}{{#str}} sortbydates, block_timeline {{/str}}{{/sorttimelinedates}}
</span>
</button>
<div id="menusortby" role="menu" class="dropdown-menu dropdown-menu-right list-group hidden" data-show-active-item data-skip-active-class="true">
<a
class="dropdown-item"
href="#view_dates_{{uniqid}}-{{timelineinstanceid}}"
data-toggle="tab"
data-filtername="sortbydates"
{{#sorttimelinedates}}aria-current="true"{{/sorttimelinedates}}
aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbydates, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} sortbydates, block_timeline {{/str}}
</a>
<a
class="dropdown-item"
href="#view_courses_{{uniqid}}-{{timelineinstanceid}}"
data-toggle="tab"
data-filtername="sortbycourses"
{{#sorttimelinecourses}}aria-current="true"{{/sorttimelinecourses}}
aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbycourses, block_timeline {{/str}}{{/str}}"
role="menuitem"
>
{{#str}} sortbycourses, block_timeline {{/str}}
</a>
</div>
</div>
@@ -0,0 +1,39 @@
{{!
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_timeline/no-courses
This template renders a notice that the user has no active courses.
Example context (json):
{
"hascourses": false,
"urls": {
"noevents": "#"
}
}
}}
{{^hascourses}}
<div class="text-xs-center text-center mt-3" data-region="no-courses-empty-message">
<img
src="{{urls.noevents}}"
alt=""
style="height: 70px; width: 70px"
>
<p class="text-muted mt-1">{{#str}} nocoursesinprogress, block_timeline {{/str}}</p>
</div>
{{/hascourses}}
@@ -0,0 +1,38 @@
{{!
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_timeline/no-events
This template renders confirmation that no events require action.
Example context (json):
{
"urls": {
"noevents": "#"
}
}
}}
{{#urls.noevents}}
<div class="hidden text-xs-center text-center mt-3" data-region="no-events-empty-message">
<img
src="{{urls.noevents}}"
alt=""
style="height: 70px; width: 70px"
>
<p class="text-muted mt-1">{{#str}} noevents, block_timeline {{/str}}</p>
</div>
{{/urls.noevents}}
@@ -0,0 +1,42 @@
{{!
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_timeline/placeholder-event-list-item
This template renders an event list item loading placeholder for the timeline block.
Example context (json):
{}
}}
<li class="list-group-item px-2">
<div class="row">
<div class="col-8 pr-0">
<div class="d-flex flex-row align-items-center" style="height: 32px">
<div class="bg-pulse-grey rounded-circle" style="height: 32px; width: 32px;"></div>
<div style="flex: 1" class="pl-2">
<div class="bg-pulse-grey w-100" style="height: 15px;"></div>
<div class="bg-pulse-grey w-75 mt-1" style="height: 10px;"></div>
</div>
</div>
</div>
<div class="col-4 pr-3">
<div class="d-flex flex-row justify-content-end" style="height: 32px; padding-top: 2px">
<div class="bg-pulse-grey w-75" style="height: 15px;"></div>
</div>
</div>
</div>
</li>
@@ -0,0 +1,44 @@
{{!
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_timeline/view-courses
This template renders the timeline view by courses for the timeline block.
Example context (json):
{}
}}
{{#hascourses}}
<div data-region="course-items-loading-placeholder">
<ul class="list-group unstyled">
{{> block_timeline/course-item-loading-placeholder }}
{{> block_timeline/course-item-loading-placeholder }}
</ul>
<div class="bg-pulse-grey mt-1" style="width: 100px; height: 30px; margin-left: auto; margin-right: auto"></div>
</div>
{{/hascourses}}
<ul class="list-group unstyled" data-region="courses-list"></ul>
<div class="hidden text-xs-center text-center pt-3" data-region="more-courses-button-container">
<button type="button" class="btn btn-primary" data-action="more-courses">
{{#str}} morecourses, block_timeline {{/str}}
<span class="hidden" data-region="loading-icon-container">
{{> core/loading }}
</span>
</button>
</div>
{{> block_timeline/no-events }}
{{> block_timeline/no-courses }}
@@ -0,0 +1,27 @@
{{!
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_timeline/view-dates
This template renders the timeline view by dates for the timeline block.
Example context (json):
{}
}}
<div data-region="timeline-view-dates">
{{> block_timeline/event-list }}
</div>
+71
View File
@@ -0,0 +1,71 @@
{{!
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_timeline/view
This template renders the timeline view for the timeline block.
Example context (json):
{
"midnight": 1538954668,
"coursepages": [
{}
],
"urls": {
"nocourses": "#",
"noevents": "#"
},
"sorttimelinedates": true,
"sorttimelinecourses": false,
"selectedfilter": "all",
"hasdaysoffset": true,
"hasdayslimit": false,
"nodayslimit": true,
"all": true,
"overdue": false,
"next7days": false,
"next30days": false,
"next3months": false,
"next6months": false,
"daysoffset": -14,
"dayslimit": false,
"limit": 0,
"hascourses": true,
"overdue": false
}
}}
<div data-region="timeline-view">
<div class="tab-content">
<div class="tab-pane {{#sorttimelinedates}}active show{{/sorttimelinedates}} fade" data-limit="{{limit}}" data-region="view-dates" id="view_dates_{{uniqid}}-{{timelineinstanceid}}">
{{> block_timeline/view-dates }}
</div>
<div
class="tab-pane {{#sorttimelinecourses}}active show{{/sorttimelinecourses}} fade"
data-region="view-courses"
data-midnight="{{midnight}}"
data-limit="2"
data-offset="0"
data-days-limit="{{dayslimit}}"
data-days-offset="{{daysoffset}}"
data-no-events-url="{{urls.noevents}}"
{{#overdue}}data-filter-overdue="{{overdue}}"{{/overdue}}
id="view_courses_{{uniqid}}-{{timelineinstanceid}}"
>
{{> block_timeline/view-courses }}
</div>
</div>
</div>
@@ -0,0 +1,255 @@
@block @block_timeline @javascript
Feature: The timeline block allows users to see upcoming courses
In order to enable the timeline block
As a student
I can add the timeline block to my dashboard
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | 1 | student1@example.com | S1 |
| student2 | Student | 2 | student2@example.com | S2 |
And the following "courses" exist:
| fullname | shortname | category | startdate | enddate |
| Course 1 | C1 | 0 | ##yesterday## | ##tomorrow## |
| Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## |
| Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## |
| Course 4 | C4 | 0 | ##first day of next month## | ##last day of next month## |
| Course 5 | C5 | 0 | ##yesterday## | ##tomorrow## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | timeclose |
| choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## |
| choice | C1 | choice2 | Test choice 2 | Test choice description | ##1 month ago## | ##15 days ago## |
| choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +5 months## |
| feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## |
| feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +10 months## | ##last day of +10 months## |
| feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## |
| feedback | C4 | feedback4 | Test feedback 4 | Test feedback description | ##yesterday## | ##tomorrow## |
| feedback | C1 | feedback5 | Test feedback 5 | Test feedback description | ##yesterday## | ##now +1 minute## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | duedate |
| assign | C1 | assign1 | Test assign 1 | Test assign description | ##1 month ago## | ##yesterday## |
| assign | C2 | assign2 | Test assign 2 | Test assign description | ##yesterday## | ##now -1 minute## |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
Scenario: Next 30 days in course view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Next 30 days" "link" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
When I click on "Sort by courses" "link" in the "Timeline" "block"
Then I should see "Course 1" in the ".block_timeline [data-region='view-courses']" "css_element"
And I should see "Course 2" in the ".block_timeline [data-region='view-courses']" "css_element"
And I should see "Show more courses" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 4" in the ".block_timeline [data-region='view-courses']" "css_element"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And I should see "Choice closes" in the "Timeline" "block"
And "Test feedback 1" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes" in the "Timeline" "block"
And "Test feedback 4" "link" should exist in the "Timeline" "block"
And "Test feedback 5" "link" should exist in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And I should not see "Course 3" in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test choice 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test assign 1" "link" should not exist in the "Timeline" "block"
Scenario: All in course view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
When I click on "Show more courses" "button" in the "Timeline" "block"
Then I should see "Course 3" in the "Timeline" "block"
And I should see "Course 2" in the "Timeline" "block"
And I should see "Course 1" in the "Timeline" "block"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And "Test choice 3" "link" should exist in the "Timeline" "block"
And "Test feedback 1" "link" should exist in the "Timeline" "block"
And "Test feedback 2" "link" should exist in the "Timeline" "block"
And "Test feedback 3" "link" should exist in the "Timeline" "block"
And "Test feedback 5" "link" should exist in the "Timeline" "block"
And "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due" in the "Timeline" "block"
And I should see "Choice closes" in the "Timeline" "block"
And I should see "Course 4" in the "Timeline" "block"
And I should see "Test feedback 4" in the "Timeline" "block"
And I should not see "Show more courses" in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
Scenario: Persistent sort filter
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
When I reload the page
Then I should see "Course 1" in the "Timeline" "block"
And I should see "Course 2" in the "Timeline" "block"
And I should see "Show more courses" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 4" in the ".block_timeline [data-region='view-courses']" "css_element"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And "Test feedback 1" "link" should exist in the "Timeline" "block"
And "Test feedback 4" "link" should exist in the "Timeline" "block"
And "Test feedback 5" "link" should exist in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And I should not see "Course 3" in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test choice 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test assign 1" "link" should not exist in the "Timeline" "block"
Scenario: Persistent All in course view
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I reload the page
And I should not see "Course 3" in the "Timeline" "block"
And I should see "Course 2" in the "Timeline" "block"
And I should see "Course 1" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
Then I should see "Course 3" in the "Timeline" "block"
And I should see "Course 2" in the "Timeline" "block"
And I should see "Course 1" in the "Timeline" "block"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And "Test choice 3" "link" should exist in the "Timeline" "block"
And "Test feedback 1" "link" should exist in the "Timeline" "block"
And "Test feedback 2" "link" should exist in the "Timeline" "block"
And "Test feedback 3" "link" should exist in the "Timeline" "block"
And "Test assign 1" "link" should exist in the "Timeline" "block"
And I should not see "Show more courses" in the "Timeline" "block"
And I should see "Course 4" in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test feedback 4" "link" should exist in the "Timeline" "block"
Scenario: Current filtering always applies in courses view
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Overdue" "link" in the "Timeline" "block"
And I reload the page
And "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
# Confirm that when we switch back to courses view, the "All" filer continues to be applied (and not "overdue").
When I click on "All" "link" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test feedback 2" "link" should exist in the "Timeline" "block"
Scenario: Overdue in course view
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And I should not see "Test feedback 1" in the "Timeline" "block"
And I should not see "Test feedback 2" in the "Timeline" "block"
And I should not see "Test feedback 3" in the "Timeline" "block"
And I should not see "Test feedback 4" in the "Timeline" "block"
And I should not see "Test feedback 5" in the "Timeline" "block"
And I should not see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
And I should not see "Test choice 3" in the "Timeline" "block"
Scenario: Persistent Overdue in course view
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
And I reload the page
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And "Test feedback 1" "link" should not exist in the "Timeline" "block"
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 4" "link" should not exist in the "Timeline" "block"
And "Test feedback 5" "link" should not exist in the "Timeline" "block"
And "Test choice 1" "link" should not exist in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test choice 3" "link" should not exist in the "Timeline" "block"
Scenario: Student not enrolled in any courses sees a message
Given I log in as "student2"
When I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
Then I should see "No in-progress courses" in the "Timeline" "block"
And I should not see "Test choice 1"
Scenario: Only courses with matching events are displayed and are refreshed when filtering changes
Given the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | duedate |
| assign | C5 | assign3 | Test assign 3 | Test assign description | ##yesterday## | ##now +1 minute## |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C5 | student |
When I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Overdue" "link" in the "Timeline" "block"
Then I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 3" in the "Timeline" "block"
And I should not see "Course 5" in the "Timeline" "block"
And I should not see "Show more courses" in the "Timeline" "block"
And "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 2" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test feedback 5" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Next 7 days" "link" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 3" in the "Timeline" "block"
And I should not see "Show more courses" in the "Timeline" "block"
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 1" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test feedback 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 2" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test choice 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
@@ -0,0 +1,125 @@
@block @block_timeline @javascript
Feature: The timeline block allows users to see courses with overdue activities
In order to view overdue activities in the timeline block
As a student
I can select the overdue filter in courses view
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | 1 | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category | startdate | enddate |
| Course 1 | C1 | 0 | ##now -3 months## | ##tomorrow## |
| Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## |
| Course 3 | C3 | 0 | ##yesterday## | ##tomorrow## |
| Course 4 | C4 | 0 | ##yesterday## | ##tomorrow## |
| Course 5 | C5 | 0 | ##yesterday## | ##tomorrow## |
| Course 6 | C6 | 0 | ##yesterday## | ##tomorrow## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | duedate |
| assign | C1 | assign1 | Test assign 1 | Assign due last month | ##now -2 months## | ##now -1 month## |
| assign | C2 | assign2 | Test assign 2 | Assign due yesterday | ##now -2 days## | ##yesterday## |
| assign | C3 | assign3 | Test assign 3 | Assign due yesterday | ##now -2 days## | ##yesterday## |
| assign | C4 | assign4 | Test assign 4 | Assign due later today | ##yesterday## | ##now +10 minutes## |
| assign | C5 | assign5 | Test assign 5 | Assign due yesterday | ##now -2 days## | ##yesterday## |
| assign | C6 | assign6 | Test assign 6 | Assign due tomorrow | ##yesterday## | ##tomorrow## |
Scenario: No activities to display as overdue displays expected message
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C4 | student |
| student1 | C6 | student |
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
Then I should see "No activities require action" in the "Timeline" "block"
And I reload the page
And I should see "No activities require action" in the "Timeline" "block"
Scenario: If filtering by overdue, only courses with a matching item are included
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
When I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Overdue" "link" in the "Timeline" "block"
Then I should not see "Show more courses" in the "Timeline" "block"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And I reload the page
And I should not see "Show more courses" in the "Timeline" "block"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 3" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
Scenario: If filtering by overdue, only courses with a matching item are included and loading more is supported
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
When I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Overdue" "link" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
Then I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Show more courses" in the "Timeline" "block"
And I reload the page
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 6" in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 2" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 3" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 5" "link" should exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 1" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 4" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
And "Test assign 6" "link" should not exist in the ".block-timeline [data-region='view-courses']" "css_element"
@@ -0,0 +1,160 @@
@block @block_timeline @javascript
Feature: The timeline block allows users to see upcoming activities
In order to enable the timeline block
As a student
I can add the timeline block to my dashboard
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | 1 | student1@example.com | S1 |
| student2 | Student | 2 | student2@example.com | S2 |
And the following "courses" exist:
| fullname | shortname | category | startdate | enddate |
| Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## |
| Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## |
| Course 3 | C3 | 0 | ##first day of next month## | ##last day of next month## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | timeclose |
| choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## |
| choice | C1 | choice2 | Test choice 2 | Test choice description | ##1 month ago## | ##15 days ago## |
| choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +5 months## |
| feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## |
| feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +10 months## | ##last day of +10 months## |
| feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## |
| feedback | C2 | feedback4 | Test feedback 4 | Test feedback description | ##yesterday## | ##now +1 minute## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | duedate |
| assign | C1 | assign1 | Test assign 1 | Test assign description | ##1 month ago## | ##yesterday## |
| assign | C2 | assign2 | Test assign 2 | Test assign description | ##yesterday## | ##now -1 minute## |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
And I change window size to "large"
Scenario: Next 7 days in date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "Next 7 days" "link" in the "Timeline" "block"
Then "Test choice 1" "link" should exist in the "Timeline" "block"
And I should see "Choice closes · Course 2" in the "Timeline" "block"
And "Test feedback 1" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And "Test feedback 4" "link" should exist in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test choice 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test assign 1" "link" should not exist in the "Timeline" "block"
And I should not see "Assignment is due · Course 1" in the "Timeline" "block"
Scenario: Overdue in date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test feedback 1" "link" should not exist in the "Timeline" "block"
And "Test choice 1" "link" should not exist in the "Timeline" "block"
And "Test choice 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 4" "link" should not exist in the "Timeline" "block"
Scenario: All in date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "All" "link" in the "Timeline" "block"
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 2" in the "Timeline" "block"
And "Test feedback 1" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And I should see "Choice closes · Course 2" in the "Timeline" "block"
And "Test feedback 4" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
And I click on "Show more activities" "button"
And "Test feedback 2" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes · Course 1" in the "Timeline" "block"
And "Test choice 3" "link" should exist in the "Timeline" "block"
And I should see "Test assign 1" in the "Timeline" "block"
And I should see "Test feedback 1" in the "Timeline" "block"
And I should see "Test choice 1" in the "Timeline" "block"
And I should see "Test choice 3" in the "Timeline" "block"
And I should see "Test feedback 3" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
Scenario: Persistent All in date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "All" "link" in the "Timeline" "block"
And I reload the page
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 2" in the "Timeline" "block"
And "Test feedback 1" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And I should see "Choice closes · Course 2" in the "Timeline" "block"
And "Test feedback 4" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes · Course 2" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
And I should not see "Test feedback 2" in the "Timeline" "block"
And I click on "Show more activities" "button"
And "Test feedback 2" "link" should exist in the "Timeline" "block"
And I should see "Feedback closes · Course 1" in the "Timeline" "block"
And I should see "Test assign 1" in the "Timeline" "block"
And I should see "Test feedback 1" in the "Timeline" "block"
And I should see "Test feedback 3" in the "Timeline" "block"
And I should see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
And I should see "Test choice 3" in the "Timeline" "block"
Scenario: Persistent Overdue in date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
And I reload the page
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 1" in the "Timeline" "block"
And "Test assign 2" "link" should exist in the "Timeline" "block"
And I should see "Assignment is due · Course 2" in the "Timeline" "block"
And "Test feedback 1" "link" should not exist in the "Timeline" "block"
And "Test feedback 3" "link" should not exist in the "Timeline" "block"
And "Test feedback 4" "link" should not exist in the "Timeline" "block"
And "Test choice 1" "link" should not exist in the "Timeline" "block"
And "Test choice 2" "link" should not exist in the "Timeline" "block"
And "Test choice 3" "link" should not exist in the "Timeline" "block"
Scenario: Current filtering always applies in date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "Overdue" "link" in the "Timeline" "block"
And I reload the page
And "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test feedback 2" "link" should not exist in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
# Confirm that when we switch back to date view, the "All" filer continues to be applied (and not "overdue")
When I click on "All" "link" in the "Timeline" "block"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And I click on "Show more activities" "button"
And "Test feedback 2" "link" should exist in the "Timeline" "block"
Scenario: Student not enrolled in any courses sees a message
Given I log in as "student2"
When I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
Then I should see "No in-progress courses" in the "Timeline" "block"
And I should not see "Test choice 1"
@@ -0,0 +1,90 @@
@block @block_timeline @javascript
Feature: The timeline block allows users to use the lazy loading to view more activities
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | 1 | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category | startdate | enddate |
| Course 1 | C1 | 0 | ##yesterday## | ##last day of next month## |
| Course 2 | C2 | 0 | ##yesterday## | ##last day of next month## |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | timeclose |
| choice | C1 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## |
| choice | C1 | choice2 | Test choice 2 | Test choice description | ##yesterday## | ##+1 days## |
| choice | C1 | choice3 | Test choice 3 | Test choice description | ##yesterday## | ##+2 days## |
| choice | C1 | choice4 | Test choice 4 | Test choice description | ##yesterday## | ##+3 days## |
| choice | C1 | choice5 | Test choice 5 | Test choice description | ##yesterday## | ##+4 days## |
| choice | C1 | choice6 | Test choice 6 | Test choice description | ##yesterday## | ##+5 days## |
| choice | C1 | choice7 | Test choice 7 | Test choice description | ##yesterday## | ##+6 days## |
| choice | C1 | choice8 | Test choice 8 | Test choice description | ##yesterday## | ##+7 days## |
| choice | C1 | choice9 | Test choice 9 | Test choice description | ##yesterday## | ##+8 days## |
| choice | C1 | choice10 | Test choice 10 | Test choice description | ##yesterday## | ##+9 days## |
| choice | C2 | choice10 | Test choice 11 | Test choice description | ##yesterday## | ##+9 days## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | duedate |
| assign | C1 | assign1 | Test assign 1 | Test assign description | ##1 month ago## | ##yesterday## |
Scenario: Lazy loading for date view
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "All" "link" in the "Timeline" "block"
Then I should see "Test choice 1" in the "Timeline" "block"
And "Test assign 1" "link" should exist in the "Timeline" "block"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And "Test choice 2" "link" should exist in the "Timeline" "block"
And "Test choice 3" "link" should exist in the "Timeline" "block"
And "Test choice 4" "link" should exist in the "Timeline" "block"
And "Test choice 5" "link" should not exist in the "Timeline" "block"
And "Test choice 6" "link" should not exist in the "Timeline" "block"
And "Test choice 7" "link" should not exist in the "Timeline" "block"
And "Test choice 8" "link" should not exist in the "Timeline" "block"
And "Test choice 9" "link" should not exist in the "Timeline" "block"
And "Test choice 10" "link" should not exist in the "Timeline" "block"
And "Test choice 11" "link" should not exist in the "Timeline" "block"
And I click on "Show more activities" "button" in the "[data-region='view-dates']" "css_element"
And "Test choice 5" "link" should exist in the "Timeline" "block"
And "Test choice 6" "link" should exist in the "Timeline" "block"
And "Test choice 7" "link" should exist in the "Timeline" "block"
And "Test choice 8" "link" should exist in the "Timeline" "block"
And "Test choice 9" "link" should exist in the "Timeline" "block"
And "Test choice 10" "link" should exist in the "Timeline" "block"
And "Test choice 11" "link" should exist in the "Timeline" "block"
Scenario: Lazy loading for course view
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
When I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And "Test choice 1" "link" should exist in the "Timeline" "block"
And "Test choice 2" "link" should exist in the "Timeline" "block"
And "Test choice 3" "link" should exist in the "Timeline" "block"
And "Test choice 4" "link" should exist in the "Timeline" "block"
And "Test choice 5" "link" should exist in the "Timeline" "block"
And "Test choice 11" "link" should exist in the "Timeline" "block"
And "Test choice 6" "link" should not exist in the "Timeline" "block"
And "Test choice 7" "link" should not exist in the "Timeline" "block"
And "Test choice 8" "link" should not exist in the "Timeline" "block"
And "Test choice 9" "link" should not exist in the "Timeline" "block"
And "Test choice 10" "link" should not exist in the "Timeline" "block"
And I click on "Show more activities" "button" in the "[data-region='view-courses']" "css_element"
And "Test choice 6" "link" should exist in the "Timeline" "block"
And "Test choice 7" "link" should exist in the "Timeline" "block"
And "Test choice 8" "link" should exist in the "Timeline" "block"
And "Test choice 9" "link" should exist in the "Timeline" "block"
And "Test choice 10" "link" should exist in the "Timeline" "block"
Scenario: The lazy loading button will not be shown if the number of events is smaller than 5
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by dates" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
When I click on "Overdue" "link" in the "Timeline" "block"
Then "Test assign 1" "link" should exist in the "Timeline" "block"
And "Show more activities" "button" should not exist in the "[data-region='view-courses']" "css_element"
@@ -0,0 +1,154 @@
@block @block_timeline @javascript
Feature: The timeline block allows users to search for upcoming activities
As a student
I can search for the upcoming activities in the timeline block
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | 1 | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category | startdate | enddate |
| Course 1 | C1 | 0 | ##1 month ago## | ##15 days ago## |
| Course 2 | C2 | 0 | ##yesterday## | ##tomorrow## |
| Course 3 | C3 | 0 | ##first day of next month## | ##last day of next month## |
| Course 4 | C4 | 0 | ##1 month ago## | ##tomorrow## |
| Course 5 | C5 | 0 | ##first day of last month## | ##last day of next month## |
| Course with advanced name | C6 | 0 | ##first day of last month## | ##last day of next month## |
And the following "activities" exist:
| activity | course | idnumber | name | intro | timeopen | timeclose | duedate |
| choice | C2 | choice1 | Test choice 1 | Test choice description | ##yesterday## | ##tomorrow## | |
| choice | C1 | choice2 | Test choice 2 | Test choice description | ##first day of +5 months## | ##last day of +5 months## | |
| choice | C3 | choice3 | Test choice 3 | Test choice description | ##first day of +5 months## | ##last day of +10 months## | |
| choice | C2 | choice4 | Test choice 4 | Test choice description | ##first day of +5 months## | ##last day of +15 months## | |
| choice | C1 | choice5 | Test choice 5 | Test choice description | ##first day of +5 months## | ##last day of +20 months## | |
| choice | C3 | choice6 | Test choice 6 | Test choice description | ##first day of +5 months## | ##last day of +25 months## | |
| choice | C4 | choice7 | Test choice 7 | Test choice description | ##first day of +5 months## | ##last day of +5 months## | |
| choice | C5 | choice8 | Test choice 8 | Test choice description | ##first day of +5 months## | ##last day of +10 months## | |
| feedback | C2 | feedback1 | Test feedback 1 | Test feedback description | ##yesterday## | ##tomorrow## | |
| feedback | C1 | feedback2 | Test feedback 2 | Test feedback description | ##first day of +5 months## | ##last day of +5 months## | |
| feedback | C3 | feedback3 | Test feedback 3 | Test feedback description | ##first day of +5 months## | ##last day of +10 months## | |
| assign | C6 | assign1 | Assign with advanced name | Test assign description | ##yesterday## | | ##tomorrow## |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
Scenario: The search should return no events if I enter the wrong value
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search" in the "Timeline" "block" to "Fake example"
Then I should see "No activities require action" in the "Timeline" "block"
Scenario: Search for Course name
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search" in the "Timeline" "block" to "Course 1"
Then I should see "Test choice 2" in the "Timeline" "block"
And I should see "Test choice 5" in the "Timeline" "block"
And I should see "Test feedback 2" in the "Timeline" "block"
And I should not see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 3" in the "Timeline" "block"
And I should not see "Test choice 4" in the "Timeline" "block"
And I should not see "Test feedback 1" in the "Timeline" "block"
And I should not see "Test feedback 3" in the "Timeline" "block"
Scenario: Search for Course name - Advanced
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search" in the "Timeline" "block" to "Course advanced"
Then I should see "Assign with advanced name" in the "Timeline" "block"
Scenario: Search for Activity name
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search" in the "Timeline" "block" to "Test choice 1"
And I wait until "Test choice 2" "text" does not exist
Then I should see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
And I should not see "Test choice 3" in the "Timeline" "block"
And I should not see "Test choice 4" in the "Timeline" "block"
And I should not see "Test choice 5" in the "Timeline" "block"
And I should not see "Test choice 6" in the "Timeline" "block"
And I should not see "Test feedback 1" in the "Timeline" "block"
And I should not see "Test feedback 2" in the "Timeline" "block"
And I should not see "Test feedback 3" in the "Timeline" "block"
Scenario: Search for Activity name - Advanced
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search" in the "Timeline" "block" to "Assign advanced"
Then I should see "Assign with advanced name" in the "Timeline" "block"
Scenario: Search for Activity type
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search" in the "Timeline" "block" to "feedback"
Then I should see "Test feedback 1" in the "Timeline" "block"
And I should see "Test feedback 2" in the "Timeline" "block"
And I should see "Test feedback 3" in the "Timeline" "block"
And I should not see "Test choice 1" in the "Timeline" "block"
And I should not see "Test choice 2" in the "Timeline" "block"
And I should not see "Test choice 3" in the "Timeline" "block"
And I should not see "Test choice 4" in the "Timeline" "block"
And I should not see "Test choice 5" in the "Timeline" "block"
Scenario: Timeline paginated search
Given I log in as "student1"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
When I set the field "Search" in the "Timeline" "block" to "choice"
Then I should see "Test choice 1" in the "Timeline" "block"
And I should see "Test choice 2" in the "Timeline" "block"
And I should see "Test choice 3" in the "Timeline" "block"
And I should see "Test choice 7" in the "Timeline" "block"
And I should see "Test choice 8" in the "Timeline" "block"
And I should not see "Test choice 4" in the "Timeline" "block"
And I should not see "Test choice 5" in the "Timeline" "block"
And I should not see "Test choice 6" in the "Timeline" "block"
And I click on "Show more activities" "button"
And I should see "Test choice 4" in the "Timeline" "block"
And I should see "Test choice 5" in the "Timeline" "block"
And I should see "Test choice 6" in the "Timeline" "block"
Scenario: Courses view is refreshed when search changes
Given I log in as "student1"
And I click on "Sort timeline items" "button" in the "Timeline" "block"
And I click on "Sort by courses" "link" in the "Timeline" "block"
And I click on "Filter timeline by date" "button" in the "Timeline" "block"
And I click on "All" "link" in the "Timeline" "block"
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I click on "Show more courses" "button" in the "Timeline" "block"
And I should see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Test choice 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Test choice 7" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should see "Test choice 8" in the ".block-timeline [data-region='view-courses']" "css_element"
When I set the field "Search by activity type or name" to "choice 1"
And I wait until "Course 4" "text" does not exist
Then I should see "Test choice 1" in the "Timeline" "block"
And I should see "Course 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 1" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 4" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Course 5" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Test choice 2" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Test choice 3" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Test choice 7" in the ".block-timeline [data-region='view-courses']" "css_element"
And I should not see "Test choice 8" in the ".block-timeline [data-region='view-courses']" "css_element"
@@ -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/>.
/**
* Unit tests for the block_timeline implementation of the privacy API.
*
* @package block_timeline
* @category test
* @copyright 2018 Peter Dias <peter@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace block_timeline\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\request\writer;
use block_timeline\privacy\provider;
/**
* Unit tests for the block_timeline 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 provider_test 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(): void {
$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
* @param string $expected The expected value override
*
* @dataProvider user_preference_provider
*/
public function test_export_user_preferences($type, $value, $expected): void {
$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_timeline');
if (!$expected) {
$expected = get_string($value, 'block_timeline');
}
$this->assertEquals($expected, $blockpreferences->{$type}->value);
}
/**
* Create an array of valid user preferences for the timeline block.
*
* @return array Array of valid user preferences.
*/
public function user_preference_provider() {
return array(
array('block_timeline_user_sort_preference', 'sortbydates', ''),
array('block_timeline_user_sort_preference', 'sortbycourses', ''),
array('block_timeline_user_sort_preference', 'next7days', ''),
array('block_timeline_user_sort_preference', 'all', ''),
array('block_timeline_user_limit_preference', 5, 5),
);
}
}
+10
View File
@@ -0,0 +1,10 @@
This file describes API changes in the timeline block code.
=== 4.0 ===
* The timeline block courses view has been updated to only list courses which contain at least one action event within the chosen
filter, so now uses \core_course\external\get_enrolled_courses_with_action_events_by_timeline_classification to fetch courses
instead of core_course_external::get_enrolled_courses_by_timeline_classification, which fetches all courses within the limit.
=== 3.7 ===
* The 'block/timeline:addinstance' capability has been removed. It has never been used in code.
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details for the timeline block.
*
* @package block_timeline
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'block_timeline'; // Full name of the plugin (used for diagnostics).