diff --git a/packages/rekit-studio/.eslintrc b/packages/rekit-studio/.eslintrc index 6aa0b18e..41afa62d 100644 --- a/packages/rekit-studio/.eslintrc +++ b/packages/rekit-studio/.eslintrc @@ -35,6 +35,8 @@ "new-cap": 0, "class-methods-use-this": 0, "react/jsx-filename-extension": 0, + "jsx-a11y/no-noninteractive-element-interactions": 0, + "no-return-assign": 0, "react/prefer-stateless-function": 0, "react/forbid-prop-types": 0, "react/no-danger": 0, diff --git a/packages/rekit-studio/package.json b/packages/rekit-studio/package.json index 23544e53..801c63d4 100644 --- a/packages/rekit-studio/package.json +++ b/packages/rekit-studio/package.json @@ -133,8 +133,7 @@ "express": "^4.15.2", "express-history-api-fallback": "^2.0.0", "file-loader": "^1.1.6", - "fuse.js": "^3.2.0", - "fuzzy": "^0.1.3", + "fuzzysort": "^1.1.1", "highlight.js": "^9.11.0", "immutability-helper": "^2.6.4", "isomorphic-fetch": "^2.2.1", diff --git a/packages/rekit-studio/src/features/home/QuickOpen.js b/packages/rekit-studio/src/features/home/QuickOpen.js index 1d08e792..e9e80631 100644 --- a/packages/rekit-studio/src/features/home/QuickOpen.js +++ b/packages/rekit-studio/src/features/home/QuickOpen.js @@ -3,9 +3,9 @@ import PropTypes from 'prop-types'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { Icon } from 'antd'; -// import Fuse from 'fuse.js'; -import fuzzy from 'fuzzy'; -import { getElementData } from './helpers'; +import fuzzysort from 'fuzzysort'; +import scrollIntoView from 'dom-scroll-into-view'; +import history from '../../common/history'; import * as actions from './redux/actions'; export class QuickOpen extends Component { @@ -15,6 +15,7 @@ export class QuickOpen extends Component { }; state = { + selectedIndex: 0, search: '', results: [], visible: false, @@ -27,8 +28,27 @@ export class QuickOpen extends Component { document.body.removeEventListener('keydown', this.handleKeydown); } + highlightMatch(item) { + const indexes = item.indexes.reduce((prev, i) => { + prev[i] = true; + return prev; + }, {}); + + const posLength = item.obj.position.length + 1; + const highlightedName = item.obj.name + .split('') + .map((c, i) => (indexes[posLength + i] ? `${c}` : c)) + .join(''); + + const highlightedPos = item.obj.position + .split('') + .map((c, i) => (indexes[i] ? `${c}` : c)) + .join(''); + return `${highlightedName}${highlightedPos}`; + } + handleKeydown = evt => { - if (evt.key === 'p' && evt.metaKey) { + if (evt.key === 'p' && (evt.metaKey || evt.ctrlKey)) { evt.preventDefault(); evt.stopPropagation(); const newState = { @@ -37,6 +57,7 @@ export class QuickOpen extends Component { if (newState.visible) { newState.search = ''; newState.results = []; + newState.selectedIndex = 0; } this.setState(newState, () => { if (this.inputNode) this.inputNode.focus(); @@ -54,7 +75,6 @@ export class QuickOpen extends Component { }; handleInputChange = evt => { - console.log('searching: ', evt.target.value); this.setState({ search: evt.target.value }); if (evt.target.value.indexOf('|') >= 0) { this.setState({ results: [] }); @@ -64,44 +84,60 @@ export class QuickOpen extends Component { const { elementById } = this.props.home; const list = Object.keys(elementById) .map(k => { - const item = { ...elementById[k] }; - if (/component|action/.test(item.type)) { + const item = { + ...elementById[k], + }; + if (/component|action|misc/.test(item.type)) { item.position = item.file .split('/') .slice(0, -1) .join('/'); + item.toSearch = `${item.position}/${item.name}`; } return item; }) - .filter(e => /component|action/.test(e.type)) - .map(e => `${e.position}/|${e.name}`); - - const options = { pre: '', post: '' }; - const results = fuzzy.filter(evt.target.value, list, options).map(s => { - const arr = s.string.split('|'); - const name = arr.pop(); - return `${name}${arr.join('')}`; - }); - this.setState({ results }); + .filter(e => /component|action|misc/.test(e.type)); + + const results = fuzzysort.go(evt.target.value, list, { key: 'toSearch' }); + this.setState({ results, selectedIndex: 0 }); }; + handleInputKeyDown = evt => { + const scrollToSelected = () => { + scrollIntoView(this.resultsNode.querySelectorAll('li')[this.state.selectedIndex], this.resultsNode, { + onlyScrollIfNeeded: true, + }); + }; + switch (evt.key) { + case 'Enter': + this.handleItemClick(this.state.selectedIndex); + evt.preventDefault(); + break; + case 'ArrowUp': + evt.preventDefault(); + this.setState({ + selectedIndex: this.state.selectedIndex < 1 ? this.state.results.length - 1 : this.state.selectedIndex - 1, + }, scrollToSelected); + + break; + case 'ArrowDown': + evt.preventDefault(); + this.setState({ + selectedIndex: this.state.selectedIndex < this.state.results.length - 1 ? this.state.selectedIndex + 1 : 0, + }, scrollToSelected); + break; + default: + break; + } + }; + + handleItemClick = index => { + this.setState({ visible: false }); + const item = this.state.results[index]; + history.push(`/element/${encodeURIComponent(item.obj.file)}/code`); + }; render() { if (!this.state.visible) return null; - const { elementById } = this.props.home; - - const result = Object.keys(elementById) - .map(k => { - const item = { ...elementById[k] }; - if (/component|action/.test(item.type)) { - item.position = item.file - .split('/') - .slice(0, -1) - .join('/'); - } - return item; - }) - .filter(e => /component|action|misc|file/.test(e.type)) - .slice(0, 15); const iconTypes = { component: 'appstore-o', @@ -117,16 +153,21 @@ export class QuickOpen extends Component { onBlur={this.handleInputBlur} placeholder="Type something to search..." value={this.state.search} + onKeyDown={this.handleInputKeyDown} onChange={this.handleInputChange} /> -
+
(this.resultsNode = n)}> diff --git a/packages/rekit-studio/src/features/home/QuickOpen.less b/packages/rekit-studio/src/features/home/QuickOpen.less index b63b4dfd..12469f52 100644 --- a/packages/rekit-studio/src/features/home/QuickOpen.less +++ b/packages/rekit-studio/src/features/home/QuickOpen.less @@ -38,7 +38,7 @@ left: 50%; transform: translateX(-50%); background-color: #191919; - max-height: 600px; + max-height: 500px; overflow: auto; &::-webkit-scrollbar { width: 0.5rem; diff --git a/packages/rekit-studio/src/features/home/helpers.js b/packages/rekit-studio/src/features/home/helpers.js index 36d53d5f..b3ea9d33 100644 --- a/packages/rekit-studio/src/features/home/helpers.js +++ b/packages/rekit-studio/src/features/home/helpers.js @@ -36,3 +36,14 @@ export function getElementFiles(homeState, file) { if (data.type === 'component') files.style = `src/features/${data.feature}/${data.name}.${homeState.cssExt}`; return files; } + +export function getElementUrl(element) { + switch (element.type) { + case 'component': + case 'action': + case 'misc': + return `/element/${encodeURIComponent(element.file)}/code`; + default: + return '/'; + } +} diff --git a/yarn.lock b/yarn.lock index de893f77..d21d482f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4227,13 +4227,9 @@ functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" -fuse.js@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.2.0.tgz#f0448e8069855bf2a3e683cdc1d320e7e2a07ef4" - -fuzzy@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" +fuzzysort@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-1.1.1.tgz#bf128f1a4cc6e6b7188665ac5676de46a3d81768" gauge@~1.2.5: version "1.2.7"