@jupyterlab/shortcuts-extension 4.0.0-alpha.2 → 4.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/lib/components/ShortcutInput.d.ts +78 -0
  2. package/lib/components/ShortcutInput.js +348 -0
  3. package/lib/components/ShortcutInput.js.map +1 -0
  4. package/lib/components/ShortcutItem.d.ts +62 -0
  5. package/lib/components/ShortcutItem.js +282 -0
  6. package/lib/components/ShortcutItem.js.map +1 -0
  7. package/lib/components/ShortcutList.d.ts +23 -0
  8. package/lib/components/ShortcutList.js +19 -0
  9. package/lib/components/ShortcutList.js.map +1 -0
  10. package/lib/components/ShortcutTitleItem.d.ts +9 -0
  11. package/lib/components/ShortcutTitleItem.js +16 -0
  12. package/lib/components/ShortcutTitleItem.js.map +1 -0
  13. package/lib/components/ShortcutUI.d.ts +56 -0
  14. package/lib/components/ShortcutUI.js +363 -0
  15. package/lib/components/ShortcutUI.js.map +1 -0
  16. package/lib/components/TopNav.d.ts +48 -0
  17. package/lib/components/TopNav.js +92 -0
  18. package/lib/components/TopNav.js.map +1 -0
  19. package/lib/components/index.d.ts +2 -0
  20. package/lib/components/index.js +6 -0
  21. package/lib/components/index.js.map +1 -0
  22. package/lib/index.js +48 -5
  23. package/lib/index.js.map +1 -1
  24. package/lib/renderer.d.ts +4 -0
  25. package/lib/renderer.js +10 -0
  26. package/lib/renderer.js.map +1 -0
  27. package/package.json +34 -15
  28. package/src/components/ShortcutInput.tsx +501 -0
  29. package/src/components/ShortcutItem.tsx +491 -0
  30. package/src/components/ShortcutList.tsx +61 -0
  31. package/src/components/ShortcutTitleItem.tsx +33 -0
  32. package/src/components/ShortcutUI.tsx +512 -0
  33. package/src/components/TopNav.tsx +193 -0
  34. package/src/components/index.ts +7 -0
  35. package/src/index.ts +297 -0
  36. package/src/renderer.tsx +13 -0
  37. package/style/base.css +393 -0
  38. package/style/index.css +10 -0
  39. package/style/index.js +10 -0
