diff --git a/src/components/Terminal.tsx b/src/components/Terminal.tsx index 059ff564..7d1f0412 100644 --- a/src/components/Terminal.tsx +++ b/src/components/Terminal.tsx @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020 The Pybricks Authors -import { ResizeSensor } from '@blueprintjs/core'; +import { Menu, MenuItem, ResizeSensor } from '@blueprintjs/core'; +// importing this way due to https://github.com/palantir/blueprint/issues/3891 +import { ContextMenuTarget } from '@blueprintjs/core/lib/esnext/components/context-menu/contextMenuTarget'; +import { WithI18nProps, withI18n } from '@shopify/react-i18n'; import React from 'react'; import { connect } from 'react-redux'; import { Observable, Unsubscribe } from 'redux'; @@ -10,6 +13,8 @@ import { FitAddon } from 'xterm-addon-fit'; import { Dispatch } from '../actions'; import { receiveData } from '../actions/terminal'; import { RootState } from '../reducers'; +import { TerminalStringId } from './terminal'; +import en from './terminal.en.json'; import 'xterm/css/xterm.css'; @@ -21,8 +26,9 @@ interface DispatchProps { onData: (data: string) => void; } -type TerminalProps = StateProps & DispatchProps; +type TerminalProps = StateProps & DispatchProps & WithI18nProps; +@ContextMenuTarget class Terminal extends React.Component { private xterm: XTerm; private fitAddon: FitAddon; @@ -89,13 +95,39 @@ class Terminal extends React.Component { render(): JSX.Element { return ( - this.fitAddon.fit()}> -
e.preventDefault()} +
+ this.fitAddon.fit()}> +
+ +
+ ); + } + + renderContextMenu(): JSX.Element { + const { i18n } = this.props; + return ( + + { + const selected = this.xterm.getSelection(); + if (selected) { + navigator.clipboard.writeText(selected); + } + }} + text={i18n.translate(TerminalStringId.Copy)} + icon="duplicate" + label={/mac/i.test(navigator.platform) ? 'Cmd-C' : 'Ctrl-Shift-C'} + disabled={!this.xterm.hasSelection()} /> - + => { + this.xterm.paste(await navigator.clipboard.readText()); + }} + text={i18n.translate(TerminalStringId.Paste)} + icon="clipboard" + label={/mac/i.test(navigator.platform) ? 'Cmd-V' : 'Ctrl-Shift-V'} + /> + ); } } @@ -110,4 +142,7 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ }, }); -export default connect(mapStateToProps, mapDispatchToProps)(Terminal); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withI18n({ id: 'terminal', fallback: en, translations: { en } })(Terminal)); diff --git a/src/components/terminal.en.json b/src/components/terminal.en.json new file mode 100644 index 00000000..f16247a5 --- /dev/null +++ b/src/components/terminal.en.json @@ -0,0 +1,6 @@ +{ + "terminal": { + "copy": "Copy", + "paste": "Paste" + } +} diff --git a/src/components/terminal.test.ts b/src/components/terminal.test.ts new file mode 100644 index 00000000..92705601 --- /dev/null +++ b/src/components/terminal.test.ts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2020 The Pybricks Authors + +import { lookup } from '../../test'; +import { TerminalStringId } from './terminal'; +import en from './terminal.en.json'; + +describe('Ensure .json file has matches for TerminalStringIds', () => { + test.each(Object.values(TerminalStringId))('%s', (id) => { + expect(lookup(en, id)).toBeDefined(); + }); +}); diff --git a/src/components/terminal.ts b/src/components/terminal.ts new file mode 100644 index 00000000..c2dca68c --- /dev/null +++ b/src/components/terminal.ts @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2020 The Pybricks Authors + +export enum TerminalStringId { + Copy = 'terminal.copy', + Paste = 'terminal.paste', +}