From 5459e6caff67df2a854f313e55a9694041e3fea0 Mon Sep 17 00:00:00 2001 From: Chad Phillips Date: Sat, 26 Mar 2016 21:15:46 -0600 Subject: [PATCH] Dynamic updating of event interface on event admin change This patch refactors all event rendering functionality to enable updating the event interface in the case where an event admin is added or removed. Only the user who was added or removed has their event interface updated. --- lib/unhangout-sockets.js | 7 ++++ public/js/event-app.js | 69 ++++++++++++++++++++++++++++++++-------- public/js/event-views.js | 52 ++++++++++++++++++++++-------- views/event.ejs | 20 ++++++------ 4 files changed, 112 insertions(+), 36 deletions(-) diff --git a/lib/unhangout-sockets.js b/lib/unhangout-sockets.js index 56c6e1f5..79c6ac93 100644 --- a/lib/unhangout-sockets.js +++ b/lib/unhangout-sockets.js @@ -764,6 +764,13 @@ _.extend(UnhangoutSocketManager.prototype, events.EventEmitter.prototype, { value: message }); }); + this.db.events.on("change:admins", function(event, admins) { + mgr.sync(event.getRoomId(), "state", { + path: ["event", "admins"], + op: "set", + value: admins + }); + }); // // Hangout-on-air events diff --git a/public/js/event-app.js b/public/js/event-app.js index 86e5b36e..622a2858 100644 --- a/public/js/event-app.js +++ b/public/js/event-app.js @@ -61,7 +61,7 @@ $(document).ready(function() { }); curEvent.on("change:open", function(model, open, options) { - if (IS_ADMIN) { + if (app.currentUser.isAdminOf(curEvent)) { app.chatView.chatInputView.onRender(); } else if (!open) { window.location.reload(); @@ -121,25 +121,34 @@ $(document).ready(function() { var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + this.currentUser = new models.User(USER); + this.currentUserIsAdmin = (function() { + return this.currentUser.isAdminOf(curEvent); + }).bind(this); + // create all the basic views this.sessionListView = new eventViews.SessionListView({ collection: curEvent.get("sessions"), + currentUser: this.currentUser, event: curEvent, transport: trans }); this.topicListView = new eventViews.TopicListView({ collection: curEvent.get("sessions"), + currentUser: this.currentUser, event: curEvent, transport: trans }); this.chatView = new eventViews.ChatLayout({ + currentUser: this.currentUser, messages: messages, users: curEvent.get("connectedUsers"), event: curEvent, transport: trans }); this.youtubeEmbedView = new eventViews.VideoEmbedView({ - model: curEvent, transport: trans + model: curEvent, transport: trans, + currentUser: this.currentUser }); this.dialogView = new eventViews.DialogView({ event: curEvent, transport: trans @@ -147,6 +156,31 @@ $(document).ready(function() { this.aboutView = new eventViews.AboutEventView({model: curEvent}); + // Always include this in the DOM for callback support, even if it's + // not inserted. + this.adminButtonView = new eventViews.AdminButtonView({ + event: curEvent, transport: trans + }); + + this.adminChanged = function() { + var previousAdmins = _.pluck(curEvent.previous("admins"), "id"); + var currentAdmins = _.pluck(curEvent.get("admins"), "id"); + // This user's interface needs to be refreshed if: + // 1. The user is in either the previous or current list. + // 2. The user isn't in both lists. + // Refresh all views that are impacted by admin status. + var inPrevious = previousAdmins.indexOf(USER.id) !== -1; + var inCurrent = currentAdmins.indexOf(USER.id) !== -1; + if ((inPrevious || inCurrent) && !(inPrevious && inCurrent)) { + this.setupAdminControls(); + this.youtubeEmbedView.render(); + this.sessionListView.render(); + this.topicListView.render(); + this.chatView.whiteboardView.render(); + this.chatView.chatInputView.render(); + } + } + // present the views in their respective regions this.right.show(this.chatView); this.topLeft.show(this.youtubeEmbedView); @@ -179,6 +213,10 @@ $(document).ready(function() { $("#topic-list").hide(); } + curEvent.on("change:admins", _.bind(function() { + this.adminChanged(); + }, this)); + curEvent.on("change:adminProposedSessions change:sessionsOpen change:open", _.bind(function() { this.adminButtonView.render(); }, this)); @@ -325,21 +363,24 @@ $(document).ready(function() { } } - // obviously this is not secure, but any admin requests are re-authenticated on - // the server. Showing the admin UI is harmless if a non-admin messes with it. - if(IS_ADMIN) { - if (NUM_HANGOUT_URLS_WARNING > 0 && NUM_HANGOUT_URLS_AVAILABLE < NUM_HANGOUT_URLS_WARNING) { - console.error("Too few hangout URLS available!", NUM_HANGOUT_URLS_AVAILABLE); - } - this.adminButtonView = new eventViews.AdminButtonView({ - event: curEvent, transport: trans - }); + this.hotkeys = this.initHotkeys(); - this.admin.show(this.adminButtonView); - this.hotkeys = this.initHotkeys(); - this.hotkeys.activate($(document)); + this.setupAdminControls = function() { + if(this.currentUserIsAdmin()) { + this.admin.show(this.adminButtonView); + this.hotkeys.activate($(document)); + } + else { + // Marionette requires a view, and there's no need to destroy + // the existing admin view, so use a dummy view to hide it. + var dummyView = new Backbone.Marionette.ItemView({template: '#dummy-template'}); + this.admin.show(dummyView); + this.hotkeys.deactivate($(document)); + } } + this.setupAdminControls(); + var maybeMute = function() { var hoa = curEvent.get("hoa"); if (hoa && _.findWhere(hoa.get("connectedParticipants"), {id: auth.USER_ID})) { diff --git a/public/js/event-views.js b/public/js/event-views.js index 437e3b64..5003ae59 100644 --- a/public/js/event-views.js +++ b/public/js/event-views.js @@ -70,6 +70,12 @@ views.SessionView = Backbone.Marionette.ItemView.extend({ this.listenTo(this.model, 'change:title', this.render, this); }, + serializeData: function() { + return _.extend(this.model.toJSON(), { + eventAdmin: this.options.currentUser.isAdminOf(this.options.event) + }); + }, + onRender: function() { $('.tooltip').hide(); var start = new Date().getTime(); @@ -87,7 +93,7 @@ views.SessionView = Backbone.Marionette.ItemView.extend({ } //Show delete button only for admins - if(IS_ADMIN) { + if(this.options.currentUser.isAdminOf(this.options.event)) { this.ui.deleteButton.show(); } else { this.ui.deleteButton.hide(); @@ -109,7 +115,7 @@ views.SessionView = Backbone.Marionette.ItemView.extend({ this.ui.unapprove.hide(); this.ui.proposeeDetails.hide(); - if(IS_ADMIN) { + if(this.options.currentUser.isAdminOf(this.options.event)) { this.ui.deleteButton.removeClass("top-margin"); } @@ -350,6 +356,12 @@ views.TopicView = Backbone.Marionette.ItemView.extend({ this.listenTo(this.model, 'change:title', this.render, this); }, + serializeData: function() { + return _.extend(this.model.toJSON(), { + eventAdmin: this.options.currentUser.isAdminOf(this.options.event) + }); + }, + onRender: function() { $('.tooltip').hide(); @@ -382,7 +394,7 @@ views.TopicView = Backbone.Marionette.ItemView.extend({ //, hide approve button from the topic template and position // delete button - if(!IS_ADMIN && (this.model.get("proposedBy") && + if(!this.options.currentUser.isAdminOf(this.options.event) && (this.model.get("proposedBy") && USER.id === this.model.get("proposedBy").id)) { this.ui.approve.hide(); this.ui.deleteButton.addClass("pos-admin-delete"); @@ -491,7 +503,9 @@ views.SessionListView = Backbone.Marionette.CollectionView.extend({ itemViewOptions: function() { return { - event: this.options.event, transport: this.options.transport + event: this.options.event, + currentUser: this.options.currentUser, + transport: this.options.transport }; }, @@ -524,7 +538,9 @@ views.TopicListView = Backbone.Marionette.CollectionView.extend({ itemViewOptions: function() { return { - event: this.options.event, transport: this.options.transport + event: this.options.event, + currentUser: this.options.currentUser, + transport: this.options.transport }; }, @@ -1132,6 +1148,7 @@ views.ChatLayout = Backbone.Marionette.Layout.extend({ Backbone.Marionette.View.prototype.initialize.call(this, options); this.whiteboardView = new views.WhiteboardView({ model: this.options.event, + currentUser: this.options.currentUser, transport: this.options.transport, messages: this.options.messages }); @@ -1155,6 +1172,7 @@ views.ChatLayout = Backbone.Marionette.Layout.extend({ this.chatInputView = new views.ChatInputView({ event: this.options.event, + currentUser: this.options.currentUser, transport: this.options.transport }); @@ -1210,7 +1228,7 @@ views.WhiteboardView = Backbone.Marionette.ItemView.extend({ // Function to toggle the view of the form only if the user is an admin toggleForm: function(){ - if(IS_ADMIN){ + if(this.options.currentUser.isAdminOf(this.model)){ this.ui.form.toggle(); this.ui.buttons.toggle(); this.ui.message.toggle(); @@ -1234,7 +1252,7 @@ views.WhiteboardView = Backbone.Marionette.ItemView.extend({ this.ui.message.html(utils.linkify(_.escape(whiteboard.message))); } else { // If not an admin, we hide the whole whiteboard, else we show an empty whiteboard for admins - if(IS_ADMIN){ + if(this.options.currentUser.isAdminOf(this.model)){ this.ui.message.html('') } else { this.ui.message.hide(); @@ -1249,7 +1267,8 @@ views.WhiteboardView = Backbone.Marionette.ItemView.extend({ chatArchiveUrl = this.model.getChatArchiveUrl(); } return _.extend(this.model.toJSON(), { - chatArchiveUrl: this.model.getChatArchiveUrl() + chatArchiveUrl: this.model.getChatArchiveUrl(), + eventAdmin: this.options.currentUser.isAdminOf(this.model) }); } }); @@ -1271,9 +1290,15 @@ views.ChatInputView = Backbone.Marionette.ItemView.extend({ Backbone.Marionette.View.prototype.initialize.call(this, options); }, + serializeData: function() { + return { + eventAdmin: this.options.currentUser.isAdminOf(this.options.event) + }; + }, + chat: function(e) { var msg = this.ui.chatInput.val(); - var postAsAdmin = IS_ADMIN && this.ui.asAdmin.is(":checked"); + var postAsAdmin = this.options.currentUser.isAdminOf(this.options.event) && this.ui.asAdmin.is(":checked"); if(msg.length>0) { @@ -1598,6 +1623,7 @@ views.VideoEmbedView = Backbone.Marionette.ItemView.extend({ if (this.model.get("hoa")) { context.hoa = this.model.get("hoa").toJSON(); } + context.eventAdmin = this.options.currentUser.isAdminOf(this.model); return context; }, setVideo: function(jqevt) { @@ -1672,9 +1698,9 @@ views.VideoEmbedView = Backbone.Marionette.ItemView.extend({ this.ui.player.toggle(visible); // Show a placeholder ("video goes here") if video is not visible and // the user is an admin. Non-admins get nothing. - this.ui.placeholder.toggle(!visible && IS_ADMIN); + this.ui.placeholder.toggle(!visible && this.options.currentUser.isAdminOf(this.model)); // Always show controls if the user is an admin. - this.ui.controls.toggle(IS_ADMIN); + this.ui.controls.toggle(this.options.currentUser.isAdminOf(this.model)); }, renderControls: function() { var hoa = this.model.get("hoa"); @@ -1708,10 +1734,10 @@ views.VideoEmbedView = Backbone.Marionette.ItemView.extend({ onRender: function() { this.yt = new video.YoutubeVideo({ ytID: this.model.get("youtubeEmbed"), - permitGroupControl: IS_ADMIN, + permitGroupControl: this.options.currentUser.isAdminOf(this.model), showGroupControls: false // We're doing our own controls on event pages. }); - if (IS_ADMIN) { + if (this.options.currentUser.isAdminOf(this.model)) { this.yt.on("renderControls", _.bind(function() { this.renderControls(); }, this)); diff --git a/views/event.ejs b/views/event.ejs index de81292e..702552a2 100644 --- a/views/event.ejs +++ b/views/event.ejs @@ -3,7 +3,6 @@ + + <% include _video-templates.ejs %> <%- requireScripts("/public/js/event-app.js") %>