diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb51eb2..b3d2b2c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- It's now shown in the front-end if an instance wasn't crawled because of its robots.txt.
+- You can now link directly to instances at e.g. /instance/mastodon.social.
+- Instance details now have a link to the corresponding fediverse.network page.
### Changed
### Deprecated
### Removed
diff --git a/backend/config/dev.exs b/backend/config/dev.exs
index 1f3c48b..c58a4ca 100644
--- a/backend/config/dev.exs
+++ b/backend/config/dev.exs
@@ -68,6 +68,6 @@ config :backend, :crawler,
config :backend, Backend.Scheduler,
jobs: [
- # Every 15 minutes
- {"*/15 * * * *", {Backend.Scheduler, :prune_crawls, [12, "hour"]}}
+ # Every 5 minutes
+ {"*/5 * * * *", {Backend.Scheduler, :prune_crawls, [12, "month"]}}
]
diff --git a/frontend/README.md b/frontend/README.md
index 0652bb2..64c27a2 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -3,6 +3,7 @@
The React frontend for [fediverse.space](https://fediverse.space). Written in Typescript.
- Set the environment variable `REACT_APP_STAGING=true` when building to use the staging backend.
+- React components are organized into atoms, molecules, organisms, and screens according to [Atomic Design](http://bradfrost.com/blog/post/atomic-web-design/).
# Default README
diff --git a/frontend/package.json b/frontend/package.json
index a08508e..3b2b09b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -32,6 +32,7 @@
"@blueprintjs/icons": "^3.9.1",
"@blueprintjs/select": "^3.9.0",
"classnames": "^2.2.6",
+ "connected-react-router": "^6.5.2",
"cross-fetch": "^3.0.4",
"cytoscape": "^3.8.1",
"cytoscape-popper": "^1.0.4",
diff --git a/frontend/src/AppRouter.tsx b/frontend/src/AppRouter.tsx
index e78cd2b..a709401 100644
--- a/frontend/src/AppRouter.tsx
+++ b/frontend/src/AppRouter.tsx
@@ -3,11 +3,12 @@ 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";
+import { ConnectedRouter } from "connected-react-router";
+import { Route, RouteComponentProps } from "react-router-dom";
+import { Nav } from "./components/organisms/";
+import { AboutScreen, GraphScreen } from "./components/screens/";
+import { DESKTOP_WIDTH_THRESHOLD, IInstanceDomainPath, INSTANCE_DOMAIN_PATH } from "./constants";
+import { history } from "./index";
interface IAppLocalState {
mobileDialogOpen: boolean;
@@ -20,14 +21,19 @@ export class AppRouter extends React.Component<{}, IAppLocalState> {
public render() {
return (
-
+
-
+ {/* We use `children={}` instead of `component={}` such that the graph is never unmounted */}
+
+
+ {(routeProps: RouteComponentProps) => }
+
+
{this.renderMobileDialog()}
-
+
);
}
diff --git a/frontend/src/components/ErrorState.tsx b/frontend/src/components/ErrorState.tsx
deleted file mode 100644
index a16db4f..0000000
--- a/frontend/src/components/ErrorState.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { NonIdealState } from "@blueprintjs/core";
-import { IconNames } from "@blueprintjs/icons";
-import * as React from "react";
-
-export const ErrorState: React.SFC = () => ;
diff --git a/frontend/src/components/Graph.tsx b/frontend/src/components/Graph.tsx
deleted file mode 100644
index f213a5f..0000000
--- a/frontend/src/components/Graph.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { get } from "lodash";
-import * as React from "react";
-import { connect } from "react-redux";
-
-import { Dispatch } from "redux";
-import { selectAndLoadInstance } from "../redux/actions";
-import { IAppState, IGraph } from "../redux/types";
-import Cytoscape from "./Cytoscape";
-import { ErrorState } from "./ErrorState";
-import { FloatingResetButton } from "./FloatingResetButton";
-
-interface IGraphProps {
- graph?: IGraph;
- currentInstanceName: string | null;
- selectAndLoadInstance: (name: string) => void;
-}
-class GraphImpl extends React.Component {
- private cytoscapeComponent: React.RefObject;
-
- public constructor(props: IGraphProps) {
- super(props);
- this.cytoscapeComponent = React.createRef();
- }
-
- public render() {
- if (!this.props.graph) {
- return ;
- }
-
- return (
-
-
-
-
- );
- }
-
- public componentDidUpdate(prevProps: IGraphProps) {
- const { currentInstanceName } = this.props;
- if (prevProps.currentInstanceName !== currentInstanceName) {
- const cy = this.getCytoscape();
- cy.$id(prevProps.currentInstanceName).unselect();
- if (currentInstanceName) {
- // Select instance
- cy.$id(`${currentInstanceName}`).select();
- // Center it
- const selected = cy.$id(currentInstanceName);
- cy.center(selected);
- }
- }
- }
-
- private resetGraphPosition = () => {
- const cy = this.getCytoscape();
- const { currentInstanceName } = this.props;
- if (currentInstanceName) {
- cy.zoom({
- level: 0.2,
- position: cy.$id(currentInstanceName).position()
- });
- } else {
- cy.zoom({
- level: 0.2,
- position: { x: 0, y: 0 }
- });
- }
- };
-
- private onInstanceSelect = (domain: string) => {
- this.props.selectAndLoadInstance(domain);
- };
-
- private onInstanceDeselect = () => {
- this.props.selectAndLoadInstance("");
- };
-
- private getCytoscape = () => {
- const cy = get(this.cytoscapeComponent, "current.cy");
- if (!cy) {
- throw new Error("Expected cytoscape component but did not find one.");
- }
- return cy;
- };
-}
-const mapStateToProps = (state: IAppState) => ({
- currentInstanceName: state.currentInstance.currentInstanceName,
- graph: state.data.graph
-});
-const mapDispatchToProps = (dispatch: Dispatch) => ({
- selectAndLoadInstance: (instanceName: string) => dispatch(selectAndLoadInstance(instanceName) as any)
-});
-const Graph = connect(
- mapStateToProps,
- mapDispatchToProps
-)(GraphImpl);
-export default Graph;
diff --git a/frontend/src/components/Nav.tsx b/frontend/src/components/Nav.tsx
deleted file mode 100644
index 509b028..0000000
--- a/frontend/src/components/Nav.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import * as React from "react";
-
-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 {
- aboutIsOpen: boolean;
-}
-export class Nav extends React.Component<{}, INavState> {
- constructor(props: any) {
- super(props);
- this.state = { aboutIsOpen: false };
- }
-
- public render() {
- const StyledLink = styled(Link)`
- color: white !important;
- `;
- return (
-
-
- fediverse.space
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
diff --git a/frontend/src/components/Page.tsx b/frontend/src/components/Page.tsx
deleted file mode 100644
index bc7096a..0000000
--- a/frontend/src/components/Page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import styled from "styled-components";
-
-export const Page = styled.div`
- max-width: 800px;
- margin: auto;
- padding: 2em;
-`;
diff --git a/frontend/src/components/FloatingCard.tsx b/frontend/src/components/atoms/FloatingCard.tsx
similarity index 100%
rename from frontend/src/components/FloatingCard.tsx
rename to frontend/src/components/atoms/FloatingCard.tsx
diff --git a/frontend/src/components/atoms/Page.tsx b/frontend/src/components/atoms/Page.tsx
new file mode 100644
index 0000000..59c7cbb
--- /dev/null
+++ b/frontend/src/components/atoms/Page.tsx
@@ -0,0 +1,26 @@
+import * as React from "react";
+import styled from "styled-components";
+
+const Backdrop = styled.div`
+ position: absolute;
+ top: 50px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: #293742;
+ z-index: 100;
+`;
+
+const Container = styled.div`
+ max-width: 800px;
+ margin: auto;
+ padding: 2em;
+`;
+
+const Page: React.FC = ({ children }) => (
+
+ {children}
+
+);
+
+export default Page;
diff --git a/frontend/src/components/atoms/index.ts b/frontend/src/components/atoms/index.ts
new file mode 100644
index 0000000..8f59e88
--- /dev/null
+++ b/frontend/src/components/atoms/index.ts
@@ -0,0 +1,2 @@
+export { default as Page } from "./Page";
+export { default as FloatingCard } from "./FloatingCard";
diff --git a/frontend/src/components/Cytoscape.tsx b/frontend/src/components/molecules/Cytoscape.tsx
similarity index 62%
rename from frontend/src/components/Cytoscape.tsx
rename to frontend/src/components/molecules/Cytoscape.tsx
index f787838..6f27aa7 100644
--- a/frontend/src/components/Cytoscape.tsx
+++ b/frontend/src/components/molecules/Cytoscape.tsx
@@ -1,30 +1,32 @@
import cytoscape from "cytoscape";
-import popper from "cytoscape-popper";
import * as React from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import tippy, { Instance } from "tippy.js";
-import { DEFAULT_NODE_COLOR, SELECTED_NODE_COLOR } from "../constants";
+import { DEFAULT_NODE_COLOR, SELECTED_NODE_COLOR } from "../../constants";
-const EntireWindowDiv = styled.div`
- position: absolute;
- top: 50px;
- bottom: 0;
- right: 0;
- left: 0;
+const CytoscapeContainer = styled.div`
+ width: 100%;
+ height: 100%;
`;
interface ICytoscapeProps {
+ currentNodeId: string | null;
elements: cytoscape.ElementsDefinition;
- onInstanceSelect: (domain: string) => void;
- onInstanceDeselect: () => void;
+ navigateToInstancePath: (domain: string) => void;
+ navigateToRoot: () => void;
}
class Cytoscape extends React.Component {
- public cy?: cytoscape.Core;
+ private cy?: cytoscape.Core;
+
+ public shouldComponentUpdate(prevProps: ICytoscapeProps) {
+ // We only want to update this component if the current instance selection changes.
+ // We know that the `elements` prop will never change so we skip the expensive computations here.
+ return prevProps.currentNodeId !== this.props.currentNodeId;
+ }
public componentDidMount() {
const container = ReactDOM.findDOMNode(this);
- cytoscape.use(popper as any);
this.cy = cytoscape({
autoungrabify: true,
container: container as any,
@@ -90,11 +92,11 @@ class Cytoscape extends React.Component {
"font-size": 50,
"min-zoomed-font-size": 16
})
- .selector(".hidden")
+ .selector(".hidden") // used to hide nodes not in the neighborhood of the selected
.style({
display: "none"
})
- .selector(".thickEdge")
+ .selector(".thickEdge") // when a node is selected, make edges thicker so you can actually see them
.style({
width: 2
})
@@ -102,8 +104,8 @@ class Cytoscape extends React.Component {
this.cy.nodes().on("select", e => {
const instanceId = e.target.data("id");
- if (instanceId) {
- this.props.onInstanceSelect(instanceId);
+ if (instanceId && instanceId !== this.props.currentNodeId) {
+ this.props.navigateToInstancePath(instanceId);
}
const neighborhood = this.cy!.$id(instanceId).closedNeighborhood();
@@ -119,7 +121,6 @@ class Cytoscape extends React.Component {
});
});
this.cy.nodes().on("unselect", e => {
- this.props.onInstanceDeselect();
this.cy!.batch(() => {
this.cy!.nodes().removeClass("hidden");
this.cy!.edges().removeClass("thickEdge");
@@ -128,14 +129,17 @@ class Cytoscape extends React.Component {
this.cy.on("click", e => {
// Clicking on the background should also deselect
const target = e.target;
- if (!target) {
- this.props.onInstanceDeselect();
+ if (!target || target === this.cy || target.isEdge()) {
+ // Go to the URL "/"
+ this.props.navigateToRoot();
}
- this.cy!.batch(() => {
- this.cy!.nodes().removeClass("hidden");
- this.cy!.edges().removeClass("thickEdge");
- });
});
+
+ this.setNodeSelection();
+ }
+
+ public componentDidUpdate(prevProps: ICytoscapeProps) {
+ this.setNodeSelection(prevProps.currentNodeId);
}
public componentWillUnmount() {
@@ -145,8 +149,47 @@ class Cytoscape extends React.Component {
}
public render() {
- return ;
+ return ;
}
+
+ public resetGraphPosition() {
+ if (!this.cy) {
+ return;
+ }
+ const { currentNodeId } = this.props;
+ if (currentNodeId) {
+ this.cy.zoom({
+ level: 0.2,
+ position: this.cy.$id(currentNodeId).position()
+ });
+ } else {
+ this.cy.zoom({
+ level: 0.2,
+ position: { x: 0, y: 0 }
+ });
+ }
+ }
+
+ /**
+ * Updates cytoscape's internal state to match our props.
+ */
+ private setNodeSelection = (prevNodeId?: string | null) => {
+ if (!this.cy) {
+ return;
+ }
+ if (prevNodeId) {
+ this.cy.$id(prevNodeId).unselect();
+ }
+
+ const { currentNodeId } = this.props;
+ if (currentNodeId) {
+ // Select instance
+ this.cy.$id(currentNodeId).select();
+ // Center it
+ const selected = this.cy.$id(currentNodeId);
+ this.cy.center(selected);
+ }
+ };
}
export default Cytoscape;
diff --git a/frontend/src/components/molecules/ErrorState.tsx b/frontend/src/components/molecules/ErrorState.tsx
new file mode 100644
index 0000000..beed0b5
--- /dev/null
+++ b/frontend/src/components/molecules/ErrorState.tsx
@@ -0,0 +1,7 @@
+import { NonIdealState } from "@blueprintjs/core";
+import { IconNames } from "@blueprintjs/icons";
+import * as React from "react";
+
+const ErrorState: React.SFC = () => ;
+
+export default ErrorState;
diff --git a/frontend/src/components/FloatingResetButton.tsx b/frontend/src/components/molecules/FloatingResetButton.tsx
similarity index 58%
rename from frontend/src/components/FloatingResetButton.tsx
rename to frontend/src/components/molecules/FloatingResetButton.tsx
index c4cd478..a8b8cf5 100644
--- a/frontend/src/components/FloatingResetButton.tsx
+++ b/frontend/src/components/molecules/FloatingResetButton.tsx
@@ -1,12 +1,13 @@
import { Button } from "@blueprintjs/core";
import * as React from "react";
-import FloatingCard from "./FloatingCard";
+import { FloatingCard } from "../atoms/";
interface IFloatingResetButtonProps {
onClick?: () => any;
}
-export const FloatingResetButton: React.FC = ({ onClick }) => (
+const FloatingResetButton: React.FC = ({ onClick }) => (
);
+export default FloatingResetButton;
diff --git a/frontend/src/components/molecules/index.ts b/frontend/src/components/molecules/index.ts
new file mode 100644
index 0000000..bf597ac
--- /dev/null
+++ b/frontend/src/components/molecules/index.ts
@@ -0,0 +1,3 @@
+export { default as Cytoscape } from "./Cytoscape";
+export { default as ErrorState } from "./ErrorState";
+export { default as FloatingResetButton } from "./FloatingResetButton";
diff --git a/frontend/src/components/organisms/Graph.tsx b/frontend/src/components/organisms/Graph.tsx
new file mode 100644
index 0000000..afc8aa5
--- /dev/null
+++ b/frontend/src/components/organisms/Graph.tsx
@@ -0,0 +1,76 @@
+import * as React from "react";
+import { connect } from "react-redux";
+
+import { push } from "connected-react-router";
+import { Dispatch } from "redux";
+import styled from "styled-components";
+import { IAppState, IGraph } from "../../redux/types";
+import { domainMatchSelector } from "../../util";
+import { Cytoscape, ErrorState, FloatingResetButton } from "../molecules/";
+
+const GraphDiv = styled.div`
+ flex: 3;
+`;
+
+// TODO: merge this component with Cytoscape.tsx
+interface IGraphProps {
+ graph?: IGraph;
+ currentInstanceName: string | null;
+ navigate: (path: string) => void;
+}
+class GraphImpl extends React.Component {
+ private cytoscapeComponent: React.RefObject;
+
+ public constructor(props: IGraphProps) {
+ super(props);
+ this.cytoscapeComponent = React.createRef();
+ }
+
+ public render() {
+ if (!this.props.graph) {
+ return ;
+ }
+
+ return (
+
+
+
+
+ );
+ }
+
+ private resetGraphPosition = () => {
+ if (this.cytoscapeComponent.current) {
+ this.cytoscapeComponent.current.resetGraphPosition();
+ }
+ };
+
+ private navigateToInstancePath = (domain: string) => {
+ this.props.navigate(`/instance/${domain}`);
+ };
+
+ private navigateToRoot = () => {
+ this.props.navigate("/");
+ };
+}
+const mapStateToProps = (state: IAppState) => {
+ const match = domainMatchSelector(state);
+ return {
+ currentInstanceName: match && match.params.domain,
+ graph: state.data.graph
+ };
+};
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+ navigate: (path: string) => dispatch(push(path))
+});
+const Graph = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(GraphImpl);
+export default Graph;
diff --git a/frontend/src/components/InstanceSearch.tsx b/frontend/src/components/organisms/InstanceSearch.tsx
similarity index 69%
rename from frontend/src/components/InstanceSearch.tsx
rename to frontend/src/components/organisms/InstanceSearch.tsx
index 1833460..2a401bd 100644
--- a/frontend/src/components/InstanceSearch.tsx
+++ b/frontend/src/components/organisms/InstanceSearch.tsx
@@ -6,14 +6,15 @@ 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";
+import { push } from "connected-react-router";
+import { IAppState, IInstance } from "../../redux/types";
+import { domainMatchSelector } from "../../util";
-interface IInstanceSearchProps extends RouteComponentProps {
+interface IInstanceSearchProps {
currentInstanceName: string | null;
+ pathname: string;
instances?: IInstance[];
- selectAndLoadInstance: (instanceName: string) => void;
+ selectInstance: (instanceName: string) => void;
}
const InstanceSelect = Select.ofType();
@@ -26,7 +27,9 @@ class InstanceSearchImpl extends React.Component {
itemRenderer={this.itemRenderer}
onItemSelect={this.onItemSelect}
itemPredicate={this.itemPredicate}
- disabled={!this.props.instances || this.props.location.pathname !== "/"}
+ disabled={
+ !this.props.instances || (!this.props.pathname.startsWith("/instance/") && this.props.pathname !== "/")
+ }
initialContent={this.renderInitialContent()}
noResults={this.renderNoResults()}
popoverProps={{ popoverClassName: "fediverse-instance-search-popover" }}
@@ -66,20 +69,23 @@ class InstanceSearchImpl extends React.Component {
};
private onItemSelect = (item: IInstance, event?: React.SyntheticEvent) => {
- this.props.selectAndLoadInstance(item.name);
+ this.props.selectInstance(item.name);
};
}
-const mapStateToProps = (state: IAppState) => ({
- currentInstanceName: state.currentInstance.currentInstanceName,
- instances: state.data.instances
-});
+const mapStateToProps = (state: IAppState) => {
+ const match = domainMatchSelector(state);
+ return {
+ currentInstanceName: match && match.params.domain,
+ instances: state.data.instances,
+ pathname: state.router.location.pathname
+ };
+};
const mapDispatchToProps = (dispatch: Dispatch) => ({
- selectAndLoadInstance: (instanceName: string) => dispatch(selectAndLoadInstance(instanceName) as any)
+ selectInstance: (domain: string) => dispatch(push(`/instance/${domain}`))
});
-export const InstanceSearch = withRouter(
- connect(
- mapStateToProps,
- mapDispatchToProps
- )(InstanceSearchImpl)
-);
+const InstanceSearch = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(InstanceSearchImpl);
+export default InstanceSearch;
diff --git a/frontend/src/components/organisms/Nav.tsx b/frontend/src/components/organisms/Nav.tsx
new file mode 100644
index 0000000..058c17f
--- /dev/null
+++ b/frontend/src/components/organisms/Nav.tsx
@@ -0,0 +1,56 @@
+import * as React from "react";
+
+import { Alignment, Navbar } from "@blueprintjs/core";
+import { IconNames } from "@blueprintjs/icons";
+
+import { Classes } from "@blueprintjs/core";
+import { match, NavLink } from "react-router-dom";
+import { InstanceSearch } from ".";
+import { IInstanceDomainPath } from "../../constants";
+
+interface INavState {
+ aboutIsOpen: boolean;
+}
+
+const linkIsActive = (currMatch: match, location: Location) => {
+ return location.pathname === "/" || location.pathname.startsWith("/instance/");
+};
+
+class Nav extends React.Component<{}, INavState> {
+ constructor(props: any) {
+ super(props);
+ this.state = { aboutIsOpen: false };
+ }
+
+ public render() {
+ return (
+
+
+ fediverse.space
+
+
+ Home
+
+
+ About
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Nav;
diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/organisms/Sidebar.tsx
similarity index 79%
rename from frontend/src/components/Sidebar.tsx
rename to frontend/src/components/organisms/Sidebar.tsx
index 02366bc..e2536ca 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/organisms/Sidebar.tsx
@@ -3,7 +3,6 @@ import moment from "moment";
import * as numeral from "numeral";
import * as React from "react";
import { connect } from "react-redux";
-import { Dispatch } from "redux";
import sanitize from "sanitize-html";
import {
@@ -27,10 +26,53 @@ import {
} from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons";
-import { selectAndLoadInstance } from "../redux/actions";
-import { IAppState, IGraph, IInstanceDetails } from "../redux/types";
-import FullDiv from "./atoms/FullDiv";
-import { ErrorState } from "./ErrorState";
+import { Link } from "react-router-dom";
+import styled from "styled-components";
+import { IAppState, IGraph, IInstanceDetails } from "../../redux/types";
+import { domainMatchSelector } from "../../util";
+import { ErrorState } from "../molecules/";
+import { FullDiv } from "../styled-components";
+
+interface IClosedProp {
+ closed?: boolean;
+}
+const SidebarContainer = styled.div`
+ position: fixed;
+ top: 50px;
+ bottom: 0;
+ right: ${props => (props.closed ? "-400px" : 0)};
+ min-width: 400px;
+ width: 25%;
+ z-index: 20;
+ overflow: scroll;
+ overflow-x: hidden;
+ transition-property: all;
+ transition-duration: 0.5s;
+ transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+ @media screen and (min-width: 1600px) {
+ right: ${props => (props.closed ? "-25%" : 0)};
+ }
+`;
+const StyledCard = styled(Card)`
+ min-height: 100%;
+ width: 100%;
+`;
+const StyledButton = styled(Button)`
+ position: absolute;
+ top: 0;
+ left: -40px;
+ z-index: 20;
+ transition-property: all;
+ transition-duration: 0.5s;
+ transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
+`;
+const StyledHTMLTable = styled(HTMLTable)`
+ width: 100%;
+`;
+const StyledLinkToFdNetwork = styled.div`
+ margin-top: 3em;
+ text-align: center;
+`;
interface ISidebarProps {
graph?: IGraph;
@@ -38,7 +80,6 @@ interface ISidebarProps {
instanceLoadError: boolean;
instanceDetails: IInstanceDetails | null;
isLoadingInstanceDetails: boolean;
- selectAndLoadInstance: (instanceName: string) => void;
}
interface ISidebarState {
isOpen: boolean;
@@ -63,21 +104,12 @@ class SidebarImpl extends React.Component {
}
public render() {
- const closedClass = this.state.isOpen ? "" : " closed";
const buttonIcon = this.state.isOpen ? IconNames.DOUBLE_CHEVRON_RIGHT : IconNames.DOUBLE_CHEVRON_LEFT;
return (
-
-
-
- {this.renderSidebarContents()}
-
-
+
+
+ {this.renderSidebarContents()}
+
);
}
@@ -149,6 +181,16 @@ class SidebarImpl extends React.Component {
+
+
+ See more statistics at fediverse.network
+
+
);
};
@@ -196,7 +238,7 @@ class SidebarImpl extends React.Component {
const { version, userCount, statusCount, domainCount, lastUpdated, insularity } = this.props.instanceDetails;
return (
-
+
Version
@@ -238,7 +280,7 @@ class SidebarImpl extends React.Component {
@@ -274,7 +320,7 @@ class SidebarImpl extends React.Component {
The mention ratio is the average of how many times the two instances mention each other per status. A mention
ratio of 1 would mean that every single status contained a mention of a user on the other instance.
-
+
Instance
@@ -282,7 +328,7 @@ class SidebarImpl extends React.Component {