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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
@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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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:&nbsp;
              <span className={ctCss.cta__version}>{release.name}</span>
              &nbsp;({releaseDate})
            </p>
            {os != null && os !== "macos" && assets[os] && (
              <Button href={assets[os].href} newTab={false}>
                {os}&nbsp;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 &quot;rt&quot; 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&nbsp;
            <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 vote down vote up
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&apos;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&apos;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&apos;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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
                &nbsp;{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 &raquo;</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 vote down vote up
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 vote down vote up
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>
    </>
  );
}