mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
chore(ui): migrate more code to typescript
This commit is contained in:
committed by
Łukasz Mierzwa
parent
55170f8812
commit
4d4dd111c1
@@ -18,6 +18,7 @@ import {
|
||||
ReactSelectStyles,
|
||||
} from "Components/Theme/ReactSelect";
|
||||
import { BodyTheme, ThemeContext } from "Components/Theme";
|
||||
import { UIDefaults } from "./AppBoot";
|
||||
import { ErrorBoundary } from "./ErrorBoundary";
|
||||
|
||||
import "Styles/ResetCSS.scss";
|
||||
@@ -46,19 +47,9 @@ const FaviconBadge = React.lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
interface UIDefaults {
|
||||
Refresh: number;
|
||||
HideFiltersWhenIdle: boolean;
|
||||
ColorTitlebar: boolean;
|
||||
Theme: "light" | "dark" | "auto";
|
||||
MinimalGroupWidth: number;
|
||||
AlertsPerGroup: number;
|
||||
CollapseGroups: "expanded" | "collapsed" | "collapsedOnMobile";
|
||||
}
|
||||
|
||||
interface AppProps {
|
||||
defaultFilters: Array<string>;
|
||||
uiDefaults: UIDefaults;
|
||||
uiDefaults: UIDefaults | null;
|
||||
}
|
||||
|
||||
const App: FunctionComponent<AppProps> = ({ defaultFilters, uiDefaults }) => {
|
||||
|
||||
@@ -2,9 +2,19 @@
|
||||
|
||||
import { init } from "@sentry/browser";
|
||||
|
||||
export interface UIDefaults {
|
||||
Refresh: number;
|
||||
HideFiltersWhenIdle: boolean;
|
||||
ColorTitlebar: boolean;
|
||||
Theme: "light" | "dark" | "auto";
|
||||
MinimalGroupWidth: number;
|
||||
AlertsPerGroup: number;
|
||||
CollapseGroups: "expanded" | "collapsed" | "collapsedOnMobile";
|
||||
}
|
||||
|
||||
const SettingsElement = () => document.getElementById("settings");
|
||||
|
||||
const SetupSentry = (settingsElement) => {
|
||||
const SetupSentry = (settingsElement: HTMLElement | null) => {
|
||||
if (
|
||||
settingsElement !== null &&
|
||||
settingsElement.dataset.sentryDsn &&
|
||||
@@ -29,7 +39,7 @@ const SetupSentry = (settingsElement) => {
|
||||
}
|
||||
};
|
||||
|
||||
const ParseDefaultFilters = (settingsElement) => {
|
||||
const ParseDefaultFilters = (settingsElement: HTMLElement | null): string[] => {
|
||||
let defaultFilters = [];
|
||||
if (
|
||||
settingsElement !== null &&
|
||||
@@ -48,7 +58,9 @@ const ParseDefaultFilters = (settingsElement) => {
|
||||
return defaultFilters;
|
||||
};
|
||||
|
||||
const ParseUIDefaults = (defaultsElement) => {
|
||||
const ParseUIDefaults = (
|
||||
defaultsElement: HTMLElement | null
|
||||
): UIDefaults | null => {
|
||||
if (defaultsElement === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
function IsMobile() {
|
||||
function IsMobile(): boolean {
|
||||
return window.innerWidth < 768;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,13 @@ const FetchRetryConfig = {
|
||||
maxTimeout: 5000,
|
||||
};
|
||||
|
||||
const FetchGet = async (uri, options, beforeRetry) =>
|
||||
type PreRetryCallback = (number: number) => void;
|
||||
|
||||
const FetchGet = async (
|
||||
uri: string,
|
||||
options: RequestInit,
|
||||
beforeRetry: PreRetryCallback
|
||||
) =>
|
||||
await promiseRetry(
|
||||
(retry, number) =>
|
||||
fetch(
|
||||
@@ -14,7 +14,7 @@ const StaticLabels = Object.freeze({
|
||||
SilenceID: "@silence_id",
|
||||
});
|
||||
|
||||
function FormatQuery(name, operator, value) {
|
||||
function FormatQuery(name: string, operator: string, value: string) {
|
||||
return `${name}${operator}${value}`;
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
const NewLabelName = (v) => `New label: ${v}`;
|
||||
|
||||
const NewLabelValue = (v) => `New value: ${v}`;
|
||||
|
||||
export { NewLabelName, NewLabelValue };
|
||||
5
ui/src/Common/Select.ts
Normal file
5
ui/src/Common/Select.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
const NewLabelName = (v: string) => `New label: ${v}`;
|
||||
|
||||
const NewLabelValue = (v: string) => `New value: ${v}`;
|
||||
|
||||
export { NewLabelName, NewLabelValue };
|
||||
@@ -1,9 +1,12 @@
|
||||
import React from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
const DropdownSlide = ({ children, duration, ...props }) => (
|
||||
const DropdownSlide: FC<{
|
||||
children: ReactNode;
|
||||
duration: number;
|
||||
}> = ({ children, duration, ...props }) => (
|
||||
<CSSTransition
|
||||
classNames="components-animation-slide"
|
||||
timeout={150}
|
||||
@@ -1,9 +1,13 @@
|
||||
import React from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
const MountModal = ({ children, duration, ...props }) => (
|
||||
const MountModal: FC<{
|
||||
children: ReactNode;
|
||||
in: boolean;
|
||||
unmountOnExit?: boolean;
|
||||
}> = ({ children, ...props }) => (
|
||||
<CSSTransition
|
||||
classNames="components-animation-modal"
|
||||
timeout={200}
|
||||
@@ -19,9 +23,12 @@ MountModal.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
const MountModalBackdrop = ({ children, duration, ...props }) => (
|
||||
const MountModalBackdrop: FC<{
|
||||
children: ReactNode;
|
||||
in?: boolean;
|
||||
unmountOnExit?: boolean;
|
||||
}> = ({ children, ...props }) => (
|
||||
<CSSTransition
|
||||
in={true}
|
||||
classNames="components-animation-backdrop"
|
||||
timeout={200}
|
||||
appear={true}
|
||||
@@ -1,10 +1,13 @@
|
||||
import React from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const CenteredMessage = ({ children, className }) => {
|
||||
const CenteredMessage: FC<{
|
||||
children: ReactNode;
|
||||
className: string;
|
||||
}> = ({ children, className }) => {
|
||||
const context = React.useContext(ThemeContext);
|
||||
return (
|
||||
<CSSTransition
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useState, useEffect, FC } from "react";
|
||||
|
||||
import { autorun } from "mobx";
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
@@ -8,7 +7,9 @@ import Favico from "favico.js";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
const FaviconBadge = ({ alertStore }) => {
|
||||
const FaviconBadge: FC<{
|
||||
alertStore: AlertStore;
|
||||
}> = ({ alertStore }) => {
|
||||
const [favico] = useState(
|
||||
new Favico({
|
||||
animation: "none",
|
||||
@@ -38,8 +39,5 @@ const FaviconBadge = ({ alertStore }) => {
|
||||
/>
|
||||
));
|
||||
};
|
||||
FaviconBadge.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
};
|
||||
|
||||
export { FaviconBadge };
|
||||
@@ -1,19 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
const FetchPauser = ({ children, alertStore }) => {
|
||||
useEffect(() => {
|
||||
alertStore.status.pause();
|
||||
return alertStore.status.resume;
|
||||
}, [alertStore.status]);
|
||||
|
||||
return children;
|
||||
};
|
||||
FetchPauser.propTypes = {
|
||||
children: PropTypes.any,
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
};
|
||||
|
||||
export { FetchPauser };
|
||||
17
ui/src/Components/FetchPauser/index.tsx
Normal file
17
ui/src/Components/FetchPauser/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { FC, ReactElement, useEffect } from "react";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
|
||||
const FetchPauser: FC<{
|
||||
children: ReactElement;
|
||||
alertStore: AlertStore;
|
||||
}> = ({ children, alertStore }) => {
|
||||
useEffect(() => {
|
||||
alertStore.status.pause();
|
||||
return alertStore.status.resume;
|
||||
}, [alertStore.status]);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export { FetchPauser };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useEffect, useRef, FC } from "react";
|
||||
|
||||
import { reaction } from "mobx";
|
||||
|
||||
@@ -8,8 +7,11 @@ import addSeconds from "date-fns/addSeconds";
|
||||
import { AlertStore, AlertStoreStatuses } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
const timer = useRef(null);
|
||||
const Fetcher: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
}> = ({ alertStore, settingsStore }) => {
|
||||
const timer = useRef(undefined as number | undefined);
|
||||
|
||||
const getSortSettings = () => {
|
||||
let sortSettings = {
|
||||
@@ -81,7 +83,7 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearInterval(timer.current);
|
||||
return () => window.clearInterval(timer.current);
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
@@ -89,7 +91,9 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
reaction(
|
||||
() =>
|
||||
JSON.stringify({
|
||||
filters: alertStore.filters.values.map((f) => f.raw).join(" "),
|
||||
filters: alertStore.filters.values
|
||||
.map((f: { raw: string }) => f.raw)
|
||||
.join(" "),
|
||||
grid: {
|
||||
sortOrder: settingsStore.gridConfig.config.sortOrder,
|
||||
sortLabel: settingsStore.gridConfig.config.sortLabel,
|
||||
@@ -115,10 +119,10 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
() => alertStore.status.paused,
|
||||
(paused) => {
|
||||
if (paused) {
|
||||
clearInterval(timer.current);
|
||||
timer.current = null;
|
||||
window.clearInterval(timer.current);
|
||||
timer.current = undefined;
|
||||
} else {
|
||||
timer.current = setInterval(
|
||||
timer.current = window.setInterval(
|
||||
() => window.requestAnimationFrame(fetchIfIdle),
|
||||
1000
|
||||
);
|
||||
@@ -131,9 +135,5 @@ const Fetcher = ({ alertStore, settingsStore }) => {
|
||||
|
||||
return <span />;
|
||||
};
|
||||
Fetcher.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
};
|
||||
|
||||
export { Fetcher };
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Tab = ({ title, active, onClick }) => (
|
||||
<span
|
||||
className={`nav-item nav-link cursor-pointer mx-1 px-2 ${
|
||||
active ? "active" : "components-tab-inactive"
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
);
|
||||
Tab.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
active: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { Tab };
|
||||
18
ui/src/Components/Modal/Tab.tsx
Normal file
18
ui/src/Components/Modal/Tab.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React, { FC, MouseEvent } from "react";
|
||||
|
||||
const Tab: FC<{
|
||||
title: string;
|
||||
active?: boolean;
|
||||
onClick: (event: MouseEvent<HTMLElement>) => void;
|
||||
}> = ({ title, active, onClick }) => (
|
||||
<span
|
||||
className={`nav-item nav-link cursor-pointer mx-1 px-2 ${
|
||||
active ? "active" : "components-tab-inactive"
|
||||
}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
);
|
||||
|
||||
export { Tab };
|
||||
@@ -3,7 +3,16 @@ import React from "react";
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { PressKey } from "__mocks__/PressKey";
|
||||
import { Modal } from ".";
|
||||
import { Modal, ModalInner } from ".";
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
document.body.className = "";
|
||||
});
|
||||
|
||||
const fakeToggle = jest.fn();
|
||||
|
||||
@@ -15,12 +24,21 @@ const MountedModal = (isOpen, isUpper) => {
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
document.body.className = "";
|
||||
});
|
||||
describe("<ModalInner />", () => {
|
||||
it("scroll isn't enabled if ref is null", () => {
|
||||
const useRefSpy = jest.spyOn(React, "useRef").mockImplementation(() =>
|
||||
Object.defineProperty({}, "current", {
|
||||
get: () => null,
|
||||
set: () => {},
|
||||
})
|
||||
);
|
||||
const tree = mount(<ModalInner isUpper toggleOpen={fakeToggle} />);
|
||||
tree.setProps({ isUpper: false });
|
||||
tree.setProps({ isUpper: true });
|
||||
tree.setProps({ isUpper: false });
|
||||
expect(useRefSpy).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
describe("<Modal />", () => {
|
||||
it("'modal-open' class is appended to MountModal container", () => {
|
||||
const tree = MountedModal(true);
|
||||
expect(tree.find("div").at(0).hasClass("modal-open")).toBe(true);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import React, { FC, useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
|
||||
|
||||
@@ -11,18 +10,25 @@ import {
|
||||
MountModalBackdrop,
|
||||
} from "Components/Animations/MountModal";
|
||||
|
||||
const ModalInner = ({ size, isUpper, toggleOpen, children }) => {
|
||||
const ref = useRef(null);
|
||||
const ModalInner: FC<{
|
||||
size: "lg" | "xl";
|
||||
isUpper: boolean;
|
||||
toggleOpen: () => void;
|
||||
}> = ({ size, isUpper, toggleOpen, children }) => {
|
||||
// needed for tests to spy on useRef
|
||||
const ref = React.useRef(null as HTMLDivElement | null);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.add("modal-open");
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true });
|
||||
if (ref.current !== null) {
|
||||
document.body.classList.add("modal-open");
|
||||
disableBodyScroll(ref.current, { reserveScrollBarGap: true });
|
||||
|
||||
let modal = ref.current;
|
||||
return () => {
|
||||
if (!isUpper) document.body.classList.remove("modal-open");
|
||||
enableBodyScroll(modal);
|
||||
};
|
||||
let modal = ref.current;
|
||||
return () => {
|
||||
if (!isUpper) document.body.classList.remove("modal-open");
|
||||
enableBodyScroll(modal);
|
||||
};
|
||||
}
|
||||
}, [isUpper]);
|
||||
|
||||
useHotkeys("esc", toggleOpen);
|
||||
@@ -43,7 +49,19 @@ const ModalInner = ({ size, isUpper, toggleOpen, children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Modal = ({ size, isOpen, isUpper, toggleOpen, children, ...props }) => {
|
||||
const Modal: FC<{
|
||||
size?: "lg" | "xl";
|
||||
isOpen: boolean;
|
||||
isUpper?: boolean;
|
||||
toggleOpen: () => void;
|
||||
}> = ({
|
||||
size = "lg",
|
||||
isOpen,
|
||||
isUpper = false,
|
||||
toggleOpen,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return ReactDOM.createPortal(
|
||||
<React.Fragment>
|
||||
<MountModal in={isOpen} unmountOnExit {...props}>
|
||||
@@ -58,16 +76,5 @@ const Modal = ({ size, isOpen, isUpper, toggleOpen, children, ...props }) => {
|
||||
document.body
|
||||
);
|
||||
};
|
||||
Modal.propTypes = {
|
||||
size: PropTypes.oneOf(["lg", "xl"]),
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
isUpper: PropTypes.bool,
|
||||
toggleOpen: PropTypes.func.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
Modal.defaultProps = {
|
||||
size: "lg",
|
||||
isUpper: false,
|
||||
};
|
||||
|
||||
export { Modal };
|
||||
export { Modal, ModalInner };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
} from "Components/LabelSetList";
|
||||
import { useFetchGet } from "Hooks/useFetchGet";
|
||||
|
||||
const FetchError = ({ message }) => (
|
||||
const FetchError: FC<{ message: ReactNode }> = ({ message }) => (
|
||||
<div className="text-center">
|
||||
<h2 className="display-2 text-danger">
|
||||
<FontAwesomeIcon icon={faExclamationCircle} />
|
||||
@@ -20,9 +19,6 @@ const FetchError = ({ message }) => (
|
||||
<p className="lead text-muted">{message}</p>
|
||||
</div>
|
||||
);
|
||||
FetchError.propTypes = {
|
||||
message: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
const Placeholder = () => (
|
||||
<div className="jumbotron bg-transparent">
|
||||
@@ -32,7 +28,11 @@ const Placeholder = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const PaginatedAlertList = ({ alertStore, filters, title }) => {
|
||||
const PaginatedAlertList: FC<{
|
||||
alertStore: AlertStore;
|
||||
filters: string[];
|
||||
title: string;
|
||||
}> = ({ alertStore, filters, title }) => {
|
||||
const { response, error, isLoading } = useFetchGet(
|
||||
FormatBackendURI("alerts.json?") + FormatAlertsQ(filters)
|
||||
);
|
||||
@@ -51,10 +51,5 @@ const PaginatedAlertList = ({ alertStore, filters, title }) => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
PaginatedAlertList.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export { PaginatedAlertList };
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useState, useEffect, FC } from "react";
|
||||
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
@@ -13,16 +12,24 @@ import { faAngleDoubleRight } from "@fortawesome/free-solid-svg-icons/faAngleDou
|
||||
|
||||
import { IsMobile } from "Common/Device";
|
||||
|
||||
const PageSelect = ({
|
||||
type PageCallback = (page: number) => void;
|
||||
|
||||
const PageSelect: FC<{
|
||||
totalItemsCount: number;
|
||||
totalPages: number;
|
||||
maxPerPage: number;
|
||||
initialPage: number;
|
||||
setPageCallback: PageCallback;
|
||||
}> = ({
|
||||
totalItemsCount,
|
||||
totalPages,
|
||||
maxPerPage,
|
||||
initialPage,
|
||||
initialPage = 1,
|
||||
setPageCallback,
|
||||
}) => {
|
||||
const [activePage, setActivePage] = useState(initialPage);
|
||||
|
||||
const onChange = (page) => {
|
||||
const onChange = (page: number) => {
|
||||
setActivePage(page);
|
||||
setPageCallback(page);
|
||||
};
|
||||
@@ -83,15 +90,5 @@ const PageSelect = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
PageSelect.propTypes = {
|
||||
totalPages: PropTypes.number.isRequired,
|
||||
initialPage: PropTypes.number,
|
||||
maxPerPage: PropTypes.number.isRequired,
|
||||
totalItemsCount: PropTypes.number.isRequired,
|
||||
setPageCallback: PropTypes.func.isRequired,
|
||||
};
|
||||
PageSelect.defaultProps = {
|
||||
initialPage: 1,
|
||||
};
|
||||
|
||||
export { PageSelect };
|
||||
@@ -1,4 +1,25 @@
|
||||
const ReactSelectColors = {
|
||||
import { CSSProperties } from "react";
|
||||
import { Styles } from "react-select";
|
||||
|
||||
interface ReactSelectTheme {
|
||||
color: string;
|
||||
singleValueColor: string;
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
focusedBoxShadow: string;
|
||||
focusedBorderColor: string;
|
||||
menuBackground: string;
|
||||
optionHoverBackground: string;
|
||||
valueContainerBackground: string;
|
||||
disabledValueContainerBackground: string;
|
||||
}
|
||||
|
||||
interface ReactSelectThemes {
|
||||
Light: ReactSelectTheme;
|
||||
Dark: ReactSelectTheme;
|
||||
}
|
||||
|
||||
const ReactSelectColors: ReactSelectThemes = {
|
||||
Light: {
|
||||
color: "#fff",
|
||||
singleValueColor: "#000",
|
||||
@@ -25,8 +46,8 @@ const ReactSelectColors = {
|
||||
},
|
||||
};
|
||||
|
||||
const ReactSelectStyles = (theme) => ({
|
||||
control: (base, state) =>
|
||||
const ReactSelectStyles = (theme: ReactSelectTheme): Styles => ({
|
||||
control: (base: CSSProperties, state: any) =>
|
||||
state.isFocused
|
||||
? {
|
||||
...base,
|
||||
@@ -47,7 +68,7 @@ const ReactSelectStyles = (theme) => ({
|
||||
borderColor: theme.borderColor,
|
||||
"&:hover": { borderColor: theme.borderColor },
|
||||
},
|
||||
valueContainer: (base, state) =>
|
||||
valueContainer: (base: CSSProperties, state: any) =>
|
||||
state.isMulti
|
||||
? {
|
||||
...base,
|
||||
@@ -72,11 +93,11 @@ const ReactSelectStyles = (theme) => ({
|
||||
? theme.disabledValueContainerBackground
|
||||
: theme.valueContainerBackground,
|
||||
},
|
||||
singleValue: (base, state) => ({
|
||||
singleValue: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: theme.singleValueColor,
|
||||
}),
|
||||
multiValue: (base, state) => ({
|
||||
multiValue: (base: CSSProperties) => ({
|
||||
...base,
|
||||
borderRadius: "4px",
|
||||
backgroundColor: theme.optionHoverBackground,
|
||||
@@ -84,7 +105,7 @@ const ReactSelectStyles = (theme) => ({
|
||||
backgroundColor: theme.optionHoverBackground,
|
||||
},
|
||||
}),
|
||||
multiValueLabel: (base, state) => ({
|
||||
multiValueLabel: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: theme.color,
|
||||
whiteSpace: "normal",
|
||||
@@ -94,7 +115,7 @@ const ReactSelectStyles = (theme) => ({
|
||||
color: theme.color,
|
||||
},
|
||||
}),
|
||||
multiValueRemove: (base, state) => ({
|
||||
multiValueRemove: (base: CSSProperties) => ({
|
||||
...base,
|
||||
cursor: "pointer",
|
||||
color: theme.color,
|
||||
@@ -107,11 +128,11 @@ const ReactSelectStyles = (theme) => ({
|
||||
opacity: "0.75",
|
||||
},
|
||||
}),
|
||||
input: (base, state) => ({
|
||||
input: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: "inherit",
|
||||
}),
|
||||
indicatorsContainer: (base, state) => ({
|
||||
indicatorsContainer: (base: CSSProperties, state: any) => ({
|
||||
...base,
|
||||
backgroundColor: state.isDisabled
|
||||
? theme.disabledValueContainerBackground
|
||||
@@ -119,19 +140,19 @@ const ReactSelectStyles = (theme) => ({
|
||||
borderTopRightRadius: "0.25rem",
|
||||
borderBottomRightRadius: "0.25rem",
|
||||
}),
|
||||
dropdownIndicator: (base, state) =>
|
||||
dropdownIndicator: (base: CSSProperties, state: any) =>
|
||||
state.isFocused
|
||||
? {
|
||||
...base,
|
||||
"&:hover": { color: "inherit" },
|
||||
}
|
||||
: { ...base },
|
||||
menu: (base, state) => ({
|
||||
menu: (base: CSSProperties) => ({
|
||||
...base,
|
||||
zIndex: 1500,
|
||||
backgroundColor: theme.menuBackground,
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
option: (base: CSSProperties) => ({
|
||||
...base,
|
||||
color: "inherit",
|
||||
backgroundColor: "inherit",
|
||||
@@ -38,7 +38,19 @@ const Placeholder = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const ThemeContext = React.createContext();
|
||||
interface ThemeCtx {
|
||||
isDark: boolean;
|
||||
reactSelectStyles: any;
|
||||
animations: {
|
||||
duration: number;
|
||||
};
|
||||
}
|
||||
|
||||
const ThemeContext = React.createContext({
|
||||
isDark: false,
|
||||
reactSelectStyles: {},
|
||||
animations: { duration: 1000 },
|
||||
} as ThemeCtx);
|
||||
|
||||
const BodyTheme = () => {
|
||||
const context = React.useContext(ThemeContext);
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, ReactNode, FC } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
|
||||
@@ -8,16 +7,24 @@ import { usePopper } from "react-popper";
|
||||
|
||||
import { useSupportsTouch } from "Hooks/useSupportsTouch";
|
||||
|
||||
const TooltipWrapper = ({ title, children, className }) => {
|
||||
const [referenceElement, setReferenceElement] = useState(null);
|
||||
const [popperElement, setPopperElement] = useState(null);
|
||||
const TooltipWrapper: FC<{
|
||||
title: ReactNode;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}> = ({ title, children, className }) => {
|
||||
const [referenceElement, setReferenceElement] = useState(
|
||||
null as HTMLElement | null
|
||||
);
|
||||
const [popperElement, setPopperElement] = useState(
|
||||
null as HTMLElement | null
|
||||
);
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "top",
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
boundariesElement: "viewport",
|
||||
rootBoundary: "viewport",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -32,22 +39,22 @@ const TooltipWrapper = ({ title, children, className }) => {
|
||||
const hideTooltip = () => setIsHovering(false);
|
||||
|
||||
useEffect(() => {
|
||||
let timerShow;
|
||||
let timerHide;
|
||||
let timerShow: number | undefined;
|
||||
let timerHide: number | undefined;
|
||||
|
||||
if (!isHovering) {
|
||||
if (isVisible) {
|
||||
clearTimeout(timerShow);
|
||||
timerHide = setTimeout(() => setIsVisible(false), 100);
|
||||
window.clearTimeout(timerShow);
|
||||
timerHide = window.setTimeout(() => setIsVisible(false), 100);
|
||||
}
|
||||
setWasClicked(false);
|
||||
} else if (wasClicked) {
|
||||
clearTimeout(timerShow);
|
||||
clearTimeout(timerHide);
|
||||
window.clearTimeout(timerShow);
|
||||
window.clearTimeout(timerHide);
|
||||
setIsVisible(false);
|
||||
} else if (!isVisible && isHovering) {
|
||||
clearTimeout(timerHide);
|
||||
timerShow = setTimeout(() => setIsVisible(true), 1000);
|
||||
timerShow = window.setTimeout(() => setIsVisible(true), 1000);
|
||||
}
|
||||
return () => {
|
||||
clearTimeout(timerShow);
|
||||
@@ -59,11 +66,11 @@ const TooltipWrapper = ({ title, children, className }) => {
|
||||
<React.Fragment>
|
||||
<div
|
||||
onClick={() => setWasClicked(true)}
|
||||
onMouseOver={supportsTouch ? null : showTooltip}
|
||||
onMouseLeave={supportsTouch ? null : hideTooltip}
|
||||
onTouchStart={supportsTouch ? showTooltip : null}
|
||||
onTouchCancel={supportsTouch ? hideTooltip : null}
|
||||
onTouchEnd={supportsTouch ? hideTooltip : null}
|
||||
onMouseOver={supportsTouch ? undefined : showTooltip}
|
||||
onMouseLeave={supportsTouch ? undefined : hideTooltip}
|
||||
onTouchStart={supportsTouch ? showTooltip : undefined}
|
||||
onTouchCancel={supportsTouch ? hideTooltip : undefined}
|
||||
onTouchEnd={supportsTouch ? hideTooltip : undefined}
|
||||
ref={setReferenceElement}
|
||||
className={`${className ? className : ""} tooltip-trigger`}
|
||||
>
|
||||
@@ -94,10 +101,5 @@ const TooltipWrapper = ({ title, children, className }) => {
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
TooltipWrapper.propTypes = {
|
||||
title: PropTypes.node.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export { TooltipWrapper };
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
// https://usehooks.com/useDebounce/
|
||||
function useDebounce(value, delay) {
|
||||
function useDebounce(value: any, delay: number) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -4,14 +4,26 @@ import merge from "lodash.merge";
|
||||
|
||||
import { CommonOptions } from "Common/Fetch";
|
||||
|
||||
const useFetchAny = (upstreams, { fetcher = null } = {}) => {
|
||||
interface Upstream {
|
||||
uri: string;
|
||||
options: RequestInit;
|
||||
}
|
||||
|
||||
interface ResponseState {
|
||||
response: string | null;
|
||||
error: string | null;
|
||||
responseURI: string | null;
|
||||
inProgress: boolean;
|
||||
}
|
||||
|
||||
const useFetchAny = (upstreams: Upstream[], { fetcher = null } = {}) => {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [response, setResponse] = useState({
|
||||
response: null,
|
||||
error: null,
|
||||
responseURI: null,
|
||||
inProgress: false,
|
||||
});
|
||||
} as ResponseState);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setIndex(0);
|
||||
@@ -39,7 +51,7 @@ const useFetchAny = (upstreams, { fetcher = null } = {}) => {
|
||||
try {
|
||||
const res = await (fetcher || fetch)(
|
||||
uri,
|
||||
merge({}, { method: "GET" }, CommonOptions, options)
|
||||
merge({}, { method: "GET" }, CommonOptions, options) as RequestInit
|
||||
);
|
||||
|
||||
if (!isCancelled) {
|
||||
@@ -4,9 +4,9 @@ import merge from "lodash.merge";
|
||||
|
||||
import { CommonOptions } from "Common/Fetch";
|
||||
|
||||
const useFetchDelete = (uri, options, deps = []) => {
|
||||
const [response, setResponse] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const useFetchDelete = (uri: string, options: RequestInit, deps = []) => {
|
||||
const [response, setResponse] = useState(null as string | null);
|
||||
const [error, setError] = useState(null as string | null);
|
||||
const [isDeleting, setIsDeleting] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -7,11 +7,11 @@ import promiseRetry from "promise-retry";
|
||||
import { CommonOptions, FetchRetryConfig } from "Common/Fetch";
|
||||
|
||||
const useFetchGet = (
|
||||
uri,
|
||||
uri: string,
|
||||
{ autorun = true, deps = [], fetcher = null } = {}
|
||||
) => {
|
||||
const [response, setResponse] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [response, setResponse] = useState(null as any);
|
||||
const [error, setError] = useState(null as string | null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isRetrying, setIsRetrying] = useState(false);
|
||||
const [retryCount, setRetryCount] = useState(0);
|
||||
@@ -29,7 +29,7 @@ const useFetchGet = (
|
||||
setRetryCount(0);
|
||||
setError(null);
|
||||
const res = await promiseRetry(
|
||||
(retry, number) =>
|
||||
(retry: Function, number: number) =>
|
||||
(fetcher || fetch)(
|
||||
uri,
|
||||
merge(
|
||||
@@ -41,7 +41,7 @@ const useFetchGet = (
|
||||
{
|
||||
mode: number <= FetchRetryConfig.retries ? "cors" : "no-cors",
|
||||
}
|
||||
)
|
||||
) as RequestInit
|
||||
).catch((err) => {
|
||||
if (!isCanceled.current) {
|
||||
setIsRetrying(true);
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
import { TransitionProps } from "react-transition-group/Transition";
|
||||
|
||||
import { useInView } from "react-intersection-observer";
|
||||
|
||||
const defaultProps = {
|
||||
const defaultProps: TransitionProps = {
|
||||
in: false,
|
||||
classNames: "components-animation-flash",
|
||||
timeout: 800,
|
||||
@@ -11,7 +13,7 @@ const defaultProps = {
|
||||
exit: false,
|
||||
};
|
||||
|
||||
const useFlashTransition = (flashOn) => {
|
||||
const useFlashTransition = (flashOn: any) => {
|
||||
const mountRef = useRef(false);
|
||||
const [ref, inView] = useInView();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
import Bricks from "bricks.js";
|
||||
import Bricks, { SizeDetail, Instance } from "bricks.js";
|
||||
|
||||
const useGrid = (sizes) => {
|
||||
const ref = useRef(null);
|
||||
const grid = useRef(null);
|
||||
const useGrid = (sizes: SizeDetail[]) => {
|
||||
const ref = useRef(null as Node | null);
|
||||
const grid = useRef(null as Instance | null);
|
||||
const [repack, setRepack] = useState(() => () => {});
|
||||
|
||||
useEffect(() => {
|
||||
if (!grid.current && ref.current) {
|
||||
grid.current = new Bricks({
|
||||
grid.current = Bricks({
|
||||
container: ref.current,
|
||||
sizes: sizes,
|
||||
packed: "packed",
|
||||
@@ -1,10 +1,18 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, RefObject } from "react";
|
||||
|
||||
type Handler = (event: MouseEvent | TouchEvent) => void;
|
||||
|
||||
// https://usehooks.com/useOnClickOutside/
|
||||
function useOnClickOutside(ref, handler, enabled) {
|
||||
function useOnClickOutside(
|
||||
ref: RefObject<HTMLElement>,
|
||||
handler: Handler,
|
||||
enabled: boolean
|
||||
) {
|
||||
useEffect(() => {
|
||||
const listener = (event) => {
|
||||
if (!ref.current || ref.current.contains(event.target)) {
|
||||
const listener: { (event: MouseEvent | TouchEvent): void } = (
|
||||
event: MouseEvent | TouchEvent
|
||||
) => {
|
||||
if (!ref.current || ref.current.contains(event.target as Node)) {
|
||||
return;
|
||||
}
|
||||
handler(event);
|
||||
2
ui/src/react-app-env.d.ts
vendored
2
ui/src/react-app-env.d.ts
vendored
@@ -3,3 +3,5 @@
|
||||
declare module "react-media-hook" {
|
||||
function useMediaPredicate(query: string): boolean;
|
||||
}
|
||||
|
||||
declare module "favico.js";
|
||||
|
||||
Reference in New Issue
Block a user