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 2017/10/13 11:21:41 UTC

openmeetings git commit: [OPENMEETINGS-1720] import converts WB files

Repository: openmeetings
Updated Branches:
  refs/heads/master 4225fdb5b -> 5e2dffb96


[OPENMEETINGS-1720] import converts WB files


Project: http://git-wip-us.apache.org/repos/asf/openmeetings/repo
Commit: http://git-wip-us.apache.org/repos/asf/openmeetings/commit/5e2dffb9
Tree: http://git-wip-us.apache.org/repos/asf/openmeetings/tree/5e2dffb9
Diff: http://git-wip-us.apache.org/repos/asf/openmeetings/diff/5e2dffb9

Branch: refs/heads/master
Commit: 5e2dffb96a96c288efdb8888a04d689f78ebfb13
Parents: 4225fdb
Author: Maxim Solodovnik <so...@gmail.com>
Authored: Fri Oct 13 18:21:32 2017 +0700
Committer: Maxim Solodovnik <so...@gmail.com>
Committed: Fri Oct 13 18:21:32 2017 +0700

----------------------------------------------------------------------
 .../openmeetings/db/dto/room/Whiteboard.java    |  37 ++-
 .../db/entity/file/BaseFileItem.java            |   6 +
 .../openmeetings/backup/BackupImport.java       |  31 +-
 .../backup/converter/WbConverter.java           | 295 +++++++++++++++++++
 .../openmeetings/web/room/wb/WbPanel.java       |  10 +-
 .../openmeetings/web/room/wb/tool-clipart.js    |   5 +-
 .../openmeetings/web/room/wb/tool-text.js       |   5 +-
 .../apache/openmeetings/web/room/wb/wb-board.js |   2 +-
 8 files changed, 365 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/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 c351e9a..6b375c2 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
@@ -18,7 +18,13 @@
  */
 package org.apache.openmeetings.db.dto.room;
 
+import static org.apache.openmeetings.util.OpenmeetingsVariables.getWebAppRootKey;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
 import java.io.Serializable;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Date;
 import java.util.LinkedHashMap;
