Split App into JSONEditor and App

This commit is contained in:
josdejong 2020-05-04 21:50:32 +02:00
parent a4c58cfa15
commit 9b5891d5cb
4 changed files with 315 additions and 279 deletions

View File

@ -1,144 +1,119 @@
<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'
import JSONEditor from './JSONEditor.svelte'
import { beforeUpdate, afterUpdate } from 'svelte'
export let searchText = ''
let json = {
'array': [1, 2, 3, {
name: 'Item ' + 2,
id: String(2),
index: 2,
time: new Date().toISOString(),
location: {
latitude: 1.23,
longitude: 23.44,
coordinates: [23.44, 1.23]
}
}],
'boolean': true,
'color': '#82b92c',
'null': null,
'number': 123,
'object': {
'a': 'b', 'c': 'd', nested: {
name: 'Item ' + 2,
id: String(2),
index: 2,
time: new Date().toISOString(),
location: {
latitude: 1.23,
longitude: 23.44,
coordinates: [23.44, 1.23]
}
}
},
'string': 'Hello World',
'url': 'https://jsoneditoronline.org'
}
export let json = {
'array': [1, 2, 3, {
name: 'Item ' + 2,
id: String(2),
index: 2,
time: new Date().toISOString(),
location: {
latitude: 1.23,
longitude: 23.44,
coordinates: [23.44, 1.23]
},
}],
'boolean': true,
'color': '#82b92c',
'null': null,
'number': 123,
'object': {'a': 'b', 'c': 'd', nested: {
name: 'Item ' + 2,
id: String(2),
index: 2,
time: new Date().toISOString(),
location: {
latitude: 1.23,
longitude: 23.44,
coordinates: [23.44, 1.23]
},
}},
'string': 'Hello World',
'url': 'https://jsoneditoronline.org'
}
export function get() {
return json
}
let uniDirectionalValue = 'test uni directional flow in Svelte';
console.time('load editor')
export function set(newJson) {
json = newJson
}
beforeUpdate(() => console.time('render app'))
afterUpdate(() => console.timeEnd('render app'))
console.time('load editor')
// console.log('search results "d"', search(null, json, 'd'))
// console.log('search results "e"', search(null, json, 'e'))
// console.log('search results "2"', search(null, json, '2'))
beforeUpdate(() => console.time('render app'))
afterUpdate(() => console.timeEnd('render app'))
function doSearch (json, searchText) {
console.time('search')
const result = search(null, json, searchText)
console.timeEnd('search')
return result
}
function loadLargeJson() {
const count = 500
$: searchResult = searchText ? doSearch(json, searchText) : undefined
console.log('create large json', {count})
console.time('create large json')
const largeJson = {}
largeJson.numbers = []
largeJson.array = []
for (let i = 0; i < count; i++) {
const longitude = 4 + i / count
const latitude = 51 + i / count
$: formattedName = `"${name}"`
largeJson.numbers.push(i)
largeJson.array.push({
name: 'Item ' + i,
id: String(i),
index: i,
time: new Date().toISOString(),
location: {
latitude,
longitude,
coordinates: [longitude, latitude]
},
random: Math.random()
})
}
console.timeEnd('create large json')
function loadLargeJson () {
const count = 500
// const stringifiedSize = JSON.stringify(largeJson).length
// console.log(`large json stringified size: ${filesize(stringifiedSize)}`)
console.log('create large json', { count })
console.time('create large json')
const largeJson = {}
largeJson.numbers = []
largeJson.array = []
for (let i = 0; i < count; i++) {
const longitude = 4 + i / count;
const latitude = 51 + i / count;
return largeJson
}
largeJson.numbers.push(i);
largeJson.array.push({
name: 'Item ' + i,
id: String(i),
index: i,
time: new Date().toISOString(),
location: {
latitude,
longitude,
coordinates: [longitude, latitude]
},
random: Math.random()
})
}
console.timeEnd('create large json')
// json = loadLargeJson()
// const stringifiedSize = JSON.stringify(largeJson).length
// console.log(`large json stringified size: ${filesize(stringifiedSize)}`)
function handleLoadLargeJson() {
json = loadLargeJson()
}
return largeJson
}
function handleClearJson() {
json = {}
}
// json = loadLargeJson()
function handleChange (json) {
console.log('App handleChange', json)
}
function handleChangeKey (key, oldKey) {
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()
}
function handleClearJson () {
json = {}
}
setTimeout(() => {
console.timeEnd('load editor')
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
}
setTimeout(() => {
console.timeEnd('load editor')
console.log('loaded')
})
</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>
html, body {
position: relative;
@ -163,38 +138,5 @@
.editor {
width: 500px;
height: 500px;
overflow: auto;
border: 1px solid lightgray;
}
</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> -->

92
src/JSONEditor.svelte Normal file
View File

@ -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>

View File

@ -128,6 +128,128 @@
}
</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">&#123;</span>
{:else}
<span class="delimiter"> &#123;</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">
@import './styles.scss';
@ -281,125 +403,3 @@
background-color: $highlight-color;
}
</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">&#123;</span>
{:else}
<span class="delimiter"> &#123;</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>

View File

@ -29,4 +29,6 @@ $input-padding: 5px;
$line-height: 18px;
$indentation-width: 18px;
$input-padding: 5px;
$input-padding: 5px;
$menu-padding: 5px;