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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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>
)
}