Implement Transform modal (WIP)
This commit is contained in:
parent
ad35e6fc16
commit
00d0099355
|
@ -1,2 +1,37 @@
|
|||
@import '../../styles.scss';
|
||||
@import './Modal.scss';
|
||||
|
||||
.jsoneditor-modal.transform {
|
||||
|
||||
.description {
|
||||
padding-bottom: $padding;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
padding: $padding/2 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
textarea.query,
|
||||
textarea.preview {
|
||||
border: 1px solid $border-gray;
|
||||
border-radius: $border-radius;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
resize: vertical; // prevent resizing horizontally
|
||||
box-sizing: border-box;
|
||||
padding: $padding / 2;
|
||||
font-family: $font-family-mono;
|
||||
font-size: $font-size-mono;
|
||||
}
|
||||
|
||||
textarea.preview {
|
||||
height: 200px;
|
||||
|
||||
&.error {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,94 @@
|
|||
|
||||
<script>
|
||||
import { getContext } from 'svelte'
|
||||
import { compileJSONPointer } from '../../utils/jsonPointer';
|
||||
import Header from './Header.svelte'
|
||||
|
||||
export let id
|
||||
export let json
|
||||
export let rootPath
|
||||
export let onTransform
|
||||
|
||||
const {close} = getContext('simple-modal')
|
||||
|
||||
let query = 'function query (data) {\n return data\n}'
|
||||
let previewHasError = false
|
||||
let preview = ''
|
||||
|
||||
function evalTransform(json, query) {
|
||||
// TODO: replace unsafe eval with a JS based query language
|
||||
const queryFn = eval(`(${query})`)
|
||||
return queryFn(json)
|
||||
}
|
||||
|
||||
function previewTransform(json, query) {
|
||||
try {
|
||||
const jsonTransformed = evalTransform(json, query)
|
||||
|
||||
preview = JSON.stringify(jsonTransformed, null, 2) // TODO: limit preview length
|
||||
previewHasError = false
|
||||
} catch (err) {
|
||||
preview = err.toString()
|
||||
previewHasError = true
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
previewTransform(json, query)
|
||||
}
|
||||
|
||||
function handleTransform () {
|
||||
try {
|
||||
const jsonTransformed = evalTransform(json, query)
|
||||
|
||||
onTransform([
|
||||
{
|
||||
op: 'replace',
|
||||
path: compileJSONPointer(rootPath),
|
||||
value: jsonTransformed
|
||||
}
|
||||
])
|
||||
|
||||
close()
|
||||
} catch (err) {
|
||||
// this should never occur since we can only press the Transform
|
||||
// button when creating a preview was succesful
|
||||
console.error(err)
|
||||
preview = err.toString()
|
||||
previewHasError = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="jsoneditor-modal transform">
|
||||
<Header title='Transform' />
|
||||
<div class="contents">
|
||||
TODO...
|
||||
<div class='description'>
|
||||
Enter a JavaScript function to filter, sort, or transform the data.
|
||||
</div>
|
||||
|
||||
<label>Query</label>
|
||||
<textarea class="query" bind:value={query} />
|
||||
|
||||
<label>Preview</label>
|
||||
<textarea
|
||||
class="preview"
|
||||
class:error={previewHasError}
|
||||
bind:value={preview}
|
||||
readonly
|
||||
/>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
class="primary"
|
||||
on:click={handleTransform}
|
||||
disabled={previewHasError}
|
||||
>
|
||||
Transform
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
import {
|
||||
createPathsMap,
|
||||
createSelectionFromOperations,
|
||||
expandSelection
|
||||
expandSelection,
|
||||
findRootPath
|
||||
} from '../../logic/selection.js'
|
||||
import { isContentEditableDiv } from '../../utils/domUtils.js'
|
||||
import {
|
||||
|
@ -43,6 +44,7 @@
|
|||
|
||||
const { open } = getContext('simple-modal')
|
||||
const sortModalId = uniqueId()
|
||||
const transformModalId = uniqueId()
|
||||
|
||||
let divContents
|
||||
let domHiddenInput
|
||||
|
@ -264,11 +266,7 @@
|
|||
}
|
||||
|
||||
function handleSort () {
|
||||
const rootPath = selection && selection.paths
|
||||
? selection.paths.length > 1
|
||||
? initial(first(selection.paths)) // the parent path of the paths
|
||||
: first(selection.paths) // the first and only path
|
||||
: []
|
||||
const rootPath = findRootPath(selection)
|
||||
|
||||
open(SortModal, {
|
||||
id: sortModalId,
|
||||
|
@ -288,7 +286,26 @@
|
|||
}
|
||||
|
||||
function handleTransform () {
|
||||
open(TransformModal, {}, SIMPLE_MODAL_OPTIONS)
|
||||
const rootPath = findRootPath(selection)
|
||||
|
||||
open(TransformModal, {
|
||||
id: transformModalId,
|
||||
json: getIn(doc, rootPath),
|
||||
rootPath,
|
||||
onTransform: async (operations) => {
|
||||
console.log('onTransform', rootPath, operations)
|
||||
patch(operations, selection)
|
||||
|
||||
// FIXME: replaced node should keep it's expanded state (if expanded)
|
||||
|
||||
}
|
||||
}, {
|
||||
...SIMPLE_MODAL_OPTIONS,
|
||||
styleWindow: {
|
||||
...SIMPLE_MODAL_OPTIONS.styleWindow,
|
||||
width: '600px'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function handleSearchText (text) {
|
||||
|
|
|
@ -17,14 +17,14 @@ import { isObject } from '../utils/typeUtils.js'
|
|||
export function expandSelection (doc, state, anchorPath, focusPath) {
|
||||
if (isEqual(anchorPath, focusPath)) {
|
||||
// just a single node
|
||||
return [ anchorPath ]
|
||||
return [anchorPath]
|
||||
} else {
|
||||
// multiple nodes
|
||||
let sharedPath = findSharedPath(anchorPath, focusPath)
|
||||
const sharedPath = findSharedPath(anchorPath, focusPath)
|
||||
|
||||
if (anchorPath.length === sharedPath.length || focusPath.length === sharedPath.length) {
|
||||
// a parent and a child, like ['arr', 1] and ['arr']
|
||||
return [ sharedPath ]
|
||||
return [sharedPath]
|
||||
}
|
||||
|
||||
const anchorKey = anchorPath[sharedPath.length]
|
||||
|
@ -66,8 +66,7 @@ export function expandSelection (doc, state, anchorPath, focusPath) {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Selection} selection
|
||||
* @param {Selection} selection
|
||||
* @return {Path} Returns parent path
|
||||
*/
|
||||
export function getParentPath (selection) {
|
||||
|
@ -94,7 +93,7 @@ export function createSelectionFromOperations (operations) {
|
|||
.filter(operation => operation.op === 'add' || operation.op === 'copy')
|
||||
.map(operation => parseJSONPointer(operation.path))
|
||||
|
||||
return {
|
||||
return {
|
||||
paths,
|
||||
pathsMap: createPathsMap(paths)
|
||||
}
|
||||
|
@ -123,10 +122,21 @@ export function createPathsMap (paths) {
|
|||
*/
|
||||
// TODO: write unit tests for findSharedPath
|
||||
export function findSharedPath (path1, path2) {
|
||||
let i = 0;
|
||||
let i = 0
|
||||
while (i < path1.length && path1[i] === path2[i]) {
|
||||
i++;
|
||||
i++
|
||||
}
|
||||
|
||||
return path1.slice(0, i)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Selection} selection
|
||||
*/
|
||||
export function findRootPath (selection) {
|
||||
return selection && selection.paths
|
||||
? selection.paths.length > 1
|
||||
? initial(first(selection.paths)) // the parent path of the paths
|
||||
: first(selection.paths) // the first and only path
|
||||
: []
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import assert from 'assert'
|
||||
import { expandSelection, getParentPath } from './selection.js'
|
||||
import { expandSelection, getParentPath, findRootPath } from './selection.js'
|
||||
import { syncState } from './documentState.js'
|
||||
|
||||
describe ('selection', () => {
|
||||
describe('selection', () => {
|
||||
const doc = {
|
||||
"obj": {
|
||||
"arr": [1,2, {"first":3,"last":4}]
|
||||
obj: {
|
||||
arr: [1, 2, { first: 3, last: 4 }]
|
||||
},
|
||||
"str": "hello world",
|
||||
"nill": null,
|
||||
"bool": false
|
||||
str: 'hello world',
|
||||
nill: null,
|
||||
bool: false
|
||||
}
|
||||
const state = syncState(doc, undefined, [], () => true)
|
||||
|
||||
|
@ -67,12 +67,27 @@ describe ('selection', () => {
|
|||
})
|
||||
|
||||
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:[
|
||||
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'])
|
||||
]
|
||||
}), ['a'])
|
||||
})
|
||||
|
||||
it('should find the root path from a selection', () => {
|
||||
assert.deepStrictEqual(findRootPath({
|
||||
paths: [
|
||||
['a', 'b'],
|
||||
['a', 'c'],
|
||||
['a', 'd']
|
||||
]
|
||||
}), ['a'])
|
||||
assert.deepStrictEqual(findRootPath({ beforePath: ['a', 'b'] }), [])
|
||||
assert.deepStrictEqual(findRootPath({ appendPath: ['a', 'b'] }), [])
|
||||
assert.deepStrictEqual(findRootPath(), [])
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue