diff --git a/src/actions.js b/src/actions.js index 4c46a48..cc3813f 100644 --- a/src/actions.js +++ b/src/actions.js @@ -92,37 +92,44 @@ export function changeType (data, path, type) { * and object property * * @param {ESON} data - * @param {Path} path + * @param {ESONSelection} selection * @return {Array} */ -export function duplicate (data, path) { +export function duplicate (data, selection) { // console.log('duplicate', path) - const parentPath = path.slice(0, path.length - 1) + const rootPath = findRootPath(selection) + const start = selection.start.path[rootPath.length] + const end = selection.end.path[rootPath.length] + const root = getIn(data, toEsonPath(data, rootPath)) + const { maxIndex } = findSelectionIndices(root, start, end) + const paths = pathsFromSelection(data, selection) - const esonPath = toEsonPath(data, parentPath) - const parent = getIn(data, esonPath) - - if (parent.type === 'Array') { - const index = parseInt(path[path.length - 1]) + 1 - return [{ + if (root.type === 'Array') { + return paths.map((path, offset) => ({ op: 'copy', from: compileJSONPointer(path), - path: compileJSONPointer(parentPath.concat(index)) - }] + path: compileJSONPointer(rootPath.concat(maxIndex + offset)) + })) } else { // object.type === 'Object' - const prop = path[path.length - 1] - const newProp = findUniqueName(prop, parent.props.map(p => p.name)) + const { maxIndex } = findSelectionIndices(root, start, end) + const nextProp = root.props && root.props[maxIndex] + const before = nextProp ? nextProp.name : null - return [{ - op: 'copy', - from: compileJSONPointer(path), - path: compileJSONPointer(parentPath.concat(newProp)), - jsoneditor: { - before: findNextProp(parent, prop) + return paths.map(path => { + const prop = last(path) + const newProp = findUniqueName(prop, root.props.map(p => p.name)) + + return { + op: 'copy', + from: compileJSONPointer(path), + path: compileJSONPointer(rootPath.concat(newProp)), + jsoneditor: { + before + } } - }] + }) } } @@ -226,7 +233,7 @@ export function insertBefore (data, path, values) { // TODO: find a better name * and object property * * @param {ESON} data - * @param {Selection} selection + * @param {ESONSelection} selection * @param {Array.<{name?: string, value: JSONType, type?: ESONType}>} values * @return {Array} */ @@ -235,10 +242,8 @@ export function replace (data, selection, values) { // TODO: find a better name const rootPath = findRootPath(selection) const start = selection.start.path[rootPath.length] const end = selection.end.path[rootPath.length] - console.log('rootPath', rootPath, start, end) const root = getIn(data, toEsonPath(data, rootPath)) const { minIndex, maxIndex } = findSelectionIndices(root, start, end) - console.log('selection', minIndex, maxIndex) if (root.type === 'Array') { const removeActions = removeAll(pathsFromSelection(data, selection)) diff --git a/src/components/TreeMode.js b/src/components/TreeMode.js index d301c45..4ed0c84 100644 --- a/src/components/TreeMode.js +++ b/src/components/TreeMode.js @@ -72,6 +72,7 @@ export default class TreeMode extends Component { 'cut': this.handleKeyDownCut, 'copy': this.handleKeyDownCopy, 'paste': this.handleKeyDownPaste, + 'duplicate': this.handleKeyDownDuplicate, 'undo': this.handleUndo, 'redo': this.handleRedo, 'find': this.handleFocusFind, @@ -372,11 +373,11 @@ export default class TreeMode extends Component { this.focusToNext(parentPath) } - handleDuplicate = (path) => { - this.handlePatch(duplicate(this.state.data, path)) - - // apply focus to the duplicated node - this.focusToNext(path) + handleDuplicate = () => { + if (this.state.selection) { + this.handlePatch(duplicate(this.state.data, this.state.selection)) + // TODO: focus to duplicated selection + } } handleRemove = (path) => { @@ -455,6 +456,17 @@ export default class TreeMode extends Component { } } + handleKeyDownDuplicate = (event) => { + const path = this.findDataPathFromElement(event.target) + if (path) { + const selection = { start: {path}, end: {path} } + this.handlePatch(duplicate(this.state.data, selection)) + + // apply focus to the duplicated node + this.focusToNext(path) + } + } + handleCut = () => { const selection = this.state.selection if (selection && selection.start && selection.end) { diff --git a/src/components/menu/FloatingMenu.js b/src/components/menu/FloatingMenu.js index dad8869..94a91e9 100644 --- a/src/components/menu/FloatingMenu.js +++ b/src/components/menu/FloatingMenu.js @@ -53,42 +53,42 @@ const CREATE_TYPE = { duplicate: (path, events) => h('button', { key: 'duplicate', className: MENU_ITEM_CLASS_NAME, - onClick: () => events.onDuplicate(path), + onClick: () => events.onDuplicate(), title: 'Duplicate' }, 'Duplicate'), cut: (path, events) => h('button', { key: 'cut', className: MENU_ITEM_CLASS_NAME, - onClick: () => events.onCut(path), + onClick: () => events.onCut(), title: 'Cut' }, 'Cut'), copy: (path, events) => h('button', { key: 'copy', className: MENU_ITEM_CLASS_NAME, - onClick: () => events.onCopy(path), + onClick: () => events.onCopy(), title: 'Copy' }, 'Copy'), paste: (path, events) => h('button', { key: 'paste', className: MENU_ITEM_CLASS_NAME, - onClick: () => events.onPaste(path), + onClick: () => events.onPaste(), title: 'Paste' }, 'Paste'), remove: (path, events) => h('button', { key: 'remove', className: MENU_ITEM_CLASS_NAME, - onClick: () => events.onRemove(null), // do not pass path: we want to remove selection + onClick: () => events.onRemove(), title: 'Remove' }, 'Remove'), insertStructure: (path, events) => h('button', { key: 'insertStructure', className: MENU_ITEM_CLASS_NAME, - onClick: () => events.onInsertStructure(path), + onClick: () => events.onInsertStructure(), title: 'Insert a new object with the same data structure as the item above' }, 'Insert structure'),