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,4 +1,3 @@
// @flow
import * as React from "react";
type InitialState = boolean | (() => boolean);
@@ -8,9 +7,10 @@ type InitialState = boolean | (() => boolean);
*
* @param initialState the initial boolean state value
*/
export default function useBoolean(initialState: InitialState = false) {
export default function useBoolean(
initialState: InitialState = false
): [boolean, () => void, () => void] {
const [value, setValue] = React.useState(initialState);
const setTrue = React.useCallback(() => {
setValue(true);
}, []);

View File

@@ -1,17 +1,15 @@
// @flow
import { useRegisterActions } from "kbar";
import { flattenDeep } from "lodash";
import { useTranslation } from "react-i18next";
import { useLocation } from "react-router-dom";
import { actionToKBar } from "actions";
import useStores from "hooks/useStores";
import type { Action } from "types";
import { actionToKBar } from "~/actions";
import useStores from "~/hooks/useStores";
import { Action } from "~/types";
export default function useCommandBarActions(actions: Action[]) {
const stores = useStores();
const { t } = useTranslation();
const location = useLocation();
const context = {
t,
isCommandBar: true,

View File

@@ -1,4 +1,3 @@
// @flow
import invariant from "invariant";
import useStores from "./useStores";

View File

@@ -1,4 +1,3 @@
// @flow
import invariant from "invariant";
import useStores from "./useStores";

View File

@@ -1,4 +1,3 @@
// @flow
import invariant from "invariant";
import useStores from "./useStores";

View File

@@ -1,13 +1,12 @@
// @flow
import * as React from "react";
export default function useDebouncedCallback(
callback: (any) => mixed,
callback: (arg0: any) => unknown,
wait: number
) {
// track args & timeout handle between calls
const argsRef = React.useRef();
const timeout = React.useRef();
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
function cleanup() {
if (timeout.current) {
@@ -17,13 +16,12 @@ export default function useDebouncedCallback(
// make sure our timeout gets cleared if consuming component gets unmounted
React.useEffect(() => cleanup, []);
return function (...args: any) {
argsRef.current = args;
cleanup();
timeout.current = setTimeout(() => {
if (argsRef.current) {
// @ts-expect-error ts-migrate(2556) FIXME: Expected 1 arguments, but got 0 or more.
callback(...argsRef.current);
}
}, wait);

View File

@@ -1,4 +1,3 @@
// @flow
import * as React from "react";
const activityEvents = [
@@ -21,7 +20,7 @@ const activityEvents = [
*/
export default function useIdle(timeToIdle: number = 3 * 60 * 1000) {
const [isIdle, setIsIdle] = React.useState(false);
const timeout = React.useRef();
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
const onActivity = React.useCallback(() => {
if (timeout.current) {
@@ -42,7 +41,6 @@ export default function useIdle(timeToIdle: number = 3 * 60 * 1000) {
activityEvents.forEach((eventName) =>
window.addEventListener(eventName, handleUserActivityEvent)
);
return () => {
activityEvents.forEach((eventName) =>
window.removeEventListener(eventName, handleUserActivityEvent)

View File

@@ -1,23 +1,24 @@
// @flow
import invariant from "invariant";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import useStores from "hooks/useStores";
import useToasts from "hooks/useToasts";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
let importingLock = false;
export default function useImportDocument(
collectionId: string,
collectionId?: string,
documentId?: string
): {| handleFiles: (files: File[]) => Promise<void>, isImporting: boolean |} {
): {
handleFiles: (files: File[]) => Promise<void>;
isImporting: boolean;
} {
const { documents } = useStores();
const { showToast } = useToasts();
const [isImporting, setImporting] = React.useState(false);
const { t } = useTranslation();
const history = useHistory();
const handleFiles = React.useCallback(
async (files = []) => {
if (importingLock) {
@@ -38,7 +39,7 @@ export default function useImportDocument(
const redirect = files.length === 1;
if (documentId && !collectionId) {
const { document } = await documents.fetch(documentId);
const document = await documents.fetch(documentId);
invariant(document, "Document not available");
cId = document.collectionId;
}

View File

@@ -1,4 +1,3 @@
// @flow
import * as React from "react";
/**
@@ -11,7 +10,6 @@ export default function useIsMounted() {
React.useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};

View File

@@ -1,11 +1,12 @@
// @flow
import * as React from "react";
import isTextInput from "utils/isTextInput";
import isTextInput from "~/utils/isTextInput";
export type KeyFilter = ((event: KeyboardEvent) => boolean) | string;
type Callback = (event: KeyboardEvent) => void;
// Registered keyboard event callbacks
let callbacks = [];
let callbacks: Callback[] = [];
// Track if IME input suggestions are open so we can ignore keydown shortcuts
// in this case, they should never be triggered from mobile keyboards.
@@ -21,13 +22,10 @@ const createKeyPredicate = (keyFilter: KeyFilter) =>
event.key === keyFilter ||
event.code === `Key${keyFilter.toUpperCase()}`
: keyFilter
? (_event) => true
: (_event) => false;
? (_event: KeyboardEvent) => true
: (_event: KeyboardEvent) => false;
export default function useKeyDown(
key: KeyFilter,
fn: (event: KeyboardEvent) => void
): void {
export default function useKeyDown(key: KeyFilter, fn: Callback): void {
const predicate = createKeyPredicate(key);
React.useEffect(() => {
@@ -38,7 +36,6 @@ export default function useKeyDown(
};
callbacks.push(handler);
return () => {
callbacks = callbacks.filter((cb) => cb !== handler);
};
@@ -55,7 +52,12 @@ window.addEventListener("keydown", (event) => {
if (event.defaultPrevented === true) {
break;
}
if (!isTextInput(event.target) || event.ctrlKey || event.metaKey) {
if (
!isTextInput(event.target as HTMLElement) ||
event.ctrlKey ||
event.metaKey
) {
callback(event);
}
}
@@ -64,6 +66,7 @@ window.addEventListener("keydown", (event) => {
window.addEventListener("compositionstart", () => {
imeOpen = true;
});
window.addEventListener("compositionend", () => {
imeOpen = false;
});

View File

@@ -1,4 +1,3 @@
// @flow
import { useState, useEffect } from "react";
export default function useMediaQuery(query: string): boolean {
@@ -7,15 +6,20 @@ export default function useMediaQuery(query: string): boolean {
useEffect(() => {
if (window.matchMedia) {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => {
setMatches(media.matches);
};
media.addListener(listener);
return () => media.removeListener(listener);
}
return undefined;
}, [matches, query]);
return matches;

View File

@@ -1,14 +1,12 @@
// @flow
import * as React from "react";
import { type ElementRef } from "reakit";
import useMobile from "hooks/useMobile";
import useWindowSize from "hooks/useWindowSize";
import useMobile from "~/hooks/useMobile";
import useWindowSize from "~/hooks/useWindowSize";
const useMenuHeight = (
visible: void | boolean,
unstable_disclosureRef: void | { current: null | ElementRef<"button"> }
unstable_disclosureRef?: React.RefObject<HTMLElement | null>
) => {
const [maxHeight, setMaxHeight] = React.useState(undefined);
const [maxHeight, setMaxHeight] = React.useState<number | undefined>();
const isMobile = useMobile();
const { height: windowHeight } = useWindowSize();
@@ -25,7 +23,6 @@ const useMenuHeight = (
);
}
}, [visible, unstable_disclosureRef, windowHeight, isMobile]);
return maxHeight;
};

View File

@@ -1,6 +1,5 @@
// @flow
import { useTheme } from "styled-components";
import useMediaQuery from "hooks/useMediaQuery";
import useMediaQuery from "~/hooks/useMediaQuery";
export default function useMobile(): boolean {
const theme = useTheme();

View File

@@ -1,22 +1,20 @@
// @flow
import * as React from "react";
/**
* Hook to return page visibility state.
*
* @returns boolean if the page is visible
*/
export default function usePageVisibility(): boolean {
const [visible, setVisible] = React.useState(true);
React.useEffect(() => {
const handleVisibilityChange = () => setVisible(!document.hidden);
document.addEventListener("visibilitychange", handleVisibilityChange);
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);
return visible;
}

View File

@@ -1,10 +1,11 @@
// @flow
import * as React from "react";
export default function usePrevious<T>(value: T): T | void {
const ref = React.useRef();
const ref = React.useRef<T>();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
}

View File

@@ -1,4 +1,3 @@
// @flow
import { useLocation } from "react-router-dom";
export default function useQuery() {

View File

@@ -1,17 +1,15 @@
// @flow
import * as React from "react";
import { getCookie } from "tiny-cookie";
type Session = {|
url: string,
logoUrl: string,
name: string,
teamId: string,
|};
type Session = {
url: string;
logoUrl: string;
name: string;
teamId: string;
};
function loadSessionsFromCookie(): Session[] {
const sessions = JSON.parse(getCookie("sessions") || "{}");
return Object.keys(sessions).map((teamId) => ({
teamId,
...sessions[teamId],
@@ -20,10 +18,8 @@ function loadSessionsFromCookie(): Session[] {
export default function useSessions() {
const [sessions, setSessions] = React.useState(loadSessionsFromCookie);
const reload = React.useCallback(() => {
setSessions(loadSessionsFromCookie());
}, []);
return [sessions, reload];
}

View File

@@ -1,8 +0,0 @@
// @flow
import { MobXProviderContext } from "mobx-react";
import * as React from "react";
import RootStore from "stores";
export default function useStores(): typeof RootStore {
return React.useContext(MobXProviderContext);
}

7
app/hooks/useStores.ts Normal file
View File

@@ -0,0 +1,7 @@
import { MobXProviderContext } from "mobx-react";
import * as React from "react";
import RootStore from "~/stores";
export default function useStores() {
return React.useContext(MobXProviderContext) as typeof RootStore;
}

View File

@@ -1,8 +1,9 @@
// @flow
import useStores from "./useStores";
export default function useToasts() {
const { toasts } = useStores();
return { showToast: toasts.showToast, hideToast: toasts.hideToast };
return {
showToast: toasts.showToast,
hideToast: toasts.hideToast,
};
}

View File

@@ -1,9 +1,7 @@
// @flow
import * as React from "react";
const useUnmount = (callback: Function) => {
const useUnmount = (callback: (...args: Array<any>) => any) => {
const ref = React.useRef(callback);
ref.current = callback;
React.useEffect(() => {

View File

@@ -1,4 +1,3 @@
// @flow
import useStores from "./useStores";
export default function useUserLocale() {

View File

@@ -1,19 +1,23 @@
// @flow
// Based on https://github.com/rehooks/window-scroll-position which is no longer
// maintained.
import { throttle } from "lodash";
import { useState, useEffect } from "react";
let supportsPassive = false;
try {
var opts = Object.defineProperty({}, "passive", {
const opts = Object.defineProperty({}, "passive", {
get: function () {
supportsPassive = true;
},
});
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
window.addEventListener("testPassive", null, opts);
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
window.removeEventListener("testPassive", null, opts);
} catch (e) {}
} catch (e) {
// No-op
}
const getPosition = () => ({
x: window.pageXOffset,
@@ -25,23 +29,27 @@ const defaultOptions = {
};
export default function useWindowScrollPosition(options: {
throttle: number,
}): { x: number, y: number } {
let opts = Object.assign({}, defaultOptions, options);
let [position, setPosition] = useState(getPosition());
throttle: number;
}): {
x: number;
y: number;
} {
const opts = Object.assign({}, defaultOptions, options);
const [position, setPosition] = useState(getPosition());
useEffect(() => {
let handleScroll = throttle(() => {
const handleScroll = throttle(() => {
setPosition(getPosition());
}, opts.throttle);
window.addEventListener(
"scroll",
handleScroll,
supportsPassive ? { passive: true } : false
supportsPassive
? {
passive: true,
}
: false
);
return () => {
handleScroll.cancel();
window.removeEventListener("scroll", handleScroll);

View File

@@ -1,11 +1,10 @@
// @flow
import { debounce } from "lodash";
import * as React from "react";
export default function useWindowSize() {
const [windowSize, setWindowSize] = React.useState({
width: parseInt(window.innerWidth),
height: parseInt(window.innerHeight),
width: window.innerWidth,
height: window.innerHeight,
});
React.useEffect(() => {
@@ -13,8 +12,8 @@ export default function useWindowSize() {
const handleResize = debounce(() => {
// Set window width/height to state
setWindowSize({
width: parseInt(window.innerWidth),
height: parseInt(window.innerHeight),
width: window.innerWidth,
height: window.innerHeight,
});
}, 100);
@@ -23,9 +22,7 @@ export default function useWindowSize() {
// Call handler right away so state gets updated with initial window size
handleResize();
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
}