Repository: openmeetings Updated Branches: refs/heads/master 568c47ac6 -> dd043e069
[OPENMEETINGS-1639] seems to be implemented Project: http://git-wip-us.apache.org/repos/asf/openmeetings/repo Commit: http://git-wip-us.apache.org/repos/asf/openmeetings/commit/dd043e06 Tree: http://git-wip-us.apache.org/repos/asf/openmeetings/tree/dd043e06 Diff: http://git-wip-us.apache.org/repos/asf/openmeetings/diff/dd043e06 Branch: refs/heads/master Commit: dd043e06962a4d3f57290f6bc6414b06b14ed1c2 Parents: 568c47a Author: Maxim Solodovnik <[email protected]> Authored: Sun Jul 2 23:03:34 2017 +0700 Committer: Maxim Solodovnik <[email protected]> Committed: Sun Jul 2 23:03:34 2017 +0700 ---------------------------------------------------------------------- .../openmeetings/db/dto/room/Whiteboard.java | 8 +- .../openmeetings/web/common/MainPanel.java | 10 +- .../openmeetings/web/room/wb/UndoObject.java | 61 +++++++++ .../openmeetings/web/room/wb/WbPanel.java | 129 ++++++++++++++++--- .../org/apache/openmeetings/web/room/wb/wb.js | 117 ++++++++++------- 5 files changed, 249 insertions(+), 76 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/openmeetings/blob/dd043e06/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/Whiteboard.java ---------------------------------------------------------------------- diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/Whiteboard.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/Whiteboard.java index be67d0e..393f608 100644 --- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/Whiteboard.java +++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dto/room/Whiteboard.java @@ -102,8 +102,8 @@ public class Whiteboard { return roomItems; } - public void put(String uid, JSONObject obj) { - roomItems.put(uid, obj); + public JSONObject put(String uid, JSONObject obj) { + return roomItems.put(uid, obj); } public JSONObject get(String uid) { @@ -114,8 +114,8 @@ public class Whiteboard { return roomItems.entrySet(); } - public void remove(Object oid) { - roomItems.remove(oid); + public JSONObject remove(Object oid) { + return roomItems.remove(oid); } public String getName() { http://git-wip-us.apache.org/repos/asf/openmeetings/blob/dd043e06/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/MainPanel.java ---------------------------------------------------------------------- diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/MainPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/MainPanel.java index 50afb36..edd8c9f 100644 --- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/MainPanel.java +++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/MainPanel.java @@ -102,11 +102,11 @@ public class MainPanel extends Panel { private static final WebMarkupContainer EMPTY = new WebMarkupContainer(CHILD_ID); public static final String PARAM_USER_ID = "userId"; private String uid = null; - private final MenuPanel menu; + private final MenuPanel menu = new MenuPanel("menu", getMainMenu()); private final WebMarkupContainer topControls = new WebMarkupContainer("topControls"); private final WebMarkupContainer topLinks = new WebMarkupContainer("topLinks"); - private final MarkupContainer contents; - private final ChatPanel chat; + private final MarkupContainer contents = new WebMarkupContainer("contents"); + private final ChatPanel chat = new ChatPanel("chatPanel"); private final MessageDialog newMessage; private final UserInfoDialog userInfo; private BasePanel panel; @@ -128,9 +128,7 @@ public class MainPanel extends Panel { super(id); this.panel = _panel; setOutputMarkupId(true); - menu = new MenuPanel("menu", getMainMenu()); - contents = new WebMarkupContainer("contents"); - add(chat = new ChatPanel("chatPanel")); + add(chat); add(newMessage = new MessageDialog("newMessageDialog", new CompoundPropertyModel<>(new PrivateMessage())) { private static final long serialVersionUID = 1L; http://git-wip-us.apache.org/repos/asf/openmeetings/blob/dd043e06/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/UndoObject.java ---------------------------------------------------------------------- diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/UndoObject.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/UndoObject.java new file mode 100644 index 0000000..08b9134 --- /dev/null +++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/UndoObject.java @@ -0,0 +1,61 @@ +/* + * 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.wb; + +import java.io.Serializable; + +import org.apache.openmeetings.util.NullStringer; + +import com.github.openjson.JSONArray; +import com.github.openjson.JSONObject; + +public class UndoObject implements Serializable { + private static final long serialVersionUID = 1L; + + public enum Type { + add + , remove + , modify + } + private final Type type; + private final String object; + + public UndoObject(Type type, JSONObject obj) { + this.type = type; + this.object = obj.toString(new NullStringer()); + } + + public UndoObject(Type type, JSONArray arr) { + this.type = type; + this.object = arr.toString(new NullStringer()); + } + + public Type getType() { + return type; + } + + public String getObject() { + return object; + } + + @Override + public String toString() { + return "UndoObject [type=" + type + "]"; + } +} http://git-wip-us.apache.org/repos/asf/openmeetings/blob/dd043e06/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java ---------------------------------------------------------------------- diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java index e70e410..3bd23a9 100644 --- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java +++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/WbPanel.java @@ -30,8 +30,13 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; import java.util.Map.Entry; import java.util.UUID; +import java.util.function.Function; import java.util.function.Predicate; import org.apache.openmeetings.core.data.whiteboard.WhiteboardCache; @@ -86,6 +91,7 @@ public class WbPanel extends Panel { private static final int UPLOAD_WB_TOP = 0; private static final int DEFAULT_WIDTH = 640; private static final int DEFAULT_HEIGHT = 480; + private static final int UNDO_SIZE = 20; public static final String FUNC_ACTION = "wbAction"; public static final String PARAM_ACTION = "action"; public static final String PARAM_OBJ = "obj"; @@ -106,7 +112,9 @@ public class WbPanel extends Panel { , clearSlide , save , load + , undo } + private final Map<Long, Deque<UndoObject>> undoList = new HashMap<>(); private final AbstractDefaultAjaxBehavior wbAction = new AbstractDefaultAjaxBehavior() { private static final long serialVersionUID = 1L; @@ -122,7 +130,8 @@ public class WbPanel extends Panel { StringValue sv = getRequest().getRequestParameters().getParameterValue(PARAM_OBJ); JSONObject obj = sv.isEmpty() ? new JSONObject() : new JSONObject(sv.toString()); if (Action.createObj == a || Action.modifyObj == a) { - if ("pointer".equals(obj.getJSONObject("obj").getString("type"))) { + JSONObject o = obj.optJSONObject("obj"); + if (o != null && "pointer".equals(o.getString("type"))) { sendWbOthers(a, obj); return; } @@ -181,31 +190,41 @@ public class WbPanel extends Panel { Whiteboard wb = getBean(WhiteboardCache.class).get(roomId).get(obj.getLong("wbId")); JSONObject o = obj.getJSONObject("obj"); wb.put(o.getString("uid"), o); + addUndo(wb.getId(), new UndoObject(UndoObject.Type.add, o)); sendWbOthers(Action.createObj, obj); } break; case modifyObj: { Whiteboard wb = getBean(WhiteboardCache.class).get(roomId).get(obj.getLong("wbId")); - JSONObject o = obj.getJSONObject("obj"); - JSONArray arr = o.optJSONArray("objects"); - if (arr == null) { - wb.put(o.getString("uid"), o); - } else { - for (int i = 0; i < arr.length(); ++i) { - JSONObject _o = arr.getJSONObject(i); - wb.put(_o.getString("uid"), _o); - } + JSONArray arr = obj.getJSONArray("obj"); + JSONArray undo = new JSONArray(); + for (int i = 0; i < arr.length(); ++i) { + JSONObject _o = arr.getJSONObject(i); + String uid = _o.getString("uid"); + undo.put(wb.get(uid)); + wb.put(uid, _o); + } + if (arr.length() != 0) { + addUndo(wb.getId(), new UndoObject(UndoObject.Type.modify, undo)); } sendWbOthers(Action.modifyObj, obj); } + break; case deleteObj: { Whiteboard wb = getBean(WhiteboardCache.class).get(roomId).get(obj.getLong("wbId")); JSONArray arr = obj.getJSONArray("obj"); + JSONArray undo = new JSONArray(); for (int i = 0; i < arr.length(); ++i) { JSONObject _o = arr.getJSONObject(i); - wb.remove(_o.getString("uid")); + JSONObject u = wb.remove(_o.getString("uid")); + if (u != null) { + undo.put(u); + } + } + if (undo.length() != 0) { + addUndo(wb.getId(), new UndoObject(UndoObject.Type.remove, undo)); } sendWbAll(Action.deleteObj, obj); } @@ -213,7 +232,17 @@ public class WbPanel extends Panel { case clearSlide: { Whiteboard wb = getBean(WhiteboardCache.class).get(roomId).get(obj.getLong("wbId")); - wb.entrySet().removeIf(e -> e.getValue().optInt("slide", -1) == obj.getInt("slide")); + JSONArray arr = new JSONArray(); + wb.entrySet().removeIf(e -> { + boolean match = e.getValue().optInt("slide", -1) == obj.getInt("slide"); + if (match) { + arr.put(e); + } + return match; + }); + if (arr.length() != 0) { + addUndo(wb.getId(), new UndoObject(UndoObject.Type.remove, arr)); + } sendWbAll(Action.clearSlide, obj); } break; @@ -221,6 +250,25 @@ public class WbPanel extends Panel { wb2save = obj.getLong("wbId"); fileName.open(target); break; + case undo: + { + Long wbId = obj.getLong("wbId"); + UndoObject uo = getUndo(wbId); + if (uo != null) { + switch (uo.getType()) { + case add: + sendWbAll(Action.deleteObj, obj.put("obj", new JSONArray().put(new JSONObject(uo.getObject())))); + break; + case remove: + sendWbAll(Action.createObj, obj.put("obj", new JSONArray(uo.getObject()))); + break; + case modify: + sendWbAll(Action.modifyObj, obj.put("obj", new JSONArray(uo.getObject()))); + break; + } + } + } + break; default: break; } @@ -367,8 +415,9 @@ public class WbPanel extends Panel { } return _file; } + private JSONObject addFileUrl(String ruid, JSONObject _file, FileItem fi, Client c) { - JSONObject file = new JSONObject(_file, JSONObject.getNames(_file)); //FIXME TODO openjson 1.0.2 + JSONObject file = new JSONObject(_file.toString(new NullStringer())); final FileSystemResourceReference ref; final PageParameters pp = new PageParameters() .add("id", fi.getId()).add("uid", c.getUid()) @@ -397,7 +446,24 @@ public class WbPanel extends Panel { return file; } + private static JSONArray getArray(JSONObject wb, Function<JSONObject, JSONObject> postprocess) { + JSONObject items = wb.getJSONObject("roomItems"); + JSONArray arr = new JSONArray(); + for (String uid : items.keySet()) { + JSONObject o = items.getJSONObject(uid); + if (postprocess != null) { + o = postprocess.apply(o); + } + arr.put(o); + } + return arr; + } + private void clearAll(Whiteboard wb) { + JSONArray arr = getArray(wb.toJson(), null); + if (arr.length() != 0) { + addUndo(wb.getId(), new UndoObject(UndoObject.Type.remove, arr)); + } wb.clear(); sendWbAll(Action.clearAll, new JSONObject().put("wbId", wb.getId())); } @@ -416,12 +482,7 @@ public class WbPanel extends Panel { File f = fi.getFile(); if (f.exists() && f.isFile()) { try (BufferedReader br = Files.newBufferedReader(f.toPath())) { - JSONObject items = new JSONObject(new JSONTokener(br)).getJSONObject("roomItems"); - JSONArray arr = new JSONArray(); - for (String uid : items.keySet()) { - JSONObject o = items.getJSONObject(uid); - arr.put(addFileUrl(wbs.getUid(), o)); - } + JSONArray arr = getArray(new JSONObject(new JSONTokener(br)), (o) -> addFileUrl(wbs.getUid(), o)); sendWbAll(Action.load, getObjWbJson(wb.getId(), arr)); } catch (Exception e) { log.error("Unexpected error while loading WB", e); @@ -466,4 +527,34 @@ public class WbPanel extends Panel { } } } + + private void addUndo(Long wbId, UndoObject u) { + if (wbId == null) { + return; + } + if (!undoList.containsKey(wbId)) { + undoList.put(wbId, new LimitedLinkedList<>()); + } + undoList.get(wbId).push(u); + } + + private UndoObject getUndo(Long wbId) { + if (wbId == null || !undoList.containsKey(wbId)) { + return null; + } + Deque<UndoObject> deq = undoList.get(wbId); + return deq.isEmpty() ? null : deq.pop(); + } + + private static class LimitedLinkedList<T> extends LinkedList<T> { + private static final long serialVersionUID = 1L; + + @Override + public void push(T e) { + super.push(e); + while (size() > UNDO_SIZE) { + removeLast(); + } + } + } } http://git-wip-us.apache.org/repos/asf/openmeetings/blob/dd043e06/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb.js ---------------------------------------------------------------------- diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb.js index 6c8a7be..bf0074d 100644 --- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb.js +++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb.js @@ -513,7 +513,7 @@ var Wb = function() { const ACTIVE = 'active'; const BUMPER = 100; var wb = {id: -1}, a, t, s, canvases = [], mode, slide = 0, width = 0, height = 0 - , minWidth = 0, minHeight = 0, role = null; + , minWidth = 0, minHeight = 0, role = null, extraProps = ['uid', 'fileId', 'fileType', 'count', 'slide']; function getBtn(m) { return !!t ? t.find(".om-icon." + (m || mode)) : null; @@ -625,6 +625,9 @@ var Wb = function() { t.find('.om-icon.save').click(function() { wbAction('save', JSON.stringify({wbId: wb.id})); }); + t.find('.om-icon.undo').click(function() { + wbAction('undo', JSON.stringify({wbId: wb.id})); + }); s.find('.wb-prop-b, .wb-prop-i') .button() .click(function() { @@ -721,14 +724,16 @@ var Wb = function() { function _removeHandler(o) { var __o = _findObject(o); if (!!__o) { - canvases[o.slide].remove(__o); + var cnvs = canvases[o.slide]; + if (!!cnvs) { + cnvs.discardActiveGroup(); + cnvs.remove(__o); + } } } function _modifyHandler(_o) { _removeHandler(_o); - var canvas = canvases[_o.slide]; - _o.selectable = canvas.selection; - canvas.add(_o); + _createHandler(_o); } function _createHandler(_o) { switch (_o.fileType) { @@ -803,7 +808,7 @@ var Wb = function() { }; function toOmJson(o) { - return o.toJSON(['uid', 'fileId', 'fileType', 'count', 'slide']); + return o.toJSON(extraProps); } //events function wbObjCreatedHandler(o) { @@ -843,9 +848,22 @@ var Wb = function() { if (role === NONE && o.type != 'pointer') return; o.includeDefaultValues = false; + var items = []; + if ("group" === o.type) { + o.clone(function(_o) { + // ungrouping + _o.includeDefaultValues = false; + var _items = _o.destroy().getObjects(); + for (var i = 0; i < _items.length; ++i) { + items.push(toOmJson(_items[i])); + } + }, extraProps); + } else { + items.push(toOmJson(o)); + } wbAction('modifyObj', JSON.stringify({ wbId: wb.id - , obj: toOmJson(o) + , obj: items })); }; function objSelectedHandler(e) { @@ -898,19 +916,21 @@ var Wb = function() { console.log('Text Changed', obj); };*/ function setHandlers(canvas) { + // off everything first to prevent duplicates + canvas.off({ + 'wb:object:created': wbObjCreatedHandler + , 'object:modified': objModifiedHandler + , 'object:added': objAddedHandler + , 'object:selected': objSelectedHandler + , 'path:created': pathCreatedHandler + //, 'text:editing:exited': textEditedHandler + //, 'text:changed': textChangedHandler + }); canvas.on({ 'wb:object:created': wbObjCreatedHandler , 'object:modified': objModifiedHandler }); - if (role === NONE) { - canvas.off({ - 'object:added': objAddedHandler - , 'object:selected': objSelectedHandler - , 'path:created': pathCreatedHandler - //, 'text:editing:exited': textEditedHandler - //, 'text:changed': textChangedHandler - }); - } else { + if (role !== NONE) { canvas.on({ 'object:added': objAddedHandler , 'object:selected': objSelectedHandler @@ -1002,26 +1022,38 @@ var Wb = function() { } }; wb.createObj = function(o) { - switch(o.type) { - case 'pointer': + var arr = []; + if (!Array.isArray(o)) { + if ('pointer' === o.type) { APointer().create(canvases[o.slide], o); - break; - default: - var __o = _findObject(o); - if (!__o) { - _createObject([o], _createHandler); - } - /* - * https://jsfiddle.net/l2aelba/kro7h6rv/2/ - if ('Video' === o.fileType || 'Recording' === o.fileType) { - fabric.util.requestAnimFrame(function render() { - canvas.renderAll(); - fabric.util.requestAnimFrame(render); - }); - } - */ - break; + return; + } + switch(o.type) { + case 'pointer': + APointer().create(canvases[o.slide], o); + break; + default: + var __o = _findObject(o); + if (!__o) { + arr.push(o); + } + break; + } + } else { + arr = o; + } + if (arr.length > 0) { + _createObject(arr, _createHandler); + } + /* FIXME TODO animation + * https://jsfiddle.net/l2aelba/kro7h6rv/2/ + if ('Video' === o.fileType || 'Recording' === o.fileType) { + fabric.util.requestAnimFrame(function render() { + canvas.renderAll(); + fabric.util.requestAnimFrame(render); + }); } + */ }; wb.modifyObj = function(o) { //TODO need to be unified switch(o.type) { @@ -1029,16 +1061,7 @@ var Wb = function() { _modifyHandler(APointer().create(canvases[o.slide], o)) break; default: - var arr = [o]; - if (!!o.objects) { - arr = o.objects; - for (var i = 0; i < arr.length; ++i) { - var _o = arr[i]; - _o.left += o.left; - _o.top += o.top; - } - } - _createObject(o.objects || [o], _modifyHandler); + _createObject(o, _modifyHandler); break; } }; @@ -1104,8 +1127,8 @@ var WbArea = (function() { var canvas = wb.getCanvas(); if (!!canvas) { var arr = []; - if (canvas.getActiveGroup()) { - canvas.getActiveGroup().forEachObject(function(o){ + if (!!canvas.getActiveGroup()) { + canvas.getActiveGroup().forEachObject(function(o) { arr.push({ uid: o.uid , slide: o.slide @@ -1309,7 +1332,7 @@ $(function() { } } } catch (err) { - //console.log(err); + console.log(err); //no-op } });
