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:
@@ -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={
|
||||
@@ -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);
|
||||
@@ -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 = ({
|
||||
@@ -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} />;
|
||||
}
|
||||
|
||||
@@ -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 && (
|
||||
<>
|
||||
{" "}
|
||||
@@ -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")}
|
||||
@@ -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 (
|
||||
@@ -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;
|
||||
52
app/scenes/Settings/components/UserListItem.tsx
Normal file
52
app/scenes/Settings/components/UserListItem.tsx
Normal 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;
|
||||
@@ -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(
|
||||
() => [
|
||||
{
|
||||
Reference in New Issue
Block a user