diff --git a/app/public/js/controllers/activity-list.js b/app/public/js/controllers/activity-list.js index fd4c3c8..d79146c 100644 --- a/app/public/js/controllers/activity-list.js +++ b/app/public/js/controllers/activity-list.js @@ -92,7 +92,6 @@ angular.module('activitiesApp', ['ngStorage', 'services']) var classId = $scope.classId ? $scope.classId : $scope.activity.classId; var gameId = $scope.gameId ? $scope.gameId : $scope.activity.gameId; var versionId = $scope.versionId; - console.log(gameId); $scope.activityCreatedError = ''; if (!gameId) { @@ -182,9 +181,9 @@ angular.module('activitiesApp', ['ngStorage', 'services']) $scope.goToActivity(activity); $rootScope.$broadcast('refreshActivities'); }).error(function (data, status) { - console.error('Error on post /kibana/dashboard/activity/' + activity._id + ' ' + - JSON.stringify(data) + ', status: ' + status); - }); + console.error('Error on post /kibana/dashboard/activity/' + activity._id + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); } }).error(function (data, status) { console.error('Error on post /kibana/visualization/activity/' + visualizationId + '/' + activity._id + ' ' + diff --git a/app/public/js/controllers/activity.js b/app/public/js/controllers/activity.js index c61dade..7903f8a 100644 --- a/app/public/js/controllers/activity.js +++ b/app/public/js/controllers/activity.js @@ -34,36 +34,82 @@ angular.module('activityApp', ['myApp', 'ngStorage', 'services']) }; }) .controller('ActivityCtrl', ['$rootScope', '$scope', '$attrs', '$location', '$http', 'Activities', 'Classes', '_', - 'Results', 'Versions', '$sce', '$interval', 'CONSTANTS', - function ($rootScope, $scope, $attrs, $location, $http, Activities, Classes, _, Results, Versions, $sce, $interval, CONSTANTS) { + 'Results', 'Versions', 'Groups', 'Groupings', '$sce', '$interval', 'Role', 'CONSTANTS', + function ($rootScope, $scope, $attrs, $location, $http, Activities, Classes, _, Results, + Versions, Groups, Groupings, $sce, $interval, Role, CONSTANTS) { var refresh; + var groupsReady = false; + var groupingsReady = false; + var classReady = false; + $scope.class = {}; var onSetActivity = function() { - $scope.refreshResults = function () { - var rawResults = Results.query({ - id: $scope.activity._id - }, - function () { - calculateResults(rawResults); - }); - }; - - if (!$attrs.lite) { - $scope.iframeDashboardUrl = dashboardLink(); - $scope.studentIframe = dashboardLink($scope.$storage.user.username); - - $scope.version = Versions.get({ - gameId: $scope.activity.gameId, - versionId: $scope.activity.versionId - }, function () { - $scope.refreshResults(); - if (!$scope.activity.end) { - refresh = $interval(function () { - $scope.refreshResults(); - }, 10000); + Classes.get({classId: $scope.activity.classId}).$promise.then(function(c) { + classReady = true; + $scope.class = c; + if ($scope.activity.groupings && $scope.activity.groupings.length > 0) { + $scope.unlockGroupings(); + } else if ($scope.activity.groups && $scope.activity.groups.length > 0) { + $scope.unlockGroups(); + } + updateGroups(); + updateGroupings(); + $scope.refreshResults = function () { + if (Role.isUser()) { + if (!$scope.gameplaysShown) { + $scope.gameplaysShown = {}; + } + if (Role.isTeacher()) { + Activities.attempts({activityId: $scope.activity._id}, function (attempts) { + $scope.attempts = attempts; + }); + } } - }); - } + var rawResults = Results.query({ + id: $scope.activity._id + }, + function () { + calculateResults(rawResults); + }); + }; + + if (!$attrs.lite) { + $scope.iframeDashboardUrl = dashboardLink(); + $scope.studentIframe = dashboardLink($scope.$storage.user.username); + + $scope.version = Versions.get({ + gameId: $scope.activity.gameId, + versionId: $scope.activity.versionId + }, function () { + $scope.refreshResults(); + if (!$scope.activity.end) { + refresh = $interval(function () { + $scope.refreshResults(); + }, 10000); + } + }); + } + }); + }; + + var updateGroups = function () { + var route = CONSTANTS.PROXY + '/classes/' + $scope.class._id + '/groups'; + $http.get(route).success(function (data) { + $scope.classGroups = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }; + + var updateGroupings = function () { + var route = CONSTANTS.PROXY + '/classes/' + $scope.class._id + '/groupings'; + $http.get(route).success(function (data) { + $scope.classGroupings = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); }; $scope.$on('$destroy', function() { @@ -77,12 +123,14 @@ angular.module('activityApp', ['myApp', 'ngStorage', 'services']) }); $attrs.$observe('activity', function() { + groupsReady = false; + groupingsReady = false; + classReady = false; $scope.activity = JSON.parse($attrs.activity); Activities.get({activityId: $scope.activity._id}).$promise.then(function(a) { $scope.activity = a; + onSetActivity(); }); - - onSetActivity(); }); $scope.student = {}; @@ -98,20 +146,35 @@ angular.module('activityApp', ['myApp', 'ngStorage', 'services']) }; - var dashboardLink = function (userName) { + var dashboardLink = function (userName, attempt) { var url = CONSTANTS.KIBANA + '/app/kibana#/dashboard/dashboard_' + $scope.activity._id + '?embed=true_g=(refreshInterval:(display:\'5%20seconds\',' + 'pause:!f,section:1,value:5000),time:(from:now-1h,mode:quick,to:now))'; if (url.startsWith('localhost')) { url = 'http://' + url; } + var filter = {}; if (userName) { - url += '&_a=(filters:!(),options:(darkTheme:!f),query:(query_string:(analyze_wildcard:!t,query:\'out.name:' + - userName + '\')))'; + filter.name = userName; } else if ($scope.player) { - url += '&_a=(filters:!(),options:(darkTheme:!f),query:(query_string:(analyze_wildcard:!t,query:\'out.name:' + - $scope.player.name + '\')))'; + filter.name = $scope.player.name; + } + + if (attempt) { + filter.session = attempt; + } else if ($scope.attempt) { + filter.session = $scope.attempt.number; + } + + if (filter.length > 0) { + var props = []; + for (var key in filter) { + if (filter.hasOwnProperty(key)) { + props.push(key + ': ' + filter[key]); + } + } + url += '&_a=(filters:!(),options:(darkTheme:!f),query:(query_string:(analyze_wildcard:!t,query:\'' + props.join(',') + '\')))'; } if (url.startsWith('localhost')) { @@ -159,11 +222,33 @@ angular.module('activityApp', ['myApp', 'ngStorage', 'services']) $scope.viewAll = function () { $scope.player = null; + $scope.attempt = null; $scope.iframeDashboardUrl = dashboardLink(); }; $scope.viewPlayer = function (result) { $scope.player = result; + $scope.attempt = null; + $scope.iframeDashboardUrl = dashboardLink(); + }; + + $scope.viewAttempt = function (gameplay, attempt) { + if ($scope.results) { + var lookForName = gameplay.playerType === 'anonymous' ? gameplay.animalName : gameplay.playerName; + for (var i = 0; i < $scope.results.length; i++) { + if ($scope.results[i].name === lookForName) { + $scope.player = $scope.results[i]; + break; + } + } + } + // This code manually changes the tab, this might be solved with tab('show') in newer versions + // as mentioned in https://github.com/twbs/bootstrap/issues/23594 + $('.active[data-toggle=\'tab\']').toggleClass('active').toggleClass('show'); + $('span[href=\'#realtime\'][data-toggle=\'tab\']').toggleClass('active').toggleClass('show'); + $('.tab-pane.active').toggleClass('active').toggleClass('show'); + $('#realtime').toggleClass('active').toggleClass('show'); + $scope.attempt = attempt; $scope.iframeDashboardUrl = dashboardLink(); }; @@ -175,117 +260,168 @@ angular.module('activityApp', ['myApp', 'ngStorage', 'services']) $scope.activity.$update(); }; - // Teachers + // Students + $scope.classGroups = []; + $scope.classGroupings = []; - $scope.isRemovable = function (dev) { - var teachers = $scope.activity.teachers; - if (teachers && teachers.length === 1) { - return false; - } - if ($scope.username === dev) { - return false; - } - return true; + $scope.selectedGroup = undefined; + $scope.selectedGrouping = undefined; + + $scope.unlockedGroups = false; + $scope.unlockedGroupings = false; + + $scope.isUsingGroupings = function () { + return $scope.activity.groupings && $scope.activity.groupings.length > 0; }; - $scope.inviteTeacher = function () { - if ($scope.teacher.name && $scope.teacher.name.trim() !== '') { - $scope.activity.teachers.push($scope.teacher.name); - $scope.activity.$update(function() { - $scope.teacher.name = ''; - }); - } + $scope.isUsingGroups = function () { + return !$scope.isUsingGroupings() && $scope.activity.groups && $scope.activity.groups.length > 0; }; - $scope.ejectTeacher = function (teacher) { + $scope.unlockGroups = function() { var route = CONSTANTS.PROXY + '/activities/' + $scope.activity._id + '/remove'; - $http.put(route, {teachers: teacher}).success(function (data) { - $scope.activity.teachers = data.teachers; - }).error(function (data, status) { - console.error('Error on put' + route + ' ' + - JSON.stringify(data) + ', status: ' + status); - }); + if ($scope.unlockedGroupings) { + $http.put(route, {groupings: $scope.activity.groupings}).success(function (data) { + $scope.activity = data; + $scope.unlockedGroupings = false; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + if ($scope.unlockedGroups) { + $http.put(route, {groups: $scope.activity.groups}).success(function (data) { + $scope.activity = data; + $scope.unlockedGroups = false; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } else { + $scope.unlockedGroups = true; + } }; - // Students - - $scope.inviteStudent = function () { - if ($scope.student.name && $scope.student.name.trim() !== '') { - var route = CONSTANTS.PROXY + '/activities/' + $scope.activity._id; - $http.put(route, {students: $scope.student.name}).success(function (data) { - $scope.student.name = ''; - $scope.activity.students = data.students; + $scope.unlockGroupings = function() { + var route = CONSTANTS.PROXY + '/activities/' + $scope.activity._id + '/remove'; + if ($scope.unlockedGroups) { + $http.put(route, {groups: $scope.activity.groups}).success(function (data) { + $scope.activity = data; + $scope.unlockedGroups = false; }).error(function (data, status) { console.error('Error on put' + route + ' ' + JSON.stringify(data) + ', status: ' + status); }); } - + if ($scope.unlockedGroupings) { + $scope.put(route, {groupings: $scope.activity.groupings}).success(function (data) { + $scope.activity = data; + $scope.unlockedGroupings = false; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } else { + $scope.unlockedGroupings = true; + } }; - $scope.ejectStudent = function (student) { - var route = CONSTANTS.PROXY + '/activities/' + $scope.activity._id + '/remove'; - $http.put(route, {students: student}).success(function (data) { - $scope.activity.students = data.students; - }).error(function (data, status) { - console.error('Error on put' + route + ' ' + - JSON.stringify(data) + ', status: ' + status); - }); + $scope.selectGroup = function (group) { + if ($scope.selectedGroup && $scope.selectedGroup._id === group._id) { + $scope.selectedGroup = undefined; + } else { + $scope.selectedGroup = group; + } + + $scope.selectedGrouping = undefined; }; - $scope.updateActivityToClass = function () { - Classes.get({classId: $scope.activity.classId}).$promise.then(function(c) { - angular.extend($scope.activity.students, c.students); - $scope.activity.$update(); - }); + $scope.isInSelectedGroup = function (usr, role, group) { + if (group) { + return group.participants[role].indexOf(usr) !== -1; + } + if ($scope.selectedGroup) { + return $scope.selectedGroup.participants[role].indexOf(usr) !== -1; + } + return false; }; - $scope.resetActivityToClass = function () { + $scope.selectGrouping = function (grouping) { + if ($scope.selectedGrouping && $scope.selectedGrouping._id === grouping._id) { + $scope.selectedGrouping = undefined; + } else { + $scope.selectedGrouping = grouping; + } - Classes.get({classId: $scope.activity.classId}).$promise.then(function(c) { + $scope.selectedGroup = undefined; + }; - var toRemove = _.difference($scope.activity.students, c.students); - $scope.activity.students = _.intersection($scope.activity.students, c.students); - var then = function() { - angular.extend($scope.activity.students, c.students); - $scope.activity.$update(); - }; - if (toRemove.length > 0) { - removeStudentsFromActivity(toRemove, then); - } else { - then(); - } - }); + $scope.getGroupThClass = function(group) { + if ($scope.selectedGroup && $scope.selectedGroup._id === group._id) { + return 'bg-success'; + } + if ($scope.selectedGrouping && $scope.isInSelectedGrouping(group._id, 'group')) { + return 'bg-warning'; + } + return ''; }; - var removeStudentsFromActivity = function (students, then) { - if (students.length > 0) { - var route = CONSTANTS.PROXY + '/activities/' + $scope.activity._id + '/remove'; - $http.put(route, {students: students}).success(function (data) { - $scope.activity.students = data.students; - then(); - }).error(function (data, status) { - console.error('Error on put' + route + ' ' + - JSON.stringify(data) + ', status: ' + status); - }); + $scope.getUserThClass = function(usr, role) { + if ($scope.selectedGroup && $scope.isInSelectedGroup(usr, role)) { + return 'bg-success'; + } + if ($scope.selectedGrouping && $scope.isInSelectedGrouping(usr, role)) { + return 'bg-warning'; } + return ''; }; - $scope.addCsvActivity = function () { - var students = []; - $scope.fileContent.contents.trim().split(',').forEach(function (student) { - if (student) { - students.push(student); + $scope.isInSelectedGrouping = function (id, role) { + if ($scope.selectedGrouping) { + if (role === 'group') { + return $scope.selectedGrouping.groups.indexOf(id) !== -1; } - }); + + for (var i = 0; i < $scope.selectedGrouping.groups.length; i++) { + for (var j = 0; j < $scope.classGroups.length; j++) { + if ($scope.classGroups[j]._id === $scope.selectedGrouping.groups[i]) { + if ($scope.isInSelectedGroup(id, role, $scope.classGroups[j])) { + return true; + } + } + } + + } + + } + return false; + }; + + $scope.checkGroup = function (group) { var route = CONSTANTS.PROXY + '/activities/' + $scope.activity._id; - $http.put(route, {students: students}).success(function (data) { - $scope.activity.students = data.students; + if ($scope.activity.groups && $scope.activity.groups.indexOf(group._id) !== -1) { + route += '/remove'; + } + $http.put(route, {groups: [group._id]}).success(function (data) { + $scope.activity = data; }).error(function (data, status) { - console.error('Error on put', route, status); + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); }); }; + $scope.checkGrouping = function (grouping) { + var route = CONSTANTS.PROXY + '/activities/' + $scope.activity._id; + if ($scope.activity.groupings && $scope.activity.groupings.indexOf(grouping._id) !== -1) { + route += '/remove'; + } + $http.put(route, {groupings: [grouping._id]}).success(function (data) { + $scope.activity = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }; // Name @@ -315,32 +451,29 @@ angular.module('activityApp', ['myApp', 'ngStorage', 'services']) }; $scope.$on('refreshActivity', function(evt, activity) { - $scope.activity = activity; - console.log('Activity updated'); + if ($scope.activity._id === activity._id) { + $scope.activity = activity; + console.log('Activity updated'); + } }); + var finishEvent = function(activity) { + $scope.activity = activity; + $rootScope.$broadcast('refreshActivity', $scope.activity); + }; + $scope.startActivity = function () { if (!$scope.activity || $scope.activity.loading) { return; } - $scope.activity.loading = true; - $http.post(CONSTANTS.PROXY + '/activities/' + $scope.activity._id + '/event/start').success(function (s) { - $scope.activity.loading = false; - $scope.activity.start = s.start; - $scope.activity.end = s.end; - $rootScope.$broadcast('refreshActivity', $scope.activity); - }).error(function (data, status) { - console.error('Error on get /activities/' + $scope.activity._id + '/event/start ' + - JSON.stringify(data) + ', status: ' + status); - + $scope.activity.$event({event: 'start'}).$promise.then(finishEvent).fail(function (error) { + console.error(error); $.notify('Error while opening the activity:
If the session was recently closed it ' + 'might need to be cleaned by the system.
Please try again in a few seconds.', { offset: { x: 10, y: 65 }, type: 'danger'// jscs:ignore requireCamelCaseOrUpperCaseIdentifiers }); - - $scope.activity.loading = false; $rootScope.$broadcast('refreshActivity', $scope.activity); }); }; @@ -350,22 +483,12 @@ angular.module('activityApp', ['myApp', 'ngStorage', 'services']) return; } - $scope.activity.loading = true; - $http.post(CONSTANTS.PROXY + '/activities/' + $scope.activity._id + '/event/end').success(function (s) { - $scope.activity.loading = false; - $scope.activity.start = s.start; - $scope.activity.end = s.end; - $rootScope.$broadcast('refreshActivity', $scope.activity); - }).error(function (data, status) { - console.error('Error on get /activities/' + $scope.activity._id + '/event/end ' + - JSON.stringify(data) + ', status: ' + status); - + $scope.activity.$event({event: 'end'}).$promise.then(finishEvent).fail(function (error) { + console.error(error); $.notify('Error while closing the activity:
Please try again in a few seconds.', { offset: { x: 10, y: 65 }, type: 'danger'// jscs:ignore requireCamelCaseOrUpperCaseIdentifiers }); - - $scope.activity.loading = false; $rootScope.$broadcast('refreshActivity', $scope.activity); }); }; diff --git a/app/public/js/controllers/app.js b/app/public/js/controllers/app.js index f179340..4d0b320 100644 --- a/app/public/js/controllers/app.js +++ b/app/public/js/controllers/app.js @@ -20,7 +20,7 @@ // Declare app level module which depends on filters, and services angular.module('myApp', [ - 'ngRoute', 'toolbarApp', 'signupApp', 'loginApp', 'loginPluginApp', 'classApp', 'classesApp','activitiesApp', + 'ngRoute', 'toolbarApp', 'signupApp', 'loginApp', 'loginPluginApp', 'classApp', 'participantsApp', 'classesApp', 'activitiesApp', 'activityApp', 'gameApp', 'analysisApp', 'kibanaApp', 'gamesApp', 'activityApp', 'analyticsApp', 'devVisualizatorApp', 'services', 'xeditable', 'env-vars', 'ui.router' ]).run(function (editableOptions, $localStorage, $cookies) { @@ -283,7 +283,7 @@ angular.module('myApp', [ $scope.selectedGame = Games.get({gameId: activity.gameId}); }); } - } else { + } else if (!$window.location.pathname.endsWith('loginbyplugin')) { $location.url('login'); } } diff --git a/app/public/js/controllers/class-list.js b/app/public/js/controllers/class-list.js index 9c62baa..a8881ef 100644 --- a/app/public/js/controllers/class-list.js +++ b/app/public/js/controllers/class-list.js @@ -19,22 +19,57 @@ 'use strict'; angular.module('classesApp', ['ngStorage', 'services']) - .controller('ClassListCtrl', ['$scope', '$rootScope', '$location', '$http', 'Classes', '$timeout', - function ($scope, $rootScope, $location, $http, Classes, $timeout) { + .controller('ClassListCtrl', ['$scope', '$rootScope', '$location', '$http', 'Classes', 'Courses', '$timeout', 'CONSTANTS', + function ($scope, $rootScope, $location, $http, Classes, Courses, $timeout, CONSTANTS) { $scope.activity = {}; + $scope.courseId = {}; $scope.class = {}; + $scope.newCourse = {}; + + $scope.editCourse = {}; + $scope.editBoxCourse = {}; + $scope.coursesTitles = {}; $scope.goToClass = function(c) { $rootScope.$broadcast('selectClass', { class: c}); }; var getClasses = function () { - $scope.classes = Classes.my(); + Classes.my().$promise.then(function(classes) { + $scope.classes = classes; + refreshCoursesTitles(); + }); + }; + + var getCourses = function () { + $scope.courses = Courses.all().$promise.then(function(courses) { + $scope.courses = courses; + $scope.courses.unshift({_id: 'NEW', title: 'New Course'}); + $scope.courses.unshift({title: 'No Course'}); + refreshCoursesTitles(); + }); }; getClasses(); + getCourses(); $scope.loading = false; + var refreshCoursesTitles = function() { + if ($scope.courses && $scope.classes) { + $scope.classes.forEach(function (cl) { + $scope.coursesTitles[cl.courseId] = 'No Course'; + if (cl.courseId) { + for (var i = 0; $scope.courses.length; i++) { + if ($scope.courses[i]._id === cl.courseId) { + $scope.coursesTitles[cl.courseId] = $scope.courses[i].title; + break; + } + } + } + }); + } + }; + $scope.createClass = function () { var c = new Classes(); c.name = $scope.class.name ? $scope.class.name : 'New class'; @@ -44,6 +79,64 @@ angular.module('classesApp', ['ngStorage', 'services']) }); }; + $scope.editBox = function(classObj) { + return $scope.editBoxCourse[classObj._id]; + }; + + $scope.showSelectBox = function(classObj) { + return $scope.editCourse[classObj._id]; + }; + + $scope.editCourseClass = function (classObj) { + Object.keys($scope.editCourse).forEach(function(key) { + $scope.editCourse[key] = false; + $scope.editBoxCourse[classObj._id] = false; + }); + $scope.editCourse[classObj._id] = true; + }; + + $scope.acceptCourseClass = function (classObj) { + var courseId = $scope.courseId.id; + if (courseId === 'NEW') { + if ($scope.newCourse.newName) { + $http.post(CONSTANTS.PROXY + '/courses', {title: $scope.newCourse.newName}) + .success(function(data) { + $http.put(CONSTANTS.PROXY + '/classes/' + classObj._id, {courseId: data._id}) + .success(function () { + getClasses(); + }).error(function (data, status) { + console.error('Error on put /classes/' + classObj._id + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }).error(function (data, status) { + console.error('Error on put /classes/' + classObj._id + ' ' + + JSON.stringify(data) + ', status: ' + status); + }).finally(function () { + $scope.editBoxCourse[classObj._id] = false; + $scope.editCourse[classObj._id] = false; + $scope.newCourse = {}; + getCourses(); + }); + } else { + $scope.editBoxCourse[classObj._id] = true; + } + } else { + var reqObj; + if (!courseId) { + courseId = null; + } + reqObj = {courseId: courseId}; + $http.put(CONSTANTS.PROXY + '/classes/' + classObj._id, reqObj) + .success(function () { + $scope.editCourse[classObj._id] = false; + getClasses(); + }).error(function (data, status) { + console.error('Error on put /classes/' + classObj._id + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + $scope.deleteClass = function (classObj) { classObj.$remove().then(function() { $timeout(function() { @@ -53,7 +146,7 @@ angular.module('classesApp', ['ngStorage', 'services']) }; $scope.$on('refreshClasses', function () { - Classes.my().$promise.then(function(classes) { $scope.classes = classes; }); + getClasses(); }); } ]); \ No newline at end of file diff --git a/app/public/js/controllers/class.js b/app/public/js/controllers/class.js index 8c1dd8e..57671d7 100644 --- a/app/public/js/controllers/class.js +++ b/app/public/js/controllers/class.js @@ -18,14 +18,20 @@ 'use strict'; -angular.module('classApp', ['ngStorage', 'services']) - .controller('ClassCtrl', ['$rootScope', '$scope', '$attrs', '$location', '$http', 'Classes', 'CONSTANTS', - function ($rootScope, $scope, $attrs, $location, $http, Classes, CONSTANTS) { - +angular.module('classApp', ['ngStorage', 'services', 'ngAnimate', 'ngSanitize', 'ui.bootstrap']) + .controller('ClassCtrl', ['$rootScope', '$scope', '$attrs', '$location', '$http', '$uibModal', 'Classes', 'Groups', 'Groupings', 'CONSTANTS', + function ($rootScope, $scope, $attrs, $location, $http, $uibModal, Classes, Groups, Groupings, CONSTANTS) { + var groupsReady = false; + var groupingsReady = false; + var classReady = false; var onSetClass = function() { if (!$scope.class) { throw new Error('No class for ClassCtrl'); } else { + classReady = true; + if (groupsReady && groupingsReady && classReady) { + onReadyParticipants(); + } $http.get(CONSTANTS.PROXY + '/lti/keyid/' + $scope.class._id).success(function (data) { if (data && data.length > 0) { $scope.lti.key = data[0]._id; @@ -35,8 +41,76 @@ angular.module('classApp', ['ngStorage', 'services']) } }; - $attrs.$observe('classid', function() { + var onReadyGroups = function() { + groupsReady = true; + if (groupsReady && groupingsReady && classReady) { + onReadyParticipants(); + } + }; + + var onReadyGroupings = function() { + groupingsReady = true; + if (groupsReady && groupingsReady && classReady) { + onReadyParticipants(); + } + }; + + var onReadyParticipants = function() { + $scope.participants = {teachers: [], assistants: [], students: []}; + if ($scope.isUsingGroupings()) { + $scope.class.groupings.forEach(function(groupingId) { + addParticipantsFromGroupingId(groupingId); + }); + } else if ($scope.isUsingGroups()) { + $scope.class.groups.forEach(function(groupId) { + addParticipantsFromGroupId(groupId); + }); + } else { + $scope.participants = $scope.class.participants; + } + }; + + var getClassInfo = function() { + groupsReady = false; + groupingsReady = false; + classReady = false; $scope.class = Classes.get({classId: $attrs.classid}, onSetClass); + $scope.groups = Groups.get({classId: $attrs.classid}, onReadyGroups); + $scope.groupings = Groupings.get({classId: $attrs.classid}, onReadyGroupings); + }; + + var addParticipantsFromGroupingId = function(groupingId) { + for (var i = 0; i < $scope.groupings.length; i++) { + if (groupingId === $scope.groupings[i]._id) { + for (var j = 0; j < $scope.groupings[i].groups.length; j++) { + addParticipantsFromGroupId($scope.groupings[i].groups[j]); + } + break; + } + } + }; + + var addParticipantsFromGroupId = function(groupId) { + for (var i = 0; i < $scope.groups.length; i++) { + if (groupId === $scope.groups[i]._id) { + pushUsrFromGroupToParticipants($scope.groups[i], 'teachers'); + pushUsrFromGroupToParticipants($scope.groups[i], 'assistants'); + pushUsrFromGroupToParticipants($scope.groups[i], 'students'); + return; + } + } + }; + + var pushUsrFromGroupToParticipants = function(group, role) { + group.participants[role].forEach(function (usr) { + if ($scope.participants[role].indexOf(usr) === -1) { + $scope.participants[role].push(usr); + } + }); + }; + + $attrs.$observe('classid', function() { + getClassInfo(); }); $attrs.$observe('forclass', function() { @@ -50,89 +124,46 @@ angular.module('classApp', ['ngStorage', 'services']) $scope.student = {}; $scope.teacher = {}; - // Class - - $scope.changeName = function () { - $scope.class.$update(function() { - $rootScope.$broadcast('refreshClasses'); + $scope.open = function (size) { + var modalInstance = $uibModal.open({ + animation: this.animationsEnabled, + templateUrl: 'participantsModal', + controller: 'ModalInstanceCtrl', + size: size, + controllerAs: '$scope', + resolve: { + items: function () { + return { + username: $scope.username, + classId: $scope.class._id, + classes: Classes, + http: $http, + constants: CONSTANTS + }; + } + } }); - }; - - // Teachers - - $scope.isRemovable = function (dev) { - var teachers = $scope.class.teachers; - if (teachers && teachers.length === 1) { - return false; - } - if ($scope.username === dev) { - return false; - } - return true; - }; - - $scope.inviteTeacher = function () { - if ($scope.teacher.name && $scope.teacher.name.trim() !== '') { - $scope.class.teachers.push($scope.teacher.name); - $scope.class.$update(function () { - $scope.teacher.name = ''; - }); - } - }; - $scope.ejectTeacher = function (teacher) { - var route = CONSTANTS.PROXY + '/classes/' + $scope.class._id + '/remove'; - $http.put(route, {teachers: teacher}).success(function (data) { - $scope.class.teachers = data.teachers; - }).error(function (data, status) { - console.error('Error on put' + route + ' ' + - JSON.stringify(data) + ', status: ' + status); + modalInstance.result.then(function () { + }, function () { + getClassInfo(); }); }; - // Students + // Class - $scope.inviteStudent = function () { - if ($scope.student.name && $scope.student.name.trim() !== '') { - var route = CONSTANTS.PROXY + '/classes/' + $scope.class._id; - $http.put(route, {students: $scope.student.name}).success(function (data) { - $scope.class = data; - }).error(function (data, status) { - console.error('Error on put' + route + ' ' + - JSON.stringify(data) + ', status: ' + status); - }); - } + $scope.changeName = function () { + $scope.class.$update(function() { + $rootScope.$broadcast('refreshClasses'); + }); }; - - $scope.ejectStudent = function (student, fromClass) { - var route = ''; - if (fromClass) { - route = CONSTANTS.PROXY + '/classes/' + $scope.selectedClass._id + '/remove'; - } else { - route = CONSTANTS.PROXY + '/activities/' + $scope.selectedActivity._id + '/remove'; - } - $http.put(route, {students: student}).success(function (data) { - $scope.class = data; - }).error(function (data, status) { - console.error('Error on put' + route + ' ' + - JSON.stringify(data) + ', status: ' + status); - }); + $scope.isUsingGroupings = function () { + return $scope.class.groupings && $scope.class.groupings.length > 0; }; - $scope.addCsvClass = function () { - var students = []; - $scope.fileContent.contents.trim().split(',').forEach(function (student) { - if (student) { - students.push(student); - } - }); - var route = CONSTANTS.PROXY + '/classes/' + $scope.selectedClass._id; - $http.put(route, {students: students}).success(function (data) { - $scope.class.students = data.students; - }).error(function (data, status) { - console.error('Error on put', route, status); - }); + $scope.isUsingGroups = function () { + return !$scope.isUsingGroupings() && $scope.class.groups && $scope.class.groups.length > 0; }; // LTI @@ -157,4 +188,5 @@ angular.module('classApp', ['ngStorage', 'services']) } }; } - ]); \ No newline at end of file + ]); + diff --git a/app/public/js/controllers/game.js b/app/public/js/controllers/game.js index 6d29d2c..e701205 100644 --- a/app/public/js/controllers/game.js +++ b/app/public/js/controllers/game.js @@ -68,7 +68,11 @@ angular.module('gameApp', ['ngStorage', 'services', 'ngFileUpload']) $scope.public = 'btn-default'; $scope.publicGame = function () { - $scope.game.$update(); + $http.put(CONSTANTS.PROXY + '/games/' + $scope.game._id, {public: $scope.game.public}).success(function (data) { + }).error(function (data, status) { + $scope.game.public = !$scope.game.public; + console.error('Error on post /games/' + $scope.game._id + ' ' + JSON.stringify(data) + ', status: ' + status); + }); }; $scope.changeGameLink = function () { diff --git a/app/public/js/controllers/login.js b/app/public/js/controllers/login.js index de93017..8b0c30b 100644 --- a/app/public/js/controllers/login.js +++ b/app/public/js/controllers/login.js @@ -52,7 +52,7 @@ angular.module('loginApp', ['ngStorage', 'ngCookies']) $scope.loginSaml = function () { var location = CONSTANTS.APIPATH + '/login/' + $scope.saml.pluginId + '?callback=' + encodeURIComponent( $window.location.origin + $window.location.pathname + 'byplugin'); - $location.url(location); + $window.location.href = location; }; $scope.saml = null; diff --git a/app/public/js/controllers/participantsConf.js b/app/public/js/controllers/participantsConf.js new file mode 100644 index 0000000..8d17a02 --- /dev/null +++ b/app/public/js/controllers/participantsConf.js @@ -0,0 +1,406 @@ +/* + * Copyright 2016 e-UCM (http://www.e-ucm.es/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * This project has received funding from the European Union’s Horizon + * 2020 research and innovation programme under grant agreement No 644187. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 (link is external) + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +// Please note that the close and dismiss bindings are from $uibModalInstance. + +angular.module('participantsApp', []) + .controller('ModalInstanceCtrl', + function ($uibModalInstance, items) { + var $http = items.http; + var CONSTANTS = items.constants; + var Classes = items.classes; + + var $ctrl = this; + $ctrl.classId = items.classId; + $ctrl.username = items.username; + $ctrl.classGroups = []; + $ctrl.classGroupings = []; + + $ctrl.selectedGroup = undefined; + $ctrl.selectedGrouping = undefined; + + $ctrl.unlockedGroups = false; + $ctrl.unlockedGroupings = false; + + $ctrl.$onInit = function () { + Classes.get({classId: $ctrl.classId}).$promise.then(function (data) { + $ctrl.class = data; + if (data.groupings && data.groupings.length > 0) { + $ctrl.unlockGroupings(); + } else if (data.groups && data.groups.length > 0) { + $ctrl.unlockGroups(); + } + }); + updateGroups(); + updateGroupings(); + }; + + var updateGroups = function () { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.classId + '/groups'; + $http.get(route).success(function (data) { + $ctrl.classGroups = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }; + + var updateGroupings = function () { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.classId + '/groupings'; + $http.get(route).success(function (data) { + $ctrl.classGroupings = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }; + + $ctrl.unlockGroups = function() { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id + '/remove'; + if ($ctrl.unlockedGroupings) { + $http.put(route, {groupings: $ctrl.class.groupings}).success(function (data) { + $ctrl.class = data; + $ctrl.unlockedGroupings = false; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + if ($ctrl.unlockedGroups) { + $http.put(route, {groups: $ctrl.class.groups}).success(function (data) { + $ctrl.class = data; + $ctrl.unlockedGroups = false; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } else { + $ctrl.unlockedGroups = true; + } + }; + + $ctrl.unlockGroupings = function() { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id + '/remove'; + if ($ctrl.unlockedGroups) { + $http.put(route, {groups: $ctrl.class.groups}).success(function (data) { + $ctrl.class = data; + $ctrl.unlockedGroups = false; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + if ($ctrl.unlockedGroupings) { + $http.put(route, {groupings: $ctrl.class.groupings}).success(function (data) { + $ctrl.class = data; + $ctrl.unlockedGroupings = false; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } else { + $ctrl.unlockedGroupings = true; + } + }; + + $ctrl.checkGroup = function (group) { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id; + if ($ctrl.class.groups && $ctrl.class.groups.indexOf(group._id) !== -1) { + route += '/remove'; + } + $http.put(route, {groups: [group._id]}).success(function (data) { + $ctrl.class = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }; + + $ctrl.checkGrouping = function (grouping) { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id; + if ($ctrl.class.groupings && $ctrl.class.groupings.indexOf(grouping._id) !== -1) { + route += '/remove'; + } + $http.put(route, {groupings: [grouping._id]}).success(function (data) { + $ctrl.class = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }; + + $ctrl.selectGroup = function (group) { + if ($ctrl.selectedGroup && $ctrl.selectedGroup._id === group._id) { + $ctrl.selectedGroup = undefined; + } else { + $ctrl.selectedGroup = group; + } + + $ctrl.selectedGrouping = undefined; + }; + + $ctrl.isInSelectedGroup = function (usr, role, group) { + if (group) { + return group.participants[role].indexOf(usr) !== -1; + } + if ($ctrl.selectedGroup) { + return $ctrl.selectedGroup.participants[role].indexOf(usr) !== -1; + } + return false; + }; + + $ctrl.selectGrouping = function (grouping) { + if ($ctrl.selectedGrouping && $ctrl.selectedGrouping._id === grouping._id) { + $ctrl.selectedGrouping = undefined; + } else { + $ctrl.selectedGrouping = grouping; + } + + $ctrl.selectedGroup = undefined; + }; + + $ctrl.getGroupThClass = function(group) { + if ($ctrl.selectedGroup && $ctrl.selectedGroup._id === group._id) { + return 'bg-success'; + } + if ($ctrl.selectedGrouping && $ctrl.isInSelectedGrouping(group._id, 'group')) { + return 'bg-warning'; + } + return ''; + }; + + $ctrl.getUserThClass = function(usr, role) { + if ($ctrl.selectedGroup && $ctrl.isInSelectedGroup(usr, role)) { + return 'bg-success'; + } + if ($ctrl.selectedGrouping && $ctrl.isInSelectedGrouping(usr, role)) { + return 'bg-warning'; + } + return ''; + }; + + $ctrl.isInSelectedGrouping = function (id, role) { + if ($ctrl.selectedGrouping) { + if (role === 'group') { + return $ctrl.selectedGrouping.groups.indexOf(id) !== -1; + } + + for (var i = 0; i < $ctrl.selectedGrouping.groups.length; i++) { + for (var j = 0; j < $ctrl.classGroups.length; j++) { + if ($ctrl.classGroups[j]._id === $ctrl.selectedGrouping.groups[i]) { + if ($ctrl.isInSelectedGroup(id, role, $ctrl.classGroups[j])) { + return true; + } + } + } + + } + + } + return false; + }; + + // Teachers + $ctrl.isRemovable = function (tea) { + var teachers = $ctrl.class.participants.teachers; + if (teachers && teachers.length === 1) { + return false; + } + if ($ctrl.username === tea) { + return false; + } + return true; + }; + + $ctrl.inviteUser = function (role) { + var object = {participants: {}}; + var user; + switch (role) { + case 'teacher': { + user = $ctrl.teacher.name; + object.participants = {teachers: user}; + break; + } + case 'assistant': { + user = $ctrl.assistant.name; + object.participants = {assistants: user}; + break; + } + case 'student': { + user = $ctrl.student.name; + object.participants = {students: user}; + break; + } + } + if (user && user.trim() !== '') { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id; + $http.put(route, object).success(function (data) { + $ctrl.class = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + + $ctrl.ejectUser = function (user, role) { + var object = {participants: {}}; + switch (role) { + case 'teacher': { + object.participants = {teachers: user}; + break; + } + case 'assistant': { + object.participants = {assistants: user}; + break; + } + case 'student': { + object.participants = {students: user}; + break; + } + } + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id + '/remove'; + $http.put(route, object).success(function (data) { + $ctrl.class = data; + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + }; + + $ctrl.addCsvClass = function () { + var students = []; + $ctrl.fileContent.contents.trim().split(',').forEach(function (student) { + if (student) { + students.push(student); + } + }); + + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id; + $http.put(route, {participants: {students: students}}).success(function (data) { + $ctrl.class = data; + }).error(function (data, status) { + console.error('Error on put', route, status); + }); + }; + + $ctrl.createGroup = function () { + if ($ctrl.group.name && $ctrl.group.name.trim() !== '') { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id + '/groups'; + $http.post(route, { + name: $ctrl.group.name, + participants: {students: [], assistants: []} + }).success(function (data) { + updateGroups(); + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + + $ctrl.modifyGroup = function (usr, role, toAdd) { + if ($ctrl.selectedGroup) { + var route; + if (toAdd) { + route = CONSTANTS.PROXY + '/classes/groups/' + $ctrl.selectedGroup._id; + } else { + route = CONSTANTS.PROXY + '/classes/groups/' + $ctrl.selectedGroup._id + '/remove'; + } + var participants = {participants: {students: [], assistants: [], teachers: []}}; + switch (role) { + case 'student': { + participants.participants.students.push(usr); + break; + } + case 'assistant': { + participants.participants.assistants.push(usr); + break; + } + case 'teacher': { + participants.participants.teachers.push(usr); + break; + } + } + $http.put(route, participants).success(function (data) { + $ctrl.selectedGroup = data; + updateGroups(); + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + + $ctrl.removeGroup = function (group) { + if ($ctrl.group.name && $ctrl.group.name.trim() !== '') { + var route = CONSTANTS.PROXY + '/classes/groups/' + group._id; + $http.delete(route).success(function (data) { + updateGroups(); + }).error(function (data, status) { + console.error('Error on delete' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + + $ctrl.createGrouping = function () { + if ($ctrl.grouping.name && $ctrl.grouping.name.trim() !== '') { + var route = CONSTANTS.PROXY + '/classes/' + $ctrl.class._id + '/groupings'; + $http.post(route, {name: $ctrl.grouping.name, groups: []}).success(function (data) { + updateGroupings(); + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + + $ctrl.modifyGrouping = function (group, toAdd) { + if ($ctrl.selectedGrouping) { + var route; + if (toAdd) { + route = CONSTANTS.PROXY + '/classes/groupings/' + $ctrl.selectedGrouping._id; + } else { + route = CONSTANTS.PROXY + '/classes/groupings/' + $ctrl.selectedGrouping._id + '/remove'; + } + $http.put(route, {groups: [group._id]}).success(function (data) { + $ctrl.selectedGrouping = data; + updateGroupings(); + }).error(function (data, status) { + console.error('Error on put' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + + $ctrl.removeGrouping = function (grouping) { + if ($ctrl.grouping.name && $ctrl.grouping.name.trim() !== '') { + var route = CONSTANTS.PROXY + '/classes/groupings/' + grouping._id; + $http.delete(route).success(function (data) { + updateGroupings(); + }).error(function (data, status) { + console.error('Error on delete' + route + ' ' + + JSON.stringify(data) + ', status: ' + status); + }); + } + }; + } + ); diff --git a/app/public/js/services.js b/app/public/js/services.js index 0062099..9333bcb 100644 --- a/app/public/js/services.js +++ b/app/public/js/services.js @@ -25,7 +25,18 @@ services.factory('Games', ['$resource', 'CONSTANTS', return $resource(CONSTANTS.PROXY + '/games/:gameId', { gameId: '@_id' }, { my: { method: 'GET', isArray: true , url: CONSTANTS.PROXY + '/games/my' }, public: { method: 'GET', isArray: true , url: CONSTANTS.PROXY + '/games/public' }, - update: { method: 'PUT' } + update: { + method: 'PUT', + transformRequest: function (data, headersGetter) { + if (data._id !== undefined) { + delete data._id; + } + if (data.created !== undefined) { + delete data.created; + } + return angular.toJson(data); + } + } }); } ]); @@ -45,7 +56,21 @@ services.factory('Versions', ['$resource', 'CONSTANTS', gameId: '@gameId' }, { forGame: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/games/:gameId/versions' }, - update: { method: 'POST' } // TODO Update this to PUT or update all the others to POST + update: { + method: 'POST', // TODO Update this to PUT or update all the others to POST + transformRequest: function (data, headersGetter) { + if (data._id !== undefined) { + delete data._id; + } + if (data.created !== undefined) { + delete data.created; + } + if (data.trackingCode !== undefined) { + delete data.trackingCode; + } + return angular.toJson(data); + } + } }); } ]); @@ -56,7 +81,35 @@ services.factory('Classes', ['$resource', 'CONSTANTS', classId: '@_id' }, { my: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/classes/my' }, - update: { method: 'PUT' } + update: { + method: 'PUT', + transformRequest: function (data, headersGetter) { + if (data._id !== undefined) { + delete data._id; + } + if (data.created !== undefined) { + delete data.created; + } + return angular.toJson(data); + } + } + }); + } +]); + +services.factory('Courses', ['$resource', 'CONSTANTS', + function ($resource, CONSTANTS) { + return $resource(CONSTANTS.PROXY + '/courses', {}, { + all: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/courses'}, + update: { + method: 'PUT', + transformRequest: function (data, headersGetter) { + if (data._id !== undefined) { + delete data._id; + } + return angular.toJson(data); + } + } }); } ]); @@ -68,12 +121,68 @@ services.factory('Activities', ['$resource', 'CONSTANTS', var Activity = $resource(CONSTANTS.PROXY + '/activities/:activityId', { activityId: '@_id', versionId: '@versionId', - gameId: '@gameId' + gameId: '@gameId', + username: '@username' }, { my: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/activities/my' }, forClass: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/classes/:classId/activities/my' }, forGame: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/games/:gameId/versions/:versionId/activities/my' }, - update: { method: 'PUT' } + update: { + method: 'PUT', + transformRequest: function (data, headersGetter) { + if (data._id !== undefined) { + delete data._id; + } + if (data.classId !== undefined) { + delete data.classId; + } + if (data.gameId !== undefined) { + delete data.gameId; + } + if (data.versionId !== undefined) { + delete data.versionId; + } + if (data.start !== undefined) { + delete data.start; + } + if (data.end !== undefined) { + delete data.end; + } + if (data.open !== undefined) { + delete data.open; + } + if (data.created !== undefined) { + delete data.created; + } + if (data.rootId !== undefined) { + delete data.rootId; + } + if (data.trackingCode !== undefined) { + delete data.trackingCode; + } + return angular.toJson(data); + } + }, + event: { + method: 'POST', + url: CONSTANTS.PROXY + '/activities/:activityId/event/:event', + transformRequest: function (data, headersGetter) { + loadingStatus[data._id] = true; + return angular.toJson({_id: data._id, event: data.event}); + }, + transformResponse: function (data, headersGetter) { + var object = angular.fromJson(data); + console.info(data); + console.info(object); + if (object._id !== undefined) { + loadingStatus[object._id] = false; + } + return object; + } + }, + attempts: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/activities/:activityId/attempts'}, + myAttempts: { method: 'GET', url: CONSTANTS.PROXY + '/activities/:activityId/attempts/my'}, + userAttempts: { method: 'GET', url: CONSTANTS.PROXY + '/activities/:activityId/attempts/:username'} }); Object.defineProperty(Activity.prototype, 'loading', { @@ -89,6 +198,22 @@ services.factory('Activities', ['$resource', 'CONSTANTS', } ]); +services.factory('Groups', ['$resource', 'CONSTANTS', + function ($resource, CONSTANTS) { + return $resource(CONSTANTS.PROXY + '/classes/:classId/groups', {classId: '@_id'}, { + get: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/classes/:classId/groups'} + }); + } +]); + +services.factory('Groupings', ['$resource', 'CONSTANTS', + function ($resource, CONSTANTS) { + return $resource(CONSTANTS.PROXY + '/classes/:classId/groupings', {classId: '@_id'}, { + get: { method: 'GET', isArray: true, url: CONSTANTS.PROXY + '/classes/:classId/groupings'} + }); + } +]); + services.factory('Results', ['$resource', 'CONSTANTS', function ($resource, CONSTANTS) { return $resource(CONSTANTS.PROXY + '/activities/:id/results/:resultId', { @@ -101,7 +226,7 @@ services.factory('Role', ['$localStorage', function ($localStorage) { return { isUser: function () { - return $localStorage && $localStorage.user; + return ($localStorage !== undefined) && ($localStorage.user !== undefined); }, isAdmin: function () { return $localStorage.user && $localStorage.user.roles && $localStorage.user.roles.indexOf('admin') !== -1; diff --git a/app/viewRoutes.js b/app/viewRoutes.js index 38c71e9..7a517f2 100644 --- a/app/viewRoutes.js +++ b/app/viewRoutes.js @@ -30,6 +30,11 @@ var getBasePath = function(req) { return proto + '://' + req.headers['x-forwarded-host']; }; +router.get('/loginbyplugin', function (req, res) { + console.log('loginbyplugin'); + res.render('view/loginplugin', {user: JSON.stringify(req.query), basePath: getBasePath(req)}); +}); + router.get('/view/:page', function (req, res) { res.render('view/' + req.param('page'), {basePath: getBasePath(req)}); }); diff --git a/app/views/layout.jade b/app/views/layout.jade index a6ca3d2..c8ccf71 100644 --- a/app/views/layout.jade +++ b/app/views/layout.jade @@ -56,6 +56,8 @@ html script(src="bower/jquery-file-upload/js/jquery.fileupload.js") script(src="bower/checklist-model/checklist-model.js") script(src="bower/ngstorage/ngStorage.min.js") + script(src="bower/angular-animate/angular-animate.min.js") + script(src="bower/angular-sanitize/angular-sanitize.min.js") script(src="bower/ng-file-upload/ng-file-upload.js") script(src="bower/d3/d3.min.js") script(src='libs/xapicollection.min.js') @@ -72,10 +74,12 @@ html script(src='js/controllers/kibana.js') script(src='js/controllers/game-list.js') script(src='js/controllers/class.js') + script(src='js/controllers/participantsConf.js') script(src='js/controllers/class-list.js') script(src='js/controllers/activity.js') script(src='js/controllers/activity-list.js') script(src='js/controllers/app.js') script(src='js/env-vars.js') block toolbar + block loginplugin ui-view \ No newline at end of file diff --git a/app/views/view/classactivity.jade b/app/views/view/classactivity.jade index ceaa047..a4b5942 100644 --- a/app/views/view/classactivity.jade +++ b/app/views/view/classactivity.jade @@ -1,20 +1,16 @@ //- Copyright 2016 e-UCM (http://www.e-ucm.es/) - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. This project has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No 644187. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 (link is external) - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - .container.theme-showcase(ng-if='selectedClass' ng-controller='ClassCtrl' ng-attr-classid='{{ selectedClass._id }}') div(ng-controller='ActivityListCtrl', ng-attr-classid='{{ class._id }}' ng-class='(isTeacher() ? "col-md-8" : "col-md-12")') .panel.panel-default @@ -56,7 +52,7 @@ td a(ng-href='#', ng-click='goToGame(getGameById(activity.gameId))') {{ getGameById(activity.gameId).title }} td {{activity._id | prettyDateId }} - td(ng-if='isTeacher()', ng-controller='ActivityCtrl' activity='{{ activity }}') + td(ng-if='isTeacher()', ng-controller='ActivityCtrl' lite activity='{{ activity }}') button.btn-success(ng-if='activityState() == 2' ng-click='endActivity()') Opened button.btn-warning(ng-if='activityState() == 0' ng-click='startActivity()') Closed button.btn-info(ng-if='activityState() == 1') Loading @@ -96,64 +92,250 @@ .col-md-10(ng-if='lti.key') label Launch URL: kbd.label-success {{ lti.launch }} + .col-md-4(ng-if='isTeacher()') .panel.panel-default .panel-heading h3 - label Class teachers + label Configure your class participants .panel-body - p An activity will inherit all the class teachers when created - .panel.panel-primary - .panel-heading Add teacher to class - .panel-body - label Username: - input.form-control(type='text' ng-model='teacher.name') - a.btn.btn-primary(type='button', ng-click='inviteTeacher(true)') - span.glyphicon.glyphicon-plus.right10 - table.table.table-hover - thead - tr - th Teacher - th Remove - tbody - tr(ng-if='class.teachers.length == 0') - td(colspan=6) - div.alert.alert-warning(style='margin-bottom: 0px') No students found - tr(ng-repeat='teacher in class.teachers') - td - label {{teacher}} - td - a(ng-if='isRemovable(teacher)' ng-click='ejectTeacher(teacher, true)') - span.glyphicon.glyphicon-remove-sign + button.btn.btn-primary(ng-click="open('lg')") Configure + span.right20 + span.glyphicon.glyphicon-user .panel.panel-default .panel-heading h3 - label Class Students + label List of current participants + label.label-success(ng-if='isUsingGroups()') Using a subclass (Groups selected) + label.label-warning(ng-if='isUsingGroupings()') Using a subclass (Groupings selected) .panel-body - p An activity will inherit all the class students when created - .panel.panel-primary - .panel-heading Add students to class - .panel-body - label Add students to class from file: - input#inputFile.file(type='file' file-reader="fileContent") - button.btn.btn-primary(ng-click='addCsvClass(true)') Add class - hr - label Username: - input.form-control(type='text' ng-model='student.name') - a.btn.btn-primary(type='button', ng-click='inviteStudent(true)') - span.glyphicon.glyphicon-plus.right10 table.table.table-hover thead tr - th Student - th Remove + th User + th Role tbody - tr(ng-if='class.students.length == 0') - td(colspan=6) - div.alert.alert-warning(style='margin-bottom: 0px') No students found - tr(ng-repeat='student in class.students') + tr(ng-repeat='teacher in participants.teachers') + td + label {{teacher}} + td + label teacher + tr(ng-repeat='assistant in participants.assistants') + td + label {{assistant}} + td + label assistant + tr(ng-repeat='student in participants.students') td label {{student}} td - a(ng-click='ejectStudent(student, true)') - span.glyphicon.glyphicon-remove-sign + label student + // PARTICIPANTS MODAL + script#participantsModal(type="text/ng-template") + div.modal-header.row + .modal-title + .col-md-6 + h1 Configure Participants + .col-md-12(ng-if='!$scope.unlockedGroups && !$scope.unlockedGroupings') + label.alert-info *If you want restrict the default access to the class with groups o groupings, please click on a star icon and check the groups or groupings + .col-md-12(ng-if='$scope.unlockedGroups') + label.alert-success You are using groups (if you want use all participants, please, click star icon in groups area) + .col-md-12(ng-if='$scope.unlockedGroupings') + label.alert-warning You are using groupings (if you want use all participants, please, click star icon in groupings area) + div.modal-body.row + .col-md-6 + .panel.panel-default + .panel-heading + h3 + label Class Teachers + .panel-body + div(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping') + p An activity will inherit all the class teachers when created + .panel.panel-primary + .panel-heading Add teacher to class + .panel-body + label Username: + input.form-control(type='text' ng-model='$scope.teacher.name') + a.btn.btn-primary(type='button', ng-click='$scope.inviteUser("teacher")') + span.glyphicon.glyphicon-plus.right10 + table.table.table-hover + thead + tr + th Teacher + th(ng-show='$scope.selectedGroup') Add or Remove + th(ng-show='!$scope.selectedGroup') Remove + tbody + tr(ng-if='$scope.class.participants.teachers.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No students found + tr(ng-repeat='teacher in $scope.class.participants.teachers') + td(ng-class='$scope.getUserThClass(teacher, "teachers")') + label {{teacher}} + td(ng-show='$scope.selectedGroup && !$scope.isInSelectedGroup(teacher, "teachers")' ng-class='$scope.isInSelectedGroup(teacher, "teachers") ? "bg-success" : ""') + a(ng-click='$scope.modifyGroup(teacher, "teacher", true)') + span.glyphicon.glyphicon-plus + td(ng-show='$scope.isInSelectedGroup(teacher, "teachers")' ng-class='$scope.isInSelectedGroup(teacher, "teachers") ? "bg-success" : ""') + a(ng-click='$scope.modifyGroup(teacher, "teacher", false)') + span.glyphicon.glyphicon-minus + td(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping' ng-class='$scope.isInSelectedGroup(teacher, "teachers") ? "bg-success" : ""') + a(ng-if='$scope.isRemovable(teacher)' ng-click='$scope.ejectUser(teacher, "teacher")') + span.glyphicon.glyphicon-remove-sign + .panel.panel-default + .panel-heading + h3 + label Class Assistants + .panel-body + div(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping') + p An activity will inherit all the class assistants when created + .panel.panel-primary + .panel-heading Add assistant to class + .panel-body + label Username: + input.form-control(type='text' ng-model='$scope.assistant.name') + a.btn.btn-primary(type='button', ng-click='$scope.inviteUser("assistant")') + span.glyphicon.glyphicon-plus.right10 + table.table.table-hover + thead + tr + th Assistant + th(ng-show='$scope.selectedGroup') Add or Remove + th(ng-show='!$scope.selectedGroup') Remove + tbody + tr(ng-if='$scope.class.participants.assistants.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No assistants found + tr(ng-repeat='assistant in $scope.class.participants.assistants') + td(ng-class='$scope.getUserThClass(assistant, "assistants")') + label {{assistant}} + td(ng-show='$scope.selectedGroup && !$scope.isInSelectedGroup(assistant, "assistants")' ng-class='$scope.isInSelectedGroup(assistant, "assistants") ? "bg-success" : ""') + a(ng-click='$scope.modifyGroup(assistant, "assistant", true)') + span.glyphicon.glyphicon-plus + td(ng-show='$scope.isInSelectedGroup(assistant, "assistants")' ng-class='$scope.isInSelectedGroup(assistant, "assistants") ? "bg-success" : ""') + a(ng-click='$scope.modifyGroup(assistant, "assistant", false)') + span.glyphicon.glyphicon-minus + td(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping' ng-class='$scope.isInSelectedGroup(assistant, "assistants") ? "bg-success" : ""') + a(ng-click='$scope.ejectUser(assistant, "assistant")') + span.glyphicon.glyphicon-remove-sign + .panel.panel-default + .panel-heading + h3 + label Class Students + .panel-body + div(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping') + p An activity will inherit all the class students when created + .panel.panel-primary + .panel-heading Add students to class + .panel-body + label Add students to class from file: + input#inputFile.file(type='file' file-reader="$scope.fileContent") + button.btn.btn-primary(ng-click='$scope.addCsvClass()') Add class + hr + label Username: + input.form-control(type='text' ng-model='$scope.student.name') + a.btn.btn-primary(type='button', ng-click='$scope.inviteUser("student")') + span.glyphicon.glyphicon-plus.right10 + table.table.table-hover + thead + tr + th Student + th(ng-show='$scope.selectedGroup') Add or Remove + th(ng-show='!$scope.selectedGroup') Remove + tbody + tr(ng-if='$scope.class.participants.students.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No students found + tr(ng-repeat='student in $scope.class.participants.students') + td(ng-class='$scope.getUserThClass(student, "students")') + label {{student}} + td(ng-show='$scope.selectedGroup && !$scope.isInSelectedGroup(student, "students")' ng-class='$scope.isInSelectedGroup(student, "students") ? "bg-success" : ""') + a(ng-click='$scope.modifyGroup(student, "student", true)') + span.glyphicon.glyphicon-plus + td(ng-show='$scope.isInSelectedGroup(student, "students")' ng-class='$scope.isInSelectedGroup(student, "students") ? "bg-success" : ""') + a(ng-click='$scope.modifyGroup(student, "student", false)') + span.glyphicon.glyphicon-minus + td(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping' ng-class='$scope.isInSelectedGroup(student, "students") ? "bg-success" : ""') + a(ng-click='$scope.ejectUser(student, "student")') + span.glyphicon.glyphicon-remove-sign + .col-md-6 + .panel.panel-default + .panel-heading + .row + .col-md-9 + h3 + label Class Groups + .col-md-3 + a.btn.btn-default(type='button', ng-click='$scope.unlockGroups()' ng-class='$scope.unlockedGroups ? "btn-success" : "btn-default"') + span.glyphicon(ng-class='$scope.unlockedGroups ? "glyphicon-star" : "glyphicon-star-empty"') + .panel-body + div(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping') + p The groups help you to configure your users activities easely + .panel.panel-success + .panel-heading Add group to class + .panel-body + label Group name: + input.form-control(type='text' ng-model='$scope.group.name') + a.btn.btn-success(type='button', ng-click='$scope.createGroup()') + span.glyphicon.glyphicon-plus.right10 + table.table.table-hover + thead + tr + th Group + th(ng-if='$scope.unlockedGroups') Use this groups in the class + th Remove + tbody + tr(ng-if='$scope.classGroups.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No groups created yet + tr(ng-repeat='group in $scope.classGroups') + td(ng-class='$scope.getGroupThClass(group)') + a.btn(ng-click='$scope.selectGroup(group)') {{group.name}} + td(ng-if='$scope.unlockedGroups') + a.btn(ng-click='$scope.checkGroup(group)') + span.glyphicon(ng-class='$scope.class.groups.indexOf(group._id)===-1 ? "glyphicon-unchecked" : "glyphicon-saved"') + td(ng-show='$scope.selectedGrouping && !$scope.isInSelectedGrouping(group._id, "group")' ng-class='$scope.getGroupThClass(group)') + a.btn(ng-click='$scope.modifyGrouping(group, true)') + span.glyphicon.glyphicon-plus + td(ng-show='$scope.selectedGrouping && $scope.isInSelectedGrouping(group._id, "group")' ng-class='$scope.getGroupThClass(group)') + a.btn(ng-click='$scope.modifyGrouping(group, false)') + span.glyphicon.glyphicon-minus + td(ng-show='!$scope.selectedGrouping' ng-class='$scope.getGroupThClass(group)') + a.btn(ng-click='$scope.removeGroup(group)') + span.glyphicon.glyphicon-remove-sign + .panel.panel-default + .panel-heading + .row + .col-md-9 + h3 + label Class Groupings + .col-md-3 + a.btn(type='button', ng-click='$scope.unlockGroupings()' ng-class='$scope.unlockedGroupings ? "btn-warning" : "btn-default"') + span.glyphicon(ng-class='$scope.unlockedGroupings ? "glyphicon-star" : "glyphicon-star-empty"') + .panel-body + div(ng-show='!$scope.selectedGroup && !$scope.selectedGrouping') + p The groupings help you to configure your users activities grouping groups of participants + .panel.panel-warning + .panel-heading Add grouping to class + .panel-body + label Grouping name: + input.form-control(type='text' ng-model='$scope.grouping.name') + a.btn.btn-warning(type='button', ng-click='$scope.createGrouping()') + span.glyphicon.glyphicon-plus.right10 + table.table.table-hover + thead + tr + th Grouping + th(ng-if='$scope.unlockedGroupings') Use this groupings in the class + th Remove + tbody + tr(ng-if='$scope.classGroupings.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No groups created yet + tr(ng-class='$scope.selectedGrouping._id === grouping._id ? "bg-warning" : ""' ng-repeat='grouping in $scope.classGroupings') + td + a.btn(ng-click='$scope.selectGrouping(grouping)') {{grouping.name}} + td(ng-if='$scope.unlockedGroupings') + a.btn(ng-click='$scope.checkGrouping(grouping)') + span.glyphicon(ng-class='$scope.class.groupings.indexOf(grouping._id)===-1 ? "glyphicon-unchecked" : "glyphicon-saved"') + td + a(ng-click='$scope.removeGrouping(grouping)') +span.glyphicon.glyphicon-remove-sign \ No newline at end of file diff --git a/app/views/view/data.jade b/app/views/view/data.jade index 745635f..03d22c5 100644 --- a/app/views/view/data.jade +++ b/app/views/view/data.jade @@ -46,10 +46,10 @@ include data/information #realtime.tab-pane include data/realtime/realtime - #students.tab-pane - include data/students - #teachers.tab-pane - include data/teachers + #participants.tab-pane + include data/participants + #attempts.tab-pane + include data/attempts #config.tab-pane include data/config #analysis.tab-pane @@ -58,7 +58,7 @@ include data/dev-visualization #game-analytics.tab-pane include data/game-analytics - .panel.panel-default(ng-if='!isDeveloper() && selectedActivity' ng-controller='ActivityCtrl' lite='true' activity='{{ selectedActivity }}' style='width=100%') + .panel.panel-default(ng-if='!isDeveloper() && selectedActivity' ng-controller='ActivityCtrl' activity='{{ selectedActivity }}' style='width=100%') .panel-body(ng-if='!game || !version || !activity') img(src='loading.svg') .panel-heading(ng-if='game && version && activity' style='font-size: 2.5em') diff --git a/app/views/view/data/attempts.jade b/app/views/view/data/attempts.jade new file mode 100644 index 0000000..394facf --- /dev/null +++ b/app/views/view/data/attempts.jade @@ -0,0 +1,88 @@ +//- + Copyright 2016 e-UCM (http://www.e-ucm.es/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + This project has received funding from the European Union’s Horizon + 2020 research and innovation programme under grant agreement No 644187. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 (link is external) + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +.container-fluid.theme-showcase + h1 Attempts + .row(ng-if='isTeacher()') + hr + .col-xs-12 + h4 All Attempts + .row + .col-md-12 + table.table.table-hover + thead + tr + th Type + th Username + th Animal Name + th Created + th Last start + th Last end + th Last + th Attempts + tbody + tr(ng-if='!attempts || attempts.length === 0') + td(colspan=8) + div.alert.alert-info(style='margin-bottom: 0px') No attempts found + tbody(ng-repeat='gameplay in attempts') + tr + td.text-center + span.glyphicon(ng-class='(gameplay.playerType === "identified") ? "glyphicon-user" : "glyphicon-sunglasses"') + td + label(tooltip="gameplay.playerName" tooltip-placement="left") {{gameplay.playerType === 'anonymous' ? 'Anonymous' : gameplay.playerName }} + td + label {{gameplay.animalName}} + td + label {{ gameplay.firstSessionStarted | prettyDate }} + td + label {{ gameplay.attempts[gameplay.sessions-1].start | prettyDate }} + td + label {{ gameplay.attempts[gameplay.sessions-1].end | prettyDate }} + td + a.btn.btn-primary(type='button' ng-click='viewAttempt(gameplay, gameplay.attempts[gameplay.sessions-1])') + .glyphicon.glyphicon-stats + td + a.btn.btn-primary(type='button', ng-click='gameplaysShown[gameplay._id] = !gameplaysShown[gameplay._id]') + label {{gameplay.sessions}}   + .glyphicon(ng-class='gameplaysShown[gameplay._id] ? "glyphicon-remove-sign" : "glyphicon-plus"') + tr.attemptRow(ng-show='gameplaysShown[gameplay._id]') + td(colspan=8) + .panel.panel-primary + .panel-heading {{ gameplay.playerName }} attempts + .panel-body + table.table.table-hover + thead + tr + th Number + th Start + th End + th View Stats + tbody + tr(ng-if='gameplay.attempts.length === 0') + td(colspan=4) + div.alert.alert-warning(style='margin-bottom: 0px') No attempts for this player + tbody(ng-repeat='attempt in gameplay.attempts') + tr + td + label {{ attempt.number }} + td + label {{ attempt.start | prettyDate }} + td + label {{ attempt.end | prettyDate }} + td + a.btn.btn-primary(type='button' ng-click='viewAttempt(gameplay, attempt)') + .glyphicon.glyphicon-stats \ No newline at end of file diff --git a/app/views/view/data/information.jade b/app/views/view/data/information.jade index 74e1012..ff891e7 100644 --- a/app/views/view/data/information.jade +++ b/app/views/view/data/information.jade @@ -23,7 +23,8 @@ a(href='{{ game.link }}') {{ game.link }} .col-xs-12 | Tracking code: - kbd.left10 {{ version.trackingCode }} + kbd.left10(ng-if='isDeveloper()') {{ version.trackingCode }} + kbd.left10(ng-if='isTeacher()') {{ activity.trackingCode }} .row(ng-if='isDeveloper()') hr .col-xs-12 diff --git a/app/views/view/data/menu.jade b/app/views/view/data/menu.jade index 94f1f47..1aa571f 100644 --- a/app/views/view/data/menu.jade +++ b/app/views/view/data/menu.jade @@ -21,13 +21,13 @@ ul.nav span.glyphicon.glyphicon-info-sign.right20 | Information li(ng-if='isTeacher()') - span.left-menu-item(href="#teachers", role="tab", data-toggle="tab", ng-click="view = 'teachers'") - span.glyphicon.glyphicon-briefcase.right20 - | Teachers - li(ng-if='isTeacher()') - span.left-menu-item(href="#students", role="tab", data-toggle="tab", ng-click="view = 'students'") + span.left-menu-item(href="#participants", role="tab", data-toggle="tab", ng-click="view = 'participants'") span.glyphicon.glyphicon-apple.right20 - | Students + | Participants + li + span.left-menu-item(href="#attempts", role="tab", data-toggle="tab", ng-click="view = 'attempts'") + span.glyphicon.glyphicon-tasks.right20 + | Attempts li(ng-if='isTeacher() || isStudent()') span.left-menu-item(href="#realtime", role="tab", data-toggle="tab", ng-click="view = 'realtime'") span.glyphicon.glyphicon-dashboard.right20 diff --git a/app/views/view/data/participants.jade b/app/views/view/data/participants.jade new file mode 100644 index 0000000..780fc44 --- /dev/null +++ b/app/views/view/data/participants.jade @@ -0,0 +1,141 @@ +//- + Copyright 2016 e-UCM (http://www.e-ucm.es/) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + This project has received funding from the European Union’s Horizon + 2020 research and innovation programme under grant agreement No 644187. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 (link is external) + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +.container-fluid.theme-showcase + h1 Participants + .row + .col-md-12 + .checkbox + label + input(type="checkbox" ng-change='allowAnonymous()' ng-model='activity.allowAnonymous') + | Allow anonymous users + br + div.modal-header.row + .modal-title + .col-md-12(ng-if='!unlockedGroups && !unlockedGroupings') + label.alert-info *If you want restrict the default access to the class with groups o groupings, please click on a star icon and check the groups or groupings + .col-md-12(ng-if='unlockedGroups') + label.alert-success You are using groups (if you want use all participants, please, click star icon in groups area) + .col-md-12(ng-if='unlockedGroupings') + label.alert-warning You are using groupings (if you want use all participants, please, click star icon in groupings area) + div.modal-body.row + .col-md-6 + .panel.panel-default + .panel-heading + h3 + label Class Teachers + .panel-body + table.table.table-hover + thead + tr + th Teacher + tbody + tr(ng-if='class.participants.teachers.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No students found + tr(ng-repeat='teacher in class.participants.teachers') + td(ng-class='getUserThClass(teacher, "teachers")') + label {{teacher}} + .panel.panel-default + .panel-heading + h3 + label Class Assistants + .panel-body + table.table.table-hover + thead + tr + th Assistant + tbody + tr(ng-if='class.participants.assistants.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No assistants found + tr(ng-repeat='assistant in class.participants.assistants') + td(ng-class='getUserThClass(assistant, "assistants")') + label {{assistant}} + .panel.panel-default + .panel-heading + h3 + label Class Students + .panel-body + table.table.table-hover + thead + tr + th Student + tbody + tr(ng-if='class.participants.students.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No students found + tr(ng-repeat='student in class.participants.students') + td(ng-class='getUserThClass(student, "students")') + label {{student}} + .col-md-6 + .panel.panel-default + .panel-heading + .row + .col-md-9 + h3 + label Class Groups + .col-md-3 + a.btn.btn-default(type='button', ng-click='unlockGroups()' ng-class='unlockedGroups ? "btn-success" : "btn-default"') + span.glyphicon(ng-class='unlockedGroups ? "glyphicon-star" : "glyphicon-star-empty"') + .panel-body + table.table.table-hover + thead + tr + th Group + th(ng-if='unlockedGroups') Use this groups in the class + tbody + tr(ng-if='classGroups.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No groups created yet + tr(ng-repeat='group in classGroups') + td(ng-class='getGroupThClass(group)') + a.btn(ng-click='selectGroup(group)') {{group.name}} + td(ng-if='unlockedGroups') + a.btn(ng-click='checkGroup(group)') + span.glyphicon(ng-class='activity.groups.indexOf(group._id)===-1 ? "glyphicon-unchecked" : "glyphicon-saved"') + td(ng-show='selectedGrouping && !isInSelectedGrouping(group._id, "group")' ng-class='getGroupThClass(group)') + a.btn(ng-click='modifyGrouping(group, true)') + span.glyphicon.glyphicon-plus + td(ng-show='selectedGrouping && isInSelectedGrouping(group._id, "group")' ng-class='getGroupThClass(group)') + a.btn(ng-click='modifyGrouping(group, false)') + span.glyphicon.glyphicon-minus + .panel.panel-default + .panel-heading + .row + .col-md-9 + h3 + label Class Groupings + .col-md-3 + a.btn(type='button', ng-click='unlockGroupings()' ng-class='unlockedGroupings ? "btn-warning" : "btn-default"') + span.glyphicon(ng-class='unlockedGroupings ? "glyphicon-star" : "glyphicon-star-empty"') + .panel-body + table.table.table-hover + thead + tr + th Grouping + th(ng-if='unlockedGroupings') Use this groupings in the class + tbody + tr(ng-if='classGroupings.length == 0') + td(colspan=6) + div.alert.alert-warning(style='margin-bottom: 0px') No groups created yet + tr(ng-class='selectedGrouping._id === grouping._id ? "bg-warning" : ""' ng-repeat='grouping in classGroupings') + td + a.btn(ng-click='selectGrouping(grouping)') {{grouping.name}} + td(ng-if='unlockedGroupings') + a.btn(ng-click='checkGrouping(grouping)') + span.glyphicon(ng-class='activity.groupings.indexOf(grouping._id)===-1 ? "glyphicon-unchecked" : "glyphicon-saved"') diff --git a/app/views/view/data/realtime/realtime.jade b/app/views/view/data/realtime/realtime.jade index 0170fb8..c1d898c 100644 --- a/app/views/view/data/realtime/realtime.jade +++ b/app/views/view/data/realtime/realtime.jade @@ -16,7 +16,7 @@ limitations under the License. link(rel="stylesheet", href="css/realtime.css") -.container-fluid.theme-showcase.height100(ng-if='selectedActivity' ng-controller="ActivityCtrl" activityid='{{selectedActivity._id}}' role="tabpanel" style="position:relative") +.container-fluid.theme-showcase.height100(role="tabpanel" style="position:relative") .row(ng-if="isTeacher()") h1(ng-if="results.length > 0") Alerts & Warnings .row(ng-show="player") diff --git a/app/views/view/data/students.jade b/app/views/view/data/students.jade deleted file mode 100644 index 68c74bb..0000000 --- a/app/views/view/data/students.jade +++ /dev/null @@ -1,64 +0,0 @@ -//- - Copyright 2016 e-UCM (http://www.e-ucm.es/) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - This project has received funding from the European Union’s Horizon - 2020 research and innovation programme under grant agreement No 644187. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 (link is external) - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -.container-fluid.theme-showcase(ng-if='selectedActivity' ng-controller="ActivityCtrl" lite='true' ng-attr-activityid='{{selectedActivity._id}}') - h1 Students - .row - .col-md-12 - .checkbox - label - input(type="checkbox" ng-change='allowAnonymous()' ng-model='activity.allowAnonymous') - | Allow anonymous users - br - .row - .col-md-12 - .panel.panel-primary - .panel-heading Add students to this activity - .panel-body - label Add class from file: - input.form-control#inputFile.file(type='file' file-reader="fileContent") - span   - button.btn.btn-primary(ng-click='addCsvActivity()') Add class - hr - label Username: - input.form-control(type='text' ng-model='student.name') - span   - a.btn.btn-primary(type='button', ng-click='inviteStudent()') - .glyphicon.glyphicon-plus - span   - button.btn.btn-primary Add missing students from class  - .glyphicon.glyphicon-plus(ng-click='updateActivityToClass(class)' title="Add missing students from class") - span   - button.btn.btn-primary Reset students to class students  - .glyphicon.glyphicon-adjust(ng-click='resetActivityToClass(class)' title="Reset students to class students") - .row - .col-md-12 - table.table.table-hover - thead - tr - th Students - th Remove - tbody - tr(ng-if='activity.students.length == 0') - td(colspan=6) - div.alert.alert-warning(style='margin-bottom: 0px') No students found - tr(ng-repeat='student in activity.students') - td - label {{student}} - td - a(ng-click='ejectStudent(student)') - span.glyphicon.glyphicon-remove-sign diff --git a/app/views/view/data/teachers.jade b/app/views/view/data/teachers.jade deleted file mode 100644 index 4a3dbcd..0000000 --- a/app/views/view/data/teachers.jade +++ /dev/null @@ -1,45 +0,0 @@ -//- - Copyright 2016 e-UCM (http://www.e-ucm.es/) - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - This project has received funding from the European Union’s Horizon - 2020 research and innovation programme under grant agreement No 644187. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 (link is external) - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -.container-fluid.theme-showcase(ng-if='selectedActivity' ng-controller="ActivityCtrl" lite='true' ng-attr-activityid='{{selectedActivity._id}}') - h1 Teachers - .row - .col-md-12 - .panel.panel-primary - .panel-heading Add teacher to this activity - .panel-body - label Username: - input.form-control(type='text' ng-model='teacher.name') - a.btn.btn-primary(type='button', ng-click='inviteTeacher()') - .glyphicon.glyphicon-plus - .row - .col-md-12 - table.table.table-hover - thead - tr - th Teachers - th Remove - tbody - tr(ng-if='activity.teachers.length == 0') - td(colspan=6) - div.alert.alert-warning(style='margin-bottom: 0px') No teachers found - tr(ng-repeat='teacher in activity.teachers') - td - label {{teacher}} - td - a(ng-if='isRemovable(teacher)' ng-click='ejectTeacher(teacher)') - span.glyphicon.glyphicon-remove-sign diff --git a/app/views/view/gameactivity.jade b/app/views/view/gameactivity.jade index 2a9d467..dc4cda1 100644 --- a/app/views/view/gameactivity.jade +++ b/app/views/view/gameactivity.jade @@ -48,7 +48,7 @@ tr(ng-if='activities.length == 0') td(colspan=6) div.alert.alert-info(style='margin-bottom: 0px') No activities found - tr(ng-repeat='activity in activities', ng-controller='ActivityCtrl' activity='{{ activity }}') + tr(ng-repeat='activity in activities', ng-controller='ActivityCtrl' lite activity='{{ activity }}') td span.glyphicon.glyphicon-stats.right10 a(ng-href="#" ng-click='goToActivity(activity)') {{activity.name}} diff --git a/app/views/view/home.jade b/app/views/view/home.jade index 900b592..3073a0c 100644 --- a/app/views/view/home.jade +++ b/app/views/view/home.jade @@ -50,7 +50,7 @@ tr(ng-if='activities.length == 0') td(colspan=6) div.alert.alert-info(style='margin-bottom: 0px') No activities found - tr(ng-repeat='localactivity in activities', ng-controller='ActivityCtrl' activity='{{ localactivity }}') + tr(ng-repeat='localactivity in activities', ng-controller='ActivityCtrl' lite activity='{{ localactivity }}') td span.glyphicon.glyphicon-stats.right10 a(ng-href="#" ng-click='goToActivity(localactivity)') {{ localactivity.name }} @@ -117,6 +117,8 @@ thead tr th Name + th Course + th(ng-if='isTeacher()') th Created th(ng-if='isTeacher()') Delete tbody @@ -127,6 +129,18 @@ td(ng-controller='AppCtrl') span.glyphicon.glyphicon-stats.right10 a(ng-href='#', ng-click='goToClass(class)') {{class.name}} + td(ng-if='showSelectBox(class) && !editBox(class)') + select.form-control(ng-model='courseId.id') + option(ng-repeat='course in courses' value='{{ course._id }}') {{ course.title }} + td(ng-if='!showSelectBox(class) && !editBox(class)') {{coursesTitles[class.courseId]}} + td(ng-if='editBox(class)') + input.form-control(placeholder='New Course Name' type='text' ng-model='newCourse.newName') + td(ng-if='isTeacher() && !showSelectBox(class)') + a(ng-click='editCourseClass(class)') + span.glyphicon.glyphicon-pencil + td(ng-if='isTeacher() && showSelectBox(class)') + a(ng-click='acceptCourseClass(class)') + span.glyphicon.glyphicon-ok td {{class._id | prettyDateId }} td(ng-if='isTeacher()') a(ng-click='deleteClass(class)') diff --git a/app/views/view/loginplugin.jade b/app/views/view/loginplugin.jade index bf6ae6d..0aa8047 100644 --- a/app/views/view/loginplugin.jade +++ b/app/views/view/loginplugin.jade @@ -11,4 +11,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -.row(ng-controller='LoginPluginCtrl' ng-init="setupUser(#{user})") + +extends ../page + +block loginplugin + p login by plugin + #login(ng-controller='LoginPluginCtrl' ng-init="setupUser(#{user})") \ No newline at end of file diff --git a/bower.json b/bower.json index e190b90..3a9e13f 100644 --- a/bower.json +++ b/bower.json @@ -9,9 +9,12 @@ "angular-mocks": "1.5.8", "angular-xeditable": "0.5.0", "angular-gridster": "0.13.15", + "angular-animate": "1.5.8", + "angular-sanitize": "1.5.8", "checklist-model": "0.10.0", "angular-bootstrap": "2.1.4", "angular-ui-router": "^1.0.3", + "angular-ui-bootstrap": "^2.5.6", "ng-file-upload": "12.2.13", "ngstorage" : "0.3.11", "bootstrap": "3.3.7",