mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-07-28 04:08:05 +00:00
add context menu
This commit is contained in:
committed by
David Lechner
parent
89f93b0faf
commit
e8ddf09323
@@ -0,0 +1 @@
|
||||
nodejs 10.21.0
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
"@types/react-splitter-layout": "^3.0.0",
|
||||
"@types/redux-logger": "^3.0.8",
|
||||
"@types/web-bluetooth": "^0.0.6",
|
||||
"ace-builds": "^1.4.9",
|
||||
"ace-builds": "^1.4.11",
|
||||
"file-saver": "^2.0.2",
|
||||
"jszip": "^3.4.0",
|
||||
"node-sass": "^4.14.1",
|
||||
|
||||
+80
-13
@@ -1,27 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { ResizeSensor } from '@blueprintjs/core';
|
||||
import { Menu, MenuDivider, 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 { Ace } from 'ace-builds';
|
||||
import { Ace, config } from 'ace-builds';
|
||||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import { connect } from 'react-redux';
|
||||
import { Action, Dispatch } from '../actions';
|
||||
import { setEditSession } from '../actions/editor';
|
||||
import { EditorStringId } from './editor';
|
||||
import en from './editor.en.json';
|
||||
|
||||
import 'ace-builds/src-noconflict/mode-python';
|
||||
import 'ace-builds/src-noconflict/theme-xcode';
|
||||
import 'ace-builds/src-min-noconflict/ext-searchbox';
|
||||
import 'ace-builds/src-min-noconflict/ext-language_tools';
|
||||
import 'ace-builds/src-noconflict/ext-searchbox';
|
||||
import 'ace-builds/src-noconflict/ext-keybinding_menu';
|
||||
import 'ace-builds/src-noconflict/ext-language_tools';
|
||||
|
||||
type DispatchProps = { onSessionChanged: (session?: Ace.EditSession) => void };
|
||||
|
||||
type EditorProps = DispatchProps & WithI18nProps;
|
||||
|
||||
@ContextMenuTarget
|
||||
class Editor extends React.Component<EditorProps> {
|
||||
private editorRef: React.RefObject<AceEditor>;
|
||||
private keyBindings?: Array<{ key: string; command: string }>;
|
||||
|
||||
constructor(props: EditorProps) {
|
||||
super(props);
|
||||
@@ -29,11 +35,11 @@ class Editor extends React.Component<EditorProps> {
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const { i18n, onSessionChanged } = this.props;
|
||||
const editor = this.editorRef.current?.editor;
|
||||
return (
|
||||
<ResizeSensor
|
||||
onResize={(): void => this.editorRef.current?.editor?.resize()}
|
||||
>
|
||||
<div className="h-100">
|
||||
<div className="h-100">
|
||||
<ResizeSensor onResize={(): void => editor?.resize()}>
|
||||
<AceEditor
|
||||
ref={this.editorRef}
|
||||
mode="python"
|
||||
@@ -42,17 +48,30 @@ class Editor extends React.Component<EditorProps> {
|
||||
width="100%"
|
||||
height="100%"
|
||||
focus={true}
|
||||
placeholder={this.props.i18n.translate('editor.placeholder')}
|
||||
placeholder={i18n.translate(EditorStringId.Placeholder)}
|
||||
defaultValue={localStorage.getItem('program') || undefined}
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
setOptions={{
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true,
|
||||
}}
|
||||
onLoad={(e): void => {
|
||||
config.loadModule(
|
||||
'ace/ext/menu_tools/get_editor_keyboard_shortcuts',
|
||||
(m) => {
|
||||
this.keyBindings = m.getEditorKeybordShortcuts(e);
|
||||
},
|
||||
);
|
||||
config.loadModule('ace/ext/keybinding_menu', (m) =>
|
||||
m.init(e),
|
||||
);
|
||||
}}
|
||||
onFocus={(_, e): void => {
|
||||
this.props.onSessionChanged(e?.session);
|
||||
onSessionChanged(e?.session);
|
||||
}}
|
||||
onChange={(v): void => {
|
||||
localStorage.setItem('program', v);
|
||||
}}
|
||||
onChange={(v): void => localStorage.setItem('program', v)}
|
||||
commands={[
|
||||
{
|
||||
name: 'save',
|
||||
@@ -66,8 +85,56 @@ class Editor extends React.Component<EditorProps> {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ResizeSensor>
|
||||
</ResizeSensor>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderContextMenu(): JSX.Element {
|
||||
const { i18n } = this.props;
|
||||
const editor = this.editorRef.current?.editor;
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
onClick={(): void => {
|
||||
const selected = editor?.getSelectedText();
|
||||
if (selected) {
|
||||
navigator.clipboard.writeText(selected);
|
||||
}
|
||||
}}
|
||||
text={i18n.translate(EditorStringId.Copy)}
|
||||
icon="duplicate"
|
||||
label={/mac/i.test(navigator.platform) ? 'Cmd-C' : 'Ctrl-C'}
|
||||
disabled={editor?.getSelection().isEmpty()}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={async (): Promise<void> => {
|
||||
editor?.execCommand(
|
||||
'paste',
|
||||
await navigator.clipboard.readText(),
|
||||
);
|
||||
}}
|
||||
text={i18n.translate(EditorStringId.Paste)}
|
||||
icon="clipboard"
|
||||
label={/mac/i.test(navigator.platform) ? 'Cmd-V' : 'Ctrl-V'}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
onClick={(): void => editor?.undo()}
|
||||
text={i18n.translate(EditorStringId.Undo)}
|
||||
icon="undo"
|
||||
label={this.keyBindings?.find((x) => x.command === 'undo')?.key}
|
||||
disabled={!editor?.session.getUndoManager().canUndo()}
|
||||
/>
|
||||
<MenuItem
|
||||
onClick={(): void => editor?.redo()}
|
||||
text={i18n.translate(EditorStringId.Redo)}
|
||||
icon="redo"
|
||||
label={this.keyBindings?.find((x) => x.command === 'redo')?.key}
|
||||
active
|
||||
disabled={!editor?.session.getUndoManager().canRedo()}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type StatusProps = StateProps;
|
||||
class StatusBar extends React.Component<StatusProps> {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<div className="status-bar">
|
||||
<div className="status-bar" onContextMenu={(e): void => e.preventDefault()}>
|
||||
<ProgressBar
|
||||
className="status-bar-item"
|
||||
value={this.props.progress}
|
||||
|
||||
@@ -63,7 +63,11 @@ class Terminal extends React.Component<TerminalProps> {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<ResizeSensor onResize={(): void => this.fitAddon.fit()}>
|
||||
<div ref={this.terminalRef} className="h-100" />
|
||||
<div
|
||||
ref={this.terminalRef}
|
||||
className="h-100"
|
||||
onContextMenu={(e): void => e.preventDefault()}
|
||||
/>
|
||||
</ResizeSensor>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,11 @@ import StopButton from './StopButton';
|
||||
class Toolbar extends React.Component {
|
||||
render(): JSX.Element {
|
||||
return (
|
||||
<Navbar fixedToTop={true} className="no-box-shadow">
|
||||
<Navbar
|
||||
onContextMenu={(e): void => e.preventDefault()}
|
||||
fixedToTop={true}
|
||||
className="no-box-shadow"
|
||||
>
|
||||
<Navbar.Group>
|
||||
<ButtonGroup>
|
||||
<OpenButton id="open" />
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"editor": {
|
||||
"placeholder": "Write your program here..."
|
||||
"placeholder": "Write your program here...",
|
||||
"copy": "Copy",
|
||||
"paste": "Paste",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import { EditorStringId } from './editor';
|
||||
import en from './editor.en.json';
|
||||
|
||||
function lookup(obj: object, id: string): string | undefined {
|
||||
const value = id
|
||||
.split('.')
|
||||
.reduce((pv, cv) => pv && (pv as Record<string, object>)[cv], obj);
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
describe('Ensure .json file has matches for EditorStringId', () => {
|
||||
test.each(Object.values(EditorStringId))('%s', (id) => {
|
||||
expect(lookup(en, id)).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
export enum EditorStringId {
|
||||
Placeholder = 'editor.placeholder',
|
||||
Copy = 'editor.copy',
|
||||
Paste = 'editor.paste',
|
||||
Undo = 'editor.undo',
|
||||
Redo = 'editor.redo',
|
||||
}
|
||||
+2
-1
@@ -14,7 +14,8 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react",
|
||||
"downlevelIteration": true
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -2053,7 +2053,7 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
|
||||
mime-types "~2.1.24"
|
||||
negotiator "0.6.2"
|
||||
|
||||
ace-builds@^1.4.6, ace-builds@^1.4.9:
|
||||
ace-builds@^1.4.11, ace-builds@^1.4.6:
|
||||
version "1.4.11"
|
||||
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.11.tgz#b1f19a891afcef1d26522473082baf80067e855f"
|
||||
integrity sha512-keACH1d7MvAh72fE/us36WQzOFQPJbHphNpj33pXwVZOM84pTWcdFzIAvngxOGIGLTm7gtUP2eJ4Ku6VaPo8bw==
|
||||
|
||||
Reference in New Issue
Block a user