SHARE
TWEET

Untitled

a guest May 27th, 2019 80 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import React, { HTMLProps } from 'react';
  2. import { ClipboardEvent, Component, ComponentClass, FunctionComponent } from 'react';
  3. import { connect } from 'react-redux';
  4. import { styled } from 'styletron-react';
  5.  
  6. import { withTheme } from '@benzinga/themetron';
  7. import DatePicker from 'antd/lib/date-picker';
  8. import { RangePickerValue } from 'antd/lib/date-picker/interface';
  9. import Radio from 'antd/lib/radio';
  10. import { RadioChangeEvent } from 'antd/lib/radio/interface';
  11. import Select from 'antd/lib/select';
  12. import Slider from 'antd/lib/slider';
  13. import invariant from 'invariant';
  14. import moment from 'moment-timezone';
  15. import { addIndex, contains, filter, find, isEmpty, isNil, join, map, pipe, prop, propEq, reduce } from 'ramda';
  16.  
  17. import { moversUpdateParameters } from '../../../../actions/moversActions';
  18. import { sagaScreenerCheckAutoRefresh, sagaScreenerRequestMovers } from '../../../../actions/sagaTriggers';
  19. import {
  20.   ChevronDownIcon,
  21.   ChevronRightIcon,
  22.   DebugIcon,
  23.   TrendingDownIcon,
  24.   TrendingUpIcon
  25. } from '../../../../assets/icons/';
  26. import { MarketSession } from '../../../../entities/marketEntity';
  27. import { RootState } from '../../../../entities/rootEntity';
  28. import { WidgetId } from '../../../../entities/widgetEntity';
  29. import { MoversWidgetParameters, MoversWidgetTransient } from '../../../../entities/widgetEntity/screenerWidgetEntity';
  30. import { selectDebugMode } from '../../../../selectors/appSelectors';
  31. import { selectWidgetById } from '../../../../selectors/widgetSelectors';
  32. import { Column, ColumnStyles, Div, Inline, InlineStyles, RowStyles } from '../../../../styles/StyledHOCs';
  33. import { MECSSectorCode, NBSP } from '../../../../utils/global';
  34. import {
  35.   createMoversURL,
  36.   intraSessionIntervals,
  37.   marketCapMarks,
  38.   moversSectorOptions,
  39.   moversTimeFormat,
  40.   outOfSessionIntervals,
  41.   priceMarks,
  42. } from '../../../../utils/moversUtils';
  43. import NoResults from '../../../search/NoResults';
  44. import Spinner from '../../../ui/Spinner';
  45. import MoversGrid from './MoversGrid';
  46.  
  47. export interface OwnProps {
  48.   widgetId: WidgetId;
  49.   withQuoteSubscription(props: string[]): Component<any>; // TODO: type more accurately
  50. }
  51.  
  52. export interface ReduxState {
  53.   debugMode: boolean;
  54.   parameters: MoversWidgetParameters;
  55.   transient: MoversWidgetTransient;
  56. }
  57.  
  58. export interface DispatchableActions {
  59.   moversUpdateParameters: typeof moversUpdateParameters;
  60.   sagaScreenerCheckAutoRefresh: typeof sagaScreenerCheckAutoRefresh;
  61.   sagaScreenerRequestMovers: typeof sagaScreenerRequestMovers,
  62. }
  63.  
  64. type Props = Readonly<OwnProps & ReduxState & DispatchableActions>;
  65.  
  66. const reduceIndexed: <TResult, T>
  67.   (func: (acc: TResult, property: T, index: number) => TResult, acc: TResult, values: T[]) =>
  68.     TResult = addIndex(reduce);
  69.  
  70. const { Button: RadioButton, Group: RadioGroup } = Radio;
  71. const { Option } = Select;
  72. const { RangePicker } = DatePicker;
  73.  
  74. const relativeTimeFormat = {
  75.   lastDay: '[Yesterday]',
  76.   lastWeek: 'dddd, MMMM Do YYYY',
  77.   nextDay: '[Tomorrow]',
  78.   nextWeek: 'dddd, MMMM Do YYYY',
  79.   sameDay: '[Today]',
  80.   sameElse: 'dddd, MMMM Do YYYY',
  81. };
  82.  
  83. const Parameter = styled('div', {
  84.   ...RowStyles,
  85.   alignItems: 'center',
  86.   flexWrap: 'wrap',
  87.   padding: '0.5em 1.5em',
  88. });
  89.  
  90. const ParameterLabel = styled('div', {
  91.   ...RowStyles,
  92.   alignItems: 'center',
  93.   display: 'inline-flex',
  94.   margin: '0.5em 0',
  95.   minWidth: '100px',
  96.   whiteSpace: 'nowrap',
  97. });
  98.  
  99. const DatePickerContainer = styled('div', {
  100.   ...InlineStyles,
  101.   paddingTop: '0.25em',
  102. });
  103.  
  104. const SliderContainer = styled('div', {
  105.   ...InlineStyles,
  106.   flexGrow: 1,
  107.   height: '30px',
  108.   marginTop: '-20px',
  109.   maxWidth: '300px',
  110.   width: '100%',
  111. });
  112.  
  113. const MoversContainer = styled('div', {
  114.   ...ColumnStyles,
  115.   flex: 1,
  116.   overflowX: 'hidden',
  117.   overflowY: 'auto',
  118. });
  119.  
  120. const WidgetBody = styled('div', {
  121.   ...ColumnStyles,
  122.   flex: 1,
  123.   overflow: 'auto',
  124. });
  125.  
  126. const StyledSelect = styled(Select, {
  127.   minWidth: '225px',
  128. });
  129.  
  130. interface ParametersHeaderProps {
  131.   $expanded: boolean;
  132.   onClick(): void;
  133. }
  134.  
  135. const ParametersHeader = withTheme<ParametersHeaderProps, HTMLProps<HTMLDivElement>>((props, theme) => ({
  136.   ...InlineStyles,
  137.   backgroundColor: props.$expanded ? theme.colors.brandMuted : 'transparent',
  138.   color: props.$expanded ? theme.colors.brandForeground : theme.colors.foregroundInactive,
  139.   cursor: 'pointer',
  140.   fill: theme.colors.foregroundInactive,
  141.   padding: '0.5em',
  142. }))(Div);
  143.  
  144. interface MoversParametersProps {
  145.   $expanded: boolean;
  146. }
  147.  
  148. const MoversParameters = withTheme<MoversParametersProps, HTMLProps<HTMLDivElement>>((props, theme) => ({
  149.   borderBottom: props.$expanded ? `2px solid ${theme.colors.brandMuted}` : 'none',
  150.   borderLeft: `2px solid ${theme.colors.brandMuted}`,
  151.   borderRight: props.$expanded ? `2px solid ${theme.colors.brandMuted}` : 'none',
  152.   borderTop: props.$expanded ? `2px solid ${theme.colors.brandMuted}` : 'none',
  153.   flex: '0 0 auto',
  154. }))(Column);
  155.  
  156. const Emphasis = withTheme((_, theme) => ({
  157.   color: theme.colors.foregroundActive,
  158.   whiteSpace: 'nowrap',
  159. }))(Inline);
  160.  
  161. const DatesContainerLabel = withTheme((_, theme) => ({
  162.   color: theme.colors.foregroundInactive,
  163.   fontSize: '0.825em',
  164.   marginTop: '0.25em',
  165. }))(Div);
  166.  
  167. const GreenText = withTheme((_, theme) => ({
  168.   color: theme.colors.statistic.positive,
  169.   fill: theme.colors.statistic.positive,
  170. }))(Inline);
  171.  
  172. const RedText = withTheme((_, theme) => ({
  173.   color: theme.colors.statistic.negative,
  174.   fill: theme.colors.statistic.negative,
  175. }))(Inline);
  176.  
  177. const StyledTrendingUpIcon = withTheme((_, theme) => ({
  178.   fill: theme.colors.statistic.positive,
  179. }))(TrendingUpIcon);
  180.  
  181. const StyledTrendingDownIcon = withTheme((_, theme) => ({
  182.   fill: theme.colors.statistic.negative,
  183. }))(TrendingDownIcon);
  184.  
  185. const chevronIconEnhancer = withTheme((_, theme) => ({
  186.   fill: theme.colors.brandForeground,
  187.   marginRight: '0.25em',
  188. }));
  189.  
  190. const StyledChevronDownIcon = chevronIconEnhancer(ChevronDownIcon);
  191. const StyledChevronRightIcon = chevronIconEnhancer(ChevronRightIcon);
  192.  
  193. const StyledSpinner = styled(Spinner, {
  194.   display: 'inline-flex',
  195.   fontSize: '90%',
  196.   marginLeft: '0.5em',
  197. });
  198.  
  199. const marketCapTuple: ['minimumMarketCap', 'maximumMarketCap'] = ['minimumMarketCap', 'maximumMarketCap'];
  200. const priceTuple: ['minimumPrice', 'maximumPrice'] = ['minimumPrice', 'maximumPrice'];
  201.  
  202. class Movers extends Component<Props> {
  203.   componentDidMount() {
  204.     const { sagaScreenerCheckAutoRefresh } = this.props;
  205.  
  206.     this.loadMoversData();
  207.     sagaScreenerCheckAutoRefresh();
  208.   }
  209.  
  210.   componentWillUnmount() {
  211.     const { sagaScreenerCheckAutoRefresh } = this.props;
  212.  
  213.     sagaScreenerCheckAutoRefresh();
  214.   }
  215.  
  216.   loadMoversData = () => {
  217.     const {
  218.       sagaScreenerRequestMovers,
  219.       widgetId,
  220.     } = this.props;
  221.  
  222.     sagaScreenerRequestMovers(widgetId);
  223.   };
  224.  
  225.   updateParameters = (parameter: Partial<MoversWidgetParameters>) => () => {
  226.     const {
  227.       moversUpdateParameters,
  228.       parameters: {
  229.         interval,
  230.         session,
  231.       },
  232.       sagaScreenerCheckAutoRefresh,
  233.       widgetId,
  234.     } = this.props;
  235.  
  236.     let parameters: Partial<MoversWidgetParameters> = {
  237.       ...parameter,
  238.     };
  239.  
  240.     const updatedSession = Boolean(parameters.session);
  241.     if (updatedSession && session === 'REGULAR') {
  242.       const isIntervalOutOfSession = !find(propEq('id', interval), outOfSessionIntervals);
  243.       if (isIntervalOutOfSession) {
  244.         parameters = {
  245.           ...parameters,
  246.           interval: 'session',
  247.         };
  248.       }
  249.     }
  250.  
  251.     moversUpdateParameters(widgetId, parameters);
  252.  
  253.     // If updating autorefresh property, check if the autorefreshing saga should start/stop
  254.     if (!isNil(parameters.autoRefresh)) {
  255.       sagaScreenerCheckAutoRefresh();
  256.     }
  257.  
  258.     // Dont load movers data if turning autoRefresh off or expanding/minimising filtersExpanded
  259.     const dontLoadMoversData = parameters.autoRefresh === false || !isNil(parameters.filtersExpanded);
  260.     if (dontLoadMoversData) {
  261.       return;
  262.     }
  263.  
  264.     this.loadMoversData();
  265.   };
  266.  
  267.   onPaste = (nextDate: 'fromDate' | 'toDate') => (event: ClipboardEvent<HTMLElement>) => {
  268.     event.preventDefault();
  269.     const clipData = event.clipboardData.getData('text');
  270.     const momentDate = moment(clipData).format('MM/DD/YYYY');
  271.     if (momentDate === 'Invalid date') {
  272.       // eslint-disable-next-line
  273.       return;
  274.     }
  275.   };
  276.  
  277.   onDayPickerInputChange = (nextDate: 'fromDate' | 'toDate') => (date, { disabled }) => {
  278.     if (disabled || !date) {
  279.       return;
  280.     }
  281.  
  282.     const dateToMoment = moment(date);
  283.  
  284.     this.updateParameters({
  285.       interval: 'custom',
  286.       [nextDate]: dateToMoment.format(moversTimeFormat),
  287.     })();
  288.   };
  289.  
  290.   renderCollapsedFilterMenu() {
  291.     const {
  292.       parameters: {
  293.         gainersLosersDir,
  294.         interval,
  295.         session,
  296.         fromDate,
  297.         toDate,
  298.       },
  299.       transient: {
  300.         loading,
  301.       },
  302.     } = this.props;
  303.  
  304.     const emphasize = (input: string | JSX.Element | null) => (
  305.       <Emphasis>{input}</Emphasis>
  306.     );
  307.  
  308.     const gainersBlurb = <GreenText><StyledTrendingUpIcon /> Gainers</GreenText>;
  309.     const losersBlurb = <RedText><StyledTrendingDownIcon /> Losers</RedText>;
  310.  
  311.     const screenerType = {
  312.       both: <span>{gainersBlurb}{' & '}{losersBlurb}</span>,
  313.       gainers: gainersBlurb,
  314.       losers: losersBlurb,
  315.     }[gainersLosersDir];
  316.  
  317.     const sessionType = {
  318.       AFTER_HOURS:  <span>of the {emphasize(MarketSession.afterHours)} session</span>,
  319.       PRE_MARKET:   <span>of the {emphasize(MarketSession.preMarket)}</span>,
  320.       REGULAR:      <span>of the {emphasize(MarketSession.regular)} session</span>,
  321.     }[session];
  322.  
  323.     // TODO it would be great to statically type the keys of this object against MoversInterval once typescript 2.7 drops
  324.     const dateRange = {
  325.       '-15m':    <span>over the last {emphasize('15 minutes')}</span>,
  326.       '-180d':   <span>over the last {emphasize('6 months')}</span>,
  327.       '-1w':     <span>over the last {emphasize('week')}</span>,
  328.       '-30d':    <span>over the last {emphasize('month')}</span>,
  329.       '-30m':    <span>over the last {emphasize('30 minutes')}</span>,
  330.       '-60m':    <span>over the last {emphasize('hour')}</span>,
  331.       '-90d':    <span>over last {emphasize('3 months')}</span>,
  332.       YTD:     <span>over the {emphasize('Year to Date')}</span>,
  333.       custom:  (
  334.         <span>
  335.           from {emphasize(moment(fromDate || '').format('MM/DD/YYYY'))}
  336.           {NBSP}to {emphasize(moment(toDate || '').format('MM/DD/YYYY'))}
  337.         </span>
  338.       ),
  339.       session: <span>over this {emphasize('session')}</span>,
  340.     }[interval];
  341.  
  342.     return (
  343.       <span>
  344.         : {screenerType}
  345.         {' | '}{emphasize(this.getSectorsLabel())}
  346.         {' | '}{dateRange}{NBSP}{sessionType}
  347.         {loading && <StyledSpinner />}
  348.       </span>
  349.     );
  350.   }
  351.  
  352.   getSectorsLabel() {
  353.     const { sectors } = this.props.parameters;
  354.     let sectorLabel: string | null = null;
  355.     const matchedSectors = filter(
  356.       option => contains(option.id, sectors),
  357.       moversSectorOptions,
  358.     );
  359.     if (!isEmpty(matchedSectors)) {
  360.       sectorLabel = pipe(
  361.         map(prop('name')),
  362.         join(', '),
  363.       )(matchedSectors);
  364.     }
  365.     return sectorLabel;
  366.   }
  367.  
  368.   // takes a tuple of keys, which are then updated by the rc-slider's onAfterChange callback
  369.   handleRangeChange =
  370.     <T extends [keyof MoversWidgetParameters, keyof MoversWidgetParameters], K extends [number, number]>
  371.     (properties: T) => (values: K) => {
  372.     // tslint:disable-next-line:max-line-length
  373.     const parameters: Partial<MoversWidgetParameters> = reduceIndexed<Partial<MoversWidgetParameters>, keyof MoversWidgetParameters>(
  374.       (accumulator: Partial<MoversWidgetParameters>, property: keyof MoversWidgetParameters, index: number) => {
  375.         accumulator[property] = values[index];
  376.         return accumulator;
  377.       },
  378.       {},
  379.       properties,
  380.     );
  381.     this.updateParameters(parameters)();
  382.   };
  383.  
  384.   handleGainersLosersChange = (event: RadioChangeEvent) => {
  385.     const { target: { value } } = event;
  386.     invariant(
  387.       value === 'gainers' ||
  388.       value === 'losers' ||
  389.       value === 'both',
  390.       'Invalid value for gainers/losers radio group',
  391.     );
  392.     const gainersLosersDir = value as 'gainers' | 'losers' | 'both';
  393.  
  394.     this.updateParameters({ gainersLosersDir })();
  395.   };
  396.  
  397.   handleSessionChange = (event: RadioChangeEvent) => {
  398.     const { target: { value } } = event;
  399.     invariant(
  400.       value === 'PRE_MARKET' ||
  401.       value === 'REGULAR' ||
  402.       value === 'AFTER_HOURS',
  403.       'Invalid value for session type radio group',
  404.     );
  405.     const session = value as 'PRE_MARKET' | 'REGULAR' | 'AFTER_HOURS';
  406.  
  407.     this.updateParameters({ session })();
  408.   };
  409.  
  410.   handleIntervalChange = (event: RadioChangeEvent) => {
  411.     this.updateParameters({ interval: event.target.value })();
  412.   };
  413.  
  414.   handleAutoRefreshChange = (event: RadioChangeEvent) => {
  415.     const autoRefresh = Boolean(event.target.value);
  416.     this.updateParameters({ autoRefresh })();
  417.   };
  418.  
  419.   handleRangePickerChange = (date: RangePickerValue) => {
  420.     const [fromDate, toDate] = date;
  421.  
  422.     if (fromDate && toDate) {
  423.       this.updateParameters({
  424.         fromDate: fromDate.format(moversTimeFormat),
  425.         toDate: toDate.format(moversTimeFormat),
  426.       })();
  427.     }
  428.   };
  429.  
  430.   handleSectorFiltersChange = (sectors: MECSSectorCode[]) => {
  431.     this.updateParameters({ sectors })();
  432.   };
  433.  
  434.   renderFullMenu() {
  435.     const {
  436.       debugMode,
  437.       parameters: {
  438.         autoRefresh,
  439.         fromDate,
  440.         gainersLosersDir,
  441.         interval,
  442.         maximumMarketCap,
  443.         maximumPrice,
  444.         minimumMarketCap,
  445.         minimumPrice,
  446.         sectors,
  447.         session,
  448.         toDate,
  449.       },
  450.     } = this.props;
  451.  
  452.     const fromMoment = moment.tz(fromDate, 'America/New_York');
  453.     const toMoment = moment.tz(toDate, 'America/New_York');
  454.  
  455.     let debugZone;
  456.  
  457.     const gainersLosersFilter = (
  458.       <Parameter>
  459.         <ParameterLabel className="TUTORIAL_ScreenerFilters_Movers">Movers</ParameterLabel>
  460.         <RadioGroup
  461.           defaultValue={gainersLosersDir}
  462.           onChange={this.handleGainersLosersChange}
  463.           size="small"
  464.         >
  465.           <RadioButton value="both">Gainers &amp; Losers</RadioButton>
  466.           <RadioButton value="gainers">Gainers</RadioButton>
  467.           <RadioButton value="losers">Losers</RadioButton>
  468.         </RadioGroup>
  469.  
  470.       </Parameter>
  471.     );
  472.  
  473.     const sessionFilters = (
  474.       <Parameter>
  475.         <ParameterLabel className="TUTORIAL_ScreenerFilters_Session">Session</ParameterLabel>
  476.         <RadioGroup
  477.           defaultValue={session}
  478.           onChange={this.handleSessionChange}
  479.           size="small"
  480.         >
  481.           <RadioButton value="PRE_MARKET">{MarketSession.preMarket}</RadioButton>
  482.           <RadioButton value="REGULAR">{MarketSession.regular}</RadioButton>
  483.           <RadioButton value="AFTER_HOURS">{MarketSession.afterHours}</RadioButton>
  484.         </RadioGroup>
  485.  
  486.       </Parameter>
  487.     );
  488.  
  489.     const renderCustomDateInputs = () => {
  490.       const shouldShowCustomDateInputs = interval === 'custom';
  491.  
  492.       const presentDateTime = (theMoment: moment.Moment, relativeTime?: boolean) => (
  493.         <span>
  494.           {relativeTime ? theMoment.calendar(moment(), relativeTimeFormat) : theMoment.format('MMMM Do YYYY')}
  495.           {NBSP}at{NBSP}
  496.           <Emphasis>{theMoment.format('h:mma')}</Emphasis>
  497.         </span>
  498.       );
  499.  
  500.       if (!shouldShowCustomDateInputs) {
  501.         return (
  502.           <DatesContainerLabel>
  503.             comparing {presentDateTime(fromMoment, true)} to {presentDateTime(toMoment, true)}
  504.           </DatesContainerLabel>
  505.         );
  506.       }
  507.  
  508.       const disabledDates = current => current > moment().endOf('day');
  509.  
  510.       return (
  511.         <DatePickerContainer>
  512.           <RangePicker
  513.             disabledDate={disabledDates}
  514.             format={'MM/DD/YYYY'}
  515.             onChange={this.handleRangePickerChange}
  516.             size="small"
  517.             value={[moment(fromMoment, 'MM/DD/YYYY'), moment(toMoment, 'MM/DD/YYYY')]}
  518.           />
  519.  
  520.           <DatesContainerLabel>
  521.             {presentDateTime(fromMoment)} to {presentDateTime(toMoment)}
  522.           </DatesContainerLabel>
  523.         </DatePickerContainer>
  524.       );
  525.     };
  526.  
  527.     const period = {
  528.       AFTER_HOURS: outOfSessionIntervals,
  529.       PRE_MARKET: outOfSessionIntervals,
  530.       REGULAR: intraSessionIntervals,
  531.     }[session];
  532.  
  533.     const intervalFilter = (
  534.       <Parameter>
  535.         <ParameterLabel className="TUTORIAL_ScreenerFilters_Period">Period</ParameterLabel>
  536.         <Column>
  537.           <RadioGroup
  538.             onChange={this.handleIntervalChange}
  539.             size="small"
  540.             value={interval}
  541.           >
  542.             {map(
  543.               ({ id, label }) => <RadioButton key={id} value={id}>{label}</RadioButton>,
  544.               period,
  545.             )}
  546.           </RadioGroup>
  547.           {fromDate && renderCustomDateInputs()}
  548.         </Column>
  549.       </Parameter>
  550.     );
  551.  
  552.     const sectorsFilter = (
  553.       <Parameter>
  554.         <ParameterLabel className="TUTORIAL_ScreenerFilters_Sectors">Sectors</ParameterLabel>
  555.         <StyledSelect
  556.           allowClear
  557.           filterOption
  558.           mode="multiple"
  559.           notFoundContent="Sector Not Found"
  560.           onChange={this.handleSectorFiltersChange}
  561.           optionFilterProp="children"
  562.           placeholder="All Sectors"
  563.           value={sectors}
  564.         >
  565.           {map(
  566.             ({ name, id }) => <Option key={id}>{name}</Option>,
  567.             moversSectorOptions,
  568.           )}
  569.         </StyledSelect>
  570.       </Parameter>
  571.     );
  572.  
  573.     const marketCapFilter = (
  574.       <Parameter>
  575.         <ParameterLabel className="TUTORIAL_ScreenerFilters_MarketCap">Market Cap ($)</ParameterLabel>
  576.         <SliderContainer>
  577.           <Slider
  578.             defaultValue={[minimumMarketCap, maximumMarketCap]}
  579.             dots
  580.             marks={marketCapMarks}
  581.             max={5}
  582.             min={0}
  583.             onAfterChange={this.handleRangeChange(marketCapTuple)}
  584.             range
  585.             step={1}
  586.             tipFormatter={null}
  587.           />
  588.         </SliderContainer>
  589.       </Parameter>
  590.     );
  591.  
  592.     const priceFilter = (
  593.       <Parameter>
  594.         <ParameterLabel className="TUTORIAL_ScreenerFilters_Price">Price ($)</ParameterLabel>
  595.         <SliderContainer>
  596.           <Slider
  597.             defaultValue={[minimumPrice, maximumPrice]}
  598.             dots
  599.             marks={priceMarks}
  600.             max={6}
  601.             min={0}
  602.             onAfterChange={this.handleRangeChange(priceTuple)}
  603.             range
  604.             step={1}
  605.             tipFormatter={null}
  606.           />
  607.         </SliderContainer>
  608.       </Parameter>
  609.     );
  610.  
  611.     const autoRefreshFilter = (
  612.       <Parameter>
  613.         <ParameterLabel className="TUTORIAL_ScreenerFilters_Refresh">Refresh (1 min)</ParameterLabel>
  614.         <RadioGroup
  615.           defaultValue={autoRefresh}
  616.           onChange={this.handleAutoRefreshChange}
  617.           size="small"
  618.         >
  619.           <RadioButton value>Auto Refresh</RadioButton>
  620.           <RadioButton value={false}>Freeze</RadioButton>
  621.         </RadioGroup>
  622.       </Parameter>
  623.     );
  624.  
  625.     if (debugMode) {
  626.       const url = createMoversURL(this.props.parameters);
  627.       debugZone = (
  628.         <Parameter>
  629.           <ParameterLabel>Debug <DebugIcon /></ParameterLabel>
  630.           <a href={url} target="_blank" rel="noopener noreferrer">{url}</a>
  631.         </Parameter>
  632.       );
  633.     }
  634.  
  635.     return (
  636.       <Column>
  637.         {gainersLosersFilter}
  638.         {sessionFilters}
  639.         {intervalFilter}
  640.         {sectorsFilter}
  641.         {marketCapFilter}
  642.         {priceFilter}
  643.         {autoRefreshFilter}
  644.         {debugZone}
  645.       </Column>
  646.     );
  647.   }
  648.  
  649.   renderContent = () => {
  650.     const {
  651.       transient: {
  652.         error,
  653.         instruments,
  654.         loading,
  655.       },
  656.       withQuoteSubscription,
  657.     } = this.props;
  658.  
  659.     if (error) {
  660.       return <NoResults hideSearchTips message={error} />;
  661.     }
  662.  
  663.     // on first load, instruments will be null in the absence of an error.
  664.     if (loading && (isEmpty(instruments) || isNil(instruments))) {
  665.       return <Spinner />;
  666.     }
  667.  
  668.     return (
  669.       <MoversGrid
  670.         rowData={instruments}
  671.         withQuoteSubscription={withQuoteSubscription}
  672.       />
  673.     );
  674.   };
  675.  
  676.   render() {
  677.     const {
  678.       parameters: {
  679.         filtersExpanded,
  680.       },
  681.     } = this.props;
  682.  
  683.     const header = (
  684.       <ParametersHeader
  685.         className="TUTORIAL_Screener-FilterMenu"
  686.         onClick={this.updateParameters({ filtersExpanded: !filtersExpanded })}
  687.         $expanded={filtersExpanded}
  688.       >
  689.         {filtersExpanded ? <StyledChevronDownIcon /> : <StyledChevronRightIcon />}
  690.         Applied Filters{!filtersExpanded && this.renderCollapsedFilterMenu()}
  691.       </ParametersHeader>
  692.     );
  693.  
  694.     return (
  695.       <MoversContainer>
  696.         <MoversParameters $expanded={filtersExpanded}>
  697.           {header}
  698.           {filtersExpanded && this.renderFullMenu()}
  699.         </MoversParameters>
  700.         <WidgetBody>
  701.           {this.renderContent()}
  702.         </WidgetBody>
  703.       </MoversContainer>
  704.     );
  705.   }
  706. }
  707.  
  708. const mapStateToProps = (state: RootState, ownProps: Props): ReduxState => {
  709.   const { data: { parameters, transient } } = selectWidgetById(state, ownProps);
  710.  
  711.   return {
  712.     debugMode: selectDebugMode(state),
  713.     parameters,
  714.     transient,
  715.   };
  716. };
  717.  
  718. const mapDispatchToProps = {
  719.   moversUpdateParameters,
  720.   sagaScreenerCheckAutoRefresh,
  721.   sagaScreenerRequestMovers,
  722. };
  723.  
  724. export type ExternalProps = Pick<OwnProps & ReduxState & DispatchableActions, 'widgetId' | 'withQuoteSubscription'>;
  725. const MoversConnect: ComponentClass<ExternalProps> & {
  726.   WrappedComponent: ComponentClass<OwnProps & ReduxState & DispatchableActions> |
  727.   FunctionComponent<OwnProps & ReduxState & DispatchableActions>;
  728. } = connect<ReduxState, DispatchableActions, OwnProps, RootState>(mapStateToProps, mapDispatchToProps)(Movers);
  729.  
  730. export default MoversConnect;
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top