/* * SonarQube * Copyright (C) 2009-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { Link } from 'react-router'; import ComponentNavBranchesMenuItem from './ComponentNavBranchesMenuItem'; import { Branch, Component } from '../../../types'; import { sortBranchesAsTree, isLongLivingBranch, isShortLivingBranch } from '../../../../helpers/branches'; import { translate } from '../../../../helpers/l10n'; import { getProjectBranchUrl } from '../../../../helpers/urls'; import Tooltip from '../../../../components/controls/Tooltip'; interface Props { branches: Branch[]; canAdmin?: boolean; component: Component; currentBranch: Branch; onClose: () => void; } interface State { query: string; selected: string | null; } export default class ComponentNavBranchesMenu extends React.PureComponent { private node: HTMLElement | null; static contextTypes = { router: PropTypes.object }; constructor(props: Props) { super(props); this.state = { query: '', selected: null }; } componentDidMount() { window.addEventListener('click', this.handleClickOutside); } componentWillUnmount() { window.removeEventListener('click', this.handleClickOutside); } getFilteredBranches = () => sortBranchesAsTree(this.props.branches).filter(branch => branch.name.toLowerCase().includes(this.state.query.toLowerCase()) ); handleClickOutside = (event: Event) => { if (!this.node || !this.node.contains(event.target as HTMLElement)) { this.props.onClose(); } }; handleSearchChange = (event: React.SyntheticEvent) => this.setState({ query: event.currentTarget.value, selected: null }); handleKeyDown = (event: React.KeyboardEvent) => { switch (event.keyCode) { case 13: event.preventDefault(); this.openSelected(); return; case 27: event.preventDefault(); this.props.onClose(); return; case 38: event.preventDefault(); this.selectPrevious(); return; case 40: event.preventDefault(); this.selectNext(); return; } }; openSelected = () => { const selected = this.getSelected(); const branch = this.getFilteredBranches().find(branch => branch.name === selected); if (branch) { this.context.router.push(this.getProjectBranchUrl(branch)); } }; selectPrevious = () => { const selected = this.getSelected(); const branches = this.getFilteredBranches(); const index = branches.findIndex(branch => branch.name === selected); if (index > 0) { this.setState({ selected: branches[index - 1].name }); } }; selectNext = () => { const selected = this.getSelected(); const branches = this.getFilteredBranches(); const index = branches.findIndex(branch => branch.name === selected); if (index >= 0 && index < branches.length - 1) { this.setState({ selected: branches[index + 1].name }); } }; handleSelect = (branch: Branch) => { this.setState({ selected: branch.name }); }; getSelected = () => { if (this.state.selected) { return this.state.selected; } const branches = this.getFilteredBranches(); if (branches.find(b => b.name === this.props.currentBranch.name)) { return this.props.currentBranch.name; } if (branches.length > 0) { return branches[0].name; } return undefined; }; getProjectBranchUrl = (branch: Branch) => getProjectBranchUrl(this.props.component.key, branch); isSelected = (branch: Branch) => branch.name === this.getSelected(); renderSearch = () => (
); renderBranchesList = () => { const branches = this.getFilteredBranches(); const selected = this.getSelected(); if (branches.length === 0) { return
{translate('no_results')}
; } const menu: JSX.Element[] = []; branches.forEach((branch, index) => { const isOrphan = isShortLivingBranch(branch) && branch.isOrphan; const previous = index > 0 ? branches[index - 1] : null; const isPreviousOrphan = isShortLivingBranch(previous) ? previous.isOrphan : false; if (isLongLivingBranch(branch) || (isOrphan && !isPreviousOrphan)) { menu.push(
  • ); } if (isOrphan && !isPreviousOrphan) { menu.push(
  • {translate('branches.orphan_branches')}
  • ); } menu.push( ); }); return
      {menu}
    ; }; render() { const { component } = this.props; const showManageLink = component.qualifier === 'TRK' && component.configuration && component.configuration.showSettings; return (
    (this.node = node)}> {this.renderSearch()} {this.renderBranchesList()} {showManageLink && (
    {translate('branches.manage')}
    )}
    ); } }