date-fns#differenceInDays TypeScript Examples
The following examples show how to use
date-fns#differenceInDays.
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: RelativeRangePicker.tsx From gio-design with Apache License 2.0 | 6 votes |
function RelativeRangePicker({ disabledDate, timeRange, onSelect, onCancel, ...rest }: RangePickerProps) {
const defaultDates = parseStartAndEndDate(timeRange ?? 'day:2,1');
const [dates, setDates] = React.useState<[Date, Date]>(defaultDates as [Date, Date]);
const [endDateHidden, setEndDateHidden] = React.useState<boolean>(isYesterday(dates[1]));
const inputDisabled = !isNil(disabledDate);
const handleDisabledDate = (current: Date) =>
disabledDate?.(current) || isAfter(startOfDay(current), endDateHidden ? startOfYesterday() : startOfToday());
const handleOnOK = () => {
onSelect(`day:${differenceInDays(startOfToday(), dates[0]) + 1},${differenceInDays(startOfToday(), dates[1])}`);
};
return (
<InnerRangePanel
data-testid="relative-range-picker"
disableOK={!isValid(dates[0]) || !isValid(dates[1])}
header={<RelativeRangeHeader inputDisabled={inputDisabled} dateRange={dates} onRangeChange={setDates} onModeChange={setEndDateHidden} />}
body={
<RelativeRangeBody
dateRange={dates}
fixedMode={endDateHidden}
disabledDate={handleDisabledDate}
onRangeChange={setDates}
/>
}
onCancel={onCancel}
onOK={handleOnOK}
{...rest}
/>
);
}
Example #2
Source File: Album.tsx From jellyfin-audio-player with MIT License | 6 votes |
Album: React.FC = () => {
const { params: { id } } = useRoute<Route>();
const dispatch = useAppDispatch();
// Retrieve the album data from the store
const album = useTypedSelector((state) => state.music.albums.entities[id]);
const albumTracks = useTypedSelector((state) => state.music.tracks.byAlbum[id]);
// Define a function for refreshing this entity
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]);
// Auto-fetch the track data periodically
useEffect(() => {
if (!album?.lastRefreshed || differenceInDays(album?.lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
refresh();
}
}, [album?.lastRefreshed, refresh]);
return (
<TrackListView
trackIds={albumTracks || []}
title={album?.Name}
artist={album?.AlbumArtist}
entityId={id}
refresh={refresh}
playButtonText={t('play-album')}
shuffleButtonText={t('shuffle-album')}
downloadButtonText={t('download-album')}
deleteButtonText={t('delete-album')}
/>
);
}
Example #3
Source File: Playlist.tsx From jellyfin-audio-player with MIT License | 6 votes |
Playlist: React.FC = () => {
const { params: { id } } = useRoute<Route>();
const dispatch = useAppDispatch();
// Retrieve the album data from the store
const playlist = useTypedSelector((state) => state.music.playlists.entities[id]);
const playlistTracks = useTypedSelector((state) => state.music.tracks.byPlaylist[id]);
// Define a function for refreshing this entity
const refresh = useCallback(() => dispatch(fetchTracksByPlaylist(id)), [dispatch, id]);
// Auto-fetch the track data periodically
useEffect(() => {
if (!playlist?.lastRefreshed || differenceInDays(playlist?.lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
refresh();
}
}, [playlist?.lastRefreshed, refresh]);
return (
<TrackListView
trackIds={playlistTracks || []}
title={playlist?.Name}
entityId={id}
refresh={refresh}
listNumberingStyle='index'
playButtonText={t('play-playlist')}
shuffleButtonText={t('shuffle-playlist')}
downloadButtonText={t('download-playlist')}
deleteButtonText={t('delete-playlist')}
/>
);
}
Example #4
Source File: axisUtils.ts From anchor-web-app with Apache License 2.0 | 5 votes |
findPrevDay = (now: number) => {
return ({ timestamp }: { timestamp: number }) => {
return differenceInDays(now, timestamp) === 1;
};
}
Example #5
Source File: index.ts From MDDL with MIT License | 5 votes |
@Action({ rawError: true })
async createCollection(payload: CollectionCreate): Promise<Collection> {
if (!this.ownerId) return Promise.reject(new Error('UserID not set'))
const { data } = await api.user.addUserCollection(this.ownerId, payload)
const documents = await this.getDocuments()
const dates = documents.map((d) => new Date(d.createdDate))
const oldest = dates.reduce(
(oldest, current) => (isBefore(current, oldest) ? current : oldest),
dates[0],
)
const newest = dates.reduce(
(newest, current) => (isBefore(current, newest) ? newest : current),
dates[0],
)
const currentDate = Date.now()
const daysSinceOldest = differenceInDays(currentDate, oldest)
const daysSinceNewest = differenceInDays(currentDate, newest)
this.$ga.event({
eventCategory: 'collection_shared',
eventAction: 'number_of_recipients',
eventLabel: UserRole[this._role!],
eventValue: payload.individualEmailAddresses.length,
})
this.$ga.event({
eventCategory: 'collection_shared',
eventAction: 'number_of_documents',
eventLabel: UserRole[this._role!],
eventValue: payload.documentIds.length,
})
this.$ga.event({
eventCategory: 'collection_shared',
eventAction: 'days_since_oldest_document',
eventLabel: UserRole[this._role!],
eventValue: daysSinceOldest,
})
this.$ga.event({
eventCategory: 'collection_shared',
eventAction: 'days_since_newest_document',
eventLabel: UserRole[this._role!],
eventValue: daysSinceNewest,
})
return data
}
Example #6
Source File: matchDayUtils.tsx From symphony-ui-toolkit with Apache License 2.0 | 5 votes |
function matchDayInRange(day: Date, matcher: Modifier): boolean {
if (!('from' in matcher) || !('to' in matcher)) return false;
if (differenceInDays(matcher.to, matcher.from) <= 0) return false;
return isWithinInterval(day, { start: matcher.from, end: matcher.to });
}
Example #7
Source File: matchDayUtils.tsx From symphony-ui-toolkit with Apache License 2.0 | 5 votes |
function matchDayBetween(day: Date, matcher: Modifier): boolean {
if (!('after' in matcher) || !('before' in matcher)) return false;
if (differenceInDays(matcher.before, matcher.after) <= 0) return false;
return isDayAfter(day, matcher.after) && isDayBefore(day, matcher.before);
}
Example #8
Source File: ApiTokenListItem.tsx From amplication with Apache License 2.0 | 5 votes |
ApiTokenListItem = ({
applicationId,
apiToken,
onDelete,
onError,
}: Props) => {
const expirationDate = addDays(
new Date(apiToken.lastAccessAt),
EXPIRATION_DAYS
);
const expired = differenceInDays(new Date(), expirationDate) > 0;
const newToken = !isEmpty(apiToken.token);
const createdDate = new Date(apiToken.createdAt);
return (
<Panel
className={classNames(
CLASS_NAME,
{
[`${CLASS_NAME}--expired`]: expired,
},
{
[`${CLASS_NAME}--new`]: newToken,
}
)}
panelStyle={EnumPanelStyle.Bordered}
>
<div className={`${CLASS_NAME}__panel-tag`}>
<Icon icon="key" size="medium" />
</div>
<div className={`${CLASS_NAME}__panel-details`}>
<div className={`${CLASS_NAME}__row`}>
<h3>{apiToken.name}</h3>
<span className="spacer" />
<div className={`${CLASS_NAME}__created`}>
Created <TimeSince time={createdDate} />
</div>
<div
className={classNames(`${CLASS_NAME}__expiration`, {
[`${CLASS_NAME}__expiration--expired`]: expired,
})}
>
Expiration <TimeSince time={expirationDate} />
</div>
</div>
{newToken && (
<div className={`${CLASS_NAME}__row`}>{apiToken.token}</div>
)}
<div className={`${CLASS_NAME}__row`}>
{!newToken && (
<span className={`${CLASS_NAME}__token-preview`}>
***********{apiToken.previewChars}
</span>
)}
<span className="spacer" />
<DeleteApiToken
apiToken={apiToken}
onDelete={onDelete}
onError={onError}
/>
</div>
</div>
</Panel>
);
}
Example #9
Source File: Playlists.tsx From jellyfin-audio-player with MIT License | 5 votes |
Playlists: React.FC = () => {
// Retrieve data from store
const { entities, ids } = useTypedSelector((state) => state.music.playlists);
const isLoading = useTypedSelector((state) => state.music.playlists.isLoading);
const lastRefreshed = useTypedSelector((state) => state.music.playlists.lastRefreshed);
// Initialise helpers
const dispatch = useAppDispatch();
const navigation = useNavigation<MusicNavigationProp>();
const getImage = useGetImage();
const listRef = useRef<FlatList<EntityId>>(null);
const getItemLayout = useCallback((data: EntityId[] | null | undefined, index: number): { offset: number, length: number, index: number } => {
const length = 220;
const offset = length * index;
return { index, length, offset };
}, []);
// Set callbacks
const retrieveData = useCallback(() => dispatch(fetchAllPlaylists()), [dispatch]);
const selectAlbum = useCallback((id: string) => {
navigation.navigate('Playlist', { id });
}, [navigation]);
const generateItem: ListRenderItem<EntityId> = useCallback(({ item, index }) => {
if (index % 2 === 1) {
return <View key={item} />;
}
const nextItemId = ids[index + 1];
const nextItem = entities[nextItemId];
return (
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item}>
<GeneratedPlaylistItem
id={item}
imageUrl={getImage(item as string)}
name={entities[item]?.Name || ''}
onPress={selectAlbum}
/>
{nextItem &&
<GeneratedPlaylistItem
id={nextItemId}
imageUrl={getImage(nextItemId as string)}
name={nextItem.Name || ''}
onPress={selectAlbum}
/>
}
</View>
);
}, [entities, getImage, selectAlbum, ids]);
// Retrieve data on mount
useEffect(() => {
// GUARD: Only refresh this API call every set amounts of days
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > PLAYLIST_CACHE_AMOUNT_OF_DAYS) {
retrieveData();
}
});
return (
<FlatList
data={ids}
refreshing={isLoading}
onRefresh={retrieveData}
getItemLayout={getItemLayout}
ref={listRef}
keyExtractor={(item, index) => `${item}_${index}`}
renderItem={generateItem}
/>
);
}
Example #10
Source File: PastTimePicker.stories.tsx From gio-design with Apache License 2.0 | 5 votes |
DisabledDate = () => {
const disabledDate = (current: Date) => differenceInDays(startOfToday(), current) > 31
return <PastTimePicker onSelect={action('selected value:')} placeholder="时间范围" disabledDate={disabledDate} />;
}
Example #11
Source File: PastTimePicker.stories.tsx From gio-design with Apache License 2.0 | 5 votes |
StaticDisabledDate.args = {
disabledDate: (current: Date) => differenceInDays(startOfToday(), current) > 31,
};
Example #12
Source File: RelativeRangeHeader.tsx From gio-design with Apache License 2.0 | 5 votes |
convertDateToDays = (date: Date | undefined, defaultValue: number) =>
date ? differenceInDays(startOfToday(), startOfDay(date)) : defaultValue
Example #13
Source File: get-questdb.tsx From questdb.io with Apache License 2.0 | 4 votes |
GetQuestdbPage = () => {
const title = "Download QuestDB"
const description =
"Download QuestDB, an open source time series SQL database for fast ingestion and queries"
const { release } = usePluginData<{ release: Release }>(
"fetch-latest-release",
)
const [os, setOs] = useState<Os | undefined>()
const [releaseDate, setReleaseDate] = useState(
format(new Date(release.published_at), "MMMM M, yyyy"),
)
const assets = getAssets(release)
useEffect(() => {
const isClient = typeof window !== "undefined"
if (!isClient) {
return
}
if (differenceInDays(new Date(), new Date(release.published_at)) < 31) {
setReleaseDate(
`${formatDistanceToNowStrict(new Date(release.published_at))} ago`,
)
}
setOs(getOs())
}, [release.published_at])
const perOs = {
linux: (
<Binary
architecture
href={assets.linux.href}
logo={
<img
alt="Linux Logo"
className={biCss.binary__logo}
height={49}
src="/img/pages/getQuestdb/linux.svg"
width={42}
/>
}
rt
size={assets.linux.size}
title="Linux"
>
<p className={biCss.binary__docs}>
<a href="/docs/get-started/binaries#your-operating-system-version">
Docs
</a>
</p>
</Binary>
),
macos: (
<Binary
basis="15px"
grow={1}
logo={
<img
alt="macOS Logo"
className={biCss.binary__logo}
height={49}
src="/img/pages/getQuestdb/macos.svg"
width={41}
/>
}
title="macOS (via Homebrew)"
>
<div />
<CodeBlock className="language-shell">
{`brew update
brew install questdb`}
</CodeBlock>
<p className={biCss.binary__docs}>
<a href="/docs/get-started/homebrew">Docs</a>
</p>
</Binary>
),
windows: (
<Binary
architecture
href={assets.windows.href}
logo={
<img
alt="Windows Logo"
className={biCss.binary__logo}
height={49}
src="/img/pages/getQuestdb/windows.svg"
width={49}
/>
}
rt
size={assets.windows.size}
title="Windows"
>
<p className={biCss.binary__docs}>
<a href="/docs/get-started/binaries#your-operating-system-version">
Docs
</a>
</p>
</Binary>
),
}
useEffect(() => {
const isClient = typeof window !== "undefined"
if (!isClient) {
return
}
setOs(getOs())
}, [])
return (
<Layout canonical="/get-questdb" description={description} title={title}>
<section
className={clsx(seCss["section--inner"], seCss["section--accent"])}
>
<div className={seCss.section__header}>
<h1
className={clsx(
seCss.section__title,
seCss["section__title--accent"],
)}
>
Download QuestDB
</h1>
<p
className={clsx(
seCss.section__subtitle,
seCss["section__subtitle--accent"],
"text--center",
)}
>
You can find below download links for the latest version of QuestDB
({release.name}). Once your download is finished, you can check our
documentation for <a href="/docs/get-started/docker/">Docker</a>,
the <a href="/docs/get-started/binaries/">binaries</a> or{" "}
<a href="/docs/get-started/homebrew/">Homebrew</a> to get started.
</p>
<img
alt="Screenshot of the Web Console showing various SQL statements and the result of one as a chart"
className={ilCss.illustration}
height={375}
src="/img/pages/getQuestdb/console.png"
width={500}
/>
<div className={ctCss.cta}>
<p
className={clsx(ctCss.cta__details, {
[ctCss["cta__details--download"]]: os !== "macos",
})}
>
Latest Release:
<span className={ctCss.cta__version}>{release.name}</span>
({releaseDate})
</p>
{os != null && os !== "macos" && assets[os] && (
<Button href={assets[os].href} newTab={false}>
{os} Download
</Button>
)}
</div>
<div className={chCss.changelog}>
<a
className={chCss.changelog__link}
href={release.html_url}
rel="noopener noreferrer"
target="_blank"
>
View the changelog
</a>
<a
className={chCss.changelog__link}
href={`${customFields.githubUrl}/tags`}
rel="noopener noreferrer"
target="_blank"
>
View previous releases
</a>
</div>
</div>
</section>
<div className={seCss["section--flex-wrap"]}>
<Binary
basis="40px"
grow={2.6}
logo={
<img
alt="Docker logo"
className={biCss.binary__logo}
height={49}
src="/img/pages/getQuestdb/docker.svg"
width={69}
/>
}
title="Docker"
>
<CodeBlock className="language-shell">
docker run -p 9000:9000 questdb/questdb
</CodeBlock>
<p className={biCss.binary__docs}>
<a href="/docs/get-started/docker">Docs</a>
</p>
</Binary>
<Binary
grow={0.6}
logo={
<img
alt="Helm logo"
className={biCss.binary__logo}
height={49}
src="/img/pages/getQuestdb/helm.svg"
width={50}
/>
}
title="Kubernetes (via Helm)"
>
<CodeBlock className="language-shell">
{`helm repo add questdb https://helm.${customFields.domain}/
helm install my-questdb questdb/questdb --version ${customFields.helmVersion}`}
</CodeBlock>
<p className={biCss.binary__docs}>
<a
href={customFields.artifactHubUrl}
rel="noopener noreferrer"
target="_blank"
>
Docs
</a>
</p>
</Binary>
{os != null ? (
<>
{perOs[os]}
{os !== "linux" && perOs.linux}
{os !== "macos" && perOs.macos}
{os !== "windows" && perOs.windows}
</>
) : (
<>
{perOs.linux}
{perOs.macos}
{perOs.windows}
</>
)}
<Binary
architecture
detailsGrow={3.5}
href={assets.noJre.href}
logo={
<img
alt="Planet with wings"
className={biCss.binary__logo}
height={49}
src="/img/pages/getQuestdb/nojre.svg"
width={75}
/>
}
size={assets.noJre.size}
title="Any (no JVM)"
>
<p className={biCss.binary__docs}>
<a href="/docs/get-started/binaries#any-no-jvm-version">Docs</a>
</p>
</Binary>
<Binary
grow={0.5}
logo={
<img
alt="Maven logo"
className={biCss.binary__logo}
height={49}
src="/img/pages/getQuestdb/maven.svg"
width={37}
/>
}
title="Maven"
>
<CodeBlock className="language-xml">
{`<dependency>
<groupId>org.questdb</groupId>
<artifactId>questdb</artifactId>
<version>${release.name}</version>
</dependency>`}
</CodeBlock>
<p className={biCss.binary__docs}>
<a href="/docs/reference/api/java-embedded">Docs</a>
</p>
</Binary>
<Binary
grow={2}
logo={
<img
alt="Gradle logo"
className={biCss.binary__logo}
height={48}
src="/img/pages/getQuestdb/gradle.svg"
width={67}
/>
}
title="Gradle"
>
<CodeBlock className="language-shell">
{`implementation 'org.questdb:questdb:${release.name}'`}
</CodeBlock>
<div style={{ height: "2.75rem" }} />
<p className={biCss.binary__docs}>
<a href="/docs/reference/api/java-embedded">Docs</a>
</p>
</Binary>
</div>
<div className={heCss.help}>
<img
alt="SQL statement in a code editor with an artistic view of the query result shown as a chart and a table"
className={heCss.help__illustration}
height={468}
src="/img/pages/getQuestdb/query.svg"
width={500}
/>
<div className={heCss.help__text}>
<h2 className={heCss.help__title}>How does it work</h2>
<p>
QuestDB is distributed as a single binary. You can download either:
</p>
<ul className={heCss.help__list}>
<li className={heCss.help__bullet}>
The "rt" version, this includes a trimmed JVM so you do
not need anything else (~ {assets.linux.size})
</li>
<li className={heCss.help__bullet}>
The binary itself (~ {assets.noJre.size}), without the JVM. In
this case, you need Java 11 installed locally
</li>
</ul>
<p>
To find out more about how to use the binaries, please check
the
<a href="/docs/get-started/binaries/">dedicated page</a> in our
documentation.
</p>
<p>
Check out the{" "}
<a
href={release.html_url}
rel="noopener noreferrer"
target="_blank"
>
v{release.name} CHANGELOG
</a>{" "}
for information on the latest release.
</p>
</div>
</div>
</Layout>
)
}
Example #14
Source File: donate.tsx From crosshare with GNU Affero General Public License v3.0 | 4 votes |
export default function DonatePage({ donors }: DonateProps) {
const now = new Date();
return (
<>
<Head>
<title>Donate and Become a Crosshare Patron</title>
</Head>
<DefaultTopBar />
<div
css={{
padding: '1em',
[SMALL_AND_UP]: {
padding: '2em',
},
backgroundColor: 'var(--secondary)',
textAlign: 'center',
color: 'var(--text)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<PatronIcon css={PatronHeaderCSS} />
<div
css={{
maxWidth: 900,
margin: '0 1em',
[SMALL_AND_UP]: {
margin: '0 2em',
},
}}
>
<div
css={{
fontFamily: 'Georgia, serif',
fontSize: 20,
[SMALL_AND_UP]: {
fontSize: 30,
},
fontStyle: 'italic',
marginBottom: '0.5em',
}}
>
A free crossword community for everyone
</div>
<div
css={{
[SMALL_AND_UP]: {
fontSize: 20,
},
}}
>
Crosshare is <b>free</b> and <b>ad-free</b>, but it isn't free
to run. We rely on support from people like you to make it possible.
</div>
</div>
<PatronIcon css={PatronHeaderCSS} />
</div>
<div css={{ margin: 'auto', maxWidth: 900, padding: '1em' }}>
<h2 css={{ textAlign: 'center' }}>Become a Crosshare Patron</h2>
<p>
Crosshare is developed by a <b>very</b> small team of volunteers.
Every donation, no matter how small, makes a huge difference towards
keeping this project alive.
</p>
<p>
Monthly recurring donations are especially helpful, as they allow us
to plan for ever increasing server costs from new users and new
features. To encourage recurring donations, we've added a new
patron icon - <PatronIcon /> - which lasts one month from the time of
your last donation. Please consider making a recurring donation and
becoming a Crosshare patron!
</p>
<form
css={{
marginBottom: '0.5em',
textAlign: 'center',
fontSize: '1.3em',
}}
action="https://www.paypal.com/donate"
method="post"
target="_top"
>
<input type="hidden" name="business" value="FTAE6AJHUAJ42" />
<input
type="hidden"
name="item_name"
value="All donations support crosshare.org"
/>
<input type="hidden" name="currency_code" value="USD" />
<Button type="submit" text="Donate (via credit card / paypal)" />
</form>
<p css={{ textAlign: 'center' }}>
<i>
If you'd like to donate but not via paypal, please contact us
via <ContactLinks />.
</i>
</p>
</div>
<h2 css={{ textAlign: 'center' }}>
The contributors who make Crosshare possible
</h2>
<p></p>
<ul css={{ listStyleType: 'none', columnWidth: '20em', margin: 0 }}>
{donors.map((d, i) => (
<li key={i} css={{ fontWeight: d.above100 ? 'bold' : 'normal' }}>
{differenceInDays(now, new Date(d.date)) <= 32 ? (
<PatronIcon css={{ marginRight: '0.5em' }} />
) : (
''
)}
{d.page ? <a href={`/${d.page}`}>{d.name}</a> : d.name}
</li>
))}
</ul>
<div css={{ fontStyle: 'italic', textAlign: 'right', margin: '1em' }}>
Contributors who have totaled $100 or more are in bold
</div>
</>
);
}
Example #15
Source File: Albums.tsx From jellyfin-audio-player with MIT License | 4 votes |
Albums: React.FC = () => {
// Retrieve data from store
const { entities: albums } = useTypedSelector((state) => state.music.albums);
const isLoading = useTypedSelector((state) => state.music.albums.isLoading);
const lastRefreshed = useTypedSelector((state) => state.music.albums.lastRefreshed);
const sections = useTypedSelector(selectAlbumsByAlphabet);
// Initialise helpers
const dispatch = useAppDispatch();
const navigation = useNavigation<MusicNavigationProp>();
const getImage = useGetImage();
const listRef = useRef<SectionList<EntityId>>(null);
const getItemLayout = useCallback((data: SectionedId[] | null, index: number): { offset: number, length: number, index: number } => {
// We must wait for the ref to become available before we can use the
// native item retriever in VirtualizedSectionList
if (!listRef.current) {
return { offset: 0, length: 0, index };
}
// Retrieve the right item info
// @ts-ignore
const wrapperListRef = (listRef.current?._wrapperListRef) as VirtualizedSectionList;
const info: VirtualizedItemInfo = wrapperListRef._subExtractor(index);
const { index: itemIndex, header, key } = info;
const sectionIndex = parseInt(key.split(':')[0]);
// We can then determine the "length" (=height) of this item. Header items
// end up with an itemIndex of -1, thus are easy to identify.
const length = header ? 50 : (itemIndex % 2 === 0 ? AlbumHeight : 0);
// We'll also need to account for any unevenly-ended lists up until the
// current item.
const previousRows = data?.filter((row, i) => i < sectionIndex)
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
// We must also calcuate the offset, total distance from the top of the
// screen. First off, we'll account for each sectionIndex that is shown up
// until now. This only includes the heading for the current section if the
// item is not the section header
const headingOffset = HeadingHeight * (header ? sectionIndex : sectionIndex + 1);
const currentRows = itemIndex > 1 ? Math.ceil((itemIndex + 1) / 2) : 0;
const itemOffset = AlbumHeight * (previousRows + currentRows);
const offset = headingOffset + itemOffset;
return { index, length, offset };
}, [listRef]);
// Set callbacks
const retrieveData = useCallback(() => dispatch(fetchAllAlbums()), [dispatch]);
const selectAlbum = useCallback((id: string) => navigation.navigate('Album', { id, album: albums[id] as Album }), [navigation, albums]);
const selectLetter = useCallback((sectionIndex: number) => {
listRef.current?.scrollToLocation({ sectionIndex, itemIndex: 0, animated: false, });
}, [listRef]);
const generateItem = useCallback(({ item, index, section }: { item: EntityId, index: number, section: SectionedId }) => {
if (index % 2 === 1) {
return <View key={item} />;
}
const nextItem = section.data[index + 1];
return (
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item}>
<GeneratedAlbumItem
id={item}
imageUrl={getImage(item as string)}
name={albums[item]?.Name || ''}
artist={albums[item]?.AlbumArtist || ''}
onPress={selectAlbum}
/>
{albums[nextItem] &&
<GeneratedAlbumItem
id={nextItem}
imageUrl={getImage(nextItem as string)}
name={albums[nextItem]?.Name || ''}
artist={albums[nextItem]?.AlbumArtist || ''}
onPress={selectAlbum}
/>
}
</View>
);
}, [albums, getImage, selectAlbum]);
// Retrieve data on mount
useEffect(() => {
// GUARD: Only refresh this API call every set amounts of days
if (!lastRefreshed || differenceInDays(lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
retrieveData();
}
});
return (
<SafeAreaView>
<AlphabetScroller onSelect={selectLetter} />
<SectionList
sections={sections}
refreshing={isLoading}
onRefresh={retrieveData}
getItemLayout={getItemLayout}
ref={listRef}
keyExtractor={(item) => item as string}
renderSectionHeader={generateSection}
renderItem={generateItem}
/>
</SafeAreaView>
);
}
Example #16
Source File: CalendarHeatmap.tsx From apps with GNU Affero General Public License v3.0 | 4 votes |
export default function CalendarHeatmap<T extends { date: string }>({
startDate,
endDate,
gutterSize = 2,
values,
valueToCount,
valueToTooltip,
}: CalendarHeatmapProps<T>): ReactElement {
const squareSizeWithGutter = SQUARE_SIZE + gutterSize;
const numEmptyDaysAtEnd = DAYS_IN_WEEK - 1 - endDate.getDay();
const numEmptyDaysAtStart = startDate.getDay();
const startDateWithEmptyDays = addDays(startDate, -numEmptyDaysAtStart);
const dateDifferenceInDays = differenceInDays(endDate, startDate);
const numDaysRoundedToWeek =
dateDifferenceInDays + numEmptyDaysAtStart + numEmptyDaysAtEnd;
const weekCount = Math.ceil(numDaysRoundedToWeek / DAYS_IN_WEEK);
const weekWidth = DAYS_IN_WEEK * squareSizeWithGutter;
const width =
weekCount * squareSizeWithGutter - (gutterSize - weekdayLabelSize);
const height = weekWidth + (MONTH_LABEL_SIZE - gutterSize);
const bins = useMemo<number[]>(
() => getBins(values.map(valueToCount)),
[values, valueToCount],
);
const computedValues = useMemo<
Record<number, { count: number; date: Date; bin: number; originalValue: T }>
>(
() =>
values.reduce((acc, value) => {
const date = new Date(value.date);
const index = differenceInDays(date, startDateWithEmptyDays);
if (index < 0) {
return acc;
}
const count = valueToCount(value);
acc[index] = {
count,
bin: getBin(count, bins),
date,
originalValue: value,
};
return acc;
}, {}),
[values, valueToCount, startDate, endDate, bins],
);
const getMonthLabelCoordinates = (weekIndex: number): [number, number] => [
weekIndex * squareSizeWithGutter,
(MONTH_LABEL_SIZE - MONTH_LABEL_GUTTER_SIZE) / 2 + 1,
];
const renderMonthLabels = (): ReactNode => {
const weekRange = getRange(weekCount - 2);
return weekRange.map((weekIndex) => {
const date = endOfWeek(
addDays(startDateWithEmptyDays, weekIndex * DAYS_IN_WEEK),
);
const [x, y] = getMonthLabelCoordinates(weekIndex);
return date.getDate() >= DAYS_IN_WEEK &&
date.getDate() <= 2 * DAYS_IN_WEEK - 1 ? (
<text key={weekIndex} x={x} y={y} style={labelStyle}>
{MONTH_LABELS[date.getMonth()]}
</text>
) : null;
});
};
const getWeekdayLabelCoordinates = (dayIndex: number): [number, number] => [
0,
(dayIndex + 1) * SQUARE_SIZE + dayIndex * gutterSize,
];
const renderWeekdayLabels = (): ReactNode => {
return DAY_LABELS.map((weekdayLabel, dayIndex) => {
const [x, y] = getWeekdayLabelCoordinates(dayIndex);
return weekdayLabel.length ? (
<text key={`${x}${y}`} x={x} y={y} style={labelStyle}>
{weekdayLabel}
</text>
) : null;
});
};
const getSquareCoordinates = (dayIndex: number): [number, number] => [
0,
dayIndex * squareSizeWithGutter,
];
const renderSquare = (dayIndex: number, index: number): ReactNode => {
const indexOutOfRange =
index < numEmptyDaysAtStart ||
index >= numEmptyDaysAtStart + dateDifferenceInDays;
if (indexOutOfRange) {
return null;
}
const [x, y] = getSquareCoordinates(dayIndex);
const value = computedValues[index];
const bin = value?.bin || 0;
const attrs = binsAttributes[bin];
return (
<SimpleTooltip
content={valueToTooltip(
value?.originalValue,
addDays(startDateWithEmptyDays, index),
)}
duration={0}
delay={[0, 70]}
container={{
paddingClassName: 'py-3 px-4',
roundedClassName: 'rounded-3',
}}
>
<g>
<rect
key={index}
width={SQUARE_SIZE}
height={SQUARE_SIZE}
x={x}
y={y}
rx="3"
{...attrs}
/>
{!bin && (
<rect
width={SQUARE_SIZE - 2}
height={SQUARE_SIZE - 2}
x={x + 1}
y={y + 1}
rx="2"
fill="var(--theme-background-primary)"
/>
)}
</g>
</SimpleTooltip>
);
};
const renderWeek = (weekIndex: number): ReactNode => {
return (
<g
key={weekIndex}
transform={`translate(${weekIndex * squareSizeWithGutter}, 0)`}
>
{getRange(DAYS_IN_WEEK).map((dayIndex) =>
renderSquare(dayIndex, weekIndex * DAYS_IN_WEEK + dayIndex),
)}
</g>
);
};
const renderAllWeeks = (): ReactNode =>
getRange(weekCount).map((weekIndex) => renderWeek(weekIndex));
return (
<svg
width={width}
viewBox={`0 0 ${width} ${height}`}
onMouseDown={(e) => e.preventDefault()}
>
<g transform={`translate(${weekdayLabelSize}, 0)`}>
{renderMonthLabels()}
</g>
<g transform={`translate(0, ${MONTH_LABEL_SIZE})`}>
{renderWeekdayLabels()}
</g>
<g transform={`translate(${weekdayLabelSize}, ${MONTH_LABEL_SIZE})`}>
{renderAllWeeks()}
</g>
</svg>
);
}
Example #17
Source File: EventsList.tsx From norfolkdevelopers-website with MIT License | 4 votes |
export default function EventsList({ initialData, endpoint }: Props) {
const { data, error } = useSWR<MeetupEvent[]>(
`/api/proxy?url=${endpoint}`,
fetcher,
{
initialData,
}
);
if (error)
return (
<div>
Error loading events from meetup.com{" "}
<span role="img" aria-label="sad face">
?
</span>
</div>
);
if (!data)
return (
<div>
Loading events from{" "}
<a
className="text-href-base hover:text-href-hover"
href="https://www.meetup.com/Norfolk-Developers-NorDev/events/"
>
https://www.meetup.com
</a>
...
</div>
);
return (
<ul className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{data.slice(0, 9).map((event) => {
const currentDate = new Date();
const dateOfEvent = new Date(event.time);
const dateDifference = differenceInDays(dateOfEvent, currentDate);
let formattedDateOfEvent;
if (dateDifference < 1) {
formattedDateOfEvent = `Today, ${format(
new Date(event.time),
"do LLL "
)}`;
} else if (dateDifference < 2) {
formattedDateOfEvent = `Tomorrow, ${format(
new Date(event.time),
"do LLL"
)}`;
} else if (dateDifference < 7) {
formattedDateOfEvent = `This ${format(
new Date(event.time),
"eeee, do LLL"
)}`;
} else if (dateDifference < 14) {
formattedDateOfEvent = `Next ${format(
new Date(event.time),
"eeee, do LLL "
)}`;
} else {
formattedDateOfEvent = format(
new Date(event.time),
"eeee, do LLL yyyy"
);
}
return (
<li
key={event.id}
className="block bg-background-secondary rounded leading-tight tracking-tight"
>
<a href={event.link} className="p-4 block hover:outline">
<h3 className="font-bold text-lg">
<span role="img" aria-label="calendar">
?
</span>
{event.name}
</h3>
<p className="text-foreground-secondary mt-2 font-bold">
{formattedDateOfEvent}
</p>
<p className="mt-1 text-foreground-secondary">
{format(new Date(event.time), "HH:mm")} to{" "}
{format(new Date(event.time + event.duration), "HH:mm")}
</p>
<p className="text-href-base mt-1 font-bold">RSVP »</p>
<p className="text-sm mt-1 text-foreground-tertiary">
{event.yes_rsvp_count}
{event.rsvp_limit
? ` / ${event.rsvp_limit}`
: null} attending{" "}
</p>
</a>
</li>
);
})}
</ul>
);
}
Example #18
Source File: NotificationTile.tsx From atlas with GNU General Public License v3.0 | 4 votes |
NotificationTile: React.FC<NotificationProps> = ({
notification,
loading,
onCheckboxChange,
onClick,
selected = false,
variant = 'default',
className,
}) => {
const { date, video, member, read } = notification
const { url: avatarUrl, isLoadingAsset: isLoadingAvatar } = useMemberAvatar(member)
const formattedDate = useMemo(() => {
const differenceDays = differenceInDays(new Date(), date)
const differenceYears = differenceInCalendarYears(new Date(), date)
if (differenceYears >= 1) {
return format(date, 'dd LLL yyyy')
}
if (differenceDays > 3) {
return format(date, 'LLL d')
}
return formatDateAgo(date)
}, [date])
if (variant === 'compact') {
return (
<StyledLink to={absoluteRoutes.viewer.video(notification.video.id)} onClick={onClick}>
<StyledListItem
loading={loading}
read={read}
variant="compact"
nodeStart={
member ? (
<Avatar size="default" assetUrl={avatarUrl} loading={isLoadingAvatar || loading} />
) : (
<NoActorNotificationAvatar size="small" />
)
}
caption={!loading ? `${formattedDate} • ${video.title}` : <SkeletonLoader width="50%" height={19} />}
label={
!loading ? (
<>
{member && (
<Text as="span" variant="t200-strong" secondary>
{`${member.handle} `}
</Text>
)}
<Text as="span" variant="t200-strong">
{getNotificationText(notification)}
</Text>
</>
) : (
<SkeletonLoader width="40%" height={20} bottomSpace={2} />
)
}
/>
</StyledLink>
)
}
return (
<Wrapper
to={absoluteRoutes.viewer.video(notification.video.id)}
read={read}
selected={selected}
loading={loading}
className={className}
variant="default"
onClick={onClick}
>
{!loading ? (
<Checkbox onChange={onCheckboxChange} value={selected} />
) : (
<CheckboxSkeleton width={16} height={16} />
)}
<AvatarWrapper>
{member ? (
<Avatar size="small" assetUrl={avatarUrl} loading={isLoadingAvatar || loading} />
) : (
<NoActorNotificationAvatar size="regular" />
)}
</AvatarWrapper>
{!loading ? (
<Content>
<Title>
{member && (
<Text as="span" variant="h300" secondary>
{`${member.handle} `}
</Text>
)}
<Text as="span" variant="h300">
{getNotificationText(notification)}
</Text>
</Title>
<Text variant="t200" secondary>
{formattedDate} • {video.title}
</Text>
</Content>
) : (
<Content>
<SkeletonLoader width="40%" height={24} bottomSpace={2} />
<SkeletonLoader width="50%" height={20} />
</Content>
)}
</Wrapper>
)
}
Example #19
Source File: UpdateChecker.tsx From firetable with Apache License 2.0 | 4 votes |
export default function UpdateChecker() {
const classes = useStyles();
const [
lastCheckedUpdate,
setLastCheckedUpdate,
] = useLastCheckedUpdateState<string>();
const [latestUpdate, setLatestUpdate] = useLatestUpdateState<null | Record<
string,
any
>>(null);
const [checkState, setCheckState] = useState<null | "LOADING" | "NO_UPDATE">(
null
);
const checkForUpdate = async () => {
setCheckState("LOADING");
// https://docs.github.com/en/rest/reference/repos#get-the-latest-release
const endpoint = meta.repository.url
.replace("github.com", "api.github.com/repos")
.replace(/.git$/, "/releases/latest");
try {
const res = await fetch(endpoint, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
const json = await res.json();
if (json.tag_name > "v" + meta.version) {
setLatestUpdate(json);
setCheckState(null);
} else {
setCheckState("NO_UPDATE");
}
setLastCheckedUpdate(new Date().toISOString());
} catch (e) {
console.error(e);
setLatestUpdate(null);
setCheckState("NO_UPDATE");
}
};
// Check for new updates on page load, if last check was more than 7 days ago
useEffect(() => {
if (!lastCheckedUpdate) checkForUpdate();
else if (differenceInDays(new Date(), new Date(lastCheckedUpdate)) > 7)
checkForUpdate();
}, [lastCheckedUpdate]);
// Verify latest update is not installed yet
useEffect(() => {
if (latestUpdate?.tag_name <= "v" + meta.version) setLatestUpdate(null);
}, [latestUpdate, setLatestUpdate]);
return (
<>
{checkState === "LOADING" ? (
<MenuItem disabled>Checking for updates…</MenuItem>
) : checkState === "NO_UPDATE" ? (
<MenuItem disabled>No updates available</MenuItem>
) : latestUpdate === null ? (
<MenuItem onClick={checkForUpdate}>Check for updates</MenuItem>
) : (
<>
<MenuItem
component="a"
href={latestUpdate?.html_url}
target="_blank"
rel="noopener"
>
<ListItemText
primary="Update available"
secondary={latestUpdate?.tag_name}
/>
<ListItemSecondaryAction className={classes.secondaryAction}>
<OpenInNewIcon className={classes.secondaryIcon} />
</ListItemSecondaryAction>
</MenuItem>
<MenuItem
component="a"
href={WIKI_LINKS.updatingFiretable}
target="_blank"
rel="noopener"
>
<ListItemText secondary="How to update Firetable" />
<ListItemSecondaryAction className={classes.secondaryAction}>
<OpenInNewIcon
color="secondary"
fontSize="small"
className={classes.secondaryIcon}
/>
</ListItemSecondaryAction>
</MenuItem>
</>
)}
<Link
variant="caption"
component="a"
href={meta.repository.url.replace(".git", "") + "/releases"}
target="_blank"
rel="noopener"
className={classes.version}
>
{meta.name} v{meta.version}
</Link>
</>
);
}