diff --git a/CHANGELOG.md b/CHANGELOG.md index f2196c1..20f9ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm [#19600] revisit the "Get Info" Dialog in a modern view +#### New Features + +[#19695] Show the file preview via Google Docs Viewer + ## [v6.30.1] [r4.24.0] - 2020-06-25 diff --git a/src/main/java/org/gcube/portlets/user/workspace/client/resources/Icons.java b/src/main/java/org/gcube/portlets/user/workspace/client/resources/Icons.java index de6ec65..7e211ca 100644 --- a/src/main/java/org/gcube/portlets/user/workspace/client/resources/Icons.java +++ b/src/main/java/org/gcube/portlets/user/workspace/client/resources/Icons.java @@ -417,8 +417,9 @@ public interface Icons extends ClientBundle { @Source("icons/sync-icon-synched.png") ImageResource syncIconSynched(); - - + + @Source("icons/preview-not-available.png") + ImageResource previewNotAvailable(); } diff --git a/src/main/java/org/gcube/portlets/user/workspace/client/resources/Resources.java b/src/main/java/org/gcube/portlets/user/workspace/client/resources/Resources.java index 930e967..b506b1d 100644 --- a/src/main/java/org/gcube/portlets/user/workspace/client/resources/Resources.java +++ b/src/main/java/org/gcube/portlets/user/workspace/client/resources/Resources.java @@ -7,6 +7,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.ui.AbstractImagePrototype; +// TODO: Auto-generated Javadoc /** * The Class Resources. * @@ -1248,15 +1249,30 @@ public class Resources { } + /** + * Gets the icon sync to. + * + * @return the icon sync to + */ public static AbstractImagePrototype getIconSyncTo() { return AbstractImagePrototype.create(ICONS.syncIconTo()); } + /** + * Gets the icon sync from. + * + * @return the icon sync from + */ public static AbstractImagePrototype getIconSyncFrom() { return AbstractImagePrototype.create(ICONS.syncIconFrom()); } + /** + * Gets the icon synched. + * + * @return the icon synched + */ public static AbstractImagePrototype getIconSynched() { return AbstractImagePrototype.create(ICONS.syncIconSynched()); } @@ -1344,6 +1360,17 @@ public class Resources { public static ImageResource getImageAttachs() { return ICONS.attach(); } + + + /** + * Gets the preview not available. + * + * @return the preview not available + */ + public static ImageResource getPreviewNotAvailable() { + return ICONS.previewNotAvailable(); + } + /** * Gets the icon by media type name. @@ -1503,10 +1530,4 @@ public class Resources { return null; } - - - - - - } diff --git a/src/main/java/org/gcube/portlets/user/workspace/client/resources/icons/preview-not-available.png b/src/main/java/org/gcube/portlets/user/workspace/client/resources/icons/preview-not-available.png new file mode 100644 index 0000000..fe85c9e Binary files /dev/null and b/src/main/java/org/gcube/portlets/user/workspace/client/resources/icons/preview-not-available.png differ diff --git a/src/main/java/org/gcube/portlets/user/workspace/client/view/windows/DialogGetInfoBootstrap.java b/src/main/java/org/gcube/portlets/user/workspace/client/view/windows/DialogGetInfoBootstrap.java index f5a254d..ff4926d 100644 --- a/src/main/java/org/gcube/portlets/user/workspace/client/view/windows/DialogGetInfoBootstrap.java +++ b/src/main/java/org/gcube/portlets/user/workspace/client/view/windows/DialogGetInfoBootstrap.java @@ -12,6 +12,8 @@ import org.gcube.portlets.user.workspace.client.event.FileDownloadEvent.Download import org.gcube.portlets.user.workspace.client.interfaces.GXTFolderItemTypeEnum; import org.gcube.portlets.user.workspace.client.model.FileGridModel; import org.gcube.portlets.user.workspace.client.model.FileModel; +import org.gcube.portlets.user.workspace.client.resources.Icons; +import org.gcube.portlets.user.workspace.client.resources.Resources; import org.gcube.portlets.user.workspace.client.workspace.GWTWorkspaceItem; import org.gcube.portlets.user.workspace.client.workspace.folder.item.GWTExternalImage; import org.gcube.portlets.user.workspace.client.workspace.folder.item.gcube.GWTImageDocument; @@ -29,21 +31,26 @@ import com.github.gwtbootstrap.client.ui.constants.LabelType; import com.github.gwtbootstrap.client.ui.constants.ResizeType; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Float; +import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.LoadEvent; +import com.google.gwt.event.dom.client.LoadHandler; import com.google.gwt.http.client.URL; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.NumberFormat; -import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Frame; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.NamedFrame; import com.google.gwt.user.client.ui.Widget; // TODO: Auto-generated Javadoc @@ -177,6 +184,10 @@ public class DialogGetInfoBootstrap extends Composite { // private Button buttonShareableLink = new Button(); private DateTimeFormat dateFormatter = DateTimeFormat.getFormat("dd MMM yyyy, hh:mm aaa"); + + private boolean iFrameGoogleDocViewerLoaded = false; + + private Long fileSize = null; /** * Instantiates a new dialog get info bootstrap. @@ -297,6 +308,7 @@ public class DialogGetInfoBootstrap extends Composite { //last update htmlSetValue(txtLastMofication, dateFormatter.format(fileGridModel.getLastModification())); //size + fileSize = fileGridModel.getSize(); htmlSetValue(txtSize, getFormattedSize(fileGridModel.getSize())); }else { loadLastModificationDate(fileModel.getIdentifier()); @@ -327,9 +339,88 @@ public class DialogGetInfoBootstrap extends Composite { } - if(typeEnum.equals(GXTFolderItemTypeEnum.EXTERNAL_PDF_FILE) || - typeEnum.equals(GXTFolderItemTypeEnum.PDF_DOCUMENT) || - typeEnum.equals(GXTFolderItemTypeEnum.EXTERNAL_FILE)) { + // SOLUTION BASED ON GOOGLE DOC VIEWER + if (typeEnum.equals(GXTFolderItemTypeEnum.EXTERNAL_PDF_FILE) || + + typeEnum.equals(GXTFolderItemTypeEnum.PDF_DOCUMENT) + || typeEnum.equals(GXTFolderItemTypeEnum.EXTERNAL_FILE)) { + + AppControllerExplorer.rpcWorkspaceService.getPublicLinkForFileItemId(fileModel.getIdentifier(), false, + new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + } + + @Override + public void onSuccess(PublicLink result) { + + GWT.log("The PublicLink link is: " + result); + + if (result != null) { + + //if file size is null or greater than 25MB + long byteTo25MB = 1024*1024*25; + GWT.log("The file size is: "+fileSize); + if(fileSize==null || fileSize>byteTo25MB) { + GWT.log("The file size is null or greater than "+byteTo25MB+", returning"); + //htmlPanelFilePreview.add(new Image(Resources.getPreviewNotAvailable())); + return; + } + +// String pdfPreview = ""; + + String googleDocViewerURL = "https://docs.google.com/viewer?url=" + + URL.encode(result.getCompleteURL()) + "&embedded=true"; + + final HTML loadingPreviewHTML = new HTML(); + setPlaceholder(loadingPreviewHTML, "loading preview..."); + + final Frame frame = instanceFrame(googleDocViewerURL, loadingPreviewHTML); + + final long startTime = new Date().getTime(); + Timer timer = new Timer() { + + @Override + public void run() { + GWT.log("Checking if the iFrameGoogleDocViewer is ready"); + if(iFrameGoogleDocViewerLoaded) { + removePlaceHolder(loadingPreviewHTML); + GWT.log("iFrameGoogleDocViewer currently loaded, cancelling timer"); + cancel(); + return; + } + long checkTime = new Date().getTime(); + long diff = checkTime - startTime; + if(diff>5000) {//is greater than 5 sec + try { + GWT.log("iFrameGoogleDocViewer not loaded within 5 sec, cancelling timer, removing iframe"); + cancel(); + removePlaceHolder(loadingPreviewHTML); + htmlPanelFilePreview.add(new Image(Resources.getPreviewNotAvailable())); + frame.setVisible(false); + htmlPanelFilePreview.remove(frame); + }catch (Exception e) { + //Silent + } + } + } + }; + timer.scheduleRepeating(200); + + htmlPanelFilePreview.add(loadingPreviewHTML); + htmlPanelFilePreview.add(frame); + htmlPanelFilePreview.setVisible(true); + } + } + }); + } + + //SOLUTION BASED ON PDFOBEJCT + /*if(typeEnum.equals(GXTFolderItemTypeEnum.EXTERNAL_PDF_FILE) || + typeEnum.equals(GXTFolderItemTypeEnum.PDF_DOCUMENT)) { GWT.log("The file is a PDF"); @@ -338,7 +429,7 @@ public class DialogGetInfoBootstrap extends Composite { @Override public void onFailure(Throwable caught) { } - + @Override public void onSuccess(PublicLink result) { @@ -346,20 +437,45 @@ public class DialogGetInfoBootstrap extends Composite { if(result!=null) { - String pdfPreview = ""; - + String pdfPreview = "
"; htmlPanelFilePreview.add(new HTML(pdfPreview)); + showPDFPreview(result.getCompleteURL(), "pdfPrewiewDiv"); htmlPanelFilePreview.setVisible(true); } } }); - } + }*/ } addHandlers(); } + + public Frame instanceFrame(String fileURL, final HTML thePreviewPlaceholder) { + //addLoading(); + String urlEncoded = URL.encode(fileURL); + GWT.log("Encoded url for instanciating frame is " + urlEncoded); + + iFrameGoogleDocViewerLoaded = false; + + final NamedFrame frame = new NamedFrame("iFrameGoogleDocViewer"); + frame.setUrl(urlEncoded); + frame.setVisible(false); + frame.getElement().setId("iFrameGoogleDocViewer"); + frame.getElement().getStyle().setBorderWidth(0, Unit.PX); + frame.addLoadHandler(new LoadHandler() { + + @Override + public void onLoad(LoadEvent arg0) { + GWT.log("iFrameGoogleDocViewer loaded"); + iFrameGoogleDocViewerLoaded = true; + removePlaceHolder(thePreviewPlaceholder); + frame.getElement().addClassName("my-preview-doc"); + frame.setVisible(true); + + } + }); + return frame; + } /** * Adds the handlers. @@ -526,10 +642,12 @@ public class DialogGetInfoBootstrap extends Composite { private void loadSize(final String itemId) { GWT.log("Load size"); setPlaceholder(txtSize, "loading..."); + fileSize = new Long(-1); //means is loading AppControllerExplorer.rpcWorkspaceService.loadSizeByItemId(itemId, new AsyncCallback() { @Override public void onFailure(Throwable caught) { + fileSize = null; GWT.log("an error occured in load creation date by Id " + itemId + " " + caught.getMessage()); removePlaceHolder(txtSize); @@ -538,6 +656,7 @@ public class DialogGetInfoBootstrap extends Composite { @Override public void onSuccess(Long result) { GWT.log("Loaded size=" + result); + fileSize = result; removePlaceHolder(txtSize); if(result!=null) htmlSetValue(txtSize, getFormattedSize(result)); @@ -758,5 +877,11 @@ public class DialogGetInfoBootstrap extends Composite { html.setHTML(""); html.getElement().removeClassName("placeholder-loading"); } + + + public static native String showPDFPreview(String pdfURL, String divId)/*-{ + var theDivContainer = "#"+divId; + $wnd.PDFObject.embed(pdfURL, theDivContainer); + }-*/; } diff --git a/src/main/java/org/gcube/portlets/user/workspace/public/js/pdfobject.js b/src/main/java/org/gcube/portlets/user/workspace/public/js/pdfobject.js new file mode 100644 index 0000000..b1e3ec5 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/workspace/public/js/pdfobject.js @@ -0,0 +1,286 @@ +/*global ActiveXObject, window, console, define, module, jQuery */ +//jshint unused:false, strict: false + +/* + PDFObject v2.1.1 + https://github.com/pipwerks/PDFObject + Copyright (c) 2008-2018 Philip Hutchison + MIT-style license: http://pipwerks.mit-license.org/ + UMD module pattern from https://github.com/umdjs/umd/blob/master/templates/returnExports.js +*/ + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], factory); + } else if (typeof module === 'object' && module.exports) { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like environments that support module.exports, + // like Node. + module.exports = factory(); + } else { + // Browser globals (root is window) + root.PDFObject = factory(); + } +}(this, function () { + + "use strict"; + //jshint unused:true + + //PDFObject is designed for client-side (browsers), not server-side (node) + //Will choke on undefined navigator and window vars when run on server + //Return boolean false and exit function when running server-side + + if(typeof window === "undefined" || typeof navigator === "undefined"){ return false; } + + var pdfobjectversion = "2.1.1", + ua = window.navigator.userAgent, + + //declare booleans + supportsPDFs, + isIE, + supportsPdfMimeType = (typeof navigator.mimeTypes['application/pdf'] !== "undefined"), + supportsPdfActiveX, + isModernBrowser = (function (){ return (typeof window.Promise !== "undefined"); })(), + isFirefox = (function (){ return (ua.indexOf("irefox") !== -1); } )(), + isFirefoxWithPDFJS = (function (){ + //Firefox started shipping PDF.js in Firefox 19. + //If this is Firefox 19 or greater, assume PDF.js is available + if(!isFirefox){ return false; } + //parse userAgent string to get release version ("rv") + //ex: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:57.0) Gecko/20100101 Firefox/57.0 + return (parseInt(ua.split("rv:")[1].split(".")[0], 10) > 18); + })(), + isIOS = (function (){ return (/iphone|ipad|ipod/i.test(ua.toLowerCase())); })(), + + //declare functions + createAXO, + buildFragmentString, + log, + embedError, + embed, + getTargetElement, + generatePDFJSiframe, + generateEmbedElement; + + + /* ---------------------------------------------------- + Supporting functions + ---------------------------------------------------- */ + + createAXO = function (type){ + var ax; + try { + ax = new ActiveXObject(type); + } catch (e) { + ax = null; //ensure ax remains null + } + return ax; + }; + + //IE11 still uses ActiveX for Adobe Reader, but IE 11 doesn't expose + //window.ActiveXObject the same way previous versions of IE did + //window.ActiveXObject will evaluate to false in IE 11, but "ActiveXObject" in window evaluates to true + //so check the first one for older IE, and the second for IE11 + //FWIW, MS Edge (replacing IE11) does not support ActiveX at all, both will evaluate false + //Constructed as a method (not a prop) to avoid unneccesarry overhead -- will only be evaluated if needed + isIE = function (){ return !!(window.ActiveXObject || "ActiveXObject" in window); }; + + //If either ActiveX support for "AcroPDF.PDF" or "PDF.PdfCtrl" are found, return true + //Constructed as a method (not a prop) to avoid unneccesarry overhead -- will only be evaluated if needed + supportsPdfActiveX = function (){ return !!(createAXO("AcroPDF.PDF") || createAXO("PDF.PdfCtrl")); }; + + //Determines whether PDF support is available + supportsPDFs = ( + //as of iOS 12, inline PDF rendering is still not supported in Safari or native webview + //3rd-party browsers (eg Chrome, Firefox) use Apple's webview for rendering, and thus the same result as Safari + //Therefore if iOS, we shall assume that PDF support is not available + !isIOS && ( + //Modern versions of Firefox come bundled with PDFJS + isFirefoxWithPDFJS || + //Browsers that still support the original MIME type check + supportsPdfMimeType || ( + //Pity the poor souls still using IE + isIE() && supportsPdfActiveX() + ) + ) + ); + + //Create a fragment identifier for using PDF Open parameters when embedding PDF + buildFragmentString = function(pdfParams){ + + var string = "", + prop; + + if(pdfParams){ + + for (prop in pdfParams) { + if (pdfParams.hasOwnProperty(prop)) { + string += encodeURIComponent(prop) + "=" + encodeURIComponent(pdfParams[prop]) + "&"; + } + } + + //The string will be empty if no PDF Params found + if(string){ + + string = "#" + string; + + //Remove last ampersand + string = string.slice(0, string.length - 1); + + } + + } + + return string; + + }; + + log = function (msg){ + if(typeof console !== "undefined" && console.log){ + console.log("[PDFObject] " + msg); + } + }; + + embedError = function (msg){ + log(msg); + return false; + }; + + getTargetElement = function (targetSelector){ + + //Default to body for full-browser PDF + var targetNode = document.body; + + //If a targetSelector is specified, check to see whether + //it's passing a selector, jQuery object, or an HTML element + + if(typeof targetSelector === "string"){ + + //Is CSS selector + targetNode = document.querySelector(targetSelector); + + } else if (typeof jQuery !== "undefined" && targetSelector instanceof jQuery && targetSelector.length) { + + //Is jQuery element. Extract HTML node + targetNode = targetSelector.get(0); + + } else if (typeof targetSelector.nodeType !== "undefined" && targetSelector.nodeType === 1){ + + //Is HTML element + targetNode = targetSelector; + + } + + return targetNode; + + }; + + generatePDFJSiframe = function (targetNode, url, pdfOpenFragment, PDFJS_URL, id){ + + var fullURL = PDFJS_URL + "?file=" + encodeURIComponent(url) + pdfOpenFragment; + var scrollfix = (isIOS) ? "-webkit-overflow-scrolling: touch; overflow-y: scroll; " : "overflow: hidden; "; + var iframe = "
"; + targetNode.className += " pdfobject-container"; + targetNode.style.position = "relative"; + targetNode.style.overflow = "auto"; + targetNode.innerHTML = iframe; + return targetNode.getElementsByTagName("iframe")[0]; + + }; + + generateEmbedElement = function (targetNode, targetSelector, url, pdfOpenFragment, width, height, id){ + + var style = ""; + + if(targetSelector && targetSelector !== document.body){ + style = "width: " + width + "; height: " + height + ";"; + } else { + style = "position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 100%; height: 100%;"; + } + + targetNode.className += " pdfobject-container"; + targetNode.innerHTML = ""; + + return targetNode.getElementsByTagName("embed")[0]; + + }; + + embed = function(url, targetSelector, options){ + + //Ensure URL is available. If not, exit now. + if(typeof url !== "string"){ return embedError("URL is not valid"); } + + //If targetSelector is not defined, convert to boolean + targetSelector = (typeof targetSelector !== "undefined") ? targetSelector : false; + + //Ensure options object is not undefined -- enables easier error checking below + options = (typeof options !== "undefined") ? options : {}; + + //Get passed options, or set reasonable defaults + var id = (options.id && typeof options.id === "string") ? "id='" + options.id + "'" : "", + page = (options.page) ? options.page : false, + pdfOpenParams = (options.pdfOpenParams) ? options.pdfOpenParams : {}, + fallbackLink = (typeof options.fallbackLink !== "undefined") ? options.fallbackLink : true, + width = (options.width) ? options.width : "100%", + height = (options.height) ? options.height : "100%", + assumptionMode = (typeof options.assumptionMode === "boolean") ? options.assumptionMode : true, + forcePDFJS = (typeof options.forcePDFJS === "boolean") ? options.forcePDFJS : false, + PDFJS_URL = (options.PDFJS_URL) ? options.PDFJS_URL : false, + targetNode = getTargetElement(targetSelector), + fallbackHTML = "", + pdfOpenFragment = "", + fallbackHTML_default = "

This browser does not support inline PDFs. Please download the PDF to view it: Download PDF

"; + + //If target element is specified but is not valid, exit without doing anything + if(!targetNode){ return embedError("Target element cannot be determined"); } + + + //page option overrides pdfOpenParams, if found + if(page){ + pdfOpenParams.page = page; + } + + //Stringify optional Adobe params for opening document (as fragment identifier) + pdfOpenFragment = buildFragmentString(pdfOpenParams); + + //Do the dance + + //If the forcePDFJS option is invoked, skip everything else and embed as directed + if(forcePDFJS && PDFJS_URL){ + + return generatePDFJSiframe(targetNode, url, pdfOpenFragment, PDFJS_URL, id); + + //If traditional support is provided, or if this is a modern browser and not iOS (see comment for supportsPDFs declaration) + } else if(supportsPDFs || (assumptionMode && isModernBrowser && !isIOS)){ + + return generateEmbedElement(targetNode, targetSelector, url, pdfOpenFragment, width, height, id); + + //If everything else has failed and a PDFJS fallback is provided, try to use it + } else if(PDFJS_URL){ + + return generatePDFJSiframe(targetNode, url, pdfOpenFragment, PDFJS_URL, id); + + } else { + + //Display the fallback link if available + if(fallbackLink){ + + fallbackHTML = (typeof fallbackLink === "string") ? fallbackLink : fallbackHTML_default; + targetNode.innerHTML = fallbackHTML.replace(/\[url\]/g, url); + + } + + return embedError("This browser does not support embedded PDFs"); + + } + + }; + + return { + embed: function (a,b,c){ return embed(a,b,c); }, + pdfobjectversion: (function () { return pdfobjectversion; })(), + supportsPDFs: (function (){ return supportsPDFs; })() + }; + +})); \ No newline at end of file