text: /**
   * @flow
   * @file Content Explorer Component
   * @author Box
   */
  
  import 'regenerator-runtime/runtime';
  import React, { Component } from 'react';
  import classNames from 'classnames';
  import cloneDeep from 'lodash/cloneDeep';
  import debounce from 'lodash/debounce';
  import flow from 'lodash/flow';
  import getProp from 'lodash/get';
  import noop from 'lodash/noop';
  import uniqueid from 'lodash/uniqueId';
  import CreateFolderDialog from '../common/create-folder-dialog';
  import UploadDialog from '../common/upload-dialog';
  import Header from '../common/header';
  import Pagination from '../../features/pagination';
  import SubHeader from '../common/sub-header/SubHeader';
  import makeResponsive from '../common/makeResponsive';
  import openUrlInsideIframe from '../../utils/iframe';
  import Internationalize from '../common/Internationalize';
  import API from '../../api';
  import MetadataQueryAPIHelper from '../../features/metadata-based-view/MetadataQueryAPIHelper';
  import Footer from './Footer';
  import PreviewDialog from './PreviewDialog';
  import ShareDialog from './ShareDialog';
  import RenameDialog from './RenameDialog';
  import DeleteConfirmationDialog from './DeleteConfirmationDialog';
  import Content from './Content';
  import isThumbnailReady from './utils';
  import { isFocusableElement, isInputElement, focus } from '../../utils/dom';
  import { FILE_SHARED_LINK_FIELDS_TO_FETCH } from '../../utils/fields';
  import CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH from './constants';
  import LocalStore from '../../utils/LocalStore';
  import { withFeatureConsumer, withFeatureProvider, type FeatureConfig } from '../common/feature-checking';
  import {
      DEFAULT_HOSTNAME_UPLOAD,
      DEFAULT_HOSTNAME_API,
      DEFAULT_HOSTNAME_APP,
      DEFAULT_HOSTNAME_STATIC,
      DEFAULT_SEARCH_DEBOUNCE,
      SORT_ASC,
      FIELD_NAME,
      DEFAULT_ROOT,
      VIEW_SEARCH,
      VIEW_FOLDER,
      VIEW_ERROR,
      VIEW_RECENTS,
      VIEW_METADATA,
      VIEW_MODE_LIST,
      TYPE_FILE,
      TYPE_WEBLINK,
      TYPE_FOLDER,
      CLIENT_NAME_CONTENT_EXPLORER,
      DEFAULT_PAGE_NUMBER,
      DEFAULT_PAGE_SIZE,
      DEFAULT_VIEW_FILES,
      DEFAULT_VIEW_RECENTS,
      DEFAULT_VIEW_METADATA,
      ERROR_CODE_ITEM_NAME_INVALID,
      ERROR_CODE_ITEM_NAME_TOO_LONG,
      TYPED_ID_FOLDER_PREFIX,
  } from '../../constants';
  import type { ViewMode } from '../common/flowTypes';
  import type { MetadataQuery, FieldsToShow } from '../../common/types/metadataQueries';
  import type { MetadataFieldValue } from '../../common/types/metadata';
  import type {
      View,
      DefaultView,
      StringMap,
      SortBy,
      SortDirection,
      Token,
      Access,
      Collection,
      BoxItemPermission,
      BoxItem,
  } from '../../common/types/core';
  
  import '../common/fonts.scss';
  import '../common/base.scss';
  import '../common/modal.scss';
  import './ContentExplorer.scss';
  
  const GRID_VIEW_MAX_COLUMNS = 7;
  const GRID_VIEW_MIN_COLUMNS = 1;
  
  type Props = {
      apiHost: string,
      appHost: string,
      autoFocus: boolean,
      canCreateNewFolder: boolean,
      canDelete: boolean,
      canDownload: boolean,
      canPreview: boolean,
      canRename: boolean,
      canSetShareAccess: boolean,
      canShare: boolean,
      canUpload: boolean,
      className: string,
      contentPreviewProps: ContentPreviewProps,
      contentUploaderProps: ContentUploaderProps,
      currentFolderId?: string,
      defaultView: DefaultView,
      features: FeatureConfig,
      fieldsToShow?: FieldsToShow,
      initialPage: number,
      initialPageSize: number,
      isLarge: boolean,
      isMedium: boolean,
      isSmall: boolean,
      isTouch: boolean,
      isVeryLarge: boolean,
      language?: string,
      logoUrl?: string,
      measureRef?: Function,
      messages?: StringMap,
      metadataQuery?: MetadataQuery,
      onCreate: Function,
      onDelete: Function,
      onDownload: Function,
      onNavigate: Function,
      onPreview: Function,
      onRename: Function,
      onSelect: Function,
      onUpload: Function,
      previewLibraryVersion: string,
      requestInterceptor?: Function,
      responseInterceptor?: Function,
      rootFolderId: string,
      sharedLink?: string,
      sharedLinkPassword?: string,
      sortBy: SortBy,
      sortDirection: SortDirection,
      staticHost: string,
      staticPath: string,
      token: Token,
      uploadHost: string,
  };
  
  type State = {
      currentCollection: Collection,
      currentOffset: number,
      currentPageNumber: number,
      currentPageSize: number,
      errorCode: string,
      focusedRow: number,
      gridColumnCount: number,
      isCreateFolderModalOpen: boolean,
      isDeleteModalOpen: boolean,
      isLoading: boolean,
      isPreviewModalOpen: boolean,
      isRenameModalOpen: boolean,
      isShareModalOpen: boolean,
      isUploadModalOpen: boolean,
      markers: Array<?string>,
      rootName: string,
      searchQuery: string,
      selected?: BoxItem,
      sortBy: SortBy,
      sortDirection: SortDirection,
      view: View,
  };
  
  const localStoreViewMode = 'bce.defaultViewMode';
  
  class ContentExplorer extends Component<Props, State> {
      id: string;
  
      api: API;
  
      state: State;
  
      props: Props;
  
      table: any;
  
      rootElement: HTMLElement;
  
      appElement: HTMLElement;
  
      globalModifier: boolean;
  
      firstLoad: boolean = true; // Keeps track of very 1st load
  
      store: LocalStore = new LocalStore();
  
      metadataQueryAPIHelper: MetadataQueryAPIHelper;
  
      static defaultProps = {
          rootFolderId: DEFAULT_ROOT,
          sortBy: FIELD_NAME,
          sortDirection: SORT_ASC,
          canDownload: true,
          canDelete: true,
          canUpload: true,
          canRename: true,
          canShare: true,
          canPreview: true,
          canSetShareAccess: true,
          canCreateNewFolder: true,
          autoFocus: false,
          apiHost: DEFAULT_HOSTNAME_API,
          appHost: DEFAULT_HOSTNAME_APP,
          staticHost: DEFAULT_HOSTNAME_STATIC,
          uploadHost: DEFAULT_HOSTNAME_UPLOAD,
          className: '',
          onDelete: noop,
          onDownload: noop,
          onPreview: noop,
          onRename: noop,
          onCreate: noop,
          onSelect: noop,
          onUpload: noop,
          onNavigate: noop,
          defaultView: DEFAULT_VIEW_FILES,
          initialPage: DEFAULT_PAGE_NUMBER,
          initialPageSize: DEFAULT_PAGE_SIZE,
          contentPreviewProps: {
              contentSidebarProps: {},
          },
          contentUploaderProps: {},
      };
  
      /**
       * [constructor]
       *
       * @private
       * @return {ContentExplorer}
       */
      constructor(props: Props) {
          super(props);
  
          const {
              apiHost,
              initialPage,
              initialPageSize,
              language,
              requestInterceptor,
              responseInterceptor,
              rootFolderId,
              sharedLink,
              sharedLinkPassword,
              sortBy,
              sortDirection,
              token,
              uploadHost,
          }: Props = props;
  
          this.api = new API({
              apiHost,
              clientName: CLIENT_NAME_CONTENT_EXPLORER,
              id: `${TYPED_ID_FOLDER_PREFIX}${rootFolderId}`,
              language,
              requestInterceptor,
              responseInterceptor,
              sharedLink,
              sharedLinkPassword,
              token,
              uploadHost,
          });
  
          this.id = uniqueid('bce_');
  
          this.state = {
              currentCollection: {},
              currentOffset: initialPageSize * (initialPage - 1),
              currentPageSize: initialPageSize,
              currentPageNumber: 0,
              errorCode: '',
              focusedRow: 0,
              gridColumnCount: 4,
              isCreateFolderModalOpen: false,
              isDeleteModalOpen: false,
              isLoading: false,
              isPreviewModalOpen: false,
              isRenameModalOpen: false,
              isShareModalOpen: false,
              isUploadModalOpen: false,
              markers: [],
              rootName: '',
              searchQuery: '',
              sortBy,
              sortDirection,
              view: VIEW_FOLDER,
          };
      }
  
      /**
       * Destroys api instances
       *
       * @private
       * @return {void}
       */
      clearCache(): void {
          this.api.destroy(true);
      }
  
      /**
       * Cleanup
       *
       * @private
       * @inheritdoc
       * @return {void}
       */
      componentWillUnmount() {
          this.clearCache();
      }
  
      /**
       * Fetches the root folder on load
       *
       * @private
       * @inheritdoc
       * @return {void}
       */
      componentDidMount() {
          const { currentFolderId, defaultView }: Props = this.props;
          this.rootElement = ((document.getElementById(this.id): any): HTMLElement);
          this.appElement = ((this.rootElement.firstElementChild: any): HTMLElement);
  
          switch (defaultView) {
              case DEFAULT_VIEW_RECENTS:
                  this.showRecents();
                  break;
              case DEFAULT_VIEW_METADATA:
                  this.showMetadataQueryResults();
                  break;
              default:
                  this.fetchFolder(currentFolderId);
          }
      }
  
      /**
       * Fetches the current folder if different
       * from what was already fetched before.
       *
       * @private
       * @inheritdoc
       * @return {void}
       */
      componentDidUpdate({ currentFolderId: prevFolderId }: Props, prevState: State): void {
          const { currentFolderId }: Props = this.props;
          const {
              currentCollection: { id },
          }: State = prevState;
  
          if (prevFolderId === currentFolderId) {
              return;
          }
  
          if (typeof currentFolderId === 'string' && id !== currentFolderId) {
              this.fetchFolder(currentFolderId);
          }
      }
  
      /**
       * Metadata queries success callback
       *
       * @private
       * @param {Object} metadataQueryCollection - Metadata query response collection
       * @return {void}
       */
      showMetadataQueryResultsSuccessCallback = (metadataQueryCollection: Collection): void => {
          const { nextMarker } = metadataQueryCollection;
          const { currentCollection, currentPageNumber, markers }: State = this.state;
          const cloneMarkers = [...markers];
          if (nextMarker) {
              cloneMarkers[currentPageNumber + 1] = nextMarker;
          }
          this.setState({
              currentCollection: {
                  ...currentCollection,
                  ...metadataQueryCollection,
                  percentLoaded: 100,
              },
              markers: cloneMarkers,
          });
      };
  
      /**
       * Queries metadata_queries/execute API and fetches the result
       *
       * @private
       * @return {void}
       */
      showMetadataQueryResults() {
          const { metadataQuery = {} }: Props = this.props;
          const { currentPageNumber, markers }: State = this.state;
          const metadataQueryClone = cloneDeep(metadataQuery);
  
          if (currentPageNumber === 0) {
              // Preserve the marker as part of the original query
              markers[currentPageNumber] = metadataQueryClone.marker;
          }
  
          if (typeof markers[currentPageNumber] === 'string') {
              // Set marker to the query to get next set of results
              metadataQueryClone.marker = markers[currentPageNumber];
          }
  
          if (typeof metadataQueryClone.limit !== 'number') {
              // Set limit to the query for pagination support
              metadataQueryClone.limit = DEFAULT_PAGE_SIZE;
          }
          // Reset search state, the view and show busy indicator
          this.setState({
              searchQuery: '',
              currentCollection: this.currentUnloadedCollection(),
              view: VIEW_METADATA,
          });
          this.metadataQueryAPIHelper = new MetadataQueryAPIHelper(this.api);
          this.metadataQueryAPIHelper.fetchMetadataQueryResults(
              metadataQueryClone,
              this.showMetadataQueryResultsSuccessCallback,
              this.errorCallback,
          );
      }
  
      /**
       * Resets the collection so that the loading bar starts showing
       *
       * @private
       * @return {Collection}
       */
      currentUnloadedCollection(): Collection {
          const { currentCollection }: State = this.state;
          return Object.assign(currentCollection, {
              percentLoaded: 0,
          });
      }
  
      /**
       * Network error callback
       *
       * @private
       * @param {Error} error error object
       * @return {void}
       */
      errorCallback = (error: any) => {
          this.setState({
              view: VIEW_ERROR,
          });
          /* eslint-disable no-console */
          console.error(error);
          /* eslint-enable no-console */
      };
  
      /**
       * Focuses the grid and fires navigate event
       *
       * @private
       * @return {void}
       */
      finishNavigation() {
          const { autoFocus }: Props = this.props;
          const {
              currentCollection: { percentLoaded },
          }: State = this.state;
  
          // If loading for the very first time, only allow focus if autoFocus is true
          if (this.firstLoad && !autoFocus) {
              this.firstLoad = false;
              return;
          }
  
          // Don't focus the grid until its loaded and user is not already on an interactable element
          if (percentLoaded === 100 && !isFocusableElement(document.activeElement)) {
              focus(this.rootElement, '.bce-item-row');
              this.setState({ focusedRow: 0 });
          }
  
          this.firstLoad = false;
      }
  
      /**
       * Refreshing the item collection depending upon the view.
       * Navigation event is prevented.
       *
       * @private
       * @return {void}
       */
      refreshCollection = () => {
          const {
              currentCollection: { id },
              view,
              searchQuery,
          }: State = this.state;
          if (view === VIEW_FOLDER && id) {
              this.fetchFolder(id, false);
          } else if (view === VIEW_RECENTS) {
              this.showRecents(false);
          } else if (view === VIEW_SEARCH && searchQuery) {
              this.search(searchQuery);
          } else if (view === VIEW_METADATA) {
              this.showMetadataQueryResults();
          } else {
              throw new Error('Cannot refresh incompatible view!');
          }
      };
  
      /**
       * Folder fetch success callback
       *
       * @private
       * @param {Object} collection - item collection object
       * @param {Boolean|void} triggerNavigationEvent - To trigger navigate event and focus grid
       * @return {void}
       */
      fetchFolderSuccessCallback(collection: Collection, triggerNavigationEvent: boolean): void {
          const { onNavigate, rootFolderId }: Props = this.props;
          const { boxItem, id, name }: Collection = collection;
          const { selected }: State = this.state;
          const rootName = id === rootFolderId ? name : '';
  
          // Close any open modals
          this.closeModals();
  
          this.updateCollection(collection, selected, () => {
              if (triggerNavigationEvent) {
                  // Fire folder navigation event
                  this.setState({ rootName }, this.finishNavigation);
                  if (boxItem) {
                      onNavigate(cloneDeep(boxItem));
                  }
              } else {
                  this.setState({ rootName });
              }
          });
      }
  
      /**
       * Fetches a folder, defaults to fetching root folder
       *
       * @private
       * @param {string|void} [id] folder id
       * @param {Boolean|void} [triggerNavigationEvent] To trigger navigate event
       * @return {void}
       */
      fetchFolder = (id?: string, triggerNavigationEvent?: boolean = true) => {
          const { rootFolderId }: Props = this.props;
          const {
              currentCollection: { id: currentId },
              currentOffset,
              currentPageSize: limit,
              searchQuery = '',
              sortBy,
              sortDirection,
          }: State = this.state;
          const folderId: string = typeof id === 'string' ? id : rootFolderId;
          const hasFolderChanged = currentId && currentId !== folderId;
          const hasSearchQuery = !!searchQuery.trim().length;
          const offset = hasFolderChanged || hasSearchQuery ? 0 : currentOffset; // Reset offset on folder or mode change
  
          // If we are navigating around, aka not first load
          // then reset the focus to the root so that after
          // the collection loads the activeElement is not the
          // button that was clicked to fetch the folder
          if (!this.firstLoad) {
              this.rootElement.focus();
          }
  
          // Reset search state, the view and show busy indicator
          this.setState({
              searchQuery: '',
              view: VIEW_FOLDER,
              currentCollection: this.currentUnloadedCollection(),
              currentOffset: offset,
          });
  
          // Fetch the folder using folder API
          this.api.getFolderAPI().getFolder(
              folderId,
              limit,
              offset,
              sortBy,
              sortDirection,
              (collection: Collection) => {
                  this.fetchFolderSuccessCallback(collection, triggerNavigationEvent);
              },
              this.errorCallback,
              { fields: CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH, forceFetch: true },
          );
      };
  
      /**
       * Action performed when clicking on an item
       *
       * @private
       * @param {Object|string} item - the clicked box item
       * @return {void}
       */
      onItemClick = (item: BoxItem | string) => {
          // If the id was passed in, just use that
          if (typeof item === 'string') {
              this.fetchFolder(item);
              return;
          }
  
          const { id, type }: BoxItem = item;
          const { isTouch }: Props = this.props;
  
          if (type === TYPE_FOLDER) {
              this.fetchFolder(id);
              return;
          }
  
          if (isTouch) {
              return;
          }
  
          this.preview(item);
      };
  
      /**
       * Search success callback
       *
       * @private
       * @param {Object} collection item collection object
       * @return {void}
       */
      searchSuccessCallback = (collection: Collection) => {
          const { selected }: State = this.state;
  
          // Close any open modals
          this.closeModals();
  
          this.updateCollection(collection, selected);
      };
  
      /**
       * Debounced searching
       *
       * @private
       * @param {string} id folder id
       * @param {string} query search string
       * @return {void}
       */
      debouncedSearch = debounce((id: string, query: string) => {
          const { currentOffset, currentPageSize }: State = this.state;
  
          this.api
              .getSearchAPI()
              .search(id, query, currentPageSize, currentOffset, this.searchSuccessCallback, this.errorCallback, {
                  fields: CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH,
                  forceFetch: true,
              });
      }, DEFAULT_SEARCH_DEBOUNCE);
  
      /**
       * Searches
       *
       * @private
       * @param {string} query search string
       * @return {void}
       */
      search = (query: string) => {
          const { rootFolderId }: Props = this.props;
          const {
              currentCollection: { id },
              currentOffset,
              searchQuery,
          }: State = this.state;
          const folderId = typeof id === 'string' ? id : rootFolderId;
          const trimmedQuery: string = query.trim();
  
          if (!query) {
              // Cancel the debounce so we don't search on a previous query
              this.debouncedSearch.cancel();
  
              // Query was cleared out, load the prior folder
              // The prior folder is always the parent folder for search
              this.setState({ currentOffset: 0 }, () => {
                  this.fetchFolder(folderId, false);
              });
  
              return;
          }
  
          if (!trimmedQuery) {
              // Query now only has bunch of spaces
              // do nothing and but update prior state
              this.setState({
                  searchQuery: query,
              });
              return;
          }
  
          this.setState({
              currentCollection: this.currentUnloadedCollection(),
              currentOffset: trimmedQuery === searchQuery ? currentOffset : 0,
              searchQuery: query,
              selected: undefined,
              view: VIEW_SEARCH,
          });
  
          this.debouncedSearch(folderId, query);
      };
  
      /**
       * Recents fetch success callback
       *
       * @private
       * @param {Object} collection item collection object
       * @param {Boolean} triggerNavigationEvent - To trigger navigate event
       * @return {void}
       */
      recentsSuccessCallback(collection: Collection, triggerNavigationEvent: boolean) {
          if (triggerNavigationEvent) {
              this.updateCollection(collection, undefined, this.finishNavigation);
          } else {
              this.updateCollection(collection);
          }
      }
  
      /**
       * Shows recents.
       *
       * @private
       * @param {Boolean|void} [triggerNavigationEvent] To trigger navigate event
       * @return {void}
       */
      showRecents(triggerNavigationEvent: boolean = true): void {
          const { rootFolderId }: Props = this.props;
  
          // Reset search state, the view and show busy indicator
          this.setState({
              searchQuery: '',
              view: VIEW_RECENTS,
              currentCollection: this.currentUnloadedCollection(),
              currentOffset: 0,
          });
  
          // Fetch the folder using folder API
          this.api.getRecentsAPI().recents(
              rootFolderId,
              (collection: Collection) => {
                  this.recentsSuccessCallback(collection, triggerNavigationEvent);
              },
              this.errorCallback,
              { fields: CONTENT_EXPLORER_FOLDER_FIELDS_TO_FETCH, forceFetch: true },
          );
      }
  
      /**
       * Uploads
       *
       * @private
       * @param {File} file dom file object
       * @return {void}
       */
      upload = () => {
          const {
              currentCollection: { id, permissions },
          }: State = this.state;
          const { canUpload }: Props = this.props;
          if (!canUpload || !id || !permissions) {
              return;
          }
  
          const { can_upload }: BoxItemPermission = permissions;
          if (!can_upload) {
              return;
          }
  
          this.setState({
              isUploadModalOpen: true,
          });
      };
  
      /**
       * Upload success handler
       *
       * @private
       * @param {File} file dom file object
       * @return {void}
       */
      uploadSuccessHandler = () => {
          const {
              currentCollection: { id },
          }: State = this.state;
          this.fetchFolder(id, false);
      };
  
      /**
       * Changes the share access of an item
       *
       * @private
       * @param {Object} item file or folder object
       * @param {string} access share access
       * @return {void}
       */
      changeShareAccess = (access: Access) => {
          const { selected }: State = this.state;
          const { canSetShareAccess }: Props = this.props;
          if (!selected || !canSetShareAccess) {
              return;
          }
  
          const { permissions, type }: BoxItem = selected;
          if (!permissions || !type) {
              return;
          }
  
          const { can_set_share_access }: BoxItemPermission = permissions;
          if (!can_set_share_access) {
              return;
          }
  
          this.setState({ isLoading: true });
          this.api.getAPI(type).share(selected, access, (updatedItem: BoxItem) => {
              this.setState({ isLoading: false });
              this.select(updatedItem);
          });
      };
  
      /**
       * Chages the sort by and sort direction
       *
       * @private
       * @param {string} sortBy - field to sort by
       * @param {string} sortDirection - sort direction
       * @return {void}
       */
      sort = (sortBy: SortBy, sortDirection: SortDirection) => {
          const {
              currentCollection: { id },
          }: State = this.state;
          if (id) {
              this.setState({ sortBy, sortDirection }, this.refreshCollection);
          }
      };
  
      /**
       * Sets state with currentCollection updated to have items.selected properties
       * set according to the given selected param. Also updates the selected item in the
       * currentcollection. selectedItem will be set to the selected state
       * item if it is in currentCollection, otherwise it will be set to undefined.
       *
       * @private
       * @param {Collection} collection - collection that needs to be updated
       * @param {Object} [selectedItem] - The item that should be selected in that collection (if present)
       * @param {Function} [callback] - callback function that should be called after setState occurs
       * @return {void}
       */
      async updateCollection(collection: Collection, selectedItem: ?BoxItem, callback: Function = noop): Object {
          const { items = [] } = collection;
          const fileAPI = this.api.getFileAPI(false);
          const newCollection: Collection = { ...collection };
          const selectedId = selectedItem ? selectedItem.id : null;
          let newSelectedItem: ?BoxItem;
  
          const itemThumbnails = await Promise.all(
              items.map(item => {
                  return item.type === TYPE_FILE ? fileAPI.getThumbnailUrl(item) : null;
              }),
          );
  
          newCollection.items = items.map((item, index) => {
              const isSelected = item.id === selectedId;
              const currentItem = isSelected ? selectedItem : item;
              const thumbnailUrl = itemThumbnails[index];
  
              const newItem = {
                  ...currentItem,
                  selected: isSelected,
                  thumbnailUrl,
              };
  
              if (item.type === TYPE_FILE && thumbnailUrl && !isThumbnailReady(newItem)) {
                  this.attemptThumbnailGeneration(newItem);
              }
  
              // Only if selectedItem is in the current collection do we want to set selected state
              if (isSelected) {
                  newSelectedItem = newItem;
              }
  
              return newItem;
          });
          this.setState({ currentCollection: newCollection, selected: newSelectedItem }, callback);
      }
  
      /**
       * Attempts to generate a thumbnail for the given item and assigns the
       * item its thumbnail url if successful
       *
       * @param {BoxItem} item - item to generate thumbnail for
       * @return {Promise<void>}
       */
      attemptThumbnailGeneration = async (item: BoxItem): Promise<void> => {
          const entries = getProp(item, 'representations.entries');
          const representation = getProp(entries, '[0]');
  
          if (representation) {
              const updatedRepresentation = await this.api.getFileAPI(false).generateRepresentation(representation);
              if (updatedRepresentation !== representation) {
                  this.updateItemInCollection({
                      ...cloneDeep(item),
                      representations: {
                          entries: [updatedRepresentation, ...entries.slice(1)],
                      },
                  });
              }
          }
      };
  
      /**
       * Update item in this.state.currentCollection
       *
       * @param {BoxItem} newItem - item with updated properties
       * @return {void}
       */
      updateItemInCollection = (newItem: BoxItem): void => {
          const { currentCollection } = this.state;
          const { items = [] } = currentCollection;
          const newCollection = { ...currentCollection };
  
          newCollection.items = items.map(item => (item.id === newItem.id ? newItem : item));
          this.setState({ currentCollection: newCollection });
      };
  
      /**
       * Selects or unselects an item
       *
       * @private
       * @param {Object} item - file or folder object
       * @param {Function|void} [onSelect] - optional on select callback
       * @return {void}
       */
      select = (item: BoxItem, callback: Function = noop): void => {
          const { selected, currentCollection }: State = this.state;
          const { items = [] } = currentCollection;
          const { onSelect }: Props = this.props;
  
          if (item === selected) {
              callback(item);
              return;
          }
  
          const selectedItem: BoxItem = { ...item, selected: true };
  
          this.updateCollection(currentCollection, selectedItem, () => {
              onSelect(cloneDeep([selectedItem]));
              callback(selectedItem);
          });
  
          const focusedRow: number = items.findIndex((i: BoxItem) => i.id === item.id);
  
          this.setState({ focusedRow });
      };
  
      /**
       * Selects the clicked file and then previews it
       * or opens it, if it was a web link
       *
       * @private
       * @param {Object} item - file or folder object
       * @return {void}
       */
      preview = (item: BoxItem): void => {
          const { type, url }: BoxItem = item;
          if (type === TYPE_WEBLINK) {
              window.open(url);
              return;
          }
  
          this.select(item, this.previewCallback);
      };
  
      /**
       * Previews a file
       *
       * @private
       * @param {Object} item - file or folder object
       * @return {void}
       */
      previewCallback = (): void => {
          const { selected }: State = this.state;
          const { canPreview }: Props = this.props;
          if (!selected || !canPreview) {
              return;
          }
  
          const { permissions } = selected;
          if (!permissions) {
              return;
          }
  
          const { can_preview }: BoxItemPermission = permissions;
          if (!can_preview) {
              return;
          }
  
          this.setState({ isPreviewModalOpen: true });
      };
  
      /**
       * Selects the clicked file and then downloads it
       *
       * @private
       * @param {Object} item - file or folder object
       * @return {void}
       */
      download = (item: BoxItem): void => {
          this.select(item, this.downloadCallback);
      };
  
      /**
       * Downloads a file
       *
       * @private
       * @return {void}
       */
      downloadCallback = (): void => {
          const { selected }: State = this.state;
          const { canDownload, onDownload }: Props = this.props;
          if (!selected || !canDownload) {
              return;
          }
  
          const { id, permissions } = selected;
          if (!id || !permissions) {
              return;
          }
  
          const { can_download }: BoxItemPermission = permissions;
          if (!can_download) {
              return;
          }
  
          const openUrl: Function = (url: string) => {
              openUrlInsideIframe(url);
              onDownload(cloneDeep([selected]));
          };
  
          const { type }: BoxItem = selected;
          if (type === TYPE_FILE) {
              this.api.getFileAPI().getDownloadUrl(id, selected, openUrl, noop);
          }
      };
  
      /**
       * Selects the clicked file and then deletes it
       *
       * @private
       * @param {Object} item - file or folder object
       * @return {void}
       */
      delete = (item: BoxItem): void => {
          this.select(item, this.deleteCallback);
      };
  
      /**
       * Deletes a file
       *
       * @private
       * @return {void}
       */
      deleteCallback = (): void => {
          const { selected, isDeleteModalOpen }: State = this.state;
          const { canDelete, onDelete }: Props = this.props;
          if (!selected || !canDelete) {
              return;
          }
  
          const { id, permissions, parent, type }: BoxItem = selected;
          if (!id || !permissions || !parent || !type) {
              return;
          }
  
          const { id: parentId } = parent;
          const { can_delete }: BoxItemPermission = permissions;
          if (!can_delete || !parentId) {
              return;
          }
  
          if (!isDeleteModalOpen) {
              this.setState({ isDeleteModalOpen: true });
              return;
          }
  
          this.setState({ isLoading: true });
          this.api.getAPI(type).deleteItem(selected, () => {
              onDelete(cloneDeep([selected]));
              this.refreshCollection();
          });
      };
  
      /**
       * Selects the clicked file and then renames it
       *
       * @private
       * @param {Object} item - file or folder object
       * @return {void}
       */
      rename = (item: BoxItem): void => {
          this.select(item, this.renameCallback);
      };
  
      /**
       * Callback for renaming an item
       *
       * @private
       * @param {string} value new item name
       * @return {void}
       */
      renameCallback = (nameWithoutExt: string, extension: string): void => {
          const { selected, isRenameModalOpen }: State = this.state;
          const { canRename, onRename }: Props = this.props;
          if (!selected || !canRename) {
              return;
          }
  
          const { id, permissions, type }: BoxItem = selected;
          if (!id || !permissions || !type) {
              return;
          }
  
          const { can_rename }: BoxItemPermission = permissions;
          if (!can_rename) {
              return;
          }
  
          if (!isRenameModalOpen || !nameWithoutExt) {
              this.setState({ isRenameModalOpen: true, errorCode: '' });
              return;
          }
  
          const name = `${nameWithoutExt}${extension}`;
          if (!nameWithoutExt.trim()) {
              this.setState({
                  errorCode: ERROR_CODE_ITEM_NAME_INVALID,
                  isLoading: false,
              });
              return;
          }
  
          this.setState({ isLoading: true });
          this.api.getAPI(type).rename(
              selected,
              name,
              (updatedItem: BoxItem) => {
                  this.setState({ isRenameModalOpen: false });
                  this.refreshCollection();
                  this.select(updatedItem);
                  onRename(cloneDeep(selected));
              },
              ({ code }) => {
                  this.setState({ errorCode: code, isLoading: false });
              },
          );
      };
  
      /**
       * Creates a new folder
       *
       * @private
       * @return {void}
       */
      createFolder = (): void => {
          this.createFolderCallback();
      };
  
      /**
       * New folder callback
       *
       * @private
       * @param {string} name - folder name
       * @return {void}
       */
      createFolderCallback = (name?: string): void => {
          const { isCreateFolderModalOpen, currentCollection }: State = this.state;
          const { canCreateNewFolder, onCreate }: Props = this.props;
          if (!canCreateNewFolder) {
              return;
          }
  
          const { id, permissions }: Collection = currentCollection;
          if (!id || !permissions) {
              return;
          }
  
          const { can_upload }: BoxItemPermission = permissions;
          if (!can_upload) {
              return;
          }
  
          if (!isCreateFolderModalOpen || !name) {
              this.setState({ isCreateFolderModalOpen: true, errorCode: '' });
              return;
          }
  
          if (!name) {
              this.setState({
                  errorCode: ERROR_CODE_ITEM_NAME_INVALID,
                  isLoading: false,
              });
              return;
          }
  
          if (name.length > 255) {
              this.setState({
                  errorCode: ERROR_CODE_ITEM_NAME_TOO_LONG,
                  isLoading: false,
              });
              return;
          }
  
          this.setState({ isLoading: true });
          this.api.getFolderAPI().create(
              id,
              name,
              (item: BoxItem) => {
                  this.refreshCollection();
                  this.select(item);
                  onCreate(cloneDeep(item));
              },
              ({ code }) => {
                  this.setState({
                      errorCode: code,
                      isLoading: false,
                  });
              },
          );
      };
  
      /**
       * Selects the clicked file and then shares it
       *
       * @private
       * @param {Object} item - file or folder object
       * @return {void}
       */
      share = (item: BoxItem): void => {
          this.select(item, this.shareCallback);
      };
  
      /**
       * Fetch the shared link info
       * @param {BoxItem} item - The item (folder, file, weblink)
       * @returns {void}
       */
      fetchSharedLinkInfo = (item: BoxItem): void => {
          const { id, type }: BoxItem = item;
  
          switch (type) {
              case TYPE_FOLDER:
                  this.api.getFolderAPI().getFolderFields(id, this.handleSharedLinkSuccess, noop, {
                      fields: FILE_SHARED_LINK_FIELDS_TO_FETCH,
                  });
                  break;
              case TYPE_FILE:
                  this.api
                      .getFileAPI()
                      .getFile(id, this.handleSharedLinkSuccess, noop, { fields: FILE_SHARED_LINK_FIELDS_TO_FETCH });
                  break;
              case TYPE_WEBLINK:
                  this.api
                      .getWebLinkAPI()
                      .getWeblink(id, this.handleSharedLinkSuccess, noop, { fields: FILE_SHARED_LINK_FIELDS_TO_FETCH });
                  break;
              default:
                  throw new Error('Unknown Type');
          }
      };
  
      /**
       * Handles the shared link info by either creating a share link using enterprise defaults if
       * it does not already exist, otherwise update the item in the state currentCollection.
       *
       * @param {Object} item file or folder
       * @returns {void}
       */
      handleSharedLinkSuccess = (newItem: BoxItem) => {
          const { currentCollection } = this.state;
  
          // Update item in collection
          this.updateCollection(currentCollection, newItem, () => this.setState({ isShareModalOpen: true }));
      };
  
      /**
       * Chages the sort by and sort direction
       *
       * @private
       * @return {void}
       */
      shareCallback = (): void => {
          const { selected }: State = this.state;
          const { canShare }: Props = this.props;
  
          if (!selected || !canShare) {
              return;
          }
  
          const { permissions } = selected;
          if (!permissions) {
              return;
          }
  
          const { can_share }: BoxItemPermission = permissions;
          if (!can_share) {
              return;
          }
  
          this.fetchSharedLinkInfo(selected);
      };
  
      /**
       * Saves reference to table component
       *
       * @private
       * @param {Component} react component
       * @return {void}
       */
      tableRef = (table: React$Component<*, *>): void => {
          this.table = table;
      };
  
      /**
       * Closes the modal dialogs that may be open
       *
       * @private
       * @return {void}
       */
      closeModals = (): void => {
          const { focusedRow }: State = this.state;
  
          this.setState({
              isLoading: false,
              isDeleteModalOpen: false,
              isRenameModalOpen: false,
              isCreateFolderModalOpen: false,
              isShareModalOpen: false,
              isUploadModalOpen: false,
              isPreviewModalOpen: false,
          });
  
          const {
              selected,
              currentCollection: { items = [] },
          }: State = this.state;
          if (selected && items.length > 0) {
              focus(this.rootElement, `.bce-item-row-${focusedRow}`);
          }
      };
  
      /**
       * Keyboard events
       *
       * @private
       * @return {void}
       */
      onKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
          if (isInputElement(event.target)) {
              return;
          }
  
          const { rootFolderId }: Props = this.props;
          const key = event.key.toLowerCase();
  
          switch (key) {
              case '/':
                  focus(this.rootElement, '.be-search input[type="search"]', false);
                  event.preventDefault();
                  break;
              case 'arrowdown':
                  focus(this.rootElement, '.bce-item-row', false);
                  this.setState({ focusedRow: 0 });
                  event.preventDefault();
                  break;
              case 'g':
                  break;
              case 'b':
                  if (this.globalModifier) {
                      focus(this.rootElement, '.be-breadcrumb button', false);
                      event.preventDefault();
                  }
  
                  break;
              case 'f':
                  if (this.globalModifier) {
                      this.fetchFolder(rootFolderId);
                      event.preventDefault();
                  }
  
                  break;
              case 'u':
                  if (this.globalModifier) {
                      this.upload();
                      event.preventDefault();
                  }
  
                  break;
              case 'r':
                  if (this.globalModifier) {
                      this.showRecents();
                      event.preventDefault();
                  }
  
                  break;
              case 'n':
                  if (this.globalModifier) {
                      this.createFolder();
                      event.preventDefault();
                  }
  
                  break;
              default:
                  this.globalModifier = false;
                  return;
          }
  
          this.globalModifier = key === 'g';
      };
  
      /**
       * Handle pagination changes for offset based pagination
       *
       * @param {number} newOffset - the new page offset value
       */
      paginate = (newOffset: number) => {
          this.setState({ currentOffset: newOffset }, this.refreshCollection);
      };
  
      /**
       * Handle pagination changes for marker based pagination
       * @param {number} newOffset - the new page offset value
       */
      markerBasedPaginate = (newOffset: number) => {
          const { currentPageNumber } = this.state;
          this.setState(
              {
                  currentPageNumber: currentPageNumber + newOffset, // newOffset could be negative
              },
              this.refreshCollection,
          );
      };
  
      /**
       * Get the current viewMode, checking local store if applicable
       *
       * @return {ViewMode}
       */
      getViewMode = (): ViewMode => this.store.getItem(localStoreViewMode) || VIEW_MODE_LIST;
  
      /**
       * Get the maximum number of grid view columns based on the current width of the
       * content explorer.
       *
       * @return {number}
       */
      getMaxNumberOfGridViewColumnsForWidth = (): number => {
          const { isSmall, isMedium, isLarge } = this.props;
          let maxWidthColumns = GRID_VIEW_MAX_COLUMNS;
          if (isSmall) {
              maxWidthColumns = 1;
          } else if (isMedium) {
              maxWidthColumns = 3;
          } else if (isLarge) {
              maxWidthColumns = 5;
          }
          return maxWidthColumns;
      };
  
      /**
       * Change the current view mode
       *
       * @param {ViewMode} viewMode - the new view mode
       * @return {void}
       */
      changeViewMode = (viewMode: ViewMode): void => {
          this.store.setItem(localStoreViewMode, viewMode);
          this.forceUpdate();
      };
  
      /**
       * Callback for when value of GridViewSlider changes
       *
       * @param {number} sliderValue - value of slider
       * @return {void}
       */
      onGridViewSliderChange = (sliderValue: number): void => {
          // need to do this calculation since lowest value of grid view slider
          // means highest number of columns
          const gridColumnCount = GRID_VIEW_MAX_COLUMNS - sliderValue + 1;
          this.setState({ gridColumnCount });
      };
  
      /**
       * Function to update metadata field value in metadata based view
       * @param {BoxItem} item - file item whose metadata is being changed
       * @param {string} field - metadata template field name
       * @param {MetadataFieldValue} oldValue - current value
       * @param {MetadataFieldValue} newVlaue - new value the field to be updated to
       */
  
      updateMetadata = (
          item: BoxItem,
          field: string,
          oldValue: ?MetadataFieldValue,
          newValue: ?MetadataFieldValue,
      ): void => {
          this.metadataQueryAPIHelper.updateMetadata(
              item,
              field,
              oldValue,
              newValue,
              () => {
                  this.updateMetadataSuccessCallback(item, field, newValue);
              },
              this.errorCallback,
          );
      };
  
      updateMetadataSuccessCallback = (item: BoxItem, field: string, newValue: ?MetadataFieldValue): void => {
          const { currentCollection }: State = this.state;
          const { items = [], nextMarker } = currentCollection;
          const updatedItems = items.map(collectionItem => {
              const clonedItem = cloneDeep(collectionItem);
              if (item.id === clonedItem.id) {
                  const fields = getProp(clonedItem, 'metadata.enterprise.fields', []);
                  fields.forEach(itemField => {
                      if (itemField.key.split('.').pop() === field) {
                          itemField.value = newValue; // set updated metadata value to correct item in currentCollection
                      }
                  });
              }
              return clonedItem;
          });
  
          this.setState({
              currentCollection: {
                  items: updatedItems,
                  nextMarker,
                  percentLoaded: 100,
              },
          });
      };
  
      /**
       * Renders the file picker
       *
       * @private
       * @inheritdoc
       * @return {Element}
       */
      render() {
          const {
              apiHost,
              appHost,
              canCreateNewFolder,
              canDelete,
              canDownload,
              canPreview,
              canRename,
              canSetShareAccess,
              canShare,
              canUpload,
              className,
              contentPreviewProps,
              contentUploaderProps,
              defaultView,
              isMedium,
              isSmall,
              isTouch,
              language,
              logoUrl,
              measureRef,
              messages,
              fieldsToShow,
              onDownload,
              onPreview,
              onUpload,
              requestInterceptor,
              responseInterceptor,
              rootFolderId,
              sharedLink,
              sharedLinkPassword,
              staticHost,
              staticPath,
              previewLibraryVersion,
              token,
              uploadHost,
          }: Props = this.props;
  
          const {
              currentCollection,
              currentPageNumber,
              currentPageSize,
              errorCode,
              focusedRow,
              gridColumnCount,
              isCreateFolderModalOpen,
              isDeleteModalOpen,
              isLoading,
              isPreviewModalOpen,
              isRenameModalOpen,
              isShareModalOpen,
              isUploadModalOpen,
              markers,
              rootName,
              searchQuery,
              selected,
              view,
          }: State = this.state;
  
          const { id, offset, permissions, totalCount }: Collection = currentCollection;
          const { can_upload }: BoxItemPermission = permissions || {};
          const styleClassName = classNames('be bce', className);
          const allowUpload: boolean = canUpload && !!can_upload;
          const allowCreate: boolean = canCreateNewFolder && !!can_upload;
          const isDefaultViewMetadata: boolean = defaultView === DEFAULT_VIEW_METADATA;
          const isErrorView: boolean = view === VIEW_ERROR;
  
          const viewMode = this.getViewMode();
          const maxGridColumnCount = this.getMaxNumberOfGridViewColumnsForWidth();
  
          const hasNextMarker: boolean = !!markers[currentPageNumber + 1];
          const hasPreviousMarker: boolean = currentPageNumber === 1 || !!markers[currentPageNumber - 1];
  
          /* eslint-disable jsx-a11y/no-static-element-interactions */
          /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
          return (
              <Internationalize language={language} messages={messages}>
                  <div id={this.id} className={styleClassName} ref={measureRef} data-testid="content-explorer">
                      <div className="be-app-element" onKeyDown={this.onKeyDown} tabIndex={0}>
                          {!isDefaultViewMetadata && (
                              <>
                                  <Header
                                      view={view}
                                      isSmall={isSmall}
                                      searchQuery={searchQuery}
                                      logoUrl={logoUrl}
                                      onSearch={this.search}
                                  />
                                  <SubHeader
                                      view={view}
                                      viewMode={viewMode}
                                      rootId={rootFolderId}
                                      isSmall={isSmall}
                                      rootName={rootName}
                                      currentCollection={currentCollection}
                                      canUpload={allowUpload}
                                      canCreateNewFolder={allowCreate}
                                      gridColumnCount={gridColumnCount}
                                      gridMaxColumns={GRID_VIEW_MAX_COLUMNS}
                                      gridMinColumns={GRID_VIEW_MIN_COLUMNS}
                                      maxGridColumnCountForWidth={maxGridColumnCount}
                                      onUpload={this.upload}
                                      onCreate={this.createFolder}
                                      onGridViewSliderChange={this.onGridViewSliderChange}
                                      onItemClick={this.fetchFolder}
                                      onSortChange={this.sort}
                                      onViewModeChange={this.changeViewMode}
                                  />
                              </>
                          )}
                          <Content
                              canDelete={canDelete}
                              canDownload={canDownload}
                              canPreview={canPreview}
                              canRename={canRename}
                              canSetShareAccess={canSetShareAccess}
                              canShare={canShare}
                              currentCollection={currentCollection}
                              focusedRow={focusedRow}
                              gridColumnCount={Math.min(gridColumnCount, maxGridColumnCount)}
                              isMedium={isMedium}
                              isSmall={isSmall}
                              isTouch={isTouch}
                              fieldsToShow={fieldsToShow}
                              onItemClick={this.onItemClick}
                              onItemDelete={this.delete}
                              onItemDownload={this.download}
                              onItemPreview={this.preview}
                              onItemRename={this.rename}
                              onItemSelect={this.select}
                              onItemShare={this.share}
                              onMetadataUpdate={this.updateMetadata}
                              onSortChange={this.sort}
                              rootElement={this.rootElement}
                              rootId={rootFolderId}
                              tableRef={this.tableRef}
                              view={view}
                              viewMode={viewMode}
                          />
                          {!isErrorView && (
                              <Footer>
                                  <Pagination
                                      hasNextMarker={hasNextMarker}
                                      hasPrevMarker={hasPreviousMarker}
                                      offset={offset}
                                      onOffsetChange={this.paginate}
                                      pageSize={currentPageSize}
                                      totalCount={totalCount}
                                      onMarkerBasedPageChange={this.markerBasedPaginate}
                                  />
                              </Footer>
                          )}
                      </div>
                      {allowUpload && !!this.appElement ? (
                          <UploadDialog
                              isOpen={isUploadModalOpen}
                              currentFolderId={id}
                              token={token}
                              sharedLink={sharedLink}
                              sharedLinkPassword={sharedLinkPassword}
                              apiHost={apiHost}
                              uploadHost={uploadHost}
                              onClose={this.uploadSuccessHandler}
                              parentElement={this.rootElement}
                              appElement={this.appElement}
                              onUpload={onUpload}
                              contentUploaderProps={contentUploaderProps}
                              requestInterceptor={requestInterceptor}
                              responseInterceptor={responseInterceptor}
                          />
                      ) : null}
                      {allowCreate && !!this.appElement ? (
                          <CreateFolderDialog
                              isOpen={isCreateFolderModalOpen}
                              onCreate={this.createFolderCallback}
                              onCancel={this.closeModals}
                              isLoading={isLoading}
                              errorCode={errorCode}
                              parentElement={this.rootElement}
                              appElement={this.appElement}
                          />
                      ) : null}
                      {canDelete && selected && !!this.appElement ? (
                          <DeleteConfirmationDialog
                              isOpen={isDeleteModalOpen}
                              onDelete={this.deleteCallback}
                              onCancel={this.closeModals}
                              item={selected}
                              isLoading={isLoading}
                              parentElement={this.rootElement}
                              appElement={this.appElement}
                          />
                      ) : null}
                      {canRename && selected && !!this.appElement ? (
                          <RenameDialog
                              isOpen={isRenameModalOpen}
                              onRename={this.renameCallback}
                              onCancel={this.closeModals}
                              item={selected}
                              isLoading={isLoading}
                              errorCode={errorCode}
                              parentElement={this.rootElement}
                              appElement={this.appElement}
                          />
                      ) : null}
                      {canShare && selected && !!this.appElement ? (
                          <ShareDialog
                              isOpen={isShareModalOpen}
                              canSetShareAccess={canSetShareAccess}
                              onShareAccessChange={this.changeShareAccess}
                              onCancel={this.refreshCollection}
                              item={selected}
                              isLoading={isLoading}
                              parentElement={this.rootElement}
                              appElement={this.appElement}
                          />
                      ) : null}
                      {canPreview && selected && !!this.appElement ? (
                          <PreviewDialog
                              isOpen={isPreviewModalOpen}
                              isTouch={isTouch}
                              onCancel={this.closeModals}
                              item={selected}
                              currentCollection={currentCollection}
                              token={token}
                              parentElement={this.rootElement}
                              appElement={this.appElement}
                              onPreview={onPreview}
                              onDownload={onDownload}
                              canDownload={canDownload}
                              cache={this.api.getCache()}
                              apiHost={apiHost}
                              appHost={appHost}
                              staticHost={staticHost}
                              staticPath={staticPath}
                              previewLibraryVersion={previewLibraryVersion}
                              sharedLink={sharedLink}
                              sharedLinkPassword={sharedLinkPassword}
                              contentPreviewProps={contentPreviewProps}
                              requestInterceptor={requestInterceptor}
                              responseInterceptor={responseInterceptor}
                          />
                      ) : null}
                  </div>
              </Internationalize>
          );
          /* eslint-enable jsx-a11y/no-static-element-interactions */
          /* eslint-enable jsx-a11y/no-noninteractive-tabindex */
      }
  }
  
  export { ContentExplorer as ContentExplorerComponent };
  export default flow([makeResponsive, withFeatureConsumer, withFeatureProvider])(ContentExplorer);
  