// @flow
import React from 'react';
import Helmet from 'react-helmet';
+import key from 'keymaster';
import MeasureContentContainer from './MeasureContentContainer';
import MeasureOverviewContainer from './MeasureOverviewContainer';
import Sidebar from '../sidebar/Sidebar';
this.mounted = true;
this.props.fetchMetrics();
this.fetchMeasures(this.props);
-
+ key.setScope('measures-files');
const footer = document.getElementById('footer');
if (footer) {
footer.classList.add('search-navigator-footer');
componentWillUnmount() {
this.mounted = false;
+ key.deleteScope('measures-files');
const footer = document.getElementById('footer');
if (footer) {
footer.classList.remove('search-navigator-footer');
*/
// @flow
import React from 'react';
+import key from 'keymaster';
import Breadcrumb from './Breadcrumb';
import { getBreadcrumbs } from '../../../api/components';
import type { Component } from '../types';
componentDidMount() {
this.mounted = true;
this.fetchBreadcrumbs(this.props);
+ this.attachShortcuts();
}
componentWillReceiveProps(nextProps: Props) {
componentWillUnmount() {
this.mounted = false;
+ this.detachShortcuts();
+ }
+
+ attachShortcuts() {
+ key('left', 'measures-files', () => {
+ const { breadcrumbs } = this.state;
+ if (breadcrumbs.length > 1) {
+ this.props.handleSelect(breadcrumbs[breadcrumbs.length - 2].key);
+ }
+ return false;
+ });
+ }
+
+ detachShortcuts() {
+ key.unbind('left', 'measures-files');
}
fetchBreadcrumbs = ({ component, rootComponent }: Props) => {
components: Array<ComponentEnhanced>,
metric: ?Metric,
paging?: Paging,
+ selected: ?string,
view: ?string
};
components: [],
metric: null,
paging: null,
+ selected: null,
view: null
};
this.mounted = false;
}
+ getSelectedIndex = (): ?number => {
+ const index = this.state.components.findIndex(
+ component => component.key === this.state.selected
+ );
+ return index !== -1 ? index : null;
+ };
+
getComponentRequestParams = (view: string, metric: Metric, options: Object = {}) => {
const strategy = view === 'list' ? 'leaves' : 'children';
const metricKeys = [metric.key];
),
metric,
paging: r.paging,
+ selected: r.components.length > 0 ? r.components[0].key : null,
view
});
}
);
};
+ onSelectComponent = (component: string) => this.setState({ selected: component });
+
renderContent() {
const { component, leakPeriod } = this.props;
}
if (['list', 'tree'].includes(view)) {
+ const selectedIdx = this.getSelectedIndex();
return (
<FilesView
components={this.state.components}
fetchMore={this.fetchMoreComponents}
- handleSelect={this.props.updateSelected}
+ handleOpen={this.props.updateSelected}
+ handleSelect={this.onSelectComponent}
metric={metric}
metrics={this.props.metrics}
paging={this.state.paging}
+ selectedKey={selectedIdx != null ? this.state.selected : null}
+ selectedIdx={selectedIdx}
/>
);
}
view={view}
/>}
<PageActions
- current={this.state.components.length}
+ current={this.getSelectedIndex() + 1}
loading={this.props.loading}
isFile={isFile}
paging={this.state.paging}
render() {
return (
<Select
+ autoBlur={true}
className={this.props.className}
clearable={false}
searchable={false}
<Select
addLabelText="Add \\"{label}\\"?"
arrowRenderer={[Function]}
+ autoBlur={true}
autosize={true}
backspaceRemoves={true}
backspaceToRemoveMessage="Press backspace to remove {label}"
*/
// @flow
import React from 'react';
-import classNames from 'classnames';
import QualifierIcon from '../../../components/shared/QualifierIcon';
import { splitPath } from '../../../helpers/path';
import { getComponentUrl } from '../../../helpers/urls';
type Props = {
component: Component,
- isSelected: boolean,
onClick: string => void
};
render() {
const { component } = this.props;
- const linkClassName = classNames('link-no-underline', {
- selected: this.props.isSelected
- });
-
return (
<td className="measure-details-component-cell">
<div className="text-ellipsis">
{component.refId == null
? <a
id={'component-measures-component-link-' + component.key}
- className={linkClassName}
+ className="link-no-underline"
href={getComponentUrl(component.key)}
onClick={this.handleClick}>
{this.renderInner()}
</a>
: <a
id={'component-measures-component-link-' + component.key}
- className={linkClassName}
+ className="link-no-underline"
href={getComponentUrl(component.refKey || component.key)}>
<span className="big-spacer-right">
<i className="icon-detach" />
*/
// @flow
import React from 'react';
+import classNames from 'classnames';
import ComponentCell from './ComponentCell';
import MeasureCell from './MeasureCell';
import type { Component } from '../types';
const measure = component.measures.find(measure => measure.metric === metric.key);
return { ...measure, metric };
});
+ const rowClass = classNames('measure-details-component-row', {
+ selected: props.isSelected
+ });
return (
- <tr>
- <ComponentCell component={component} isSelected={props.isSelected} onClick={props.onClick} />
+ <tr className={rowClass}>
+ <ComponentCell component={component} onClick={props.onClick} />
<MeasureCell component={component} metric={props.metric} />
*/
// @flow
import React from 'react';
+import key from 'keymaster';
+import { throttle } from 'lodash';
import ComponentsList from './ComponentsList';
import ListFooter from '../../../components/controls/ListFooter';
+import { scrollToElement } from '../../../helpers/scrolling';
import type { ComponentEnhanced, Paging } from '../types';
import type { Metric } from '../../../store/metrics/actions';
components: Array<ComponentEnhanced>,
fetchMore: () => void,
handleSelect: string => void,
+ handleOpen: string => void,
metric: Metric,
metrics: { [string]: Metric },
- paging: ?Paging
+ paging: ?Paging,
+ selectedKey: ?string,
+ selectedIdx: ?number
|};
-export default function ListView(props: Props) {
- return (
- <div>
- <ComponentsList
- components={props.components}
- metrics={props.metrics}
- metric={props.metric}
- onClick={props.handleSelect}
- />
- {props.paging &&
- <ListFooter
- count={props.components.length}
- total={props.paging.total}
- loadMore={props.fetchMore}
- />}
- </div>
- );
+export default class ListView extends React.PureComponent {
+ listContainer: HTMLElement;
+ props: Props;
+
+ constructor(props: Props) {
+ super(props);
+ this.selectNext = throttle(this.selectNext, 100);
+ this.selectPrevious = throttle(this.selectPrevious, 100);
+ }
+
+ componentDidMount() {
+ this.attachShortcuts();
+ }
+
+ componentDidUpdate() {
+ if (this.listContainer && this.props.selectedIdx != null) {
+ const elem = this.listContainer.getElementsByClassName('selected')[0];
+ if (elem) {
+ scrollToElement(elem, { topOffset: 215, bottomOffset: 100 });
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ this.detachShortcuts();
+ }
+
+ attachShortcuts() {
+ key('up', 'measures-files', () => {
+ this.selectPrevious();
+ return false;
+ });
+ key('down', 'measures-files', () => {
+ this.selectNext();
+ return false;
+ });
+ key('right', 'measures-files', () => {
+ this.openSelected();
+ return false;
+ });
+ }
+
+ detachShortcuts() {
+ ['up', 'down', 'right'].map(action => key.unbind(action, 'measures-files'));
+ }
+
+ openSelected = () => {
+ if (this.props.selectedKey != null) {
+ this.props.handleOpen(this.props.selectedKey);
+ }
+ };
+
+ selectPrevious = () => {
+ const { selectedIdx } = this.props;
+ if (selectedIdx != null && selectedIdx > 0) {
+ this.props.handleSelect(this.props.components[selectedIdx - 1].key);
+ } else {
+ this.props.handleSelect(this.props.components[this.props.components.length - 1].key);
+ }
+ };
+
+ selectNext = () => {
+ const { selectedIdx } = this.props;
+ if (selectedIdx != null && selectedIdx < this.props.components.length - 1) {
+ this.props.handleSelect(this.props.components[selectedIdx + 1].key);
+ } else {
+ this.props.handleSelect(this.props.components[0].key);
+ }
+ };
+
+ render() {
+ return (
+ <div
+ ref={elem => {
+ this.listContainer = elem;
+ }}>
+ <ComponentsList
+ components={this.props.components}
+ metrics={this.props.metrics}
+ metric={this.props.metric}
+ onClick={this.props.handleOpen}
+ selectedComponent={this.props.selectedKey}
+ />
+ {this.props.paging &&
+ <ListFooter
+ count={this.props.components.length}
+ total={this.props.paging.total}
+ loadMore={this.props.fetchMore}
+ />}
+ </div>
+ );
+ }
}
margin-top: 4px;
}
+.measure-details-component-row.selected {
+ background-color: #cae3f2 !important;
+}
+
.measure-details-component-cell {
max-width: 0;
}