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 })}
+ />
+
;
}
+
+export function AutoFocusInput(props) {
+ return (
+ el && setTimeout(() => el.focus(), 100)} {...props} />
+ );
+}