= ({ selectedFilters, selectF
return (
- {selectedFilters.map(filter => (
-
+ {selectedFilters.map((filter) => (
+
{filter.displayValue}
))}
-
- {"Add filter"}
+
+ Add filter
diff --git a/frontend/src/components/organisms/SidebarContainer.tsx b/frontend/src/components/organisms/SidebarContainer.tsx
index 6af77a4..c9f882c 100644
--- a/frontend/src/components/organisms/SidebarContainer.tsx
+++ b/frontend/src/components/organisms/SidebarContainer.tsx
@@ -17,11 +17,9 @@ const StyledCard = styled(Card)`
display: flex;
flex-direction: column;
`;
-const SidebarContainer: React.FC = ({ children }) => {
- return (
-
- {children}
-
- );
-};
+const SidebarContainer: React.FC = ({ children }) => (
+
+ {children}
+
+);
export default SidebarContainer;
diff --git a/frontend/src/components/screens/AboutScreen.tsx b/frontend/src/components/screens/AboutScreen.tsx
index fad79c0..88b5c8e 100644
--- a/frontend/src/components/screens/AboutScreen.tsx
+++ b/frontend/src/components/screens/AboutScreen.tsx
@@ -1,10 +1,10 @@
-import { Classes, Code, H1, H2, H4 } from "@blueprintjs/core";
+import { Classes, Code, H1, H2, H3 } from "@blueprintjs/core";
import * as React from "react";
import styled from "styled-components";
// import appsignalLogo from "../../assets/appsignal.svg";
-import gitlabLogo from "../../assets/gitlab.png";
-import nlnetLogo from "../../assets/nlnet.png";
-import { Page } from "../atoms/";
+import * as gitlabLogo from "../../assets/gitlab.png";
+import * as nlnetLogo from "../../assets/nlnet.png";
+import { Page } from "../atoms";
const SponsorContainer = styled.div`
margin-bottom: 20px;
@@ -36,15 +36,16 @@ const AboutScreen: React.FC = () => (
FAQ
- Why can't I see details about my instance?
+ Why can't I see details about my instance?
fediverse.space only supports servers using the Mastodon API, the Misskey API, the GNU Social API, or Nodeinfo.
- Instances with 10 or fewer users won't be scraped -- it's a tool for understanding communities, not individuals.
+ Instances with 10 or fewer users won't be crawled -- it's a tool for understanding communities, not
+ individuals.
-
+
When is $OTHER_FEDIVERSE_SERVER
going to be added?
-
+
Check out{" "}
@@ -53,10 +54,10 @@ const AboutScreen: React.FC = () => (
.
- How do I add my personal instance?
+ How do I add my personal instance?
Click on the Administration link in the top right to opt-in.
- How do you calculate the strength of relationships between instances?
+ How do you calculate the strength of relationships between instances?
fediverse.space looks at public statuses from within the last month on the public timeline of each instance. It
calculates at the ratio of
diff --git a/frontend/src/components/screens/AdminScreen.tsx b/frontend/src/components/screens/AdminScreen.tsx
index feb122a..52e0821 100644
--- a/frontend/src/components/screens/AdminScreen.tsx
+++ b/frontend/src/components/screens/AdminScreen.tsx
@@ -17,7 +17,7 @@ const ButtonContainer = styled.div`
justify-content: space-between;
`;
-interface IAdminSettings {
+interface AdminSettings {
domain: string;
optIn: boolean;
optOut: boolean;
@@ -25,26 +25,26 @@ interface IAdminSettings {
statusCount: number;
}
-interface IAdminScreenProps {
+interface AdminScreenProps {
navigate: (path: string) => void;
}
-interface IAdminScreenState {
- settings?: IAdminSettings;
+interface AdminScreenState {
+ settings?: AdminSettings;
isUpdating: boolean;
}
-class AdminScreen extends React.PureComponent {
+class AdminScreen extends React.PureComponent {
private authToken = getAuthToken();
- public constructor(props: IAdminScreenProps) {
+ public constructor(props: AdminScreenProps) {
super(props);
this.state = { isUpdating: false };
}
public componentDidMount() {
// Load instance settings from server
- if (!!this.authToken) {
- getFromApi(`admin`, this.authToken!)
- .then(response => {
+ if (this.authToken) {
+ getFromApi(`admin`, this.authToken)
+ .then((response) => {
this.setState({ settings: response });
})
.catch(() => {
@@ -52,7 +52,7 @@ class AdminScreen extends React.PureComponent) => {
- const settings = this.state.settings as IAdminSettings;
+ const settings = this.state.settings as AdminSettings;
const optIn = e.currentTarget.checked;
- let optOut = settings.optOut;
+ let { optOut } = settings;
if (optIn) {
optOut = false;
}
@@ -126,9 +126,9 @@ class AdminScreen extends React.PureComponent) => {
- const settings = this.state.settings as IAdminSettings;
+ const settings = this.state.settings as AdminSettings;
const optOut = e.currentTarget.checked;
- let optIn = settings.optIn;
+ let { optIn } = settings;
if (optOut) {
optIn = false;
}
@@ -140,15 +140,15 @@ class AdminScreen extends React.PureComponent {
+ .then((response) => {
this.setState({ settings: response, isUpdating: false });
AppToaster.show({
icon: IconNames.TICK,
intent: Intent.SUCCESS,
- message: "Successfully updated settings."
+ message: "Successfully updated settings.",
});
})
.catch(() => {
@@ -161,16 +161,13 @@ class AdminScreen extends React.PureComponent ({
- navigate: (path: string) => dispatch(push(path))
+ navigate: (path: string) => dispatch(push(path)),
});
-export default connect(
- undefined,
- mapDispatchToProps
-)(AdminScreen);
+export default connect(undefined, mapDispatchToProps)(AdminScreen);
diff --git a/frontend/src/components/screens/GraphScreen.tsx b/frontend/src/components/screens/GraphScreen.tsx
index 85ea9c4..2fb5fd0 100644
--- a/frontend/src/components/screens/GraphScreen.tsx
+++ b/frontend/src/components/screens/GraphScreen.tsx
@@ -7,9 +7,9 @@ import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
import { InstanceScreen, SearchScreen } from ".";
import { INSTANCE_DOMAIN_PATH } from "../../constants";
import { loadInstance } from "../../redux/actions";
-import { IAppState } from "../../redux/types";
+import { AppState } from "../../redux/types";
import { domainMatchSelector, isSmallScreen } from "../../util";
-import { Graph, SidebarContainer } from "../organisms/";
+import { Graph, SidebarContainer } from "../organisms";
const GraphContainer = styled.div`
display: flex;
@@ -24,13 +24,13 @@ const FullDiv = styled.div`
right: 0;
`;
-interface IGraphScreenProps extends RouteComponentProps {
+interface GraphScreenProps extends RouteComponentProps {
currentInstanceName: string | null;
pathname: string;
graphLoadError: boolean;
loadInstance: (domain: string | null) => void;
}
-interface IGraphScreenState {
+interface GraphScreenState {
hasBeenViewed: boolean;
}
/**
@@ -41,8 +41,8 @@ interface IGraphScreenState {
* However, if it's not the first page viewed (e.g. if someone opens directly on /about) we don't want to render the
* graph since it slows down everything else!
*/
-class GraphScreenImpl extends React.Component {
- public constructor(props: IGraphScreenProps) {
+class GraphScreenImpl extends React.Component {
+ public constructor(props: GraphScreenProps) {
super(props);
this.state = { hasBeenViewed: false };
}
@@ -56,7 +56,7 @@ class GraphScreenImpl extends React.Component (
+ private renderRoutes = () => (
{/* Smaller screens never load the entire graph. Instead, `InstanceScreen` shows only the neighborhood. */}
@@ -80,7 +80,7 @@ class GraphScreenImpl extends React.Component
-
+
@@ -94,19 +94,16 @@ class GraphScreenImpl extends React.Component {
+const mapStateToProps = (state: AppState) => {
const match = domainMatchSelector(state);
return {
currentInstanceName: match && match.params.domain,
graphLoadError: state.data.graphLoadError,
- pathname: state.router.location.pathname
+ pathname: state.router.location.pathname,
};
};
const mapDispatchToProps = (dispatch: Dispatch) => ({
- loadInstance: (domain: string | null) => dispatch(loadInstance(domain) as any)
+ loadInstance: (domain: string | null) => dispatch(loadInstance(domain) as any),
});
-const GraphScreen = connect(
- mapStateToProps,
- mapDispatchToProps
-)(GraphScreenImpl);
+const GraphScreen = connect(mapStateToProps, mapDispatchToProps)(GraphScreenImpl);
export default withRouter(GraphScreen);
diff --git a/frontend/src/components/screens/InstanceScreen.tsx b/frontend/src/components/screens/InstanceScreen.tsx
index b493ecb..4c2b2a2 100644
--- a/frontend/src/components/screens/InstanceScreen.tsx
+++ b/frontend/src/components/screens/InstanceScreen.tsx
@@ -20,7 +20,7 @@ import {
Spinner,
Tab,
Tabs,
- Tooltip
+ Tooltip,
} from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
@@ -28,10 +28,10 @@ import { push } from "connected-react-router";
import { Link } from "react-router-dom";
import { Dispatch } from "redux";
import styled from "styled-components";
-import { IAppState, IGraph, IGraphResponse, IInstanceDetails } from "../../redux/types";
+import { AppState, Graph, GraphResponse, InstanceDetails, Peer } from "../../redux/types";
import { domainMatchSelector, getFromApi, isSmallScreen } from "../../util";
import { InstanceType } from "../atoms";
-import { Cytoscape, ErrorState } from "../molecules/";
+import { Cytoscape, ErrorState } from "../molecules";
import { FederationTab } from "../organisms";
const InstanceScreenContainer = styled.div`
@@ -82,25 +82,25 @@ const StyledGraphContainer = styled.div`
flex-direction: column;
margin-bottom: 10px;
`;
-interface IInstanceScreenProps {
- graph?: IGraph;
+interface InstanceScreenProps {
+ graph?: Graph;
instanceName: string | null;
instanceLoadError: boolean;
- instanceDetails: IInstanceDetails | null;
+ instanceDetails: InstanceDetails | null;
isLoadingInstanceDetails: boolean;
navigateToRoot: () => void;
navigateToInstance: (domain: string) => void;
}
-interface IInstanceScreenState {
+interface InstanceScreenState {
neighbors?: string[];
isProcessingNeighbors: boolean;
// Local (neighborhood) graph. Used only on small screens (mobile devices).
isLoadingLocalGraph: boolean;
- localGraph?: IGraph;
+ localGraph?: Graph;
localGraphLoadError?: boolean;
}
-class InstanceScreenImpl extends React.PureComponent {
- public constructor(props: IInstanceScreenProps) {
+class InstanceScreenImpl extends React.PureComponent {
+ public constructor(props: InstanceScreenProps) {
super(props);
this.state = { isProcessingNeighbors: false, isLoadingLocalGraph: false, localGraphLoadError: false };
}
@@ -116,9 +116,9 @@ class InstanceScreenImpl extends React.PureComponent ;
- } else if (this.props.instanceDetails.status.toLowerCase().indexOf("personal instance") > -1) {
+ } else if (this.props.instanceDetails.status.toLowerCase().includes("personal instance")) {
content = this.renderPersonalInstanceErrorState();
- } else if (this.props.instanceDetails.status.toLowerCase().indexOf("robots.txt") > -1) {
+ } else if (this.props.instanceDetails.status.toLowerCase().includes("robots.txt")) {
content = this.renderRobotsTxtState();
} else if (this.props.instanceDetails.status !== "success") {
content = this.renderMissingDataState();
@@ -130,7 +130,7 @@ class InstanceScreenImpl extends React.PureComponent
{this.props.instanceName}
-
+
@@ -145,7 +145,7 @@ class InstanceScreenImpl extends React.PureComponent [e.data.source, e.data.target].indexOf(instanceName!) > -1);
+ const graphToUse = graph || localGraph;
+ if (!graphToUse) {
+ return;
+ }
+ const edges = graphToUse.edges.filter((e) => [e.data.source, e.data.target].includes(instanceName));
const neighbors: any[] = [];
- edges.forEach(e => {
+ edges.forEach((e) => {
if (e.data.source === instanceName) {
neighbors.push({ neighbor: e.data.target, weight: e.data.weight });
} else {
@@ -183,39 +186,38 @@ class InstanceScreenImpl extends React.PureComponent {
+ .then((response: GraphResponse) => {
// We do some processing of edges here to make sure that every edge's source and target are in the neighborhood
// We could (and should) be doing this in the backend, but I don't want to mess around with complex SQL
// queries.
// TODO: think more about moving the backend to a graph database that would make this easier.
- const graph = response.graph;
- const nodeIds = new Set(graph.nodes.map(n => n.data.id));
- const edges = graph.edges.filter(e => nodeIds.has(e.data.source) && nodeIds.has(e.data.target));
+ const { graph } = response;
+ const nodeIds = new Set(graph.nodes.map((n) => n.data.id));
+ const edges = graph.edges.filter((e) => nodeIds.has(e.data.source) && nodeIds.has(e.data.target));
this.setState({ isLoadingLocalGraph: false, localGraph: { ...graph, edges } });
})
.catch(() => this.setState({ isLoadingLocalGraph: false, localGraphLoadError: true }));
};
private renderTabs = () => {
+ const { instanceDetails } = this.props;
const hasNeighbors = this.state.neighbors && this.state.neighbors.length > 0;
- const federationRestrictions = this.props.instanceDetails && this.props.instanceDetails.federationRestrictions;
+ const federationRestrictions = instanceDetails && instanceDetails.federationRestrictions;
const hasLocalGraph =
!!this.state.localGraph && this.state.localGraph.nodes.length > 0 && this.state.localGraph.edges.length > 0;
const insularCallout =
this.props.graph && !this.state.isProcessingNeighbors && !hasNeighbors && !hasLocalGraph ? (
- This instance doesn't have any neighbors that we know of, so it's hidden from the graph.
+ This instance doesn't have any neighbors that we know of, so it's hidden from the graph.
- ) : (
- undefined
- );
+ ) : undefined;
return (
<>
{insularCallout}
{this.maybeRenderLocalGraph()}
- {this.props.instanceDetails!.description && (
+ {instanceDetails && instanceDetails.description && (
)}
{this.shouldRenderStats() && }
@@ -232,7 +234,7 @@ class InstanceScreenImpl extends React.PureComponent
{
const { localGraph } = this.state;
- const hasLocalGraph =
- !!this.state.localGraph && this.state.localGraph.nodes.length > 0 && this.state.localGraph.edges.length > 0;
- if (!hasLocalGraph) {
+ const hasLocalGraph = !!localGraph && localGraph.nodes.length > 0 && localGraph.edges.length > 0;
+ if (!hasLocalGraph || !localGraph) {
return;
}
return (
-
+
@@ -268,7 +269,11 @@ class InstanceScreenImpl extends React.PureComponent {
- const description = this.props.instanceDetails!.description;
+ const { instanceDetails } = this.props;
+ if (!instanceDetails) {
+ return;
+ }
+ const { description } = instanceDetails;
if (!description) {
return;
}
@@ -288,10 +293,10 @@ class InstanceScreenImpl extends React.PureComponent
+
Version
@@ -299,7 +304,7 @@ class InstanceScreenImpl extends React.PureComponent
Instance type
- {(type && ) || "Unknown"}
+ {(type && ) || "Unknown"}
Users
@@ -311,7 +316,8 @@ class InstanceScreenImpl extends React.PureComponent
- Insularity{" "}
+ Insularity
+ {" "}
@@ -330,7 +336,8 @@ class InstanceScreenImpl extends React.PureComponent
- Statuses / day{" "}
+ Statuses / day
+ {" "}
@@ -349,7 +356,8 @@ class InstanceScreenImpl extends React.PureComponent
- Statuses / person / day{" "}
+ Statuses / person / day
+ {" "}
@@ -372,7 +380,7 @@ class InstanceScreenImpl extends React.PureComponent
Last updated
- {moment(lastUpdated + "Z").fromNow() || "Unknown"}
+ {moment(`${lastUpdated}Z`).fromNow() || "Unknown"}
@@ -412,7 +420,7 @@ class InstanceScreenImpl extends React.PureComponent
-
+
Instance
@@ -426,11 +434,15 @@ class InstanceScreenImpl extends React.PureComponent {
- const peers = this.props.instanceDetails!.peers;
+ const { instanceDetails } = this.props;
+ if (!instanceDetails) {
+ return;
+ }
+ const { peers } = instanceDetails;
if (!peers || peers.length === 0) {
return;
}
- const peerRows = peers.map(instance => (
+ const peerRows = peers.map((instance: Peer) => (
@@ -444,7 +456,7 @@ class InstanceScreenImpl extends React.PureComponent
All the instances, past and present, that {this.props.instanceName} knows about.
-
+
{peerRows}
@@ -453,71 +465,62 @@ class InstanceScreenImpl extends React.PureComponent } />;
- private renderPersonalInstanceErrorState = () => {
- return (
-
- {"Opt in"}
-
- }
- />
- );
- };
+ private renderPersonalInstanceErrorState = () => (
+
+ Opt in
+
+ }
+ />
+ );
- private renderMissingDataState = () => {
- return (
- <>
-
-
- {this.props.instanceDetails && this.props.instanceDetails.status}
+ private renderMissingDataState = () => (
+ <>
+
+
+ {this.props.instanceDetails && this.props.instanceDetails.status}
+
+ >
+ );
+
+ private renderRobotsTxtState = () => (
+
+ 🤖
- >
- );
- };
-
- private renderRobotsTxtState = () => {
- return (
-
- 🤖
-
- }
- title="No data"
- description="This instance was not crawled because its robots.txt did not allow us to."
- />
- );
- };
+ }
+ title="No data"
+ description="This instance was not crawled because its robots.txt did not allow us to."
+ />
+ );
private openInstanceLink = () => {
- window.open("https://" + this.props.instanceName, "_blank");
+ window.open(`https://${this.props.instanceName}`, "_blank");
};
}
-const mapStateToProps = (state: IAppState) => {
+const mapStateToProps = (state: AppState) => {
const match = domainMatchSelector(state);
return {
graph: state.data.graphResponse && state.data.graphResponse.graph,
instanceDetails: state.currentInstance.currentInstanceDetails,
instanceLoadError: state.currentInstance.error,
instanceName: match && match.params.domain,
- isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails
+ isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails,
};
};
const mapDispatchToProps = (dispatch: Dispatch) => ({
navigateToInstance: (domain: string) => dispatch(push(`/instance/${domain}`)),
- navigateToRoot: () => dispatch(push("/"))
+ navigateToRoot: () => dispatch(push("/")),
});
-const InstanceScreen = connect(
- mapStateToProps,
- mapDispatchToProps
-)(InstanceScreenImpl);
+const InstanceScreen = connect(mapStateToProps, mapDispatchToProps)(InstanceScreenImpl);
export default InstanceScreen;
diff --git a/frontend/src/components/screens/LoginScreen.tsx b/frontend/src/components/screens/LoginScreen.tsx
index 57f0569..0548185 100644
--- a/frontend/src/components/screens/LoginScreen.tsx
+++ b/frontend/src/components/screens/LoginScreen.tsx
@@ -1,4 +1,4 @@
-import { Button, Classes, FormGroup, H1, H4, Icon, InputGroup, Intent } from "@blueprintjs/core";
+import { Button, Classes, FormGroup, H1, H2, Icon, InputGroup, Intent } from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
import React from "react";
import { Redirect } from "react-router";
@@ -8,11 +8,11 @@ import { getAuthToken, getFromApi, postToApi } from "../../util";
import { Page } from "../atoms";
import { ErrorState } from "../molecules";
-interface IFormContainerProps {
+interface FormContainerProps {
error: boolean;
}
-const FormContainer = styled.div`
- ${props => (props.error ? "margin: 20px auto 0 auto;" : "margin-top: 20px;")}
+const FormContainer = styled.div`
+ ${(props) => (props.error ? "margin: 20px auto 0 auto;" : "margin-top: 20px;")}
`;
const LoginTypeContainer = styled.div`
display: flex;
@@ -31,23 +31,28 @@ const StyledIcon = styled(Icon)`
margin-bottom: 10px;
`;
-interface ILoginTypes {
+interface LoginTypes {
domain: string;
email?: string;
fediverseAccount?: string;
}
-interface ILoginScreenState {
+interface LoginScreenState {
domain: string;
isGettingLoginTypes: boolean;
isSendingLoginRequest: boolean;
- loginTypes?: ILoginTypes;
+ loginTypes?: LoginTypes;
selectedLoginType?: "email" | "fediverseAccount";
error: boolean;
}
-class LoginScreen extends React.PureComponent<{}, ILoginScreenState> {
+class LoginScreen extends React.PureComponent<{}, LoginScreenState> {
public constructor(props: any) {
super(props);
- this.state = { domain: "", error: false, isGettingLoginTypes: false, isSendingLoginRequest: false };
+ this.state = {
+ domain: "",
+ error: false,
+ isGettingLoginTypes: false,
+ isSendingLoginRequest: false,
+ };
}
public render() {
@@ -59,13 +64,13 @@ class LoginScreen extends React.PureComponent<{}, ILoginScreenState> {
const { error, loginTypes, isSendingLoginRequest, selectedLoginType } = this.state;
let content;
- if (!!error) {
+ if (error) {
content = (
);
} else if (!!selectedLoginType && !isSendingLoginRequest) {
content = this.renderPostLogin();
- } else if (!!loginTypes) {
+ } else if (loginTypes) {
content = this.renderChooseLoginType();
} else {
content = this.renderChooseInstance();
@@ -78,7 +83,7 @@ class LoginScreen extends React.PureComponent<{}, ILoginScreenState> {
You must be the instance admin to manage how fediverse.space interacts with your instance.
- It's currently only possible to administrate Mastodon and Pleroma instances. If you want to login with a
+ It's currently only possible to administrate Mastodon and Pleroma instances. If you want to login with a
direct message, your instance must federate with mastodon.social and vice versa.
@@ -95,7 +100,7 @@ class LoginScreen extends React.PureComponent<{}, ILoginScreenState> {
const onButtonClick = () => this.getLoginTypes();
return (