chore: Move to Typescript (#2783)

This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously.

closes #1282
This commit is contained in:
Tom Moor
2021-11-29 06:40:55 -08:00
committed by GitHub
parent 25ccfb5d04
commit 15b1069bcc
1017 changed files with 17410 additions and 54942 deletions

View File

@@ -0,0 +1,184 @@
import ArrowKeyNavigation from "boundless-arrow-key-navigation";
import { isEqual } from "lodash";
import { observable, action } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import { withTranslation, WithTranslation } from "react-i18next";
import { Waypoint } from "react-waypoint";
import { DEFAULT_PAGINATION_LIMIT } from "~/stores/BaseStore";
import RootStore from "~/stores/RootStore";
import DelayedMount from "~/components/DelayedMount";
import PlaceholderList from "~/components/List/Placeholder";
import withStores from "~/components/withStores";
import { dateToHeading } from "~/utils/dates";
type Props = WithTranslation &
RootStore & {
fetch?: (options: Record<string, any> | null | undefined) => Promise<any>;
options?: Record<string, any>;
heading?: React.ReactNode;
empty?: React.ReactNode;
items: any[];
renderItem: (arg0: any, index: number) => React.ReactNode;
renderHeading?: (name: React.ReactElement<any> | string) => React.ReactNode;
};
@observer
class PaginatedList extends React.Component<Props> {
isInitiallyLoaded = this.props.items.length > 0;
@observable
isLoaded = false;
@observable
isFetchingMore = false;
@observable
isFetching = false;
@observable
renderCount: number = DEFAULT_PAGINATION_LIMIT;
@observable
offset = 0;
@observable
allowLoadMore = true;
componentDidMount() {
this.fetchResults();
}
componentDidUpdate(prevProps: Props) {
if (
prevProps.fetch !== this.props.fetch ||
!isEqual(prevProps.options, this.props.options)
) {
this.reset();
this.fetchResults();
}
}
reset = () => {
this.offset = 0;
this.allowLoadMore = true;
this.renderCount = DEFAULT_PAGINATION_LIMIT;
this.isFetching = false;
this.isFetchingMore = false;
this.isLoaded = false;
};
fetchResults = async () => {
if (!this.props.fetch) return;
this.isFetching = true;
const limit = DEFAULT_PAGINATION_LIMIT;
const results = await this.props.fetch({
limit,
offset: this.offset,
...this.props.options,
});
if (results && (results.length === 0 || results.length < limit)) {
this.allowLoadMore = false;
} else {
this.offset += limit;
}
this.renderCount += limit;
this.isLoaded = true;
this.isFetching = false;
this.isFetchingMore = false;
};
@action
loadMoreResults = async () => {
// Don't paginate if there aren't more results or were currently fetching
if (!this.allowLoadMore || this.isFetching) return;
// If there are already cached results that we haven't yet rendered because
// of lazy rendering then show another page.
const leftToRender = this.props.items.length - this.renderCount;
if (leftToRender > 1) {
this.renderCount += DEFAULT_PAGINATION_LIMIT;
}
// If there are less than a pages results in the cache go ahead and fetch
// another page from the server
if (leftToRender <= DEFAULT_PAGINATION_LIMIT) {
this.isFetchingMore = true;
await this.fetchResults();
}
};
render() {
const { items, heading, auth, empty, renderHeading } = this.props;
let previousHeading = "";
const showLoading =
this.isFetching && !this.isFetchingMore && !this.isInitiallyLoaded;
const showEmpty = !items.length && !showLoading;
const showList =
(this.isLoaded || this.isInitiallyLoaded) && !showLoading && !showEmpty;
return (
<>
{showEmpty && empty}
{showList && (
<>
{heading}
<ArrowKeyNavigation
mode={ArrowKeyNavigation.mode.VERTICAL}
defaultActiveChildIndex={0}
>
{items.slice(0, this.renderCount).map((item, index) => {
const children = this.props.renderItem(item, index);
// If there is no renderHeading method passed then no date
// headings are rendered
if (!renderHeading) {
return children;
}
// Our models have standard date fields, updatedAt > createdAt.
// Get what a heading would look like for this item
const currentDate =
item.updatedAt || item.createdAt || previousHeading;
const currentHeading = dateToHeading(
currentDate,
this.props.t,
auth.user?.language
);
// If the heading is different to any previous heading then we
// should render it, otherwise the item can go under the previous
// heading
if (!previousHeading || currentHeading !== previousHeading) {
previousHeading = currentHeading;
return (
<React.Fragment key={item.id}>
{renderHeading(currentHeading)}
{children}
</React.Fragment>
);
}
return children;
})}
</ArrowKeyNavigation>
{this.allowLoadMore && (
<Waypoint key={this.renderCount} onEnter={this.loadMoreResults} />
)}
</>
)}
{showLoading && (
<DelayedMount>
<PlaceholderList count={5} />
</DelayedMount>
)}
</>
);
}
}
export const Component = PaginatedList;
export default withTranslation()(withStores(PaginatedList));