@@ -41,6 +41,19 @@ | |||
margin-right: 4px; | |||
} | |||
.shortcut-button-tiny { | |||
width: 14px; | |||
min-width: auto; | |||
padding: 0; | |||
height: 14px; | |||
line-height: inherit; | |||
font-size: 6px; | |||
} | |||
.page-shortcuts-tooltip { | |||
line-height: 12px; | |||
} | |||
.identity-provider { | |||
display: inline-block; | |||
line-height: 14px; |
@@ -22,18 +22,23 @@ import ReloadButton from 'sonar-ui-common/components/controls/ReloadButton'; | |||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import PageCounter from '../../../components/common/PageCounter'; | |||
import PageShortcutsTooltip from '../../../components/ui/PageShortcutsTooltip'; | |||
interface Props { | |||
export interface PageActionsProps { | |||
loading: boolean; | |||
onReload: () => void; | |||
paging?: T.Paging; | |||
selectedIndex?: number; | |||
} | |||
export default function PageActions(props: Props) { | |||
export default function PageActions(props: PageActionsProps) { | |||
return ( | |||
<div className="pull-right"> | |||
<Shortcuts /> | |||
<div className="display-flex-center display-flex-justify-end"> | |||
<PageShortcutsTooltip | |||
className="big-spacer-right" | |||
leftAndRightLabel={translate('issues.to_navigate')} | |||
upAndDownLabel={translate('coding_rules.to_select_rules')} | |||
/> | |||
<DeferredSpinner loading={props.loading}> | |||
<ReloadButton onClick={props.onReload} /> | |||
@@ -50,21 +55,3 @@ export default function PageActions(props: Props) { | |||
</div> | |||
); | |||
} | |||
function Shortcuts() { | |||
return ( | |||
<span className="note big-spacer-right"> | |||
<span className="big-spacer-right"> | |||
<span className="shortcut-button little-spacer-right">↑</span> | |||
<span className="shortcut-button little-spacer-right">↓</span> | |||
{translate('coding_rules.to_select_rules')} | |||
</span> | |||
<span> | |||
<span className="shortcut-button little-spacer-right">←</span> | |||
<span className="shortcut-button little-spacer-right">→</span> | |||
{translate('issues.to_navigate')} | |||
</span> | |||
</span> | |||
); | |||
} |
@@ -17,24 +17,17 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import PageActions, { PageActionsProps } from '../PageActions'; | |||
export interface Props { | |||
issue: Pick<T.Issue, 'flows' | 'secondaryLocations'> | undefined; | |||
} | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
expect(shallowRender({ paging: { total: 100 } as T.Paging })).toMatchSnapshot('with paging'); | |||
}); | |||
export default function LocationNavigationKeyboardShortcuts({ issue }: Props) { | |||
if (!issue || (!issue.secondaryLocations.length && !issue.flows.length)) { | |||
return null; | |||
} | |||
const hasSeveralFlows = issue.flows.length > 1; | |||
return ( | |||
<div className="navigation-keyboard-shortcuts big-spacer-top text-center"> | |||
<span> | |||
alt + ↑ ↓ {hasSeveralFlows && <>←→</>} | |||
{translate('issues.to_navigate_issue_locations')} | |||
</span> | |||
</div> | |||
); | |||
function shallowRender(props: Partial<PageActionsProps> = {}) { | |||
return shallow<PageActionsProps>(<PageActions loading={false} onReload={jest.fn()} {...props} />); | |||
} |
@@ -0,0 +1,63 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="display-flex-center display-flex-justify-end" | |||
> | |||
<PageShortcutsTooltip | |||
className="big-spacer-right" | |||
leftAndRightLabel="issues.to_navigate" | |||
upAndDownLabel="coding_rules.to_select_rules" | |||
/> | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
<ReloadButton | |||
onClick={[MockFunction]} | |||
/> | |||
</DeferredSpinner> | |||
</div> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<div | |||
className="display-flex-center display-flex-justify-end" | |||
> | |||
<PageShortcutsTooltip | |||
className="big-spacer-right" | |||
leftAndRightLabel="issues.to_navigate" | |||
upAndDownLabel="coding_rules.to_select_rules" | |||
/> | |||
<DeferredSpinner | |||
loading={true} | |||
> | |||
<ReloadButton | |||
onClick={[MockFunction]} | |||
/> | |||
</DeferredSpinner> | |||
</div> | |||
`; | |||
exports[`should render correctly: with paging 1`] = ` | |||
<div | |||
className="display-flex-center display-flex-justify-end" | |||
> | |||
<PageShortcutsTooltip | |||
className="big-spacer-right" | |||
leftAndRightLabel="issues.to_navigate" | |||
upAndDownLabel="coding_rules.to_select_rules" | |||
/> | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
<ReloadButton | |||
onClick={[MockFunction]} | |||
/> | |||
</DeferredSpinner> | |||
<PageCounter | |||
className="spacer-left" | |||
label="coding_rules._rules" | |||
total={100} | |||
/> | |||
</div> | |||
`; |
@@ -797,26 +797,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
this.handleCloseBulkChange(); | |||
}; | |||
handleReload = () => { | |||
this.fetchFirstIssues(); | |||
this.refreshBranchStatus(); | |||
const { branchLike, onBranchesChange } = this.props; | |||
if (onBranchesChange && isPullRequest(branchLike)) { | |||
onBranchesChange(); | |||
} | |||
}; | |||
handleReloadAndOpenFirst = () => { | |||
this.fetchFirstIssues().then( | |||
(issues: T.Issue[]) => { | |||
if (issues.length > 0) { | |||
this.openIssue(issues[0].key); | |||
} | |||
}, | |||
() => {} | |||
); | |||
}; | |||
selectLocation = (index: number) => { | |||
this.setState(actions.selectLocation(index)); | |||
}; | |||
@@ -946,9 +926,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
displayBackButton={query.issues.length !== 1} | |||
loading={this.state.loading} | |||
onBackClick={this.closeIssue} | |||
onReload={this.handleReloadAndOpenFirst} | |||
paging={paging} | |||
selectedIndex={this.getSelectedIndex()} | |||
/> | |||
<ConciseIssuesList | |||
issues={issues} | |||
@@ -1070,7 +1047,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
<PageActions | |||
canSetHome={!this.props.component} | |||
effortTotal={this.state.effortTotal} | |||
onReload={this.handleReload} | |||
paging={paging} | |||
selectedIndex={selectedIndex} | |||
/> | |||
@@ -1124,7 +1100,8 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
render() { | |||
const { openIssue } = this.state; | |||
const { openIssue, paging } = this.state; | |||
const selectedIndex = this.getSelectedIndex(); | |||
return ( | |||
<div className="layout-page issues" id="issues-page"> | |||
<Suggestions suggestions="issues" /> |
@@ -18,60 +18,41 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import ReloadButton from 'sonar-ui-common/components/controls/ReloadButton'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import HomePageSelect from '../../../components/controls/HomePageSelect'; | |||
import PageShortcutsTooltip from '../../../components/ui/PageShortcutsTooltip'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
import IssuesCounter from './IssuesCounter'; | |||
import TotalEffort from './TotalEffort'; | |||
interface Props { | |||
export interface PageActionsProps { | |||
canSetHome: boolean; | |||
effortTotal: number | undefined; | |||
onReload: () => void; | |||
paging: T.Paging | undefined; | |||
selectedIndex: number | undefined; | |||
paging?: T.Paging; | |||
selectedIndex?: number; | |||
} | |||
export default class PageActions extends React.PureComponent<Props> { | |||
renderShortcuts() { | |||
return ( | |||
<span className="note big-spacer-right"> | |||
<span className="big-spacer-right"> | |||
<span className="shortcut-button little-spacer-right">↑</span> | |||
<span className="shortcut-button little-spacer-right">↓</span> | |||
{translate('issues.to_select_issues')} | |||
</span> | |||
export default function PageActions(props: PageActionsProps) { | |||
const { canSetHome, effortTotal, paging, selectedIndex } = props; | |||
<span> | |||
<span className="shortcut-button little-spacer-right">←</span> | |||
<span className="shortcut-button little-spacer-right">→</span> | |||
{translate('issues.to_navigate')} | |||
</span> | |||
</span> | |||
); | |||
} | |||
return ( | |||
<div className="display-flex-center display-flex-justify-end"> | |||
<PageShortcutsTooltip | |||
leftAndRightLabel={translate('issues.to_navigate')} | |||
upAndDownLabel={translate('issues.to_select_issues')} | |||
/> | |||
render() { | |||
const { effortTotal, paging, selectedIndex } = this.props; | |||
return ( | |||
<div className="pull-right"> | |||
{this.renderShortcuts()} | |||
<div className="issues-page-actions"> | |||
<ReloadButton onClick={this.props.onReload} /> | |||
{paging != null && <IssuesCounter current={selectedIndex} total={paging.total} />} | |||
{effortTotal !== undefined && <TotalEffort effort={effortTotal} />} | |||
</div> | |||
{this.props.canSetHome && ( | |||
<HomePageSelect | |||
className="huge-spacer-left" | |||
currentPage={isSonarCloud() ? { type: 'MY_ISSUES' } : { type: 'ISSUES' }} | |||
/> | |||
)} | |||
<div className="spacer-left issues-page-actions"> | |||
{paging != null && <IssuesCounter current={selectedIndex} total={paging.total} />} | |||
{effortTotal !== undefined && <TotalEffort effort={effortTotal} />} | |||
</div> | |||
); | |||
} | |||
{canSetHome && ( | |||
<HomePageSelect | |||
className="huge-spacer-left" | |||
currentPage={isSonarCloud() ? { type: 'MY_ISSUES' } : { type: 'ISSUES' }} | |||
/> | |||
)} | |||
</div> | |||
); | |||
} |
@@ -467,10 +467,6 @@ it('should refresh branch status if issues are updated', async () => { | |||
fetchBranchStatus.mockClear(); | |||
instance.handleBulkChangeDone(); | |||
expect(fetchBranchStatus).toBeCalled(); | |||
fetchBranchStatus.mockClear(); | |||
instance.handleReload(); | |||
expect(fetchBranchStatus).toBeCalled(); | |||
}); | |||
it('should update the open issue when it is changed', async () => { |
@@ -22,15 +22,5 @@ import * as React from 'react'; | |||
import PageActions from '../PageActions'; | |||
it('should render', () => { | |||
expect( | |||
shallow( | |||
<PageActions | |||
canSetHome={true} | |||
effortTotal={125} | |||
onReload={jest.fn()} | |||
paging={{ pageIndex: 1, pageSize: 100, total: 12345 }} | |||
selectedIndex={5} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
expect(shallow(<PageActions canSetHome={true} effortTotal={125} />)).toMatchSnapshot(); | |||
}); |
@@ -82,7 +82,6 @@ exports[`should switch to source view if an issue is selected 1`] = ` | |||
<PageActions | |||
canSetHome={false} | |||
effortTotal={1} | |||
onReload={[Function]} | |||
paging={ | |||
Object { | |||
"pageIndex": 1, |
@@ -1,23 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="navigation-keyboard-shortcuts big-spacer-top text-center" | |||
> | |||
<span> | |||
alt + ↑ ↓ | |||
issues.to_navigate_issue_locations | |||
</span> | |||
</div> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<div | |||
className="navigation-keyboard-shortcuts big-spacer-top text-center" | |||
> | |||
<span> | |||
alt + ↑ ↓ | |||
issues.to_navigate_issue_locations | |||
</span> | |||
</div> | |||
`; |
@@ -2,50 +2,15 @@ | |||
exports[`should render 1`] = ` | |||
<div | |||
className="pull-right" | |||
className="display-flex-center display-flex-justify-end" | |||
> | |||
<span | |||
className="note big-spacer-right" | |||
> | |||
<span | |||
className="big-spacer-right" | |||
> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
↑ | |||
</span> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
↓ | |||
</span> | |||
issues.to_select_issues | |||
</span> | |||
<span> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
← | |||
</span> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
→ | |||
</span> | |||
issues.to_navigate | |||
</span> | |||
</span> | |||
<PageShortcutsTooltip | |||
leftAndRightLabel="issues.to_navigate" | |||
upAndDownLabel="issues.to_select_issues" | |||
/> | |||
<div | |||
className="issues-page-actions" | |||
className="spacer-left issues-page-actions" | |||
> | |||
<ReloadButton | |||
onClick={[MockFunction]} | |||
/> | |||
<IssuesCounter | |||
current={5} | |||
total={12345} | |||
/> | |||
<TotalEffort | |||
effort={125} | |||
/> |
@@ -20,7 +20,6 @@ | |||
import * as classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import TypeHelper from '../../../components/shared/TypeHelper'; | |||
import LocationNavigationKeyboardShortcuts from '../components/LocationNavigationKeyboardShortcuts'; | |||
import ConciseIssueLocations from './ConciseIssueLocations'; | |||
import ConciseIssueLocationsNavigator from './ConciseIssueLocationsNavigator'; | |||
@@ -105,16 +104,13 @@ export default class ConciseIssueBox extends React.PureComponent<Props> { | |||
/> | |||
</div> | |||
{selected && ( | |||
<> | |||
<ConciseIssueLocationsNavigator | |||
issue={issue} | |||
onLocationSelect={this.props.onLocationSelect} | |||
scroll={this.props.scroll} | |||
selectedFlowIndex={this.props.selectedFlowIndex} | |||
selectedLocationIndex={this.props.selectedLocationIndex} | |||
/> | |||
<LocationNavigationKeyboardShortcuts issue={issue} /> | |||
</> | |||
<ConciseIssueLocationsNavigator | |||
issue={issue} | |||
onLocationSelect={this.props.onLocationSelect} | |||
scroll={this.props.scroll} | |||
selectedFlowIndex={this.props.selectedFlowIndex} | |||
selectedLocationIndex={this.props.selectedLocationIndex} | |||
/> | |||
)} | |||
</div> | |||
); |
@@ -19,33 +19,28 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import BackButton from 'sonar-ui-common/components/controls/BackButton'; | |||
import ReloadButton from 'sonar-ui-common/components/controls/ReloadButton'; | |||
import IssuesCounter from '../components/IssuesCounter'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import PageShortcutsTooltip from '../../../components/ui/PageShortcutsTooltip'; | |||
interface Props { | |||
displayBackButton?: boolean; | |||
export interface ConciseIssuesListHeaderProps { | |||
displayBackButton: boolean; | |||
loading: boolean; | |||
onBackClick: () => void; | |||
onReload: () => void; | |||
paging: T.Paging | undefined; | |||
selectedIndex: number | undefined; | |||
} | |||
export default function ConciseIssuesListHeader(props: Props) { | |||
const { displayBackButton = true, paging, selectedIndex } = props; | |||
export default function ConciseIssuesListHeader(props: ConciseIssuesListHeaderProps) { | |||
const { displayBackButton, loading } = props; | |||
return ( | |||
<header className="layout-page-header-panel concise-issues-list-header"> | |||
<div className="layout-page-header-panel-inner concise-issues-list-header-inner"> | |||
{displayBackButton && ( | |||
<BackButton className="pull-left" disabled={props.loading} onClick={props.onBackClick} /> | |||
)} | |||
{props.loading ? ( | |||
<i className="spinner pull-right" /> | |||
) : ( | |||
<ReloadButton className="pull-right" onClick={props.onReload} /> | |||
)} | |||
{paging && <IssuesCounter current={selectedIndex} total={paging.total} />} | |||
<div className="layout-page-header-panel-inner concise-issues-list-header-inner display-flex-center display-flex-space-between"> | |||
{displayBackButton && <BackButton disabled={loading} onClick={props.onBackClick} />} | |||
<PageShortcutsTooltip | |||
leftLabel={translate('issues.to_navigate_back')} | |||
upAndDownLabel={translate('issues.to_select_issues')} | |||
metaModifierLabel={translate('issues.to_navigate_issue_locations')} | |||
/> | |||
{loading && <i className="spinner" />} | |||
</div> | |||
</header> | |||
); |
@@ -17,22 +17,24 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockFlowLocation } from '../../../../helpers/testMocks'; | |||
import LocationNavigationKeyboardShortcuts, { Props } from '../LocationNavigationKeyboardShortcuts'; | |||
import ConciseIssuesListHeader, { ConciseIssuesListHeaderProps } from '../ConciseIssuesListHeader'; | |||
it('should render correctly', () => { | |||
expect(shallowRender().type()).toBeNull(); | |||
expect(shallowRender({ issue: { flows: [], secondaryLocations: [] } }).type()).toBeNull(); | |||
expect( | |||
shallowRender({ issue: { flows: [], secondaryLocations: [mockFlowLocation()] } }) | |||
).toMatchSnapshot(); | |||
expect( | |||
shallowRender({ issue: { flows: [[mockFlowLocation()]], secondaryLocations: [] } }) | |||
).toMatchSnapshot(); | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
expect(shallowRender({ displayBackButton: true })).toMatchSnapshot('with back button'); | |||
}); | |||
const shallowRender = (props: Partial<Props> = {}) => { | |||
return shallow(<LocationNavigationKeyboardShortcuts issue={undefined} {...props} />); | |||
}; | |||
function shallowRender(props: Partial<ConciseIssuesListHeaderProps> = {}) { | |||
return shallow<ConciseIssuesListHeaderProps>( | |||
<ConciseIssuesListHeader | |||
displayBackButton={false} | |||
loading={false} | |||
onBackClick={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -92,39 +92,6 @@ exports[`should render correctly 1`] = ` | |||
selectedFlowIndex={0} | |||
selectedLocationIndex={0} | |||
/> | |||
<LocationNavigationKeyboardShortcuts | |||
issue={ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
} | |||
} | |||
/> | |||
</div> | |||
`; | |||
@@ -358,107 +325,5 @@ exports[`should render correctly 2`] = ` | |||
selectedFlowIndex={0} | |||
selectedLocationIndex={0} | |||
/> | |||
<LocationNavigationKeyboardShortcuts | |||
issue={ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [ | |||
Array [ | |||
Object { | |||
"component": "main.js", | |||
"textRange": Object { | |||
"endLine": 2, | |||
"endOffset": 2, | |||
"startLine": 1, | |||
"startOffset": 1, | |||
}, | |||
}, | |||
Object { | |||
"component": "main.js", | |||
"textRange": Object { | |||
"endLine": 2, | |||
"endOffset": 2, | |||
"startLine": 1, | |||
"startOffset": 1, | |||
}, | |||
}, | |||
Object { | |||
"component": "main.js", | |||
"textRange": Object { | |||
"endLine": 2, | |||
"endOffset": 2, | |||
"startLine": 1, | |||
"startOffset": 1, | |||
}, | |||
}, | |||
], | |||
Array [ | |||
Object { | |||
"component": "main.js", | |||
"textRange": Object { | |||
"endLine": 2, | |||
"endOffset": 2, | |||
"startLine": 1, | |||
"startOffset": 1, | |||
}, | |||
}, | |||
Object { | |||
"component": "main.js", | |||
"textRange": Object { | |||
"endLine": 2, | |||
"endOffset": 2, | |||
"startLine": 1, | |||
"startOffset": 1, | |||
}, | |||
}, | |||
], | |||
], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [ | |||
Object { | |||
"component": "main.js", | |||
"textRange": Object { | |||
"endLine": 2, | |||
"endOffset": 2, | |||
"startLine": 1, | |||
"startOffset": 1, | |||
}, | |||
}, | |||
Object { | |||
"component": "main.js", | |||
"textRange": Object { | |||
"endLine": 2, | |||
"endOffset": 2, | |||
"startLine": 1, | |||
"startOffset": 1, | |||
}, | |||
}, | |||
], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
} | |||
} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,56 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<header | |||
className="layout-page-header-panel concise-issues-list-header" | |||
> | |||
<div | |||
className="layout-page-header-panel-inner concise-issues-list-header-inner display-flex-center display-flex-space-between" | |||
> | |||
<PageShortcutsTooltip | |||
leftLabel="issues.to_navigate_back" | |||
metaModifierLabel="issues.to_navigate_issue_locations" | |||
upAndDownLabel="issues.to_select_issues" | |||
/> | |||
</div> | |||
</header> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<header | |||
className="layout-page-header-panel concise-issues-list-header" | |||
> | |||
<div | |||
className="layout-page-header-panel-inner concise-issues-list-header-inner display-flex-center display-flex-space-between" | |||
> | |||
<PageShortcutsTooltip | |||
leftLabel="issues.to_navigate_back" | |||
metaModifierLabel="issues.to_navigate_issue_locations" | |||
upAndDownLabel="issues.to_select_issues" | |||
/> | |||
<i | |||
className="spinner" | |||
/> | |||
</div> | |||
</header> | |||
`; | |||
exports[`should render correctly: with back button 1`] = ` | |||
<header | |||
className="layout-page-header-panel concise-issues-list-header" | |||
> | |||
<div | |||
className="layout-page-header-panel-inner concise-issues-list-header-inner display-flex-center display-flex-space-between" | |||
> | |||
<BackButton | |||
disabled={false} | |||
onClick={[MockFunction]} | |||
/> | |||
<PageShortcutsTooltip | |||
leftLabel="issues.to_navigate_back" | |||
metaModifierLabel="issues.to_navigate_issue_locations" | |||
upAndDownLabel="issues.to_select_issues" | |||
/> | |||
</div> | |||
</header> | |||
`; |
@@ -327,12 +327,3 @@ | |||
.bulk-change-radio-button:hover { | |||
background-color: var(--barBackgroundColor); | |||
} | |||
.navigation-keyboard-shortcuts > span { | |||
background-color: var(--transparentGray); | |||
border-radius: 16px; | |||
display: inline-block; | |||
font-size: var(--smallFontSize); | |||
height: 16px; | |||
padding: calc(var(--gridSize) / 2) var(--gridSize); | |||
} |
@@ -0,0 +1,109 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info 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 classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||
export interface PageShortcutsTooltipProps { | |||
className?: string; | |||
leftAndRightLabel?: string; | |||
leftLabel?: string; | |||
upAndDownLabel?: string; | |||
metaModifierLabel?: string; | |||
} | |||
export default function PageShortcutsTooltip(props: PageShortcutsTooltipProps) { | |||
const { className, leftAndRightLabel, leftLabel, upAndDownLabel, metaModifierLabel } = props; | |||
return ( | |||
<Tooltip | |||
overlay={ | |||
<div className="small nowrap"> | |||
<div> | |||
{upAndDownLabel && ( | |||
<span> | |||
<span className="shortcut-button little-spacer-right">↑</span> | |||
<span className="shortcut-button spacer-right">↓</span> | |||
{upAndDownLabel} | |||
</span> | |||
)} | |||
{leftAndRightLabel && ( | |||
<span className={classNames({ 'big-spacer-left': upAndDownLabel })}> | |||
<span className="shortcut-button little-spacer-right">←</span> | |||
<span className="shortcut-button spacer-right">→</span> | |||
{leftAndRightLabel} | |||
</span> | |||
)} | |||
{leftLabel && ( | |||
<span className={classNames({ 'big-spacer-left': upAndDownLabel })}> | |||
<span className="shortcut-button spacer-right">←</span> | |||
{leftLabel} | |||
</span> | |||
)} | |||
</div> | |||
{metaModifierLabel && ( | |||
<div className="big-spacer-top big-padded-top bordered-top"> | |||
<span className="shortcut-button little-spacer-right">alt</span> | |||
<span className="little-spacer-right">+</span> | |||
<span className="shortcut-button little-spacer-right">↑</span> | |||
<span className="shortcut-button spacer-right">↓</span> | |||
<span className="shortcut-button little-spacer-right">←</span> | |||
<span className="shortcut-button spacer-right">→</span> | |||
{metaModifierLabel} | |||
</div> | |||
)} | |||
</div> | |||
}> | |||
<div | |||
aria-label={` | |||
${translate('shortcuts.on_page.intro')} | |||
${ | |||
upAndDownLabel | |||
? translateWithParameters('shortcuts.on_page.up_down_x', upAndDownLabel) | |||
: '' | |||
} | |||
${ | |||
leftAndRightLabel | |||
? translateWithParameters('shortcuts.on_page.left_right_x', leftAndRightLabel) | |||
: '' | |||
} | |||
${leftLabel ? translateWithParameters('shortcuts.on_page.left_x', leftLabel) : ''} | |||
${ | |||
metaModifierLabel | |||
? translateWithParameters('shortcuts.on_page.meta_x', metaModifierLabel) | |||
: '' | |||
} | |||
`} | |||
className={classNames( | |||
className, | |||
'page-shortcuts-tooltip note text-center display-inline-block' | |||
)}> | |||
<div> | |||
<span className="shortcut-button shortcut-button-tiny">↑</span> | |||
</div> | |||
<div> | |||
<span className="shortcut-button shortcut-button-tiny">←</span> | |||
<span className="shortcut-button shortcut-button-tiny">↓</span> | |||
<span className="shortcut-button shortcut-button-tiny">→</span> | |||
</div> | |||
</div> | |||
</Tooltip> | |||
); | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import PageShortcutsTooltip, { PageShortcutsTooltipProps } from '../PageShortcutsTooltip'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ upAndDownLabel: 'foo', leftAndRightLabel: 'bar' })).toMatchSnapshot( | |||
'with up/down and left/right labels' | |||
); | |||
expect(shallowRender({ leftLabel: 'baz' })).toMatchSnapshot('only left label'); | |||
expect(shallowRender({ metaModifierLabel: 'funky' })).toMatchSnapshot('with meta label'); | |||
}); | |||
function shallowRender(props: Partial<PageShortcutsTooltipProps> = {}) { | |||
return shallow<PageShortcutsTooltipProps>(<PageShortcutsTooltip {...props} />); | |||
} |
@@ -0,0 +1,268 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Tooltip | |||
overlay={ | |||
<div | |||
className="small nowrap" | |||
> | |||
<div /> | |||
</div> | |||
} | |||
> | |||
<div | |||
aria-label=" | |||
shortcuts.on_page.intro | |||
" | |||
className="page-shortcuts-tooltip note text-center display-inline-block" | |||
> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↑ | |||
</span> | |||
</div> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
← | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↓ | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
→ | |||
</span> | |||
</div> | |||
</div> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly: only left label 1`] = ` | |||
<Tooltip | |||
overlay={ | |||
<div | |||
className="small nowrap" | |||
> | |||
<div> | |||
<span | |||
className="" | |||
> | |||
<span | |||
className="shortcut-button spacer-right" | |||
> | |||
← | |||
</span> | |||
baz | |||
</span> | |||
</div> | |||
</div> | |||
} | |||
> | |||
<div | |||
aria-label=" | |||
shortcuts.on_page.intro | |||
shortcuts.on_page.left_x.baz | |||
" | |||
className="page-shortcuts-tooltip note text-center display-inline-block" | |||
> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↑ | |||
</span> | |||
</div> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
← | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↓ | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
→ | |||
</span> | |||
</div> | |||
</div> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly: with meta label 1`] = ` | |||
<Tooltip | |||
overlay={ | |||
<div | |||
className="small nowrap" | |||
> | |||
<div /> | |||
<div | |||
className="big-spacer-top big-padded-top bordered-top" | |||
> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
alt | |||
</span> | |||
<span | |||
className="little-spacer-right" | |||
> | |||
+ | |||
</span> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
↑ | |||
</span> | |||
<span | |||
className="shortcut-button spacer-right" | |||
> | |||
↓ | |||
</span> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
← | |||
</span> | |||
<span | |||
className="shortcut-button spacer-right" | |||
> | |||
→ | |||
</span> | |||
funky | |||
</div> | |||
</div> | |||
} | |||
> | |||
<div | |||
aria-label=" | |||
shortcuts.on_page.intro | |||
shortcuts.on_page.meta_x.funky | |||
" | |||
className="page-shortcuts-tooltip note text-center display-inline-block" | |||
> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↑ | |||
</span> | |||
</div> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
← | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↓ | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
→ | |||
</span> | |||
</div> | |||
</div> | |||
</Tooltip> | |||
`; | |||
exports[`should render correctly: with up/down and left/right labels 1`] = ` | |||
<Tooltip | |||
overlay={ | |||
<div | |||
className="small nowrap" | |||
> | |||
<div> | |||
<span> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
↑ | |||
</span> | |||
<span | |||
className="shortcut-button spacer-right" | |||
> | |||
↓ | |||
</span> | |||
foo | |||
</span> | |||
<span | |||
className="big-spacer-left" | |||
> | |||
<span | |||
className="shortcut-button little-spacer-right" | |||
> | |||
← | |||
</span> | |||
<span | |||
className="shortcut-button spacer-right" | |||
> | |||
→ | |||
</span> | |||
bar | |||
</span> | |||
</div> | |||
</div> | |||
} | |||
> | |||
<div | |||
aria-label=" | |||
shortcuts.on_page.intro | |||
shortcuts.on_page.up_down_x.foo | |||
shortcuts.on_page.left_right_x.bar | |||
" | |||
className="page-shortcuts-tooltip note text-center display-inline-block" | |||
> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↑ | |||
</span> | |||
</div> | |||
<div> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
← | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
↓ | |||
</span> | |||
<span | |||
className="shortcut-button shortcut-button-tiny" | |||
> | |||
→ | |||
</span> | |||
</div> | |||
</div> | |||
</Tooltip> | |||
`; |
@@ -863,6 +863,7 @@ issues.select_all_issues=Select all Issues | |||
issues.issues=issues | |||
issues.to_select_issues=to select issues | |||
issues.to_navigate=to navigate | |||
issues.to_navigate_back=to navigate back | |||
issues.to_navigate_issue_locations=to navigate issue locations | |||
issues.to_switch_flows=to switch flows | |||
issues.new_code=New code | |||
@@ -1316,6 +1317,12 @@ shortcuts.section.rules.return_to_list=return back to the list | |||
shortcuts.section.rules.activate=activate selected rule | |||
shortcuts.section.rules.deactivate=deactivate selected rule | |||
shortcuts.on_page.intro=This page allows you to use the following keyboard shortcuts: | |||
shortcuts.on_page.left_x=Left arrow key: {0} | |||
shortcuts.on_page.left_right_x=Left and right arrow keys: {0} | |||
shortcuts.on_page.up_down_x=Up and down arrow keys: {0} | |||
shortcuts.on_page.meta_x=Alt key + arrow keys: {0} | |||
tutorials.onboarding=Analyze a new project | |||
tutorials.skip=Skip this tutorial | |||
tutorials.finish=Finish this tutorial |