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

@@ -1,28 +1,26 @@
// @flow
import * as React from "react";
import { useTranslation } from "react-i18next";
import FileOperation from "models/FileOperation";
import { Action } from "components/Actions";
import ListItem from "components/List/Item";
import Time from "components/Time";
import useCurrentUser from "hooks/useCurrentUser";
import FileOperationMenu from "menus/FileOperationMenu";
type Props = {|
fileOperation: FileOperation,
handleDelete: (FileOperation) => Promise<void>,
|};
import FileOperation from "~/models/FileOperation";
import { Action } from "~/components/Actions";
import ListItem from "~/components/List/Item";
import Time from "~/components/Time";
import useCurrentUser from "~/hooks/useCurrentUser";
import FileOperationMenu from "~/menus/FileOperationMenu";
type Props = {
fileOperation: FileOperation;
handleDelete: (arg0: FileOperation) => Promise<void>;
};
const FileOperationListItem = ({ fileOperation, handleDelete }: Props) => {
const { t } = useTranslation();
const user = useCurrentUser();
const stateMapping = {
creating: t("Processing"),
expired: t("Expired"),
uploading: t("Processing"),
error: t("Error"),
};
return (
<ListItem
title={

View File

@@ -1,36 +1,44 @@
// @flow
import invariant from "invariant";
import { observable } from "mobx";
import { observer, inject } from "mobx-react";
import { observer } from "mobx-react";
import * as React from "react";
import AvatarEditor from "react-avatar-editor";
import Dropzone from "react-dropzone";
import styled from "styled-components";
import UiStore from "stores/UiStore";
import Button from "components/Button";
import Flex from "components/Flex";
import LoadingIndicator from "components/LoadingIndicator";
import Modal from "components/Modal";
import { compressImage } from "utils/compressImage";
import { uploadFile, dataUrlToBlob } from "utils/uploadFile";
import RootStore from "~/stores/RootStore";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import LoadingIndicator from "~/components/LoadingIndicator";
import Modal from "~/components/Modal";
import withStores from "~/components/withStores";
import { compressImage } from "~/utils/compressImage";
import { uploadFile, dataUrlToBlob } from "~/utils/uploadFile";
const EMPTY_OBJECT = {};
type Props = {
children?: React.Node,
onSuccess: (string) => void | Promise<void>,
onError: (string) => void,
submitText: string,
borderRadius: number,
ui: UiStore,
type Props = RootStore & {
children?: React.ReactNode;
onSuccess: (arg0: string) => void | Promise<void>;
onError: (arg0: string) => void;
submitText?: string;
borderRadius?: number;
};
@observer
class ImageUpload extends React.Component<Props> {
@observable isUploading: boolean = false;
@observable isCropping: boolean = false;
@observable zoom: number = 1;
@observable file: File;
avatarEditorRef: AvatarEditor;
@observable
isUploading = false;
@observable
isCropping = false;
@observable
zoom = 1;
@observable
file: File;
avatarEditorRef = React.createRef<AvatarEditor>();
static defaultProps = {
submitText: "Crop Picture",
@@ -44,15 +52,16 @@ class ImageUpload extends React.Component<Props> {
handleCrop = () => {
this.isUploading = true;
// allow the UI to update before converting the canvas to a Blob
// for large images this can cause the page rendering to hang.
setImmediate(this.uploadImage);
};
uploadImage = async () => {
const canvas = this.avatarEditorRef.getImage();
const canvas = this.avatarEditorRef.current?.getImage();
invariant(canvas, "canvas is not defined");
const imageBlob = dataUrlToBlob(canvas.toDataURL());
try {
const compressed = await compressImage(imageBlob, {
maxHeight: 512,
@@ -76,8 +85,9 @@ class ImageUpload extends React.Component<Props> {
this.isCropping = false;
};
handleZoom = (event: SyntheticDragEvent<*>) => {
let target = event.target;
handleZoom = (event: React.DragEvent<any>) => {
const target = event.target;
if (target instanceof HTMLInputElement) {
this.zoom = parseFloat(target.value);
}
@@ -85,14 +95,13 @@ class ImageUpload extends React.Component<Props> {
renderCropping() {
const { ui, submitText } = this.props;
return (
<Modal isOpen onRequestClose={this.handleClose} title="">
<Flex auto column align="center" justify="center">
{this.isUploading && <LoadingIndicator />}
<AvatarEditorContainer>
<AvatarEditor
ref={(ref) => (this.avatarEditorRef = ref)}
ref={this.avatarEditorRef}
image={this.file}
width={250}
height={250}
@@ -111,6 +120,7 @@ class ImageUpload extends React.Component<Props> {
max="2"
step="0.01"
defaultValue="1"
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
onChange={this.handleZoom}
/>
<CropButton onClick={this.handleCrop} disabled={this.isUploading}>
@@ -130,6 +140,7 @@ class ImageUpload extends React.Component<Props> {
<Dropzone
accept="image/png, image/jpeg"
onDropAccepted={this.onDropAccepted}
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: ({ getRootProps, getInputProps }... Remove this comment to see the full error message
style={EMPTY_OBJECT}
disablePreview
>
@@ -177,4 +188,4 @@ const CropButton = styled(Button)`
width: 300px;
`;
export default inject("ui")(ImageUpload);
export default withStores(ImageUpload);

View File

@@ -1,15 +1,14 @@
// @flow
import * as React from "react";
import NotificationSetting from "models/NotificationSetting";
import Checkbox from "components/Checkbox";
import NotificationSetting from "~/models/NotificationSetting";
import Checkbox from "~/components/Checkbox";
type Props = {
setting?: NotificationSetting,
title: string,
event: string,
description: string,
disabled: boolean,
onChange: (ev: SyntheticInputEvent<>) => void | Promise<void>,
setting?: NotificationSetting;
title: string;
event: string;
description: string;
disabled: boolean;
onChange: (ev: React.SyntheticEvent) => void | Promise<void>;
};
const NotificationListItem = ({

View File

@@ -1,31 +1,38 @@
// @flow
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import User from "models/User";
import Avatar from "components/Avatar";
import Badge from "components/Badge";
import Flex from "components/Flex";
import { type Props as TableProps } from "components/Table";
import Time from "components/Time";
import useCurrentUser from "hooks/useCurrentUser";
import UserMenu from "menus/UserMenu";
import { $Diff } from "utility-types";
import User from "~/models/User";
import Avatar from "~/components/Avatar";
import Badge from "~/components/Badge";
import Flex from "~/components/Flex";
import { Props as TableProps } from "~/components/Table";
import Time from "~/components/Time";
import useCurrentUser from "~/hooks/useCurrentUser";
import UserMenu from "~/menus/UserMenu";
const Table = React.lazy<TableProps>(() =>
import(/* webpackChunkName: "table" */ "components/Table")
// @ts-expect-error ts-migrate(2344) FIXME: Type 'Props' does not satisfy the constraint 'Comp... Remove this comment to see the full error message
const Table = React.lazy<TableProps>(
() =>
import(
/* webpackChunkName: "table" */
"~/components/Table"
)
);
type Props = {|
...$Diff<TableProps, { columns: any }>,
data: User[],
canManage: boolean,
|};
type Props = $Diff<
TableProps,
{
columns: any;
}
> & {
data: User[];
canManage: boolean;
};
function PeopleTable({ canManage, ...rest }: Props) {
const { t } = useTranslation();
const currentUser = useCurrentUser();
const columns = React.useMemo(
() =>
[
@@ -33,6 +40,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
id: "name",
Header: t("Name"),
accessor: "name",
// @ts-expect-error ts-migrate(7031) FIXME: Binding element 'value' implicitly has an 'any' ty... Remove this comment to see the full error message
Cell: observer(({ value, row }) => (
<Flex align="center" gap={8}>
<Avatar src={row.original.avatarUrl} size={32} /> {value}{" "}
@@ -45,6 +53,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
id: "email",
Header: t("Email"),
accessor: "email",
// @ts-expect-error ts-migrate(7031) FIXME: Binding element 'value' implicitly has an 'any' ty... Remove this comment to see the full error message
Cell: observer(({ value }) => value),
}
: undefined,
@@ -53,6 +62,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
Header: t("Last active"),
accessor: "lastActiveAt",
Cell: observer(
// @ts-expect-error ts-migrate(7031) FIXME: Binding element 'value' implicitly has an 'any' ty... Remove this comment to see the full error message
({ value }) => value && <Time dateTime={value} addSuffix />
),
},
@@ -60,6 +70,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
id: "isAdmin",
Header: t("Role"),
accessor: "rank",
// @ts-expect-error ts-migrate(7031) FIXME: Binding element 'row' implicitly has an 'any' type... Remove this comment to see the full error message
Cell: observer(({ row }) => (
<Badges>
{!row.original.lastActiveAt && <Badge>{t("Invited")}</Badge>}
@@ -75,6 +86,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
accessor: "id",
className: "actions",
Cell: observer(
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '({ row, value }: { row: any; val... Remove this comment to see the full error message
({ row, value }) =>
currentUser.id !== value && <UserMenu user={row.original} />
),
@@ -83,7 +95,7 @@ function PeopleTable({ canManage, ...rest }: Props) {
].filter((i) => i),
[t, canManage, currentUser]
);
// @ts-expect-error ts-migrate(2322) FIXME: Type '{ data: any[] & User[]; offset?: number | un... Remove this comment to see the full error message
return <Table columns={columns} {...rest} />;
}

View File

@@ -1,14 +1,13 @@
// @flow
import * as React from "react";
import { useTranslation } from "react-i18next";
import Share from "models/Share";
import ListItem from "components/List/Item";
import Time from "components/Time";
import ShareMenu from "menus/ShareMenu";
import Share from "~/models/Share";
import ListItem from "~/components/List/Item";
import Time from "~/components/Time";
import ShareMenu from "~/menus/ShareMenu";
type Props = {|
share: Share,
|};
type Props = {
share: Share;
};
const ShareListItem = ({ share }: Props) => {
const { t } = useTranslation();
@@ -20,7 +19,9 @@ const ShareListItem = ({ share }: Props) => {
subtitle={
<>
{t("Shared")} <Time dateTime={share.createdAt} addSuffix />{" "}
{t("by {{ name }}", { name: share.createdBy.name })}{" "}
{t("by {{ name }}", {
name: share.createdBy.name,
})}{" "}
{lastAccessedAt && (
<>
{" "}

View File

@@ -1,20 +1,20 @@
// @flow
import * as React from "react";
import { useTranslation } from "react-i18next";
import { slackAuth } from "shared/utils/routeHelpers";
import Button from "components/Button";
import SlackIcon from "components/SlackIcon";
import env from "env";
import { slackAuth } from "@shared/utils/routeHelpers";
import Button from "~/components/Button";
import SlackIcon from "~/components/SlackIcon";
import env from "~/env";
type Props = {|
scopes?: string[],
redirectUri: string,
state?: string,
label?: string,
|};
type Props = {
scopes?: string[];
redirectUri: string;
state?: string;
label?: string;
};
function SlackButton({ state = "", scopes, redirectUri, label }: Props) {
const { t } = useTranslation();
const handleClick = () =>
(window.location.href = slackAuth(
state,
@@ -26,7 +26,7 @@ function SlackButton({ state = "", scopes, redirectUri, label }: Props) {
return (
<Button
onClick={handleClick}
icon={<SlackIcon fill="currentColor" />}
icon={<SlackIcon color="currentColor" />}
neutral
>
{label || t("Add to Slack")}

View File

@@ -1,13 +1,12 @@
// @flow
import * as React from "react";
import ApiKey from "models/ApiKey";
import Button from "components/Button";
import ListItem from "components/List/Item";
import ApiKey from "~/models/ApiKey";
import Button from "~/components/Button";
import ListItem from "~/components/List/Item";
type Props = {|
token: ApiKey,
onDelete: (tokenId: string) => Promise<void>,
|};
type Props = {
token: ApiKey;
onDelete: (tokenId: string) => Promise<void>;
};
const TokenListItem = ({ token, onDelete }: Props) => {
return (

View File

@@ -1,78 +0,0 @@
// @flow
import { observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import User from "models/User";
import UserProfile from "scenes/UserProfile";
import Avatar from "components/Avatar";
import Badge from "components/Badge";
import ListItem from "components/List/Item";
import Time from "components/Time";
import UserMenu from "menus/UserMenu";
type Props = {
user: User,
showMenu: boolean,
};
@observer
class UserListItem extends React.Component<Props> {
@observable profileOpen: boolean = false;
handleOpenProfile = () => {
this.profileOpen = true;
};
handleCloseProfile = () => {
this.profileOpen = false;
};
render() {
const { user, showMenu } = this.props;
return (
<ListItem
title={<Title onClick={this.handleOpenProfile}>{user.name}</Title>}
image={
<>
<Avatar
src={user.avatarUrl}
size={32}
onClick={this.handleOpenProfile}
/>
<UserProfile
user={user}
isOpen={this.profileOpen}
onRequestClose={this.handleCloseProfile}
/>
</>
}
subtitle={
<>
{user.email ? `${user.email} · ` : undefined}
{user.lastActiveAt ? (
<>
Active <Time dateTime={user.lastActiveAt} /> ago
</>
) : (
"Invited"
)}
{user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>}
{user.isSuspended && <Badge>Suspended</Badge>}
</>
}
actions={showMenu ? <UserMenu user={user} /> : undefined}
/>
);
}
}
const Title = styled.span`
&:hover {
text-decoration: underline;
cursor: pointer;
}
`;
export default UserListItem;

View File

@@ -0,0 +1,52 @@
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import User from "~/models/User";
import Avatar from "~/components/Avatar";
import Badge from "~/components/Badge";
import ListItem from "~/components/List/Item";
import Time from "~/components/Time";
import UserMenu from "~/menus/UserMenu";
type Props = {
user: User;
showMenu: boolean;
};
@observer
class UserListItem extends React.Component<Props> {
render() {
const { user, showMenu } = this.props;
return (
<ListItem
title={<Title>{user.name}</Title>}
image={<Avatar src={user.avatarUrl} size={32} />}
subtitle={
<>
{user.email ? `${user.email} · ` : undefined}
{user.lastActiveAt ? (
<>
Active <Time dateTime={user.lastActiveAt} /> ago
</>
) : (
"Invited"
)}
{user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>}
{user.isSuspended && <Badge>Suspended</Badge>}
</>
}
actions={showMenu ? <UserMenu user={user} /> : undefined}
/>
);
}
}
const Title = styled.span`
&:hover {
text-decoration: underline;
cursor: pointer;
}
`;
export default UserListItem;

View File

@@ -1,16 +1,14 @@
// @flow
import * as React from "react";
import { useTranslation } from "react-i18next";
import FilterOptions from "components/FilterOptions";
import FilterOptions from "~/components/FilterOptions";
type Props = {|
activeKey: string,
onSelect: (key: ?string) => void,
|};
type Props = {
activeKey: string;
onSelect: (key: string | null | undefined) => void;
};
const UserStatusFilter = ({ activeKey, onSelect }: Props) => {
const { t } = useTranslation();
const options = React.useMemo(
() => [
{