Implement insert
This commit is contained in:
parent
a6bb790f5e
commit
eea6e09bd8
|
@ -10,7 +10,6 @@
|
|||
export let title = null
|
||||
export let width = '120px'
|
||||
export let visible = false
|
||||
export let disabled = false
|
||||
|
||||
function toggleShow (event) {
|
||||
event.stopPropagation()
|
||||
|
@ -51,7 +50,11 @@
|
|||
<ul>
|
||||
{#each items as item}
|
||||
<li>
|
||||
<button on:click={item.onClick} disabled={disabled}>
|
||||
<button
|
||||
on:click={item.onClick}
|
||||
title={item.title}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.text}
|
||||
</button>
|
||||
</li>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
import { tick } from 'svelte'
|
||||
import {
|
||||
append,
|
||||
duplicate,
|
||||
insertBefore,
|
||||
removeAll,
|
||||
replace
|
||||
insert,
|
||||
createNewValue,
|
||||
removeAll
|
||||
} from '../logic/operations.js'
|
||||
import {
|
||||
STATE_EXPANDED,
|
||||
|
@ -18,7 +17,8 @@
|
|||
import {
|
||||
createPathsMap,
|
||||
createSelectionFromOperations,
|
||||
expandSelection
|
||||
expandSelection,
|
||||
getParentPath
|
||||
} from '../logic/selection.js'
|
||||
import { isContentEditableDiv } from '../utils/domUtils.js'
|
||||
import {
|
||||
|
@ -34,6 +34,7 @@
|
|||
import jump from '../assets/jump.js/src/jump.js'
|
||||
import { expandPath, syncState, getNextKeys, patchProps } from '../logic/documentState.js'
|
||||
import Menu from './Menu.svelte'
|
||||
import { isObjectOrArray } from '../utils/typeUtils.js';
|
||||
|
||||
let divContents
|
||||
let domHiddenInput
|
||||
|
@ -150,42 +151,10 @@
|
|||
if (selection && clipboard) {
|
||||
console.log('paste', { clipboard, selection })
|
||||
|
||||
function createNewSelection (operations) {
|
||||
const paths = operations
|
||||
.filter(operation => operation.op === 'add')
|
||||
.map(operation => parseJSONPointer(operation.path))
|
||||
|
||||
return {
|
||||
paths,
|
||||
pathsMap: createPathsMap(paths)
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.beforePath) {
|
||||
const parentPath = initial(selection.beforePath)
|
||||
const beforeKey = last(selection.beforePath)
|
||||
const props = getIn(state, parentPath.concat(STATE_PROPS))
|
||||
const nextKeys = getNextKeys(props, beforeKey, true)
|
||||
const operations = insertBefore(doc, selection.beforePath, clipboard, nextKeys)
|
||||
const operations = insert(doc, state, selection, clipboard)
|
||||
const newSelection = createSelectionFromOperations(operations)
|
||||
|
||||
handlePatch(operations, newSelection)
|
||||
} else if (selection.appendPath) {
|
||||
const operations = append(doc, selection.appendPath, clipboard)
|
||||
const newSelection = createSelectionFromOperations(operations)
|
||||
|
||||
handlePatch(operations, newSelection)
|
||||
} else if (selection.paths) {
|
||||
const lastPath = last(selection.paths) // FIXME: here we assume selection.paths is sorted correctly, that's a dangerous assumption
|
||||
const parentPath = initial(lastPath)
|
||||
const beforeKey = last(lastPath)
|
||||
const props = getIn(state, parentPath.concat(STATE_PROPS))
|
||||
const nextKeys = getNextKeys(props, beforeKey, true)
|
||||
const operations = replace(doc, selection.paths, clipboard, nextKeys)
|
||||
const newSelection = createSelectionFromOperations(operations)
|
||||
|
||||
handlePatch(operations, newSelection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,6 +162,8 @@
|
|||
if (selection && selection.paths) {
|
||||
console.log('duplicate', { selection })
|
||||
|
||||
// TODO: move this logic inside duplicate()
|
||||
|
||||
const lastPath = last(selection.paths) // FIXME: here we assume selection.paths is sorted correctly, that's a dangerous assumption
|
||||
const parentPath = initial(lastPath)
|
||||
const beforeKey = last(lastPath)
|
||||
|
@ -202,17 +173,35 @@
|
|||
const operations = duplicate(doc, selection.paths, nextKeys)
|
||||
const newSelection = createSelectionFromOperations(operations)
|
||||
|
||||
console.log('newSelection', newSelection)
|
||||
handlePatch(operations, newSelection)
|
||||
}
|
||||
}
|
||||
|
||||
function handleInsert() {
|
||||
|
||||
/**
|
||||
* @param {'value' | 'object' | 'array' | 'structure'} type
|
||||
*/
|
||||
function handleInsert(type) {
|
||||
if (selection != null) {
|
||||
console.log('insert', { selection })
|
||||
console.log('insert', { type, selection })
|
||||
|
||||
// TODO: impelemnt insert
|
||||
const value = createNewValue(doc, selection, type)
|
||||
const values = [
|
||||
{
|
||||
key: 'new',
|
||||
value
|
||||
}
|
||||
]
|
||||
const operations = insert(doc, state, selection, values)
|
||||
const newSelection = createSelectionFromOperations(operations)
|
||||
|
||||
handlePatch(operations, newSelection)
|
||||
|
||||
if (isObjectOrArray(value)) {
|
||||
// expand the new object/array in case of inserting a structure
|
||||
operations
|
||||
.filter(operation => operation.op === 'add')
|
||||
.forEach(operation => handleExpand(parseJSONPointer(operation.path), true, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,7 +380,7 @@
|
|||
}
|
||||
if (combo === 'Ctrl+Insert' || combo === 'Command+Insert') {
|
||||
event.preventDefault()
|
||||
handleInsert()
|
||||
handleInsert('structure')
|
||||
}
|
||||
if (combo === 'Escape') {
|
||||
event.preventDefault()
|
||||
|
@ -444,6 +433,8 @@
|
|||
searchText={searchText}
|
||||
searchResult={searchResult}
|
||||
bind:showSearch
|
||||
|
||||
doc={doc}
|
||||
selection={selection}
|
||||
clipboard={clipboard}
|
||||
|
||||
|
@ -451,6 +442,7 @@
|
|||
onCopy={handleCopy}
|
||||
onPaste={handlePaste}
|
||||
onDuplicate={handleDuplicate}
|
||||
onInsert={handleInsert}
|
||||
onUndo={handleUndo}
|
||||
onRedo={handleRedo}
|
||||
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
import { faCut, faClone, faCopy, faPaste, faSearch, faUndo, faRedo, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import SearchBox from './SearchBox.svelte'
|
||||
import DropdownMenu from './DropdownMenu.svelte'
|
||||
import { getParentPath } from '../logic/selection'
|
||||
import { getIn } from '../utils/immutabilityHelpers'
|
||||
|
||||
export let searchText
|
||||
export let searchResult
|
||||
export let showSearch = false
|
||||
export let doc
|
||||
export let selection
|
||||
export let clipboard
|
||||
export let historyState
|
||||
|
@ -15,6 +18,7 @@
|
|||
export let onCopy
|
||||
export let onPaste
|
||||
export let onDuplicate
|
||||
export let onInsert
|
||||
export let onUndo
|
||||
export let onRedo
|
||||
|
||||
|
@ -24,6 +28,7 @@
|
|||
|
||||
$: hasSelection = selection != null
|
||||
$: hasSelectionContents = selection != null && selection.paths != null
|
||||
$: hasSelectionWithoutContents = selection != null && selection.paths == null
|
||||
$: hasClipboardContents = clipboard != null && selection != null
|
||||
|
||||
function handleToggleSearch() {
|
||||
|
@ -36,33 +41,36 @@
|
|||
}
|
||||
|
||||
function handleInsertValue () {
|
||||
console.log('TODO: insert value')
|
||||
onInsert('value')
|
||||
}
|
||||
|
||||
/** @type {MenuDropdownItem[]} */
|
||||
const insertItems = [
|
||||
$: insertItems = [
|
||||
{
|
||||
text: "Insert value",
|
||||
text: 'Insert value',
|
||||
title: 'Insert a new value',
|
||||
onClick: handleInsertValue,
|
||||
disabled: !hasSelectionWithoutContents,
|
||||
default: true
|
||||
},
|
||||
{
|
||||
text: "Insert object",
|
||||
onClick: () => {
|
||||
console.log('TODO: insert object')
|
||||
}
|
||||
text: 'Insert object',
|
||||
title: 'Insert a new object',
|
||||
onClick: () => onInsert('object'),
|
||||
disabled: !hasSelectionWithoutContents
|
||||
},
|
||||
{
|
||||
text: "Insert array",
|
||||
onClick: () => {
|
||||
console.log('TODO: insert array')
|
||||
}
|
||||
text: 'Insert array',
|
||||
title: 'Insert a new array',
|
||||
onClick: () => onInsert('array'),
|
||||
disabled: !hasSelectionWithoutContents
|
||||
},
|
||||
{
|
||||
text: "Insert structure",
|
||||
onClick: () => {
|
||||
console.log('TODO: insert structure')
|
||||
}
|
||||
text: 'Insert structure',
|
||||
title: 'Insert a new item with the same structure as other items. ' +
|
||||
'Only applicable inside an array',
|
||||
onClick: () => onInsert('structure'),
|
||||
disabled: !hasSelectionWithoutContents
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
@ -107,13 +115,12 @@
|
|||
<DropdownMenu
|
||||
items={insertItems}
|
||||
title="Insert new value (Ctrl+Insert)"
|
||||
disabled={!hasSelection}
|
||||
>
|
||||
<button
|
||||
class="button insert"
|
||||
slot="defaultItem"
|
||||
on:click={handleInsertValue}
|
||||
disabled={!hasSelection}
|
||||
disabled={!hasSelectionWithoutContents}
|
||||
>
|
||||
<Icon data={faPlus} />
|
||||
</button>
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { first, initial, last, pickBy } from 'lodash-es'
|
||||
import { getIn } from '../utils/immutabilityHelpers'
|
||||
import { compileJSONPointer } from '../utils/jsonPointer'
|
||||
import { findUniqueName } from '../utils/stringUtils'
|
||||
import { first, initial, isEmpty, last, pickBy, cloneDeepWith } from 'lodash-es'
|
||||
import { STATE_PROPS } from '../constants.js'
|
||||
import { getNextKeys } from '../logic/documentState.js'
|
||||
import { getParentPath } from '../logic/selection.js'
|
||||
import { getIn } from '../utils/immutabilityHelpers.js'
|
||||
import { compileJSONPointer } from '../utils/jsonPointer.js'
|
||||
import { findUniqueName } from '../utils/stringUtils.js'
|
||||
import { isObjectOrArray } from '../utils/typeUtils.js'
|
||||
|
||||
/**
|
||||
* Create a JSONPatch for an insert operation.
|
||||
|
@ -229,6 +233,66 @@ export function duplicate (json, paths, nextKeys) {
|
|||
}
|
||||
}
|
||||
|
||||
export function insert (doc, state, selection, values) {
|
||||
if (selection.beforePath) {
|
||||
const parentPath = initial(selection.beforePath)
|
||||
const beforeKey = last(selection.beforePath)
|
||||
const props = getIn(state, parentPath.concat(STATE_PROPS))
|
||||
const nextKeys = getNextKeys(props, beforeKey, true)
|
||||
const operations = insertBefore(doc, selection.beforePath, values, nextKeys)
|
||||
|
||||
return operations
|
||||
} else if (selection.appendPath) {
|
||||
const operations = append(doc, selection.appendPath, values)
|
||||
|
||||
return operations
|
||||
} else if (selection.paths) {
|
||||
const lastPath = last(selection.paths) // FIXME: here we assume selection.paths is sorted correctly, that's a dangerous assumption
|
||||
const parentPath = initial(lastPath)
|
||||
const beforeKey = last(lastPath)
|
||||
const props = getIn(state, parentPath.concat(STATE_PROPS))
|
||||
const nextKeys = getNextKeys(props, beforeKey, true)
|
||||
const operations = replace(doc, selection.paths, values, nextKeys)
|
||||
|
||||
return operations
|
||||
}
|
||||
}
|
||||
|
||||
export function createNewValue (doc, selection, type) {
|
||||
switch (type) {
|
||||
case 'value':
|
||||
return ''
|
||||
|
||||
case 'object':
|
||||
return {}
|
||||
|
||||
case 'array':
|
||||
return []
|
||||
|
||||
case 'structure':
|
||||
const parentPath = getParentPath(selection)
|
||||
const parent = getIn(doc, parentPath)
|
||||
|
||||
if (Array.isArray(parent) && !isEmpty(parent)) {
|
||||
const jsonExample = first(parent)
|
||||
const structure = cloneDeepWith(jsonExample, (value) => {
|
||||
return isObjectOrArray(value)
|
||||
? undefined // leave as is
|
||||
: ''
|
||||
})
|
||||
|
||||
console.log('structure', jsonExample, structure)
|
||||
|
||||
return structure
|
||||
} else {
|
||||
// no example structure
|
||||
return ''
|
||||
}
|
||||
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JSONPatch for a remove operation
|
||||
* @param {Path} path
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { isEqual } from 'lodash-es'
|
||||
import { first, initial, isEqual } from 'lodash-es'
|
||||
import { STATE_PROPS } from '../constants.js'
|
||||
import { getIn } from '../utils/immutabilityHelpers.js'
|
||||
import { compileJSONPointer, parseJSONPointer } from '../utils/jsonPointer.js'
|
||||
|
@ -65,6 +65,26 @@ export function expandSelection (doc, state, anchorPath, focusPath) {
|
|||
throw new Error('Failed to create selection')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Selection} selection
|
||||
* @return {Path} Returns parent path
|
||||
*/
|
||||
export function getParentPath (selection) {
|
||||
if (selection.beforePath) {
|
||||
return initial(selection.beforePath)
|
||||
}
|
||||
|
||||
if (selection.appendPath) {
|
||||
return selection.appendPath
|
||||
}
|
||||
|
||||
if (selection.paths) {
|
||||
const firstPath = first(selection.paths)
|
||||
return initial(firstPath)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {JSONPatchDocument} operations
|
||||
* @returns {MultiSelection}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import assert from 'assert'
|
||||
import { expandSelection } from './selection.js'
|
||||
import { expandSelection, getParentPath } from './selection.js'
|
||||
import { syncState } from './documentState.js'
|
||||
|
||||
describe ('selection', () => {
|
||||
|
@ -65,4 +65,14 @@ describe ('selection', () => {
|
|||
['obj', 'arr']
|
||||
])
|
||||
})
|
||||
|
||||
it('should get parent path from a selection', () => {
|
||||
assert.deepStrictEqual(getParentPath({ beforePath: ['a', 'b']}), ['a'])
|
||||
assert.deepStrictEqual(getParentPath({ appendPath: ['a', 'b']}), ['a', 'b'])
|
||||
assert.deepStrictEqual(getParentPath({ paths:[
|
||||
['a', 'b'],
|
||||
['a', 'c'],
|
||||
['a', 'd']
|
||||
]}), ['a'])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -80,5 +80,6 @@
|
|||
* @typedef {Object} MenuDropdownItem
|
||||
* @property {string} text
|
||||
* @property {function} onClick
|
||||
* @property {string} [title=undefined]
|
||||
* @property {boolean} [default=false]
|
||||
**/
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue