Create global state containing expanded state

This commit is contained in:
josdejong 2020-05-23 20:45:28 +02:00
parent d2a84b29fe
commit d04c94a1c6
7 changed files with 82 additions and 21 deletions

View File

@ -1,4 +1,5 @@
<script> <script>
import { EXPANDED_PROPERTY } from './constants.js'
import SearchBox from './SearchBox.svelte' import SearchBox from './SearchBox.svelte'
import Icon from 'svelte-awesome' import Icon from 'svelte-awesome'
import { faSearch, faUndo, faRedo } from '@fortawesome/free-solid-svg-icons' import { faSearch, faUndo, faRedo } from '@fortawesome/free-solid-svg-icons'
@ -14,6 +15,10 @@
export let onChangeJson = () => { export let onChangeJson = () => {
} }
let state = {
[EXPANDED_PROPERTY]: true
}
let showSearch = true // FIXME: change to false let showSearch = true // FIXME: change to false
let searchText = '' let searchText = ''
@ -33,10 +38,19 @@
history.clear() history.clear()
} }
function applyPatch (operations) {
const patchResult = immutableJSONPatch(json, operations)
json = patchResult.json
state = immutableJSONPatch(state, operations).json
return patchResult
}
export function patch(operations) { export function patch(operations) {
console.log('patch', operations) console.log('patch', operations)
const patchResult = immutableJSONPatch(json, operations) const patchResult = applyPatch(operations)
history.add({ history.add({
undo: patchResult.revert, undo: patchResult.revert,
@ -123,7 +137,7 @@
if (history.getState().canUndo) { if (history.getState().canUndo) {
const item = history.undo() const item = history.undo()
if (item) { if (item) {
json = immutableJSONPatch(json, item.undo).json applyPatch(item.undo)
emitOnChange() emitOnChange()
} }
} }
@ -133,12 +147,21 @@
if (history.getState().canRedo) { if (history.getState().canRedo) {
const item = history.redo() const item = history.redo()
if (item) { if (item) {
json = immutableJSONPatch(json, item.redo).json applyPatch(item.redo)
emitOnChange() emitOnChange()
} }
} }
} }
/**
* Toggle expanded state of a node
* @param {Path} path
* @param {boolean} expanded
*/
function handleExpand (path, expanded) {
state = setIn(state, path.concat(EXPANDED_PROPERTY), expanded)
}
function handleKeyDown (event) { function handleKeyDown (event) {
const combo = keyComboFromEvent(event) const combo = keyComboFromEvent(event)
@ -228,10 +251,11 @@
<div class="contents"> <div class="contents">
<Node <Node
value={json} value={json}
state={state}
searchResult={searchResultWithActive} searchResult={searchResultWithActive}
expanded={true}
onChangeKey={handleChangeKey} onChangeKey={handleChangeKey}
onPatch={handlePatch} onPatch={handlePatch}
onExpand={handleExpand}
getParentPath={getPath} getParentPath={getPath}
/> />
<div class='bottom'></div> <div class='bottom'></div>

View File

@ -1,8 +1,12 @@
<script> <script>
import {
EXPANDED_PROPERTY,
SEARCH_PROPERTY,
SEARCH_VALUE
} from './constants.js'
import { getPlainText, setPlainText } from './utils/domUtils.js' import { getPlainText, setPlainText } from './utils/domUtils.js'
import Icon from 'svelte-awesome' import Icon from 'svelte-awesome'
import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons' import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'
import { SEARCH_PROPERTY, SEARCH_VALUE } from './utils/search.js'
import classnames from 'classnames' import classnames from 'classnames'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import { findUniqueName } from './utils/stringUtils.js' import { findUniqueName } from './utils/stringUtils.js'
@ -12,13 +16,16 @@
export let key = undefined // only applicable for object properties export let key = undefined // only applicable for object properties
export let value export let value
export let state
export let searchResult export let searchResult
export let onPatch export let onPatch
export let onChangeKey export let onChangeKey
export let expanded = false export let onExpand
export let getParentPath export let getParentPath
$: expanded = state && state[EXPANDED_PROPERTY]
function getPath () { function getPath () {
return key !== undefined return key !== undefined
? getParentPath().concat(key) ? getParentPath().concat(key)
@ -93,7 +100,7 @@
} }
function toggle () { function toggle () {
expanded = !expanded onExpand(getPath(), !expanded)
} }
function updateKey () { function updateKey () {
@ -238,7 +245,7 @@
<div class="delimiter">[</div> <div class="delimiter">[</div>
{:else} {:else}
<div class="delimiter">[</div> <div class="delimiter">[</div>
<button class="tag" on:click={() => expanded = true}>{value.length} items</button> <button class="tag" on:click={() => onExpand(getPath(), true)}>{value.length} items</button>
<div class="delimiter">]</div> <div class="delimiter">]</div>
{/if} {/if}
</div> </div>
@ -248,9 +255,11 @@
<svelte:self <svelte:self
key={index} key={index}
value={item} value={item}
state={state && state[index]}
searchResult={searchResult ? searchResult[index] : undefined} searchResult={searchResult ? searchResult[index] : undefined}
onChangeKey={handleChangeKey} onChangeKey={handleChangeKey}
onPatch={onPatch} onPatch={onPatch}
onExpand={onExpand}
getParentPath={getPath} getParentPath={getPath}
/> />
{/each} {/each}
@ -288,7 +297,7 @@
<span class="delimiter">&#123;</span> <span class="delimiter">&#123;</span>
{:else} {:else}
<span class="delimiter"> &#123;</span> <span class="delimiter"> &#123;</span>
<button class="tag" on:click={() => expanded = true}>{props.length} props</button> <button class="tag" on:click={() => onExpand(getPath(), true)}>{props.length} props</button>
<span class="delimiter">}</span> <span class="delimiter">}</span>
{/if} {/if}
</div> </div>
@ -298,9 +307,11 @@
<svelte:self <svelte:self
key={prop.key} key={prop.key}
value={value[prop.key]} value={value[prop.key]}
state={state && state[prop.key]}
searchResult={searchResult ? searchResult[prop.key] : undefined} searchResult={searchResult ? searchResult[prop.key] : undefined}
onChangeKey={handleChangeKey} onChangeKey={handleChangeKey}
onPatch={onPatch} onPatch={onPatch}
onExpand={onExpand}
getParentPath={getPath} getParentPath={getPath}
/> />
{/each} {/each}

5
src/constants.js Normal file
View File

@ -0,0 +1,5 @@
export const EXPANDED_PROPERTY = '$jse:expanded'
export const SEARCH_PROPERTY = '$jse:search:property'
export const SEARCH_VALUE = '$jse:search:value'

View File

@ -25,7 +25,7 @@
*/ */
/** /**
* @typedef {string[]} Path * @typedef {<string | number>[]} Path
*/ */
/** /**

View File

@ -70,12 +70,22 @@ export function setIn (object, path, value) {
return value return value
} }
if (!isObjectOrArray(object)) { // TODO: rewrite this function into a while loop instead of recursive (like getIn)
throw new Error('Path does not exist')
}
const key = path[0] const key = path[0]
const updatedValue = setIn(object[key], path.slice(1), value)
// TODO: check whether path is string -> object expected, path is number -> array expected
const updatedValue = setIn(object && object[key], path.slice(1), value)
if (!isObjectOrArray(object)) {
const newObject = typeof key === 'number'
? []
: {}
newObject[key] = updatedValue
return newObject
}
if (object[key] === updatedValue) { if (object[key] === updatedValue) {
// return original object unchanged when the new value is identical to the old one // return original object unchanged when the new value is identical to the old one
return object return object

View File

@ -59,19 +59,32 @@ test('setIn basic', () => {
expect(obj).not.toBe(updated) expect(obj).not.toBe(updated)
}) })
test('setIn non existing path', () => { test('setIn non existing path should create the path (1)', () => {
const obj = {} const obj = {}
expect(() => setIn(obj, ['a', 'b', 'c'], 4)).toThrow(/Path does not exist/) expect(setIn(obj, ['a', 'b', 'c'], 4)).toEqual({
a: {
b: {
c: 4
}
}
})
}) })
test('setIn replace value with object should throw an exception', () => { test('setIn on non-existing path should create the path (2)', () => {
const obj = { const obj = {
a: 42, a: 42,
d: 3 d: 3
} }
expect(() => setIn(obj, ['a', 'b', 'c'], 4)).toThrow(/Path does not exist/) expect(setIn(obj, ['a', 'b', 'c'], 4)).toEqual({
a: {
b: {
c: 4
}
},
d: 3
})
}) })
test('setIn replace value inside nested array', () => { test('setIn replace value inside nested array', () => {

View File

@ -1,8 +1,6 @@
import { SEARCH_PROPERTY, SEARCH_VALUE } from '../constants.js'
import { valueType } from './typeUtils.js' import { valueType } from './typeUtils.js'
export const SEARCH_PROPERTY = '$jse:search:property'
export const SEARCH_VALUE = '$jse:search:value'
export function search (key, value, searchText) { export function search (key, value, searchText) {
let results = undefined let results = undefined