'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** * Licensed Materials - Property of IBM * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2018, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ /** * * View to swap the Live Widget Visualization through the ODT * Options * { * showTitles: boolean, // Whether to show the titles for the available/recommended visualizations * visualizations: { * recommended: [], // Array of visualizations to place in recommended section * other: [] // Array of visualizations to put in other section * }, * currentVis: String, // The id of the currently selected visualization * widget: Live Widget // The widget that is creating the vischanger view * } * **/ define(['../../../lib/@waca/core-client/js/core-client/ui/core/View', '../../../lib/@waca/core-client/js/core-client/utils/Utils', '../nls/StringResources', 'jquery', 'underscore', 'react-dom', 'react', 'ca-ui-toolkit', 'dashboard-analytics/DynamicFileLoader', 'gemini/app/util/ErrorUtils'], function (BaseView, Utils, resources, $, _, ReactDOM, React, Toolkit, DynamicFileLoader, ErrorUtils) { /* eslint react/prop-types: 0 */ // TODO: Remove once we have prop-types brought in from glass. var Accordion = Toolkit.Accordion; var AccordionItem = Toolkit.AccordionItem; var SVGIcon = Toolkit.SVGIcon; var Label = Toolkit.Label; var SVGIconDecoration = Toolkit.SVGIconDecoration; var ProgressIndicator = Toolkit.ProgressIndicator; // Limit the number of recommended visualizations to show in the recommended section. var MaxRecommendedVisualizationsToShow = 6; var AUTO_ID = 'auto'; /****************************************************** * Recommended Accordion Section * AccordionItem * [PropItems] - SVG (with possible deco) and Label * All visualizations Accordion Section * AccordionItem * [PropItems] - SVG (with possible deco) and Label ********************************************************/ /* * Class to render the react flyout components */ var ReactComponents = function (_React$Component) { _inherits(ReactComponents, _React$Component); /* * The props hold a state that should be used as this classes state. * When the state changes the component gets re-rendered. */ function ReactComponents(props) { _classCallCheck(this, ReactComponents); var _this = _possibleConstructorReturn(this, _React$Component.call(this, props)); _this.state = _extends({}, props.state); _this.state.checkMarkIcon = props.state.iconsFeature.getIcon('CheckmarkSVG'); return _this; } /** * @param {string} id of the selected visualization * @return {React Component} the decoration if the specified id matched the selected id. */ ReactComponents.prototype.getSelectedDecoration = function getSelectedDecoration(id) { // The decoration is basically a checkmark to show the selected vis. if (this.state.selectedId === id) { return React.createElement(SVGIconDecoration, { iconId: this.state.checkMarkIcon.id, location: 'bottomRight', style: { right: '-4px' } }); } }; /** * @param {object} item describing all the info needed to create a prop item * @param {String} dataType either 'Recommended' or 'Other' * @return {React Object} */ ReactComponents.prototype.renderPropItem = function renderPropItem(item, dataType) { var decoration = this.getSelectedDecoration(item.id); var pressed = this.state.selectedId === item.id ? 'true' : 'false'; return React.createElement( 'div', { role: 'group', className: 'prop-item' }, React.createElement( 'div', { tabIndex: 0, 'data-type': dataType, className: 'prop-icon vis', role: 'button', title: item.name, 'data-id': item.id, 'appcues-data-id': item.id, 'aria-label': item.name, 'aria-pressed': pressed }, React.createElement( SVGIcon, { iconId: this.state.svgsMap[item.id].id, height: 42, width: 42 }, decoration ), React.createElement(Label, { className: 'prop-icon-label', label: item.name }) ) ); }; /** * @param {Array} items all the visualizations to add to the row * @param {Boolean} isRecommended true iff the Vis Row is the recommended row. * @returns {Array} of React components for each Vis in the Vis Row. */ ReactComponents.prototype.loadVisRowItems = function loadVisRowItems(items, isRecommended) { var _this2 = this; var svgs = []; var dataType = isRecommended ? 'Recommended' : 'Other'; items.forEach(function (item) { if (item.id && _this2.state.svgsMap[item.id]) { svgs.push(_this2.renderPropItem(item, dataType)); } }); return svgs; }; /** * Render the progress indicator to show we are loading. */ ReactComponents.prototype._renderLoading = function _renderLoading() { return React.createElement( 'div', { className: 'vis-changer-progressIndicator' }, React.createElement(ProgressIndicator, { size: 'large', variant: 'circle' }) ); }; ReactComponents.prototype._getVisRowItems = function _getVisRowItems(options) { var items = options.items, isRecommended = options.isRecommended; var loadedSvgs = this.loadVisRowItems(items, isRecommended); return React.createElement( 'div', { className: 'visChangerRow' }, loadedSvgs ); }; ReactComponents.prototype._getRecommendedAccordionItem = function _getRecommendedAccordionItem() { var recommendedOptions = { items: this.state.recommended.items, isRecommended: true }; return this.state.recommended.isLoading ? this._renderLoading() : this._getVisRowItems(recommendedOptions); }; ReactComponents.prototype._getAllVisAccordionItem = function _getAllVisAccordionItem() { var allVisOptions = { items: this.state.all.items, isRecommended: false }; return this.state.all.isLoading ? this._renderLoading() : this._getVisRowItems(allVisOptions); }; /** * Render the collapsible sections (accordions) * @return {React Object} representing the Accordion and its sections. */ ReactComponents.prototype._renderAccordions = function _renderAccordions() { var recommendedItem = this._getRecommendedAccordionItem(); var isConsumer = this.state.isConsumer; if (!isConsumer) { return React.createElement( Accordion, null, React.createElement( AccordionItem, { itemName: resources.get('recommended_visualizations'), icon: 'left', open: true }, recommendedItem ), React.createElement( AccordionItem, { itemName: resources.get('all_visualizations'), icon: 'left', open: false, onChange: this.state.onItemChange }, this._getAllVisAccordionItem() ) ); } else { return React.createElement( Accordion, null, React.createElement( AccordionItem, { itemName: resources.get('recommended_visualizations'), icon: 'left', open: true }, recommendedItem ) ); } }; /** * @returns {React Object} progress indicator if we are loading, accordions otherwise. */ ReactComponents.prototype.render = function render() { return this._renderAccordions(); }; return ReactComponents; }(React.Component); /** * The following view is the actual ODT Vis Changer view. It will handle * selection of the react components to change vis type, rendering the different * choices into two collapsible sections as well as showing a loading indicator * until all the SVGs are loaded. */ var View = BaseView.extend({ events: { 'primaryaction .vis': '_selectVis' }, /** * Load the SVG files representing all the visualizations */ _loadSVGFileMap: function _loadSVGFileMap() { var _this3 = this; // If we've already loaded the SVG files, return the map if (this.viewState.svgsMap && _.keys(this.viewState.svgsMap).length > 0) { return Promise.resolve(this.viewState.svgsMap); } else { this.viewState.svgsMap = {}; this._getAllAvailableVisualizations(); if (this.viewState.all && this.viewState.all.items) { var iconsFeature = this.viewState.iconsFeature; this.viewState.all.items.forEach(function (item) { var visIcon = iconsFeature.getIcon(item.visId); _this3.viewState.svgsMap[item.visId] = visIcon; }); if (!(Object.keys(this.viewState.svgsMap).indexOf('auto') !== -1)) { var autoIcon = iconsFeature.getIcon('auto'); this.viewState.svgsMap['auto'] = autoIcon; } return Promise.resolve(this.viewState.svgsMap); } } }, init: function init(options) { // Provide the basic element. this.el = $('
'); View.inherited('init', this, arguments); this.applyAction = options && options.actions && options.actions.apply; var finalOptions = options && options.state ? options.state : options; _.extend(this, finalOptions || {}); this.iconsFeature = this.dashboard.getFeature('Icons'); this._initializeViewState(); }, _elementExists: function _elementExists() { var hasLength = false, hasKids = false, el = this.el; if (el) { if (el.length) { hasLength = el.length > 0; } if (el.children) { hasKids = el.children !== 0; } } return el && (hasKids || hasLength); }, remove: function remove() { if (this._elementExists()) { ReactDOM.unmountComponentAtNode(this.el); } this.viewState = null; return View.inherited('remove', this, arguments); }, setFocus: function setFocus() { this.$('.vis').first().focus(); }, _processVisualizations: function _processVisualizations(visualizations) { var _this4 = this; var markupPayload = []; _.each(visualizations, function (vis) { _this4.viewState.visualizationMap[vis.id || vis.getId()] = vis; markupPayload.push({ id: vis.id || vis.getId(), name: vis.caption || vis.getLabel(), visId: vis.id || vis.getId(), caption: vis.caption || vis.getLabel(), iconUri: vis.icon || vis.getIcon() }); }); return markupPayload; }, /** * We want to dynamically load all the SVGs (why load them if we never need them) * This method loads the SVG files into a map, asks the widget for a list * of recommended and non-recommended visualizations, and adds the 'Auto' * choice as well. */ _preload: function _preload() { // Load the file map this.viewState.visualizationMap = this.viewState.visualizationMap || {}; return this._loadSVGFileMap(); }, _getAllAvailableVisualizations: function _getAllAvailableVisualizations() { var allVisualizations = this.dashboard.getFeature('VisDefinitions').getList(); var processedItems = this._processVisualizations(allVisualizations); this.viewState.all.items = _.sortBy(processedItems, function (vis) { return vis.name; }); this.viewState.all.isLoading = false; }, _getRecommendedVisualizations: function _getRecommendedVisualizations() { var _this5 = this; return this.content.getFeature('Visualization.SmartsRecommender').getRecommendedVisualizations().then(function (recommended) { if (_this5.viewState === null) { return; //Remove has already been called, stop processing } var processedVisualizations = _this5._processVisualizations(recommended); processedVisualizations.unshift({ id: AUTO_ID, name: resources.get('automaticTypeCaption'), visId: AUTO_ID, caption: resources.get('automaticTypeCaption'), iconUri: 'visualizations-changeVisualization', disabled: processedVisualizations.length === 0 }); var maxVisualizationsToShow = MaxRecommendedVisualizationsToShow; if (_this5.viewState.selectedId) { // Don't include Auto in the limit maxVisualizationsToShow++; } _this5.viewState.recommended.items = processedVisualizations.slice(0, maxVisualizationsToShow); _this5.viewState.recommended.isLoading = false; }); }, _initializeViewState: function _initializeViewState() { this.viewState = { recommended: { items: [], isLoading: true }, all: { items: [], isLoading: true }, svgsMap: null, selectedId: this._getSelectedVizId(), visualizationMap: null, iconsFeature: this.iconsFeature, onItemChange: this._onItemChange.bind(this) }; }, //called when an item in the accordian is changed _onItemChange: function _onItemChange() { //ensure this flyout view is not off the screen var diff = this._getOffScreenHeight(); if (diff > 0) { //TODO: This is not a great solution, especially if we change the way popovers are handled. // in the future, any resizing should be handled by the popover class. var popover = $(this.el).parents('.popover'); var top = Math.max(0, popover.position().top - diff); popover && popover.css({ top: top }); } }, /** * Checks to see if this View is off the visible screen. * @returns the number of pixels this view is off the screen, or -1 if it is not off the screen */ _getOffScreenHeight: function _getOffScreenHeight() { var htmlScrollHeight = this._getDocumentElement().scrollHeight; var innerHeight = Utils.getCurrentWindow().innerHeight; if (htmlScrollHeight > innerHeight) { return htmlScrollHeight - innerHeight; } return -1; }, /** * @returns The document.documentElement. * This is here to more easily mock for tests. */ _getDocumentElement: function _getDocumentElement() { return document.documentElement; }, _getSelectedVizId: function _getSelectedVizId() { var visualization = this.content.getFeature('Visualization'); if (this.currentVis && this.currentVis === AUTO_ID || visualization && !visualization.isTypeLocked()) { return AUTO_ID; } else { return visualization.getDefinition().getId(); } }, _resetRecommendedViewState: function _resetRecommendedViewState() { this.viewState.recommended = { items: [], isLoading: true }; if (this._elementExists() && this.reactComponents) { ReactDOM.unmountComponentAtNode(this.el); } }, /** * Create the react components with an initial loading state. */ _showLoading: function _showLoading() { this._resetRecommendedViewState(); // Render the react components this.reactComponents = ReactDOM.render(React.createElement(ReactComponents, { state: this.viewState }), this.el); }, /** * Render the react components with a state to show the accordions. */ _showReactSections: function _showReactSections() { this.reactComponents.setState(JSON.parse(JSON.stringify(this.viewState))); }, _render: function _render(options) { var _this6 = this; _.extend(this, options || {}); this._isConsumer(); this._showLoading(); return this._preload().then(function () { _this6._showReactSections(); }).then(this._getRecommendedVisualizations.bind(this)).then(function () { _this6._showReactSections(); }); }, _isConsumer: function _isConsumer() { var currentContentView = this.dashboard && this.dashboard.getCurrentContentView(); var glassContext = currentContentView && currentContentView.glassContext; if (this.viewState && glassContext) { this.viewState.isConsumer = !ErrorUtils.hasCapability(glassContext, 'canAuthorDashboard'); } }, /** * Method to render the view. This includes creating the react components to * initially show a progress indicator, preload all the svg icons, divide the * choices into sections, then update the react components to show the accordions. */ render: function render(options) { this._render(options); return Promise.resolve(); }, _getTargetFromEvent: function _getTargetFromEvent(event) { return $(event.currentTarget); }, // when id auto we highlight auto too /** * Event handler for a .vis button is clicked. * Send event to be handled by Live Widget and updates its view with current selection */ _selectVis: function _selectVis(event) { var $currentTarget = this._getTargetFromEvent(event); var id = $currentTarget.attr('data-id'); var recommendedCategory = $currentTarget.attr('data-type'); // If selected visualization target is same as current visualization target if (this.viewState.selectedId === id) { return; } this.viewState.selectedId = id; this.reactComponents.setState({ selectedId: id }); var visDefinition = this.dashboard.getFeature('VisDefinitions').getById(id); var visType = visDefinition && visDefinition.getType() || id; this.applyAction(visType, { recommendedCategory: recommendedCategory }); if (this.selectVisCB) { this.selectVisCB(); } } }); return View; }); //# sourceMappingURL=VisChangerFlyoutView.js.map