@@ -28,12 +34,15 @@ import java.util.Map;
 import java.util.Map.Entry;
 
 import org.apache.openmeetings.util.NullStringer;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
 
 import com.github.openjson.JSONArray;
 import com.github.openjson.JSONObject;
 
 public class Whiteboard implements Serializable {
 	private static final long serialVersionUID = 1L;
+	private static final Logger log = Red5LoggerFactory.getLogger(Whiteboard.class, getWebAppRootKey());
 	public enum ZoomMode {
 		fullFit
 		, pageWidth
@@ -177,15 +186,29 @@ public class Whiteboard implements Serializable {
 		//deep-copy
 		JSONObject json = new JSONObject(new JSONObject(this).toString(new NullStringer()));
 		json.remove("id"); //filtering
-		if (!json.has(ITEMS_KEY)) {
-			json.put(ITEMS_KEY, new JSONObject());
-		}
-		JSONObject items = json.getJSONObject(ITEMS_KEY);
-		for (String uid : items.keySet()) {
-			JSONObject o = items.getJSONObject(uid);
+		json.remove("empty"); //filtering
+		JSONObject items = new JSONObject();
+		for (Entry<String, String> e : roomItems.entrySet()) {
+			JSONObject o = new JSONObject(e.getValue());
+			//filtering
+			o.remove("src");
+			if ("Clipart".equals(o.opt("omType"))) {
+				o.put("src", o.get("_src"));
+			}
 			o.remove("_src");
-			o.remove("src"); //filtering
+			items.put(e.getKey(), o);
 		}
+		json.put(ITEMS_KEY, items);
 		return json;
 	}
+
+	public String save(Path path) {
+		try (BufferedWriter writer = Files.newBufferedWriter(path)) {
+			writer.write(toJson().toString(new NullStringer(2)));
+		} catch (IOException e) {
+			log.error("Unexpected error while saving WB", e);
+			return e.getMessage();
+		}
+		return null;
+	}
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java
----------------------------------------------------------------------
diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java
index 20b4a80..2871a67 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/entity/file/BaseFileItem.java
@@ -377,4 +377,10 @@ public abstract class BaseFileItem extends HistoricalEntity {
 			return n.startsWith(getHash()) && !exclusions.contains(ext);
 		}
 	}
+
+	@Override
+	public String toString() {
+		return "FileItem [id=" + id + ", name=" + name + ", room=" + roomId + ", type=" + type.name() + "]";
+	}
+
 }

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java
----------------------------------------------------------------------
diff --git a/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java
index 53b239e..4efb183 100644
--- a/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java
+++ b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/BackupImport.java
@@ -138,6 +138,7 @@ import org.apache.openmeetings.backup.converter.RoomConverter;
 import org.apache.openmeetings.backup.converter.RoomTypeConverter;
 import org.apache.openmeetings.backup.converter.SalutationConverter;
 import org.apache.openmeetings.backup.converter.UserConverter;
+import org.apache.openmeetings.backup.converter.WbConverter;
 import org.apache.openmeetings.core.converter.DocumentConverter;
 import org.apache.openmeetings.db.dao.basic.ChatDao;
 import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
@@ -155,6 +156,7 @@ import org.apache.openmeetings.db.dao.user.PrivateMessageDao;
 import org.apache.openmeetings.db.dao.user.PrivateMessageFolderDao;
 import org.apache.openmeetings.db.dao.user.UserContactDao;
 import org.apache.openmeetings.db.dao.user.UserDao;
+import org.apache.openmeetings.db.dto.room.Whiteboard;
 import org.apache.openmeetings.db.entity.basic.ChatMessage;
 import org.apache.openmeetings.db.entity.basic.Configuration;
 import org.apache.openmeetings.db.entity.calendar.Appointment;
@@ -403,7 +405,7 @@ public class BackupImport {
 		importPrivateMsgFolders(f, simpleSerializer);
 		importContacts(f);
 		importPrivateMsgs(f);
-		importFiles(f, ver.compareTo(BackupVersion.get("4.0.0")) < 0);
+		List<FileItem> files = importFiles(f, ver.compareTo(BackupVersion.get("4.0.0")) < 0);
 		importPolls(f);
 		importRoomFiles(f);
 
@@ -414,9 +416,17 @@ public class BackupImport {
 		importFolders(f);
 
 		if (ver.compareTo(BackupVersion.get("4.0.0")) < 0) {
-			for (BaseFileItem bfi : fileItemDao.get()) {
+			for (BaseFileItem bfi : files) {
 				if (BaseFileItem.Type.Presentation == bfi.getType()) {
-					convertOldPresentation(bfi);
+					convertOldPresentation((FileItem)bfi);
+				}
+				if (BaseFileItem.Type.WmlFile == bfi.getType()) {
+					try {
+						Whiteboard wb = WbConverter.convert((FileItem)bfi);
+						wb.save(bfi.getFile().toPath());
+					} catch (Exception e) {
+						log.error("Unexpected error while converting WB", e);
+					}
 				}
 			}
 		}
@@ -847,8 +857,9 @@ public class BackupImport {
 	/*
 	 * ##################### Import File-Explorer Items
 	 */
-	private void importFiles(File f, boolean old) throws Exception {
+	private List<FileItem> importFiles(File f, boolean old) throws Exception {
 		log.info("Private message import complete, starting file explorer item import");
+		List<FileItem> result = new ArrayList<>();
 		List<FileItem> list = readFileItemList(f, "fileExplorerItems.xml", "fileExplorerItems");
 		for (FileItem file : list) {
 			Long fId = file.getId();
@@ -866,8 +877,10 @@ public class BackupImport {
 				file.setHash(UUID.randomUUID().toString());
 			}
 			file = fileItemDao.update(file);
+			result.add(file);
 			fileItemMap.put(fId, file.getId());
 		}
+		return result;
 	}
 
 	/*
@@ -1012,7 +1025,7 @@ public class BackupImport {
 								f.setType(BaseFileItem.Type.WmlFile);
 								f.setHash(val);
 							}
-							if ("isChart".equals(name) && "true".equals(val)) {
+							if (f.getType() == null && "isChart".equals(name) && "true".equals(val)) {
 								f.setType(BaseFileItem.Type.PollChart);
 							}
 							if ("isImage".equals(name) && "true".equals(val)) {
@@ -1501,12 +1514,12 @@ public class BackupImport {
 		return countries.getProperty(String.format("country.%s", countryId));
 	}
 
-	private void convertOldPresentation(BaseFileItem bfi) {
-		File f = bfi.getOriginal();
+	private void convertOldPresentation(FileItem fi) {
+		File f = fi.getOriginal();
 		if (f != null && f.exists()) {
 			try {
-				StoredFile sf = new StoredFile(bfi.getHash(), getFileExt(f.getName()), f);
-				docConverter.convertPDF((FileItem)bfi, sf);
+				StoredFile sf = new StoredFile(fi.getHash(), getFileExt(f.getName()), f);
+				docConverter.convertPDF(fi, sf);
 			} catch (Exception e) {
 				log.error("Unexpected exception while converting OLD format presentations", e);
 			}

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/openmeetings-install/src/main/java/org/apache/openmeetings/backup/converter/WbConverter.java
----------------------------------------------------------------------
diff --git a/openmeetings-install/src/main/java/org/apache/openmeetings/backup/converter/WbConverter.java b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/converter/WbConverter.java
new file mode 100644
index 0000000..67a4f48
--- /dev/null
+++ b/openmeetings-install/src/main/java/org/apache/openmeetings/backup/converter/WbConverter.java
@@ -0,0 +1,295 @@
+/*
+ * 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.backup.converter;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.lang3.math.NumberUtils.toLong;
+import static org.apache.openmeetings.util.OmFileHelper.EXTENSION_WML;
+import static org.apache.openmeetings.util.OpenmeetingsVariables.getWebAppRootKey;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.openmeetings.db.dto.room.Whiteboard;
+import org.apache.openmeetings.db.entity.file.BaseFileItem;
+import org.apache.openmeetings.db.entity.file.FileItem;
+import org.apache.openmeetings.util.OmFileHelper;
+import org.red5.logging.Red5LoggerFactory;
+import org.slf4j.Logger;
+
+import com.github.openjson.JSONArray;
+import com.github.openjson.JSONObject;
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.XppDriver;
+
+public class WbConverter {
+	private static final Logger log = Red5LoggerFactory.getLogger(WbConverter.class, getWebAppRootKey());
+
+	private static String getColor(int val) {
+		return String.format("#%06X", (0xFFFFFF & val));
+	}
+
+	private static void add(Whiteboard wb, JSONObject o) {
+		String uid = UUID.randomUUID().toString();
+		wb.put(uid, o.put("uid", uid));
+	}
+
+	private static JSONObject init(Whiteboard wb, List<?> props) {
+		return init(wb, props, true);
+	}
+
+	private static JSONObject init(Whiteboard wb, List<?> props, boolean addDim) {
+		double left = ((Number)props.get(props.size() - 5)).doubleValue();
+		double top = ((Number)props.get(props.size() - 4)).doubleValue();
+		double w = ((Number)props.get(props.size() - 3)).doubleValue();
+		double h = ((Number)props.get(props.size() - 2)).doubleValue();
+		JSONObject o = new JSONObject().put("slide", 0);
+		if (addDim) {
+			o.put("left", left)
+				.put("top", top)
+				.put("width", w)
+				.put("height", h);
+			wb.setWidth((int)Math.max(wb.getWidth(), w + left));
+			wb.setHeight((int)Math.max(wb.getHeight(), h + top));
+		}
+		return o;
+	}
+
+	private static JSONObject setColor(JSONObject o, String stroke, String fill) {
+		return o.put("stroke", stroke).put("fill", fill);
+	}
+
+	private static void processText(Whiteboard wb, List<?> props) {
+		if (props.size() < 12) {
+			return;
+		}
+		String color = getColor((int)props.get(2));
+		String style = (String)props.get(4);
+		JSONObject o = setColor(init(wb, props), color, color)
+				.put("type", "i-text")
+				.put("text", props.get(1))
+				.put("fontSize", props.get(3));
+		if (style.indexOf("bold") > -1) {
+			o.put("fontWeight", "bold");
+		}
+		if (style.indexOf("italic") > -1) {
+			o.put("fontStyle", "inalic");
+		}
+		add(wb, o);
+	}
+
+	private static void processPath(Whiteboard wb, List<?> props) {
+		if (props.size() < 13) {
+			return;
+		}
+		String color = getColor((int)props.get(4));
+		JSONObject o = setColor(init(wb, props), color, null)
+				.put("type", "path")
+				.put("strokeWidth", props.get(3));
+		@SuppressWarnings("unchecked")
+		List<List<?>> points = (List<List<?>>)props.get(1);
+		JSONArray path = new JSONArray();
+		for (List<?> point : points) {
+			if (path.length() == 0) {
+				path.put(new JSONArray(Arrays.asList("M", (int)point.get(1), (int)point.get(2))));
+			} else if (path.length() == points.size() - 1) {
+				path.put(new JSONArray(Arrays.asList("L", (int)point.get(3), (int)point.get(4))));
+			} else {
+				path.put(new JSONArray(Arrays.asList("Q"
+						, (int)point.get(1), (int)point.get(2)
+						, (int)point.get(3), (int)point.get(4))));
+			}
+		}
+		add(wb, o.put("path", path).put("opacity", props.get(5)));
+	}
+
+	private static void processLine(Whiteboard wb, List<?> props) {
+		if (props.size() < 16) {
+			return;
+		}
+		String color = getColor((int)props.get(1));
+		add(wb, setColor(init(wb, props), color, color)
+				.put("type", "line")
+				.put("strokeWidth", props.get(2))
+				.put("opacity", props.get(3))
+				.put("x1", props.get(4))
+				.put("y1", props.get(5))
+				.put("x2", props.get(6))
+				.put("y2", props.get(7)));
+	}
+
+	private static JSONObject processRect(Whiteboard wb, List<?> props) {
+		if (props.size() < 15) {
+			return null;
+		}
+		return setColor(init(wb, props)
+					, 1 == (int)props.get(4) ? getColor((int)props.get(1)) : null
+					, 1 == (int)props.get(5) ? getColor((int)props.get(3)) : null)
+				.put("type", "rect")
+				.put("strokeWidth", props.get(2))
+				.put("opacity", props.get(6));
+	}
+
+	private static void processEllipse(Whiteboard wb, List<?> props) {
+		JSONObject o = processRect(wb, props);
+		if (o != null) {
+			o.put("type", "ellipse")
+				.put("rx", o.getDouble("width") / 2)
+				.put("ry", o.getDouble("height") / 2);
+			add(wb, o);
+		}
+	}
+
+	private static void processClipart(Whiteboard wb, List<?> props) {
+		if (props.size() < 19) {
+			return;
+		}
+		String src = (String)props.get(2);
+		int idx = src.indexOf("cliparts");
+		if (idx > -1) {
+			src = String.format("./public/%s", src.substring(idx));
+		}
+		add(wb, init(wb, props)
+			.put("type", "image")
+			.put("omType", "Clipart")
+			.put("_src", src)
+			.put("angle", props.get(3)));
+	}
+
+	private static long getFileId(String src) {
+		int idx1 = src.lastIndexOf('/'), idx2 = src.indexOf('?');
+		if (idx1 < 0 || idx2 < 0) {
+			return -1;
+		}
+		return toLong(src.substring(idx1 + 1, idx2), -1);
+	}
+
+	// will support only import from 3.2.x+
+	private static void processImage(Whiteboard wb, List<?> props) {
+		if (props.size() < 17) {
+			return;
+		}
+		long fileId = getFileId((String)props.get(2));
+		if (fileId < 0) {
+			return;
+		}
+		add(wb, init(wb, props)
+			.put("type", "image")
+			.put("fileType", BaseFileItem.Type.Image.name())
+			.put("fileId", fileId));
+	}
+
+	private static void processDoc(Whiteboard wb, List<?> props) {
+		if (props.size() < 27) {
+			return;
+		}
+		long fileId = getFileId((String)props.get(2));
+		if (fileId < 0) {
+			return;
+		}
+		add(wb, init(wb, props, false)
+			.put("type", "image")
+			.put("fileType", BaseFileItem.Type.Presentation.name())
+			.put("fileId", fileId));
+	}
+
+	private static void processVid(Whiteboard wb, List<?> props) {
+		if (props.size() < 14) {
+			return;
+		}
+		add(wb, init(wb, props)
+			.put("type", "image")
+			.put("fileType", BaseFileItem.Type.Video.name())
+			.put("fileId", props.get(1)));
+	}
+
+	public static Whiteboard convert(FileItem fi) {
+		Whiteboard wb = new Whiteboard();
+		wb.setWidth(0);
+		wb.setHeight(0);
+		Set<String> uids = new HashSet<>();
+		List<?> wml = loadWmlFile(fi.getHash());
+		for (Object wo : wml) {
+			List<?> props = (List<?>)wo;
+			if (props.size() > 0) {
+				String uid = (String)props.get(props.size() - 1);
+				if (uids.contains(uid)) {
+					continue;
+				}
+				uids.add(uid);
+				switch ((String)props.get(0)) {
+					case "letter":
+						processText(wb, props);
+						break;
+					case "paint":
+						processPath(wb, props);
+						break;
+					case "line":
+					case "uline":
+						processLine(wb, props);
+						break;
+					case "rectangle":
+					case "drawarrow": // will replace with rectangle
+						add(wb, processRect(wb, props));
+						break;
+					case "ellipse":
+						processEllipse(wb, props);
+						break;
+					case "clipart":
+						processClipart(wb, props);
+						break;
+					case "image":
+						processImage(wb, props);
+						break;
+					case "swf":
+						processDoc(wb, props);
+						break;
+					case "flv":
+						processVid(wb, props);
+						break;
+				}
+			}
+		}
+		return wb;
+	}
+
+	public static List<?> loadWmlFile(String hash) {
+		String name = OmFileHelper.getName(hash, EXTENSION_WML);
+		File file = new File(OmFileHelper.getUploadWmlDir(), name);
+		log.debug("filepathComplete: {}", file);
+
+		XStream xStream = new XStream(new XppDriver());
+		xStream.setMode(XStream.NO_REFERENCES);
+		try (InputStream is = new FileInputStream(file); BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) {
+			return (List<?>) xStream.fromXML(reader);
+		} catch (Exception err) {
+			log.error("loadWmlFile", err);
+		}
+		return new ArrayList<>();
+	}
+}

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/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 cc9f020..f2f7d80 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
@@ -28,7 +28,6 @@ import static org.apache.wicket.AttributeModifier.append;
 
 import java.awt.image.BufferedImage;
 import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -50,6 +49,7 @@ import java.util.function.Function;
 import javax.imageio.ImageIO;
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.directory.api.util.Strings;
 import org.apache.openmeetings.core.data.whiteboard.WhiteboardCache;
 import org.apache.openmeetings.db.dao.file.FileItemDao;
 import org.apache.openmeetings.db.dao.record.RecordingDao;
@@ -116,12 +116,10 @@ public class WbPanel extends AbstractWbPanel {
 			f.setHash(UUID.randomUUID().toString());
 			f.setName(getModelObject());
 			f = getBean(FileItemDao.class).update(f);
-			try (BufferedWriter writer = Files.newBufferedWriter(f.getFile().toPath())) {
-				writer.write(wb.toJson().toString(new NullStringer(2)));
-			} catch (IOException e) {
-				error("Unexpected error while saving WB: " + e.getMessage());
+			String res = wb.save(f.getFile().toPath());
+			if (!Strings.isEmpty(res)) {
+				error("Unexpected error while saving WB: " + res);
 				target.add(feedback);
-				log.error("Unexpected error while saving WB", e);
 			}
 		}
 

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-clipart.js
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-clipart.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-clipart.js
index c5d5533..dd3d189 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-clipart.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-clipart.js
@@ -3,7 +3,8 @@ var Clipart = function(wb, btn) {
 	const art = Shape(wb);
 	art.add2Canvas = function(canvas) {}
 	art.createShape = function(canvas) {
-		fabric.Image.fromURL(btn.data('image'), function(img) {
+		const imgSrc = btn.data('image');
+		fabric.Image.fromURL(imgSrc, function(img) {
 			art.orig.width = img.width;
 			art.orig.height = img.height;
 			art.obj = img.set({
@@ -11,6 +12,8 @@ var Clipart = function(wb, btn) {
 				, top: art.orig.y
 				, width: 0
 				, height: 0
+				, omType: 'Clipart'
+				, _src: imgSrc
 			});
 			canvas.add(art.obj);
 		});

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-text.js
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-text.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-text.js
index a81126b..703ecc3 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-text.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/tool-text.js
@@ -3,7 +3,7 @@ var Text = function(wb, s) {
 	const text = ShapeBase();
 	text.obj = null;
 	text.fill.color = '#000000';
-	text.stroke.width = 1;
+	text.stroke.width = 12; //fontSize
 	text.stroke.color = '#000000';
 	text.style = {bold: false, italic: false};
 	//TODO font size, background color
@@ -21,7 +21,8 @@ var Text = function(wb, s) {
 				, padding: 7
 				, fill: text.fill.enabled ? text.fill.color : 'rgba(0,0,0,0)'
 				, stroke: text.stroke.enabled ? text.stroke.color : 'rgba(0,0,0,0)'
-				, strokeWidth: text.stroke.width
+				//, strokeWidth: text.stroke.width
+				, fontSize: text.stroke.width
 				, opacity: text.opacity
 			});
 			if (text.style.bold) {

http://git-wip-us.apache.org/repos/asf/openmeetings/blob/5e2dffb9/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb-board.js
----------------------------------------------------------------------
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb-board.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb-board.js
index 1f3b0c1..c827f11 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb-board.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/wb/wb-board.js
@@ -2,7 +2,7 @@
 var Wb = function() {
 	const ACTIVE = 'active', BUMPER = 100
 		, wb = {id: -1, name: ''}, canvases = []
-		, extraProps = ['uid', 'fileId', 'fileType', 'count', 'slide'];
+		, extraProps = ['uid', 'fileId', 'fileType', 'count', 'slide', 'omType', '_src'];
 	let a, t, z, s, mode, slide = 0, width = 0, height = 0
 			, zoom = 1., zoomMode = 'fullFit', role = null;