Source: api/sharing/ShareableItems.js

/**
 * Licensed Materials - Property of IBM
 * IBM Cognos Products: Collaboration
 * (C) Copyright IBM Corp. 2017, 2019
 *
 * US Government Users Restricted Rights - Use, duplication or disclosure
 * restricted by GSA ADP Schedule Contract with IBM Corp.
 */

define([
	'jquery',
	'../../lib/@waca/core-client/js/core-client/ui/core/Class',
	'../../lib/@waca/baglass/js/baglass/utils/Utils',
	'../../lib/@waca/baglass/js/baglass/api/Url',
	'../../nls/StringResources',
	'./GenerateImage'
], function ($, Class, Utils, Url, StringResources, GenerateImage) {
	'use strict';

	const HTML_2_CANVAS_PROXY = 'v1/collaboration/proxy/html2canvas';
	const SHARE_CONTEXTUAL_ACTION_KEY = 'com.ibm.ca.collaboration.shareContextual';

	const SVG_NO_IMAGE = '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">' +
		'<rect x="0" y="0" width="100%" height="100%" fill="#eaeaea"/>' +
		'<g transform="translate(-64, -64)">' +
		'<svg x="50%" y="50%" width="128" height="128">' +
		'<g fill="#c0bfc0">' +
		'<path d="M64,12L4,116h120L64,12z M64,20.004L117.074,112H10.926L64,20.004z"/>' +
		'<polygon points="60,56 60,64 62,84 66,84 68,64 68,56"/>' +
		'<circle cx="64" cy="92" r="4"/>' +
		'</g>' +
		'</svg>' +
		'</g>' +
		'</svg>';

	const ShareableItems = Class.extend( /** @lends ShareableItems */ {

		/**
		 * @desc Constructor for ShareableItems.
		 * @constructs ShareableItems
		 * @extends Class
		 * @public
		 * @param {object} options
		 * @param {object} options.logger
		 */
		init: function (options) {
			this._url = new Url();
			this._proxy = options && options.html2canvasProxy || HTML_2_CANVAS_PROXY;
			this.setLogger(options);
		},

		setLogger: function (options) {
			if (options && options.glassContext) {
				this._logger = options.glassContext.getCoreSvc('.Logger');
			} else {
				this._logger = null;
			}
		},

		/**
		 * Returns an object containing a link representing the current context.
		 *
		 * @instance
		 * @param {object} glassContext
		 * @param {object} target
		 * @returns {Promise} resolved as an object that contains shareUrl and embedUrl
		 */
		getLink: function (glassContext, target) {
			return this._isValid(glassContext, target)
				.then(function (isValid) {
					//if target exist, means share accessed from context menu so no need to validate
					if (!target && !isValid) {
						return '';
					}
					return this._getPublicContext(glassContext, target)
						.then(function (publicContext) {
							return this._getActionController(glassContext, publicContext, target)
								.then(function (actionController) {
									return this._url.getUrlMap(actionController, glassContext, publicContext)
										.then(function (urlMap) {
											if (urlMap.link) {
												// special case: take the link as-is
												return { shareUrl: urlMap.link };
											}
											return {
												shareUrl: this._url.getUrl({ urlMap: urlMap }, glassContext),
												embedUrl: this._url.getUrl({ urlMap: urlMap, isEmbed: true }, glassContext)
											};
										}.bind(this));
								}.bind(this));
						}.bind(this));
				}.bind(this));
		},

		_isValid: function (glassContext, target) {
			return Promise.resolve(!!this._getAssetId(glassContext, target));
		},

		_getResourceToShare: function (glassContext, target) {
			return this._getPublicContext(glassContext, target)
				.then(function (publicContext) {
					return this._getActionController(glassContext, publicContext, target)
						.then(function (actionController) {
							return this._getShareableItems(actionController, publicContext)
								.then(function (items) {
									const item = items[0];
									return {
										el: item.el && item.el.length ? item.el[0] : item.el,
										label: item.label,
										type: publicContext.urlMap.type
									};
								}.bind(this));
						}.bind(this));
				}.bind(this))
				.catch(function (err) {
					if (this._logger) {
						this._logger.error(err);
					}
					throw err;
				}.bind(this));
		},

		/**
		 * Returns an object containing an image representing the current context.
		 *
		 * @instance
		 * @param {object} glassContext
		 * @returns {Promise} with image (as text).
		 */
		getScreenshot: function (glassContext, target) {
			return this._getResourceToShare(glassContext, target)
				.then(resource => {
					return this._buildItem(resource.el, resource.label, resource.type);
				});
		},

		/**
		 * Check whether it's possible to screenshot the current context.
		 * 
		 * @param {*} glassContext 
		 * @param {*} target 
		 * @returns {Promise} that resolves to true or false.
		 */
		canCaptureImage: function (glassContext, target) {

			const isPdfReport = (resource) => {
				const reportTypes = [ 'report', 'reportView', 'output' ];
				if (reportTypes.indexOf(resource.type) > -1) {
					return $(resource.el).find('div#idPdfViewer').is(':visible');
				}
				return false;
			};

			return this._getResourceToShare(glassContext, target)
				.then(resource => {
					if (isPdfReport(resource)) {
						return false;
					}

					// Add more checks here to detect resources that can't be captured.

					return true;
				});
		},

		/**
		 * Gets share action controller
		 *
		 * @instance
		 * @param {object} glassContext
		 * @returns {Promise}
		 */
		getActionController: function (glassContext, target) {
			return this._getPublicContext(glassContext, target)
				.then(this._getActionController.bind(this, glassContext));
		},

		/**
		 * Notifies share action controller after share slideout shows.
		 * @param {object} options
		 * @param {object} options.glassContext
		 * @param {object} options.slideout
		 * @instance
		 * @returns {Promise}
		 */
		enterShareState: function (options) {
			return this.getActionController(options.glassContext)
				.then(function (actionController) {
					if (actionController && actionController.enterShareState) {
						return actionController.enterShareState(options);
					}
				}.bind(this));
		},

		/**
		 * Notifies share action controller after share slideout hides.
		 * @param {object} options.glassContext
		 * @param {object} options.slideout
		 * @instance
		 * @returns {Promise}
		 */
		leaveShareState: function (options) {
			return this.getActionController(options.glassContext)
				.then(function (actionController) {
					if (actionController && actionController.leaveShareState) {
						return actionController.leaveShareState(options);
					}
				}.bind(this));
		},

		_getActionController: function (glassContext, context, target) {
			const type = context.urlMap.type;
			const selection = target && target.plugin && target.plugin.activeObject && target.plugin.activeObject.aSelectedContext;

			return Utils.getSharedResourceActionController(glassContext, type, selection);
		},

		_getPublicContext: function (glassContext, target) {
			const isDefaultAction = !!target;
			target = target || {};
			return new Promise(function (resolve) {
				const publicContext = {
					urlMap: {
						objRef: this._getResourceObjRef(glassContext, target),
						type: this._getResourceType(glassContext, target)
					},
					mode: this._getMode(glassContext, target),
					isDefaultAction: isDefaultAction,
					target: target,
					glassContext: glassContext // required by getUrlMap
				};
				const mode = this._getResourceMode(glassContext, target);
				if (mode) {
					publicContext.urlMap.mode = mode;
				}
				resolve(publicContext);
			}.bind(this));
		},

		_getShareableItems: function (actionController, publicContext) {
			return new Promise(function (resolve, reject) {
				if (actionController && typeof actionController.getShareableItems === 'function') {
					resolve(actionController.getShareableItems(publicContext));
				} else {
					reject(new Error(StringResources.get('error_not_implemented')));
				}
			});
		},

		_getGenerator: function () {
			return new GenerateImage(this._getGenerateImageOptions());
		},

		_getGenerateImageOptions: function () {
			return {
				logger: this._logger,
				proxy: this._proxy,
				elementMap: {
					'mediaWidget': SVG_NO_IMAGE,
					'webpageWidget': SVG_NO_IMAGE
				}
			};
		},

		_buildItem: function (el, label, resourceType) {
			const item = {};
			item.label = label;
			// TEMP FIX FOR DEMO: delay the image generation by 1sec to help with animation
			// See 223989: [Collaboration]: screenshot panel is rolled into share panel during screenshot capture
			return new Promise(function (resolve, reject) {
				const generator = this._getGenerator();
				window.setTimeout(function () {
					generator
						.generateImage(el, null, resourceType)
						.then(function (image) {
							item.image = image;
							return item;
						})
						.then(resolve)
						.catch(reject)
						.finally(function () {
							if (generator) {
								generator.destroy();
							}
						});
				}, 1000);
			}.bind(this));
		},

		_getResourceType: function (glassContext, target) {
			let type;
			try {
				type = target.plugin.options[0].type;
			} catch (e) {
				type = glassContext.currentAppView.getType();
			}
			return type;
		},

		_getResourceObjRef: function (glassContext, target) {
			let objRef;
			try {
				objRef = target.plugin.options[0].id;
			} catch (e) {
				objRef = this._getAssetId(glassContext, target);
			}
			return objRef;
		},

		_getAssetId: function (glassContext, target) {
			const content = glassContext.currentAppView.getContent();
			//Dashboard, Exploration, Storytelling & Data Modules updates content.objRef when an asset is saved
			//Report and Datasets updates content.application.storeID when it is saved
			//Notebooks update content.id
			const type = this._getResourceType(glassContext, target);
			const id = type === 'jupyterNotebook' ? content.id : content.objRef;
			return id || (content.application && content.application.storeID);
		},

		_getResourceMode: function (glassContext, target) {
			let mode;
			try {
				const obj = target.activeObject.aSelectedContext[0];
				if (obj.defaultScreenTip === 'story') {
					mode = 'story';
				}
			} catch (e) {
				const content = glassContext.currentAppView.getContent();
				mode = content.mode;
				if (mode === undefined && content.isStoryMode) {
					// When we open a story then reload the page
					mode = 'story';
				}
			}
			return mode;
		},

		_getMode: function (glassContext, target) {
			try {
				const isDynamic = target.itemId === SHARE_CONTEXTUAL_ACTION_KEY;
				return isDynamic ? Url.MODES.DYNAMIC : Url.MODES.CURRENT;
			} catch (e) {
				return Url.MODES.CURRENT;
			}
		}
	});

	return ShareableItems;
});