You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2018/02/25 15:01:59 UTC
[openmeetings] 04/04: [OPENMEETINGS-1791] quick polls seems to work
This is an automated email from the ASF dual-hosted git repository.
solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git
commit 202b74f41a90ff64c57d5f15a99f555212353f5e
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Sun Feb 25 21:46:42 2018 +0700
[OPENMEETINGS-1791] quick polls seems to work
---
.../openmeetings/db/util/ws/RoomMessage.java | 1 +
openmeetings-web/pom.xml | 3 +
.../openmeetings/web/app/QuickPollManager.java | 110 ++++
.../apache/openmeetings/web/room/RoomPanel.html | 9 +
.../apache/openmeetings/web/room/RoomPanel.java | 14 +-
.../openmeetings/web/room/menu/PollsSubMenu.java | 174 ++++++
.../openmeetings/web/room/menu/RoomMenuPanel.java | 96 +---
.../org/apache/openmeetings/web/room/room-base.js | 598 ++-------------------
.../openmeetings/web/room/sidebar/RoomSidebar.java | 2 +-
.../apache/openmeetings/web/room/video-manager.js | 169 ++++++
.../org/apache/openmeetings/web/room/video-util.js | 111 ++++
.../java/org/apache/openmeetings/web/room/video.js | 268 +++++++++
openmeetings-web/src/main/webapp/css/wb.css | 26 +
13 files changed, 949 insertions(+), 632 deletions(-)
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ws/RoomMessage.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ws/RoomMessage.java
index 1e4af48..3c57781 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ws/RoomMessage.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/util/ws/RoomMessage.java
@@ -58,6 +58,7 @@ public class RoomMessage implements IWebSocketPushMessage {
, audioActivity //user speaks
, mute
, exclusive
+ , quickPollUpdated
}
private final Date timestamp;
private final String uid;
diff --git a/openmeetings-web/pom.xml b/openmeetings-web/pom.xml
index 404788e..0a77370 100644
--- a/openmeetings-web/pom.xml
+++ b/openmeetings-web/pom.xml
@@ -213,6 +213,9 @@
<jsSourceDir>../java/org/apache/openmeetings/web/room</jsSourceDir>
<jsSourceFiles>
<jsSourceFile>jquery.dialogextend.js</jsSourceFile>
+ <jsSourceFile>video-util.js</jsSourceFile>
+ <jsSourceFile>video.js</jsSourceFile>
+ <jsSourceFile>video-manager.js</jsSourceFile>
<jsSourceFile>room-base.js</jsSourceFile>
</jsSourceFiles>
<jsFinalFile>room.js</jsFinalFile>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/QuickPollManager.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/QuickPollManager.java
new file mode 100644
index 0000000..b143603
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/app/QuickPollManager.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+package org.apache.openmeetings.web.app;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.getWebAppRootKey;
+import static org.apache.openmeetings.web.app.Application.getHazelcast;
+import static org.apache.openmeetings.web.app.WebSession.getUserId;
+import static org.red5.logging.Red5LoggerFactory.getLogger;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.PostConstruct;
+
+import org.apache.openmeetings.core.util.WebSocketHelper;
+import org.apache.openmeetings.db.entity.basic.Client;
+import org.apache.openmeetings.db.entity.room.Room;
+import org.apache.openmeetings.db.util.ws.RoomMessage.Type;
+import org.apache.openmeetings.db.util.ws.TextRoomMessage;
+import org.slf4j.Logger;
+import org.springframework.stereotype.Component;
+
+import com.github.openjson.JSONObject;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.core.IMap;
+
+@Component
+public class QuickPollManager {
+ private static final Logger log = getLogger(QuickPollManager.class, getWebAppRootKey());
+ private static final String QPOLLS_KEY = "QPOLLS_KEY";
+ private HazelcastInstance hazelcast;
+
+ @PostConstruct
+ private void init() {
+ this.hazelcast = getHazelcast();
+ }
+
+ private IMap<Long, Map<Long, Boolean>> map() {
+ return hazelcast.getMap(QPOLLS_KEY);
+ }
+
+ public boolean isStarted(Long roomId) {
+ return map().containsKey(roomId);
+ }
+
+ public void start(Client c) {
+ Long roomId = c.getRoomId();
+ if (!c.hasRight(Room.Right.presenter) || isStarted(roomId)) {
+ return;
+ }
+ log.debug("Starting quick poll, room: {}", roomId);
+ IMap<Long, Map<Long, Boolean>> polls = map();
+ polls.lock(roomId);
+ polls.putIfAbsent(roomId, new ConcurrentHashMap<Long, Boolean>());
+ polls.unlock(roomId);
+ WebSocketHelper.sendRoom(new TextRoomMessage(roomId, c, Type.quickPollUpdated, c.getUid()));
+ }
+
+ public void vote(Client c, boolean vote) {
+ Long roomId = c.getRoomId();
+ IMap<Long, Map<Long, Boolean>> polls = map();
+ polls.lock(roomId);
+ if (polls.containsKey(roomId)) {
+ Map<Long, Boolean> votes = map().get(roomId);
+ if (!votes.containsKey(c.getUserId())) {
+ votes.put(c.getUserId(), vote);
+ polls.put(roomId, votes);
+ WebSocketHelper.sendRoom(new TextRoomMessage(roomId, c, Type.quickPollUpdated, c.getUid()));
+ }
+ }
+ polls.unlock(roomId);
+ }
+
+ public void close(Client c) {
+ Long roomId = c.getRoomId();
+ if (!c.hasRight(Room.Right.presenter) || !isStarted(roomId)) {
+ return;
+ }
+ map().remove(roomId);
+ WebSocketHelper.sendRoom(new TextRoomMessage(roomId, c, Type.quickPollUpdated, c.getUid()));
+ }
+
+ public JSONObject toJson(Long roomId) {
+ boolean started = isStarted(roomId);
+ JSONObject o = new JSONObject().put("started", started);
+ if (started) {
+ Map<Long, Boolean> votes = map().get(roomId);
+ o.put("voted", votes.containsKey(getUserId()));
+ o.put("pros", votes.entrySet().stream().filter(e -> e.getValue()).count())
+ .put("cons", votes.entrySet().stream().filter(e -> !e.getValue()).count());
+ }
+ return o;
+ }
+}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html
index 2b7a90a..d6c38c8 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.html
@@ -75,6 +75,15 @@
<div id="clipboard-dialog" wicket:message="title:1121,data-btn-ok:54">
<p><span class="ui-icon ui-icon-alert" style="float:left; margin:12px 12px 20px 0;"></span><span class="text"></span></p>
</div>
+ <div id="quick-vote-template">
+ <div class="close clickable"><wicket:message key="85" /></div>
+ <div class="control pro">
+ <span class="badge">0</span>
+ </div>
+ <div class="control con">
+ <span class="badge">0</span>
+ </div>
+ </div>
</div>
</wicket:panel>
</html>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
index 29ddfc4..2da8a6c 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/RoomPanel.java
@@ -58,6 +58,7 @@ import org.apache.openmeetings.db.util.ws.RoomMessage.Type;
import org.apache.openmeetings.db.util.ws.TextRoomMessage;
import org.apache.openmeetings.util.NullStringer;
import org.apache.openmeetings.web.app.ClientManager;
+import org.apache.openmeetings.web.app.QuickPollManager;
import org.apache.openmeetings.web.app.StreamClientManager;
import org.apache.openmeetings.web.app.WebSession;
import org.apache.openmeetings.web.common.BasePanel;
@@ -139,7 +140,8 @@ public class RoomPanel extends BasePanel {
}
StringBuilder sb = new StringBuilder("Room.init(").append(options.toString(new NullStringer())).append(");")
.append(wb.getInitScript())
- .append("Room.setSize();");
+ .append("Room.setSize();")
+ .append(getQuickPollJs());
target.appendJavaScript(sb);
WebSocketHelper.sendRoom(new RoomMessage(r.getId(), _c, RoomMessage.Type.roomEnter));
// play video from other participants
@@ -618,12 +620,22 @@ public class RoomPanel extends BasePanel {
handler.appendJavaScript(String.format("if (typeof(VideoManager) !== 'undefined') {VideoManager.exclusive('%s');}", uid));
}
break;
+ case quickPollUpdated:
+ {
+ menu.update(handler);
+ handler.appendJavaScript(getQuickPollJs());
+ }
+ break;
}
}
}
super.onEvent(event);
}
+ private String getQuickPollJs() {
+ return String.format("Room.quickPoll(%s);", getBean(QuickPollManager.class).toJson(r.getId()));
+ }
+
private void updateInterviewRecordingButtons(IPartialPageRequestHandler handler) {
Client _c = getClient();
if (interview && _c.hasRight(Right.moderator)) {
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/PollsSubMenu.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/PollsSubMenu.java
new file mode 100644
index 0000000..15bdcc7
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/PollsSubMenu.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") + you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+package org.apache.openmeetings.web.room.menu;
+
+import static org.apache.openmeetings.util.OpenmeetingsVariables.getWebAppRootKey;
+import static org.apache.openmeetings.web.app.Application.getBean;
+import static org.apache.openmeetings.web.app.WebSession.getUserId;
+import static org.apache.openmeetings.web.room.sidebar.RoomSidebar.PARAM_ACTION;
+import static org.apache.openmeetings.web.util.CallbackFunctionHelper.getNamedFunction;
+import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
+
+import java.io.Serializable;
+
+import org.apache.openmeetings.db.dao.room.PollDao;
+import org.apache.openmeetings.db.entity.basic.Client;
+import org.apache.openmeetings.db.entity.room.Room;
+import org.apache.openmeetings.db.entity.room.Room.RoomElement;
+import org.apache.openmeetings.db.entity.room.RoomPoll;
+import org.apache.openmeetings.web.app.QuickPollManager;
+import org.apache.openmeetings.web.common.menu.RoomMenuItem;
+import org.apache.openmeetings.web.room.RoomPanel;
+import org.apache.openmeetings.web.room.poll.CreatePollDialog;
+import org.apache.openmeetings.web.room.poll.PollResultsDialog;
+import org.apache.openmeetings.web.room.poll.VoteDialog;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.PriorityHeaderItem;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+public class PollsSubMenu implements Serializable {
+ private static final long serialVersionUID = 1L;
+ private static final Logger log = Red5LoggerFactory.getLogger(PollsSubMenu.class, getWebAppRootKey());
+ private static final String FUNC_QPOLL_ACTION = "quickPollAction";
+ private static final String PARAM_VOTE = "vote";
+ private static final String ACTION_CLOSE = "close";
+ private final RoomPanel room;
+ private final RoomMenuPanel mp;
+ private final CreatePollDialog createPoll;
+ private final VoteDialog vote;
+ private final PollResultsDialog pollResults;
+ private RoomMenuItem pollsMenu;
+ private RoomMenuItem pollQuickMenuItem;
+ private RoomMenuItem pollCreateMenuItem;
+ private RoomMenuItem pollVoteMenuItem;
+ private RoomMenuItem pollResultMenuItem;
+ private final AbstractDefaultAjaxBehavior quickPollAction = new AbstractDefaultAjaxBehavior() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void respond(AjaxRequestTarget target) {
+ try {
+ String action = mp.getRequest().getRequestParameters().getParameterValue(PARAM_ACTION).toString();
+ QuickPollManager qm = getBean(QuickPollManager.class);
+ Client c = room.getClient();
+ if (ACTION_CLOSE.equals(action)) {
+ qm.close(c);
+ } else if (PARAM_VOTE.equals(action)) {
+ boolean vote = mp.getRequest().getRequestParameters().getParameterValue(PARAM_VOTE).toBoolean();
+ qm.vote(c, vote);
+ }
+ } catch (Exception e) {
+ log.error("Unexpected exception while toggle 'quickPollAction'", e);
+ }
+ }
+ };
+
+ public PollsSubMenu(final RoomPanel room, final RoomMenuPanel mp) {
+ this.room = room;
+ this.mp = mp;
+ mp.add(createPoll = new CreatePollDialog("createPoll", room.getRoom().getId()));
+ mp.add(vote = new VoteDialog("vote"));
+ mp.add(pollResults = new PollResultsDialog("pollResults", room.getRoom().getId()));
+ }
+
+ public void init() {
+ pollsMenu = new RoomMenuItem(mp.getString("menu.polls"), null, false);
+ pollQuickMenuItem = new RoomMenuItem(mp.getString("menu.polls.quick.title"), mp.getString("menu.polls.quick.descr"), false) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick(AjaxRequestTarget target) {
+ getBean(QuickPollManager.class).start(room.getClient());
+ }
+ };
+ pollCreateMenuItem = new RoomMenuItem(mp.getString("24"), mp.getString("1483"), false) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick(AjaxRequestTarget target) {
+ createPoll.updateModel(target);
+ createPoll.open(target);
+ }
+ };
+ pollVoteMenuItem = new RoomMenuItem(mp.getString("32"), mp.getString("1485"), false) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick(AjaxRequestTarget target) {
+ RoomPoll rp = getBean(PollDao.class).getByRoom(room.getRoom().getId());
+ if (rp != null) {
+ vote.updateModel(target, rp);
+ vote.open(target);
+ }
+ }
+ };
+ pollResultMenuItem = new RoomMenuItem(mp.getString("37"), mp.getString("1484"), false) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick(AjaxRequestTarget target) {
+ pollResults.updateModel(target, room.getClient().hasRight(Room.Right.moderator));
+ pollResults.open(target);
+ }
+ };
+ mp.add(quickPollAction);
+ }
+
+ RoomMenuItem getMenu() {
+ pollsMenu.setTop(true);
+ pollsMenu.getItems().add(pollQuickMenuItem);
+ pollsMenu.getItems().add(pollCreateMenuItem);
+ pollsMenu.getItems().add(pollResultMenuItem);
+ pollsMenu.getItems().add(pollVoteMenuItem);
+ return pollsMenu;
+ }
+
+ public void update(final boolean moder, final boolean notExternalUser, final Room r) {
+ PollDao pollDao = getBean(PollDao.class);
+ boolean pollExists = pollDao.hasPoll(r.getId());
+ pollsMenu.setEnabled((moder && !r.isHidden(RoomElement.ActionMenu)) || (!moder && r.isAllowUserQuestions()));
+ pollQuickMenuItem.setEnabled(room.getClient().hasRight(Room.Right.presenter) && !getBean(QuickPollManager.class).isStarted(r.getId()));
+ pollCreateMenuItem.setEnabled(moder);
+ pollVoteMenuItem.setEnabled(pollExists && notExternalUser && !pollDao.hasVoted(r.getId(), getUserId()));
+ pollResultMenuItem.setEnabled(pollExists || !pollDao.getArchived(r.getId()).isEmpty());
+ }
+
+ public void updatePoll(IPartialPageRequestHandler handler, Long createdBy) {
+ RoomPoll rp = getBean(PollDao.class).getByRoom(room.getRoom().getId());
+ if (rp != null) {
+ vote.updateModel(handler, rp);
+ } else {
+ vote.close(handler, null);
+ }
+ if (createdBy != null && !getUserId().equals(createdBy)) {
+ vote.open(handler);
+ }
+ if (pollResults.isOpened()) {
+ pollResults.updateModel(handler, room.getClient().hasRight(Room.Right.moderator));
+ }
+ }
+
+ public void renderHead(IHeaderResponse response) {
+ response.render(new PriorityHeaderItem(getNamedFunction(FUNC_QPOLL_ACTION, quickPollAction, explicit(PARAM_ACTION), explicit(PARAM_VOTE))));
+ }
+}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java
index d3aa885..e3a2f1a 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/menu/RoomMenuPanel.java
@@ -27,7 +27,6 @@ import static org.apache.openmeetings.util.OpenmeetingsVariables.CONFIG_REDIRECT
import static org.apache.openmeetings.util.OpenmeetingsVariables.getBaseUrl;
import static org.apache.openmeetings.util.OpenmeetingsVariables.isSipEnabled;
import static org.apache.openmeetings.web.app.Application.getBean;
-import static org.apache.openmeetings.web.app.WebSession.getUserId;
import static org.apache.openmeetings.web.util.GroupLogoResourceReference.getUrl;
import static org.apache.openmeetings.web.util.OmUrlFragment.ROOMS_PUBLIC;
@@ -37,11 +36,9 @@ import java.util.List;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.openmeetings.core.util.WebSocketHelper;
import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
-import org.apache.openmeetings.db.dao.room.PollDao;
import org.apache.openmeetings.db.entity.basic.Client;
import org.apache.openmeetings.db.entity.room.Room;
import org.apache.openmeetings.db.entity.room.Room.RoomElement;
-import org.apache.openmeetings.db.entity.room.RoomPoll;
import org.apache.openmeetings.db.entity.user.Group;
import org.apache.openmeetings.db.entity.user.User;
import org.apache.openmeetings.db.util.ws.RoomMessage.Type;
@@ -55,12 +52,10 @@ import org.apache.openmeetings.web.common.menu.MenuPanel;
import org.apache.openmeetings.web.common.menu.RoomMenuItem;
import org.apache.openmeetings.web.room.OmRedirectTimerBehavior;
import org.apache.openmeetings.web.room.RoomPanel;
-import org.apache.openmeetings.web.room.poll.CreatePollDialog;
-import org.apache.openmeetings.web.room.poll.PollResultsDialog;
-import org.apache.openmeetings.web.room.poll.VoteDialog;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
+import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
@@ -72,9 +67,6 @@ import com.googlecode.wicket.jquery.ui.widget.menu.IMenuItem;
public class RoomMenuPanel extends Panel {
private static final long serialVersionUID = 1L;
private final InvitationDialog invite;
- private final CreatePollDialog createPoll;
- private final VoteDialog vote;
- private final PollResultsDialog pollResults;
private final SipDialerDialog sipDialer;
private MenuPanel menuPanel;
private final StartSharingButton shareBtn;
@@ -100,16 +92,11 @@ public class RoomMenuPanel extends Panel {
private RoomMenuItem exitMenuItem;
private RoomMenuItem filesMenu;
private RoomMenuItem actionsMenu;
- private RoomMenuItem pollsMenu;
private RoomMenuItem inviteMenuItem;
private RoomMenuItem shareMenuItem;
private RoomMenuItem applyModerMenuItem;
private RoomMenuItem applyWbMenuItem;
private RoomMenuItem applyAvMenuItem;
- private RoomMenuItem pollQuickMenuItem;
- private RoomMenuItem pollCreateMenuItem;
- private RoomMenuItem pollVoteMenuItem;
- private RoomMenuItem pollResultMenuItem;
private RoomMenuItem sipDialerMenuItem;
private RoomMenuItem downloadPngMenuItem;
private RoomMenuItem downloadJpgMenuItem;
@@ -122,6 +109,7 @@ public class RoomMenuPanel extends Panel {
return getUrl(getRequestCycle(), getGroup().getId());
}
};
+ private final PollsSubMenu pollsSubMenu;
public RoomMenuPanel(String id, final RoomPanel room) {
super(id);
@@ -137,10 +125,8 @@ public class RoomMenuPanel extends Panel {
RoomInvitationForm rif = new RoomInvitationForm("form", room.getRoom().getId());
add(invite = new InvitationDialog("invite", rif));
rif.setDialog(invite);
- add(createPoll = new CreatePollDialog("createPoll", room.getRoom().getId()));
- add(vote = new VoteDialog("vote"));
- add(pollResults = new PollResultsDialog("pollResults", room.getRoom().getId()));
add(sipDialer = new SipDialerDialog("sipDialer", room));
+ pollsSubMenu = new PollsSubMenu(room, this);
}
private Group getGroup() {
@@ -162,8 +148,7 @@ public class RoomMenuPanel extends Panel {
};
filesMenu = new RoomMenuItem(getString("245"), null, false);
actionsMenu = new RoomMenuItem(getString("635"), null, false);
- pollsMenu = new RoomMenuItem(getString("menu.polls"), null, false);
-
+ pollsSubMenu.init();
inviteMenuItem = new RoomMenuItem(getString("213"), getString("1489"), false) {
private static final long serialVersionUID = 1L;
@@ -205,45 +190,6 @@ public class RoomMenuPanel extends Panel {
room.requestRight(Room.Right.video, target);
}
};
- pollQuickMenuItem = new RoomMenuItem(getString("menu.polls.quick.title"), getString("menu.polls.quick.descr"), false) {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void onClick(AjaxRequestTarget target) {
- //createPoll.updateModel(target);
- //createPoll.open(target);
- }
- };
- pollCreateMenuItem = new RoomMenuItem(getString("24"), getString("1483"), false) {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void onClick(AjaxRequestTarget target) {
- createPoll.updateModel(target);
- createPoll.open(target);
- }
- };
- pollVoteMenuItem = new RoomMenuItem(getString("32"), getString("1485"), false) {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void onClick(AjaxRequestTarget target) {
- RoomPoll rp = getBean(PollDao.class).getByRoom(room.getRoom().getId());
- if (rp != null) {
- vote.updateModel(target, rp);
- vote.open(target);
- }
- }
- };
- pollResultMenuItem = new RoomMenuItem(getString("37"), getString("1484"), false) {
- private static final long serialVersionUID = 1L;
-
- @Override
- public void onClick(AjaxRequestTarget target) {
- pollResults.updateModel(target, room.getClient().hasRight(Room.Right.moderator));
- pollResults.open(target);
- }
- };
sipDialerMenuItem = new RoomMenuItem(getString("1447"), getString("1488"), false) {
private static final long serialVersionUID = 1L;
@@ -300,6 +246,12 @@ public class RoomMenuPanel extends Panel {
super.onInitialize();
}
+ @Override
+ public void renderHead(IHeaderResponse response) {
+ super.renderHead(response);
+ pollsSubMenu.renderHead(response);
+ }
+
private List<IMenuItem> getMenu() {
List<IMenuItem> menu = new ArrayList<>();
exitMenuItem.setEnabled(false);
@@ -327,12 +279,7 @@ public class RoomMenuPanel extends Panel {
actionsMenu.getItems().add(downloadPdfMenuItem);
menu.add(actionsMenu);
- pollsMenu.setTop(true);
- pollsMenu.getItems().add(pollQuickMenuItem);
- pollsMenu.getItems().add(pollCreateMenuItem);
- pollsMenu.getItems().add(pollResultMenuItem);
- pollsMenu.getItems().add(pollVoteMenuItem);
- menu.add(pollsMenu);
+ menu.add(pollsSubMenu.getMenu());
return menu;
}
@@ -345,25 +292,19 @@ public class RoomMenuPanel extends Panel {
downloadPngMenuItem.setEnabled(!isInterview);
downloadJpgMenuItem.setEnabled(!isInterview);
downloadPdfMenuItem.setEnabled(!isInterview);
- PollDao pollDao = getBean(PollDao.class);
- boolean pollExists = pollDao.hasPoll(r.getId());
User u = room.getClient().getUser();
boolean notExternalUser = u.getType() != User.Type.contact;
exitMenuItem.setEnabled(notExternalUser);
filesMenu.setEnabled(!isInterview && room.getSidebar().isShowFiles());
boolean moder = room.getClient().hasRight(Room.Right.moderator);
+ pollsSubMenu.update(moder, notExternalUser, r);
actionsMenu.setEnabled((moder && !r.isHidden(RoomElement.ActionMenu)) || (!moder && r.isAllowUserQuestions()));
- pollsMenu.setEnabled((moder && !r.isHidden(RoomElement.ActionMenu)) || (!moder && r.isAllowUserQuestions()));
inviteMenuItem.setEnabled(notExternalUser && moder);
boolean shareVisible = room.screenShareAllowed();
shareMenuItem.setEnabled(shareVisible);
applyModerMenuItem.setEnabled(!moder);
applyWbMenuItem.setEnabled(!room.getClient().hasRight(Room.Right.whiteBoard));
applyAvMenuItem.setEnabled(!room.getClient().hasRight(Room.Right.audio) || !room.getClient().hasRight(Room.Right.video));
- pollQuickMenuItem.setEnabled(moder);
- pollCreateMenuItem.setEnabled(moder);
- pollVoteMenuItem.setEnabled(pollExists && notExternalUser && !pollDao.hasVoted(r.getId(), getUserId()));
- pollResultMenuItem.setEnabled(pollExists || !pollDao.getArchived(r.getId()).isEmpty());
sipDialerMenuItem.setEnabled(r.isSipEnabled() && isSipEnabled());
menuPanel.update(handler);
StringBuilder roomClass = new StringBuilder("room name");
@@ -392,18 +333,7 @@ public class RoomMenuPanel extends Panel {
}
public void updatePoll(IPartialPageRequestHandler handler, Long createdBy) {
- RoomPoll rp = getBean(PollDao.class).getByRoom(room.getRoom().getId());
- if (rp != null) {
- vote.updateModel(handler, rp);
- } else {
- vote.close(handler, null);
- }
- if (createdBy != null && !getUserId().equals(createdBy)) {
- vote.open(handler);
- }
- if (pollResults.isOpened()) {
- pollResults.updateModel(handler, room.getClient().hasRight(Room.Right.moderator));
- }
+ pollsSubMenu.updatePoll(handler, createdBy);
update(handler);
}
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room-base.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room-base.js
index a150835..d89c851 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room-base.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/room-base.js
@@ -1,549 +1,4 @@
/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */
-const WB_AREA_SEL = '.room.wb.area';
-const WBA_WB_SEL = '.room.wb.area .ui-tabs-panel.ui-corner-bottom.ui-widget-content:visible';
-var WBA_SEL = WB_AREA_SEL;
-const VID_SEL = '.video.user-video';
-var VideoUtil = (function() {
- const self = {};
- function _getVid(uid) {
- return "video" + uid;
- }
- function _isSharing(c) {
- return 'sharing' === c.type && c.screenActivities.indexOf('sharing') > -1;
- }
- function _isRecording(c) {
- return 'sharing' === c.type
- && c.screenActivities.indexOf('recording') > -1
- && c.screenActivities.indexOf('sharing') < 0;
- }
- function _hasAudio(c) {
- return c.activities.indexOf('broadcastA') > -1;
- }
- function _hasVideo(c) {
- return c.activities.indexOf('broadcastV') > -1;
- }
- function _getRects(sel, excl) {
- const list = [], elems = $(sel);
- for (let i = 0; i < elems.length; ++i) {
- if (excl !== $(elems[i]).attr('aria-describedby')) {
- list.push(_getRect(elems[i]));
- }
- }
- return list;
- }
- function _getRect(e) {
- const win = $(e), winoff = win.offset();
- return {left: winoff.left
- , top: winoff.top
- , right: winoff.left + win.width()
- , bottom: winoff.top + win.height()};
- }
- function _getPos(list, w, h) {
- if (Room.getOptions().interview) {
- return {left: 0, top: 0};
- }
- const wba = $(WBA_SEL), woffset = wba.offset()
- , offsetX = 20, offsetY = 10
- , area = {left: woffset.left, top: woffset.top, right: woffset.left + wba.width(), bottom: woffset.top + wba.height()};
- const rectNew = {
- _left: area.left
- , _top: area.top
- , right: area.left + w
- , bottom: area.top + h
- , get left() {
- return this._left
- }
- , set left(l) {
- this._left = l;
- this.right = l + w;
- }
- , get top() {
- return this._top
- }
- , set top(t) {
- this._top = t;
- this.bottom = t + h;
- }
- };
- let minY = area.bottom, posFound;
- do {
- posFound = true
- for (let i = 0; i < list.length; ++i) {
- const rect = list[i];
- minY = Math.min(minY, rect.bottom);
-
- if (rectNew.left < rect.right && rectNew.right > rect.left && rectNew.top < rect.bottom && rectNew.bottom > rect.top) {
- rectNew.left = rect.right + offsetX;
- posFound = false;
- }
- if (rectNew.right >= area.right) {
- rectNew.left = area.left;
- rectNew.top = minY + offsetY;
- posFound = false;
- }
- if (rectNew.bottom >= area.bottom) {
- rectNew.top = area.top;
- posFound = true;
- break;
- }
- }
- } while (!posFound);
- return {left: rectNew.left, top: rectNew.top};
- }
- function _arrange() {
- const list = [], elems = $(VID_SEL);
- for (let i = 0; i < elems.length; ++i) {
- const v = $(elems[i]);
- v.css(_getPos(list, v.width(), v.height()));
- list.push(_getRect(v));
- }
- }
-
- self.getVid = _getVid;
- self.isSharing = _isSharing;
- self.isRecording = _isRecording;
- self.hasAudio = _hasAudio;
- self.hasVideo = _hasVideo;
- self.getRects = _getRects;
- self.getPos = _getPos;
- self.arrange = _arrange;
- return self;
-})();
-var Video = (function() {
- const self = {};
- let c, v, vc, t, f, swf, size, vol, slider, handle
- , lastVolume = 50;
-
- function _getName() {
- return c.user.firstName + ' ' + c.user.lastName;
- }
- function _resizeDlg(_ww, _hh) {
- const interview = Room.getOptions().interview;
- const _w = interview ? 320 : _ww, _h = interview ? 260 : _hh;
- const h = _h + t.height() + 2 + (f.is(":visible") ? f.height() : 0);
- v.dialog("option", "width", _w).dialog("option", "height", h);
- _resize(_w, _h);
- return h;
- }
- function _securityMode(on) {
- if (Room.getOptions().interview) {
- return;
- }
- if (on) {
- v.dialog("option", "position", {my: "center", at: "center", of: WBA_SEL});
- } else {
- const h = _resizeDlg(size.width, size.height);
- v.dialog("widget").css(VideoUtil.getPos(VideoUtil.getRects(VID_SEL, VideoUtil.getVid(c.uid)), c.width, h));
- }
- }
- function _resize(w, h) {
- vc.width(w).height(h);
- swf.attr('width', w).attr('height', h);
- }
- function _handleMicStatus(state) {
- if (!f.is(":visible")) {
- return;
- }
- if (state) {
- f.find('.off').hide();
- f.find('.on').show();
- f.addClass('ui-state-highlight');
- t.addClass('ui-state-highlight');
- } else {
- f.find('.off').show();
- f.find('.on').hide();
- f.removeClass('ui-state-highlight');
- t.removeClass('ui-state-highlight');
- }
- }
- function _handleVolume(val) {
- handle.text(val);
- const ico = vol.find('.ui-icon');
- if (val > 0 && ico.hasClass('ui-icon-volume-off')) {
- ico.toggleClass('ui-icon-volume-off ui-icon-volume-on');
- vol.removeClass('ui-state-error');
- _handleMicStatus(true);
- } else if (val === 0 && ico.hasClass('ui-icon-volume-on')) {
- ico.toggleClass('ui-icon-volume-on ui-icon-volume-off');
- vol.addClass('ui-state-error');
- _handleMicStatus(false);
- }
- if (typeof(swf[0].setVolume) === 'function') {
- swf[0].setVolume(val);
- }
- }
- function _mute(mute) {
- if (!slider) {
- return;
- }
- if (mute) {
- const val = slider.slider("option", "value");
- if (val > 0) {
- lastVolume = val;
- }
- slider.slider("option", "value", 0);
- _handleVolume(0);
- } else {
- slider.slider("option", "value", lastVolume);
- _handleVolume(lastVolume);
- }
- }
- function _init(_c, _pos) {
- c = _c;
- size = {width: c.width, height: c.height};
- const _id = VideoUtil.getVid(c.uid)
- , name = _getName()
- , _w = c.self ? Math.max(300, c.width) : c.width
- , _h = c.self ? Math.max(200, c.height) : c.height
- , opts = Room.getOptions();
- { //scope
- const cont = opts.interview ? $('.pod.pod-' + c.pod) : $('.room.box');
- cont.append(OmUtil.tmpl('#user-video', _id).attr('title', name)
- .attr('data-client-uid', c.type + c.cuid).data(self));
- }
- v = $('#' + _id);
- v.dialog({
- classes: {
- 'ui-dialog': 'ui-corner-all video user-video' + (opts.showMicStatus ? ' mic-status' : '')
- , 'ui-dialog-titlebar': 'ui-corner-all' + (opts.showMicStatus ? ' ui-state-highlight' : '')
- }
- , width: _w
- , minWidth: 40
- , minHeight: 50
- , autoOpen: true
- , appendTo: opts.interview ? '.pod.pod-' + c.pod : '.room.box'
- , draggable: !opts.interview
- , resizable: !opts.interview
- , modal: false
- , resizeStop: function(event, ui) {
- const w = ui.size.width - 2
- , h = ui.size.height - t.height() - 4 - (f.is(":visible") ? f.height() : 0);
- _resize(w, h);
- swf[0].vidResize(w, h);
- }
- , close: function() {
- VideoManager.close(c.uid, true);
- }
- }).dialogExtend({
- icons: {
- 'collapse': 'ui-icon-minus'
- }
- , closable: VideoUtil.isSharing(c)
- , collapsable: true
- , dblclick: "collapse"
- });
- t = v.parent().find('.ui-dialog-titlebar').attr('title', name);
- f = v.find('.footer');
- if (!VideoUtil.isSharing(c)) {
- v.parent().find('.ui-dialog-titlebar-buttonpane')
- .append($('#video-volume-btn').children().clone())
- .append($('#video-refresh-btn').children().clone());
- const volume = v.parent().find('.dropdown-menu.video.volume');
- slider = v.parent().find('.slider');
- if (opts.interview) {
- v.parent().find('.ui-dialog-titlebar-collapse').hide();
- }
- vol = v.parent().find('.ui-dialog-titlebar-volume')
- .on('mouseenter', function(e) {
- e.stopImmediatePropagation();
- volume.toggle();
- })
- .click(function(e) {
- e.stopImmediatePropagation();
- const muted = $(this).find('.ui-icon').hasClass('ui-icon-volume-off');
- roomAction('mute', JSON.stringify({uid: c.cuid, mute: !muted}));
- _mute(!muted);
- volume.hide();
- return false;
- }).dblclick(function(e) {
- e.stopImmediatePropagation();
- return false;
- });
- v.parent().find('.ui-dialog-titlebar-refresh')
- .click(function(e) {
- e.stopImmediatePropagation();
- _refresh();
- return false;
- }).dblclick(function(e) {
- e.stopImmediatePropagation();
- return false;
- });
- volume.on('mouseleave', function() {
- $(this).hide();
- });
- handle = v.parent().find('.slider .handle');
- slider.slider({
- orientation: 'vertical'
- , range: 'min'
- , min: 0
- , max: 100
- , value: lastVolume
- , create: function() {
- handle.text($(this).slider("value"));
- }
- , slide: function(event, ui) {
- _handleVolume(ui.value);
- }
- });
- const hasAudio = VideoUtil.hasAudio(c);
- _handleMicStatus(hasAudio);
- if (!hasAudio) {
- vol.hide();
- }
- }
- vc = v.find('.video');
- vc.width(_w).height(_h);
- //broadcast
- const o = Room.getOptions();
- if (c.self) {
- o.cam = c.cam;
- o.mic = c.mic;
- o.mode = 'broadcast';
- } else {
- o.mode = 'play';
- }
- o.av = c.activities.join();
- o.rights = o.rights.join();
- o.width = c.width;
- o.height = c.height;
- o.sid = c.sid;
- o.uid = c.uid;
- o.cuid = c.cuid;
- o.userId = c.user.id;
- o.broadcastId = c.broadcastId;
- o.type = c.type;
- delete o.keycode;
- swf = initSwf(vc, 'main.swf', _id + '-swf', o);
- swf.attr('width', _w).attr('height', _h);
- v.dialog("widget").css(_pos);
- }
- function _update(_c) {
- const opts = Room.getOptions();
- c.screenActivities = _c.screenActivities;
- c.activities = _c.activities;
- c.user.firstName = _c.user.firstName;
- c.user.lastName = _c.user.lastName;
- const hasAudio = VideoUtil.hasAudio(c);
- _handleMicStatus(hasAudio);
- if (hasAudio) {
- vol.show();
- } else {
- vol.hide();
- v.parent().find('.dropdown-menu.video.volume').hide();
- }
- if (opts.interview && c.pod !== _c.pod) {
- c.pod = _c.pod;
- v.dialog('option', 'appendTo', '.pod.pod-' + c.pod);
- }
- const name = _getName();
- v.dialog('option', 'title', name).parent().find('.ui-dialog-titlebar').attr('title', name);
- if (typeof(swf[0].update) === 'function') {
- c.self ? swf[0].update() : swf[0].update(c);
- }
- }
- function _refresh(_opts) {
- if (typeof(swf[0].refresh) === 'function') {
- const opts = _opts || {};
- if (!Room.getOptions().interview && !isNaN(opts.width)) {
- _resizeDlg(opts.width, opts.height);
- }
- try {
- swf[0].refresh(opts);
- } catch (e) {
- //swf might throw
- }
- }
- }
- function _setRights(_r) {
- if (typeof(swf[0].setRights) === 'function') {
- swf[0].setRights(_r);
- }
- }
- function _cleanup() {
- if (typeof(swf[0].cleanup) === 'function') {
- swf[0].cleanup();
- }
- }
-
- self.update = _update;
- self.refresh = _refresh;
- self.mute = _mute;
- self.isMuted = function() { return 0 === slider.slider("option", "value"); };
- self.init = _init;
- self.securityMode = _securityMode;
- self.client = function() { return c; };
- self.setRights = _setRights;
- self.cleanup = _cleanup;
- return self;
-});
-var VideoManager = (function() {
- const self = {};
- let share, inited = false;
-
- function _init() {
- if ($(WB_AREA_SEL + ' .wb-area .tabs').length > 0) {
- WBA_SEL = WBA_WB_SEL;
- }
- VideoSettings.init(Room.getOptions());
- share = $('.room.box').find('.icon.shared.ui-button');
- inited = true;
- }
- function _update(c) {
- if (!inited) {
- return;
- }
- for (let i = 0; i < c.streams.length; ++i) {
- const cl = JSON.parse(JSON.stringify(c)), s = c.streams[i];
- delete cl.streams;
- $.extend(cl, s);
- if (cl.self && VideoUtil.isSharing(cl) || VideoUtil.isRecording(cl)) {
- continue;
- }
- const _id = VideoUtil.getVid(cl.uid)
- , av = VideoUtil.hasAudio(cl) || VideoUtil.hasVideo(cl)
- , v = $('#' + _id);
- if (av && v.length !== 1 && !!cl.self) {
- Video().init(cl, VideoUtil.getPos(VideoUtil.getRects(VID_SEL), cl.width, cl.height + 25));
- } else if (av && v.length === 1) {
- v.data().update(cl);
- } else if (!av && v.length === 1) {
- _closeV(v);
- }
- }
- if (c.uid === Room.getOptions().uid) {
- Room.setRights(c.rights);
- const windows = $(VID_SEL + ' .ui-dialog-content');
- for (let i = 0; i < windows.length; ++i) {
- const w = $(windows[i]);
- w.data().setRights(c.rights);
- }
-
- }
- if (c.streams.length === 0) {
- // check for non inited video window
- const v = $('#' + VideoUtil.getVid(c.uid));
- if (v.length === 1) {
- _closeV(v);
- }
- }
- }
- function _closeV(v) {
- if (v.dialog('instance') !== undefined) {
- v.dialog('destroy');
- }
- v.remove();
- }
- function _play(c) {
- if (!inited) {
- return;
- }
- if (VideoUtil.isSharing(c)) {
- _highlight(share
- .attr('title', share.data('user') + ' ' + c.user.firstName + ' ' + c.user.lastName + ' ' + share.data('text'))
- .data('uid', c.uid)
- .show(), 10);
- share.tooltip().off('click').click(function() {
- const v = $('#' + VideoUtil.getVid(c.uid))
- if (v.length !== 1) {
- Video().init(c, $(WBA_SEL).offset());
- } else {
- v.dialog('open');
- }
- });
- } else if ('sharing' !== c.type) {
- Video().init(c, VideoUtil.getPos(VideoUtil.getRects(VID_SEL), c.width, c.height + 25));
- }
- }
- function _close(uid, showShareBtn) {
- const _id = VideoUtil.getVid(uid), v = $('#' + _id);
- if (v.length === 1) {
- _closeV(v);
- }
- if (!showShareBtn && uid === share.data('uid')) {
- share.off('click').hide();
- }
- }
- function _highlight(el, count) {
- if (count < 0) {
- return;
- }
- el.addClass('ui-state-highlight', 2000, function() {
- el.removeClass('ui-state-highlight', 2000, function() {
- _highlight(el, --count);
- });
- });
- }
- function _find(uid) {
- return $(VID_SEL + ' div[data-client-uid="room' + uid + '"]');
- }
- function _micActivity(uid, active) {
- const u = $('#user' + uid + ' .audio-activity.ui-icon')
- , v = _find(uid).parent();
- if (active) {
- u.addClass("speaking");
- v.addClass('user-speaks')
- } else {
- u.removeClass("speaking");
- v.removeClass('user-speaks')
- }
- }
- function _refresh(uid, opts) {
- const v = _find(uid);
- if (v.length > 0) {
- v.data().refresh(opts);
- }
- }
- function _mute(uid, mute) {
- const v = _find(uid);
- if (v.length > 0) {
- v.data().mute(mute);
- }
- }
- function _clickExclusive(uid) {
- const s = VideoSettings.load();
- if (false !== s.video.confirmExclusive) {
- const dlg = $('#exclusive-confirm');
- dlg.dialog({
- buttons: [
- {
- text: dlg.data('btn-ok')
- , click: function() {
- s.video.confirmExclusive = !$('#exclusive-confirm-dont-show').prop('checked');
- VideoSettings.save();
- roomAction('exclusive', uid);
- $(this).dialog('close');
- }
- }
- , {
- text: dlg.data('btn-cancel')
- , click: function() {
- $(this).dialog('close');
- }
- }
- ]
- })
- }
- }
- function _exclusive(uid) {
- const windows = $(VID_SEL + ' .ui-dialog-content');
- for (let i = 0; i < windows.length; ++i) {
- const w = $(windows[i]);
- w.data().mute('room' + uid !== w.data('client-uid'));
- }
- }
-
- self.init = _init;
- self.update = _update;
- self.play = _play;
- self.close = _close;
- self.securityMode = function(uid, on) { $('#' + VideoUtil.getVid(uid)).data().securityMode(on); };
- self.micActivity = _micActivity;
- self.refresh = _refresh;
- self.mute = _mute;
- self.clickExclusive = _clickExclusive;
- self.exclusive = _exclusive;
- return self;
-})();
var Room = (function() {
const self = {}, sbSide = Settings.isRtl ? 'right' : 'left';
let options, menuHeight, chat, sb, dock, activities;
@@ -748,15 +203,64 @@ var Room = (function() {
]
});
}
+ function _setQuickPollRights() {
+ const close = $('#quick-vote .close');
+ if (close.length === 1) {
+ close.off();
+ if (options.rights.includes('superModerator') || options.rights.includes('moderator') || options.rights.includes('presenter')) {
+ close.show().click(function() {
+ quickPollAction('close');
+ });
+ } else {
+ close.hide();
+ }
+ }
+ }
+ function _quickPoll(obj) {
+ if (obj.started) {
+ let qv = $('#quick-vote');
+ if (qv.length === 0) {
+ const wbArea = $('.room.wb.area');
+ qv = OmUtil.tmpl('#quick-vote-template', 'quick-vote');
+ wbArea.append(qv);
+ }
+ const pro = qv.find('.control.pro')
+ con = qv.find('.control.con');
+ if (obj.voted) {
+ pro.removeClass('clickable').off();
+ con.removeClass('clickable').off();
+ } else {
+ pro.addClass('clickable').off().click(function() {
+ quickPollAction('vote', true);
+ });
+ con.addClass('clickable').off().click(function() {
+ quickPollAction('vote', false);
+ });
+ }
+ pro.find('.badge').text(obj.pros);
+ con.find('.badge').text(obj.cons);
+ _setQuickPollRights();
+ } else {
+ const qv = $('#quick-vote');
+ if (qv.length === 1) {
+ qv.remove();
+ }
+ }
+ OmUtil.tmpl('#quick-vote-template', 'quick-vote');
+ }
self.init = _init;
self.getMenuHeight = function() { return menuHeight; };
self.getOptions = function() { return typeof(options) === 'object' ? JSON.parse(JSON.stringify(options)) : {}; };
- self.setRights = function(_r) { return options.rights = _r; };
+ self.setRights = function(_r) {
+ options.rights = _r;
+ _setQuickPollRights();
+ };
self.setSize = _setSize;
self.load = _load;
self.unload = _unload;
self.showClipboard = _showClipboard;
+ self.quickPoll = _quickPoll;
return self;
})();
function startPrivateChat(el) {
@@ -773,7 +277,7 @@ function sipBtnEraseClick() {
const txt = $('.sip-number')
, t = txt.val();
if (!!t) {
- txt.val(t.substring(0, t.length -1));
+ txt.val(t.substring(0, t.length - 1));
}
}
function sipGetKey(evt) {
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java
index 923d97a..c16b630 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/sidebar/RoomSidebar.java
@@ -148,7 +148,7 @@ public class RoomSidebar extends Panel {
default:
}
} catch (Exception e) {
- log.error("Unexpected exception while toggle 'action'", e);
+ log.error("Unexpected exception while toggle 'roomAction'", e);
}
}
};
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video-manager.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video-manager.js
new file mode 100644
index 0000000..66c7e61
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video-manager.js
@@ -0,0 +1,169 @@
+/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */
+var VideoManager = (function() {
+ const self = {};
+ let share, inited = false;
+
+ function _init() {
+ if ($(WB_AREA_SEL + ' .wb-area .tabs').length > 0) {
+ WBA_SEL = WBA_WB_SEL;
+ }
+ VideoSettings.init(Room.getOptions());
+ share = $('.room.box').find('.icon.shared.ui-button');
+ inited = true;
+ }
+ function _update(c) {
+ if (!inited) {
+ return;
+ }
+ for (let i = 0; i < c.streams.length; ++i) {
+ const cl = JSON.parse(JSON.stringify(c)), s = c.streams[i];
+ delete cl.streams;
+ $.extend(cl, s);
+ if (cl.self && VideoUtil.isSharing(cl) || VideoUtil.isRecording(cl)) {
+ continue;
+ }
+ const _id = VideoUtil.getVid(cl.uid)
+ , av = VideoUtil.hasAudio(cl) || VideoUtil.hasVideo(cl)
+ , v = $('#' + _id);
+ if (av && v.length !== 1 && !!cl.self) {
+ Video().init(cl, VideoUtil.getPos(VideoUtil.getRects(VID_SEL), cl.width, cl.height + 25));
+ } else if (av && v.length === 1) {
+ v.data().update(cl);
+ } else if (!av && v.length === 1) {
+ _closeV(v);
+ }
+ }
+ if (c.uid === Room.getOptions().uid) {
+ Room.setRights(c.rights);
+ const windows = $(VID_SEL + ' .ui-dialog-content');
+ for (let i = 0; i < windows.length; ++i) {
+ const w = $(windows[i]);
+ w.data().setRights(c.rights);
+ }
+
+ }
+ if (c.streams.length === 0) {
+ // check for non inited video window
+ const v = $('#' + VideoUtil.getVid(c.uid));
+ if (v.length === 1) {
+ _closeV(v);
+ }
+ }
+ }
+ function _closeV(v) {
+ if (v.dialog('instance') !== undefined) {
+ v.dialog('destroy');
+ }
+ v.remove();
+ }
+ function _play(c) {
+ if (!inited) {
+ return;
+ }
+ if (VideoUtil.isSharing(c)) {
+ _highlight(share
+ .attr('title', share.data('user') + ' ' + c.user.firstName + ' ' + c.user.lastName + ' ' + share.data('text'))
+ .data('uid', c.uid)
+ .show(), 10);
+ share.tooltip().off('click').click(function() {
+ const v = $('#' + VideoUtil.getVid(c.uid))
+ if (v.length !== 1) {
+ Video().init(c, $(WBA_SEL).offset());
+ } else {
+ v.dialog('open');
+ }
+ });
+ } else if ('sharing' !== c.type) {
+ Video().init(c, VideoUtil.getPos(VideoUtil.getRects(VID_SEL), c.width, c.height + 25));
+ }
+ }
+ function _close(uid, showShareBtn) {
+ const _id = VideoUtil.getVid(uid), v = $('#' + _id);
+ if (v.length === 1) {
+ _closeV(v);
+ }
+ if (!showShareBtn && uid === share.data('uid')) {
+ share.off('click').hide();
+ }
+ }
+ function _highlight(el, count) {
+ if (count < 0) {
+ return;
+ }
+ el.addClass('ui-state-highlight', 2000, function() {
+ el.removeClass('ui-state-highlight', 2000, function() {
+ _highlight(el, --count);
+ });
+ });
+ }
+ function _find(uid) {
+ return $(VID_SEL + ' div[data-client-uid="room' + uid + '"]');
+ }
+ function _micActivity(uid, active) {
+ const u = $('#user' + uid + ' .audio-activity.ui-icon')
+ , v = _find(uid).parent();
+ if (active) {
+ u.addClass("speaking");
+ v.addClass('user-speaks')
+ } else {
+ u.removeClass("speaking");
+ v.removeClass('user-speaks')
+ }
+ }
+ function _refresh(uid, opts) {
+ const v = _find(uid);
+ if (v.length > 0) {
+ v.data().refresh(opts);
+ }
+ }
+ function _mute(uid, mute) {
+ const v = _find(uid);
+ if (v.length > 0) {
+ v.data().mute(mute);
+ }
+ }
+ function _clickExclusive(uid) {
+ const s = VideoSettings.load();
+ if (false !== s.video.confirmExclusive) {
+ const dlg = $('#exclusive-confirm');
+ dlg.dialog({
+ buttons: [
+ {
+ text: dlg.data('btn-ok')
+ , click: function() {
+ s.video.confirmExclusive = !$('#exclusive-confirm-dont-show').prop('checked');
+ VideoSettings.save();
+ roomAction('exclusive', uid);
+ $(this).dialog('close');
+ }
+ }
+ , {
+ text: dlg.data('btn-cancel')
+ , click: function() {
+ $(this).dialog('close');
+ }
+ }
+ ]
+ })
+ }
+ }
+ function _exclusive(uid) {
+ const windows = $(VID_SEL + ' .ui-dialog-content');
+ for (let i = 0; i < windows.length; ++i) {
+ const w = $(windows[i]);
+ w.data().mute('room' + uid !== w.data('client-uid'));
+ }
+ }
+
+ self.init = _init;
+ self.update = _update;
+ self.play = _play;
+ self.close = _close;
+ self.securityMode = function(uid, on) { $('#' + VideoUtil.getVid(uid)).data().securityMode(on); };
+ self.micActivity = _micActivity;
+ self.refresh = _refresh;
+ self.mute = _mute;
+ self.clickExclusive = _clickExclusive;
+ self.exclusive = _exclusive;
+ return self;
+})();
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video-util.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video-util.js
new file mode 100644
index 0000000..5f831ff
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video-util.js
@@ -0,0 +1,111 @@
+/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */
+const WB_AREA_SEL = '.room.wb.area';
+const WBA_WB_SEL = '.room.wb.area .ui-tabs-panel.ui-corner-bottom.ui-widget-content:visible';
+var WBA_SEL = WB_AREA_SEL;
+const VID_SEL = '.video.user-video';
+var VideoUtil = (function() {
+ const self = {};
+ function _getVid(uid) {
+ return "video" + uid;
+ }
+ function _isSharing(c) {
+ return 'sharing' === c.type && c.screenActivities.indexOf('sharing') > -1;
+ }
+ function _isRecording(c) {
+ return 'sharing' === c.type
+ && c.screenActivities.indexOf('recording') > -1
+ && c.screenActivities.indexOf('sharing') < 0;
+ }
+ function _hasAudio(c) {
+ return c.activities.indexOf('broadcastA') > -1;
+ }
+ function _hasVideo(c) {
+ return c.activities.indexOf('broadcastV') > -1;
+ }
+ function _getRects(sel, excl) {
+ const list = [], elems = $(sel);
+ for (let i = 0; i < elems.length; ++i) {
+ if (excl !== $(elems[i]).attr('aria-describedby')) {
+ list.push(_getRect(elems[i]));
+ }
+ }
+ return list;
+ }
+ function _getRect(e) {
+ const win = $(e), winoff = win.offset();
+ return {left: winoff.left
+ , top: winoff.top
+ , right: winoff.left + win.width()
+ , bottom: winoff.top + win.height()};
+ }
+ function _getPos(list, w, h) {
+ if (Room.getOptions().interview) {
+ return {left: 0, top: 0};
+ }
+ const wba = $(WBA_SEL), woffset = wba.offset()
+ , offsetX = 20, offsetY = 10
+ , area = {left: woffset.left, top: woffset.top, right: woffset.left + wba.width(), bottom: woffset.top + wba.height()};
+ const rectNew = {
+ _left: area.left
+ , _top: area.top
+ , right: area.left + w
+ , bottom: area.top + h
+ , get left() {
+ return this._left
+ }
+ , set left(l) {
+ this._left = l;
+ this.right = l + w;
+ }
+ , get top() {
+ return this._top
+ }
+ , set top(t) {
+ this._top = t;
+ this.bottom = t + h;
+ }
+ };
+ let minY = area.bottom, posFound;
+ do {
+ posFound = true
+ for (let i = 0; i < list.length; ++i) {
+ const rect = list[i];
+ minY = Math.min(minY, rect.bottom);
+
+ if (rectNew.left < rect.right && rectNew.right > rect.left && rectNew.top < rect.bottom && rectNew.bottom > rect.top) {
+ rectNew.left = rect.right + offsetX;
+ posFound = false;
+ }
+ if (rectNew.right >= area.right) {
+ rectNew.left = area.left;
+ rectNew.top = minY + offsetY;
+ posFound = false;
+ }
+ if (rectNew.bottom >= area.bottom) {
+ rectNew.top = area.top;
+ posFound = true;
+ break;
+ }
+ }
+ } while (!posFound);
+ return {left: rectNew.left, top: rectNew.top};
+ }
+ function _arrange() {
+ const list = [], elems = $(VID_SEL);
+ for (let i = 0; i < elems.length; ++i) {
+ const v = $(elems[i]);
+ v.css(_getPos(list, v.width(), v.height()));
+ list.push(_getRect(v));
+ }
+ }
+
+ self.getVid = _getVid;
+ self.isSharing = _isSharing;
+ self.isRecording = _isRecording;
+ self.hasAudio = _hasAudio;
+ self.hasVideo = _hasVideo;
+ self.getRects = _getRects;
+ self.getPos = _getPos;
+ self.arrange = _arrange;
+ return self;
+})();
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video.js
new file mode 100644
index 0000000..4322f35
--- /dev/null
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/video.js
@@ -0,0 +1,268 @@
+/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */
+var Video = (function() {
+ const self = {};
+ let c, v, vc, t, f, swf, size, vol, slider, handle
+ , lastVolume = 50;
+
+ function _getName() {
+ return c.user.firstName + ' ' + c.user.lastName;
+ }
+ function _resizeDlg(_ww, _hh) {
+ const interview = Room.getOptions().interview;
+ const _w = interview ? 320 : _ww, _h = interview ? 260 : _hh;
+ const h = _h + t.height() + 2 + (f.is(":visible") ? f.height() : 0);
+ v.dialog("option", "width", _w).dialog("option", "height", h);
+ _resize(_w, _h);
+ return h;
+ }
+ function _securityMode(on) {
+ if (Room.getOptions().interview) {
+ return;
+ }
+ if (on) {
+ v.dialog("option", "position", {my: "center", at: "center", of: WBA_SEL});
+ } else {
+ const h = _resizeDlg(size.width, size.height);
+ v.dialog("widget").css(VideoUtil.getPos(VideoUtil.getRects(VID_SEL, VideoUtil.getVid(c.uid)), c.width, h));
+ }
+ }
+ function _resize(w, h) {
+ vc.width(w).height(h);
+ swf.attr('width', w).attr('height', h);
+ }
+ function _handleMicStatus(state) {
+ if (!f.is(":visible")) {
+ return;
+ }
+ if (state) {
+ f.find('.off').hide();
+ f.find('.on').show();
+ f.addClass('ui-state-highlight');
+ t.addClass('ui-state-highlight');
+ } else {
+ f.find('.off').show();
+ f.find('.on').hide();
+ f.removeClass('ui-state-highlight');
+ t.removeClass('ui-state-highlight');
+ }
+ }
+ function _handleVolume(val) {
+ handle.text(val);
+ const ico = vol.find('.ui-icon');
+ if (val > 0 && ico.hasClass('ui-icon-volume-off')) {
+ ico.toggleClass('ui-icon-volume-off ui-icon-volume-on');
+ vol.removeClass('ui-state-error');
+ _handleMicStatus(true);
+ } else if (val === 0 && ico.hasClass('ui-icon-volume-on')) {
+ ico.toggleClass('ui-icon-volume-on ui-icon-volume-off');
+ vol.addClass('ui-state-error');
+ _handleMicStatus(false);
+ }
+ if (typeof(swf[0].setVolume) === 'function') {
+ swf[0].setVolume(val);
+ }
+ }
+ function _mute(mute) {
+ if (!slider) {
+ return;
+ }
+ if (mute) {
+ const val = slider.slider("option", "value");
+ if (val > 0) {
+ lastVolume = val;
+ }
+ slider.slider("option", "value", 0);
+ _handleVolume(0);
+ } else {
+ slider.slider("option", "value", lastVolume);
+ _handleVolume(lastVolume);
+ }
+ }
+ function _init(_c, _pos) {
+ c = _c;
+ size = {width: c.width, height: c.height};
+ const _id = VideoUtil.getVid(c.uid)
+ , name = _getName()
+ , _w = c.self ? Math.max(300, c.width) : c.width
+ , _h = c.self ? Math.max(200, c.height) : c.height
+ , opts = Room.getOptions();
+ { //scope
+ const cont = opts.interview ? $('.pod.pod-' + c.pod) : $('.room.box');
+ cont.append(OmUtil.tmpl('#user-video', _id).attr('title', name)
+ .attr('data-client-uid', c.type + c.cuid).data(self));
+ }
+ v = $('#' + _id);
+ v.dialog({
+ classes: {
+ 'ui-dialog': 'ui-corner-all video user-video' + (opts.showMicStatus ? ' mic-status' : '')
+ , 'ui-dialog-titlebar': 'ui-corner-all' + (opts.showMicStatus ? ' ui-state-highlight' : '')
+ }
+ , width: _w
+ , minWidth: 40
+ , minHeight: 50
+ , autoOpen: true
+ , appendTo: opts.interview ? '.pod.pod-' + c.pod : '.room.box'
+ , draggable: !opts.interview
+ , resizable: !opts.interview
+ , modal: false
+ , resizeStop: function(event, ui) {
+ const w = ui.size.width - 2
+ , h = ui.size.height - t.height() - 4 - (f.is(":visible") ? f.height() : 0);
+ _resize(w, h);
+ swf[0].vidResize(w, h);
+ }
+ , close: function() {
+ VideoManager.close(c.uid, true);
+ }
+ }).dialogExtend({
+ icons: {
+ 'collapse': 'ui-icon-minus'
+ }
+ , closable: VideoUtil.isSharing(c)
+ , collapsable: true
+ , dblclick: "collapse"
+ });
+ t = v.parent().find('.ui-dialog-titlebar').attr('title', name);
+ f = v.find('.footer');
+ if (!VideoUtil.isSharing(c)) {
+ v.parent().find('.ui-dialog-titlebar-buttonpane')
+ .append($('#video-volume-btn').children().clone())
+ .append($('#video-refresh-btn').children().clone());
+ const volume = v.parent().find('.dropdown-menu.video.volume');
+ slider = v.parent().find('.slider');
+ if (opts.interview) {
+ v.parent().find('.ui-dialog-titlebar-collapse').hide();
+ }
+ vol = v.parent().find('.ui-dialog-titlebar-volume')
+ .on('mouseenter', function(e) {
+ e.stopImmediatePropagation();
+ volume.toggle();
+ })
+ .click(function(e) {
+ e.stopImmediatePropagation();
+ const muted = $(this).find('.ui-icon').hasClass('ui-icon-volume-off');
+ roomAction('mute', JSON.stringify({uid: c.cuid, mute: !muted}));
+ _mute(!muted);
+ volume.hide();
+ return false;
+ }).dblclick(function(e) {
+ e.stopImmediatePropagation();
+ return false;
+ });
+ v.parent().find('.ui-dialog-titlebar-refresh')
+ .click(function(e) {
+ e.stopImmediatePropagation();
+ _refresh();
+ return false;
+ }).dblclick(function(e) {
+ e.stopImmediatePropagation();
+ return false;
+ });
+ volume.on('mouseleave', function() {
+ $(this).hide();
+ });
+ handle = v.parent().find('.slider .handle');
+ slider.slider({
+ orientation: 'vertical'
+ , range: 'min'
+ , min: 0
+ , max: 100
+ , value: lastVolume
+ , create: function() {
+ handle.text($(this).slider("value"));
+ }
+ , slide: function(event, ui) {
+ _handleVolume(ui.value);
+ }
+ });
+ const hasAudio = VideoUtil.hasAudio(c);
+ _handleMicStatus(hasAudio);
+ if (!hasAudio) {
+ vol.hide();
+ }
+ }
+ vc = v.find('.video');
+ vc.width(_w).height(_h);
+ //broadcast
+ const o = Room.getOptions();
+ if (c.self) {
+ o.cam = c.cam;
+ o.mic = c.mic;
+ o.mode = 'broadcast';
+ } else {
+ o.mode = 'play';
+ }
+ o.av = c.activities.join();
+ o.rights = o.rights.join();
+ o.width = c.width;
+ o.height = c.height;
+ o.sid = c.sid;
+ o.uid = c.uid;
+ o.cuid = c.cuid;
+ o.userId = c.user.id;
+ o.broadcastId = c.broadcastId;
+ o.type = c.type;
+ delete o.keycode;
+ swf = initSwf(vc, 'main.swf', _id + '-swf', o);
+ swf.attr('width', _w).attr('height', _h);
+ v.dialog("widget").css(_pos);
+ }
+ function _update(_c) {
+ const opts = Room.getOptions();
+ c.screenActivities = _c.screenActivities;
+ c.activities = _c.activities;
+ c.user.firstName = _c.user.firstName;
+ c.user.lastName = _c.user.lastName;
+ const hasAudio = VideoUtil.hasAudio(c);
+ _handleMicStatus(hasAudio);
+ if (hasAudio) {
+ vol.show();
+ } else {
+ vol.hide();
+ v.parent().find('.dropdown-menu.video.volume').hide();
+ }
+ if (opts.interview && c.pod !== _c.pod) {
+ c.pod = _c.pod;
+ v.dialog('option', 'appendTo', '.pod.pod-' + c.pod);
+ }
+ const name = _getName();
+ v.dialog('option', 'title', name).parent().find('.ui-dialog-titlebar').attr('title', name);
+ if (typeof(swf[0].update) === 'function') {
+ c.self ? swf[0].update() : swf[0].update(c);
+ }
+ }
+ function _refresh(_opts) {
+ if (typeof(swf[0].refresh) === 'function') {
+ const opts = _opts || {};
+ if (!Room.getOptions().interview && !isNaN(opts.width)) {
+ _resizeDlg(opts.width, opts.height);
+ }
+ try {
+ swf[0].refresh(opts);
+ } catch (e) {
+ //swf might throw
+ }
+ }
+ }
+ function _setRights(_r) {
+ if (typeof(swf[0].setRights) === 'function') {
+ swf[0].setRights(_r);
+ }
+ }
+ function _cleanup() {
+ if (typeof(swf[0].cleanup) === 'function') {
+ swf[0].cleanup();
+ }
+ }
+
+ self.update = _update;
+ self.refresh = _refresh;
+ self.mute = _mute;
+ self.isMuted = function() { return 0 === slider.slider("option", "value"); };
+ self.init = _init;
+ self.securityMode = _securityMode;
+ self.client = function() { return c; };
+ self.setRights = _setRights;
+ self.cleanup = _cleanup;
+ return self;
+});
diff --git a/openmeetings-web/src/main/webapp/css/wb.css b/openmeetings-web/src/main/webapp/css/wb.css
index 2f80575..fff1226 100644
--- a/openmeetings-web/src/main/webapp/css/wb.css
+++ b/openmeetings-web/src/main/webapp/css/wb.css
@@ -291,3 +291,29 @@
.wb-area .wb-zoom button.up {
background-image: url(images/page_up.png);
}
+#quick-vote {
+ position: absolute;
+ right: 40px;
+ bottom: 40px;
+ padding: 5px;
+ background-color: aquamarine;
+}
+#quick-vote .control {
+ display: inline-block;
+ width: 40px;
+ height: 40px;
+ background-repeat: no-repeat;
+ background-size: 35px;
+ position: relative;
+}
+#quick-vote .control.pro {
+ background-image: url(images/add.png);
+}
+#quick-vote .control.con {
+ background-image: url(images/cancel.png);
+}
+#quick-vote .control .badge {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+}
--
To stop receiving notification emails like this one, please contact
solomax@apache.org.