Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -r953f62a7fc515e2dc5c4ad983df233070cf7a82c -r34bc1c178bd5ada01543d5b4637487322d3ff565 Binary files differ Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r32d1d2ae1e17adb6839a95ecd2331a08168d11a8 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 32d1d2ae1e17adb6839a95ecd2331a08168d11a8) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -90,6 +90,7 @@ index.monitor =Monitor index.participate =Participate index.dummymonitor =Dummy Monitor +index.kumalive =Kumalive title.import.result =Import tool content result title.import =Import tool content title.import.instruction =Please choose LAMS sequence to import. Index: lams_central/src/java/org/lamsfoundation/lams/web/DisplayGroupAction.java =================================================================== diff -u -r97f98aae7a2eedbfb3f591309f4d5cf8356d0e08 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_central/src/java/org/lamsfoundation/lams/web/DisplayGroupAction.java (.../DisplayGroupAction.java) (revision 97f98aae7a2eedbfb3f591309f4d5cf8356d0e08) +++ lams_central/src/java/org/lamsfoundation/lams/web/DisplayGroupAction.java (.../DisplayGroupAction.java) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -158,6 +158,8 @@ String name = org.getEnableSingleActivityLessons() ? "index.addlesson.single" : "index.addlesson"; links.add(new IndexLinkBean(name, "javascript:showAddLessonDialog(" + org.getOrganisationId() + ")", "fa fa-fw fa-plus", null)); + links.add(new IndexLinkBean("index.kumalive", "javascript:showKumaliveDialog(" + org.getOrganisationId() + ")", + "fa fa-fw fa-bolt", null)); } moreLinks.add(new IndexLinkBean("index.searchlesson", "javascript:showSearchLessonDialog(" + org.getOrganisationId() + ")", "fa fa-fw fa-search", Index: lams_central/web/includes/javascript/main.js =================================================================== diff -u -r97f98aae7a2eedbfb3f591309f4d5cf8356d0e08 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_central/web/includes/javascript/main.js (.../main.js) (revision 97f98aae7a2eedbfb3f591309f4d5cf8356d0e08) +++ lams_central/web/includes/javascript/main.js (.../main.js) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -534,7 +534,25 @@ }, true); } +function showKumaliveDialog(orgID) { + showDialog("dialogKumalive", { + 'data' : { + 'orgID' : orgID + }, + 'height': Math.max(380, Math.min(800, $(window).height() - 30)), + 'width' : Math.max(380, Math.min(1280, $(window).width() - 60)), + 'title' : LABELS.KUMALIVE_TITLE, + 'open' : function() { + var dialog = $(this); + // load contents after opening the dialog + $('iframe', dialog) + .attr('src', LAMS_URL + + '/learning/kumalive.jsp?organisationID=' + dialog.data('orgID')); + } + }); +} + function closeAddSingleActivityLessonDialog(action) { var id = 'dialogAddSingleActivityLesson', dialog = $('#' + id), Index: lams_central/web/main.jsp =================================================================== diff -u -r7af8f9e4cffe38079f5b0444a1ff9fc6733f825d -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_central/web/main.jsp (.../main.jsp) (revision 7af8f9e4cffe38079f5b0444a1ff9fc6733f825d) +++ lams_central/web/main.jsp (.../main.jsp) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -87,7 +87,9 @@ REMOVE_ORG_FAVORITE : '', - MARK_ORG_FAVORITE : '' + MARK_ORG_FAVORITE : '', + + KUMALIVE_TITLE : '', }, activeOrgId = null${activeOrgId}; Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java =================================================================== diff -u -r60e44b19b8de02a00faa437fba8117928baa3d73 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java (.../User.java) (revision 60e44b19b8de02a00faa437fba8117928baa3d73) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java (.../User.java) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -500,12 +500,10 @@ return new UserDTO(userId, firstName, lastName, login, languageIsoCode, countryIsoCode, direction, email, theme != null ? new ThemeDTO(theme) : null, // TimeZone.getTimeZone("Australia/Sydney"), - timeZone, authenticationMethod.getAuthenticationMethodId(), fckLanguageMapping, + timeZone, authenticationMethod.getAuthenticationMethodId(), fckLanguageMapping, (tutorialsDisabled == null ? false : true), // assume tutorials enabled if not set - tutorialPages, - (firstLogin == null ? true : false), // assume no firstLogin value means they haven't logged in - lastVisitedOrganisationId - ); + tutorialPages, (firstLogin == null ? true : false), // assume no firstLogin value means they haven't logged in + lastVisitedOrganisationId, portraitUuid); } public UserBasicDTO getUserBasicDTO() { @@ -641,11 +639,11 @@ } public Integer getFailedAttempts() { - return failedAttempts; + return failedAttempts; } public void setFailedAttempts(Integer failedAttempts) { - this.failedAttempts = failedAttempts; + this.failedAttempts = failedAttempts; } public Date getLockOutTime() { Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/dao/hibernate/UserDAO.java =================================================================== diff -u -r60e44b19b8de02a00faa437fba8117928baa3d73 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/dao/hibernate/UserDAO.java (.../UserDAO.java) (revision 60e44b19b8de02a00faa437fba8117928baa3d73) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/dao/hibernate/UserDAO.java (.../UserDAO.java) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -60,7 +60,7 @@ String email = (String) element[4]; UserDTO userDto = new UserDTO(userId, firstName, lastName, login, null, null, null, email, null, null, null, - null, true, null, false, null); + null, true, null, false, null, null); userDtos.add(userDto); } Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/dto/UserDTO.java =================================================================== diff -u -r60e44b19b8de02a00faa437fba8117928baa3d73 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/dto/UserDTO.java (.../UserDTO.java) (revision 60e44b19b8de02a00faa437fba8117928baa3d73) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/dto/UserDTO.java (.../UserDTO.java) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -53,11 +53,13 @@ private Set pagesWithDisabledTutorials; private Boolean firstLogin; private Integer lastVisitedOrganisationId; + private Long portraitUuid; public UserDTO(Integer userID, String firstName, String lastName, String login, String localeLanguage, String localeCountry, String direction, String email, ThemeDTO htmlTheme, TimeZone timezone, Integer authenticationMethodId, String fckLanguageMapping, Boolean tutorialsDisabled, - Set pagesWithDisabledTutorials, Boolean firstLogin, Integer lastVisitedOrganisationId) { + Set pagesWithDisabledTutorials, Boolean firstLogin, Integer lastVisitedOrganisationId, + Long portraitUuid) { this.userID = userID; this.firstName = firstName; this.lastName = lastName; @@ -74,6 +76,7 @@ this.pagesWithDisabledTutorials = pagesWithDisabledTutorials; this.firstLogin = firstLogin; this.lastVisitedOrganisationId = lastVisitedOrganisationId; + this.portraitUuid = portraitUuid; } /** @@ -168,7 +171,8 @@ .append("direction", getDirection()).append("email", getEmail()).append("htmlTheme", getTheme()) .append("timeZone", getTimeZone()).append("authenticationMethodId", getAuthenticationMethodId()) .append("fckLanguageMapping", getFckLanguageMapping()) - .append("tutorialsDisabled", "" + getTutorialsDisabled()).toString(); + .append("tutorialsDisabled", "" + getTutorialsDisabled()).append("portraitUuid", getPortraitUuid()) + .toString(); } public String getFckLanguageMapping() { @@ -202,4 +206,12 @@ public Integer getLastVisitedOrganisationId() { return lastVisitedOrganisationId; } + + public Long getPortraitUuid() { + return portraitUuid; + } + + public void setPortraitUuid(Long portraitUuid) { + this.portraitUuid = portraitUuid; + } } \ No newline at end of file Index: lams_learning/build.xml =================================================================== diff -u -re463b64cccfefc6efb39c891fb496bfed47feb4a -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_learning/build.xml (.../build.xml) (revision e463b64cccfefc6efb39c891fb496bfed47feb4a) +++ lams_learning/build.xml (.../build.xml) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -25,5 +25,9 @@ file="${basedir}/build/classes/java/org/lamsfoundation/lams/learning/command/CommandWebsocketServer.class" verbose="true" /> + \ No newline at end of file Index: lams_learning/conf/hibernate/mappings/org/lamsfoundation/lams/learning/kumalive/Kumalive.hbm.xml =================================================================== diff -u -refe322b3756d88ff1fb7d149a3f4e267065eca40 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_learning/conf/hibernate/mappings/org/lamsfoundation/lams/learning/kumalive/Kumalive.hbm.xml (.../Kumalive.hbm.xml) (revision efe322b3756d88ff1fb7d149a3f4e267065eca40) +++ lams_learning/conf/hibernate/mappings/org/lamsfoundation/lams/learning/kumalive/Kumalive.hbm.xml (.../Kumalive.hbm.xml) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -4,7 +4,7 @@ "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" > - @@ -13,7 +13,7 @@ - + @@ -22,7 +22,7 @@ - + Index: lams_learning/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r271d374f37c513e19ec64591efa9df228923ef09 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_learning/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 271d374f37c513e19ec64591efa9df228923ef09) +++ lams_learning/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -109,5 +109,4 @@ label.group.confirm.button =Confirm label.group.confirm.areyoujoining =Are you joining - #======= End labels: Exported 102 labels for en AU ===== Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java =================================================================== diff -u --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java (revision 0) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/KumaliveWebsocketServer.java (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -0,0 +1,222 @@ +package org.lamsfoundation.lams.learning.kumalive; + +import java.io.IOException; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.OnClose; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.apache.tomcat.util.json.JSONArray; +import org.apache.tomcat.util.json.JSONException; +import org.apache.tomcat.util.json.JSONObject; +import org.lamsfoundation.lams.learning.service.ILearnerService; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.usermanagement.Role; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * @author Marcin Cieslak + */ +@ServerEndpoint("/kumaliveWebsocket") +public class KumaliveWebsocketServer { + + private class KumaliveLearner { + private UserDTO userDTO; + private Session websocket; + + private KumaliveLearner(User user, Session websocket) { + this.userDTO = user.getUserDTO(); + this.websocket = websocket; + } + } + + private class KumaliveDTO { + private Long id; + private String name; + private UserDTO createdBy; + private final Map learners = new ConcurrentHashMap<>(); + + private KumaliveDTO(Long id, String name, UserDTO createdBy) { + this.id = id; + this.name = name; + this.createdBy = createdBy; + } + } + + private static Logger log = Logger.getLogger(KumaliveWebsocketServer.class); + + private static ILearnerService learnerService; + + private static ISecurityService securityService; + + private static IUserManagementService userManagementService; + + private static final Map kumalives = new TreeMap<>(); + + @OnOpen + public void registerUser(Session websocket) throws IOException { + Integer organisationId = Integer + .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); + Integer userId = getUser(websocket).getUserId(); + if (!getSecurityService().hasOrgRole(organisationId, userId, + new String[] { Role.GROUP_MANAGER, Role.MONITOR, Role.LEARNER }, "join kumalive", false)) { + String warning = "User " + userId + " is not a monitor nor a learner of organisation " + organisationId; + log.warn(warning); + websocket.close(new CloseReason(CloseCodes.CANNOT_ACCEPT, warning)); + } + } + + /** + * Removes Learner websocket from the collection. + */ + @OnClose + public void unregisterUser(Session websocket, CloseReason reason) { + String login = websocket.getUserPrincipal().getName(); + if (login == null) { + return; + } + + Integer organisationId = Integer + .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); + KumaliveDTO kumalive = KumaliveWebsocketServer.kumalives.get(organisationId); + if (kumalive == null) { + return; + } + + kumalive.learners.remove(login); + } + + @OnMessage + public void receiveRequest(String input, Session session) throws JSONException, IOException { + if (StringUtils.isBlank(input)) { + return; + } + if (input.equalsIgnoreCase("ping")) { + // just a ping every few minutes + return; + } + + JSONObject requestJSON = new JSONObject(input); + switch (requestJSON.getString("type")) { + case "start": + startKumalive(requestJSON, session); + break; + case "join": + joinKumalive(requestJSON, session); + break; + } + } + + private void startKumalive(JSONObject requestJSON, Session websocket) throws JSONException, IOException { + Integer organisationId = Integer + .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); + String name = requestJSON.getString("name"); + User user = getUser(websocket); + Long kumaliveId = KumaliveWebsocketServer.getLearnerService().startKumalive(organisationId, user.getUserId(), + name); + kumalives.put(organisationId, new KumaliveDTO(kumaliveId, name, user.getUserDTO())); + + websocket.getBasicRemote().sendText("{ \"type\" : \"join\" }"); + } + + private void joinKumalive(JSONObject requestJSON, Session websocket) throws JSONException, IOException { + Integer organisationId = Integer + .valueOf(websocket.getRequestParameterMap().get(AttributeNames.PARAM_ORGANISATION_ID).get(0)); + KumaliveDTO kumalive = kumalives.get(organisationId); + if (kumalive == null) { + websocket.getBasicRemote().sendText("{ \"type\" : \"start\"}"); + return; + } + + User user = getUser(websocket); + Integer userId = user.getUserId(); + boolean isMonitor = getUserManagementService().isUserInRole(userId, organisationId, Role.GROUP_MANAGER) + || getUserManagementService().isUserInRole(userId, organisationId, Role.MONITOR); + + Map learners = kumalive.learners; + String login = user.getLogin(); + if (learners.containsKey(login)) { + KumaliveLearner learner = learners.get(login); + learner.websocket.close( + new CloseReason(CloseCodes.NOT_CONSISTENT, "Another websocket for same user was estabilished")); + } + KumaliveLearner learner = new KumaliveLearner(user, websocket); + learners.put(login, learner); + + JSONObject responseJSON = new JSONObject(); + responseJSON.put("type", "refresh"); + responseJSON.put("name", kumalive.name); + responseJSON.put("teacherId", kumalive.createdBy.getUserID()); + responseJSON.put("teacherName", kumalive.createdBy.getFirstName() + " " + kumalive.createdBy.getLastName()); + responseJSON.put("teacherPortraitUuid", kumalive.createdBy.getPortraitUuid()); + + if (isMonitor) { + responseJSON.put("isTeacher", true); + } + + JSONArray learnersJSON = new JSONArray(); + for (KumaliveLearner participant : learners.values()) { + JSONObject learnerJSON = new JSONObject(); + UserDTO participantDTO = participant.userDTO; + learnerJSON.put("id", participantDTO.getUserID()); + learnerJSON.put("firstName", participantDTO.getFirstName()); + learnerJSON.put("lastName", participantDTO.getLastName()); + learnerJSON.put("portraitUuid", participantDTO.getPortraitUuid()); + if (isMonitor) { + learnerJSON.put("login", participantDTO.getLogin()); + } + learnersJSON.put(learnerJSON); + } + responseJSON.put("learners", learnersJSON); + + for (KumaliveLearner participant : learners.values()) { + participant.websocket.getBasicRemote().sendText(responseJSON.toString()); + } + } + + private User getUser(Session websocket) { + return getUserManagementService().getUserByLogin(websocket.getUserPrincipal().getName()); + } + + private static ILearnerService getLearnerService() { + if (learnerService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getWebApplicationContext(SessionManager.getServletContext()); + learnerService = (ILearnerService) ctx.getBean("learnerService"); + } + return learnerService; + } + + private ISecurityService getSecurityService() { + if (securityService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(SessionManager.getServletContext()); + securityService = (ISecurityService) ctx.getBean("securityService"); + } + return securityService; + } + + private IUserManagementService getUserManagementService() { + if (userManagementService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(SessionManager.getServletContext()); + userManagementService = (IUserManagementService) ctx.getBean("userManagementService"); + } + return userManagementService; + } +} \ No newline at end of file Index: lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/model/Kumalive.java =================================================================== diff -u -refe322b3756d88ff1fb7d149a3f4e267065eca40 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/model/Kumalive.java (.../Kumalive.java) (revision efe322b3756d88ff1fb7d149a3f4e267065eca40) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/kumalive/model/Kumalive.java (.../Kumalive.java) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -36,7 +36,7 @@ private Organisation organisation; private User createdBy; private String name; - private Boolean finished; + private Boolean finished = false; private Map scores; public Kumalive(Organisation organisation, User createdBy, String name) { Index: lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml =================================================================== diff -u -r8033905f61b3c293f92b5c9c468dd22606ab9a42 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml (.../learningApplicationContext.xml) (revision 8033905f61b3c293f92b5c9c468dd22606ab9a42) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/learningApplicationContext.xml (.../learningApplicationContext.xml) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -55,6 +55,8 @@ + + @@ -107,6 +109,10 @@ + + + + Index: lams_learning/src/java/org/lamsfoundation/lams/learning/service/ILearnerService.java =================================================================== diff -u -rd19a95db673b7cf90351de4d567299c5fa86b450 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_learning/src/java/org/lamsfoundation/lams/learning/service/ILearnerService.java (.../ILearnerService.java) (revision d19a95db673b7cf90351de4d567299c5fa86b450) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/service/ILearnerService.java (.../ILearnerService.java) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -60,4 +60,6 @@ void createCommandForLearner(Long lessonId, String userName, String jsonCommand); List getCommandsForLesson(Long lessonId, Date laterThan); + + Long startKumalive(Integer organisationId, Integer userId, String name); } Index: lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java =================================================================== diff -u -r60355e9a91260c605e1a55e1f2329dd69fea08b9 -r34bc1c178bd5ada01543d5b4637487322d3ff565 --- lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java (.../LearnerService.java) (revision 60355e9a91260c605e1a55e1f2329dd69fea08b9) +++ lams_learning/src/java/org/lamsfoundation/lams/learning/service/LearnerService.java (.../LearnerService.java) (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -39,6 +39,8 @@ import org.lamsfoundation.lams.gradebook.service.IGradebookService; import org.lamsfoundation.lams.learning.command.dao.ICommandDAO; import org.lamsfoundation.lams.learning.command.model.Command; +import org.lamsfoundation.lams.learning.kumalive.dao.IKumaliveDAO; +import org.lamsfoundation.lams.learning.kumalive.model.Kumalive; import org.lamsfoundation.lams.learning.progress.ProgressBuilder; import org.lamsfoundation.lams.learning.progress.ProgressEngine; import org.lamsfoundation.lams.learning.progress.ProgressException; @@ -81,6 +83,7 @@ import org.lamsfoundation.lams.lesson.service.LessonServiceException; import org.lamsfoundation.lams.logevent.LogEvent; import org.lamsfoundation.lams.logevent.service.ILogEventService; +import org.lamsfoundation.lams.security.ISecurityService; import org.lamsfoundation.lams.tool.Tool; import org.lamsfoundation.lams.tool.ToolCompletionStatus; import org.lamsfoundation.lams.tool.ToolOutput; @@ -89,6 +92,8 @@ import org.lamsfoundation.lams.tool.exception.RequiredGroupMissingException; import org.lamsfoundation.lams.tool.exception.ToolException; import org.lamsfoundation.lams.tool.service.ILamsCoreToolService; +import org.lamsfoundation.lams.usermanagement.Organisation; +import org.lamsfoundation.lams.usermanagement.Role; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; @@ -111,13 +116,15 @@ private ProgressEngine progressEngine; private IDataFlowDAO dataFlowDAO; private ICommandDAO commandDAO; + private IKumaliveDAO kumaliveDAO; private ILamsCoreToolService lamsCoreToolService; private ActivityMapping activityMapping; private IUserManagementService userManagementService; private ILessonService lessonService; - private static HashMap syncMap = new HashMap(); + private static HashMap syncMap = new HashMap<>(); private IGradebookService gradebookService; private ILogEventService logEventService; + private ISecurityService securityService; // --------------------------------------------------------------------- // Inversion of Control Methods - Constructor injection @@ -218,6 +225,10 @@ this.logEventService = logEventService; } + public void setSecurityService(ISecurityService securityService) { + this.securityService = securityService; + } + // --------------------------------------------------------------------- // Service Methods // --------------------------------------------------------------------- @@ -764,7 +775,7 @@ private boolean forceGrouping(Lesson lesson, Grouping grouping, Group group, User learner) { boolean groupingDone = false; if (lesson.isPreviewLesson()) { - ArrayList learnerList = new ArrayList(); + ArrayList learnerList = new ArrayList<>(); learnerList.add(learner); if (group != null) { if (group.getGroupId() != null) { @@ -857,7 +868,7 @@ @Override public Set getGroupsForGate(GateActivity gate) { Lesson lesson = getLessonByActivity(gate); - Set result = new HashSet(); + Set result = new HashSet<>(); Activity branchActivity = gate.getParentBranch(); while ((branchActivity != null) && !(branchActivity.getParentActivity().isChosenBranchingActivity() @@ -909,7 +920,7 @@ * @return the lesson dto array. */ private LessonDTO[] getLessonDataFor(List lessons) { - List lessonDTOList = new ArrayList(); + List lessonDTOList = new ArrayList<>(); for (Iterator i = lessons.iterator(); i.hasNext();) { Lesson currentLesson = (Lesson) i.next(); lessonDTOList.add(new LessonDTO(currentLesson)); @@ -969,7 +980,7 @@ if (toolSession != null) { // Get all the conditions for this branching activity, ordered by order id. - Map conditionsMap = new TreeMap(); + Map conditionsMap = new TreeMap<>(); Iterator branchIterator = branchingActivity.getActivities().iterator(); while (branchIterator.hasNext()) { Activity branchActivity = (Activity) branchIterator.next(); @@ -986,7 +997,7 @@ // Go through each condition until we find one that passes and that is the required branch. // Cache the tool output so that we aren't calling it over an over again. - Map toolOutputMap = new HashMap(); + Map toolOutputMap = new HashMap<>(); Iterator conditionIterator = conditionsMap.keySet().iterator(); while ((matchedBranch == null) && conditionIterator.hasNext()) { @@ -1101,7 +1112,7 @@ // Go through each condition until we find one that passes and that opens the gate. // Cache the tool output so that we aren't calling it over an over again. - Map toolOutputMap = new HashMap(); + Map toolOutputMap = new HashMap<>(); for (BranchActivityEntry entry : conditionGate.getBranchActivityEntries()) { BranchCondition condition = entry.getCondition(); String conditionName = condition.getName(); @@ -1286,14 +1297,14 @@ this.dataFlowDAO = dataFlowDAO; } - public ICommandDAO getCommandDAO() { - return commandDAO; - } - public void setCommandDAO(ICommandDAO commandDAO) { this.commandDAO = commandDAO; } + public void setKumaliveDAO(IKumaliveDAO kumaliveDAO) { + this.kumaliveDAO = kumaliveDAO; + } + /** * Gets the concreted tool output (not the definition) from a tool. This method is called by target tool in order to * get data from source tool. @@ -1400,6 +1411,16 @@ return toolSession == null ? null : getActivityPosition(toolSession.getToolActivity().getActivityId()); } + @Override + public Long startKumalive(Integer organisationId, Integer userId, String name) { + securityService.isGroupMonitor(organisationId, userId, "start kumalive", true); + Organisation organisation = (Organisation) kumaliveDAO.find(Organisation.class, organisationId); + User createdBy = (User) kumaliveDAO.find(User.class, userId); + Kumalive kumalive = new Kumalive(organisation, createdBy, name); + kumaliveDAO.insert(kumalive); + return kumalive.getKumaliveId(); + } + private boolean isActivityLast(Activity activity) { Transition transition = activity.getTransitionFrom(); while (transition != null) { @@ -1601,5 +1622,4 @@ public IActivityDAO getActivityDAO() { return activityDAO; } - } \ No newline at end of file Index: lams_learning/web/css/kumalive.css =================================================================== diff -u --- lams_learning/web/css/kumalive.css (revision 0) +++ lams_learning/web/css/kumalive.css (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -0,0 +1,86 @@ +#raiseHandContainer { + display: none; + margin-bottom : 10px; + border-bottom: thin black solid; +} + +table { + table-layout: fixed; +} + +#actionCell { + width: 400px; + border-left: thin black solid; + vertical-align: top; +} + + +.learner { + width: 80px; + height: 120px; + position: relative; + float: left; + margin: 5px 5px 0 5px; + text-align: center; + display: none; +} + +.learner .profilePictureWrapper { + width: 80px; + height: 80px; +} + +.profilePicture { + position: relative; + border-radius: 100%; + background-repeat: no-repeat; + background-position: center; + background-size: cover; +} + +.profilePictureHidden { + top: 40px; + left: 40px; + width: 0; + height: 0; +} + +.profilePictureShown { + top: 0; + left: 0; + width: 80px; + height: 80px; +} + +.name { + overflow: hidden; + text-overflow: ellipsis; +} + +.learner .name { + font-size: 12px; +} + + +.speaker { + width: 200px; + height: 300px; + margin: auto; + text-align: center; +} + +.speaker .profilePicture{ + width: 200px; + height: 200px; +} + +.speaker .name { + font-size: 18px; +} + +#raiseHandPrompt { + display: none; + text-align: center; + padding-top: 100px; + cursor: pointer; +} \ No newline at end of file Index: lams_learning/web/includes/javascript/kumalive.js =================================================================== diff -u --- lams_learning/web/includes/javascript/kumalive.js (revision 0) +++ lams_learning/web/includes/javascript/kumalive.js (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -0,0 +1,169 @@ +$(document).ready(function(){ + $('#teacher').click(raiseHandPromptShow); +}); + +var kumaliveWebsocket = new WebSocket(LEARNING_URL.replace('http', 'ws') + 'kumaliveWebsocket?organisationID=' + orgId), + initialised = false, + learnerDivTemplate = $('
').addClass('learner') + .append($('
').addClass('profilePictureWrapper').append($('
').addClass('profilePicture profilePictureHidden'))) + .append($('
').addClass('name')); + +kumaliveWebsocket.onopen = function(e) { + kumaliveWebsocket.send(JSON.stringify({ + 'type' : 'join' + })); +}; +kumaliveWebsocket.onclose = function(e){ + $('body').text("Websocket closed"); +}; + +kumaliveWebsocket.onmessage = function(e){ + // read JSON object + var message = JSON.parse(e.data), + type = message.type, + container = $('#learnersContainer'); + switch(type) { + case 'start' : { + kumaliveWebsocket.send(JSON.stringify({ + 'type' : 'start', + 'name' : 'random name' + })); + } + break; + case 'join' : { + kumaliveWebsocket.send(JSON.stringify({ + 'type' : 'join' + })); + } + break; + case 'refresh': { + if (!initialised) { + $('#dialogKumaliveLabel', window.parent.document).text('Kumalive: ' + message.name); + $('#teacher .profilePicture').css('background-image', + 'url(' + LAMS_URL + 'download?preferDownload=false&uuid=' + message.teacherPortraitUuid + ')'); + $('#teacher .name').text(message.teacherName); + initialised = true; + } + + for (var i = 0;i<30;i++) { + $.each(message.learners, function(index, learner){ + var learnerDiv = learnerDivTemplate.clone().data('id', learner.id).appendTo(container); + $('.profilePicture', learnerDiv).css('background-image', + 'url(' + LAMS_URL + 'download?preferDownload=false&uuid=' + learner.portraitUuid + ')'); + $('.name', learnerDiv).text(learner.firstName + ' ' + learner.lastName + (i % 2 ? 'asdfassafasfsdafd' : '')); + if (message.isTeacher) { + learnerDiv.attr('title', learner.login); + } + learnerFadeIn(learnerDiv); + if (i % 30 == 0) { + setTimeout(function(){ + learnerFadeOut(learnerDiv); + }, 4000); + } + if (i % 30 == 15) { + setTimeout(function(){ + raiseHand(learnerDiv); + }, 7000); + } + if (i % 30 > 17) { + setTimeout(function(){ + raiseHand(learnerDiv); + }, (i % 30) * 500); + } + }); + } + } + break; + } +}; + +function learnerFadeIn(learnerDiv) { + var nameDiv = $('.name', learnerDiv).css('color', 'green'); + learnerDiv.show(); + + $('.profilePicture', learnerDiv).switchClass('profilePictureHidden', 'profilePictureShown', 1000, function(){ + nameDiv.css('color', 'initial'); + }); +} + +function learnerFadeOut(learnerDiv) { + var nameDiv = $('.name', learnerDiv).css('color', 'red'); + + $('.profilePicture', learnerDiv).switchClass('profilePictureShown', 'profilePictureHidden', 1000, function(){ + nameDiv.remove(); + learnerDiv.animate({ + 'width' : 'toggle' + }, 1000, function(){ + learnerDiv.remove(); + }); + }); +} + +function raiseHand(learnerDiv) { + var raiseHandContainer = $('#raiseHandContainer'), + firstHand = raiseHandContainer.children('.learner').length == 0; + if (firstHand) { + raiseHandContainer.css({ + 'display' : 'none', + }) + } + + var targetLearnerDiv = learnerDiv.clone(true).css({ + 'visibility' : 'hidden', + 'cursor' : 'pointer' + }).click(learnerSpeak) + .appendTo(raiseHandContainer); + if (firstHand) { + raiseHandContainer.slideDown(500); + } + + var targetOffset = $('.profilePicture', targetLearnerDiv).offset(), + profilePicture = $('.profilePicture', learnerDiv), + transitionCopy = profilePicture.clone().appendTo('body') + .css({ + 'position' : 'fixed' + }).offset(profilePicture.offset()) + .animate({ + 'left' : targetOffset.left, + 'top' : targetOffset.top + }, 1000, function(){ + targetLearnerDiv.css('visibility', 'visible'); + transitionCopy.remove(); + }); +} + +function raiseHandPromptShow() { + $('#teacher').slideUp(function(){ + $('#raiseHandPrompt').slideDown(); + }); +} + +function learnerSpeak() { + var learnerDiv = $(this), + id = learnerDiv.data('id'), + speaker = $('
').addClass('speaker').css({ + 'margin-top' : '20px', + 'visibility' : 'hidden' + }).appendTo('#actionCell'), + targetProfilePicture = $('.profilePicture', learnerDiv).clone().removeClass('profilePictureShown').appendTo(speaker); + $('.name', learnerDiv).clone().appendTo(speaker); + $('#raiseHandPrompt').slideUp(function(){ + var targetOffset = targetProfilePicture.offset(); + profilePicture = $('.profilePicture', learnerDiv), + transitionCopy = profilePicture.clone().appendTo('body') + .css({ + 'position' : 'fixed' + }).offset(profilePicture.offset()) + .animate({ + 'left' : targetOffset.left, + 'top' : targetOffset.top, + 'width' : '200px', + 'height' : '200px' + }, 1000, function(){ + speaker.css('visibility', 'visible'); + transitionCopy.remove(); + }); + + }); + +} \ No newline at end of file Index: lams_learning/web/kumalive.jsp =================================================================== diff -u --- lams_learning/web/kumalive.jsp (revision 0) +++ lams_learning/web/kumalive.jsp (revision 34bc1c178bd5ada01543d5b4637487322d3ff565) @@ -0,0 +1,58 @@ +<%@ page contentType="text/html; charset=utf-8" language="java"%> + +<%@ taglib uri="tags-lams" prefix="lams"%> +<%@ taglib uri="tags-fmt" prefix="fmt"%> +<%@ taglib uri="tags-core" prefix="c"%> +<%@ taglib uri="tags-function" prefix="fn"%> + + + + + + + + + + + + + + + + + + + + + +
+
+

Raised hand

+
+
+

Learners

+
+
+
+

Teacher

+
+
+
+
+

Raise hand

+
+
+
+ +
\ No newline at end of file