import React from 'react'; import { Transition } from '@headlessui/react' export default class SiteSwitcher extends React.Component { constructor() { super() this.handleClick = this.handleClick.bind(this); this.handleKeydown = this.handleKeydown.bind(this); this.populateSites = this.populateSites.bind(this); this.toggle = this.toggle.bind(this); this.siteSwitcherButton = React.createRef(); this.state = { open: false, sites: null, error: null, loading: true } } componentDidMount() { this.populateSites(); this.siteSwitcherButton.current.addEventListener("click", this.toggle); document.addEventListener("keydown", this.handleKeydown); document.addEventListener('click', this.handleClick, false); } componentWillUnmount() { this.siteSwitcherButton.current.removeEventListener("click", this.toggle); document.removeEventListener("keydown", this.handleKeydown); document.removeEventListener('click', this.handleClick, false); } populateSites() { if (!this.props.loggedIn) return; fetch('/api/sites') .then( response => { if (!response.ok) { throw response } return response.json() }) .then((sites) => this.setState( { loading: false, sites: sites.data.map(s => s.domain) })) .catch((e) => this.setState({loading: false, error: e})) } handleClick(e) { // If this is an interaction with the dropdown menu itself, do nothing. if (this.dropDownNode && this.dropDownNode.contains(e.target)) return; // If the dropdown is not open, do nothing. if (!this.state.open) return; // In any other case, close it. this.setState({ open: false }); } handleKeydown(e) { if (!this.props.loggedIn) return; const { site } = this.props; const { sites } = this.state; if (e.target.tagName === 'INPUT') return true; if (e.ctrlKey || e.metaKey || e.altKey || e.isComposing || e.keyCode === 229 || !sites) return; const siteNum = parseInt(e.key) if (1 <= siteNum && siteNum <= 9 && siteNum <= sites.length && sites[siteNum-1] !== site.domain) { window.location = `/${encodeURIComponent(sites[siteNum-1])}` } } toggle(e) { /** * React doesn't seem to prioritise its own events when events are bubbling, and is unable to stop its events from propagating to the document's (root) event listeners which are attached on the DOM. * * A simple trick is to hook up our own click event listener via a ref node, which allows React to manage events in this situation better between the two. */ e.stopPropagation(); e.preventDefault(); if (!this.props.loggedIn) return; this.setState((prevState) => ({ open: !prevState.open })) if (this.props.loggedIn && !this.state.sites) { this.populateSites(); } } renderSiteLink(domain, index) { const extraClass = domain === this.props.site.domain ? 'font-medium text-gray-900 dark:text-gray-100 cursor-default font-bold' : 'hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-900 dark:focus:text-gray-100' const showHotkey = !this.props.loggedIn return ( {domain} {showHotkey ? index < 9 && {index+1} : null} ) } renderSettingsLink() { if (['owner', 'admin', 'super_admin'].includes(this.props.currentUserRole)) { return (
Site settings
) } } /** * Render a dropdown regardless of whether the user is logged in or not. In case they are not logged in (such as in an embed), the dropdown merely contains the current domain name. */ renderDropdown() { if (this.state.loading) { return
} else if (this.state.error) { return
Something went wrong, try again
} else if (!this.props.loggedIn) { return (
{ [this.props.site.domain].map(this.renderSiteLink.bind(this)) }
) } else { return ( { this.renderSettingsLink() }
{ this.state.sites.map(this.renderSiteLink.bind(this)) }
View all
) } } renderArrow() { if (this.props.loggedIn) { return ( ) } } render() { const hoverClass = this.props.loggedIn ? 'hover:text-gray-500 dark:hover:text-gray-200 focus:border-blue-300 focus:ring ' : 'cursor-default' return (
this.dropDownNode = node} >
{ this.renderDropdown() }
) } }