Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 5282612

Browse files
committed
feat: TopTab buttons should have improved tooltips
Fixes #6806 Also adds a ctrlOrMeta+U keyboard shortcut for new split
1 parent 338ae2f commit 5282612

File tree

12 files changed

+226
-31
lines changed

12 files changed

+226
-31
lines changed

packages/core/src/main/menu.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ const isDev = false
3232
*/
3333
const newTab = () => tellRendererToExecute('tab new')
3434

35+
/**
36+
* tell the current window to open a new split
37+
*
38+
*/
39+
const newSplit = () => tellRendererToExecute('split')
40+
3541
/**
3642
* tell the current window to close the current tab
3743
*
@@ -100,6 +106,11 @@ export const install = (createWindow: (executeThisArgvPlease?: string[]) => void
100106
click: () => newTab(),
101107
accelerator: 'CommandOrControl+T'
102108
},
109+
{
110+
label: 'New Split',
111+
click: () => newSplit(),
112+
accelerator: 'CommandOrControl+U'
113+
},
103114
{
104115
label: 'Open',
105116
click: () => open(createWindow),

packages/core/src/webapp/electron-events.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ debug('loading')
2020

2121
import { IpcRenderer } from 'electron'
2222

23+
import { pexecInCurrentTab } from './tab'
2324
import { inElectron, Media, setMedia } from '../core/capabilities'
2425

2526
/**
@@ -38,8 +39,7 @@ const listenForRemoteEvents = (ipcRenderer: IpcRenderer) => {
3839

3940
ipcRenderer.on('/repl/qexec', async (event, { command }) => {
4041
debug('remote qexec', command)
41-
const { qexec } = await import('../repl/exec')
42-
return qexec(command)
42+
return pexecInCurrentTab(command, undefined, false, true)
4343
})
4444
}
4545
}

plugins/plugin-client-common/i18n/resources_en_US.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@
3434
"Show X": "Show {0}",
3535
"Hide X": "Hide {0}",
3636
"Last updated": "Last updated {0}",
37-
"New Tab": "Open a new tab",
37+
"New Tab": "Open a new tab {0}",
3838
"Created a split": "Created a split",
3939
"Created a split with inverted colors": "Created a split with inverted colors",
4040
"Duration": "Duration: {0}",
4141
"Cold Start": "Cold start delay: {0}. {1}",
42-
"Close this tab": "Close this tab",
42+
"Close this tab": "Close this tab {0}",
4343
"You clicked to show the": "You clicked to show the",
4444
"Drilling down to show the": "Drilling down to show the",
4545
"concurrencyInDurationSplit": "{0}-way concurrency with execution time {1}",
@@ -48,7 +48,7 @@
4848
"Rerun": "Rerun",
4949
"Re-execute this command": "Re-execute this command",
5050
"Insert Command": "Insert Command",
51-
"Split the Terminal": "Split the Terminal",
51+
"Split the terminal": "Split the terminal {0}",
5252
"Output has been pinned to a watch pane": "Output has been **pinned** to a watch pane",
5353
"No more splits allowed": "No more splits allowed",
5454
"No more pins allowed": "You have reached the maximum number of pinned views. Consider either closing one, or re-executing the command in a new tab.",

plugins/plugin-client-common/src/components/Client/StatusStripe/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { eventBus, pexecInCurrentTab, i18n, StatusStripeChangeEvent } from '@kui
2222

2323
import Settings from './Settings'
2424
import Icons from '../../spi/Icons'
25+
import Tooltip from '../../spi/Tooltip'
2526
import MeterWidgets from './MeterWidgets'
2627
const Markdown = React.lazy(() => import('../../Content/Markdown'))
2728
import '../../../../web/scss/components/StatusStripe/StatusStripe.scss'
@@ -37,6 +38,8 @@ function hasType(evt: Partial<StatusStripeChangeEvent>): evt is Pick<Required<St
3738
}
3839

3940
export default class StatusStripe extends React.PureComponent<Props, State> {
41+
private readonly helpRef = React.createRef<HTMLAnchorElement>()
42+
4043
public constructor(props: Props) {
4144
super(props)
4245
eventBus.onStatusStripeChangeRequest(this.onChangeRequest.bind(this))
@@ -125,11 +128,14 @@ export default class StatusStripe extends React.PureComponent<Props, State> {
125128
id="help-button"
126129
aria-label="Help"
127130
tabIndex={0}
128-
title={strings('Click for help')}
131+
ref={this.helpRef}
129132
onClick={() => this.doAbout()}
130133
>
131134
<Icons icon="Help" />
132135
</a>
136+
<Tooltip reference={this.helpRef} position="top">
137+
{strings('Click for help')}
138+
</Tooltip>
133139
</div>
134140
</div>
135141
</React.Suspense>

plugins/plugin-client-common/src/components/Client/TopTabStripe/NewTabButton.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import React from 'react'
1818
import { i18n } from '@kui-shell/core'
1919

2020
import Icons from '../../spi/Icons'
21+
import Tooltip from '../../spi/Tooltip'
22+
import ctrlOrMeta from './ctrlOrMeta'
2123

2224
const strings = i18n('plugin-client-common')
2325

@@ -26,19 +28,39 @@ interface Props {
2628
}
2729

2830
export default class NewTabButton extends React.PureComponent<Props> {
29-
public render() {
31+
private readonly ref = React.createRef<HTMLAnchorElement>()
32+
private readonly _onNewTab = () => this.props.onNewTab()
33+
34+
private tooltip() {
35+
return (
36+
<Tooltip reference={this.ref} position="bottom">
37+
{strings('New Tab', ctrlOrMeta('T'))}
38+
</Tooltip>
39+
)
40+
}
41+
42+
private button() {
3043
return (
3144
<a
3245
href="#"
3346
className="kui--tab-navigatable kui--top-tab-button kui-new-tab"
3447
id="new-tab-button"
3548
aria-label="Open a new tab"
3649
tabIndex={0}
37-
title={strings('New Tab')}
38-
onClick={() => this.props.onNewTab()}
50+
ref={this.ref}
51+
onClick={this._onNewTab}
3952
>
4053
<Icons icon="Add" className="kui-new-tab__plus" />
4154
</a>
4255
)
4356
}
57+
58+
public render() {
59+
return (
60+
<React.Fragment>
61+
{this.button()}
62+
{this.tooltip()}
63+
</React.Fragment>
64+
)
65+
}
4466
}

plugins/plugin-client-common/src/components/Client/TopTabStripe/SplitTerminalButton.tsx

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { i18n, pexecInCurrentTab } from '@kui-shell/core'
1919

2020
import Icons from '../../spi/Icons'
2121
import KuiContext from '../context'
22+
import ctrlOrMeta from './ctrlOrMeta'
23+
import Tooltip from '../../spi/Tooltip'
2224

2325
const strings = i18n('plugin-client-common')
2426

@@ -28,22 +30,42 @@ const strings = i18n('plugin-client-common')
2830
*
2931
*/
3032
export default class SplitTerminalButton extends React.PureComponent {
33+
private readonly ref = React.createRef<HTMLAnchorElement>()
34+
private readonly _onSplit = () => pexecInCurrentTab('split', undefined, false, true)
35+
36+
private tooltip() {
37+
return (
38+
<Tooltip reference={this.ref} position="bottom">
39+
{strings('Split the terminal', ctrlOrMeta('U'))}
40+
</Tooltip>
41+
)
42+
}
43+
44+
private button() {
45+
return (
46+
<a
47+
href="#"
48+
className="kui--tab-navigatable kui--top-tab-button"
49+
id="kui--split-terminal-button"
50+
aria-label="Split terminal"
51+
tabIndex={0}
52+
ref={this.ref}
53+
onClick={this._onSplit}
54+
>
55+
<Icons icon="Split" />
56+
</a>
57+
)
58+
}
59+
3160
public render() {
3261
return (
3362
<KuiContext.Consumer>
3463
{config =>
3564
config.splitTerminals && (
36-
<a
37-
href="#"
38-
className="kui--tab-navigatable kui--top-tab-button"
39-
id="kui--split-terminal-button"
40-
aria-label="Split terminal"
41-
tabIndex={0}
42-
title={strings('Split the Terminal')}
43-
onClick={() => pexecInCurrentTab('split', undefined, false, true)}
44-
>
45-
<Icons icon="Split" />
46-
</a>
65+
<React.Fragment>
66+
{this.button()}
67+
{this.tooltip()}
68+
</React.Fragment>
4769
)
4870
}
4971
</KuiContext.Consumer>

plugins/plugin-client-common/src/components/Client/TopTabStripe/Tab.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import {
2929
import { NavItem } from '@patternfly/react-core'
3030

3131
import Icons from '../../spi/Icons'
32+
import Tooltip from '../../spi/Tooltip'
33+
import ctrlOrMeta from './ctrlOrMeta'
34+
3235
const Markdown = React.lazy(() => import('../../Content/Markdown'))
3336

3437
const strings = i18n('plugin-core-support')
@@ -56,6 +59,8 @@ interface State {
5659
}
5760

5861
export default class Tab extends React.PureComponent<Props, State> {
62+
private readonly closeTabRef = React.createRef<HTMLDivElement>()
63+
5964
private onCommandStart: (evt: Event) => void
6065
private onCommandComplete: (evt: Event) => void
6166
private onThemeChange: ({ themeModel: Theme }) => void
@@ -187,17 +192,22 @@ export default class Tab extends React.PureComponent<Props, State> {
187192
</div>
188193

189194
{this.props.closeable && (
190-
<div
191-
className="kui--tab-close"
192-
title={strings2('Close this tab')}
193-
onClick={evt => {
194-
evt.stopPropagation()
195-
evt.preventDefault()
196-
this.props.onCloseTab(this.props.idx)
197-
}}
198-
>
199-
<Icons icon="WindowClose" focusable="false" preserveAspectRatio="xMidYMid meet" aria-hidden="true" />
200-
</div>
195+
<React.Fragment>
196+
<div
197+
className="kui--tab-close"
198+
ref={this.closeTabRef}
199+
onClick={evt => {
200+
evt.stopPropagation()
201+
evt.preventDefault()
202+
this.props.onCloseTab(this.props.idx)
203+
}}
204+
>
205+
<Icons icon="WindowClose" focusable="false" preserveAspectRatio="xMidYMid meet" aria-hidden="true" />
206+
</div>
207+
<Tooltip reference={this.closeTabRef} position="bottom">
208+
{strings2('Close this tab', ctrlOrMeta('W'))}
209+
</Tooltip>
210+
</React.Fragment>
201211
)}
202212
</NavItem>
203213
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2021 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export default function ctrlOrMeta(c: string) {
18+
return (process.platform === 'darwin' ? '\u2318' : '\u2303') + c
19+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2021 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from 'react'
18+
import Props from '../model'
19+
20+
import { Tooltip } from '@patternfly/react-core'
21+
import '../../../../../web/scss/components/Tooltip/PatternFly.scss'
22+
23+
export default function PatternFlyTooltip(props: Props): React.ReactElement {
24+
return (
25+
<Tooltip
26+
className="kui--tooltip"
27+
entryDelay={200}
28+
reference={props.reference}
29+
content={props.children}
30+
position={props.position}
31+
isContentLeftAligned
32+
/>
33+
)
34+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2021 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from 'react'
18+
import Props from './model'
19+
20+
const PatternFly4 = React.lazy(() => import('./impl/PatternFly'))
21+
22+
export { Props }
23+
24+
export default function TooltipSpi(props: Props): React.ReactElement {
25+
return <PatternFly4 {...props} />
26+
}

0 commit comments

Comments
 (0)