gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

components.jsx (4760B)


      1 /*
      2 	GoToSocial
      3 	Copyright (C) GoToSocial Authors admin@gotosocial.org
      4 	SPDX-License-Identifier: AGPL-3.0-or-later
      5 
      6 	This program is free software: you can redistribute it and/or modify
      7 	it under the terms of the GNU Affero General Public License as published by
      8 	the Free Software Foundation, either version 3 of the License, or
      9 	(at your option) any later version.
     10 
     11 	This program is distributed in the hope that it will be useful,
     12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 	GNU Affero General Public License for more details.
     15 
     16 	You should have received a copy of the GNU Affero General Public License
     17 	along with this program.  If not, see <http://www.gnu.org/licenses/>.
     18 */
     19 
     20 "use strict";
     21 
     22 const React = require("react");
     23 const { Link, Route, Redirect, Switch, useLocation, useRouter } = require("wouter");
     24 const syncpipe = require("syncpipe");
     25 
     26 const {
     27 	RoleContext,
     28 	useHasPermission,
     29 	checkPermission,
     30 	BaseUrlContext
     31 } = require("./util");
     32 
     33 const ActiveRouteCtx = React.createContext();
     34 function useActiveRoute() {
     35 	return React.useContext(ActiveRouteCtx);
     36 }
     37 
     38 function Sidebar(menuTree, routing) {
     39 	const components = menuTree.map((m) => m.MenuEntry);
     40 
     41 	return function SidebarComponent() {
     42 		const router = useRouter();
     43 		const [location] = useLocation();
     44 
     45 		let activeRoute = routing.find((l) => {
     46 			let [match] = router.matcher(l.routingUrl, location);
     47 			return match;
     48 		})?.routingUrl;
     49 
     50 		return (
     51 			<nav className="menu-tree">
     52 				<ul className="top-level">
     53 					<ActiveRouteCtx.Provider value={activeRoute}>
     54 						{components}
     55 					</ActiveRouteCtx.Provider>
     56 				</ul>
     57 			</nav>
     58 		);
     59 	};
     60 }
     61 
     62 function ViewRouter(routing, defaultRoute) {
     63 	return function ViewRouterComponent() {
     64 		const permissions = React.useContext(RoleContext);
     65 
     66 		const filteredRoutes = React.useMemo(() => {
     67 			return syncpipe(routing, [
     68 				(_) => _.filter((route) => checkPermission(route.permissions, permissions)),
     69 				(_) => _.map((route) => {
     70 					return (
     71 						<Route path={route.routingUrl} key={route.key}>
     72 							<ErrorBoundary>
     73 								{/* FIXME: implement reset */}
     74 								<BaseUrlContext.Provider value={route.url}>
     75 									{route.view}
     76 								</BaseUrlContext.Provider>
     77 							</ErrorBoundary>
     78 						</Route>
     79 					);
     80 				})
     81 			]);
     82 		}, [permissions]);
     83 
     84 		return (
     85 			<Switch>
     86 				{filteredRoutes}
     87 				<Redirect to={defaultRoute} />
     88 			</Switch>
     89 		);
     90 	};
     91 }
     92 
     93 function MenuComponent({ type, name, url, icon, permissions, links, level, children }) {
     94 	const activeRoute = useActiveRoute();
     95 
     96 	if (!useHasPermission(permissions)) {
     97 		return null;
     98 	}
     99 
    100 	const classes = [type];
    101 
    102 	if (level == 0) {
    103 		classes.push("top-level");
    104 	} else if (level == 1) {
    105 		classes.push("expanding");
    106 	} else {
    107 		classes.push("nested");
    108 	}
    109 
    110 	const isActive = links.includes(activeRoute);
    111 	if (isActive) {
    112 		classes.push("active");
    113 	}
    114 
    115 	const className = classes.join(" ");
    116 
    117 	return (
    118 		<li className={className}>
    119 			<Link href={url}>
    120 				<a tabIndex={level == 0 ? "-1" : null} className="title">
    121 					{icon && <i className={`icon fa fa-fw ${icon}`} aria-hidden="true" />}
    122 					{name}
    123 				</a>
    124 			</Link>
    125 			{(type == "category" && (level == 0 || isActive) && children?.length > 0) &&
    126 				<ul>
    127 					{children}
    128 				</ul>
    129 			}
    130 		</li>
    131 	);
    132 }
    133 
    134 class ErrorBoundary extends React.Component {
    135 
    136 	constructor() {
    137 		super();
    138 		this.state = {};
    139 
    140 		this.resetErrorBoundary = () => {
    141 			this.setState({});
    142 		};
    143 	}
    144 
    145 	static getDerivedStateFromError(error) {
    146 		return { hadError: true, error };
    147 	}
    148 
    149 	componentDidCatch(_e, info) {
    150 		this.setState({
    151 			...this.state,
    152 			componentStack: info.componentStack
    153 		});
    154 	}
    155 
    156 	render() {
    157 		if (this.state.hadError) {
    158 			return (
    159 				<ErrorFallback
    160 					error={this.state.error}
    161 					componentStack={this.state.componentStack}
    162 					resetErrorBoundary={this.resetErrorBoundary}
    163 				/>
    164 			);
    165 		} else {
    166 			return this.props.children;
    167 		}
    168 	}
    169 }
    170 
    171 function ErrorFallback({ error, componentStack, resetErrorBoundary }) {
    172 	return (
    173 		<div className="error">
    174 			<p>
    175 				{"An error occured, please report this on the "}
    176 				<a href="https://github.com/superseriousbusiness/gotosocial/issues">GoToSocial issue tracker</a>
    177 				{" or "}
    178 				<a href="https://matrix.to/#/#gotosocial-help:superseriousbusiness.org">Matrix support room</a>.
    179 				<br />Include the details below:
    180 			</p>
    181 			<div className="details">
    182 				<pre>
    183 					{error.name}: {error.message}
    184 
    185 					{componentStack && [
    186 						"\n\nComponent trace:",
    187 						componentStack
    188 					]}
    189 					{["\n\nError trace: ", error.stack]}
    190 				</pre>
    191 			</div>
    192 			<p>
    193 				<button onClick={resetErrorBoundary}>Try again</button> or <a href="">refresh the page</a>
    194 			</p>
    195 		</div>
    196 	);
    197 }
    198 
    199 module.exports = {
    200 	Sidebar,
    201 	ViewRouter,
    202 	MenuComponent
    203 };