Split App into JSONEditor and App
This commit is contained in:
parent
a4c58cfa15
commit
9b5891d5cb
134
src/App.svelte
134
src/App.svelte
|
@ -1,13 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import Icon from 'svelte-awesome'
|
import JSONEditor from './JSONEditor.svelte'
|
||||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
|
||||||
import Node from './JSONNode.svelte'
|
|
||||||
import { search } from './search'
|
|
||||||
import { beforeUpdate, afterUpdate } from 'svelte'
|
import { beforeUpdate, afterUpdate } from 'svelte'
|
||||||
|
|
||||||
export let searchText = ''
|
let json = {
|
||||||
|
|
||||||
export let json = {
|
|
||||||
'array': [1, 2, 3, {
|
'array': [1, 2, 3, {
|
||||||
name: 'Item ' + 2,
|
name: 'Item ' + 2,
|
||||||
id: String(2),
|
id: String(2),
|
||||||
|
@ -17,13 +12,14 @@
|
||||||
latitude: 1.23,
|
latitude: 1.23,
|
||||||
longitude: 23.44,
|
longitude: 23.44,
|
||||||
coordinates: [23.44, 1.23]
|
coordinates: [23.44, 1.23]
|
||||||
},
|
}
|
||||||
}],
|
}],
|
||||||
'boolean': true,
|
'boolean': true,
|
||||||
'color': '#82b92c',
|
'color': '#82b92c',
|
||||||
'null': null,
|
'null': null,
|
||||||
'number': 123,
|
'number': 123,
|
||||||
'object': {'a': 'b', 'c': 'd', nested: {
|
'object': {
|
||||||
|
'a': 'b', 'c': 'd', nested: {
|
||||||
name: 'Item ' + 2,
|
name: 'Item ' + 2,
|
||||||
id: String(2),
|
id: String(2),
|
||||||
index: 2,
|
index: 2,
|
||||||
|
@ -32,47 +28,39 @@
|
||||||
latitude: 1.23,
|
latitude: 1.23,
|
||||||
longitude: 23.44,
|
longitude: 23.44,
|
||||||
coordinates: [23.44, 1.23]
|
coordinates: [23.44, 1.23]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}},
|
|
||||||
'string': 'Hello World',
|
'string': 'Hello World',
|
||||||
'url': 'https://jsoneditoronline.org'
|
'url': 'https://jsoneditoronline.org'
|
||||||
}
|
}
|
||||||
|
|
||||||
let uniDirectionalValue = 'test uni directional flow in Svelte';
|
export function get() {
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set(newJson) {
|
||||||
|
json = newJson
|
||||||
|
}
|
||||||
|
|
||||||
console.time('load editor')
|
console.time('load editor')
|
||||||
|
|
||||||
beforeUpdate(() => console.time('render app'))
|
beforeUpdate(() => console.time('render app'))
|
||||||
afterUpdate(() => console.timeEnd('render app'))
|
afterUpdate(() => console.timeEnd('render app'))
|
||||||
|
|
||||||
// console.log('search results "d"', search(null, json, 'd'))
|
function loadLargeJson() {
|
||||||
// console.log('search results "e"', search(null, json, 'e'))
|
|
||||||
// console.log('search results "2"', search(null, json, '2'))
|
|
||||||
|
|
||||||
function doSearch (json, searchText) {
|
|
||||||
console.time('search')
|
|
||||||
const result = search(null, json, searchText)
|
|
||||||
console.timeEnd('search')
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
$: searchResult = searchText ? doSearch(json, searchText) : undefined
|
|
||||||
|
|
||||||
$: formattedName = `"${name}"`
|
|
||||||
|
|
||||||
function loadLargeJson () {
|
|
||||||
const count = 500
|
const count = 500
|
||||||
|
|
||||||
console.log('create large json', { count })
|
console.log('create large json', {count})
|
||||||
console.time('create large json')
|
console.time('create large json')
|
||||||
const largeJson = {}
|
const largeJson = {}
|
||||||
largeJson.numbers = []
|
largeJson.numbers = []
|
||||||
largeJson.array = []
|
largeJson.array = []
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const longitude = 4 + i / count;
|
const longitude = 4 + i / count
|
||||||
const latitude = 51 + i / count;
|
const latitude = 51 + i / count
|
||||||
|
|
||||||
largeJson.numbers.push(i);
|
largeJson.numbers.push(i)
|
||||||
largeJson.array.push({
|
largeJson.array.push({
|
||||||
name: 'Item ' + i,
|
name: 'Item ' + i,
|
||||||
id: String(i),
|
id: String(i),
|
||||||
|
@ -96,49 +84,36 @@
|
||||||
|
|
||||||
// json = loadLargeJson()
|
// json = loadLargeJson()
|
||||||
|
|
||||||
function handleChangeKey (key, oldKey) {
|
function handleLoadLargeJson() {
|
||||||
console.log('App handleChangeKey', { key, oldKey })
|
|
||||||
// TODO: this should not happen?
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChangeValue (value, key) {
|
|
||||||
console.log('App handleChangeValue', value, key)
|
|
||||||
json = value
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInputTextArea (event) {
|
|
||||||
console.log('on:input')
|
|
||||||
try {
|
|
||||||
json = JSON.parse(event.target.value)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoadLargeJson () {
|
|
||||||
json = loadLargeJson()
|
json = loadLargeJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClearJson () {
|
function handleClearJson() {
|
||||||
json = {}
|
json = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleChange (json) {
|
||||||
|
console.log('App handleChange', json)
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.timeEnd('load editor')
|
console.timeEnd('load editor')
|
||||||
console.log('loaded' )
|
console.log('loaded')
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleInputUniDirectional (event) {
|
|
||||||
console.log('change', event.target.value)
|
|
||||||
uniDirectionalValue = event.target.value
|
|
||||||
}
|
|
||||||
|
|
||||||
let inputTestValue = 'input test'
|
|
||||||
function onChangeInputTest (value) {
|
|
||||||
inputTestValue = value
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="editor">
|
||||||
|
<JSONEditor
|
||||||
|
json={json}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button on:click={handleLoadLargeJson}>load large json</button>
|
||||||
|
<button on:click={handleClearJson}>clear json</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -163,38 +138,5 @@
|
||||||
.editor {
|
.editor {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
overflow: auto;
|
|
||||||
border: 1px solid lightgray;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<p>
|
|
||||||
<Icon data={faSearch} /> Search: <input bind:value={searchText} />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="editor">
|
|
||||||
<Node
|
|
||||||
value={json}
|
|
||||||
searchResult={searchResult}
|
|
||||||
expanded={true}
|
|
||||||
onChangeKey={handleChangeKey}
|
|
||||||
onChangeValue={handleChangeValue}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--<textarea-->
|
|
||||||
<!-- class='code'-->
|
|
||||||
<!-- value={JSON.stringify(json, null, 2)}-->
|
|
||||||
<!-- on:input={handleInputTextArea}-->
|
|
||||||
<!--/>-->
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<button on:click={handleLoadLargeJson}>load large json</button>
|
|
||||||
<button on:click={handleClearJson}>clear json</button>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- <pre>
|
|
||||||
<code>
|
|
||||||
{JSON.stringify(json, null, 2)}
|
|
||||||
</code>
|
|
||||||
</pre> -->
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<script>
|
||||||
|
import Icon from 'svelte-awesome'
|
||||||
|
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import Node from './JSONNode.svelte'
|
||||||
|
import { search } from './search'
|
||||||
|
import { beforeUpdate, afterUpdate } from 'svelte'
|
||||||
|
|
||||||
|
export let json = {}
|
||||||
|
export let onChange = () => {}
|
||||||
|
export let searchText = ''
|
||||||
|
|
||||||
|
export function get() {
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set(newJson) {
|
||||||
|
json = newJson
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeUpdate(() => console.time('render JSONEditor'))
|
||||||
|
afterUpdate(() => console.timeEnd('render JSONEditor'))
|
||||||
|
|
||||||
|
function doSearch (json, searchText) {
|
||||||
|
console.time('search')
|
||||||
|
const result = search(null, json, searchText)
|
||||||
|
console.timeEnd('search')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
$: searchResult = searchText ? doSearch(json, searchText) : undefined
|
||||||
|
|
||||||
|
$: onChange(json)
|
||||||
|
|
||||||
|
function handleChangeKey (key, oldKey) {
|
||||||
|
console.log('handleChangeKey', { key, oldKey })
|
||||||
|
// TODO: this should not happen?
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleChangeValue (value, key) {
|
||||||
|
console.log('handleChangeValue', value, key)
|
||||||
|
json = value
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInputTextArea (event) {
|
||||||
|
console.log('on:input')
|
||||||
|
try {
|
||||||
|
json = JSON.parse(event.target.value)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="jsoneditor">
|
||||||
|
<div class="menu">
|
||||||
|
<Icon data={faSearch} /> Search: <input class="search-input" bind:value={searchText} />
|
||||||
|
</div>
|
||||||
|
<Node
|
||||||
|
value={json}
|
||||||
|
searchResult={searchResult}
|
||||||
|
expanded={true}
|
||||||
|
onChangeKey={handleChangeKey}
|
||||||
|
onChangeValue={handleChangeValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style type="text/scss">
|
||||||
|
@import './styles.scss';
|
||||||
|
|
||||||
|
.jsoneditor {
|
||||||
|
border: 1px solid gray;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 150px;
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
font-family: $font-family-menu;
|
||||||
|
font-size: $font-size;
|
||||||
|
padding: $menu-padding;
|
||||||
|
background: $theme-color;
|
||||||
|
color: $white;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
border: none;
|
||||||
|
font-family: $font-family-menu;
|
||||||
|
font-size: $font-size;
|
||||||
|
padding: $input-padding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -128,6 +128,128 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class='json-node'>
|
||||||
|
{#if type === 'array'}
|
||||||
|
<div class='header'>
|
||||||
|
<button class='expand' on:click={toggle}>
|
||||||
|
{#if expanded}
|
||||||
|
<Icon data={faCaretDown} />
|
||||||
|
{:else}
|
||||||
|
<Icon data={faCaretRight} />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{#if typeof key === 'string'}
|
||||||
|
<div
|
||||||
|
class="key {searchResult && searchResult[SEARCH_PROPERTY] ? 'search' : ''}"
|
||||||
|
contenteditable="true"
|
||||||
|
spellcheck="false"
|
||||||
|
on:input={handleKeyInput}
|
||||||
|
>
|
||||||
|
{escapedKey}
|
||||||
|
</div>
|
||||||
|
<div class="separator">:</div>
|
||||||
|
{/if}
|
||||||
|
{#if expanded}
|
||||||
|
<div class="delimiter">[</div>
|
||||||
|
{:else}
|
||||||
|
<div class="delimiter">[</div>
|
||||||
|
<button class="tag" on:click={() => expanded = true}>{value.length} items</button>
|
||||||
|
<div class="delimiter">]</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if expanded}
|
||||||
|
<div class="items">
|
||||||
|
{#each items as item, index (index)}
|
||||||
|
<svelte:self
|
||||||
|
key={index}
|
||||||
|
value={item}
|
||||||
|
searchResult={searchResult ? searchResult[index] : undefined}
|
||||||
|
onChangeKey={handleChangeKey}
|
||||||
|
onChangeValue={handleChangeValue}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
{#if limited}
|
||||||
|
<div>
|
||||||
|
(showing {limit} of {value.length} items <button on:click={handleShowAll}>show all</button>)
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<span class="delimiter">]</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else if type === 'object'}
|
||||||
|
<div class='header'>
|
||||||
|
<button class='expand' on:click={toggle}>
|
||||||
|
{#if expanded}
|
||||||
|
<Icon data={faCaretDown} />
|
||||||
|
{:else}
|
||||||
|
<Icon data={faCaretRight} />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{#if typeof key === 'string'}
|
||||||
|
<div
|
||||||
|
class="key {searchResult && searchResult[SEARCH_PROPERTY] ? 'search' : ''}"
|
||||||
|
contenteditable="true"
|
||||||
|
spellcheck="false"
|
||||||
|
on:input={handleKeyInput}
|
||||||
|
>
|
||||||
|
{escapedKey}
|
||||||
|
</div>
|
||||||
|
<span class="separator">:</span>
|
||||||
|
{/if}
|
||||||
|
{#if expanded}
|
||||||
|
<span class="delimiter">{</span>
|
||||||
|
{:else}
|
||||||
|
<span class="delimiter"> {</span>
|
||||||
|
<button class="tag" on:click={() => expanded = true}>{props.length} props</button>
|
||||||
|
<span class="delimiter">}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if expanded}
|
||||||
|
<div class="props">
|
||||||
|
{#each props as prop (prop.id)}
|
||||||
|
<svelte:self
|
||||||
|
key={prop.key}
|
||||||
|
value={value[prop.key]}
|
||||||
|
searchResult={searchResult ? searchResult[prop.key] : undefined}
|
||||||
|
onChangeKey={handleChangeKey}
|
||||||
|
onChangeValue={handleChangeValue}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<span class="delimiter">}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="contents">
|
||||||
|
{#if typeof key === 'string'}
|
||||||
|
<div
|
||||||
|
class="key {searchResult && searchResult[SEARCH_PROPERTY] ? 'search' : ''}"
|
||||||
|
contenteditable="true"
|
||||||
|
spellcheck="false"
|
||||||
|
on:input={handleKeyInput}
|
||||||
|
>
|
||||||
|
{escapedKey}
|
||||||
|
</div>
|
||||||
|
<span class="separator">:</span>
|
||||||
|
{/if}
|
||||||
|
<div
|
||||||
|
class={valueClass}
|
||||||
|
contenteditable="true"
|
||||||
|
spellcheck="false"
|
||||||
|
on:input={handleValueInput}
|
||||||
|
on:click={handleValueClick}
|
||||||
|
on:keydown={handleValueKeyDown}
|
||||||
|
title={valueIsUrl ? 'Ctrl+Click or Ctrl+Enter to open url in new window' : null}
|
||||||
|
>
|
||||||
|
{escapedValue}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style type="text/scss">
|
<style type="text/scss">
|
||||||
@import './styles.scss';
|
@import './styles.scss';
|
||||||
|
|
||||||
|
@ -281,125 +403,3 @@
|
||||||
background-color: $highlight-color;
|
background-color: $highlight-color;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class='json-node'>
|
|
||||||
{#if type === 'array'}
|
|
||||||
<div class='header'>
|
|
||||||
<button class='expand' on:click={toggle}>
|
|
||||||
{#if expanded}
|
|
||||||
<Icon data={faCaretDown} />
|
|
||||||
{:else}
|
|
||||||
<Icon data={faCaretRight} />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{#if typeof key === 'string'}
|
|
||||||
<div
|
|
||||||
class="key {searchResult && searchResult[SEARCH_PROPERTY] ? 'search' : ''}"
|
|
||||||
contenteditable="true"
|
|
||||||
spellcheck="false"
|
|
||||||
on:input={handleKeyInput}
|
|
||||||
>
|
|
||||||
{escapedKey}
|
|
||||||
</div>
|
|
||||||
<div class="separator">:</div>
|
|
||||||
{/if}
|
|
||||||
{#if expanded}
|
|
||||||
<div class="delimiter">[</div>
|
|
||||||
{:else}
|
|
||||||
<div class="delimiter">[</div>
|
|
||||||
<button class="tag" on:click={() => expanded = true}>{value.length} items</button>
|
|
||||||
<div class="delimiter">]</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if expanded}
|
|
||||||
<div class="items">
|
|
||||||
{#each items as item, index (index)}
|
|
||||||
<svelte:self
|
|
||||||
key={index}
|
|
||||||
value={item}
|
|
||||||
searchResult={searchResult ? searchResult[index] : undefined}
|
|
||||||
onChangeKey={handleChangeKey}
|
|
||||||
onChangeValue={handleChangeValue}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
{#if limited}
|
|
||||||
<div>
|
|
||||||
(showing {limit} of {value.length} items <button on:click={handleShowAll}>show all</button>)
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<span class="delimiter">]</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{:else if type === 'object'}
|
|
||||||
<div class='header'>
|
|
||||||
<button class='expand' on:click={toggle}>
|
|
||||||
{#if expanded}
|
|
||||||
<Icon data={faCaretDown} />
|
|
||||||
{:else}
|
|
||||||
<Icon data={faCaretRight} />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{#if typeof key === 'string'}
|
|
||||||
<div
|
|
||||||
class="key {searchResult && searchResult[SEARCH_PROPERTY] ? 'search' : ''}"
|
|
||||||
contenteditable="true"
|
|
||||||
spellcheck="false"
|
|
||||||
on:input={handleKeyInput}
|
|
||||||
>
|
|
||||||
{escapedKey}
|
|
||||||
</div>
|
|
||||||
<span class="separator">:</span>
|
|
||||||
{/if}
|
|
||||||
{#if expanded}
|
|
||||||
<span class="delimiter">{</span>
|
|
||||||
{:else}
|
|
||||||
<span class="delimiter"> {</span>
|
|
||||||
<button class="tag" on:click={() => expanded = true}>{props.length} props</button>
|
|
||||||
<span class="delimiter">}</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if expanded}
|
|
||||||
<div class="props">
|
|
||||||
{#each props as prop (prop.id)}
|
|
||||||
<svelte:self
|
|
||||||
key={prop.key}
|
|
||||||
value={value[prop.key]}
|
|
||||||
searchResult={searchResult ? searchResult[prop.key] : undefined}
|
|
||||||
onChangeKey={handleChangeKey}
|
|
||||||
onChangeValue={handleChangeValue}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<span class="delimiter">}</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<div class="contents">
|
|
||||||
{#if typeof key === 'string'}
|
|
||||||
<div
|
|
||||||
class="key {searchResult && searchResult[SEARCH_PROPERTY] ? 'search' : ''}"
|
|
||||||
contenteditable="true"
|
|
||||||
spellcheck="false"
|
|
||||||
on:input={handleKeyInput}
|
|
||||||
>
|
|
||||||
{escapedKey}
|
|
||||||
</div>
|
|
||||||
<span class="separator">:</span>
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
class={valueClass}
|
|
||||||
contenteditable="true"
|
|
||||||
spellcheck="false"
|
|
||||||
on:input={handleValueInput}
|
|
||||||
on:click={handleValueClick}
|
|
||||||
on:keydown={handleValueKeyDown}
|
|
||||||
title={valueIsUrl ? 'Ctrl+Click or Ctrl+Enter to open url in new window' : null}
|
|
||||||
>
|
|
||||||
{escapedValue}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
|
@ -30,3 +30,5 @@ $input-padding: 5px;
|
||||||
$line-height: 18px;
|
$line-height: 18px;
|
||||||
$indentation-width: 18px;
|
$indentation-width: 18px;
|
||||||
$input-padding: 5px;
|
$input-padding: 5px;
|
||||||
|
|
||||||
|
$menu-padding: 5px;
|
||||||
|
|
Loading…
Reference in New Issue