HomeView.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. import React, { Component } from 'react';
  2. import PropTypes from 'prop-types';
  3. import { QuickLaunch } from '../QuickLaunch/QuickLaunch.js';
  4. import ReactHtmlParser from 'react-html-parser';
  5. import { SVGIcon, ToggleSwitch, Tooltip } from 'ca-ui-toolkit';
  6. import './HomeView.scss';
  7. import { TilesView } from '../TilesView/TilesView.js';
  8. import iconMap from '../utils/IconMap';
  9. import grid16 from '@ba-ui-toolkit/ba-graphics/dist/icons/grid_16.svg';
  10. import list32 from '@ba-ui-toolkit/ba-graphics/dist/icons/list_32.svg';
  11. import dragdrop_icon from '../../../../../images/DragAndDrop.svg';
  12. import ca_logoicon from '../../../../../images/ca_logoicon.png';
  13. import noUpload from '../../../../../images/noUploadCapability.svg';
  14. import { WelcomeView } from '../WelcomeView/WelcomeView';
  15. import { ListView } from '../ListView/ListView.js';
  16. export class HomeView extends Component {
  17. static propTypes = {
  18. /** Custom class name(s) */
  19. className: PropTypes.string,
  20. /** An array of all of the tiles, if there are no assets it defaults to empty */
  21. assets: PropTypes.array,
  22. /** A boolean which determines if a user can drag and drop uploads to quick launch */
  23. allowUploadFiles: PropTypes.bool,
  24. /** A function which communicates with glass to upload the data from quick launch */
  25. uploadFiles: PropTypes.func,
  26. /** A copy of the glass context */
  27. glassContext: PropTypes.object,
  28. /** The strings used for the welcome label */
  29. labels: PropTypes.object,
  30. /** A copy of the i18n translation object */
  31. stringGetter: PropTypes.object,
  32. /** A reference to the ES5 homeView object */
  33. homeView: PropTypes.object,
  34. /** a string used in the tiles to remove the context menu later */
  35. stateId: PropTypes.string,
  36. /** The ID used to reference the file uploaded through Quick Launch */
  37. quickLaunchCollectionId: PropTypes.string,
  38. /** The folder name in which to upload the file if a user runs QuickLaunch */
  39. folderName: PropTypes.string,
  40. /** Specifies if a jupyter server is configured */
  41. jupyterEnabled: PropTypes.string,
  42. /** The theme values from glass */
  43. themeValues: PropTypes.object
  44. };
  45. static defaultProps = {
  46. assets: [],
  47. allowUploadFiles: false
  48. }
  49. _quickRefCollectionId = 'com.ibm.bi.bahome_common.quickReferences';
  50. state = {
  51. dragEnterTargets: [],
  52. tilesLoaded: false,
  53. showQuickLaunch: false,
  54. quickRefContent: '',
  55. assets: [],
  56. showWelcome: false,
  57. enableWelcome: false,
  58. showTile: true,
  59. listHeight: 550
  60. }
  61. getTileAssets() {
  62. this._getMRUList()
  63. .then(function(mruList) {
  64. this.setState({ assets: mruList, tilesLoaded: true });
  65. }.bind(this));
  66. }
  67. constructor(props) {
  68. super(props);
  69. // Grab the quickLaunchTargets
  70. this.props.glassContext.appController.findCollection(this.props.quickLaunchCollectionId)
  71. .then(function (itemCollection) {
  72. this.quickLaunchItems = itemCollection || [];
  73. }.bind(this));
  74. // Set up quick reference items
  75. this.quickRefItemData = {
  76. label: props.stringGetter.get('quickReference'),
  77. panels: [{
  78. label: props.stringGetter.get('getStarted'),
  79. expanded: true,
  80. references:[]
  81. }, {
  82. label: props.stringGetter.get('sampleData'),
  83. expanded: false,
  84. references:[]
  85. }, {
  86. label: props.stringGetter.get('support'),
  87. expanded: false,
  88. references:[]
  89. }]
  90. };
  91. this.props.glassContext.appController.findCollection(this._quickRefCollectionId)
  92. .then(function (refItems) {
  93. this.quickRefContentData = refItems;
  94. }.bind(this));
  95. /**
  96. * Fetch assets to show on the Home page.
  97. */
  98. this.getTileAssets();
  99. }
  100. // Update local storage for the display of welcome section
  101. _toShowWelcomeAtLogin = (displayNextTime) => {
  102. const prefixID = this.localStorageIdentifier()+'_';
  103. if(typeof(Storage)!=='undefined')
  104. window.localStorage.setItem(prefixID+'showWelcome', displayNextTime ? 'true' : 'false');
  105. }
  106. // Update local storage for the display of tile or view
  107. _toShowTileViewAtLogin = (displayNextTime) => {
  108. const prefixID = this.localStorageIdentifier()+'_';
  109. if(typeof(Storage)!=='undefined')
  110. window.localStorage.setItem(prefixID+'showTile', displayNextTime ? 'true' : 'false');
  111. }
  112. componentDidMount(){
  113. const prefixID = this.localStorageIdentifier()+'_';
  114. if(typeof(Storage)!=='undefined'){
  115. // Check local storage to determine whether welcome section should be displayed
  116. if(window.localStorage.getItem(prefixID+'showWelcome')){
  117. // Not First time user
  118. this.setState({ showWelcome: window.localStorage.getItem(prefixID+'showWelcome')==='true' ? true : false });
  119. }else{
  120. // First time user
  121. window.localStorage.setItem(prefixID+'showWelcome', 'true');
  122. this.setState({ showWelcome: true });
  123. }
  124. // Check local storage to determine whether tile view or list view should be displayed
  125. if(window.localStorage.getItem(prefixID+'showTile')){
  126. // Not First time user
  127. this.setState({ showTile: window.localStorage.getItem(prefixID+'showTile')==='true' ? true : false });
  128. }else{
  129. // First time user
  130. window.localStorage.setItem(prefixID+'showTile', 'true');
  131. this.setState({ showTile: true });
  132. }
  133. }else{
  134. // local storage is not supported
  135. this.setState({ showWelcome: false, showTile: true });
  136. }
  137. // Retrieve the admin's config context to determine whether welcome section should be shown
  138. this.props.glassContext.getCoreSvc('.Config').getConfigValue('Glass.welcomeScreenDisabled').then(state => {
  139. this.setState({ enableWelcome: (state.toString() !== 'true') ? true : false }, () => {
  140. if(!this.state.enableWelcome)
  141. this.setState({ showWelcome: false });
  142. });
  143. });
  144. this._calculateListHeight();
  145. window.addEventListener('resize', () => this._calculateListHeight());
  146. }
  147. /**
  148. * Add an onClick listener for the 'browse' anchor tags. Uses a function from the AMD homeView object to upload.
  149. * Does this both for the 'Add some data' link and the 'Browse' link.
  150. */
  151. componentDidUpdate() {
  152. const browseLinks = document.getElementsByClassName('homeBrowseFile');
  153. for(let i = 0; i < browseLinks.length; i++) {
  154. let link = browseLinks[i];
  155. link.onclick = function() {
  156. this.onBrowseFile(this.quickLaunchItems).bind(this);
  157. }.bind(this.props.homeView);
  158. }
  159. }
  160. componentWillUnmount(){
  161. window.removeEventListener('resize', () => this._calculateListHeight());
  162. }
  163. localStorageIdentifier = () => {
  164. return this.props.glassContext.profile.account.email;
  165. }
  166. _getContentService = () => {
  167. return this.props.glassContext.getSvc('.Content');
  168. }
  169. _getMRUList = () => {
  170. return this.props.homeView._getMRUList();
  171. }
  172. refreshMRUList = () => {
  173. return this._getContentService()
  174. .then(function(contentSvc) {
  175. // check if the refreshMRU method is available...
  176. if (contentSvc.refreshMRU) {
  177. return contentSvc.refreshMRU();
  178. }
  179. return Promise.resolve();
  180. }.bind(this))
  181. .then(function(mruList) { // eslint-disable-line no-unused-vars
  182. this.getTileAssets(); // Note: May not need to call getTileAssets here, see if only need to do this.tiles = mruList;
  183. }.bind(this));
  184. }
  185. /**
  186. * Formats the tile assets into the correct format to pass to the toolkit
  187. */
  188. _formatAssets = () => {
  189. return this.props.homeView.formatAssetsInToolkitFormat(this.state.assets, iconMap);
  190. }
  191. _hasFiles(data) {
  192. if (data.types && data.types.length > 0) {
  193. if ((data.types.contains && data.types.contains('Files')) || (data.types.indexOf && data.types.indexOf('Files') >= 0)) {
  194. // IE11 or Chrome, Safari, Firefox
  195. return true;
  196. }
  197. }
  198. return false;
  199. }
  200. _getDataFromEvent(event) {
  201. if (event.originalEvent) {
  202. return event.originalEvent.dataTransfer;
  203. } else {
  204. return event.dataTransfer;
  205. }
  206. }
  207. _getName(glassContext) {
  208. const { account } = glassContext.profile;
  209. if (account && account.isAnonymous === true) {
  210. return null;
  211. }
  212. if (account && account.givenName) {
  213. return account.givenName;
  214. } else if (account && account.defaultName) {
  215. return account.defaultName;
  216. }
  217. return null;
  218. }
  219. _registerDragEnterEvent (target) {
  220. let state = this.state;
  221. state.dragEnterTargets.push(target);
  222. state.showQuickLaunch = true;
  223. this.setState(state);
  224. }
  225. _registerDragLeaveEvent (target) {
  226. let state = this.state;
  227. let targetsStillEntered = [];
  228. // See what targets you have left from dragging over and remove them from state
  229. let i;
  230. let oldTarget;
  231. for (i = 0; i < state.dragEnterTargets.length; i++) {
  232. oldTarget = state.dragEnterTargets[i];
  233. if (oldTarget !== target) {
  234. targetsStillEntered.push(oldTarget);
  235. }
  236. }
  237. state.dragEnterTargets = targetsStillEntered;
  238. // If you are no longer hovering over items, hide the quick launch
  239. if (targetsStillEntered.length === 0) {
  240. state.showQuickLaunch = false;
  241. }
  242. this.setState(state);
  243. }
  244. hideQuickLaunch() {
  245. let state = this.state;
  246. state.dragEnterTargets = [];
  247. state.showQuickLaunch = false;
  248. this.setState(state);
  249. }
  250. _onDragOver (e) {
  251. e.preventDefault();
  252. e.stopPropagation();
  253. }
  254. _onDragEnter(e) {
  255. e.preventDefault();
  256. e.stopPropagation();
  257. // Retrieve the data file
  258. const data = this._getDataFromEvent(e);
  259. // Check if you can download
  260. if (this.props.allowUploadFiles && this._hasFiles(data)) {
  261. this._registerDragEnterEvent(e.target);
  262. }
  263. }
  264. _onDragLeave (e) {
  265. e.preventDefault();
  266. e.stopPropagation();
  267. if (this.props.allowUploadFiles) {
  268. this._registerDragLeaveEvent(e.target);
  269. }
  270. }
  271. _onDrop(e) {
  272. e.preventDefault();
  273. e.stopPropagation();
  274. let data = this._getDataFromEvent(e);
  275. if (this.props.allowUploadFiles && this._hasFiles(data)) {
  276. // Hide Quick Launch
  277. this.setState({ showQuickLaunch: false });
  278. if (this.props.uploadFiles) {
  279. this.props.uploadFiles('drop', data.files, this.quickLaunchItems);
  280. }
  281. }
  282. }
  283. // Event handler to open the sample folder in the content nav (side-navbar)
  284. _openSamplesFolder = () => {
  285. const { homeView } = this.props;
  286. homeView._openSamples();
  287. }
  288. _checkSamplesFolder = () => {
  289. const { homeView } = this.props;
  290. return homeView._checkSamplesFolder().then(function(status) {
  291. return status;
  292. });
  293. }
  294. // Reformat the primary title to a DOM node by enforcing a new-line for brand name
  295. titleFormatter(brandText){
  296. const { stringGetter } = this.props;
  297. let primaryTitleArray = stringGetter.get('welcome_primary_title', {
  298. 'brandName': brandText
  299. }).split(' ');
  300. return ReactHtmlParser(primaryTitleArray.join(' '));
  301. }
  302. // Handler to switch the view mode via tabbing
  303. _switchModeViaTabbing = (e, mode) => {
  304. e.stopPropagation();
  305. if((e.type == 'click' && !e.shiftKey && !e.ctrlKey && e.nativeEvent.which !== 3) || (e.type=='keyup' && e.keyCode === 13)) {
  306. e.persist();
  307. if(mode==='tile'){
  308. this._showTileView();
  309. }else if(mode==='list'){
  310. this._showListView();
  311. }
  312. }
  313. }
  314. // Handler to enable the tile view
  315. _showTileView = () => {
  316. this._toShowTileViewAtLogin(true);
  317. this.setState({ showTile : true });
  318. }
  319. // Handler to enable the list view
  320. _showListView = () => {
  321. this._toShowTileViewAtLogin(false);
  322. this.setState({ showTile : false });
  323. }
  324. // Helper function to calculate the height of list view
  325. _calculateListHeight = () => {
  326. /*
  327. A little hacky trick used to determine the height of listView:
  328. 56: Margin-top of Welcome Header
  329. 64: Header Height
  330. 36: Margin-top of Recent Header while welcome is not shown
  331. 48: Height of Recent Header
  332. 20: Gap between Recent Header and Body
  333. 54: Recent View Footer
  334. 132: Calculated offset
  335. */
  336. this.setState({ listHeight: window.innerHeight - 56 - 64 - 36 - 48 - 20 - 54 - 132 });
  337. }
  338. render() {
  339. const { showQuickLaunch, assets, tilesLoaded } = this.state;
  340. const { glassContext, stringGetter, allowUploadFiles, folderName, homeView, stateId, quickLaunchCollectionId, jupyterEnabled, themeValues } = this.props;
  341. const wrapperClassName = showQuickLaunch ? 'homeViewWrapper dragging' : 'homeViewWrapper';
  342. // Only render the main container once the tiles load, this makes it not look like it renders twice
  343. const tileContainer = tilesLoaded ?
  344. (<div className='homeContentContainer recentTileContainer'>
  345. <TilesView glassContext={glassContext} tiles={this._formatAssets()} tileAssets={assets} stringGetter={stringGetter} allowUploadFiles={allowUploadFiles} homeView={homeView} stateId={stateId}/>
  346. </div>) :
  347. (<div className='homeContentContainer recentTileContainer'></div>);
  348. // Only rendered when no recent task exists
  349. const noRecentContainer = tilesLoaded ?
  350. (<div className='homeContentContainer recentNoneContainer'>
  351. <div style={{ 'width': '128px', 'height': '128px', 'margin': '0 auto' }}>
  352. <svg style={{ 'width': '128px', 'height': '128px' }}>
  353. <use xlinkHref={`#${dragdrop_icon.id}`} />
  354. </svg>
  355. </div>
  356. <h4 style={{ 'margin': '32px auto 0px auto' }}>
  357. {stringGetter.get('noRecents')}
  358. </h4>
  359. </div>) :
  360. (<div className='homeContentContainer recentNoneContainer'></div>);
  361. const noRecentContainerNoUpload = tilesLoaded ?
  362. (<div className='homeContentContainer recentNoneContainer'>
  363. <div style={{ 'width': '200px', 'height': '200px', 'margin': '0 auto' }}>
  364. <svg style={{ 'width': '200px', 'height': '200px' }}>
  365. <use xlinkHref={`#${noUpload.id}`} />
  366. </svg>
  367. </div>
  368. <h4 style={{ 'margin': '32px auto 0px auto' }}>
  369. {stringGetter.get('noRecents')}
  370. </h4>
  371. </div>) :
  372. (<div className='homeContentContainer recentNoneContainer'></div>);
  373. const viewModeSwitch = assets.length > 0 ?
  374. (<div className='recentModeSwitchWrapper'>
  375. <Tooltip title={stringGetter.get('tooltip_tileView')} orient="top">
  376. <div tabIndex='0' role="application" title={stringGetter.get('tooltip_tileView')} onKeyUp={(e) => this._switchModeViaTabbing(e, 'tile')} className={`recentViewButton recentViewButtonLeft ${this.state.showTile? 'recentViewButtonIsActive' : ''}`} id="viewRecentTile" onClick={() => this._showTileView()}>
  377. <SVGIcon style={{ 'margin': '8px' }} iconId={grid16.id} />
  378. </div>
  379. </Tooltip>
  380. <Tooltip title={stringGetter.get('tooltip_listView')} orient="top">
  381. <div tabIndex='0' role="application" title={stringGetter.get('tooltip_listView')} onKeyUp={(e) => this._switchModeViaTabbing(e, 'list')} className={`recentViewButton recentViewButtonRight ${!this.state.showTile ? 'recentViewButtonIsActive' : ''}`} id="viewRecentList" onClick={() => this._showListView()}>
  382. <SVGIcon style={{ 'margin': '8px' }} iconId={list32.id} />
  383. </div>
  384. </Tooltip>
  385. </div>):null;
  386. const welcomeSwitch = this.state.enableWelcome ?
  387. (<div className='welcomeToggleWrapper'>
  388. <label className='welcomeToggleLabel'>{stringGetter.get('welcome_show')}</label>
  389. <ToggleSwitch title={stringGetter.get('welcome_show')} style={{ 'z-index': '999' }} onChange={() => {this._toShowWelcomeAtLogin(!this.state.showWelcome); this.setState({ showWelcome: !this.state.showWelcome });}} checked={!!this.state.showWelcome} small/>
  390. </div>) : null;
  391. const welcomeContainer = this.state.showWelcome && this.state.enableWelcome ?
  392. <WelcomeView themeValues={themeValues} sampleChecker={()=>this._checkSamplesFolder()} sampleOpener={()=>this._openSamplesFolder()} glassContext={glassContext} stringGetter={stringGetter} labels={{ brand: themeValues.brandText }}/> : null;
  393. // Prepare the list component for display
  394. const listContainer = tilesLoaded ? (<div className='homeContentContainer recentListContainer'>
  395. <ListView height={this.state.listHeight} stringGetter={stringGetter} glassContext={glassContext} entries={this._formatAssets()} entryAssets={assets} allowUploadFiles={allowUploadFiles} homeView={homeView} stateId={stateId}/></div>)
  396. :(<div className='homeContentContainer recentListContainer'></div>);
  397. const recentBrandLogo = themeValues.images.brandIcon==='common-CA_Avatar_Colour_64' ?
  398. <img className="recentBrandTitleIcon" src={ca_logoicon} /> :
  399. <img className="recentBrandTitleIcon" src={themeValues.images.brandIcon} />;
  400. const recentBrandContainer = this.state.showWelcome ? null : (<div className="recentBrandTitle">
  401. {recentBrandLogo}
  402. <div className="recentBrandTitleText">{this.titleFormatter(themeValues.brandText)}</div></div>);
  403. return (
  404. <div className='homeViewReactRoot' onDragOver={ this._onDragOver.bind(this) } onDragEnter={ this._onDragEnter.bind(this) } onDragStop={ this._onDragLeave.bind(this) } onDragLeave={ this._onDragLeave.bind(this) } onDragEnd={ this._onDragLeave.bind(this) } onDrop={ this._onDrop.bind(this) }>
  405. <div className={ wrapperClassName }>
  406. <QuickLaunch showQuickLaunch={showQuickLaunch} stringGetter={stringGetter} folderName={folderName} glassContext={glassContext} quickLaunchCollectionId={quickLaunchCollectionId} homeView={homeView} hideQuickLaunch={this.hideQuickLaunch.bind(this)} jupyterEnabled={jupyterEnabled}></QuickLaunch>
  407. </div>
  408. <div>
  409. {welcomeSwitch}
  410. {recentBrandContainer}
  411. </div>
  412. <div className='homeViewContent' >
  413. <div className='homeViewBody'>
  414. {welcomeContainer}
  415. <div className='recentView'>
  416. <div className='recentViewHeader' style={{ 'margin-top': this.state.showWelcome ? '48px' : '36px' }}>
  417. <div className='recentTitleWrapper'>{stringGetter.get('recent')}</div>
  418. {viewModeSwitch}
  419. </div>
  420. <div className='recentViewBody'>
  421. {assets.length === 0 ? ( homeView.canUploadFiles() ? noRecentContainer : noRecentContainerNoUpload) : (this.state.showTile ? tileContainer : listContainer)}
  422. </div>
  423. {homeView.canUploadFiles() && homeView.allowUploadFiles &&
  424. <div className='recentViewFooter' style={assets.length === 0 ? { 'padding-top': '32px', 'margin-bottom': '64px' } : (this.state.showTile ? { 'padding-top': '48px', 'height': '54px', 'margin-bottom': '64px' } : { 'padding-top': '18px', 'height': '54px' })}>
  425. { ReactHtmlParser(stringGetter.get('dndPrompt')) }
  426. </div>
  427. }
  428. </div>
  429. </div>
  430. </div>
  431. </div>
  432. );
  433. }
  434. }