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})) .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) { 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.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' return ( {domain} {index < 9 && {index+1}} ) } renderSettingsLink() { if (['owner', 'admin'].includes(this.props.currentUserRole)) { return (
Site settings
) } } renderDropdown() { if (this.state.loading) { return
} else if (this.state.error) { return
Something went wrong, try again
} else { return ( { this.renderSettingsLink() }
{ this.state.sites.map(this.renderSiteLink.bind(this)) }
Add Site
) } } 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() }
) } }