diff --git a/package.json b/package.json index 04521ca0bf73..d89eefc14c83 100644 --- a/package.json +++ b/package.json @@ -46,16 +46,17 @@ }, "dependencies": { "@kadira/react-split-pane": "^1.4.0", - "redux": "^3.5.2", - "mantra-core": "^1.6.1", "babel-runtime": "^6.5.0", "deep-equal": "^1.0.1", "fuzzysearch": "^1.0.3", "json-stringify-safe": "^5.0.1", "keycode": "^2.1.1", "lodash.pick": "^4.2.1", + "mantra-core": "^1.6.1", "qs": "^6.2.0", - "react-modal": "^1.2.1" + "react-fuzzy": "^0.2.2", + "react-modal": "^1.2.1", + "redux": "^3.5.2" }, "main": "dist/index.js", "engines": { diff --git a/src/libs/key_events.js b/src/libs/key_events.js index 4a3b674bdfce..95ce146e9c8b 100755 --- a/src/libs/key_events.js +++ b/src/libs/key_events.js @@ -8,6 +8,7 @@ export const features = { ESCAPE: 5, NEXT_STORY: 6, PREV_STORY: 7, + SEARCH: 8, }; export function isModifierPressed(e) { @@ -39,6 +40,9 @@ export default function handle(e) { case keycode('left'): e.preventDefault(); return features.PREV_STORY; + case keycode('P'): + e.preventDefault(); + return features.SEARCH; default: return false; } diff --git a/src/modules/shortcuts/configs/reducers/shortcuts.js b/src/modules/shortcuts/configs/reducers/shortcuts.js index 69cf34515eb1..9febefeb1c67 100755 --- a/src/modules/shortcuts/configs/reducers/shortcuts.js +++ b/src/modules/shortcuts/configs/reducers/shortcuts.js @@ -5,6 +5,7 @@ const defaultState = { goFullScreen: false, showLeftPanel: true, showDownPanel: true, + showSearchBox: false, }; export function keyEventToState(state, event) { @@ -15,6 +16,8 @@ export function keyEventToState(state, event) { return { showDownPanel: !state.showDownPanel }; case features.LEFT_PANEL: return { showLeftPanel: !state.showLeftPanel }; + case features.SEARCH: + return { showSearchBox: !state.showSearchBox }; default: return {}; } diff --git a/src/modules/ui/components/search_box.js b/src/modules/ui/components/search_box.js new file mode 100644 index 000000000000..14f2c98475ed --- /dev/null +++ b/src/modules/ui/components/search_box.js @@ -0,0 +1,99 @@ +import React from 'react'; +import FuzzySearch from 'react-fuzzy'; + +import { features } from '../../../libs/key_events'; +import { baseFonts } from './theme'; + +const searchBoxStyle = { + position: 'absolute', + backgroundColor: '#FFF', + top: '100px', + left: '50%', + marginLeft: '-215px', + ...baseFonts +}; + +const formatStories = function (stories) { + const formattedStories = []; + let i = 0; + stories.forEach((val) => { + formattedStories.push({ + type: 'kind', + value: val.kind, + id: i++, + }); + + val.stories.forEach((story) => { + formattedStories.push({ + type: 'story', + value: story, + id: i++, + kind: val.kind, + }); + }); + }); + + return formattedStories; +}; + +const suggestionTemplate = function (props, state, styles) { + return state.results.map((val, i) => { + const style = state.selectedIndex === i ? styles.selectedResultStyle : styles.resultsStyle; + return ( +
+ {val.value} + + {val.type === 'story' ? `in ${val.kind}` : 'Kind'} + +
+ ); + }); +}; + +export default class SearchBox extends React.Component { + constructor(props) { + super(props); + + this.onSelect = this.onSelect.bind(this); + this.fireOnStory = this.fireOnStory.bind(this); + this.fireOnKind = this.fireOnKind.bind(this); + } + + onSelect(selected) { + const { handleEvent } = this.props; + if (selected.type === 'story') this.fireOnStory(selected.value, selected.kind); + else this.fireOnKind(selected.value); + handleEvent(features.SEARCH); + } + + fireOnKind(kind) { + const { onSelectStory } = this.props; + if (onSelectStory) onSelectStory(kind, null); + } + + fireOnStory(story, kind) { + const { onSelectStory } = this.props; + if (onSelectStory) onSelectStory(kind, story); + } + + render() { + return ( +
+ {this.props.showSearchBox && } +
+ ); + } +} + +SearchBox.propTypes = { + showSearchBox: React.PropTypes.bool.isRequired, + stories: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + onSelectStory: React.PropTypes.func.isRequired, + handleEvent: React.PropTypes.func.isRequired, +}; diff --git a/src/modules/ui/components/shortcuts_help.js b/src/modules/ui/components/shortcuts_help.js index f05d78fe90df..a3253d38f1ca 100755 --- a/src/modules/ui/components/shortcuts_help.js +++ b/src/modules/ui/components/shortcuts_help.js @@ -34,6 +34,11 @@ const modalStyles = { export const Content = () => (

Keyboard Shortcuts

+
+ ⌘ ⇧ P /   + ⌃ ⇧ P + Toggle SearchBox +
⌘ ⇧ F /   ⌃ ⇧ F diff --git a/src/modules/ui/containers/layout.js b/src/modules/ui/containers/layout.js index df8bcfd74551..fc6e44c1c301 100755 --- a/src/modules/ui/containers/layout.js +++ b/src/modules/ui/containers/layout.js @@ -4,8 +4,12 @@ import pick from 'lodash.pick'; import reduxComposer from '../libs/redux_composer'; export const composer = ({ shortcuts }) => { - const data = pick(shortcuts, 'showLeftPanel', 'showDownPanel', 'goFullScreen'); - return data; + return pick( + shortcuts, + 'showLeftPanel', + 'showDownPanel', + 'goFullScreen' + ); }; export default composeAll( diff --git a/src/modules/ui/containers/search_box.js b/src/modules/ui/containers/search_box.js new file mode 100644 index 000000000000..6522602e6131 --- /dev/null +++ b/src/modules/ui/containers/search_box.js @@ -0,0 +1,18 @@ +import SearchBox from '../components/search_box'; +import { useDeps, composeAll } from 'mantra-core'; +import reduxComposer from '../libs/redux_composer'; + +export const composer = ({ api, shortcuts }, { actions }) => { + const actionMap = actions(); + return { + showSearchBox: shortcuts.showSearchBox, + stories: api.stories, + onSelectStory: actionMap.api.selectStory, + handleEvent: actionMap.shortcuts.handleEvent, + }; +}; + +export default composeAll( + reduxComposer(composer), + useDeps() +)(SearchBox); diff --git a/src/modules/ui/routes.js b/src/modules/ui/routes.js index 369301599769..894d55ae5fc0 100755 --- a/src/modules/ui/routes.js +++ b/src/modules/ui/routes.js @@ -4,10 +4,12 @@ import Layout from './containers/layout'; import LeftPanel from './containers/left_panel'; import ActionLogger from './containers/action_logger'; import ShortcutsHelp from './containers/shortcuts_help'; +import SearchBox from './containers/search_box'; export default function (injectDeps, { reduxStore, provider, domNode }) { const InjectedLayout = injectDeps(Layout); const InjectedShortcutsHelp = injectDeps(ShortcutsHelp); + const InjectedSearchBox = injectDeps(SearchBox); // generate preview const Preview = () => { @@ -25,6 +27,7 @@ export default function (injectDeps, { reduxStore, provider, domNode }) { downPanel={() => ()} /> +
); ReactDOM.render(root, domNode);