From 78468f8a5d2b26b60bddc28613aafab799979e06 Mon Sep 17 00:00:00 2001 From: Kushagra Gour Date: Tue, 23 Oct 2018 19:56:33 +0530 Subject: [PATCH] Command pallete: first draft! --- src/commandPaletteService.js | 29 +++++++++ src/commands.js | 22 +++++++ src/components/CommandPalette.jsx | 96 +++++++++++++++++++++++++++++ src/components/ContentWrapFiles.jsx | 19 ++++++ src/components/app.jsx | 26 +++++++- src/components/common.jsx | 6 ++ 6 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 src/commandPaletteService.js create mode 100644 src/commands.js create mode 100644 src/components/CommandPalette.jsx diff --git a/src/commandPaletteService.js b/src/commandPaletteService.js new file mode 100644 index 0000000..22607b0 --- /dev/null +++ b/src/commandPaletteService.js @@ -0,0 +1,29 @@ +import { deferred } from './deferred'; +import { log } from 'util'; + +export const SWITCH_FILE_EVENT = 'switchFileEvent'; +export const OPEN_SAVED_CREATIONS_EVENT = 'openSavedCreationsEvent'; +export const SAVE_EVENT = 'saveEvent'; + +export const commandPaletteService = { + subscriptions: {}, + subscribe(eventName, callback) { + console.log('subscribed for ', eventName); + this.subscriptions[eventName] = this.subscriptions[eventName] || []; + this.subscriptions[eventName].push(callback); + return () => { + console.log('Unsubscribing ', eventName); + this.subscriptions[eventName].splice( + this.subscriptions[eventName].indexOf(callback), + 1 + ); + }; + }, + publish(eventName, ...args) { + console.log('published ', eventName, args); + const callbacks = this.subscriptions[eventName] || []; + callbacks.forEach(callback => { + callback.apply(null, args); + }); + } +}; diff --git a/src/commands.js b/src/commands.js new file mode 100644 index 0000000..226be3c --- /dev/null +++ b/src/commands.js @@ -0,0 +1,22 @@ +import { + OPEN_SAVED_CREATIONS_EVENT, + SAVE_EVENT +} from './commandPaletteService'; + +export const commands = [ + { + name: 'Open Creation', + event: OPEN_SAVED_CREATIONS_EVENT, + keyboardShortcut: 'Cmd+O' + }, + { + name: 'Save Creation', + event: SAVE_EVENT, + keyboardShortcut: 'Cmd+S' + }, + { + name: 'Add Library', + run: '', + keyboardShortcut: 'Cmd+F' + } +]; diff --git a/src/components/CommandPalette.jsx b/src/components/CommandPalette.jsx new file mode 100644 index 0000000..f48a9fb --- /dev/null +++ b/src/components/CommandPalette.jsx @@ -0,0 +1,96 @@ +import { h, Component } from 'preact'; +import Modal from './Modal'; +import { AutoFocusInput } from './common'; +import { commands } from '../commands'; +import { + commandPaletteService, + SWITCH_FILE_EVENT +} from '../commandPaletteService'; +import { FileIcon } from './FileIcon'; + +function getFolder(filePath) { + const split = filePath.split('/'); + if (split.length > 1) { + split.length = split.length - 1; + return split.join('/'); + } + return ''; +} +function Row({ item, onClick }) { + return ( +
  • + +
  • + ); +} +export class CommandPalette extends Component { + state = { list: [], search: '' }; + componentDidUpdate(previousProps) { + if (this.props.show && !previousProps.show) { + this.state.search = ''; + + this.isCommandMode = this.props.isCommandMode; + if (this.isCommandMode) { + this.setState({ search: '>' }); + } + + this.setState({ + list: this.getFilteredList() + }); + } + } + + getFilteredList(search = '') { + const list = this.isCommandMode ? commands : this.props.files; + return list.filter( + item => + item.name + .toLowerCase() + .indexOf(this.isCommandMode ? search.substr(1) : search) !== -1 + ); + } + + inputHandler(e) { + const search = e.target.value; + this.setState({ search }); + if (search.indexOf('>') === 0) { + this.isCommandMode = true; + } + this.setState({ + list: this.getFilteredList(search) + }); + } + optionClickHandler(option) { + commandPaletteService.publish( + option.path ? SWITCH_FILE_EVENT : option.event, + option + ); + this.props.closeHandler(); + } + render() { + return ( + + +
      + {this.state.list.map(item => ( + + ))} +
    +
    + ); + } +} diff --git a/src/components/ContentWrapFiles.jsx b/src/components/ContentWrapFiles.jsx index e59d555..06fd13a 100644 --- a/src/components/ContentWrapFiles.jsx +++ b/src/components/ContentWrapFiles.jsx @@ -17,6 +17,10 @@ import 'codemirror/mode/meta'; import { deferred } from '../deferred'; import { SidePane } from './SidePane'; import { Console } from './Console'; +import { + commandPaletteService, + SWITCH_FILE_EVENT +} from '../commandPaletteService'; const minCodeWrapSize = 33; @@ -105,6 +109,21 @@ export default class ContentWrapFiles extends Component { } componentDidMount() { this.props.onRef(this); + this.commandPaletteSubscriptions = []; + this.commandPaletteSubscriptions.push( + commandPaletteService.subscribe(SWITCH_FILE_EVENT, file => { + const targetFile = getFileFromPath( + this.props.currentItem.files, + file.path + ); + if (targetFile.file) { + this.fileSelectHandler(targetFile.file); + } + }) + ); + } + componentWillUnmount() { + this.commandPaletteSubscriptions.forEach(unsubscribeFn => unsubscribeFn()); } getEditorOptions(fileName = '') { diff --git a/src/components/app.jsx b/src/components/app.jsx index ade9d86..b80591c 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -55,6 +55,11 @@ import { Js13KModal } from './Js13KModal'; import { CreateNewModal } from './CreateNewModal'; import { Icons } from './Icons'; import JSZip from 'jszip'; +import { CommandPalette } from './CommandPalette'; +import { + commandPaletteService, + OPEN_SAVED_CREATIONS_EVENT +} from '../commandPaletteService'; if (module.hot) { require('preact/debug'); @@ -84,7 +89,8 @@ export default class App extends Component { isAskToImportModalOpen: false, isOnboardModalOpen: false, isJs13KModalOpen: false, - isCreateNewModalOpen: false + isCreateNewModalOpen: false, + isCommandPaletteOpen: false }; this.state = { isSavedItemPaneOpen: false, @@ -532,6 +538,12 @@ export default class App extends Component { // We might be listening on keydown for some input inside the app. In that case // we don't want this to trigger which in turn focuses back the last editor. this.closeSavedItemsPane(); + } else if ((event.ctrlKey || event.metaKey) && event.keyCode === 80) { + this.setState({ + isCommandPaletteOpen: true, + isCommandPaletteInCommandMode: !!event.shiftKey + }); + event.preventDefault(); } }); @@ -548,6 +560,10 @@ export default class App extends Component { } } }); + + commandPaletteService.subscribe(OPEN_SAVED_CREATIONS_EVENT, () => { + this.openSavedItemsPane(); + }); } closeAllOverlays() { @@ -1553,6 +1569,14 @@ export default class App extends Component { onTemplateSelect={this.templateSelectHandler.bind(this)} /> + this.setState({ isCommandPaletteOpen: false })} + files={linearizeFiles(this.state.currentItem.files || [])} + isCommandMode={this.state.isCommandPaletteInCommandMode} + closeHandler={() => this.setState({ isCommandPaletteOpen: false })} + /> +