diff --git a/src/JSONEditor.svelte b/src/JSONEditor.svelte index ee9609e..4065493 100644 --- a/src/JSONEditor.svelte +++ b/src/JSONEditor.svelte @@ -32,9 +32,9 @@ } from './utils/immutabilityHelpers.js' import { compileJSONPointer, parseJSONPointer } from './utils/jsonPointer.js' import { keyComboFromEvent } from './utils/keyBindings.js' - import { flattenSearch, search } from './utils/search.js' + import { search, searchNext, searchPrevious } from './utils/search.js' import { immutableJSONPatch } from './utils/immutableJSONPatch' - import { isEqual, isNumber, initial, last, cloneDeep, first } from 'lodash-es' + import { isNumber, initial, last, cloneDeep } from 'lodash-es' import jump from './assets/jump.js/src/jump.js' import { syncState } from './utils/syncState.js' import { getNextKeys, patchProps } from './utils/updateProps.js' @@ -42,12 +42,13 @@ let divContents let domHiddenInput - export let doc = {} - let state = undefined - let selection = null - export let onChangeJson = () => {} + export let doc = {} + let state = undefined + let searchResult = undefined + + let selection = null let clipboard = null $: hasSelectionContents = selection != null && selection.paths != null $: hasClipboardContents = clipboard != null && selection != null @@ -78,6 +79,7 @@ export function set(newDocument) { doc = newDocument + searchResult = undefined state = undefined history.clear() } @@ -98,6 +100,7 @@ doc = documentPatchResult.json state = patchProps(statePatchResult.json, operations) + searchResult = search(doc, searchText, searchResult) if (newSelection) { selection = newSelection } @@ -222,6 +225,7 @@ if (item) { doc = immutableJSONPatch(doc, item.undo).json state = item.prevState + searchResult = search(doc, searchText, searchResult) selection = item.prevSelection console.log('undo', { item, doc, state, selection }) @@ -237,6 +241,7 @@ if (item) { doc = immutableJSONPatch(doc, item.redo).json state = item.state + searchResult = search(doc, searchText, searchResult) selection = item.selection console.log('redo', { item, doc, state, selection }) @@ -246,49 +251,28 @@ } } - // TODO: refactor the search solution and move it in a separate component - // in: doc, searchText, activeSearchResultIndex - // out: searchResultWithActive - // callbacks: change searchText, change doc, change activeSearchResultIndex - let searchResult - let activeSearchResult = undefined - let activeSearchResultIndex - let flatSearchResult - let searchResultWithActive - $: searchResult = searchText ? search(doc, searchText) : undefined - $: flatSearchResult = flattenSearch(searchResult) - - $: { - if (!activeSearchResult || !existsIn(searchResult, activeSearchResult.path.concat(activeSearchResult.what))) { - activeSearchResult = flatSearchResult[0] - focusActiveSearchResult() - } + function changeSearchText (text) { + searchText = text + searchResult = search(doc, searchText, searchResult) } - $: activeSearchResultIndex = flatSearchResult.findIndex(item => isEqual(item, activeSearchResult)) - $: searchResultWithActive = searchResult - ? activeSearchResult - ? setIn(searchResult, activeSearchResult.path.concat(activeSearchResult.what), 'search active') - : searchResult - : undefined - function nextSearchResult () { - activeSearchResult = flatSearchResult[activeSearchResultIndex + 1] || first(flatSearchResult) - focusActiveSearchResult() + searchResult = searchNext(searchResult) + focusActiveSearchResult(searchResult && searchResult.activeItem) } function previousSearchResult () { - activeSearchResult = flatSearchResult[activeSearchResultIndex - 1] || last(flatSearchResult) - focusActiveSearchResult() + searchResult = searchPrevious(searchResult) + focusActiveSearchResult(searchResult && searchResult.activeItem) } - async function focusActiveSearchResult () { - if (activeSearchResult) { - expandPath(activeSearchResult.path) + async function focusActiveSearchResult (activeItem) { + if (activeItem) { + expandPath(activeItem.path) await tick() - scrollTo(activeSearchResult.path.concat(activeSearchResult.what)) + scrollTo(activeItem.path.concat(activeItem.what)) } } @@ -549,9 +533,9 @@
searchText = text} + resultCount={searchResult ? searchResult.count : 0} + activeIndex={searchResult ? searchResult.activeIndex : 0} + onChange={changeSearchText} onNext={nextSearchResult} onPrevious={previousSearchResult} onClose={() => { @@ -574,7 +558,7 @@ value={doc} path={[]} state={state} - searchResult={searchResultWithActive} + searchResult={searchResult && searchResult.itemsWithActive} onPatch={handlePatch} onUpdateKey={handleUpdateKey} onExpand={handleExpand} diff --git a/src/types.js b/src/types.js index 5ed3693..8ebee0f 100644 --- a/src/types.js +++ b/src/types.js @@ -54,16 +54,24 @@ * paths: Path[], * pathsMap: Object * }} MultiSelection - * - * @typedef {{beforePath: Path}} BeforeSelection - * - * @typedef {{appendPath: Path}} AppendSelection + */ +/** + * @typedef {{beforePath: Path}} BeforeSelection + */ + +/** + * @typedef {{appendPath: Path}} AppendSelection + */ + +/** * @typedef {MultiSelection | BeforeSelection | AppendSelection} Selection */ /** * @typedef {{anchorPath: Path, focusPath: Path}} MultiSelectionSchema - * + */ + +/** * @typedef {MultiSelectionSchema | BeforeSelection | AppendSelection} SelectionSchema */ diff --git a/src/utils/search.js b/src/utils/search.js index 0462bec..b988d49 100644 --- a/src/utils/search.js +++ b/src/utils/search.js @@ -1,9 +1,103 @@ -import { isNumber } from 'lodash-es' +import { isEqual, isNumber } from 'lodash-es' import { STATE_SEARCH_PROPERTY, STATE_SEARCH_VALUE } from '../constants.js' +import { existsIn, setIn } from './immutabilityHelpers.js' import { valueType } from './typeUtils.js' -export function search (doc, searchText) { - return searchRecursive(null, doc, searchText) + +/** + * @typedef {{path: Path, what: Symbol}} SearchItem + */ + +/** + * @typedef {Object} SearchResult + * @property {Object} items + * @property {Object} itemsWithActive + * @property {SearchItem[]} flatItems + * @property {SearchItem} activeItem + * @property {number} activeIndex + * @property {number} count + */ + +/** + * @param {JSON} doc + * @param {string} searchText + * @param {SearchResult} [previousResult] + * @returns {SearchResult} + */ +export function search (doc, searchText, previousResult) { + if (!searchText || searchText === '') { + return undefined + } + + const items = searchRecursive(null, doc, searchText) + + const flatItems = flattenSearch(items) + + const activeItem = (previousResult && previousResult.activeItem && + existsIn(items, previousResult.activeItem.path.concat(previousResult.activeItem.what))) + ? previousResult.activeItem + : flatItems[0] + + const activeIndex = flatItems.findIndex(item => isEqual(item, activeItem)) + + const itemsWithActive = (items && activeItem) + ? setIn(items, activeItem.path.concat(activeItem.what), 'search active') + : items + + return { + items, + itemsWithActive, + flatItems, + count: flatItems.length, + activeItem, + activeIndex + } +} + +/** + * @param {SearchResult} searchResult + * @return {SearchResult} nextResult + */ +export function searchNext (searchResult) { + const nextActiveIndex = searchResult.activeIndex < searchResult.flatItems.length - 1 + ? searchResult.activeIndex + 1 + : 0 + + const nextActiveItem = searchResult.flatItems[nextActiveIndex] + + const itemsWithActive = nextActiveItem + ? setIn(searchResult.items, nextActiveItem.path.concat(nextActiveItem.what), 'search active') + : searchResult.items + + return { + ...searchResult, + itemsWithActive, + activeItem: nextActiveItem, + activeIndex: nextActiveIndex + } +} + +/** + * @param {SearchResult} searchResult + * @return {SearchResult} nextResult + */ +export function searchPrevious (searchResult) { + const previousActiveIndex = searchResult.activeIndex > 0 + ? searchResult.activeIndex - 1 + : searchResult.flatItems.length - 1 + + const previousActiveItem = searchResult.flatItems[previousActiveIndex] + + const itemsWithActive = previousActiveItem + ? setIn(searchResult.items, previousActiveItem.path.concat(previousActiveItem.what), 'search active') + : searchResult.items + + return { + ...searchResult, + itemsWithActive, + activeItem: previousActiveItem, + activeIndex: previousActiveIndex + } } function searchRecursive (key, doc, searchText) { @@ -37,7 +131,7 @@ function searchRecursive (key, doc, searchText) { return results } -export function flattenSearch (searchResult) { +function flattenSearch (searchResult) { const resultArray = [] function _flattenSearch (value, path) {