diff --git a/src/JSONEditor.svelte b/src/JSONEditor.svelte index a6ce91f..1e1d9fb 100644 --- a/src/JSONEditor.svelte +++ b/src/JSONEditor.svelte @@ -8,10 +8,11 @@ } from './constants.js' import SearchBox from './SearchBox.svelte' import Icon from 'svelte-awesome' - import { faSearch, faUndo, faRedo } from '@fortawesome/free-solid-svg-icons' + import { faCut, faCopy, faPaste, faSearch, faUndo, faRedo } from '@fortawesome/free-solid-svg-icons' import { createHistory } from './history.js' import Node from './JSONNode.svelte' import { expandSelection } from './selection.js' + import { singleton } from './singleton.js' import { existsIn, getIn, @@ -22,42 +23,41 @@ import { keyComboFromEvent } from './utils/keyBindings.js' import { flattenSearch, search } from './utils/search.js' import { immutableJSONPatch } from './utils/immutableJSONPatch' - import { isEqual, isNumber, initial, last } from 'lodash-es' + import { isEqual, isNumber, initial, last, cloneDeep } from 'lodash-es' import jump from './assets/jump.js/src/jump.js' import { syncState } from './utils/syncState.js' let divContents - beforeUpdate(() => { - console.time('render') - }) - afterUpdate(() => { - console.timeEnd('render') - }) + // beforeUpdate(() => { + // console.time('render') + // }) + // afterUpdate(() => { + // console.timeEnd('render') + // }) export let doc = {} let state = undefined let selection = null let selectionMap = {} - $: { - selectionMap = {} - if (selection != null) { - selection.forEach(path => { - selectionMap[compileJSONPointer(path)] = true - }) - } - } - - $: console.log('selectionMap', selectionMap) + // $: { + // selectionMap = {} + // if (selection != null) { + // selection.forEach(path => { + // selectionMap[compileJSONPointer(path)] = true + // }) + // } + // } export let onChangeJson = () => {} - $: { - console.time('syncState') - state = syncState(doc, state, [], (path) => path.length < 1) - console.timeEnd('syncState') - } + let clipboard = null + $: canCut = selection != null + $: canCopy = selection != null + $: canPaste = clipboard != null + + $: state = syncState(doc, state, [], (path) => path.length < 1) let showSearch = false let searchText = '' @@ -134,6 +134,23 @@ } } + function handleCut() { + // FIXME: implement + } + + function handleCopy() { + if (selection) { + clipboard = selection.map(path => cloneDeep(getIn(doc, path))) + console.log('copied to clipboard', clipboard) + } + } + + function handlePaste() { + if (clipboard) { + console.log('focus', singleton.focus) + } + } + function handleUndo() { if (history.getState().canUndo) { const item = history.undo() @@ -271,9 +288,36 @@ state = setIn(state, path.concat(STATE_LIMIT), limit) } - function handleSelect (anchorPath, focusPath) { - if (anchorPath && focusPath) { - selection = expandSelection(doc, state, anchorPath, focusPath) + /** + * @param {Selection} newSelection + */ + function handleSelect (newSelection) { + if (newSelection) { + const { anchorPath, focusPath, beforePath, afterPath } = newSelection + + if (beforePath) { + selection = { + beforePath + } + } else if (afterPath) { + selection = { + afterPath + } + } else if (anchorPath && focusPath) { + // TODO: move expandSelection to JSONNode? (must change expandSelection to support relative path) + const paths = expandSelection(doc, state, anchorPath, focusPath) + + const pathsMap = {} + paths.forEach(path => { + pathsMap[compileJSONPointer(path)] = true + }) + + selection = { + paths: pathsMap + } + } else { + console.error('Unknown type of selection', newSelection) + } } else { selection = null } @@ -344,6 +388,33 @@
diff --git a/src/JSONNode.scss b/src/JSONNode.scss index 835cdd7..600fb68 100644 --- a/src/JSONNode.scss +++ b/src/JSONNode.scss @@ -1,6 +1,7 @@ @import './styles.scss'; .json-node { + position: relative; font-family: $font-family; font-size: $font-size; color: $black; @@ -17,6 +18,46 @@ background-color: $selection-background; } + $selector-height: 8px; // must be about half a line height + + .props, + .items { + position: relative; + } + + .before-node-selector, + .after-node-selector { + position: absolute; + left: 0; + right: $input-padding; + height: $selector-height; + box-sizing: border-box; + + .selector { + margin-top: $selector-height / 2; + } + + &:hover { + .selector { + border: 1px dashed $selection-background; + } + } + + &.selected { + .selector { + border: 1px dashed $theme-color; + } + } + } + + .before-node-selector { + top: -$selector-height/2 - 1px; + } + + .after-node-selector { + bottom: -$selector-height/2 - 1px; + } + .header { position: relative; } diff --git a/src/JSONNode.svelte b/src/JSONNode.svelte index ef4293c..8f73514 100644 --- a/src/JSONNode.svelte +++ b/src/JSONNode.svelte @@ -27,7 +27,7 @@ export let onExpand export let onLimit export let onSelect - export let selectionMap + export let selection $: expanded = state && state[STATE_EXPANDED] $: limit = state && state[STATE_LIMIT] @@ -158,7 +158,7 @@ debouncedUpdateValue() } - function handleValueBlur (event) { + function handleValueBlur () { // handle any pending changes still waiting in the debounce function debouncedUpdateValue.flush() @@ -206,6 +206,9 @@ singleton.mousedown = true singleton.selectionAnchor = path singleton.selectionFocus = null + + // TODO: select the clicked node directly + event.stopPropagation() } @@ -229,7 +232,11 @@ if (!isEqual(path, singleton.selectionFocus)) { singleton.selectionFocus = path - onSelect(singleton.selectionAnchor, singleton.selectionFocus) + + onSelect({ + anchorPath: singleton.selectionAnchor, + focusPath: singleton.selectionFocus + }) } } } @@ -245,8 +252,38 @@ document.removeEventListener('mouseup', handleMouseUp) } + function handleSelectBefore (event) { + event.preventDefault() + event.stopPropagation() + + onSelect({ + beforePath: path + }) + } + + function handleSelectAfter (event) { + event.preventDefault() + event.stopPropagation() + + onSelect({ + afterPath: path + }) + } + // FIXME: this is not efficient. Create a nested object with the selection and pass that - $: selected = selectionMap[compileJSONPointer(path)] === true + $: selected = (selection && selection.paths) + ? selection.paths[compileJSONPointer(path)] === true + : false + + $: selectedBefore = (selection && selection.beforePath) + ? isEqual(selection.beforePath, path) + : false + + $: selectedAfter = (selection && selection.afterPath) + ? isEqual(selection.afterPath, path) + : false + + $: indentationStyle = getIndentationStyle(path.length)
+
+
+
{#if type === 'array'} -
+
)
{/if}
-