codekingpro/portable-devtools
114k
1import { Notificar } from 'notificar';2import { FileEntry, Directory, FileType } from 'react-aspen';3import { FileTreeXEvent, IFileTreeXHandle } from '../types';4 5export class KeyboardHotkeys {6 private hotkeyActions = {7 'ArrowUp': () => this.jumpToPrevItem(),8 'ArrowDown': () => this.jumpToNextItem(),9 'ArrowRight': () => this.expandOrJumpToFirstChild(),10 'ArrowLeft': () => this.collapseOrJumpToFirstParent(),11 'Space': () => this.toggleDirectoryExpand(),12 'Enter': () => this.selectFileOrToggleDirState(),13 'Home': () => this.jumpToFirstItem(),14 'End': () => this.jumpToLastItem(),15 'Escape': () => this.resetSteppedOrSelectedItem(),16 'Ctrl+KeyC': () => this.copyEntry(),17 };18 19 constructor(private readonly fileTreeX: IFileTreeXHandle, private readonly events: Notificar<FileTreeXEvent>) { }20 21 public handleKeyDown = (ev: React.KeyboardEvent) => {22 if (!this.fileTreeX.hasDirectFocus()) {23 return false;24 }25 let { code } = ev.nativeEvent;26 27 if((ev.nativeEvent.ctrlKey || ev.nativeEvent.metaKey) && ev.nativeEvent.key !== 'Control') {28 code = `Ctrl+${code}`;29 }30 if (code in this.hotkeyActions) {31 ev.preventDefault();32 this.hotkeyActions[code]();33 return true;34 }35 };36 37 private jumpToFirstItem = (): void => {38 const { root } = this.fileTreeX.getModel();39 this.fileTreeX.setActiveFile(root.getFileEntryAtIndex(0), true);40 };41 42 private jumpToLastItem = (): void => {43 const { root } = this.fileTreeX.getModel();44 this.fileTreeX.setActiveFile(root.getFileEntryAtIndex(root.branchSize - 1), true);45 };46 47 private jumpToNextItem = (): void => {48 const { root } = this.fileTreeX.getModel();49 let currentPseudoActive = this.fileTreeX.getActiveFile();50 if (!currentPseudoActive) {51 const selectedFile = this.fileTreeX.getActiveFile();52 if (selectedFile) {53 currentPseudoActive = selectedFile;54 } else {55 return this.jumpToFirstItem();56 }57 }58 const idx = root.getIndexAtFileEntry(currentPseudoActive);59 if (idx + 1 > root.branchSize) {60 return this.jumpToFirstItem();61 } else if (idx > -1) {62 this.fileTreeX.setActiveFile(root.getFileEntryAtIndex(idx + 1), true);63 }64 };65 66 private jumpToPrevItem = (): void => {67 const { root } = this.fileTreeX.getModel();68 let currentPseudoActive = this.fileTreeX.getActiveFile();69 if (!currentPseudoActive) {70 const selectedFile = this.fileTreeX.getActiveFile();71 if (selectedFile) {72 currentPseudoActive = selectedFile;73 } else {74 return this.jumpToLastItem();75 }76 }77 const idx = root.getIndexAtFileEntry(currentPseudoActive);78 if (idx - 1 < 0) {79 return this.jumpToLastItem();80 } else if (idx > -1) {81 this.fileTreeX.setActiveFile(root.getFileEntryAtIndex(idx - 1), true);82 }83 };84 85 private expandOrJumpToFirstChild(): void {86 const currentPseudoActive = this.fileTreeX.getActiveFile();87 if (currentPseudoActive && currentPseudoActive.type === FileType.Directory) {88 if ((currentPseudoActive as Directory).expanded) {89 return this.jumpToNextItem();90 } else {91 this.fileTreeX.openDirectory(currentPseudoActive as Directory);92 }93 }94 }95 96 private collapseOrJumpToFirstParent(): void {97 const currentPseudoActive = this.fileTreeX.getActiveFile();98 if (currentPseudoActive) {99 if (currentPseudoActive.type === FileType.Directory && (currentPseudoActive as Directory).expanded) {100 return this.fileTreeX.closeDirectory(currentPseudoActive as Directory);101 }102 this.fileTreeX.setActiveFile(currentPseudoActive.parent, true);103 }104 }105 106 private selectFileOrToggleDirState = (): void => {107 const currentPseudoActive = this.fileTreeX.getActiveFile();108 if (!currentPseudoActive) { return; }109 if (currentPseudoActive.type === FileType.Directory) {110 this.fileTreeX.toggleDirectory(currentPseudoActive as Directory);111 } else if (currentPseudoActive.type === FileType.File) {112 this.fileTreeX.setActiveFile(currentPseudoActive as FileEntry, true);113 }114 };115 116 private toggleDirectoryExpand = (): void => {117 const currentPseudoActive = this.fileTreeX.getActiveFile();118 if (!currentPseudoActive) { return; }119 if (currentPseudoActive.type === FileType.Directory) {120 this.fileTreeX.toggleDirectory(currentPseudoActive as Directory);121 }122 };123 124 private resetSteppedOrSelectedItem = (): void => {125 const currentPseudoActive = this.fileTreeX.getActiveFile();126 if (currentPseudoActive) {127 return this.resetSteppedItem();128 }129 this.fileTreeX.setActiveFile(null);130 };131 132 private resetSteppedItem = () => {133 this.fileTreeX.setActiveFile(null);134 };135 136 private copyEntry = () => {137 const currentPseudoActive = this.fileTreeX.getActiveFile();138 this.events.dispatch(FileTreeXEvent.onTreeEvents, null, 'copied', currentPseudoActive);139 };140}141 