react-use#useWindowSize JavaScript Examples

The following examples show how to use react-use#useWindowSize. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.js    From ncovis-2020 with MIT License 5 votes vote down vote up
function Overview({ overview, getOverview }) {
  const { height } = useWindowSize();
  const [tab, setTab] = useState("全球");
  const { data, time } = overview;
  const formate = (time) => {
    const date = new Date(time);
    return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
  };

  const colorByName = {
    累计确诊: "#F9345e",
    累计死亡: "#6236ff",
    累计治愈: "#1cb142",
    现存确诊: "#fa6400",
  };

  useEffect(() => {
    getOverview();
  }, [getOverview]);

  return (
    <Container height={height}>
      <An id="overview" />
      <DashBoard>
        <Title>COVID-19 舆论新闻可视化</Title>
        <Tabs
          onChange={setTab}
          activeKey={tab}
          style={{ width: 500, maxWidth: "100%" }}
        >
          {data.map((d) => (
            <TabPane tab={d.name} key={d.name}>
              <Box>
                <CardContainer>
                  {d.data.map(({ name, value, change }) => (
                    <Card key={name}>
                      <Change color={colorByName[name]}>
                        {change >= 0 ? "新增" : "减少"}
                        <span>
                          {change >= 0 ? "+" : "-"}
                          {Math.abs(change)}
                        </span>
                      </Change>
                      <Value color={colorByName[name]}>{value}</Value>
                      <Name>{d.name + name}</Name>
                    </Card>
                  ))}
                </CardContainer>
                <Time>{`截至${formate(time)}, ${
                  d.name === "全国" ? "全国累计(含港澳台地区)" : "全球数据"
                }`}</Time>
              </Box>
            </TabPane>
          ))}
        </Tabs>
      </DashBoard>
      <Row>
        <Zhihu src={zhihuURL} />
        <China src={chinaURL} />
      </Row>
    </Container>
  );
}
Example #2
Source File: Player.jsx    From xetera.dev with MIT License 4 votes vote down vote up
export function Player() {
  const token = useRef()
  const win = useWindowSize(900)
  const [authorized, setAuthorized] = useState(
    () => Cookie.get(SPOTIFY_STATUS) === "true"
  )
  const [volume, setVolume] = useLocalStorage("spotifyVolume", 0.3)
  const data = useStaticQuery(query)

  const getOAuthToken = useCallback(callback => {
    const tokenStr = Cookie.get(SPOTIFY_STATUS)

    if (!tokenStr) {
      return
    }
    if (token.current) {
      return callback(token.current)
    }
    refreshToken()
      .then(a => {
        callback(a)
      })
      .catch(() => {
        Cookie.remove(SPOTIFY_STATUS)
      })
  }, [])

  if (typeof window !== "undefined" && win.width < 990) {
    return null
  }

  function logout() {
    token.current = undefined
    Cookie.remove(SPOTIFY_STATUS)
    setAuthorized(false)
  }
  async function refresh() {
    return fetch("/.netlify/functions/spotify", {
      method: "PUT",
    }).then(r => r.json())
  }
  async function refreshToken() {
    return refresh().then(result => {
      if (result.error) {
        // TODO: error handling
        return
      }
      token.current = result.token
      setAuthorized(true)
      return result.token
    })
  }
  async function play(deviceId, tracks, offset) {
    return await fetch("https://api.spotify.com/v1/me/player/play", {
      method: "PUT",
      body: JSON.stringify({
        device_id: deviceId,
        uris: tracks,
        offset: {
          position: offset,
        },
      }),
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token.current}`,
      },
    })
  }

  function login() {
    const params = new URLSearchParams({
      response_type: "code",
      client_id: clientId,
      scope: SPOTIFY_SCOPES.join(" "),
      redirect_uri: redirectUri,
      state: window.location,
    })
    window.location = `https://accounts.spotify.com/authorize?${params}`
  }

  return (
    <WebPlaybackSDK
      // we want this to re-render with all of its children
      // if the key ever changes
      key={authorized}
      deviceName="xetera.dev"
      getOAuthToken={getOAuthToken}
      connectOnInitialized={true}
      volume={volume}
    >
      <PlayerControls
        volume={volume}
        setVolume={setVolume}
        token={token}
        refreshToken={refreshToken}
        trackList={data.trackList}
        authorized={authorized}
        logout={logout}
        play={play}
        login={login}
      />
    </WebPlaybackSDK>
  )
}
Example #3
Source File: navbar.js    From covid19Nepal-react with MIT License 4 votes vote down vote up
function Navbar({pages, darkMode, setDarkMode}) {
  const [expand, setExpand] = useState(false);
  // eslint-disable-next-line
  const [isThemeSet, setIsThemeSet] = useLocalStorage('isThemeSet', false);

  useLockBodyScroll(expand);
  const windowSize = useWindowSize();

  return (
    <div className="Navbar">
      <div
        className="navbar-left"
        onClick={() => {
          setDarkMode((prevMode) => !prevMode);
          setIsThemeSet(true);
        }}
      >
        {darkMode ? <Icon.Sun color={'#ffc107'} /> : <Icon.Moon />}
      </div>
      <div className="navbar-middle">
        <Link
          to="/"
          onClick={() => {
            setExpand(false);
          }}
        >
          Nepal <span>Covid19</span>
        </Link>
      </div>

      <div
        className="navbar-right"
        onClick={() => {
          setExpand(!expand);
        }}
        onMouseEnter={() => {
          if (typeof window !== 'undefined' && window.innerWidth > 769) {
            setExpand(true);
            anime({
              targets: '.navbar-right path',
              strokeDashoffset: [anime.setDashoffset, 0],
              easing: 'easeInOutSine',
              duration: 250,
              delay: function (el, i) {
                return i * 250;
              },
              direction: 'alternate',
              loop: false,
            });
          }
        }}
      >
        {typeof window !== 'undefined' && windowSize.width < 769 && (
          <span>{expand ? 'Close' : 'Menu'}</span>
        )}
        {typeof window !== 'undefined' && windowSize.width > 769 && (
          <React.Fragment>
            <span>
              <Link to="/">
                <Icon.Home {...activeNavIcon('/')} />
              </Link>
            </span>
            <span>
              <Link to="/demographics">
                <Icon.Users {...activeNavIcon('/demographics')} />
              </Link>
            </span>
            <span>
              <Link to="/deepdive">
                <Icon.BarChart2 {...activeNavIcon('/deepdive')} />
              </Link>
            </span>
            <span>
              <Link to="/essentials">
                <Icon.Package {...activeNavIcon('/essentials')} />
              </Link>
            </span>
            <span>
              <Link to="/faq">
                <Icon.HelpCircle {...activeNavIcon('/faq')} />
              </Link>
            </span>
          </React.Fragment>
        )}
      </div>

      {expand && <Expand expand={expand} pages={pages} setExpand={setExpand} />}
    </div>
  );
}
Example #4
Source File: Home.js    From covid19india-react with MIT License 4 votes vote down vote up
function Home() {
  const [regionHighlighted, setRegionHighlighted] = useState({
    stateCode: 'TT',
    districtName: null,
  });

  const [anchor, setAnchor] = useLocalStorage('anchor', null);
  const [expandTable, setExpandTable] = useLocalStorage('expandTable', false);
  const [mapStatistic, setMapStatistic] = useSessionStorage(
    'mapStatistic',
    'active'
  );
  const [mapView, setMapView] = useLocalStorage('mapView', MAP_VIEWS.DISTRICTS);

  const [date, setDate] = useState('');
  const location = useLocation();

  const {data: timeseries} = useStickySWR(
    `${DATA_API_ROOT}/timeseries.min.json`,
    fetcher,
    {
      revalidateOnMount: true,
      refreshInterval: API_REFRESH_INTERVAL,
    }
  );

  const {data} = useStickySWR(
    `${DATA_API_ROOT}/data${date ? `-${date}` : ''}.min.json`,
    fetcher,
    {
      revalidateOnMount: true,
      refreshInterval: API_REFRESH_INTERVAL,
    }
  );

  const homeRightElement = useRef();
  const isVisible = useIsVisible(homeRightElement);
  const {width} = useWindowSize();

  const hideDistrictData = date !== '' && date < DISTRICT_START_DATE;
  const hideDistrictTestData =
    date === '' ||
    date >
      formatISO(
        addDays(parseIndiaDate(DISTRICT_TEST_END_DATE), TESTED_EXPIRING_DAYS),
        {representation: 'date'}
      );

  const hideVaccinated =
    getStatistic(data?.['TT'], 'total', 'vaccinated') === 0;

  const lastDataDate = useMemo(() => {
    const updatedDates = [
      data?.['TT']?.meta?.date,
      data?.['TT']?.meta?.tested?.date,
      data?.['TT']?.meta?.vaccinated?.date,
    ].filter((date) => date);
    return updatedDates.length > 0
      ? formatISO(max(updatedDates.map((date) => parseIndiaDate(date))), {
          representation: 'date',
        })
      : null;
  }, [data]);

  const lastUpdatedDate = useMemo(() => {
    const updatedDates = Object.keys(data || {})
      .map((stateCode) => data?.[stateCode]?.meta?.['last_updated'])
      .filter((datetime) => datetime);
    return updatedDates.length > 0
      ? formatDateObjIndia(
          max(updatedDates.map((datetime) => parseIndiaDate(datetime)))
        )
      : null;
  }, [data]);

  const noDistrictDataStates = useMemo(
    () =>
      // Heuristic: All cases are in Unknown
      Object.entries(data || {}).reduce((res, [stateCode, stateData]) => {
        res[stateCode] = !!(
          stateData?.districts &&
          stateData.districts?.[UNKNOWN_DISTRICT_KEY] &&
          PRIMARY_STATISTICS.every(
            (statistic) =>
              getStatistic(stateData, 'total', statistic) ===
              getStatistic(
                stateData.districts[UNKNOWN_DISTRICT_KEY],
                'total',
                statistic
              )
          )
        );
        return res;
      }, {}),
    [data]
  );

  const noRegionHighlightedDistrictData =
    regionHighlighted?.stateCode &&
    regionHighlighted?.districtName &&
    regionHighlighted.districtName !== UNKNOWN_DISTRICT_KEY &&
    noDistrictDataStates[regionHighlighted.stateCode];

  return (
    <>
      <Helmet>
        <title>Coronavirus Outbreak in India - covid19india.org</title>
        <meta
          name="title"
          content="Coronavirus Outbreak in India: Latest Map and Case Count"
        />
      </Helmet>

      <div className="Home">
        <div className={classnames('home-left', {expanded: expandTable})}>
          <div className="header">
            <Suspense fallback={<div />}>
              <Search />
            </Suspense>

            {!data && !timeseries && <div style={{height: '60rem'}} />}

            <>
              {!timeseries && <div style={{minHeight: '61px'}} />}
              {timeseries && (
                <Suspense fallback={<div style={{minHeight: '61px'}} />}>
                  <Actions
                    {...{
                      date,
                      setDate,
                      dates: Object.keys(timeseries['TT']?.dates),
                      lastUpdatedDate,
                    }}
                  />
                </Suspense>
              )}
            </>
          </div>

          <div style={{position: 'relative', marginTop: '1rem'}}>
            {data && (
              <Suspense fallback={<div style={{height: '50rem'}} />}>
                {width >= 769 && !expandTable && (
                  <MapSwitcher {...{mapStatistic, setMapStatistic}} />
                )}
                <Level data={data['TT']} />
              </Suspense>
            )}

            <>
              {!timeseries && <div style={{height: '123px'}} />}
              {timeseries && (
                <Suspense fallback={<div style={{height: '123px'}} />}>
                  <Minigraphs
                    timeseries={timeseries['TT']?.dates}
                    {...{date}}
                  />
                </Suspense>
              )}
            </>
          </div>

          {!hideVaccinated && <VaccinationHeader data={data['TT']} />}

          {data && (
            <Suspense fallback={<TableLoader />}>
              <Table
                {...{
                  data,
                  regionHighlighted,
                  setRegionHighlighted,
                  expandTable,
                  setExpandTable,
                  hideDistrictData,
                  hideDistrictTestData,
                  hideVaccinated,
                  lastDataDate,
                  noDistrictDataStates,
                }}
              />
            </Suspense>
          )}
        </div>

        <div
          className={classnames('home-right', {expanded: expandTable})}
          ref={homeRightElement}
          style={{minHeight: '4rem'}}
        >
          {(isVisible || location.hash) && (
            <>
              {data && (
                <div
                  className={classnames('map-container', {
                    expanded: expandTable,
                    stickied:
                      anchor === 'mapexplorer' || (expandTable && width >= 769),
                  })}
                >
                  <Suspense fallback={<div style={{height: '50rem'}} />}>
                    <StateHeader data={data['TT']} stateCode={'TT'} />
                    <MapExplorer
                      {...{
                        stateCode: 'TT',
                        data,
                        mapStatistic,
                        setMapStatistic,
                        mapView,
                        setMapView,
                        regionHighlighted,
                        setRegionHighlighted,
                        anchor,
                        setAnchor,
                        expandTable,
                        lastDataDate,
                        hideDistrictData,
                        hideDistrictTestData,
                        hideVaccinated,
                        noRegionHighlightedDistrictData,
                      }}
                    />
                  </Suspense>
                </div>
              )}

              {timeseries && (
                <Suspense fallback={<div style={{height: '50rem'}} />}>
                  <TimeseriesExplorer
                    stateCode="TT"
                    {...{
                      timeseries,
                      date,
                      regionHighlighted,
                      setRegionHighlighted,
                      anchor,
                      setAnchor,
                      expandTable,
                      hideVaccinated,
                      noRegionHighlightedDistrictData,
                    }}
                  />
                </Suspense>
              )}
            </>
          )}
        </div>
      </div>

      {isVisible && (
        <Suspense fallback={<div />}>
          <Footer />
        </Suspense>
      )}
    </>
  );
}
Example #5
Source File: MapExplorer.js    From covid19india-react with MIT License 4 votes vote down vote up
function MapExplorer({
  stateCode: mapCode = 'TT',
  data,
  mapView = MAP_VIEWS.DISTRICTS,
  setMapView,
  mapStatistic,
  setMapStatistic,
  regionHighlighted,
  setRegionHighlighted,
  noRegionHighlightedDistrictData,
  anchor,
  setAnchor,
  expandTable = false,
  lastDataDate,
  hideDistrictData = false,
  hideDistrictTestData = true,
  hideVaccinated = false,
  noDistrictData = false,
}) {
  const {t} = useTranslation();
  const mapExplorerRef = useRef();
  const {width} = useWindowSize();

  const [isPerLakh, setIsPerLakh] = useSessionStorage('isPerLakhMap', false);
  const [delta7Mode, setDelta7Mode] = useSessionStorage('delta7ModeMap', false);

  const mapMeta = MAP_META[mapCode];
  const mapData =
    mapMeta.mapType === MAP_TYPES.COUNTRY ? data : {[mapCode]: data[mapCode]};

  const statisticConfig = STATISTIC_CONFIGS[mapStatistic];

  const isDistrictView =
    mapView === MAP_VIEWS.DISTRICTS &&
    (mapMeta.mapType === MAP_TYPES.STATE ||
      (!hideDistrictData &&
        !(hideDistrictTestData && statisticConfig?.category === 'tested')));

  const hoveredRegion = useMemo(() => {
    const hoveredData =
      (regionHighlighted.districtName
        ? data[regionHighlighted.stateCode]?.districts?.[
            regionHighlighted.districtName
          ]
        : data[regionHighlighted.stateCode]) || {};

    return produce(hoveredData, (draft) => {
      draft.name =
        regionHighlighted.districtName ||
        STATE_NAMES[regionHighlighted.stateCode];
    });
  }, [data, regionHighlighted.stateCode, regionHighlighted.districtName]);

  const handlePerLakhClick = useCallback(() => {
    const statisticConfig = STATISTIC_CONFIGS[mapStatistic];
    if (statisticConfig?.nonLinear || mapStatistic === 'population') {
      return;
    }
    setIsPerLakh((isPerLakh) => !isPerLakh);
  }, [mapStatistic, setIsPerLakh]);

  const handleDistrictClick = useCallback(() => {
    const newMapView =
      mapView === MAP_VIEWS.DISTRICTS ? MAP_VIEWS.STATES : MAP_VIEWS.DISTRICTS;
    if (newMapView === MAP_VIEWS.STATES) {
      setRegionHighlighted({
        stateCode: regionHighlighted.stateCode,
        districtName: null,
      });
    }
    setMapView(newMapView);
  }, [mapView, regionHighlighted.stateCode, setMapView, setRegionHighlighted]);

  const history = useHistory();
  const panelRef = useRef();

  useEffect(() => {
    if (history.location.hash === '#MapExplorer') {
      panelRef.current.scrollIntoView();
    }
  }, [history]);

  const trail = useMemo(() => {
    const styles = [];

    [0, 0, 0, 0, 0, 0, 0].map((element, index) => {
      styles.push({
        animationDelay: `${index * 250}ms`,
      });
      return null;
    });

    return styles;
  }, []);

  const getMapStatistic = useCallback(
    (data) => {
      const statisticConfig = STATISTIC_CONFIGS[mapStatistic];

      const type =
        (statisticConfig?.showDelta && delta7Mode) ||
        statisticConfig?.onlyDelta7
          ? 'delta7'
          : 'total';

      return getStatistic(data, type, mapStatistic, {
        expiredDate: lastDataDate,
        normalizedByPopulationPer:
          isPerLakh && mapStatistic != 'population' ? 'lakh' : null,
        canBeNaN: true,
      });
    },
    [mapStatistic, isPerLakh, lastDataDate, delta7Mode]
  );

  let currentVal = getMapStatistic(hoveredRegion);
  if (isNaN(currentVal)) {
    currentVal = '-';
  }

  const spring = useSpring({
    total: currentVal,
    config: {tension: 250, ...SPRING_CONFIG_NUMBERS},
  });

  const mapStatistics = useMemo(
    () =>
      MAP_STATISTICS.filter(
        (statistic) =>
          !(STATISTIC_CONFIGS[statistic]?.category === 'vaccinated') ||
          !hideVaccinated
      ),
    [hideVaccinated]
  );

  const handleStatisticChange = useCallback(
    (direction) => {
      const currentIndex = mapStatistics.indexOf(mapStatistic);
      const toIndex =
        (mapStatistics.length + currentIndex + direction) %
        mapStatistics.length;
      setMapStatistic(mapStatistics[toIndex]);
    },
    [mapStatistic, mapStatistics, setMapStatistic]
  );

  const swipeHandlers = useSwipeable({
    onSwipedLeft: handleStatisticChange.bind(this, 1),
    onSwipedRight: handleStatisticChange.bind(this, -1),
  });

  const mapViz = statisticConfig?.mapConfig?.spike
    ? MAP_VIZS.SPIKE
    : isPerLakh ||
      statisticConfig?.mapConfig?.colorScale ||
      statisticConfig?.nonLinear
    ? MAP_VIZS.CHOROPLETH
    : MAP_VIZS.BUBBLE;

  const handleDeltaClick = useCallback(() => {
    if (statisticConfig?.showDelta) {
      setDelta7Mode((delta7Mode) => !delta7Mode);
    }
  }, [statisticConfig, setDelta7Mode]);

  const stickied = anchor === 'mapexplorer' || (expandTable && width >= 769);

  const transformStatistic = useCallback(
    (val) =>
      statisticConfig?.mapConfig?.transformFn
        ? statisticConfig.mapConfig.transformFn(val)
        : val,
    [statisticConfig]
  );

  const zoneColor = statisticConfig?.mapConfig?.colorScale
    ? statisticConfig.mapConfig.colorScale(transformStatistic(currentVal))
    : '';

  return (
    <div
      className={classnames(
        'MapExplorer',
        {stickied},
        {
          hidden:
            anchor && anchor !== 'mapexplorer' && (!expandTable || width < 769),
        }
      )}
    >
      <div
        className={classnames('anchor', 'fadeInUp', {
          stickied,
        })}
        style={{
          display: width < 769 || (width >= 769 && expandTable) ? 'none' : '',
        }}
        onClick={
          setAnchor &&
          setAnchor.bind(this, anchor === 'mapexplorer' ? null : 'mapexplorer')
        }
      >
        <PinIcon />
      </div>
      <div className="panel" ref={panelRef}>
        <div className="panel-left fadeInUp" style={trail[0]}>
          <h2
            className={classnames(mapStatistic)}
            style={{color: zoneColor || statisticConfig?.color}}
          >
            {t(hoveredRegion.name)}
            {hoveredRegion.name === UNKNOWN_DISTRICT_KEY &&
              ` [${t(STATE_NAMES[regionHighlighted.stateCode])}]`}
          </h2>

          {regionHighlighted.stateCode && (
            <h1
              className={classnames('district', mapStatistic)}
              style={{color: zoneColor || statisticConfig?.color}}
            >
              <animated.div>
                {spring.total.to((total) =>
                  !noRegionHighlightedDistrictData ||
                  !statisticConfig?.hasPrimary
                    ? formatNumber(total, statisticConfig.format, mapStatistic)
                    : '-'
                )}
              </animated.div>
              <StatisticDropdown
                currentStatistic={mapStatistic}
                statistics={mapStatistics}
                mapType={mapMeta.mapType}
                {...{
                  isPerLakh,
                  delta7Mode,
                  mapStatistic,
                  setMapStatistic,
                  hideDistrictTestData,
                  hideVaccinated,
                  zoneColor,
                }}
              />
            </h1>
          )}
        </div>

        <div className={classnames('panel-right', `is-${mapStatistic}`)}>
          <div className="switch-type">
            <Tooltip message={'Last 7 day values'} hold>
              <div
                className={classnames('toggle', 'fadeInUp', {
                  'is-highlighted':
                    (delta7Mode && statisticConfig?.showDelta) ||
                    statisticConfig?.onlyDelta7,
                  disabled: !statisticConfig?.showDelta,
                })}
                onClick={handleDeltaClick}
                style={trail[1]}
              >
                <Delta7Icon />
              </div>
            </Tooltip>

            <Tooltip message={'Per lakh people'} hold>
              <div
                className={classnames('toggle', 'fadeInUp', {
                  'is-highlighted':
                    !statisticConfig?.nonLinear &&
                    mapViz === MAP_VIZS.CHOROPLETH,
                  disabled:
                    statisticConfig?.nonLinear || mapStatistic === 'population',
                })}
                onClick={handlePerLakhClick}
                style={trail[2]}
              >
                <PerLakhIcon />
              </div>
            </Tooltip>

            {mapMeta.mapType === MAP_TYPES.COUNTRY && (
              <Tooltip message={'Toggle between states/districts'} hold>
                <div
                  className={classnames('toggle', 'boundary fadeInUp', {
                    'is-highlighted': isDistrictView,
                    disabled:
                      hideDistrictData ||
                      (statisticConfig?.category === 'tested' &&
                        hideDistrictTestData),
                  })}
                  onClick={handleDistrictClick}
                  style={trail[3]}
                >
                  <OrganizationIcon />
                </div>
              </Tooltip>
            )}

            {mapMeta.mapType === MAP_TYPES.STATE && (
              <>
                <div className="divider" />
                <div
                  className="toggle back fadeInUp"
                  onClick={() => {
                    history.push('/#MapExplorer');
                  }}
                  style={trail[4]}
                >
                  <ArrowLeftIcon />
                </div>
              </>
            )}
          </div>

          <div className="switch-statistic fadeInUp" style={trail[5]}>
            {mapStatistics.map((statistic) => (
              <div
                key={statistic}
                className={classnames(
                  'toggle',
                  'statistic-option',
                  `is-${statistic}`,
                  {
                    'is-highlighted': mapStatistic === statistic,
                  }
                )}
                onClick={setMapStatistic.bind(this, statistic)}
              >
                <DotFillIcon />
              </div>
            ))}
          </div>
        </div>
      </div>

      <div
        ref={mapExplorerRef}
        className="fadeInUp"
        style={trail[3]}
        {...swipeHandlers}
      >
        {mapStatistic && (
          <Suspense
            fallback={
              <MapVisualizerLoader
                className="map-loader"
                {...{
                  width: mapExplorerRef.current?.clientWidth,
                  statistic: mapStatistic,
                }}
              />
            }
          >
            <MapVisualizer
              data={mapData}
              statistic={mapStatistic}
              {...{
                mapCode,
                isDistrictView,
                mapViz,
                regionHighlighted,
                setRegionHighlighted,
                getMapStatistic,
                transformStatistic,
                noDistrictData,
              }}
            ></MapVisualizer>
          </Suspense>
        )}
      </div>
    </div>
  );
}
Example #6
Source File: Navbar.js    From covid19india-react with MIT License 4 votes vote down vote up
function Navbar({pages, showLanguageSwitcher, setShowLanguageSwitcher}) {
  const {i18n, t} = useTranslation();
  const currentLanguage = Object.keys(locales).includes(i18n?.language)
    ? i18n?.language
    : i18n?.options?.fallbackLng[0];

  const [expand, setExpand] = useState(false);
  const darkMode = useDarkMode(false);

  useLockBodyScroll(expand);
  const windowSize = useWindowSize();

  usePageLeave(() => setExpand(false));

  const navbarTransition = useTransition(true, {
    from: {opacity: 0},
    enter: {opacity: 1},
  });

  const expandTransition = useTransition(expand, {
    from: windowSize.width < 769 ? SLIDE_IN_MOBILE : SLIDE_IN,
    enter: windowSize.width < 769 ? SLIDE_OUT_MOBILE : SLIDE_OUT,
    leave: windowSize.width < 769 ? SLIDE_IN_MOBILE : SLIDE_IN,
    config: {mass: 1, tension: 210, friction: 26},
  });

  const handleMouseEnter = useCallback(() => {
    if (windowSize.width >= 769) {
      setExpand(true);
    }
  }, [windowSize.width]);

  const handleLanguageSwitcher = useCallback(() => {
    if (expand) setExpand(false);
    setShowLanguageSwitcher(!showLanguageSwitcher);
  }, [expand, showLanguageSwitcher, setExpand, setShowLanguageSwitcher]);

  return navbarTransition((style, item) => (
    <animated.div className="Navbar" {...{style}}>
      <div className="navbar-left" onClick={handleLanguageSwitcher}>
        {locales[currentLanguage]}
      </div>

      <div className="navbar-middle">
        <Link to="/" onClick={setExpand.bind(this, false)}>
          Covid19<span>India</span>
        </Link>
      </div>

      <div
        className="navbar-right"
        onMouseEnter={handleMouseEnter}
        {...(windowSize.width < 769 && {
          onClick: setExpand.bind(this, !expand),
        })}
      >
        {windowSize.width < 769 && (
          <span>{expand ? t('Close') : t('Menu')}</span>
        )}

        {windowSize.width >= 769 && (
          <>
            <Link to="/">
              <span>
                <Home {...activeNavIcon('/')} />
              </span>
            </Link>
            <Link to="/blog">
              <span>
                <Book {...activeNavIcon('/blog')} />
              </span>
            </Link>
            <Link to="/volunteers">
              <span>
                <Users {...activeNavIcon('/volunteers')} />
              </span>
            </Link>
            <Link to="/about">
              <span>
                <HelpCircle {...activeNavIcon('/about')} />
              </span>
            </Link>
            <span>
              <SunMoon {...{darkMode}} />
            </span>
          </>
        )}
      </div>

      {expandTransition(
        (style, item) =>
          item && (
            <animated.div {...{style}}>
              <Expand {...{pages, setExpand, darkMode, windowSize}} />
            </animated.div>
          )
      )}
    </animated.div>
  ));
}
Example #7
Source File: TimeseriesExplorer.js    From covid19india-react with MIT License 4 votes vote down vote up
function TimeseriesExplorer({
  stateCode,
  timeseries,
  date: timelineDate,
  regionHighlighted,
  setRegionHighlighted,
  anchor,
  setAnchor,
  expandTable = false,
  hideVaccinated = false,
  noRegionHighlightedDistrictData,
}) {
  const {t} = useTranslation();
  const [lookback, setLookback] = useLocalStorage('timeseriesLookbackDays', 90);
  const [chartType, setChartType] = useLocalStorage('chartType', 'delta');
  const [isUniform, setIsUniform] = useLocalStorage('isUniform', false);
  const [isLog, setIsLog] = useLocalStorage('isLog', false);
  const [isMovingAverage, setIsMovingAverage] = useLocalStorage(
    'isMovingAverage',
    false
  );

  const stateCodeDateRange = Object.keys(timeseries?.[stateCode]?.dates || {});
  const beginningDate =
    stateCodeDateRange[0] || timelineDate || getIndiaDateYesterdayISO();
  const endDate = min([
    stateCodeDateRange[stateCodeDateRange.length - 1],
    timelineDate || getIndiaDateYesterdayISO(),
  ]);

  const [brushSelectionEnd, setBrushSelectionEnd] = useState(endDate);
  useEffect(() => {
    setBrushSelectionEnd(endDate);
  }, [endDate]);

  const brushSelectionStart =
    lookback !== null
      ? formatISO(subDays(parseIndiaDate(brushSelectionEnd), lookback), {
          representation: 'date',
        })
      : beginningDate;

  const explorerElement = useRef();
  const isVisible = useIsVisible(explorerElement, {once: true});
  const {width} = useWindowSize();

  const selectedRegion = useMemo(() => {
    if (timeseries?.[regionHighlighted.stateCode]?.districts) {
      return {
        stateCode: regionHighlighted.stateCode,
        districtName: regionHighlighted.districtName,
      };
    } else {
      return {
        stateCode: regionHighlighted.stateCode,
        districtName: null,
      };
    }
  }, [timeseries, regionHighlighted.stateCode, regionHighlighted.districtName]);

  const selectedTimeseries = useMemo(() => {
    if (selectedRegion.districtName) {
      return timeseries?.[selectedRegion.stateCode]?.districts?.[
        selectedRegion.districtName
      ]?.dates;
    } else {
      return timeseries?.[selectedRegion.stateCode]?.dates;
    }
  }, [timeseries, selectedRegion.stateCode, selectedRegion.districtName]);

  const regions = useMemo(() => {
    const states = Object.keys(timeseries || {})
      .filter((code) => code !== stateCode)
      .sort((code1, code2) =>
        STATE_NAMES[code1].localeCompare(STATE_NAMES[code2])
      )
      .map((code) => {
        return {
          stateCode: code,
          districtName: null,
        };
      });
    const districts = Object.keys(timeseries || {}).reduce((acc1, code) => {
      return [
        ...acc1,
        ...Object.keys(timeseries?.[code]?.districts || {}).reduce(
          (acc2, districtName) => {
            return [
              ...acc2,
              {
                stateCode: code,
                districtName: districtName,
              },
            ];
          },
          []
        ),
      ];
    }, []);

    return [
      {
        stateCode: stateCode,
        districtName: null,
      },
      ...states,
      ...districts,
    ];
  }, [timeseries, stateCode]);

  const dropdownRegions = useMemo(() => {
    if (
      regions.find(
        (region) =>
          region.stateCode === regionHighlighted.stateCode &&
          region.districtName === regionHighlighted.districtName
      )
    )
      return regions;
    return [
      ...regions,
      {
        stateCode: regionHighlighted.stateCode,
        districtName: regionHighlighted.districtName,
      },
    ];
  }, [regionHighlighted.stateCode, regionHighlighted.districtName, regions]);

  const dates = useMemo(
    () =>
      Object.keys(selectedTimeseries || {}).filter((date) => date <= endDate),
    [selectedTimeseries, endDate]
  );

  const brushSelectionDates = useMemo(
    () =>
      dates.filter(
        (date) => brushSelectionStart <= date && date <= brushSelectionEnd
      ),
    [dates, brushSelectionStart, brushSelectionEnd]
  );

  const handleChange = useCallback(
    ({target}) => {
      setRegionHighlighted(JSON.parse(target.value));
    },
    [setRegionHighlighted]
  );

  const resetDropdown = useCallback(() => {
    setRegionHighlighted({
      stateCode: stateCode,
      districtName: null,
    });
  }, [stateCode, setRegionHighlighted]);

  const statistics = useMemo(
    () =>
      TIMESERIES_STATISTICS.filter(
        (statistic) =>
          (!(STATISTIC_CONFIGS[statistic]?.category === 'vaccinated') ||
            !hideVaccinated) &&
          // (chartType === 'total' || statistic !== 'active') &&
          (chartType === 'delta' || statistic !== 'tpr')
      ),
    [chartType, hideVaccinated]
  );

  return (
    <div
      className={classnames(
        'TimeseriesExplorer fadeInUp',
        {
          stickied: anchor === 'timeseries',
        },
        {expanded: expandTable}
      )}
      style={{
        display:
          anchor && anchor !== 'timeseries' && (!expandTable || width < 769)
            ? 'none'
            : '',
      }}
      ref={explorerElement}
    >
      <div className="timeseries-header">
        <div
          className={classnames('anchor', 'fadeInUp', {
            stickied: anchor === 'timeseries',
          })}
          style={{
            display: expandTable && width >= 769 ? 'none' : '',
          }}
          onClick={
            setAnchor &&
            setAnchor.bind(this, anchor === 'timeseries' ? null : 'timeseries')
          }
        >
          <PinIcon />
        </div>

        <h1>{t('Spread Trends')}</h1>
        <div className="tabs">
          {Object.entries(TIMESERIES_CHART_TYPES).map(
            ([ctype, value], index) => (
              <div
                className={`tab ${chartType === ctype ? 'focused' : ''}`}
                key={ctype}
                onClick={setChartType.bind(this, ctype)}
              >
                <h4>{t(value)}</h4>
              </div>
            )
          )}
        </div>

        <div className="timeseries-options">
          <div className="scale-modes">
            <label className="main">{`${t('Scale Modes')}:`}</label>
            <div className="timeseries-mode">
              <label htmlFor="timeseries-mode">{t('Uniform')}</label>
              <input
                id="timeseries-mode"
                type="checkbox"
                className="switch"
                checked={isUniform}
                aria-label={t('Checked by default to scale uniformly.')}
                onChange={setIsUniform.bind(this, !isUniform)}
              />
            </div>
            <div
              className={`timeseries-mode ${
                chartType !== 'total' ? 'disabled' : ''
              }`}
            >
              <label htmlFor="timeseries-logmode">{t('Logarithmic')}</label>
              <input
                id="timeseries-logmode"
                type="checkbox"
                checked={chartType === 'total' && isLog}
                className="switch"
                disabled={chartType !== 'total'}
                onChange={setIsLog.bind(this, !isLog)}
              />
            </div>
          </div>

          <div
            className={`timeseries-mode ${
              chartType === 'total' ? 'disabled' : ''
            } moving-average`}
          >
            <label htmlFor="timeseries-moving-average">
              {t('7 day Moving Average')}
            </label>
            <input
              id="timeseries-moving-average"
              type="checkbox"
              checked={chartType === 'delta' && isMovingAverage}
              className="switch"
              disabled={chartType !== 'delta'}
              onChange={setIsMovingAverage.bind(this, !isMovingAverage)}
            />
          </div>
        </div>
      </div>
      {dropdownRegions && (
        <div className="state-selection">
          <div className="dropdown">
            <select
              value={JSON.stringify(selectedRegion)}
              onChange={handleChange}
            >
              {dropdownRegions
                .filter(
                  (region) =>
                    STATE_NAMES[region.stateCode] !== region.districtName
                )
                .map((region) => {
                  return (
                    <option
                      value={JSON.stringify(region)}
                      key={`${region.stateCode}-${region.districtName}`}
                    >
                      {region.districtName
                        ? t(region.districtName)
                        : t(STATE_NAMES[region.stateCode])}
                    </option>
                  );
                })}
            </select>
          </div>
          <div className="reset-icon" onClick={resetDropdown}>
            <ReplyIcon />
          </div>
        </div>
      )}
      {isVisible && (
        <Suspense fallback={<TimeseriesLoader />}>
          <Timeseries
            timeseries={selectedTimeseries}
            regionHighlighted={selectedRegion}
            dates={brushSelectionDates}
            {...{
              statistics,
              endDate,
              chartType,
              isUniform,
              isLog,
              isMovingAverage,
              noRegionHighlightedDistrictData,
            }}
          />
          <TimeseriesBrush
            timeseries={selectedTimeseries}
            regionHighlighted={selectedRegion}
            currentBrushSelection={[brushSelectionStart, brushSelectionEnd]}
            animationIndex={statistics.length}
            {...{dates, endDate, lookback, setBrushSelectionEnd, setLookback}}
          />
        </Suspense>
      )}
      {!isVisible && <div style={{height: '50rem'}} />}
      <div
        className="pills fadeInUp"
        style={{animationDelay: `${(1 + statistics.length) * 250}ms`}}
      >
        {TIMESERIES_LOOKBACK_DAYS.map((numDays) => (
          <button
            key={numDays}
            type="button"
            className={classnames({
              selected: numDays === lookback,
            })}
            onClick={setLookback.bind(this, numDays)}
          >
            {numDays !== null ? `${numDays} ${t('days')}` : t('Beginning')}
          </button>
        ))}
      </div>
    </div>
  );
}
Example #8
Source File: App.js    From FSE-Planner with MIT License 4 votes vote down vote up
function App() {

  const [filtersBar, setFiltersBar] = React.useState(false)
  const [filters, setFilters] = React.useState({
    type: 'Trip-Only',
    cargo: ['passengers'],
    fromIcao: null,
    toIcao: null,
    minPax: '',
    minKg: '',
    maxPax: '',
    maxKg: '',
    minDist: '',
    maxDist: '',
    minJobPay: '',
    minLegPay: '',
    percentPay: '',
    direction: ''
  });
  const [table, setTable] = React.useState(false);
  const [updatePopup, setUpdatePopup] = React.useState(false);
  const [settingsPopup, setSettingsPopup] = React.useState(false);
  const [creditsPopup, setCreditsPopup] = React.useState(false);
  const [jobs, setJobs] = React.useState(() => {
    return transformJobs(storage.get('jobs', {}, true));
  });
  const [planes, setPlanes] = React.useState(() => {
    return transformPlanes(storage.get('planes', {}));
  });
  const [flight, setFlight] = React.useState(() => {
    return transformJobs(storage.get('flight', {}));
  });
  const [settings, setSettings] = React.useState(() => {
    const obj = {};
    // Cannot use _defaultsDeep, because it messes up with array
    [defaultSettings, storage.get('settings', {})].forEach(item => {
      _mergeWith(obj, item, (objectValue, sourceValue) => {
        return _isArray(sourceValue) ? sourceValue : undefined;
      });
    });
    return obj;
  });
  const [search, setSearch] = React.useState(null);
  const [searchDest, setSearchDest] = React.useState(null);
  const [searchInput, setSearchInput] = React.useState('');
  const [searchHistory, setSearchHistory] = React.useState(storage.get('searchHistory', []));
  const [icaodata, setIcaodata] = React.useState(icaodataSrc);
  const [isTourOpen, setIsTourOpen] = React.useState(storage.get('tutorial') === null);
  const [routeFinder, setRouteFinder] = React.useState(false);
  const [route, setRoute] = React.useState(null);
  const [searchOptions, setSearchOptions] = React.useState([]);
  const mapRef = React.useRef();
  const searchDestRef = React.useRef(null);
  const orientation = useOrientation();
  const windowSize = useWindowSize();

  const options = React.useMemo(() => ({
    jobs: jobs,
    planes: planes,
    flight: flight,
    settings: settings,
    icaodata: icaodata,
    ...filters
  }), [jobs, planes, flight, settings, icaodata, filters]);

  React.useEffect(() => {
    const obj = _clone(icaodataSrc);
    icaos.forEach((icao) => {
      const nb = wrap(obj[icao].lon, settings.display.map.center);
      obj[icao].lon += nb;
    });
    setIcaodata(obj);
  }, [settings.display.map.center]);

  React.useEffect(() => {
    // Display changelog if new version
    const last = storage.get('tutorial');
    if (last && last !== process.env.REACT_APP_VERSION) {
      setCreditsPopup(true);
      storage.set('tutorial', process.env.REACT_APP_VERSION);
    }

    // Set search if query string in URL
    const urlParams = new URLSearchParams(window.location.search);
    const icao = urlParams.get('icao');
    if (icao) {
      setSearch(icao);
    }

    // Register error logging
    window.onerror = (message, file, line, column, errorObject) => {
      // We do not use ErrorBoundary component for logging, because it does
      // not detect errors in event handlers, and we need to log these errors too
      log.error(message, {
        file: file,
        line: line,
        column: column,
        obj: errorObject,
      });
    }

  }, []);

  // Create goTo function, to allow panning to given ICAO
  // Cannot just use setSearch, because need to pan even if the ICAO was already in search var
  const goToRef = React.useRef(null);
  const goTo = React.useCallback((icao, from = null) => {
    if (icao) {
      if (from) {
        searchDestRef.current = {...icaodataSrc[icao], from: from};
        setSearchDest(icao);
        setSearch(from);
        setSearchOptions([searchDestRef.current]);
        setSearchHistory(prevList => {
          const list = [...(new Set([icao, from, ...prevList]))].slice(0, 5);
          storage.set('searchHistory', list);
          return list;
        });
        window.history.replaceState({icao:from}, '', '?icao='+from);
      }
      else {
        setSearch(prevIcao => prevIcao === icao ? null : icao);
        setSearchDest(null);
        setSearchOptions([icaodataSrc[icao]]);
        goToRef.current = icao;
        setSearchHistory(prevList => {
          const list = [...(new Set([icao, ...prevList]))].slice(0, 5);
          storage.set('searchHistory', list);
          return list;
        });
        window.history.replaceState({icao:icao}, '', '?icao='+icao);
      }
    }
    else {
      setSearch(null);
      window.history.replaceState(null, '', '?');
    }
  }, []);
  React.useEffect(() => {
    if (goToRef.current) {
      const icao = goToRef.current;
      goToRef.current = null;
      setSearch(icao);
    }
  }, [search]);

  // Invalidate map size when routing toogled
  React.useEffect(() => {
    if (!mapRef.current) { return; }
    if (window.scrollY !== 0) {
      window.scrollTo(0, 0);
    }
    mapRef.current.invalidateSize({pan:false});
  }, [routeFinder, filters, orientation, table, windowSize.width, windowSize.height]);

  // Actions
  const actions = React.useRef(null);
  const setActions = () => {
    actions.current = {
      goTo: goTo,
      fromIcao: (icao) => setFilters(prev => {
        const f = {...prev};
        f.fromIcao = icao;
        return f;
      }),
      isFromIcao: (icao) => filters.fromIcao === icao,
      isToIcao: (icao) => filters.toIcao === icao,
      toIcao: (icao) => setFilters(prev => {
        const f = {...prev};
        f.toIcao = icao;
        return f;
      }),
      contextMenu: (actions.current && actions.current.contextMenu) ? actions.current.contextMenu : undefined,
      measureDistance: () => null,
      openTable: () => { setRouteFinder(false); setTable(true); },
      getCustomLayers: (icao) => [],
      addToCustomLayer: () => null,
      removeFromCustomLayer: () => null
    };
  }
  if (!actions.current) { setActions(); }
  React.useEffect(setActions, [goTo, filters.fromIcao, filters.toIcao]);

  React.useEffect(() => {
    const inputs = searchInput.split(/\s*[><]+\s*/g);
    // Should not be more than 2 ICAOS long
    if (inputs.length > 2) { return setSearchOptions([]); }
    // If typing a second ICAO, the first one should be valid
    if (inputs.length > 1 && !icaodataSrc.hasOwnProperty(inputs[0])) { return setSearchOptions([]); }
    const input = inputs[inputs.length-1];

    let filtered = [];
    // If input is empty and search history is not, display search history
    if (!input && searchHistory.length > 0) {
      filtered = searchHistory.map(icao => icaodataSrc[icao]);
    }
    else {
      // Search for ICAO
      filtered = filter(icaodataSrcArr, { inputValue: input, getOptionLabel: (a) => a.icao });
      // If not enough results, search for city name
      if (filtered.length < 5) {
        const add = filter(icaodataSrcArr, { inputValue: input, getOptionLabel: (a) => a.name });
        filtered = filtered.concat(add.slice(0, 5-filtered.length));
      }
    }
    if (inputs.length === 2) {
      filtered = filtered.map(elm => { return {...elm, from: inputs[0]} });
    }
    if (search) {
      if (searchDest) {
        const exist = filtered.reduce((acc, elm) => acc || (elm.icao === searchDest && elm.from === search), false);
        if (!exist) { filtered.push({...icaodataSrc[searchDest], from: search}) }
      }
      else {
        const exist = filtered.reduce((acc, elm) => acc || (elm.icao === search && !elm.from), false);
        if (!exist) { filtered.push(icaodataSrc[search]) }
      }
    }
    setSearchOptions(filtered);
  }, [searchInput, searchHistory, search, searchDest]);

  return (
    <Box
      sx={{
        display: "flex",
        flexFlow: "column",
        height: "100%"
      }}
    >
      <AppBar position="static">
        <Toolbar>
          <Box
            sx={{
              display: 'flex',
              alignItems: 'flex-end',
              flexWrap: 'wrap'
            }}
          >
            <Typography variant="h6" sx={{ lineHeight: 1, py: 0.4 }}>
              FSE Planner
            </Typography>
            <Tooltip title="Changelog & credits">
              <Button
                sx={{
                  marginLeft: 1,
                  paddingLeft: '2px',
                  paddingRight: '2px',
                  px: 0.5,
                  py: 0,
                  fontWeight: 'normal',
                  color: '#fff',
                  letterSpacing: 'normal',
                  textTransform: 'none',
                  minWidth: 'auto'
                }}
                onClick={() => setCreditsPopup(true)}
                data-tour="Step10"
                size="small"
              >
                v{process.env.REACT_APP_VERSION}
              </Button>
            </Tooltip>
          </Box>
          <Box
            sx={{
              display: "flex",
              flexWrap: "wrap",
              ml: 1,
              flexGrow: 2
            }}
          >
            <Button
              sx={styles.menuBtn}
              onClick={() => setUpdatePopup(true)}
              data-tour="Step2"
              startIcon={<UpdateIcon />}
            >
              Load data
            </Button>
            <Button
              sx={styles.menuBtn}
              onClick={() => {
                if (!table) {
                  setRouteFinder(false);
                }
                setTable(!table);
              }}
              startIcon={table ? <MapIcon /> : <TableViewIcon />}
            >
              {table ? "Map" : "Table" }
            </Button>
            <Button
              sx={{
                ...styles.menuBtn,
                ...(filtersBar && {
                  backgroundColor: 'rgba(255, 255, 255, 0.3)',
                  '&:hover': {
                    backgroundColor: 'rgba(255, 255, 255, 0.3)',
                  }
                })
              }}
              onClick={() => setFiltersBar(!filtersBar)}
              data-tour="Step7"
              startIcon={<FilterAltIcon />}
            >
              Filters
            </Button>
            <Button
              sx={{
                ...styles.menuBtn,
                ...(routeFinder && {
                  backgroundColor: 'rgba(255, 255, 255, 0.3)',
                  '&:hover': {
                    backgroundColor: 'rgba(255, 255, 255, 0.3)',
                  }
                })
              }}
              onClick={() => {
                if (table) {
                  setTable(false);
                  setRouteFinder(true);
                }
                else {
                    setRouteFinder(!routeFinder)
                }
              }}
              data-tour="Step9"
              startIcon={<DirectionsIcon />}
            >
              Route finder
            </Button>
            <Button
              sx={styles.menuBtn}
              onClick={() => setSettingsPopup(true)}
              startIcon={<TuneIcon />}
            >
              Settings
            </Button>
          </Box>
          <Box
            sx={{
              display: 'flex',
              ml: 2
            }}
          >
            <Autocomplete
              options={searchOptions}
              getOptionLabel={(a) => a.icao ? (a.from ? a.from + ' > ' + a.icao : a.icao) : ''}
              renderOption={(props, a) =>
                <li {...props}>
                  <Box
                    component="span"
                    sx={{
                      display: 'flex',
                      alignItems: 'center',
                      overflow: 'hidden'
                    }}
                  >
                    {a.from &&
                      <React.Fragment>
                        <Box
                          component="b"
                          sx={{
                            minWidth: '40px',
                            textAlign: 'center'
                          }}
                        >
                          {a.from}
                        </Box>
                        <Box
                          component="span"
                          sx={{ px: 1}}
                        >
                          &gt;
                        </Box>
                      </React.Fragment>
                    }
                    <Box
                      component="b"
                      sx={{
                        minWidth: '40px',
                        textAlign: 'center'
                      }}
                    >
                      {a.icao}
                    </Box>
                    <Box
                      component="span"
                      sx={{
                        display: 'flex',
                        flexDirection: 'column',
                        marginLeft: 2,
                        overflow: 'hidden',
                      }}
                    >
                      <Box
                        component="span"
                        sx={{
                          textOverflow: 'ellipsis',
                          overflow: 'hidden',
                          whiteSpace: 'nowrap'
                        }}
                      >
                        {a.name}
                      </Box>
                      <Typography
                        variant="caption"
                        sx={{
                          textOverflow: 'ellipsis',
                          overflow: 'hidden',
                          whiteSpace: 'nowrap',
                          color: '#aaa'
                        }}
                      >
                        {a.city}, {a.state ? a.state+', ' : ''}{a.country}
                      </Typography>
                    </Box>
                  </Box>
                </li>
              }
              filterOptions={x => x}
              renderInput={(params) =>
                <InputBase
                  placeholder="Search..."
                  sx={styles.inputSearch}
                  ref={params.InputProps.ref}
                  inputProps={params.inputProps}
                  endAdornment={params.inputProps.value ?
                      <IconButton
                        size="small"
                        onClick={() => {
                          setSearchDest(null);
                          setSearch(null);
                          setSearchInput('');
                          window.history.replaceState(null, '', '?');
                        }}
                      >
                        <CloseIcon />
                      </IconButton>
                    :
                      null
                  }
                />
              }
              isOptionEqualToValue={(option, value) => option.icao === value.icao && option.from === value.from}
              PopperComponent={PopperMy}
              onChange={(evt, value) => value && goTo(value.icao, value.from)}
              value={search ? (searchDest ? searchDestRef.current : icaodataSrc[search]) : null}
              inputValue={searchInput}
              onInputChange={(evt, value) => setSearchInput(value)}
              autoHighlight={true}
              selectOnFocus={false}
            />
          </Box>
        </Toolbar>
        {filtersBar &&
          <Filters
            filters={filters}
            setFilters={setFilters}
            icaodata={icaodata}
            actions={actions}
            setSettingsPopup={setSettingsPopup}
          />
        }
      </AppBar>
      <Box
        sx={{
          display: 'flex',
          flexWrap: 'nowrap',
          flexGrow: '1',
          overflow: 'hidden'
        }}
      >
        <Routing
          options={options}
          setRoute={setRoute}
          hidden={!routeFinder}
          mapRef={mapRef}
          close={() => setRouteFinder(false)}
          actions={actions}
        />
        <FSEMap
          options={options}
          search={search}
          searchDest={searchDest}
          icaos={icaos}
          route={route}
          mapRef={mapRef}
          actions={actions}
          hidden={table}
        />
        <Table
          options={options}
          hidden={!table}
          actions={actions}
          search={search}
          searchDest={searchDest}
        />
      </Box>
      <UpdatePopup
        open={updatePopup}
        setUpdatePopup={setUpdatePopup}
        setJobs={(jobs) => setJobs(transformJobs(jobs))}
        setPlanes={(planes) => setPlanes(transformPlanes(planes))}
        setFlight={(flight) => setFlight(transformJobs(flight))}
        icaodata={icaodata}
        icaos={icaos}
        settings={settings}
      />
      <SettingsPopup
        open={settingsPopup}
        handleClose={() => setSettingsPopup(false)}
        settings={settings}
        setSettings={setSettings}
        defaultSettings={defaultSettings}
      />
      <CreditsPopup
        open={creditsPopup}
        handleClose={() => setCreditsPopup(false)}
        openTutorial={() => setIsTourOpen(true)}
      />
      <Tour
        isTourOpen={isTourOpen}
        setIsTourOpen={setIsTourOpen}
        updatePopup={updatePopup}
        setUpdatePopup={setUpdatePopup}
      />
    </Box>
  );
}
Example #9
Source File: index.js    From plant-3d-explorer with GNU Affero General Public License v3.0 4 votes vote down vote up
export default function Carousel () {
  const canvasRef = useRef(null)
  const containerRef = useRef(null)
  const windowSider = useWindowSize()
  const [scan] = useScan()
  const [urlList, setUrlList] = useState([])
  const [context, setContext] = useState(null)
  const [dragging, setDragging] = useState(false)
  const [picturesLayout, setPicturesLayout] = useState([])
  const cameraPoses = ((scan && scan.camera.poses) || [])
  const [imgs] = useImgLoader(urlList)
  const [hovered, setHovered] = useHoveredCamera()
  const [selected, setSelected] = useSelectedcamera()
  const large = moduleHeight * (6000 / 4000)
  let sizes
  const [carousel] = useCarousel()

  const hoveredLayout = useRef(null)
  const selectedLayout = useRef(null)

  const imageSet = useImageSet(carousel.photoSet)
  useEffect(
    () => {
      if (imageSet) {
        // Portion of code to get only 'rgb' images in dataset (doesn't work)
        // if(carousel.photoSet == 'images'){
        //   let index = 0
        //   imageSet.forEach(function(image){
        //     if(image.path.includes('rgb')){
        //       //imageSet.splice(index, 1)
        //       index--
        //     }
        //     index++
        //   })
        // }
        setUrlList(imageSet.map((d) => d.path))
      }
    },
    [imageSet]
  )

  useEffect(
    () => {
      const { width, height } = getSize(containerRef.current)
      const context = canvasRef.current.getContext('2d')
      scaleCanvas(canvasRef.current, context, width, height)
      context.width = width
      context.height = height
      setContext(context)
    },
    [windowSider, containerRef.current, canvasRef.current]
  )

  useEffect(
    () => {
      if (context) {
        const { width, height } = getSize(containerRef.current)
        sizes = {
          width,
          large,
          normal: (width / urlList.length),
          block: (
            (
              width - (
                (hovered || selected)
                  ? large
                  : 0
              )
            ) /
            (
              (hovered || selected)
                ? urlList.length - 1
                : urlList.length
            )
          )
        }

        let last = { x: 0, width: 0, normalX: 0, normalWidth: 0 }

        hoveredLayout.current = null
        selectedLayout.current = null
        setPicturesLayout(
          cameraPoses.map((d, i) => {
            const isSelected = selected && d.id === selected.id
            const isHovered = hovered && d.id === hovered.id
            const x = last.x + last.width
            const width = selected
              ? isSelected
                ? sizes.large
                : sizes.block
              : isHovered
                ? sizes.large
                : sizes.block
            const normalX = last.normalX + last.normalWidth

            const obj = {
              item: {
                ...d,
                photoUri: imageSet ? imageSet[i].path : null,
                texture: imageSet ? imageSet[i].texture : null
              },
              x,
              normalX,
              width,
              normalWidth: sizes.normal,
              height,
              hovered: isHovered,
              selected: isSelected
            }

            last = obj

            if (isHovered) hoveredLayout.current = obj
            if (isSelected) selectedLayout.current = obj

            return obj
          })
        )
      }
    },
    [windowSider, context, hovered, selected, urlList, cameraPoses]
  )

  if (context) {
    const { width, height } = getSize(containerRef.current)
    context.clearRect(0, 0, width, height)
    picturesLayout.forEach((d, i) => {
      if (imgs[d.item.photoUri]) {
        const imgWidth = imgs[d.item.photoUri].width
        const imgHeight = imgs[d.item.photoUri].height
        const ratio = imgWidth / large
        const sx = (imgWidth / 2) - (d.width * ratio * 0.5)
        const sy = 0

        context.globalAlpha = (d.hovered || d.selected) ? 1 : 0.5
        context.drawImage(
          imgs[d.item.photoUri],
          sx,
          sy,
          d.width * ratio,
          imgHeight,

          d.x,
          0,
          d.width,
          height
        )
      } else {
        context.fillStyle = d.hovered ? 'white' : 'grey'
        context.fillRect(
          d.x,
          0,
          d.width,
          height
        )
        context.fillStyle = 'black'
      }
    })
  }

  useEffect(
    () => {
      const handler = (e) => {
        setDragging(false)
        document.body.style.cursor = null
      }
      const moveHander = (e) => {
        if (dragging && e.movementX !== 0) {
          const dX = e.movementX < 0
            ? e.clientX - (dragging.from - dragging.triggerLeft)
            : e.clientX - (dragging.from - (dragging.triggerLeft + dragging.triggerWidth))
          const pictureDragged = picturesLayout
            .find((d) => d.x <= dX && (d.x + d.width) >= dX)

          if (pictureDragged) {
            setSelected(pictureDragged.item)
          }
        }
      }
      if (dragging) {
        window.addEventListener('mouseup', handler)
        window.addEventListener('mousemove', moveHander)
      } else {
        window.removeEventListener('mouseup', handler)
        window.removeEventListener('mousemove', moveHander)
      }

      return () => {
        window.removeEventListener('mouseup', handler)
        window.removeEventListener('mousemove', moveHander)
      }
    },
    [dragging, picturesLayout, selectedLayout]
  )

  const eventsFn = {
    onMouseMove: (e) => {
      const dX = !selectedLayout.current && hoveredLayout.current
        ? e.movementX < 0
          ? e.clientX - (hoveredLayout.current.width * 0.5) + hoveredLayout.current.normalWidth
          : e.clientX + (hoveredLayout.current.width * 0.5)
        : e.clientX
      const pictureHovered = picturesLayout
        .find((d) => d.x <= dX && (d.x + d.width) >= dX)

      if (pictureHovered && hovered !== pictureHovered.item) {
        setHovered(pictureHovered ? pictureHovered.item : null)
      }
    },
    onMouseOut: (e) => {
      setHovered(null)
    },
    onClick: () => {
      if (hovered) {
        setSelected(
          (selected && selected.id === hovered.id)
            ? null
            : hovered
        )
      }
    }
  }

  return <Container ref={containerRef}>

    <SVGCartridge
      large={large}
      hoveredLayout={hoveredLayout.current}
      selectedLayout={selectedLayout.current}
      eventsFn={eventsFn}
    />

    <Canvas ref={canvasRef} />

    {
      selectedLayout.current && <SvgDnG>
        <g transform={`translate(0, ${moduleHeight * 0.5})`}>
          <line
            x1={0}
            x2={'100%'}
            y1={0}
            y2={0}
            strokeWidth={1}
            stroke={green}
          />
          <g
            transform={`translate(${selectedLayout.current.x}, 0)`}
          >
            <rect
              style={{
                cursor: !dragging && 'grab'
              }}
              y={-15}
              width={selectedLayout.current.width}
              height={30}
              rx={15}
              ry={15}
              fill={green}
              onMouseDown={(e) => {
                const bb = e.target.getBoundingClientRect()
                document.body.style.cursor = 'grabbing'
                setDragging({
                  from: e.clientX,
                  triggerLeft: bb.left,
                  triggerWidth: bb.width
                })
              }}
            />
            <image
              xlinkHref={dragNdropIco}
              x={(large * 0.5) - 20}
              y={-8}
            />
          </g>
        </g>
      </SvgDnG>
    }
  </Container>
}