import React from "react"; import { promptForFileReferences } from "../../fileUtils"; import { clamp } from "../../utils"; const ENTRY_HEIGHT = 14; const HEIGHT_PADDING = 15; const WIDTH_PADDING = 20; const LoadingState = () => (
Loading presets
); const ListWrapper = ({ width, height, children }) => (
); class PresetOverlay extends React.Component { constructor(props) { super(props); const listIndex = this._listIndexFromPresetIndex(props.currentPreset); this.state = { selectedListIndex: clamp(listIndex, 0, this._maxListIndex()) }; } componentDidMount() { this._unsubscribeFocusedKeyDown = this.props.onFocusedKeyDown( this._handleFocusedKeyboardInput ); } componentWillUnmount() { if (this._unsubscribeFocusedKeyDown) { this._unsubscribeFocusedKeyDown(); } } _handleFocusedKeyboardInput = e => { switch (e.keyCode) { case 38: // up arrow this.setState({ selectedListIndex: Math.max(this.state.selectedListIndex - 1, 0) }); e.stopPropagation(); break; case 40: // down arrow this.setState({ selectedListIndex: Math.min( this.state.selectedListIndex + 1, this._maxListIndex() ) }); e.stopPropagation(); break; case 13: // enter if (this.state.selectedListIndex === 0) { this.loadLocalDir(); } else { this.props.selectPreset( this._presetIndexFromListIndex(this.state.selectedListIndex) ); } e.stopPropagation(); break; case 27: // escape this.props.closeOverlay(); e.stopPropagation(); break; } }; _presetIndexFromListIndex(listIndex) { return listIndex - 1; } _listIndexFromPresetIndex(listIndex) { return listIndex + 1; } _maxListIndex() { // Number of presets, plus one for the "Load Local Directory" option, minus // one to convert a length to an index. return this.props.presetKeys.length; // - 1 + 1; } async loadLocalDir() { const fileReferences = await promptForFileReferences({ directory: true }); // TODO: Technically there is a race condition here, since the component // could get unmounted before the promise resolves. this.props.loadPresets(fileReferences); } _renderList() { const { presetKeys, currentPreset, height, width } = this.props; const { selectedListIndex } = this.state; const maxVisibleRows = Math.floor((height - HEIGHT_PADDING) / ENTRY_HEIGHT); const rowsToShow = Math.floor(maxVisibleRows * 0.75); // Only fill 3/4 of the screen. const [startIndex, endIndex] = getRangeCenteredOnIndex( this._maxListIndex() + 1, // Add one to convert an index to a length rowsToShow, selectedListIndex ); const presetElms = []; for (let i = startIndex; i <= endIndex; i++) { const presetIndex = this._presetIndexFromListIndex(i); const isSelected = i === selectedListIndex; const isCurrent = presetIndex === currentPreset; let color; if (isSelected) { color = isCurrent ? "#FFCC22" : "#FF5050"; } else { color = isCurrent ? "#CCFF03" : "#CCCCCC"; } presetElms.push(
  • {i === 0 ? "Load Local Directory" : presetKeys[presetIndex]}
  • ); } return ( {presetElms} ); } render() { return this.props.presetKeys != null ? ( this._renderList() ) : ( ); } } // Find a tuple `[startIndex, endIndex]` representing start/end indexes into an // array of length `length`, that descripe a range of size up to `rangeSize` // where a best effort is made to center `indexToCenter`. export function getRangeCenteredOnIndex(length, maxRangeSize, indexToCenter) { const rangeSize = Math.min(length, maxRangeSize); const halfRangeSize = Math.floor(rangeSize / 2); const idealStartIndex = indexToCenter - halfRangeSize; const startIndex = clamp(idealStartIndex, 0, length - rangeSize); const endIndex = startIndex + rangeSize - 1; return [startIndex, endIndex]; } export default PresetOverlay;