react-use#useLocalStorage JavaScript Examples

The following examples show how to use react-use#useLocalStorage. 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: _Helpers.js    From acy-dex-interface with MIT License 6 votes vote down vote up
export function useLocalStorageByChainId(chainId, key, defaultValue) {
  const [internalValue, setInternalValue] = useLocalStorage(key, {});

  const setValue = useCallback(
    value => {
      setInternalValue(internalValue => {
        if (typeof value === "function") {
          value = value(internalValue[chainId] || defaultValue);
        }
        const newInternalValue = {
          ...internalValue,
          [chainId]: value
        };
        return newInternalValue;
      });
    },
    [chainId, setInternalValue, defaultValue]
  );

  let value;
  if (chainId in internalValue) {
    value = internalValue[chainId];
  } else {
    value = defaultValue;
  }

  return [value, setValue];
}
Example #2
Source File: themes.js    From fluentui-starter with MIT License 6 votes vote down vote up
export function DynamicThemeProvider({ children }) {
  const [theme, setTheme] = useLocalStorage("theme", "default");

  const changeTheme = (name) => themes[name] && setTheme(name);
  const themeContextValue = { theme, changeTheme };

  return (
    <ThemeContext.Provider value={themeContextValue}>
      <ThemeConsumer>{children}</ThemeConsumer>
    </ThemeContext.Provider>
  );
}
Example #3
Source File: index.js    From acy-dex-interface with MIT License 6 votes vote down vote up
export function useLocalStorageByChainId(chainId, key, defaultValue) {
  const [internalValue, setInternalValue] = useLocalStorage(key, {});

  const setValue = useCallback(
    value => {
      setInternalValue(internalValue => {
        if (typeof value === "function") {
          value = value(internalValue[chainId] || defaultValue);
        }
        const newInternalValue = {
          ...internalValue,
          [chainId]: value
        };
        return newInternalValue;
      });
    },
    [chainId, setInternalValue, defaultValue]
  );

  let value;
  if (chainId in internalValue) {
    value = internalValue[chainId];
  } else {
    value = defaultValue;
  }

  return [value, setValue];
}
Example #4
Source File: useFilters.js    From Turnip-Calculator with MIT License 6 votes vote down vote up
useFilters = () => {
  const [filters, saveFilters] = useLocalStorage("filters", []);

  // Array of strings
  const inputFilters = useMemo(
    () =>
      Array.from({ length: 13 }).map((v, i) =>
        String(Number(filters[i]) || "")
      ),
    [filters]
  );

  // Array of numbers
  const sanitizedFilters = useMemo(
    () =>
      Array.from({ length: 13 }).map((v, i) => Number(filters[i]) || undefined),
    [filters]
  );

  useEffect(() => {
    if (!Array.isArray(filters)) {
      saveFilters([]);
    }
  }, [filters, saveFilters]);

  return {
    filters: sanitizedFilters,
    inputFilters,
    saveFilters,
  };
}
Example #5
Source File: Localizer.jsx    From Turnip-Calculator with MIT License 6 votes vote down vote up
Localizer = () => {
  const { i18n } = useTranslation();
  const classes = useStyles();
  const [defaultLang, setDefaultLang] = useLocalStorage("i18n");

  // First mount effect
  useEffect(() => {
    i18n.changeLanguage(defaultLang);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return useMemo(
    () => (
      <Box mx={["0%", "10%", "25%"]}>
        <Breadcrumbs
          classes={classes}
          maxItems={list.length}
          component="div"
          aria-label="languages"
        >
          {list.map(([tag, name]) => (
            <Link
              key={tag}
              href="#"
              onClick={() => {
                i18n.changeLanguage(tag);
                setDefaultLang(tag);
              }}
            >
              {name}
            </Link>
          ))}
        </Breadcrumbs>
      </Box>
    ),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    []
  );
}
Example #6
Source File: history-context.js    From NextBook with MIT License 6 votes vote down vote up
export function HistoryContextProvider(props) {
  const [history, setHistory] = useLocalStorage('visitedChapters', [])
  const router = useRouter()

  useEffect(() => {
    // update history array with every page visit
    if (!history.includes(router.asPath)) {
      setHistory([...history, router.asPath])
    }
  }, [router.asPath, setHistory, history])

  const context = {
    history: history
  }

  return (
    <HistoryContext.Provider value={context}>
      {props.children}
    </HistoryContext.Provider>
  )
}
Example #7
Source File: sidebar-context.js    From NextBook with MIT License 6 votes vote down vote up
export function SideBarContextProvider(props) {
  const isWide = useMedia('(min-width: 1024px)', false)
  const [storedSideBar, setStoredSideBar] = useLocalStorage(
    'sideBar',
    isWide ? true : false
  )
  const [sideBar, setSideBar] = useState(storedSideBar)

  function toggleSideBar() {
    setStoredSideBar(!sideBar)
    setSideBar((currentValue) => {
      return !currentValue
    })
  }

  function hideSideBar() {
    setStoredSideBar(false)
    setSideBar(false)
  }

  const context = {
    sideBar: sideBar,
    toggleSideBar: toggleSideBar,
    hideSideBar: hideSideBar
  }

  return (
    <SideBarContext.Provider value={context}>
      {props.children}
    </SideBarContext.Provider>
  )
}
Example #8
Source File: theme-context.js    From NextBook with MIT License 6 votes vote down vote up
export function ThemeContextProvider(props) {
  const [storedTheme, setStoredTheme] = useLocalStorage('theme', 'light')
  const [theme, setTheme] = useState(storedTheme)
  const isBrowser = typeof window !== 'undefined'
  const isWindows = isBrowser && window.navigator.userAgent.indexOf('Win') != -1

  function toggleTheme() {
    const newTheme = theme === 'light' ? 'dark' : 'light'
    setStoredTheme(newTheme)
    setTheme(newTheme)
  }

  useEffect(() => {
    document.documentElement.className = `${theme}${
      isWindows ? ' with-custom-webkit-scrollbars' : ''
    }`
  }, [theme, isWindows])

  const context = {
    theme: theme,
    toggleTheme: toggleTheme
  }

  return (
    <ThemeContext.Provider value={context}>
      {props.children}
    </ThemeContext.Provider>
  )
}
Example #9
Source File: _Helpers.js    From acy-dex-interface with MIT License 5 votes vote down vote up
export function useLocalStorageSerializeKey(key, value, opts) {
  key = JSON.stringify(key);
  return useLocalStorage(key, value, opts);
}
Example #10
Source File: QuantileRange.jsx    From Turnip-Calculator with MIT License 5 votes vote down vote up
QuantileProvider = (props) => {
  const [range, setRange] = useLocalStorage("quantile-range", 75);

  return <QuantileContext.Provider {...props} value={[range, setRange]} />;
}
Example #11
Source File: Actions.js    From covid19india-react with MIT License 5 votes vote down vote up
Actions = ({date, setDate, dates, lastUpdatedDate}) => {
  const [showUpdates, setShowUpdates] = useState(false);
  const [newUpdate, setNewUpdate] = useLocalStorage('newUpdate', false);
  const [lastViewedLog, setLastViewedLog] = useLocalStorage('lastViewedLog', 0);
  const [isTimelineMode, setIsTimelineMode] = useState(false);

  const {data: updates} = useSWR(`${API_DOMAIN}/updatelog/log.json`, fetcher, {
    refreshInterval: API_REFRESH_INTERVAL,
  });

  useEffect(() => {
    if (updates !== undefined) {
      const lastTimestamp = updates.slice().reverse()[0].timestamp * 1000;
      if (lastTimestamp !== lastViewedLog) {
        setNewUpdate(true);
        setLastViewedLog(lastTimestamp);
      }
    }
  }, [lastViewedLog, updates, setLastViewedLog, setNewUpdate]);

  const maxLastUpdatedDate = useMemo(() => {
    return formatDateObjIndia(
      max(
        [lastViewedLog, lastUpdatedDate]
          .filter((date) => date)
          .map((date) => parseIndiaDate(date))
      )
    );
  }, [lastViewedLog, lastUpdatedDate]);

  return (
    <>
      <ActionsPanel
        {...{
          lastUpdatedDate: maxLastUpdatedDate,
          newUpdate,
          isTimelineMode,
          setIsTimelineMode,
          showUpdates,
          date,
          setDate,
          dates,
          setNewUpdate,
          setShowUpdates,
        }}
      />

      {showUpdates && (
        <Suspense fallback={<div />}>
          <Updates {...{updates}} />
        </Suspense>
      )}
    </>
  );
}
Example #12
Source File: index.js    From acy-dex-interface with MIT License 5 votes vote down vote up
export function useLocalStorageSerializeKey(key, value, opts) {
  key = JSON.stringify(key);
  return useLocalStorage(key, value, opts);
}
Example #13
Source File: index.js    From acy-dex-interface with MIT License 5 votes vote down vote up
export function useLocalStorageSerializeKey(key, value, opts) {
  key = JSON.stringify(key);
  return useLocalStorage(key, value, opts);
}
Example #14
Source File: home.js    From covid19Nepal-react with MIT License 4 votes vote down vote up
function Home(props) {
  const [states, setStates] = useState(null);
  const [stateDistrictWiseData, setStateDistrictWiseData] = useState(null);
  const [districtZones, setDistrictZones] = useState(null);
  const [stateTestData, setStateTestData] = useState(null);
  const [lastUpdated, setLastUpdated] = useState('');
  const [timeseries, setTimeseries] = useState(null);
  const [fetched, setFetched] = useState(false);
  const [regionHighlighted, setRegionHighlighted] = useState({
    state: 'Total',
  });
  const [showUpdates, setShowUpdates] = useState(false);
  const [anchor, setAnchor] = useState(null);
  const [mapOption, setMapOption] = useState('confirmed');
  const [isTimeseriesIntersecting, setIsTimeseriesIntersecting] = useState(
    false
  );

  const [lastViewedLog, setLastViewedLog] = useLocalStorage(
    'lastViewedLog',
    null
  );
  const [newUpdate, setNewUpdate] = useLocalStorage('newUpdate', false);

  const Bell = useMemo(
    () => (
      <Icon.Bell
        onClick={() => {
          setShowUpdates(!showUpdates);
          setNewUpdate(false);
        }}
      />
    ),
    [setNewUpdate, showUpdates]
  );

  const BellOff = useMemo(
    () => (
      <Icon.BellOff
        onClick={() => {
          setShowUpdates(!showUpdates);
        }}
      />
    ),
    [showUpdates]
  );

  useEffectOnce(() => {
    getStates();
  });

  useEffectOnce(() => {
    axios
      .get('https://api.nepalcovid19.org/updatelog/log.json')
      .then((response) => {
        const lastTimestamp = response.data
          .slice()
          .reverse()[0]
          .timestamp.toString();
        if (lastTimestamp !== lastViewedLog) {
          setNewUpdate(true);
          setLastViewedLog(lastTimestamp);
        }
      })
      .catch((err) => {
        console.log(err);
      });
  });

  const getStates = async () => {
    try {
      const [
        {data: statesDailyResponse},
        {data: zonesResponse},
      ] = await Promise.all([
        axios.get('https://api.nepalcovid19.org/states_daily.json'),
        axios.get(`${DATA_DIR}/zones.json`),
      ]);

      const [
        {data},
        {data: stateDistrictWiseResponse},
        {data: stateTestData},
      ] = await Promise.all([
        axios.get('https://api.nepalcovid19.org/latest_data.json'),
        axios.get('https://api.nepalcovid19.org/state-district-wise.json'),
        axios.get('https://api.nepalcovid19.org/state_test_data.json'),
      ]);

      setStates(data.statewise);
      setDistrictZones(parseDistrictZones(zonesResponse.zones));

      const ts = parseStateTimeseries(statesDailyResponse);
      ts['TT'] = preprocessTimeseries(data.cases_time_series);
      // Testing data timeseries
      const testTs = parseStateTestTimeseries(stateTestData.states_tested_data);
      testTs['TT'] = parseTotalTestTimeseries(data.tested);
      // Merge
      const tsMerged = mergeTimeseries(ts, testTs);
      setTimeseries(tsMerged);

      setLastUpdated(data.statewise[0].lastupdatedtime);

      const testData = [...stateTestData.states_tested_data].reverse();
      const totalTest = data.tested[data.tested.length - 1];
      testData.push({
        updatedon: totalTest.updatetimestamp.split(' ')[0],
        totaltested: totalTest.totalsamplestested,
        source: totalTest.source,
        state: 'Total',
      });
      setStateTestData(testData);

      setStateDistrictWiseData(stateDistrictWiseResponse);
      setFetched(true);
    } catch (err) {
      console.log(err);
    }
  };

  const onHighlightState = useCallback((state) => {
    if (!state) return setRegionHighlighted(null);
    setRegionHighlighted({state: state.state});
  }, []);

  const onHighlightDistrict = useCallback((district, state) => {
    if (!state && !district) return setRegionHighlighted(null);
    setRegionHighlighted({district, state: state.state});
  }, []);

  const options = {
    rootMargin: '0px 0px 0px 0px',
  };

  return (
    <React.Fragment>
      <div className="Home">
        <Helmet>
          <title>Coronavirus Outbreak in Nepal - nepalcovid19.org</title>
          <meta
            name="title"
            content="Coronavirus Outbreak in Nepal: Latest Map and Case Count"
          />
        </Helmet>

        <div className="home-left">
          <div className="header fadeInUp" style={{animationDelay: '1s'}}>
            {/* <LanguageSwitcher />*/}
            {fetched && <Search districtZones={districtZones} />}

            <div className="actions">
              <h5>
                {isNaN(Date.parse(formatDate(lastUpdated)))
                  ? ''
                  : formatDateAbsolute(lastUpdated)}
              </h5>
              {fetched && !showUpdates && (
                <div className="bell-icon">
                  {fetched && Bell}
                  {newUpdate && <div className="indicator"></div>}
                </div>
              )}
              {fetched && showUpdates && BellOff}
            </div>
          </div>

          {showUpdates && <Updates />}

          {states && <Level data={states[0]} />}
          {timeseries && <Minigraph timeseries={timeseries['TT']} />}
          {stateDistrictWiseData && (
            <Table
              states={states}
              summary={false}
              districts={stateDistrictWiseData}
              zones={districtZones}
              regionHighlighted={regionHighlighted}
              setRegionHighlighted={setRegionHighlighted}
              onHighlightState={onHighlightState}
              onHighlightDistrict={onHighlightDistrict}
            />
          )}
        </div>

        <div className="home-right">
          <React.Fragment>
            {fetched && (
              <MapExplorer
                mapName={'Nepal'}
                states={states}
                districts={stateDistrictWiseData}
                zones={districtZones}
                stateTestData={stateTestData}
                regionHighlighted={regionHighlighted}
                setRegionHighlighted={setRegionHighlighted}
                anchor={anchor}
                setAnchor={setAnchor}
                mapOption={mapOption}
                setMapOption={setMapOption}
              />
            )}

            <Observer
              options={options}
              onChange={({isIntersecting}) =>
                setIsTimeseriesIntersecting(isIntersecting)
              }
            >
              <div>
                {timeseries && (
                  <TimeSeriesExplorer
                    timeseries={
                      timeseries[
                        STATE_CODES_REVERSE[regionHighlighted?.state] || 'TT'
                      ]
                    }
                    activeStateCode={
                      STATE_CODES_REVERSE[regionHighlighted?.state] || 'TT'
                    }
                    onHighlightState={onHighlightState}
                    states={states}
                    anchor={anchor}
                    setAnchor={setAnchor}
                    isIntersecting={isTimeseriesIntersecting}
                  />
                )}
              </div>
            </Observer>
          </React.Fragment>
        </div>
      </div>
      {fetched && <Footer />}
    </React.Fragment>
  );
}
Example #15
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 #16
Source File: row.js    From covid19Nepal-react with MIT License 4 votes vote down vote up
function Row({
  index,
  state,
  districts,
  zones,
  regionHighlighted,
  onHighlightState,
  onHighlightDistrict,
}) {
  const [sortedDistricts, setSortedDistricts] = useState(districts);
  const [showDistricts, setShowDistricts] = useState(false);
  const [sortData, setSortData] = useLocalStorage('districtSortData', {
    sortColumn: 'confirmed',
    isAscending: false,
  });

  const history = useHistory();
  const {t} = useTranslation();

  const Chevron = useMemo(
    () => (
      <span
        className={classnames(
          'dropdown',
          {rotateRightDown: showDistricts},
          {rotateDownRight: !showDistricts}
        )}
      >
        <Icon.ChevronDown />
      </span>
    ),
    [showDistricts]
  );

  const _onHighlightState = useCallback(
    (state) => {
      if (!equal(state.state, regionHighlighted?.state)) {
        onHighlightState(state);
      }
    },
    [onHighlightState, regionHighlighted]
  );

  const doSort = useCallback(
    (sortData) => {
      const sorted = {};
      Object.keys(sortedDistricts)
        .sort((district1, district2) => {
          if (sortData.sortColumn !== 'district') {
            return sortData.isAscending
              ? parseInt(sortedDistricts[district1][sortData.sortColumn]) -
                  parseInt(sortedDistricts[district2][sortData.sortColumn])
              : parseInt(sortedDistricts[district2][sortData.sortColumn]) -
                  parseInt(sortedDistricts[district1][sortData.sortColumn]);
          } else {
            return sortData.isAscending
              ? district1.localeCompare(district2)
              : district2.localeCompare(district1);
          }
        })
        .forEach((key) => {
          sorted[key] = sortedDistricts[key];
        });
      setSortedDistricts(sorted);
    },
    [sortedDistricts]
  );

  const handleSort = useCallback(
    (statistic) => {
      const newSortData = {
        isAscending: !sortData.isAscending,
        sortColumn: statistic,
      };
      doSort(newSortData);
      setSortData(Object.assign({}, sortData, newSortData));
    },
    [doSort, setSortData, sortData]
  );

  useEffectOnce(() => {
    if (state.statecode !== 'TT') doSort(sortData);
  });

  return (
    <React.Fragment>
      <tr
        className={classnames(
          'state',
          {'is-total': state.statecode === 'TT'},
          {'is-highlighted': regionHighlighted?.state === state.state},
          {'is-odd': index % 2 === 0}
        )}
        onMouseEnter={() => _onHighlightState(state)}
        onClick={
          state.statecode !== 'TT'
            ? () => {
                setShowDistricts(!showDistricts);
              }
            : null
        }
      >
        <td>
          <div className="title-chevron">
            {state.statecode !== 'TT' && Chevron}
            <span className="title-icon">
              {t(state.state)}

              <span
                data-tip={[t(`${state.statenotes}`)]}
                data-event="touchstart mouseover"
                data-event-off="mouseleave"
                onClick={(e) => e.stopPropagation()}
              >
                {state.statenotes && <Icon.Info />}
              </span>
            </span>
          </div>
        </td>

        {STATE_ROW_STATISTICS.map((statistic, index) => (
          <StateCell key={index} state={state} statistic={statistic} />
        ))}
      </tr>

      {showDistricts && (
        <React.Fragment>
          <tr className="is-spacer">
            <td colSpan={5}>
              <p />
            </td>
          </tr>

          <tr className={'state-last-update'}>
            <td colSpan={3} style={{paddingBottom: 0}}>
              <p className="spacer"></p>
              <p>
                {isNaN(Date.parse(formatDate(state.lastupdatedtime)))
                  ? ''
                  : `${t('Last updated')} ${formatDistance(
                      new Date(formatDate(state.lastupdatedtime)),
                      new Date()
                    )} ${t('ago')}`}
              </p>
              {sortedDistricts?.Unknown && (
                <div className="disclaimer">
                  <Icon.AlertCircle />
                  {t('District-wise numbers are under reconciliation')}
                </div>
              )}
            </td>
            <td
              align="center"
              className="state-page-link"
              colSpan={2}
              onClick={() => {
                history.push(`state/${state.statecode}`);
              }}
            >{`View ${t(state.state)}'s Page`}</td>
          </tr>

          <tr className={classnames('district-heading')}>
            <td onClick={() => handleSort('district')}>
              <div className="heading-content">
                <abbr title="District">{t('District')}</abbr>
                <div
                  style={{
                    display:
                      sortData.sortColumn === 'district' ? 'initial' : 'none',
                  }}
                >
                  {sortData.isAscending ? (
                    <div className="arrow-up" />
                  ) : (
                    <div className="arrow-down" />
                  )}
                </div>
              </div>
            </td>

            {DISTRICT_ROW_STATISTICS.map((statistic, index) => (
              <DistrictHeaderCell
                key={index}
                handleSort={handleSort}
                statistic={statistic}
                sortData={sortData}
              />
            ))}
          </tr>
        </React.Fragment>
      )}

      {showDistricts &&
        Object.keys(sortedDistricts).map((district, index) => (
          <DistrictRow
            key={district}
            state={state}
            district={district}
            districts={districts}
            zone={zones[district]}
            sortedDistricts={sortedDistricts}
            regionHighlighted={regionHighlighted}
            onHighlightDistrict={onHighlightDistrict}
          />
        ))}

      {showDistricts && (
        <tr className="is-spacer">
          <td colSpan={5}>
            <p />
            <ReactTooltip
              id="district"
              place="right"
              type="dark"
              effect="solid"
              multiline={true}
              scrollHide={true}
              globalEventOff="click"
            />
          </td>
        </tr>
      )}
    </React.Fragment>
  );
}
Example #17
Source File: table.js    From covid19Nepal-react with MIT License 4 votes vote down vote up
function Table({
  states,
  districts,
  zones,
  regionHighlighted,
  onHighlightState,
  onHighlightDistrict,
}) {
  const [sortData, setSortData] = useLocalStorage('sortData', {
    sortColumn: 'confirmed',
    isAscending: false,
  });
  const {t} = useTranslation();

  const [sortedStates, setSortedStates] = useState(
    states.filter((state) => state.statecode !== 'TT')
  );

  const FineprintTop = useMemo(
    () => (
      <React.Fragment>
        <h5
          className="table-fineprint fadeInUp"
          style={{animationDelay: '1.5s'}}
        >
          {t('Compiled from State Govt. numbers')},{' '}
          <Link to="/faq" style={{color: '#6c757d'}}>
            {t('know more')}!
          </Link>
        </h5>
        <h5
          className="table-fineprint fadeInUp"
          style={{animationDelay: '1.5s'}}
        >
          District zones as published by Govt, source
        </h5>
      </React.Fragment>
    ),
    [t]
  );

  const FineprintBottom = useMemo(
    () => (
      <h5 className="table-fineprint fadeInUp" style={{animationDelay: '1s'}}>
        {states.slice(1).filter((s) => s && s.confirmed > 0).length} Province
        Affected
      </h5>
    ),
    [states]
  );

  const doSort = useCallback(
    (sortData) => {
      const newSortedStates = [...sortedStates].sort((x, y) => {
        if (sortData.sortColumn !== 'state') {
          return sortData.isAscending
            ? parseInt(x[sortData.sortColumn]) -
                parseInt(y[sortData.sortColumn])
            : parseInt(y[sortData.sortColumn]) -
                parseInt(x[sortData.sortColumn]);
        } else {
          return sortData.isAscending
            ? x[sortData.sortColumn].localeCompare(y[sortData.sortColumn])
            : y[sortData.sortColumn].localeCompare(x[sortData.sortColumn]);
        }
      });
      setSortedStates(newSortedStates);
    },
    [sortedStates]
  );

  const handleSort = useCallback(
    (statistic) => {
      const newSortData = {
        isAscending: !sortData.isAscending,
        sortColumn: statistic,
      };
      doSort(newSortData);
      setSortData(Object.assign({}, sortData, newSortData));
    },
    [doSort, setSortData, sortData]
  );

  useEffectOnce(() => {
    doSort(sortData);
  });

  if (states.length > 0) {
    return (
      <React.Fragment>
        <ReactTooltip
          place="right"
          type="dark"
          effect="solid"
          multiline={true}
          globalEventOff="click"
        />

        {FineprintTop}

        <table className="table fadeInUp" style={{animationDelay: '1.8s'}}>
          <thead>
            <tr>
              <th className="state-heading" onClick={() => handleSort('state')}>
                <div className="heading-content">
                  <abbr title="State">{t('Province')}</abbr>
                  <div
                    style={{
                      display:
                        sortData.sortColumn === 'state' ? 'initial' : 'none',
                    }}
                  >
                    <div
                      className={classnames(
                        {'arrow-up': sortData.isAscending},
                        {'arrow-down': !sortData.isAscending}
                      )}
                    />
                  </div>
                </div>
              </th>
              {STATE_ROW_STATISTICS.map((statistic, index) => (
                <StateHeaderCell
                  key={index}
                  handleSort={handleSort}
                  sortData={sortData}
                  statistic={statistic}
                />
              ))}
            </tr>
          </thead>

          {states && (
            <tbody>
              {sortedStates.map((state, index) => {
                if (state.confirmed > 0 && state.statecode !== 'TT') {
                  return (
                    <Row
                      key={state.statecode}
                      state={state}
                      districts={districts[state.state]?.districtData}
                      zones={zones[state.state]}
                      regionHighlighted={
                        equal(regionHighlighted?.state, state.state)
                          ? regionHighlighted
                          : null
                      }
                      onHighlightState={onHighlightState}
                      onHighlightDistrict={onHighlightDistrict}
                    />
                  );
                }
                return null;
              })}
            </tbody>
          )}

          {states && (
            <tbody>
              <Row
                key={0}
                state={states[0]}
                onHighlightState={onHighlightState}
              />
            </tbody>
          )}
        </table>
        {states && FineprintBottom}
      </React.Fragment>
    );
  } else {
    return <div style={{height: '50rem'}}></div>;
  }
}
Example #18
Source File: timeseriesexplorer.js    From covid19Nepal-react with MIT License 4 votes vote down vote up
function TimeSeriesExplorer({
  timeseries,
  activeStateCode,
  onHighlightState,
  states,
  anchor,
  setAnchor,
  isIntersecting,
}) {
  const [chartType, setChartType] = useLocalStorage('timeseriesChartType', 1);

  const [timeseriesMode, setTimeseriesMode] = useLocalStorage(
    'timeseriesMode',
    true
  );
  const [timeseriesLogMode, setTimeseriesLogMode] = useLocalStorage(
    'timeseriesLogMode',
    false
  );
  const {t} = useTranslation();

  return (
    <div
      className={`TimeSeriesExplorer ${
        anchor === 'timeseries' ? 'stickied' : ''
      }`}
      style={{display: anchor === 'mapexplorer' ? 'none' : ''}}
    >
      <div
        className="timeseries-header fadeInUp"
        style={{animationDelay: '2.5s'}}
      >
        {window.innerWidth > 769 && anchor !== undefined && (
          <div
            className={`anchor ${anchor === 'timeseries' ? 'stickied' : ''}`}
            onClick={() => {
              setAnchor(anchor === 'timeseries' ? null : 'timeseries');
            }}
          >
            <Icon.Anchor />
          </div>
        )}

        <h1>{t('Spread Trends')}</h1>
        <div className="tabs">
          <div
            className={`tab ${chartType === 1 ? 'focused' : ''}`}
            onClick={() => {
              setChartType(1);
            }}
          >
            <h4>{t('Cumulative')}</h4>
          </div>
          <div
            className={`tab ${chartType === 2 ? 'focused' : ''}`}
            onClick={() => {
              setChartType(2);
            }}
          >
            <h4>{t('Daily')}</h4>
          </div>
        </div>

        <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"
              checked={timeseriesMode}
              className="switch"
              aria-label={t('Checked by default to scale uniformly.')}
              onChange={(event) => {
                setTimeseriesMode(!timeseriesMode);
              }}
            />
          </div>
          <div
            className={`timeseries-logmode ${
              chartType !== 1 ? 'disabled' : ''
            }`}
          >
            <label htmlFor="timeseries-logmode">{t('Logarithmic')}</label>
            <input
              id="timeseries-logmode"
              type="checkbox"
              checked={chartType === 1 && timeseriesLogMode}
              className="switch"
              disabled={chartType !== 1}
              onChange={(event) => {
                setTimeseriesLogMode(!timeseriesLogMode);
              }}
            />
          </div>
        </div>

        {states && (
          <div className="trends-state-name">
            <select
              value={activeStateCode}
              onChange={({target}) => {
                const selectedState = target.selectedOptions[0].getAttribute(
                  'statedata'
                );
                onHighlightState(JSON.parse(selectedState));
              }}
            >
              {states.map((s) => {
                return (
                  <option
                    value={s.statecode}
                    key={s.statecode}
                    statedata={JSON.stringify(s)}
                  >
                    {s.statecode === 'TT' ? t('All States') : t(s.state)}
                  </option>
                );
              })}
            </select>
          </div>
        )}
      </div>

      {timeseries && (
        <TimeSeries
          timeseriesProp={timeseries}
          chartType={chartType}
          mode={timeseriesMode}
          logMode={timeseriesLogMode}
          isTotal={activeStateCode === 'TT'}
        />
      )}
    </div>
  );
}
Example #19
Source File: App.js    From covid19Nepal-react with MIT License 4 votes vote down vote up
function App() {
  const pages = [
    {
      pageLink: '/',
      view: Home,
      displayName: 'Home',
      animationDelayForNavbar: 0.2,
      showInNavbar: true,
    },
    {
      pageLink: '/demographics',
      view: PatientDB,
      displayName: 'Demographics',
      animationDelayForNavbar: 0.3,
      showInNavbar: true,
    },
    {
      pageLink: '/deepdive',
      view: DeepDive,
      displayName: 'Deep Dive',
      animationDelayForNavbar: 0.4,
      showInNavbar: true,
    },
    {
      pageLink: '/essentials',
      view: Resources,
      displayName: 'Essentials',
      animationDelayForNavbar: 0.5,
      showInNavbar: true,
    },
    {
      pageLink: '/faq',
      view: FAQ,
      displayName: 'FAQ',
      animationDelayForNavbar: 0.6,
      showInNavbar: true,
    },
    {
      pageLink: '/state/:stateCode',
      view: State,
      displayName: 'State',
      animationDelayForNavbar: 0.7,
      showInNavbar: false,
    },
  ];

  const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
  const [isThemeSet] = useLocalStorage('isThemeSet', false);

  useEffectOnce(() => {
    if (
      window.matchMedia &&
      window.matchMedia('(prefers-color-scheme: dark)').matches &&
      !isThemeSet
    ) {
      setDarkMode(true);
    } else if (
      window.matchMedia &&
      !window.matchMedia('(prefers-color-scheme: dark)').matches &&
      !isThemeSet
    ) {
      setDarkMode(false);
    }
  });

  React.useEffect(() => {
    if (darkMode) {
      document.querySelector('body').classList.add('dark-mode');
    } else {
      document.querySelector('body').classList.remove('dark-mode');
    }
  }, [darkMode]);

  return (
    <div className="App">
      <Helmet>
        <script type="application/ld+json">
          {JSON.stringify(schemaMarkup)}
        </script>
      </Helmet>
      <Route
        render={({location}) => (
          <div className="Almighty-Router">
            <Navbar
              pages={pages}
              darkMode={darkMode}
              setDarkMode={setDarkMode}
            />
            <Switch location={location}>
              {pages.map((page, index) => {
                return (
                  <Route
                    exact
                    path={page.pageLink}
                    render={({match}) => (
                      <page.view key={match.params.stateCode || index} />
                    )}
                    key={index}
                  />
                );
              })}
              <Redirect to="/" />
            </Switch>
          </div>
        )}
      />
    </div>
  );
}
Example #20
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 #21
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 #22
Source File: Player.jsx    From xetera.dev with MIT License 4 votes vote down vote up
PlayerControls = ({
  token,
  play,
  authorized,
  login,
  logout,
  trackList,
  volume,
  setVolume,
  refreshToken,
}) => {
  const inside = useRef()
  const mainPlayer = useRef()
  const [closed, setClosed] = useLocalStorage("playerClosed", true)
  const [trackListOpen, { toggle: toggleTrackList, off: closeTrackList }] =
    useBoolean(false)
  const [volumeOpen, { toggle: toggleVolume, off: closeVolume }] =
    useBoolean(false)
  const [timedOut, setTimedOut] = useState(false)
  const timeoutTimer = useRef()
  const playbackState = usePlaybackState(true, 100)
  const player = useSpotifyPlayer()
  const playerDevice = usePlayerDevice()
  const scrollerColor = "bgSecondary"

  useOutsideClick({
    ref: inside,
    handler: () => {
      closeTrackList()
      closeVolume()
    },
  })

  function takeControl() {
    if (playerDevice?.device_id === undefined) return console.log("no device")

    if (token.current) {
      // https://developer.spotify.com/documentation/web-api/reference/#endpoint-transfer-a-users-playback
      fetch(`https://api.spotify.com/v1/me/player`, {
        method: "PUT",
        body: JSON.stringify({
          device_ids: [playerDevice.device_id],
          play: false,
        }),
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token.current}`,
        },
      })
    }
  }
  async function playSong(offset, isRetry = false) {
    const res = await play(playerDevice.device_id, trackListUris, offset)
    if (!res.ok && res.status === 401) {
      if (isRetry) {
        console.log(
          `Attempted to retry playing a song after a token refresh and it failed again`
        )
        return
      }
      refreshToken().then(() => {
        playSong(offset, true)
      })
    }
  }
  function handleClick(e) {
    e.preventDefault()
    if (!authorized || timedOut) {
      login()
      return
    }
    // we don't want to handle clicks from the surrounding components
    if (e.target !== mainPlayer.current) {
      return
    }
    if (player) {
      player.togglePlay()
    }
  }
  useEffect(() => {
    if (!playbackState && !timeoutTimer.current) {
      timeoutTimer.current = setTimeout(() => {
        setTimedOut(true)
      }, 6000)
      return
    }
    if (playbackState && timeoutTimer.current) {
      clearTimeout(timeoutTimer.current)
    }
    if (playbackState && timedOut) {
      setTimedOut(false)
    }
  }, [Boolean(playbackState)])
  useEffect(takeControl, [playerDevice?.device_id, authorized])
  const trackListUris = trackList?.tracks.items.map(item => item.uri)

  const trackName = playbackState?.track_window.current_track.name
  const albumName = playbackState?.track_window.current_track.artists[0].name
  const albumArt = playbackState?.track_window.current_track.album.images[0].url
  const thisMonth = months[new Date().getMonth()]

  return (
    <>
      <AnimatePresence>
        {closed && (
          <MotionBox
            position="fixed"
            bottom={[2, null, 4, 8]}
            left={[2, null, 4, 8]}
            cursor="pointer"
            onClick={() => setClosed(false)}
            initial={{ opacity: 1 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 1 }}
            zIndex={2}
          >
            <Box
              background="bgSecondary"
              borderRadius="lg"
              p={2}
              h={10}
              w={10}
              className="spotify-button"
            >
              <StaticImage
                quality="100"
                alt="Spotify logo"
                src="./spotify.png"
                placeholder="none"
                style={{
                  filter: playbackState?.paused
                    ? "grayscale(1)"
                    : "grayscale(0)",
                }}
              />
            </Box>
          </MotionBox>
        )}
      </AnimatePresence>

      <MotionFlex
        ref={inside}
        position="fixed"
        bottom={[2, 4, 8]}
        left={[2, 4, 8]}
        alignItems="center"
        zIndex={8}
        variants={{
          closed: { y: 200 },
          open: { y: 0 },
        }}
        transition={{ stiffness: 50 }}
        initial="closed"
        animate={closed ? "closed" : "open"}
        exit="open"
        cursor="pointer"
      >
        <Flex
          alignItems="center"
          flexFlow="column"
          ref={mainPlayer}
          onClick={handleClick}
        >
          <Flex position="relative" width="300px" background="bgSecondary">
            <Flex
              position="absolute"
              bottom="-40px"
              top="-30px"
              borderRadius="sm"
              overflow="hidden"
              left="100%"
              pointerEvents={volumeOpen ? "auto" : "none"}
            >
              <MotionBox
                ml={1}
                height="100%"
                overflow="hidden"
                css={{
                  "&::-webkit-scrollbar": {
                    width: "8px",
                  },
                  "&::-webkit-scrollbar-track": {
                    width: "8px",
                  },
                }}
                display="flex"
                justifyContent="center"
                flexDirection="column"
                width="30px"
                background="bgSecondary"
                transition={{ type: "tween", duration: 0.3 }}
                variants={{
                  open: { x: 0, opacity: 1 },
                  closed: { x: -40, opacity: 0 },
                }}
                initial="closed"
                animate={volumeOpen ? "open" : "closed"}
              >
                <Text fontSize="xs" color="text.400" textAlign="center" pt={2}>
                  {Math.floor(volume * 100)}
                </Text>
                <Box p={2} h="full">
                  <Slider
                    height="100%"
                    orientation="vertical"
                    value={volume * 100}
                    onChange={e => setVolume(e / 100)}
                  >
                    <SliderTrack background="bg.100">
                      <SliderFilledTrack bg="brandBackground.200" />
                    </SliderTrack>
                    <SliderThumb />
                  </Slider>
                </Box>
              </MotionBox>
            </Flex>
            <Box
              width="100%"
              position="absolute"
              bottom="100%"
              overflow="hidden"
              right={0}
              pointerEvents={trackListOpen ? "auto" : "none"}
            >
              <MotionBox
                background="bg.100"
                transition={{ type: "tween" }}
                variants={{
                  open: { y: 0 },
                  closed: { y: 700 },
                }}
                initial="closed"
                exit="closed"
                animate={trackListOpen ? "open" : "closed"}
              >
                <Flex
                  className="themed-scrollable"
                  maxHeight="600px"
                  overflowY="auto"
                  borderColor="borderSubtle"
                  borderWidth="1px"
                  spacing={4}
                  alignItems="flex-start"
                  overflowX="hidden"
                  flexFlow="column"
                >
                  {trackList.tracks.items.map((r, i) => {
                    // album images are sorted from biggest to smallest
                    const { images } = r.album
                    const albumArt = images[images.length - 1]
                    return (
                      <Flex
                        py={2}
                        px={3}
                        w="full"
                        key={r.uri}
                        _hover={{ background: scrollerColor }}
                        onClick={() => {
                          if (!authorized) {
                            return login()
                          }
                          if (timedOut || !playerDevice) {
                            return
                          }
                          playSong(i)
                        }}
                        alignItems="center"
                        background={
                          playbackState?.track_window.current_track.uri ===
                          r.uri
                            ? "bgSecondary"
                            : ""
                        }
                        filter={
                          timedOut || !authorized
                            ? "grayscale(1)"
                            : "grayscale(0)"
                        }
                      >
                        <Box
                          width={7}
                          whiteSpace="nowrap"
                          textAlign="right"
                          color="text.400"
                          fontSize="xs"
                          pr={3}
                        >
                          {i + 1}
                        </Box>
                        <Image
                          src={albumArt?.url}
                          alt={`Song: ${r.name}`}
                          h={8}
                          w={8}
                          marginInlineEnd={2}
                          loading="lazy"
                          // spotify CDN doesn't support HTTP2 and needs to be
                          // marked as low priority to prevent it from hogging
                          // precious bandwidth
                          fetchpriorit="low"
                        />
                        <VStack alignItems="flex-start" spacing={0}>
                          <Text
                            fontWeight="bold"
                            fontSize="sm"
                            lineHeight="1.2"
                          >
                            {r.name}
                          </Text>
                          <Text fontSize="xs" lineHeight="1.2">
                            {r.artists[0]?.name ?? "Unknown artist"}
                          </Text>
                        </VStack>
                      </Flex>
                    )
                  })}
                </Flex>
                <Text
                  as="h2"
                  textAlign="center"
                  width="65%"
                  ml={6}
                  mt={1}
                  py={2}
                  lineHeight="1.2"
                  fontSize="xs"
                  fontWeight="medium"
                  color="text.100"
                  letterSpacing="1.1px"
                  textTransform="uppercase"
                >
                  {thisMonth} favorites
                </Text>
              </MotionBox>
            </Box>
            <Flex
              position="absolute"
              bottom="100%"
              right={0}
              left={0}
              justifyContent="space-between"
            >
              {authorized ? (
                <Tooltip label="Logout">
                  <Flex
                    background="bgSecondary"
                    p={1}
                    mb={1}
                    borderRadius="sm"
                    onClick={logout}
                  >
                    <RiLogoutBoxLine size={BUTTON_SIZE} />
                  </Flex>
                </Tooltip>
              ) : (
                <Box />
              )}
              <HStack spacing={1} mb={1} justifyContent="flex-end">
                <AnimatePresence>
                  {!closed && (
                    <Tooltip
                      label={
                        trackListOpen ? "Hide tracklist" : "Show tracklist"
                      }
                    >
                      <MotionFlex
                        initial={{ y: -20, opacity: 0 }}
                        animate={{ y: 0, opacity: 1 }}
                        exit={{ y: -20, opacity: 0 }}
                        transition={{ delay: 0.15, stiffness: 20 }}
                        background="bgSecondary"
                        p={1}
                        borderRadius="sm"
                        onClick={e => {
                          e.stopPropagation()
                          toggleTrackList()
                        }}
                      >
                        <RiFolderMusicLine size={BUTTON_SIZE} />
                      </MotionFlex>
                    </Tooltip>
                  )}
                </AnimatePresence>
                {authorized && (
                  <Tooltip label="Volume">
                    <MotionFlex
                      background="bgSecondary"
                      p={1}
                      borderRadius="sm"
                      onClick={e => {
                        e.stopPropagation()
                        toggleVolume()
                      }}
                    >
                      <RiVolumeUpLine size={BUTTON_SIZE} />
                    </MotionFlex>
                  </Tooltip>
                )}
                <AnimatePresence>
                  {!closed && (
                    <Tooltip label="Minimize">
                      <MotionFlex
                        background="bgSecondary"
                        p={1}
                        initial={{ y: -20, opacity: 0 }}
                        animate={{ y: 0, opacity: 1 }}
                        exit={{ y: -20, opacity: 0 }}
                        transition={{ delay: 0.3, stiffness: 20 }}
                        borderRadius="sm"
                        onClick={e => {
                          e.stopPropagation()
                          closeTrackList()
                          closeVolume()
                          setClosed(true)
                        }}
                      >
                        <RiCloseLine size={BUTTON_SIZE} />
                      </MotionFlex>
                    </Tooltip>
                  )}
                </AnimatePresence>
              </HStack>
            </Flex>
            <Flex
              w="70px"
              h="70px"
              overflow="hidden"
              maxWidth="100%"
              alignItems="center"
              justifyContent="center"
            >
              <AlbumCover
                state={
                  playbackState
                    ? { type: "ready", src: albumArt }
                    : !authorized
                    ? { type: "notAuthorized" }
                    : timedOut
                    ? { type: "timedOut" }
                    : { type: "waiting" }
                }
              />
            </Flex>
            <HStack marginInlineStart={1} p={2}>
              <VStack spacing={2} alignItems="flex-start" color="text.300">
                {trackName ? (
                  <Text
                    fontSize="sm"
                    fontWeight="bold"
                    lineHeight="1.2"
                    color="text.100"
                  >
                    {trackName}
                  </Text>
                ) : !authorized ? (
                  <Text fontSize="sm" fontWeight="bold" lineHeight="1.2">
                    Got Spotify premium?
                  </Text>
                ) : timedOut ? (
                  <Text fontSize="sm" fontWeight="bold" lineHeight="1">
                    Couldn't connect to Spotify
                  </Text>
                ) : (
                  <Skeleton height="15px" width="80px" />
                )}
                {albumName ? (
                  <Text fontSize="xs" lineHeight="1.2">
                    {albumName}
                  </Text>
                ) : !authorized ? (
                  <Text fontSize="xs" lineHeight="1">
                    Click to vibe to my playlists
                  </Text>
                ) : timedOut ? (
                  <Text fontSize="xs" lineHeight="1">
                    Click to re-authorize
                  </Text>
                ) : (
                  <Skeleton height="8px" width="30px" />
                )}
              </VStack>
            </HStack>
            {playbackState && (
              <Tooltip label="Track's Spotify page">
                <Link
                  p={1}
                  borderColor="borderSubtle"
                  right={2}
                  bottom={2}
                  w={7}
                  h={7}
                  target="_blank"
                  rel="noopener noreferrer"
                  href={`https://open.spotify.com/track/${playbackState.track_window.current_track.id}`}
                  position="absolute"
                  borderWidth="1px"
                  borderRadius="lg"
                  overflow="hidden"
                >
                  <StaticImage
                    quality="100"
                    alt="Spotify logo"
                    src="./spotify.png"
                    placeholder="none"
                  />
                </Link>
              </Tooltip>
            )}
          </Flex>
          <Flex
            background="bgSecondary"
            mt={1}
            w="full"
            h="35px"
            position="relative"
            borderTopRadius="sm"
            alignItems="center"
            justifyContent="center"
          >
            {!authorized ? (
              <Text fontSize="xs" color="text.400">
                This widget connects to your Spotify account
              </Text>
            ) : timedOut ? (
              <Text fontSize="xs" color="text.300">
                Maybe change devices in your Spotify app?
              </Text>
            ) : playbackState ? (
              <HStack py={1}>
                <Box
                  p={1}
                  borderColor="borderSubtle"
                  borderWidth="1px"
                  borderRadius="lg"
                  overflow="hidden"
                  onClick={() => player?.previousTrack()}
                >
                  {!playbackState?.disallows.skipping_prev && (
                    <RiSkipBackLine size={BUTTON_SIZE} />
                  )}
                </Box>
                <Box
                  p={1}
                  borderColor="borderSubtle"
                  borderWidth="1px"
                  borderRadius="lg"
                  overflow="hidden"
                  onClick={() => player?.togglePlay()}
                >
                  {!playbackState && (
                    <Skeleton
                      height={BUTTON_SIZE}
                      width={BUTTON_SIZE}
                      borderRadius="lg"
                    />
                  )}
                  {playbackState?.disallows.resuming && (
                    <RiPauseLine size={BUTTON_SIZE} />
                  )}
                  {playbackState?.disallows.pausing && (
                    <RiPlayLine size={BUTTON_SIZE} />
                  )}
                </Box>
                <Box
                  p={1}
                  borderColor="borderSubtle"
                  borderWidth="1px"
                  borderRadius="lg"
                  overflow="hidden"
                  onClick={() => player?.nextTrack()}
                >
                  {!playbackState?.disallows.skipping_next && (
                    <RiSkipForwardLine size={BUTTON_SIZE} />
                  )}
                </Box>
              </HStack>
            ) : (
              <Skeleton w="full" h="35px" />
            )}
            {playbackState && (
              <Seeker
                position={playbackState.position}
                duration={playbackState.duration}
                seek={e => player.seek(e)}
              />
            )}
          </Flex>
        </Flex>
      </MotionFlex>
    </>
  )
}
Example #23
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>
  )
}