ListView.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import React, { Component } from 'react';
  2. import { SimpleTable, Container, TruncatedText, SVGIcon, Tooltip, Tags } from 'ca-ui-toolkit';
  3. import PropTypes from 'prop-types';
  4. import menuOverflow16 from '@ba-ui-toolkit/ba-graphics/dist/icons/overflow-menu--horizontal_16.svg';
  5. import './ListView.scss';
  6. const COL_LENGTH = 4;
  7. /*
  8. Use in HomeView.js
  9. import { ListView } from '../ListView/ListView.js';
  10. ...
  11. <div className='homeContentContainer' style={{ 'width': '100%' }}>
  12. <ListView height={<value calculation>} stringGetter={stringGetter} glassContext={glassContext} entries={this._formatAssets()} entryAssets={assets} allowUploadFiles={allowUploadFiles} homeView={homeView} stateId={stateId}/>
  13. */
  14. export class ListView extends Component {
  15. static propTypes = {
  16. /** Custom class name(s) */
  17. className: PropTypes.string,
  18. /** The glass context handed down */
  19. glassContext: PropTypes.object,
  20. /** A reference to the ES5 homeView object */
  21. homeView: PropTypes.object,
  22. /** A copy of the i18n object used to translate strings */
  23. stringGetter: PropTypes.object,
  24. /** A list of the list entries formatted properly for use from the ca-ui-toolkit */
  25. entries: PropTypes.array,
  26. /** A list of the list entries unformatted with all information. This is to be used by glassContext */
  27. entryAssets: PropTypes.array,
  28. /** a boolean showing whether or not something can be uploaded if there is no data */
  29. allowUploadFiles: PropTypes.bool,
  30. /** An id used to remove context menus for list entries */
  31. stateId: PropTypes.string,
  32. /** Height Value for SimpleTable Component */
  33. height: PropTypes.string.isRequired
  34. };
  35. static defaultProps = {
  36. entries: []
  37. }
  38. constructor(props) {
  39. super(props);
  40. }
  41. state = {
  42. data: [],
  43. headerData: [
  44. { label: '' },
  45. { label: '' },
  46. { label: '' },
  47. { label: '' }
  48. ],
  49. menuLoading: false
  50. };
  51. componentDidMount(){
  52. // Data preparation for rendering
  53. const { entries, stringGetter } = this.props;
  54. this.setState({ entries: entries });
  55. this.setState({ data: this.dataPrep(entries) });
  56. this.setState({ headerData: [
  57. { label: stringGetter.get('list_header_name') },
  58. { label: stringGetter.get('list_header_type') },
  59. { label: stringGetter.get('list_header_last_modified') },
  60. { label: '' }
  61. ] });
  62. }
  63. componentDidUpdate(prevProps) {
  64. // In case if entries removed, refresh the list
  65. if(prevProps.entries.length !== this.props.entries.length)
  66. this.setState({ data: this.dataPrep(this.props.entries) });
  67. else{
  68. for(let i = 0; i < this.props.entries.length; i++){
  69. if(prevProps.entries[i].label !== this.props.entries[i].label || prevProps.entries[i].date !== this.props.entries[i].date){
  70. this.setState({ data: this.dataPrep(this.props.entries) });
  71. break;
  72. }
  73. }
  74. }
  75. }
  76. // Handler for data preparation and formating
  77. dataPrep = (data) => {
  78. const result = data.map(element => [
  79. { label: element.label },
  80. { label: element.type },
  81. { label: element.date },
  82. { label: '' }
  83. ]);
  84. return result;
  85. }
  86. // Return the value in the designated table cell
  87. _getValue = (row, col) => {
  88. if (this.state.data.length > row && this.state.data[row].length > col) {
  89. return this.state.data[row][col].label;
  90. }
  91. return null;
  92. };
  93. // Function for rendering the table body
  94. _cellRenderer(row, col) {
  95. /*
  96. Three situations of cell content:
  97. - Text (Name, Last updated)
  98. - Asset tag (Type)
  99. - Overflow menu icon
  100. */
  101. // Must: check the length first -> row length are fixed before state updates (concurrency issue)
  102. if(row >= this.props.entryAssets.length)
  103. return;
  104. if(col === 0) {
  105. return (
  106. <TruncatedText
  107. tabIndex='0'
  108. onKeyUp={ (e) => { this._openEntryViaTabbing(e, row); }}
  109. value={this.state.data[row][col].label}
  110. title={this.state.data[row][col].label}
  111. location='end'
  112. className='cell_assetName'
  113. />
  114. );
  115. } else if (col === 1) {
  116. const typeName = this.state.data[row][col].label;
  117. const asset = this.props.entryAssets[row];
  118. let tagColor;
  119. let supportedtypeName = typeName ? typeName[0].toUpperCase() + typeName.slice(1).toLowerCase() : '';
  120. /**
  121. * dashboard/report/story => purple tags
  122. * exploration/notebooks = blue tags
  123. * data upload, data sets, data module, data = green tags
  124. * others = grey tags
  125. */
  126. if(asset.type === 'report' || asset.type === 'reportView' || asset.type === 'interactiveReport' || asset.type === 'powerPlay8Report' || (asset.type === 'exploration' && (!asset.tags || asset.tags[0] !== 'explore'))){
  127. tagColor = 'purple';
  128. }else if((asset.type === 'exploration' && asset.tags && asset.tags[0] === 'explore') || asset.type === 'jupyterNotebook'){
  129. tagColor = 'blue';
  130. }else if(asset.type === 'dataSet2' || asset.type === 'module' || asset.type === 'uploadedFile' || asset.type === 'URL'){
  131. tagColor = 'teal';
  132. // If it's data and only one word, make all lowercased according to design doc
  133. supportedtypeName = supportedtypeName && supportedtypeName.split(' ').length === 1? supportedtypeName.toLowerCase() : supportedtypeName;
  134. }else{
  135. tagColor = 'gray';
  136. }
  137. return (<Tooltip orient='right' title={supportedtypeName} delay>
  138. <Tags tags={[{ value: supportedtypeName, label: supportedtypeName, color: tagColor }]} fullWidth={true} removable={false} />
  139. </Tooltip>);
  140. } else if (col === 3) {
  141. return (
  142. <div role="menu" tabIndex='0' className="iconWrapper" onKeyUp={ (e) => { this._openActionMenu(e, row); }}>
  143. <Tooltip title={this.props.stringGetter.get('assetActionMenu')} orient="right" delay>
  144. <SVGIcon iconId={menuOverflow16.id} rotate={90} className='menuIcon' id={`menuIconRow_${row}`} onClick={(e) => { this._openActionMenu(e); }}/>
  145. </Tooltip>
  146. </div>
  147. );
  148. } else {
  149. return (
  150. <TruncatedText
  151. value={this.state.data[row][col].label}
  152. title={this.state.data[row][col].label}
  153. location='end'
  154. className='cell_lastModified'
  155. />
  156. );
  157. }
  158. }
  159. // Handler for waking the action menu while clicking the menu icon on each table row
  160. _openActionMenu(e, index=e.target.id.split('_')[1]) {
  161. e.stopPropagation();
  162. // If not a shift click, ctrl click or a right click or the user pressed enter on the context menu
  163. if((e.type == 'click' && !e.shiftKey && !e.ctrlKey && e.nativeEvent.which !== 3) || (e.type=='keyup' && e.keyCode === 13)) {
  164. this.setState({ menuLoading: true });
  165. e.persist();
  166. const { homeView, entryAssets } = this.props;
  167. let asset = entryAssets[index];
  168. if(homeView && homeView.requiresAssetVerification) {
  169. homeView.loadAssetContextMenu(asset, e).then(function() {
  170. this.setState({ menuLoading: false });
  171. }.bind(this));
  172. }
  173. }
  174. }
  175. // Handler to open the project via tabbing action
  176. _openEntryViaTabbing(e, index=e.target.id.split('_')[1]){
  177. e.stopPropagation();
  178. // If not a shift click, ctrl click or a right click or the user pressed enter on the context menu
  179. if((e.type == 'click' && !e.shiftKey && !e.ctrlKey && e.nativeEvent.which !== 3) || (e.type=='keyup' && e.keyCode === 13)) {
  180. e.persist();
  181. this._openEntry(index);
  182. }
  183. }
  184. // Handler for clicking the entry to open the detail page
  185. _openEntry(row){
  186. this.props.homeView.onTileClick(this.props.entryAssets[row]);
  187. }
  188. // Function used to render the table header
  189. _headerRenderer(col) {
  190. return (
  191. <div style={{ fontWeight: 'bold' }}>
  192. <TruncatedText
  193. value={this.state.headerData[col].label}
  194. location="end"
  195. />
  196. </div>
  197. );
  198. }
  199. render() {
  200. /*
  201. Dynamic allocation for width division to avoid horizontal scrolling
  202. Original Proportion in design spec is: 544:205:314
  203. If necessary, reduced by 15% to make sure that moreButton won't be jumped outside of the table
  204. */
  205. return (
  206. <Container gutter={[2, 0]} id="TableCaHome" style={{ 'padding': '0 !important' }}>
  207. <SimpleTable
  208. // eslint-disable-next-line react/no-string-refs
  209. ref='simpletable'
  210. height={this.props.height}
  211. rowLength={this.state.data.length}
  212. colToAddScopeRow={1}
  213. colLength={COL_LENGTH}
  214. cellRenderer={(row, col) => (
  215. <Container gutter={[1, 1]}>
  216. {this._cellRenderer(row, col)}
  217. </Container>
  218. )}
  219. headerRenderer={col => (
  220. <Container gutter={[1, 1]}>
  221. {this._headerRenderer(col)}
  222. </Container>
  223. )}
  224. onRowClick={row => this._openEntry(row) }
  225. />
  226. </Container>
  227. );
  228. }
  229. }