123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- 'use strict';
- /**
- * Licensed Materials - Property of IBM
- * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2018, 2019
- * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
- */
- define(['../../../lib/@waca/core-client/js/core-client/ui/core/Class', '../../../app/nls/StringResources', './ThumbnailStore', '../../../lib/@waca/core-client/js/core-client/utils/UniqueId', '../../../lib/@waca/image-capture/dist/js/bundles/image-capture.min', '../../../lib/@waca/echo-cache/dist/js/bundles/echo-cache.min', '../../../lib/@waca/core-client/js/core-client/utils/ClassFactory', 'underscore', 'jquery'], function (Class, StringResources, ThumbnailStore, UniqueId, ImageCaptureLib, EchoCacheLib, ClassFactory, _, $) {
- /**
- * Unique DOM attributes to strip.
- * Every time a new thumbnail is generated, it contains unique bits of
- * information. For performance reasons, we need to compare the existing
- * thumbnail data with the one about to be stored. The unique attributes
- * need to be stripped for the compared data, or the comparison will always
- * yield false positives.
- * @type {Array}
- */
- var UNIQUE_DOM_ATTRIBUTES = ['id', 'clip-path'];
- var ImageCapture = ImageCaptureLib.default;
- var EchoCacheService = EchoCacheLib.EchoCacheService;
- var ThumbnailService = Class.extend({
- /**
- * local store
- * serves as a cache to store all thumbnails
- * @type {ThumbnailStore}
- */
- _store: null,
- /**
- * Echo cache service
- *
- * @type {EchoCacheService}
- */
- echoCacheService: null,
- /**
- * Thumbnail service class that has all the relevant logic regarding
- * the persistence of thumbnails
- *
- * @param {Object} [options] - options
- * @param {Object} [options.store] - store to initialize the instance with
- */
- init: function init() {
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- ThumbnailService.inherited('init', this, arguments);
- this._pendingRequests = {
- fetch: {}
- };
- this.readyPromise = this.initialize(options);
- },
- /**
- * Check whether the pending request type is valid or not
- *
- * @param {string} type - pending request type to validate
- *
- * @return {boolean} TRUE if pending request type is valid and FALSE if not
- */
- _isValidPendingRequestType: function _isValidPendingRequestType(type) {
- return !!this._pendingRequests[type];
- },
- /**
- * Set pending request
- *
- * @param {Object} options - pending request options
- * @param {string} options.type - type of pending request; valid values include: 'fetch'
- * @param {string} options.cacheId - unique cache id to store the request; if the request already exists,
- * @param {Promise} request - Promise that will resolve
- */
- setPendingRequest: function setPendingRequest(options, request) {
- if (!options) {
- throw new Error('Invalid options argument provided');
- }
- var type = options.type,
- cacheId = options.cacheId;
- if (!cacheId) {
- throw new Error('Invalid cacheId option provided');
- }
- if (!this._isValidPendingRequestType(type)) {
- throw new Error('Invalid type: ' + type);
- }
- this._pendingRequests[type][cacheId] = request;
- },
- /**
- * Get pending request if exists
- *
- * @param {Object} options - pending request options
- * @param {string} options.type - type of pending request; valid values include: 'fetch'
- * @param {string} options.cacheId - cache id the previous request was stored under
- *
- * @return {Promise} Promise that resolves with the result of the same request
- */
- getPendingRequest: function getPendingRequest(options) {
- if (!options) {
- throw new Error('Invalid options argument provided');
- }
- var type = options.type,
- cacheId = options.cacheId;
- if (!cacheId) {
- throw new Error('Invalid cacheId option provided');
- }
- if (!this._isValidPendingRequestType(type)) {
- throw new Error('Invalid type: ' + type);
- }
- return this._pendingRequests[type][cacheId];
- },
- /**
- * Initialize
- *
- * @param {object} options - options
- * @param {GlassContext} options.glassContext - glass context
- * @param {Object} [options.store] - store to use for the thumbnail store
- * @param {Object} [options.elementMap] - element map
- * @param {EchoCacheService} [options.echoCacheSvc] - echo cache service to use
- *
- * @return {Promise} promise resolved on success and rejected on failure
- */
- initialize: function initialize(_ref) {
- var glassContext = _ref.glassContext,
- _ref$store = _ref.store,
- store = _ref$store === undefined ? {} : _ref$store,
- elementMap = _ref.elementMap,
- echoCacheSvc = _ref.echoCacheSvc;
- this.glassContext = glassContext;
- this._store = new ThumbnailStore(store);
- this.echoCacheService = echoCacheSvc || new EchoCacheService({
- logger: glassContext.getCoreSvc('.Logger'),
- ajaxSvc: glassContext.getCoreSvc('.Ajax')
- });
- this._thumbnailGenerator = null;
- this._elements = {
- map: {},
- paths: elementMap
- };
- return this.initializeThumbnailGenerator();
- },
- /**
- * Initialize thumbnail generator
- *
- * @return {Promise} promise resolved on success and rejected on failure
- */
- initializeThumbnailGenerator: function initializeThumbnailGenerator() {
- var _this = this;
- var promise = void 0;
- if (this._elements.paths) {
- var elementKeys = Object.keys(this._elements.paths);
- promise = Promise.all(elementKeys.map(function (key) {
- return 'text!' + _this._elements.paths[key];
- }).map(function (path) {
- return ClassFactory.loadModule(path);
- })).then(function (elements) {
- elements.forEach(function (element, key) {
- var mapKey = elementKeys[key];
- if (mapKey) {
- _this._elements.map[mapKey] = element;
- }
- });
- }).catch(function (error) {
- _this.glassContext.getCoreSvc('.Logger').error('ThumbnailService.readyPromise:error', error);
- });
- } else {
- promise = Promise.resolve();
- }
- return promise.then(function () {
- _this._thumbnailGenerator = new ImageCapture({
- UniqueId: UniqueId,
- elementMap: _this._elements.map
- });
- });
- },
- /**
- * Destructor
- */
- destroy: function destroy() {
- if (this._thumbnailGenerator) {
- this._thumbnailGenerator.destroy();
- }
- this._elements = null;
- this._store = null;
- },
- /**
- * Add a thumbnail
- *
- * @param {string} widgetModel - widget model
- * @param {any} data - thumbnail data
- *
- * @return {Promise} Promise to be resolved on success and rejected with error
- */
- sync: function sync(widgetModel, data) {
- var _this2 = this;
- var result = void 0;
- if (data) {
- var updateToEchoService = function updateToEchoService(cacheId, data) {
- return _this2.update(cacheId, data).then(function (_ref2) {
- var cacheId = _ref2.cacheId,
- affinity = _ref2.affinity;
- return _this2.fetch(cacheId, affinity);
- }).then(function (_ref3) {
- var data = _ref3.data,
- cacheId = _ref3.cacheId;
- _this2._setCacheIdInWidgetAndStore(widgetModel, cacheId, data);
- }, function () {
- _this2._setCacheIdInWidgetAndStore(widgetModel, null, null); //remove previous thumbnail on failure
- });
- };
- result = this.fetch(widgetModel.thumbnailId, undefined, { silent: true }).then(function (_ref4) {
- var oldData = _ref4.data;
- if (_this2._needsUpdate(oldData, data)) {
- return updateToEchoService(widgetModel.thumbnailId, data);
- } else {
- var cachedData = _this2._store.getLocal(widgetModel.id);
- if (_this2._needsUpdate(cachedData, data)) {
- _this2._setCacheIdInWidgetAndStore(widgetModel, widgetModel.thumbnailId, data);
- }
- return { data: data };
- }
- }).catch(function () {
- return updateToEchoService(widgetModel.thumbnailId, data);
- });
- } else {
- this._setCacheIdInWidgetAndStore(widgetModel, null /*cacheId*/);
- }
- return result || Promise.resolve();
- },
- _setCacheIdInWidgetAndStore: function _setCacheIdInWidgetAndStore(widgetModel, cacheId, data) {
- var widgetId = widgetModel.id;
- var options = {
- payloadData: {
- runtimeOnly: true
- }
- };
- widgetModel.set({
- thumbnailId: cacheId
- }, options);
- if (cacheId) {
- this._store.set(widgetId, { data: data, cacheId: cacheId });
- } else {
- this._store.delete(widgetId);
- }
- },
- /**
- * Set error on the widget
- *
- * @param {string} widgetId - widget id
- * @param {Error} error - thumbnail error to store
- */
- setError: function setError(widgetId, error) {
- this._store.setError(widgetId, error);
- },
- /**
- * Set warning on the widget
- *
- * @param {string} widgetId - widget id
- * @param {Warning} warning - thumbnail warning to store
- */
- setWarning: function setWarning(widgetId, warning) {
- this._store.setWarning(widgetId, warning);
- },
- /**
- * Check whether the thumbnail is in dire need of an update
- *
- * @param {Object} data - old thumnail data
- * @param {Object} newData - new thumbnail data
- *
- * @return {boolean} TRUE if needs update and FALSE if not
- */
- _needsUpdate: function _needsUpdate(data, newData) {
- return this._stripUniqueAttributes(data) !== this._stripUniqueAttributes(newData) || this._getImgSrc(data) !== this._getImgSrc(newData);
- },
- _getImgSrc: function _getImgSrc(data) {
- var $data = $(data).find('img');
- if ($data) {
- return $data.attr('src');
- }
- return null;
- },
- /**
- * Strip unique attributes from the data
- *
- * @param {string} data - input data
- *
- * @return {string} output data with the unique DOM attributes stripped
- */
- _stripUniqueAttributes: function _stripUniqueAttributes(data) {
- var $data = $(data);
- UNIQUE_DOM_ATTRIBUTES.forEach(function (attr) {
- $data.find('[' + attr + ']').attr(attr, '');
- });
- return $data.html();
- },
- /**
- * Get store API
- *
- * @param {Object} options
- * @param {Function} options.defaultFn - default function to use in case there's no thumbnail with a specific id
- *
- * @return {object} store API
- */
- getStoreAPI: function getStoreAPI() {
- var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { defaultFn: function defaultFn() {} },
- defaultFn = _ref5.defaultFn;
- // clone the store instance and assign the `getVisTypeDefault` method
- return _.extend({}, this._store, {
- getVisTypeDefault: defaultFn
- });
- },
- /**
- * Get thumbnail base on the thumbnail model and vis type
- *
- * @param {WidgetModel} widgetModel - widget model
- * @param {string} type - vis type
- *
- * @return {Promise}
- */
- get: function get(widgetModel, type, storeAPI) {
- var _this3 = this;
- if (!storeAPI) {
- storeAPI = this._store;
- }
- var id = widgetModel.id;
- var promise = void 0;
- if (storeAPI.exists(id)) {
- promise = Promise.resolve(storeAPI.getLocal(id));
- } else {
- var cacheId = widgetModel.thumbnailId;
- if (cacheId) {
- promise = this.fetch(cacheId, undefined, { silent: true }).then(function (_ref6) {
- var data = _ref6.data,
- cacheId = _ref6.cacheId;
- _this3._store.set(id, {
- data: data,
- cacheId: cacheId
- });
- return storeAPI.getLocal(id);
- }).catch(function () {
- // retrieval from cache failed
- // but don't reject, recover
- return undefined;
- });
- } else {
- promise = Promise.resolve();
- }
- }
- return promise.then(function () {
- return storeAPI.get(id, type);
- });
- },
- // --- AJAX methods
- /**
- * Fetch a specific thumbnail
- *
- * @param {string} cacheId - the thumbnail id
- * @param {string} [affinity] - server affinity to use (returned from the update call); cacheId and affinity are closely related. Making assumptions about the affinity is not recommended at this point.
- * @param {object} [options={}] - options
- * @param {boolean} [options.silent] - silent (don't log failures)
- *
- * @return {Promise} Promise that resolves with the thumbnail or rejects with error
- */
- fetch: function fetch(cacheId, affinity) {
- var _this4 = this;
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
- if (!cacheId) {
- return Promise.reject(new Error('Invalid cacheId argument provided: ' + cacheId));
- }
- var pendingRequest = this.getPendingRequest({
- type: 'fetch',
- cacheId: cacheId
- });
- if (pendingRequest) {
- var isPromise = !!pendingRequest.then;
- if (isPromise) {
- // if promise, return as is
- return pendingRequest;
- } else {
- // it's an error
- return Promise.reject(pendingRequest);
- }
- }
- var request = this.echoCacheService.retrieve(cacheId, affinity).then(function (response) {
- // clear the pending request
- _this4.setPendingRequest({
- type: 'fetch',
- cacheId: cacheId
- }, undefined);
- var text = response.text;
- _this4.glassContext.getCoreSvc('.Logger').debug('ThumbnailService.fetch:response', response);
- return {
- cacheId: cacheId,
- data: text
- };
- }).catch(function (response) {
- var error = _this4._ajaxErrorHandler(response, 'ThumbnailService.fetch:error', options);
- // save the error as a pending request, to prevent subsequent request before any update has finished
- // @see ThumbnailService::update method
- _this4.setPendingRequest({
- type: 'fetch',
- cacheId: cacheId
- }, error);
- // reject the promise with the error.
- return Promise.reject(error);
- });
- this.setPendingRequest({
- type: 'fetch',
- cacheId: cacheId
- }, request);
- return request;
- },
- /**
- * Update a specific thumbnail
- *
- * @param {string} cacheId - the id of thumbnail
- * @param {string} thumbnail - thumbnail data
- * @param {object} [options={}] - options
- * @param {boolean} [options.silent] - silent (don't log failures)
- *
- * @return {Promise} Promise that resolves with the thumbnail or rejects with error
- */
- update: function update(cacheId, thumbnail, options) {
- var _this5 = this;
- return this.echoCacheService.stash(thumbnail, {
- cacheId: cacheId
- }).then(function (_ref7) {
- var id = _ref7.id,
- location = _ref7.location,
- affinity = _ref7.affinity;
- // clear the pending request on failure
- _this5.setPendingRequest({
- type: 'fetch',
- cacheId: id
- }, undefined);
- return {
- cacheId: id,
- location: location,
- affinity: affinity
- };
- }).catch(function (response) {
- return Promise.reject(_this5._ajaxErrorHandler(response, 'ThumbnailService.update:error', options));
- });
- },
- /**
- * Ajax error handler
- *
- * @param {Object} response - error response about to be handled
- * @param {string} logString - string used for logging
- */
- _ajaxErrorHandler: function _ajaxErrorHandler(response) {
- var logString = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'ThumbnailService:error';
- var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { silent: false };
- var silent = options.silent;
- var message = this._parseError(response, StringResources.get('thumbnails_error_fetching'));
- var error = new Error(message);
- if (!silent) {
- this.glassContext.getCoreSvc('.Logger').error(logString, error);
- }
- return error;
- },
- /**
- * Parse error response
- *
- * @param {Object} response - ajax response
- * @param {string} response.responseText - ajax response text
- * @param {string} defaultMessage - default message in case there is no response text
- *
- * @return {string} final error text
- */
- _parseError: function _parseError(response, defaultMessage) {
- var message = defaultMessage;
- try {
- if (response.responseText) {
- var text = JSON.parse(response.responseText);
- message = text.error;
- }
- } catch (e) {
- // ignore JSON parsing errors
- }
- return message;
- },
- /**
- * Prepare thumbnail data
- *
- * @param {string} data - data to be prepared
- *
- * @return {string} prepared data
- */
- _prepareThumbnail: function _prepareThumbnail(data) {
- return JSON.stringify({ data: data });
- },
- /**
- * Generate image
- *
- * @param {DOMElement} _domNode - DOM node to generate image of
- * @param {object} options - options
- * @param {number} maxHeight - maximum height
- *
- * @return {Promise} Promise to be resolved on success and rejected on faiure
- */
- generateImage: function generateImage(_domNode, options, maxHeight) {
- options = _.extend(options || {}, {
- // the thumbnail generator takes the background color of selected node and applies them to
- // the thumbnail - we don't want this for nodes with static content, e.g. webpage widgets
- onClone: function onClone(doc, node) {
- var $staticNode = $(node).find('.staticContent');
- if ($staticNode.length) {
- $staticNode.css({ 'background-color': 'transparent' });
- }
- },
- excludeEmptyIframes: true,
- transparentBackground: false
- });
- return this._thumbnailGenerator.generateImage(_domNode, options, maxHeight);
- },
- /**
- * Generate image
- *
- * @param {DOMElement} _domNode - DOM node to generate image of
- * @param {object} options - options
- * @param {number} maxHeight - maximum height
- *
- * @return {Promise} Promise to be resolved on success and rejected on faiure
- */
- generateMarkup: function generateMarkup(_domNode, options, maxHeight) {
- return this.generateImage(_domNode, options, maxHeight).then(function (content) {
- return '<div class="visThumbnail"><div class="visThumbnailImgContainer"><img src="' + content + '" /></div></div>';
- });
- },
- /**
- * Delete thumbnail
- *
- * @param {id} thumbnail id
- * @return {ThumbnailStore} self; current instance
- */
- delete: function _delete(id) {
- return this._store.delete(id);
- }
- });
- return ThumbnailService;
- });
- //# sourceMappingURL=ThumbnailService.js.map
|