refactor(routing): add routing

this is in preparation for future changes where we'll have more pages
This commit is contained in:
Tao Bror Bojlén 2019-04-17 14:38:00 +01:00
parent 59ac4cf0e3
commit 5356b58d83
No known key found for this signature in database
GPG key ID: C6EC7AAB905F9E6F
15 changed files with 495 additions and 273 deletions

View file

@ -1,3 +0,0 @@
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'

View file

@ -17,15 +17,14 @@
}
},
"lint-staged": {
"src/**/*.{ts,tsx,json,css}": [
"src/**/*.{ts,tsx}": [
"yarn pretty",
"yarn lint:fix",
"git add"
]
},
"prettier": {
"printWidth": 120,
"parser": "typescript"
"printWidth": 120
},
"dependencies": {
"@blueprintjs/core": "^3.4.0",
@ -39,12 +38,14 @@
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-redux": "^7.0.2",
"react-router-dom": "^5.0.0",
"react-scripts-ts": "3.1.0",
"react-sigma": "^1.2.30",
"react-virtualized": "^9.20.1",
"redux": "^4.0.0",
"redux-thunk": "^2.3.0",
"sanitize-html": "^1.18.4"
"sanitize-html": "^1.18.4",
"styled-components": "^4.2.0"
},
"devDependencies": {
"@blueprintjs/tslint-config": "^1.8.0",
@ -55,8 +56,10 @@
"@types/react": "^16.4.12",
"@types/react-dom": "^16.0.7",
"@types/react-redux": "^7.0.6",
"@types/react-router-dom": "^4.3.2",
"@types/react-virtualized": "^9.18.7",
"@types/sanitize-html": "^1.18.0",
"@types/styled-components": "4.1.8",
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"tslint-eslint-rules": "^5.4.0",

View file

@ -1,131 +0,0 @@
import * as React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { Button, Classes, Dialog, NonIdealState, Spinner } from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
import { ErrorState } from "./components/ErrorState";
import { Graph } from "./components/Graph";
import { Nav } from "./components/Nav";
import { Sidebar } from "./components/Sidebar";
import { DESKTOP_WIDTH_THRESHOLD } from "./constants";
import { fetchGraph, fetchInstances } from "./redux/actions";
import { IAppState, IGraph, IInstance } from "./redux/types";
interface IAppProps {
graph?: IGraph;
instances?: IInstance[];
isLoadingGraph: boolean;
isLoadingInstances: boolean;
graphLoadError: boolean;
fetchInstances: () => void;
fetchGraph: () => void;
}
interface IAppLocalState {
mobileDialogOpen: boolean;
}
class AppImpl extends React.Component<IAppProps, IAppLocalState> {
constructor(props: IAppProps) {
super(props);
this.state = { mobileDialogOpen: false };
}
public render() {
let body = <div />;
if (this.props.isLoadingInstances || this.props.isLoadingGraph) {
body = this.loadingState("Loading...");
} else {
body = this.graphState();
}
return (
<div className={`${Classes.DARK} App`}>
<Nav />
{body}
{this.renderMobileDialog()}
</div>
);
}
public componentDidMount() {
if (window.innerWidth < DESKTOP_WIDTH_THRESHOLD) {
this.handleMobileDialogOpen();
}
this.load();
}
public componentDidUpdate() {
this.load();
}
private load = () => {
if (!this.props.instances && !this.props.isLoadingInstances && !this.props.graphLoadError) {
this.props.fetchInstances();
}
if (!this.props.graph && !this.props.isLoadingGraph && !this.props.graphLoadError) {
this.props.fetchGraph();
}
};
private graphState = () => {
const content = this.props.graphLoadError ? <ErrorState /> : <Graph />;
return (
<div>
<Sidebar />
{content}
</div>
);
};
private loadingState = (title?: string) => {
return <NonIdealState icon={<Spinner />} title={title || "Loading..."} />;
};
private renderMobileDialog = () => {
return (
<Dialog
icon={IconNames.DESKTOP}
title="Desktop-optimized site"
onClose={this.handleMobileDialogClose}
isOpen={this.state.mobileDialogOpen}
className={Classes.DARK + " fediverse-about-dialog"}
>
<div className={Classes.DIALOG_BODY}>
<p className={Classes.RUNNING_TEXT}>
fediverse.space is optimized for desktop computers. Feel free to check it out on your phone (ideally in
landscape mode) but for best results, open it on a computer.
</p>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button icon={IconNames.THUMBS_UP} text="OK!" onClick={this.handleMobileDialogClose} />
</div>
</div>
</Dialog>
);
};
private handleMobileDialogOpen = () => {
this.setState({ mobileDialogOpen: true });
};
private handleMobileDialogClose = () => {
this.setState({ mobileDialogOpen: false });
};
}
const mapStateToProps = (state: IAppState) => ({
graph: state.data.graph,
graphLoadError: state.data.error,
instances: state.data.instances,
isLoadingGraph: state.data.isLoadingGraph,
isLoadingInstances: state.data.isLoadingInstances
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
fetchGraph: () => dispatch(fetchGraph() as any),
fetchInstances: () => dispatch(fetchInstances() as any)
});
export const App = connect(
mapStateToProps,
mapDispatchToProps
)(AppImpl);

View file

@ -0,0 +1,71 @@
import * as React from "react";
import { Button, Classes, Dialog } from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
import { BrowserRouter, Route } from "react-router-dom";
import { Nav } from "./components/Nav";
import { AboutScreen } from "./components/screens/AboutScreen";
import { GraphScreen } from "./components/screens/GraphScreen";
import { DESKTOP_WIDTH_THRESHOLD } from "./constants";
interface IAppLocalState {
mobileDialogOpen: boolean;
}
export class AppRouter extends React.Component<{}, IAppLocalState> {
constructor(props: {}) {
super(props);
this.state = { mobileDialogOpen: false };
}
public render() {
return (
<BrowserRouter>
<div className={`${Classes.DARK} App`}>
<Nav />
<Route exact={true} path="/" component={GraphScreen} />
<Route path="/about" component={AboutScreen} />
{this.renderMobileDialog()}
</div>
</BrowserRouter>
);
}
public componentDidMount() {
if (window.innerWidth < DESKTOP_WIDTH_THRESHOLD) {
this.handleMobileDialogOpen();
}
}
private renderMobileDialog = () => {
return (
<Dialog
icon={IconNames.DESKTOP}
title="Desktop-optimized site"
onClose={this.handleMobileDialogClose}
isOpen={this.state.mobileDialogOpen}
className={Classes.DARK + " fediverse-about-dialog"}
>
<div className={Classes.DIALOG_BODY}>
<p className={Classes.RUNNING_TEXT}>
fediverse.space is optimized for desktop computers. Feel free to check it out on your phone (ideally in
landscape mode) but for best results, open it on a computer.
</p>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button icon={IconNames.THUMBS_UP} text="OK!" onClick={this.handleMobileDialogClose} />
</div>
</div>
</Dialog>
);
};
private handleMobileDialogOpen = () => {
this.setState({ mobileDialogOpen: true });
};
private handleMobileDialogClose = () => {
this.setState({ mobileDialogOpen: false });
};
}

View file

@ -6,10 +6,11 @@ import { Button, MenuItem } from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
import { IItemRendererProps, ItemPredicate, Select } from "@blueprintjs/select";
import { RouteComponentProps, withRouter } from "react-router";
import { selectAndLoadInstance } from "../redux/actions";
import { IAppState, IInstance } from "../redux/types";
interface IInstanceSearchProps {
interface IInstanceSearchProps extends RouteComponentProps {
currentInstanceName: string | null;
instances?: IInstance[];
selectAndLoadInstance: (instanceName: string) => void;
@ -25,7 +26,7 @@ class InstanceSearchImpl extends React.Component<IInstanceSearchProps> {
itemRenderer={this.itemRenderer}
onItemSelect={this.onItemSelect}
itemPredicate={this.itemPredicate}
disabled={!this.props.instances}
disabled={!this.props.instances || this.props.location.pathname !== "/"}
initialContent={this.renderInitialContent()}
noResults={this.renderNoResults()}
popoverProps={{ popoverClassName: "fediverse-instance-search-popover" }}
@ -76,7 +77,9 @@ const mapStateToProps = (state: IAppState) => ({
const mapDispatchToProps = (dispatch: Dispatch) => ({
selectAndLoadInstance: (instanceName: string) => dispatch(selectAndLoadInstance(instanceName) as any)
});
export const InstanceSearch = connect(
export const InstanceSearch = withRouter(
connect(
mapStateToProps,
mapDispatchToProps
)(InstanceSearchImpl);
)(InstanceSearchImpl)
);

View file

@ -1,8 +1,10 @@
import * as React from "react";
import { Alignment, Button, Classes, Code, Dialog, H2, H4, Icon, Navbar } from "@blueprintjs/core";
import { Alignment, Button, Navbar } from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { InstanceSearch } from "./InstanceSearch";
interface INavState {
@ -15,26 +17,20 @@ export class Nav extends React.Component<{}, INavState> {
}
public render() {
const StyledLink = styled(Link)`
color: white !important;
`;
return (
<Navbar fixedToTop={true}>
<Navbar.Group align={Alignment.LEFT}>
<Navbar.Heading>
<Icon icon={IconNames.GLOBE_NETWORK} iconSize={Icon.SIZE_LARGE} className="fediverse-heading-icon" />
fediverse.space
</Navbar.Heading>
<Navbar.Heading>fediverse.space</Navbar.Heading>
<Navbar.Divider />
<Button icon={IconNames.INFO_SIGN} text="About" minimal={true} onClick={this.handleAboutOpen} />
{this.renderAboutDialog()}
{/* <Button
icon={<Icon icon={IconNames.GLOBE_NETWORK} />}
text="Network"
minimal={true}
/>
<Button
icon={<Icon icon={IconNames.GLOBE} />}
text="Map"
minimal={true}
/> */}
<StyledLink to="/">
<Button icon={IconNames.GLOBE_NETWORK} text="Home" minimal={true} />
</StyledLink>
<StyledLink to="/about">
<Button icon={IconNames.INFO_SIGN} text="About" minimal={true} />
</StyledLink>
</Navbar.Group>
<Navbar.Group align={Alignment.RIGHT}>
<InstanceSearch />
@ -42,84 +38,4 @@ export class Nav extends React.Component<{}, INavState> {
</Navbar>
);
}
private renderAboutDialog = () => {
return (
<Dialog
icon={IconNames.INFO_SIGN}
title="About"
onClose={this.handleAboutClose}
isOpen={this.state.aboutIsOpen}
className={Classes.DARK + " fediverse-about-dialog"}
>
<div className={Classes.DIALOG_BODY}>
<p className={Classes.RUNNING_TEXT}>
fediverse.space is a tool to visualize networks and communities on the{" "}
<a href="https://en.wikipedia.org/wiki/Fediverse" target="_blank">
fediverse
</a>
. It works by scraping every instance it can find and aggregating statistics on communication between these.
</p>
<H2>FAQ</H2>
<H4>Why can't I see details about my instance?</H4>
<p className={Classes.RUNNING_TEXT}>
Currently, fediverse.space only supports Mastodon and Pleroma instances. In addition, instances with 5 or
fewer users won't be scraped -- it's a tool for understanding communities, not individuals.
</p>
<H4>How do you calculate the strength of relationships between instances?</H4>
<p className={Classes.RUNNING_TEXT}>
fediverse.space scrapes the last 5000 statuses from within the last month on the public timeline of each
instance. It looks at the ratio of
<Code>mentions of an instance / total statuses</Code>. It uses a ratio rather than an absolute number of
mentions to reflect that smaller instances can play a large role in a community.
</p>
<H2>Credits</H2>
<p className={Classes.RUNNING_TEXT}>
This site is inspired by several other sites in the same vein:
<ul className={Classes.LIST}>
<li>
<a href="https://the-federation.info/" target="_blank">
the-federation.info
</a>
</li>
<li>
<a href="http://fediverse.network/" target="_blank">
fediverse.network
</a>
</li>
<li>
<a href="https://lucahammer.at/vis/fediverse/2018-08-30-mastoverse_hashtags/" target="_blank">
Mastodon hashtag network
</a>
{" by "}
<a href="https://vis.social/web/statuses/100634284168959187" target="_blank">
@Luca@vis.social
</a>
</li>
</ul>
The source code for fediverse.space is available on{" "}
<a href="https://gitlab.com/taobojlen/fediverse.space" target="_blank">
GitLab
</a>
; issues and pull requests are welcome!
</p>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button icon={IconNames.THUMBS_UP} text="OK!" onClick={this.handleAboutClose} />
</div>
</div>
</Dialog>
);
};
private handleAboutOpen = () => {
this.setState({ aboutIsOpen: true });
};
private handleAboutClose = () => {
this.setState({ aboutIsOpen: false });
};
}

View file

@ -0,0 +1,7 @@
import styled from "styled-components";
export const Page = styled.div`
max-width: 800px;
margin: auto;
padding: 2em;
`;

View file

@ -0,0 +1,83 @@
import { Classes, Code, H1, H2, H4 } from "@blueprintjs/core";
import * as React from "react";
import { Page } from "../Page";
export const AboutScreen: React.FC = () => (
<Page>
<H1>About</H1>
<p className={Classes.RUNNING_TEXT}>
fediverse.space is a tool to visualize networks and communities on the{" "}
<a href="https://en.wikipedia.org/wiki/Fediverse" target="_blank">
fediverse
</a>
. It works by crawling every instance it can find and aggregating statistics on communication between these.
</p>
<H2>FAQ</H2>
<H4>Why can't I see details about my instance?</H4>
<p className={Classes.RUNNING_TEXT}>
Currently, fediverse.space only supports Mastodon and Pleroma instances. In addition, instances with 10 or fewer
users won't be scraped -- it's a tool for understanding communities, not individuals.
</p>
<H4>
When is <Code>$OTHER_ACTIVITYPUB_SERVER</Code> going to be added?
</H4>
<p className={Classes.RUNNING_TEXT}>
Check out{" "}
<a href="https://gitlab.com/taobojlen/fediverse.space/issues/24" target="_blank">
this GitLab issue
</a>
.
</p>
<H4>How do I add my personal instance?</H4>
<p className={Classes.RUNNING_TEXT}>
Send a DM to{" "}
<a href="https://cursed.technology/@fediversespace" target="_blank">
@fediversespace
</a>{" "}
on Mastodon. Make sure to send it from the account that's listed as the instance admin.
</p>
<H4>How do you calculate the strength of relationships between instances?</H4>
<p className={Classes.RUNNING_TEXT}>
fediverse.space scrapes the last 5000 statuses from within the last month on the public timeline of each instance.
It looks at the ratio of
<Code>mentions of an instance / total statuses</Code>. It uses a ratio rather than an absolute number of mentions
to reflect that smaller instances can play a large role in a community.
</p>
<H2>Credits</H2>
<p className={Classes.RUNNING_TEXT}>
This site is inspired by several other sites in the same vein:
<ul className={Classes.LIST}>
<li>
<a href="https://the-federation.info/" target="_blank">
the-federation.info
</a>
</li>
<li>
<a href="http://fediverse.network/" target="_blank">
fediverse.network
</a>
</li>
<li>
<a href="https://lucahammer.at/vis/fediverse/2018-08-30-mastoverse_hashtags/" target="_blank">
Mastodon hashtag network
</a>
{" by "}
<a href="https://vis.social/web/statuses/100634284168959187" target="_blank">
@Luca@vis.social
</a>
</li>
</ul>
The source code for fediverse.space is available on{" "}
<a href="https://gitlab.com/taobojlen/fediverse.space" target="_blank">
GitLab
</a>
; issues and pull requests are welcome!
</p>
</Page>
);

View file

@ -0,0 +1,79 @@
import * as React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { NonIdealState, Spinner } from "@blueprintjs/core";
import { fetchGraph, fetchInstances } from "../../redux/actions";
import { IAppState, IGraph, IInstance } from "../../redux/types";
import { ErrorState } from "../ErrorState";
import { Graph } from "../Graph";
import { Sidebar } from "../Sidebar";
interface IGraphScreenProps {
graph?: IGraph;
instances?: IInstance[];
isLoadingGraph: boolean;
isLoadingInstances: boolean;
graphLoadError: boolean;
fetchInstances: () => void;
fetchGraph: () => void;
}
class GraphScreenImpl extends React.Component<IGraphScreenProps> {
public render() {
let body = <div />;
if (this.props.isLoadingInstances || this.props.isLoadingGraph) {
body = this.loadingState("Loading...");
} else {
body = this.graphState();
}
return <div>{body}</div>;
}
public componentDidMount() {
this.load();
}
public componentDidUpdate() {
this.load();
}
private load = () => {
if (!this.props.instances && !this.props.isLoadingInstances && !this.props.graphLoadError) {
this.props.fetchInstances();
}
if (!this.props.graph && !this.props.isLoadingGraph && !this.props.graphLoadError) {
this.props.fetchGraph();
}
};
private graphState = () => {
const content = this.props.graphLoadError ? <ErrorState /> : <Graph />;
return (
<div>
<Sidebar />
{content}
</div>
);
};
private loadingState = (title?: string) => {
return <NonIdealState icon={<Spinner />} title={title || "Loading..."} />;
};
}
const mapStateToProps = (state: IAppState) => ({
graph: state.data.graph,
graphLoadError: state.data.error,
instances: state.data.instances,
isLoadingGraph: state.data.isLoadingGraph,
isLoadingInstances: state.data.isLoadingInstances
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
fetchGraph: () => dispatch(fetchGraph() as any),
fetchInstances: () => dispatch(fetchInstances() as any)
});
export const GraphScreen = connect(
mapStateToProps,
mapDispatchToProps
)(GraphScreenImpl);

View file

@ -1,19 +1,13 @@
html, body {
html,
body {
margin: 0;
padding: 50px 0 0 0;
font-family: sans-serif;
/*background-color: #30404D;*/
background-color: #293742;
height: 100%;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,Icons16,sans-serif;
}
.fediverse-heading-icon {
margin-right: 8px;
}
.fediverse-about-dialog {
top: 100px;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue,
Icons16, sans-serif;
}
.fediverse-instance-search-popover {
@ -33,7 +27,7 @@ html, body {
overflow: scroll;
overflow-x: hidden;
transition-property: all;
transition-duration: .5s;
transition-duration: 0.5s;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
@ -47,7 +41,7 @@ html, body {
right: 400px;
z-index: 20;
transition-property: all;
transition-duration: .5s;
transition-duration: 0.5s;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}

View file

@ -12,7 +12,7 @@ import thunk from "redux-thunk";
import { FocusStyleManager } from "@blueprintjs/core";
import { App } from "./App";
import { AppRouter } from "./AppRouter";
import { rootReducer } from "./redux/reducers";
// https://blueprintjs.com/docs/#core/accessibility.focus-management
@ -25,7 +25,7 @@ const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)))
ReactDOM.render(
<Provider store={store}>
<App />
<AppRouter />
</Provider>,
document.getElementById("root") as HTMLElement
);

View file

@ -1,3 +0,0 @@
{
"extends": "./tsconfig.json"
}

View file

@ -1,6 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

View file

@ -5,5 +5,8 @@
"tslint-react",
"@blueprintjs/tslint-config/blueprint-rules",
"tslint-config-prettier"
],
"exclude": [
"**/*.css"
]
}

View file

@ -9,6 +9,20 @@
dependencies:
"@babel/highlight" "^7.0.0"
"@babel/helper-annotate-as-pure@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==
dependencies:
"@babel/types" "^7.0.0"
"@babel/helper-module-imports@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==
dependencies:
"@babel/types" "^7.0.0"
"@babel/highlight@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
@ -32,6 +46,15 @@
dependencies:
regenerator-runtime "^0.13.2"
"@babel/types@^7.0.0":
version "7.4.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c"
integrity sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==
dependencies:
esutils "^2.0.2"
lodash "^4.17.11"
to-fast-properties "^2.0.0"
"@blueprintjs/core@^3.15.0", "@blueprintjs/core@^3.4.0":
version "3.15.1"
resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-3.15.1.tgz#9792e9fb7e2e066dd5339fadeaf2f85b1485832a"
@ -76,6 +99,23 @@
tslint-plugin-prettier "^2.0.1"
tslint-react "^3.6.0"
"@emotion/is-prop-valid@^0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz#a6bf4fa5387cbba59d44e698a4680f481a8da6cc"
integrity sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==
dependencies:
"@emotion/memoize" "0.7.1"
"@emotion/memoize@0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==
"@emotion/unitless@^0.7.0":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.3.tgz#6310a047f12d21a1036fb031317219892440416f"
integrity sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg==
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@ -105,6 +145,11 @@
dependencies:
"@types/domhandler" "*"
"@types/history@*":
version "4.7.2"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.2.tgz#0e670ea254d559241b6eeb3894f8754991e73220"
integrity sha512-ui3WwXmjTaY73fOQ3/m3nnajU/Orhi6cEu5rzX+BrAAJxa3eITXZ5ch9suPqtM03OWhAHhPSyBGCN4UKoxO20Q==
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
@ -170,6 +215,23 @@
"@types/react" "*"
redux "^4.0.0"
"@types/react-router-dom@^4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.2.tgz#52c17c3682597638f31c17c42620403dc5c2a3f5"
integrity sha512-biesHodFxPgDxku2m08XwPeAfUYBcxAnrQG7pwFikuA3L2e3u2OKAb+Sb16bJuU3L5CTHd+Ivap+ke4mmGsHqQ==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.4.5.tgz#1166997dc7eef2917b5ebce890ebecb32ee5c1b3"
integrity sha512-12+VOu1+xiC8RPc9yrgHCyLI79VswjtuqeS2gPrMcywH6tkc8rGIUhs4LaL3AJPqo5d+RPnfRpNKiJ7MK2Qhcg==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-virtualized@^9.18.7":
version "9.21.1"
resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.1.tgz#c85770f5bb0ccaeb3496d97ff2a2d9028c8ed1fd"
@ -193,6 +255,15 @@
dependencies:
"@types/htmlparser2" "*"
"@types/styled-components@4.1.8":
version "4.1.8"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-4.1.8.tgz#15c8a53bb4b9066e528fafb7558963dee5690ae0"
integrity sha512-NrG0wmB9Rafy5i00GFxUM/uEge148bX2QPr+Q/MI2fXrew6WOp1hN2A3YEG0AeT45z47CMdJ3BEffPsdQCWayA==
dependencies:
"@types/node" "*"
"@types/react" "*"
csstype "^2.2.0"
abab@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
@ -826,6 +897,16 @@ babel-plugin-jest-hoist@^22.4.4:
resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.4.tgz#b9851906eab34c7bf6f8c895a2b08bea1a844c0b"
integrity sha512-DUvGfYaAIlkdnygVIEl0O4Av69NtuQWcrjMOv6DODPuhuGLDnbsARz3AwiiI/EkIMMlxQDUcrZ9yoyJvTNjcVQ==
"babel-plugin-styled-components@>= 1":
version "1.10.0"
resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.0.tgz#ff1f42ad2cc78c21f26b62266b8f564dbc862939"
integrity sha512-sQVKG8irFXx14ZfaK1bBePirfkacl3j8nZwSZK+ZjsbnadRHKQTbhXbe/RB1vT6Vgkz45E+V95LBq4KqdhZUNw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
"@babel/helper-module-imports" "^7.0.0"
babel-plugin-syntax-jsx "^6.18.0"
lodash "^4.17.10"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@ -851,7 +932,7 @@ babel-plugin-syntax-flow@^6.18.0:
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=
babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
babel-plugin-syntax-jsx@^6.18.0, babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
@ -1690,6 +1771,11 @@ camelcase@^4.0.0, camelcase@^4.1.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
camelize@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
caniuse-api@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"
@ -2230,6 +2316,14 @@ create-react-context@<=0.2.2:
fbjs "^0.8.0"
gud "^1.0.0"
create-react-context@^0.2.2:
version "0.2.3"
resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3"
integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==
dependencies:
fbjs "^0.8.0"
gud "^1.0.0"
cross-fetch@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.2.tgz#b7136491967031949c7f86b15903aef4fa3f1768"
@ -2280,6 +2374,11 @@ crypto-random-string@^1.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=
css-color-names@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -2324,6 +2423,15 @@ css-selector-tokenizer@^0.7.0:
fastparse "^1.1.1"
regexpu-core "^1.0.0"
css-to-react-native@^2.2.2:
version "2.3.0"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.3.0.tgz#bf80d24ec4a08e430306ef429c0586e6ed5485f7"
integrity sha512-IhR7bNIrCFwbJbKZOAjNDZdwpsbjTN6f1agXeELHDqg1wHPA8c2QLruttKOW7hgMGetkfraRJCIEMrptifBfVw==
dependencies:
camelize "^1.0.0"
css-color-keywords "^1.0.0"
postcss-value-parser "^3.3.0"
css-what@2.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
@ -3915,6 +4023,18 @@ he@1.2.x:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
history@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/history/-/history-4.9.0.tgz#84587c2068039ead8af769e9d6a6860a14fa1bca"
integrity sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==
dependencies:
"@babel/runtime" "^7.1.2"
loose-envify "^1.2.0"
resolve-pathname "^2.2.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
value-equal "^0.4.0"
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -3924,7 +4044,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.3.0:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b"
integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==
@ -5608,7 +5728,7 @@ longest@^1.0.1:
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -5710,6 +5830,11 @@ mem@^1.1.0:
dependencies:
mimic-fn "^1.0.0"
memoize-one@^5.0.0:
version "5.0.4"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.4.tgz#005928aced5c43d890a4dfab18ca908b0ec92cbc"
integrity sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA==
memory-fs@^0.4.0, memory-fs@~0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@ -6572,7 +6697,7 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-to-regexp@^1.0.1:
path-to-regexp@^1.0.1, path-to-regexp@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=
@ -7092,7 +7217,7 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -7309,7 +7434,7 @@ react-error-overlay@^4.0.1:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89"
integrity sha512-xXUbDAZkU08aAkjtUvldqbvI04ogv+a1XdHxvYuHPYKIVk/42BIOD0zSKTHAWV4+gDy3yGm283z2072rA2gdtw==
react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
@ -7343,6 +7468,35 @@ react-redux@^7.0.2:
prop-types "^15.7.2"
react-is "^16.8.6"
react-router-dom@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.0.tgz#542a9b86af269a37f0b87218c4c25ea8dcf0c073"
integrity sha512-wSpja5g9kh5dIteZT3tUoggjnsa+TPFHSMrpHXMpFsaHhQkm/JNVGh2jiF9Dkh4+duj4MKCkwO6H08u6inZYgQ==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.2"
react-router "5.0.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.0.tgz#349863f769ffc2fa10ee7331a4296e86bc12879d"
integrity sha512-6EQDakGdLG/it2x9EaCt9ZpEEPxnd0OCLBHQ1AcITAAx7nCnyvnzf76jKWG1s2/oJ7SSviUgfWHofdYljFexsA==
dependencies:
"@babel/runtime" "^7.1.2"
create-react-context "^0.2.2"
history "^4.9.0"
hoist-non-react-statics "^3.1.0"
loose-envify "^1.3.1"
path-to-regexp "^1.7.0"
prop-types "^15.6.2"
react-is "^16.6.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-scripts-ts@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-3.1.0.tgz#3f285c54b242ff6ecbfb91785060db10660ee7b0"
@ -7757,6 +7911,11 @@ resolve-from@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
resolve-pathname@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879"
integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@ -8483,6 +8642,33 @@ style-loader@0.19.0:
loader-utils "^1.0.2"
schema-utils "^0.3.0"
styled-components@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.2.0.tgz#811fbbec4d64c7189f6c7482b9eb6fefa7fefef7"
integrity sha512-L/LzkL3ZbBhqIVHdR7DbYujy4tqvTNRfc+4JWDCYyhTatI+8CRRQUmdaR0+ARl03DWsfKLhjewll5uNLrqrl4A==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@emotion/is-prop-valid" "^0.7.3"
"@emotion/unitless" "^0.7.0"
babel-plugin-styled-components ">= 1"
css-to-react-native "^2.2.2"
memoize-one "^5.0.0"
prop-types "^15.5.4"
react-is "^16.6.0"
stylis "^3.5.0"
stylis-rule-sheet "^0.0.10"
supports-color "^5.5.0"
stylis-rule-sheet@^0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430"
integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==
stylis@^3.5.0:
version "3.5.4"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe"
integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==
subarg@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
@ -8509,7 +8695,7 @@ supports-color@^4.2.1:
dependencies:
has-flag "^2.0.0"
supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0:
supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@ -8665,6 +8851,16 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tiny-invariant@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463"
integrity sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==
tiny-warning@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28"
integrity sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -8687,6 +8883,11 @@ to-fast-properties@^1.0.3:
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
to-object-path@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
@ -9178,6 +9379,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
value-equal@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"