@@ -0,0 +1,491 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { TranslationBundle } from '@jupyterlab/translation';
7
+ import { Platform } from '@lumino/domutils';
8
+ import * as React from 'react';
9
+ import {
10
+ ErrorObject,
11
+ ShortcutInput,
12
+ ShortcutObject,
13
+ TakenByObject
14
+ } from './ShortcutInput';
15
+ import { IShortcutUIexternal } from './TopNav';
16
+
17
+ /** Props for ShortcutItem component */
18
+ export interface IShortcutItemProps {
19
+ shortcut: ShortcutObject | ErrorObject;
20
+ handleUpdate: Function;
21
+ resetShortcut: Function;
22
+ deleteShortcut: Function;
23
+ showSelectors: boolean;
24
+ keyBindingsUsed: { [index: string]: TakenByObject };
25
+ sortConflict: Function;
26
+ clearConflicts: Function;
27
+ contextMenu: Function;
28
+ external: IShortcutUIexternal;
29
+ }
30
+
31
+ /** State for ShortcutItem component */
32
+ export interface IShortcutItemState {
33
+ displayNewInput: boolean;
34
+ displayReplaceInputLeft: boolean;
35
+ displayReplaceInputRight: boolean;
36
+ numShortcuts: number;
37
+ }
38
+
39
+ enum ShortCutLocation {
40
+ Left,
41
+ Right
42
+ }
43
+
44
+ /** Describe commands that are used by shortcuts */
45
+ function getCommands(trans: TranslationBundle): {
46
+ [key: string]: { commandId: string; label: string; caption: string };
47
+ } {
48
+ return {
49
+ shortcutEditLeft: {
50
+ commandId: 'shortcutui:EditLeft',
51
+ label: trans.__('Edit First'),
52
+ caption: trans.__('Edit existing shortcut')
53
+ },
54
+ shortcutEditRight: {
55
+ commandId: 'shortcutui:EditRight',
56
+ label: trans.__('Edit Second'),
57
+ caption: trans.__('Edit existing shortcut')
58
+ },
59
+ shortcutEdit: {
60
+ commandId: 'shortcutui:Edit',
61
+ label: trans.__('Edit'),
62
+ caption: trans.__('Edit existing shortcut')
63
+ },
64
+ shortcutAddNew: {
65
+ commandId: 'shortcutui:AddNew',
66
+ label: trans.__('Add'),
67
+ caption: trans.__('Add new shortcut')
68
+ },
69
+ shortcutAddAnother: {
70
+ commandId: 'shortcutui:AddAnother',
71
+ label: trans.__('Add'),
72
+ caption: trans.__('Add another shortcut')
73
+ },
74
+ shortcutReset: {
75
+ commandId: 'shortcutui:Reset',
76
+ label: trans.__('Reset'),
77
+ caption: trans.__('Reset shortcut back to default')
78
+ }
79
+ };
80
+ }
81
+
82
+ /** React component for each command shortcut item */
83
+ export class ShortcutItem extends React.Component<
84
+ IShortcutItemProps,
85
+ IShortcutItemState
86
+ > {
87
+ constructor(props: IShortcutItemProps) {
88
+ super(props);
89
+
90
+ this._commands = getCommands(props.external.translator.load('jupyterlab'));
91
+
92
+ this.state = {
93
+ displayNewInput: false,
94
+ displayReplaceInputLeft: false,
95
+ displayReplaceInputRight: false,
96
+ numShortcuts: Object.keys(this.props.shortcut.keys).filter(
97
+ key => this.props.shortcut.keys[key][0] !== ''
98
+ ).length
99
+ };
100
+ }
101
+
102
+ /** Toggle display state of input box */
103
+ private toggleInputNew = (): void => {
104
+ this.setState({
105
+ displayNewInput: !this.state.displayNewInput
106
+ });
107
+ };
108
+
109
+ private toggleInputReplaceLeft = (): void => {
110
+ this.setState({
111
+ displayReplaceInputLeft: !this.state.displayReplaceInputLeft
112
+ });
113
+ };
114
+
115
+ private toggleInputReplaceRight = (): void => {
116
+ this.setState({
117
+ displayReplaceInputRight: !this.state.displayReplaceInputRight
118
+ });
119
+ };
120
+
121
+ private addCommandIfNeeded = (command: any, action: () => void): void => {
122
+ const key =
123
+ this.props.shortcut.commandName + '_' + this.props.shortcut.selector;
124
+
125
+ if (!this.props.external.hasCommand(command.commandId + key)) {
126
+ this.props.external.addCommand(command.commandId + key, {
127
+ label: command.label,
128
+ caption: command.caption,
129
+ execute: action
130
+ });
131
+ }
132
+ };
133
+
134
+ private handleRightClick = (e: any): void => {
135
+ this.addCommandIfNeeded(this._commands.shortcutEdit, () =>
136
+ this.toggleInputReplaceLeft()
137
+ );
138
+ this.addCommandIfNeeded(this._commands.shortcutEditLeft, () =>
139
+ this.toggleInputReplaceLeft()
140
+ );
141
+ this.addCommandIfNeeded(this._commands.shortcutEditRight, () =>
142
+ this.toggleInputReplaceRight()
143
+ );
144
+ this.addCommandIfNeeded(this._commands.shortcutAddNew, () =>
145
+ this.toggleInputNew()
146
+ );
147
+ this.addCommandIfNeeded(this._commands.shortcutAddAnother, () =>
148
+ this.toggleInputNew()
149
+ );
150
+ this.addCommandIfNeeded(this._commands.shortcutReset, () =>
151
+ this.props.resetShortcut(this.props.shortcut)
152
+ );
153
+
154
+ const key =
155
+ this.props.shortcut.commandName + '_' + this.props.shortcut.selector;
156
+
157
+ this.setState(
158
+ {
159
+ numShortcuts: Object.keys(this.props.shortcut.keys).filter(
160
+ key => this.props.shortcut.keys[key][0] !== ''
161
+ ).length
162
+ },
163
+ () => {
164
+ let commandList: any[] = [];
165
+ if (this.state.numShortcuts == 2) {
166
+ commandList = commandList.concat([
167
+ this._commands.shortcutEditLeft.commandId + key,
168
+ this._commands.shortcutEditRight.commandId + key
169
+ ]);
170
+ } else if (this.state.numShortcuts == 1) {
171
+ commandList = commandList.concat([
172
+ this._commands.shortcutEdit.commandId + key,
173
+ this._commands.shortcutAddAnother.commandId + key
174
+ ]);
175
+ } else {
176
+ commandList = commandList.concat([
177
+ this._commands.shortcutAddNew.commandId + key
178
+ ]);
179
+ }
180
+
181
+ if (this.props.shortcut.source === 'Custom') {
182
+ commandList = commandList.concat([
183
+ this._commands.shortcutReset.commandId + key
184
+ ]);
185
+ }
186
+
187
+ this.props.contextMenu(e, commandList);
188
+ }
189
+ );
190
+ };
191
+
192
+ /** Transform special key names into unicode characters */
193
+ toSymbols = (value: string): string => {
194
+ return value.split(' ').reduce((result, key) => {
195
+ if (key === 'Ctrl') {
196
+ return (result + ' ⌃').trim();
197
+ } else if (key === 'Alt') {
198
+ return (result + ' ⌥').trim();
199
+ } else if (key === 'Shift') {
200
+ return (result + ' ⇧').trim();
201
+ } else if (key === 'Accel' && Platform.IS_MAC) {
202
+ return (result + ' ⌘').trim();
203
+ } else if (key === 'Accel') {
204
+ return (result + ' ⌃').trim();
205
+ } else {
206
+ return (result + ' ' + key).trim();
207
+ }
208
+ }, '');
209
+ };
210
+
211
+ getErrorRow(): JSX.Element {
212
+ const trans = this.props.external.translator.load('jupyterlab');
213
+
214
+ return (
215
+ <div className="jp-Shortcuts-Row">
216
+ <div className="jp-Shortcuts-ConflictContainer">
217
+ <div className="jp-Shortcuts-ErrorMessage">
218
+ {trans.__(
219
+ 'Shortcut already in use by %1. Overwrite it?',
220
+ (this.props.shortcut as ErrorObject).takenBy.takenByLabel
221
+ )}
222
+ </div>
223
+ <div className="jp-Shortcuts-ErrorButton">
224
+ <button>{trans.__('Cancel')}</button>
225
+ <button
226
+ id="no-blur"
227
+ onClick={() => {
228
+ document.getElementById('overwrite')?.click();
229
+ }}
230
+ >
231
+ {trans.__('Overwrite')}
232
+ </button>
233
+ </div>
234
+ </div>
235
+ </div>
236
+ );
237
+ }
238
+
239
+ getCategoryCell(): JSX.Element {
240
+ return (
241
+ <div className="jp-Shortcuts-Cell">{this.props.shortcut.category}</div>
242
+ );
243
+ }
244
+
245
+ getLabelCell(): JSX.Element {
246
+ return (
247
+ <div className="jp-Shortcuts-Cell">
248
+ <div className="jp-label">{this.props.shortcut.label}</div>
249
+ </div>
250
+ );
251
+ }
252
+
253
+ getResetShortCutLink(): JSX.Element {
254
+ const trans = this.props.external.translator.load('jupyterlab');
255
+ return (
256
+ <a
257
+ className="jp-Shortcuts-Reset"
258
+ onClick={() => this.props.resetShortcut(this.props.shortcut)}
259
+ >
260
+ {trans.__('Reset')}
261
+ </a>
262
+ );
263
+ }
264
+
265
+ getSourceCell(): JSX.Element {
266
+ return (
267
+ <div className="jp-Shortcuts-Cell">
268
+ <div className="jp-Shortcuts-SourceCell">
269
+ {this.props.shortcut.source}
270
+ </div>
271
+ {this.props.shortcut.source === 'Custom' && this.getResetShortCutLink()}
272
+ </div>
273
+ );
274
+ }
275
+
276
+ getOptionalSelectorCell(): JSX.Element | null {
277
+ return this.props.showSelectors ? (
278
+ <div className="jp-Shortcuts-Cell">
279
+ <div className="jp-selector">{this.props.shortcut.selector}</div>
280
+ </div>
281
+ ) : null;
282
+ }
283
+
284
+ getClassNameForShortCuts(nonEmptyKeys: string[]): string {
285
+ const classes = ['jp-Shortcuts-ShortcutCell'];
286
+ switch (nonEmptyKeys.length) {
287
+ case 1:
288
+ classes.push('jp-Shortcuts-SingleCell');
289
+ break;
290
+ case 0:
291
+ classes.push('jp-Shortcuts-EmptyCell');
292
+ break;
293
+ }
294
+ return classes.join(' ');
295
+ }
296
+
297
+ getToggleInputReplaceMethod(location: ShortCutLocation): () => void {
298
+ switch (location) {
299
+ case ShortCutLocation.Left:
300
+ return this.toggleInputReplaceLeft;
301
+ case ShortCutLocation.Right:
302
+ return this.toggleInputReplaceRight;
303
+ }
304
+ }
305
+
306
+ getDisplayReplaceInput(location: ShortCutLocation): boolean {
307
+ switch (location) {
308
+ case ShortCutLocation.Left:
309
+ return this.state.displayReplaceInputLeft;
310
+ case ShortCutLocation.Right:
311
+ return this.state.displayReplaceInputRight;
312
+ }
313
+ }
314
+
315
+ getOrDiplayIfNeeded(nonEmptyKeys: string[]): JSX.Element {
316
+ const trans = this.props.external.translator.load('jupyterlab');
317
+ return (
318
+ <div
319
+ className={
320
+ nonEmptyKeys.length == 2 || this.state.displayNewInput
321
+ ? 'jp-Shortcuts-OrTwo'
322
+ : 'jp-Shortcuts-Or'
323
+ }
324
+ id={
325
+ nonEmptyKeys.length == 2
326
+ ? 'secondor'
327
+ : this.state.displayReplaceInputLeft
328
+ ? 'noor'
329
+ : 'or'
330
+ }
331
+ >
332
+ {trans.__('or')}
333
+ </div>
334
+ );
335
+ }
336
+
337
+ getShortCutAsInput(key: string, location: ShortCutLocation): JSX.Element {
338
+ return (
339
+ <ShortcutInput
340
+ handleUpdate={this.props.handleUpdate}
341
+ deleteShortcut={this.props.deleteShortcut}
342
+ toggleInput={this.getToggleInputReplaceMethod(location)}
343
+ shortcut={this.props.shortcut}
344
+ shortcutId={key}
345
+ toSymbols={this.toSymbols}
346
+ keyBindingsUsed={this.props.keyBindingsUsed}
347
+ sortConflict={this.props.sortConflict}
348
+ clearConflicts={this.props.clearConflicts}
349
+ displayInput={this.getDisplayReplaceInput(location)}
350
+ newOrReplace={'replace'}
351
+ placeholder={this.toSymbols(this.props.shortcut.keys[key].join(', '))}
352
+ translator={this.props.external.translator}
353
+ />
354
+ );
355
+ }
356
+
357
+ getShortCutForDisplayOnly(key: string): JSX.Element[] {
358
+ return this.props.shortcut.keys[key].map(
359
+ (keyBinding: string, index: number) => (
360
+ <div className="jp-Shortcuts-ShortcutKeysContainer" key={index}>
361
+ <div className="jp-Shortcuts-ShortcutKeys">
362
+ {this.toSymbols(keyBinding)}
363
+ </div>
364
+ {index + 1 < this.props.shortcut.keys[key].length ? (
365
+ <div className="jp-Shortcuts-Comma">,</div>
366
+ ) : null}
367
+ </div>
368
+ )
369
+ );
370
+ }
371
+
372
+ isLocationBeingEdited(location: ShortCutLocation): boolean {
373
+ return (
374
+ (location === ShortCutLocation.Left &&
375
+ this.state.displayReplaceInputLeft) ||
376
+ (location === ShortCutLocation.Right &&
377
+ this.state.displayReplaceInputRight)
378
+ );
379
+ }
380
+
381
+ getLocationFromIndex(index: number): ShortCutLocation {
382
+ return index === 0 ? ShortCutLocation.Left : ShortCutLocation.Right;
383
+ }
384
+
385
+ getDivForKey(
386
+ index: number,
387
+ key: string,
388
+ nonEmptyKeys: string[]
389
+ ): JSX.Element {
390
+ const location = this.getLocationFromIndex(index);
391
+ return (
392
+ <div
393
+ className="jp-Shortcuts-ShortcutContainer"
394
+ key={this.props.shortcut.id + '_' + index}
395
+ onClick={this.getToggleInputReplaceMethod(location)}
396
+ >
397
+ {this.isLocationBeingEdited(location)
398
+ ? this.getShortCutAsInput(key, location)
399
+ : this.getShortCutForDisplayOnly(key)}
400
+ {location === ShortCutLocation.Left &&
401
+ this.getOrDiplayIfNeeded(nonEmptyKeys)}
402
+ </div>
403
+ );
404
+ }
405
+
406
+ getAddLink(): JSX.Element {
407
+ const trans = this.props.external.translator.load('jupyterlab');
408
+ return (
409
+ <a
410
+ className={!this.state.displayNewInput ? 'jp-Shortcuts-Plus' : ''}
411
+ onClick={() => {
412
+ this.toggleInputNew(), this.props.clearConflicts();
413
+ }}
414
+ id="add-link"
415
+ >
416
+ {trans.__('Add')}
417
+ </a>
418
+ );
419
+ }
420
+
421
+ getInputBoxWhenToggled(): JSX.Element {
422
+ return this.state.displayNewInput ? (
423
+ <ShortcutInput
424
+ handleUpdate={this.props.handleUpdate}
425
+ deleteShortcut={this.props.deleteShortcut}
426
+ toggleInput={this.toggleInputNew}
427
+ shortcut={this.props.shortcut}
428
+ shortcutId=""
429
+ toSymbols={this.toSymbols}
430
+ keyBindingsUsed={this.props.keyBindingsUsed}
431
+ sortConflict={this.props.sortConflict}
432
+ clearConflicts={this.props.clearConflicts}
433
+ displayInput={this.state.displayNewInput}
434
+ newOrReplace={'new'}
435
+ placeholder={''}
436
+ translator={this.props.external.translator}
437
+ />
438
+ ) : (
439
+ <div />
440
+ );
441
+ }
442
+
443
+ getShortCutsCell(nonEmptyKeys: string[]): JSX.Element {
444
+ return (
445
+ <div className="jp-Shortcuts-Cell">
446
+ <div className={this.getClassNameForShortCuts(nonEmptyKeys)}>
447
+ {nonEmptyKeys.map((key, index) =>
448
+ this.getDivForKey(index, key, nonEmptyKeys)
449
+ )}
450
+ {nonEmptyKeys.length === 1 &&
451
+ !this.state.displayNewInput &&
452
+ !this.state.displayReplaceInputLeft &&
453
+ this.getAddLink()}
454
+ {nonEmptyKeys.length === 0 &&
455
+ !this.state.displayNewInput &&
456
+ this.getAddLink()}
457
+ {this.getInputBoxWhenToggled()}
458
+ </div>
459
+ </div>
460
+ );
461
+ }
462
+
463
+ render(): JSX.Element {
464
+ const nonEmptyKeys = Object.keys(this.props.shortcut.keys).filter(
465
+ (key: string) => this.props.shortcut.keys[key][0] !== ''
466
+ );
467
+ if (this.props.shortcut.id === 'error_row') {
468
+ return this.getErrorRow();
469
+ } else {
470
+ return (
471
+ <div
472
+ className="jp-Shortcuts-Row"
473
+ onContextMenu={e => {
474
+ e.persist();
475
+ this.handleRightClick(e);
476
+ }}
477
+ >
478
+ {this.getCategoryCell()}
479
+ {this.getLabelCell()}
480
+ {this.getShortCutsCell(nonEmptyKeys)}
481
+ {this.getSourceCell()}
482
+ {this.getOptionalSelectorCell()}
483
+ </div>
484
+ );
485
+ }
486
+ }
487
+
488
+ private _commands: {
489
+ [key: string]: { commandId: string; label: string; caption: string };
490
+ };
491
+ }
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import { ShortcutObject, TakenByObject } from './ShortcutInput';
8
+ import { ShortcutItem } from './ShortcutItem';
9
+ import { IShortcutUIexternal } from './TopNav';
10
+
11
+ const TOPNAV_HEIGHT: number = 115;
12
+
13
+ /** Props for ShortcutList component */
14
+ export interface IShortcutListProps {
15
+ shortcuts: ShortcutObject[];
16
+ handleUpdate: Function;
17
+ resetShortcut: Function;
18
+ deleteShortcut: Function;
19
+ showSelectors: boolean;
20
+ keyBindingsUsed: { [index: string]: TakenByObject };
21
+ sortConflict: Function;
22
+ clearConflicts: Function;
23
+ height: number;
24
+ contextMenu: Function;
25
+ external: IShortcutUIexternal;
26
+ }
27
+
28
+ /** React component for list of shortcuts */
29
+ export class ShortcutList extends React.Component<IShortcutListProps> {
30
+ render(): JSX.Element {
31
+ return (
32
+ <div
33
+ className="jp-Shortcuts-ShortcutListContainer"
34
+ style={{
35
+ height: `${this.props.height - TOPNAV_HEIGHT}px`
36
+ }}
37
+ id="shortcutListContainer"
38
+ >
39
+ <div className="jp-Shortcuts-ShortcutList">
40
+ {this.props.shortcuts.map((shortcut: ShortcutObject) => {
41
+ return (
42
+ <ShortcutItem
43
+ key={shortcut.commandName + '_' + shortcut.selector}
44
+ resetShortcut={this.props.resetShortcut}
45
+ shortcut={shortcut}
46
+ handleUpdate={this.props.handleUpdate}
47
+ deleteShortcut={this.props.deleteShortcut}
48
+ showSelectors={this.props.showSelectors}
49
+ keyBindingsUsed={this.props.keyBindingsUsed}
50
+ sortConflict={this.props.sortConflict}
51
+ clearConflicts={this.props.clearConflicts}
52
+ contextMenu={this.props.contextMenu}
53
+ external={this.props.external}
54
+ />
55
+ );
56
+ })}
57
+ </div>
58
+ </div>
59
+ );
60
+ }
61
+ }
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { caretDownEmptyThinIcon } from '@jupyterlab/ui-components';
7
+ import * as React from 'react';
8
+
9
+ export interface IShortcutTitleItemProps {
10
+ title: string;
11
+ updateSort: Function;
12
+ active: string;
13
+ }
14
+
15
+ export class ShortcutTitleItem extends React.Component<IShortcutTitleItemProps> {
16
+ render(): JSX.Element {
17
+ return (
18
+ <div
19
+ className={
20
+ this.props.title.toLowerCase() === this.props.active
21
+ ? 'jp-Shortcuts-Header jp-Shortcuts-CurrentHeader'
22
+ : 'jp-Shortcuts-Header'
23
+ }
24
+ onClick={() => this.props.updateSort(this.props.title.toLowerCase())}
25
+ >
26
+ {this.props.title}
27
+ <caretDownEmptyThinIcon.react
28
+ className={'jp-Shortcuts-SortButton jp-ShortcutTitleItem-sortButton'}
29
+ />
30
+ </div>
31
+ );
32
+ }
33
+ }