From 9eb944d92696994dbff920cc2b06f15c41748bb5 Mon Sep 17 00:00:00 2001 From: jos Date: Mon, 1 Jan 2018 19:32:30 +0100 Subject: [PATCH] Rendering more like JSON --- src/jsoneditor/components/JSONNode.js | 108 ++++++++++++++------ src/jsoneditor/components/jsoneditor.css | 82 ++++++++------- src/jsoneditor/components/jsoneditor.scss | 117 +++++++++------------- 3 files changed, 162 insertions(+), 145 deletions(-) diff --git a/src/jsoneditor/components/JSONNode.js b/src/jsoneditor/components/JSONNode.js index 025aed0..1bdfaad 100644 --- a/src/jsoneditor/components/JSONNode.js +++ b/src/jsoneditor/components/JSONNode.js @@ -52,7 +52,6 @@ export default class JSONNode extends PureComponent { static propTypes = { prop: PropTypes.string, // in case of an object property - index: PropTypes.number, // in case of an array item value: PropTypes.oneOfType([ PropTypes.object, PropTypes.array ]).isRequired, emit: PropTypes.func.isRequired, @@ -60,7 +59,6 @@ export default class JSONNode extends PureComponent { // options options: PropTypes.shape({ - name: PropTypes.string, // name of the root item isPropertyEditable: PropTypes.func, isValueEditable: PropTypes.func, escapeUnicode: PropTypes.bool @@ -96,14 +94,22 @@ export default class JSONNode extends PureComponent { renderJSONObject () { const meta = this.props.value[META] const props = meta.props - const node = h('div', { + const nodeStart = h('div', { key: 'node', onKeyDown: this.handleKeyDown, className: 'jsoneditor-node jsoneditor-object' }, [ this.renderExpandButton(), + // this.renderDelimiter('\u2610'), this.renderProperty(), - this.renderReadonly(`{${props.length}}`, `Object containing ${props.length} items`), + this.renderSeparator(), + this.renderDelimiter('{', 'jsoneditor-delimiter-start'), + !meta.expanded + ? [ + this.renderTag(`${props.length} props`, `Object containing ${props.length} properties`), + this.renderDelimiter('}', 'jsoneditor-delimiter-start'), + ] + : null, this.renderError(meta.error) ]) @@ -130,34 +136,45 @@ export default class JSONNode extends PureComponent { const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_OBJECT, meta.selected) const insertArea = this.renderInsertBeforeArea() + const nodeEnd = meta.expanded + ? this.renderDelimiter('}', 'jsoneditor-delimiter-end') + : null return h('div', { 'data-path': compileJSONPointer(meta.path), className: this.getContainerClassName(meta.selected, this.state.hover), onMouseOver: this.handleMouseOver, onMouseLeave: this.handleMouseLeave - }, [node, floatingMenu, insertArea, childs]) + }, [nodeStart, floatingMenu, insertArea, childs, nodeEnd]) } renderJSONArray () { const meta = this.props.value[META] - const node = h('div', { + const count = this.props.value.length + const nodeStart = h('div', { key: 'node', onKeyDown: this.handleKeyDown, className: 'jsoneditor-node jsoneditor-array' }, [ this.renderExpandButton(), + // this.renderDelimiter('\u2611'), this.renderProperty(), - this.renderReadonly(`[${this.props.value.length}]`, `Array containing ${this.props.value.length} items`), + this.renderSeparator(), + this.renderDelimiter('[', 'jsoneditor-delimiter-start'), + !meta.expanded + ? [ + this.renderTag(`${count} items`, `Array containing ${count} items`), + this.renderDelimiter(']', 'jsoneditor-delimiter-start'), + ] + : null, this.renderError(meta.error) ]) let childs if (meta.expanded) { - if (this.props.value.length > 0) { - const items = this.props.value.map((item, index) => h(this.constructor, { + if (count > 0) { + const items = this.props.value.map(item => h(this.constructor, { key : item[META].id, - index, value: item, options: this.props.options, emit: this.props.emit, @@ -175,13 +192,16 @@ export default class JSONNode extends PureComponent { const floatingMenu = this.renderFloatingMenu(MENU_ITEMS_ARRAY, meta.selected) const insertArea = this.renderInsertBeforeArea() + const nodeEnd = meta.expanded + ? this.renderDelimiter(']', 'jsoneditor-delimiter-end') + : null return h('div', { 'data-path': compileJSONPointer(meta.path), className: this.getContainerClassName(meta.selected, this.state.hover), onMouseOver: this.handleMouseOver, onMouseLeave: this.handleMouseLeave - }, [node, floatingMenu, insertArea, childs]) + }, [nodeStart, floatingMenu, insertArea, childs, nodeEnd]) } renderJSONValue () { @@ -192,9 +212,12 @@ export default class JSONNode extends PureComponent { className: 'jsoneditor-node' }, [ this.renderPlaceholder(), + // this.renderDelimiter('\u2611'), this.renderProperty(), this.renderSeparator(), this.renderValue(meta.value, meta.searchValue, this.props.options), + // this.renderDelimiter('\u21B2'), + // this.renderDelimiter('\u23CE'), this.renderError(meta.error) ]) @@ -247,54 +270,73 @@ export default class JSONNode extends PureComponent { return h('div', {key: 'readonly', className: 'jsoneditor-readonly', title}, text) } + renderTag (text, title = null) { + return h('div', { + key: 'readonly', + className: 'jsoneditor-tag', + onClick: this.handleExpand, + title + }, text) + } + // TODO: simplify the method renderProperty /** * Render a property field of a JSONNode */ renderProperty () { - const isIndex = typeof this.props.index === 'number' const isProp = typeof this.props.prop === 'string' - if (!isProp && !isIndex) { - // root node - const rootName = JSONNode.getRootName(this.props.value, this.props.options) - - return h('div', { - key: 'property', - className: 'jsoneditor-property jsoneditor-readonly', - spellCheck: 'false', - onBlur: this.handleChangeProperty - }, rootName) + if (!isProp) { + return null } - const editable = !isIndex && (!this.props.options.isPropertyEditable || this.props.options.isPropertyEditable(this.props.value[META].path)) + const editable = !this.props.options.isPropertyEditable || + this.props.options.isPropertyEditable(this.props.value[META].path) const emptyClassName = (this.props.prop != null && this.props.prop.length === 0) ? ' jsoneditor-empty' : '' const searchClassName = this.props.prop != null ? JSONNode.getSearchResultClass(this.props.value[META].searchProperty) : '' const escapedPropName = this.props.prop != null ? escapeHTML(this.props.prop, this.props.options.escapeUnicode) : null if (editable) { - return h('div', { - key: 'property', - className: 'jsoneditor-property' + emptyClassName + searchClassName, - contentEditable: 'true', - suppressContentEditableWarning: true, - spellCheck: 'false', - onBlur: this.handleChangeProperty - }, escapedPropName) + return [ + // this.renderDelimiter('"'), + h('div', { + key: 'property', + className: 'jsoneditor-property' + emptyClassName + searchClassName, + contentEditable: 'true', + suppressContentEditableWarning: true, + spellCheck: 'false', + onBlur: this.handleChangeProperty + }, escapedPropName), + // this.renderDelimiter('" '), + ] } else { return h('div', { key: 'property', className: 'jsoneditor-property jsoneditor-readonly' + searchClassName, spellCheck: 'false' - }, isIndex ? this.props.index : escapedPropName) + }, escapedPropName) } } renderSeparator() { - return h('div', {key: 'separator', className: 'jsoneditor-separator'}, ':') + const isProp = typeof this.props.prop === 'string' + if (!isProp) { + return null + } + + return h('div', {key: 'separator', className: 'jsoneditor-delimiter'}, ':') + } + + renderComma() { + // TODO: don't render when it's the last item/prop + return h('div', {key: 'comma', className: 'jsoneditor-delimiter'}, ',') + } + + renderDelimiter (text, className = '') { + return h('div', {key: text, className: 'jsoneditor-delimiter ' + className}, text) } renderValue (value, searchResult, options) { diff --git a/src/jsoneditor/components/jsoneditor.css b/src/jsoneditor/components/jsoneditor.css index d56aa6f..9175681 100644 --- a/src/jsoneditor/components/jsoneditor.css +++ b/src/jsoneditor/components/jsoneditor.css @@ -179,26 +179,28 @@ div.jsoneditor-list { /* no left padding for the root div element */ .jsoneditor-contents > div.jsoneditor-list { - padding-left: 2px; - padding-bottom: 24px; } + padding-left: 2px; } .jsoneditor-property, .jsoneditor-value, .jsoneditor-readonly, -.jsoneditor-separator { - line-height: 20px; +.jsoneditor-delimiter { + line-height: 18px; font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif; font-size: 10pt; } .jsoneditor-property, .jsoneditor-value, .jsoneditor-readonly { - min-width: 24px; + min-width: 16px; word-break: normal; padding: 0 5px; color: #1A1A1A; outline: none; } +.jsoneditor-readonly { + min-width: auto; } + .jsoneditor-button-container { font-size: 0; } @@ -227,24 +229,39 @@ div.jsoneditor-list { .jsoneditor-mode-view .jsoneditor-value:hover { background-color: inherit; } -.jsoneditor-separator { - color: #808080; } - +.jsoneditor-delimiter, .jsoneditor-readonly { - color: #808080; } + color: #9d9d9d; } + +.jsoneditor-delimiter-start { + margin-left: 5px; } + +.jsoneditor-delimiter-end { + margin-left: 25px; } .jsoneditor-readonly:focus, .jsoneditor-readonly:hover { border-color: transparent; background-color: inherit; } +.jsoneditor-tag { + color: white; + background: #c0c0c0; + border-radius: 2px; + padding: 0 4px; + margin-left: 5px; + margin-top: 2px; + height: 14px; + line-height: 14px; + font-size: 80%; } + .jsoneditor-value.jsoneditor-string { color: #008000; } .jsoneditor-value.jsoneditor-object, .jsoneditor-value.jsoneditor-array { min-width: 16px; - color: #808080; } + color: #9d9d9d; } .jsoneditor-value.jsoneditor-number { color: #ee422e; } @@ -536,46 +553,25 @@ div.jsoneditor-node-container { div.jsoneditor-node-container div.jsoneditor-insert-area:after { content: ''; position: absolute; - top: -7px; + top: -6px; right: 0; width: 60px; - height: 20px; + height: 18px; background: inherit; } - div.jsoneditor-node-container.jsoneditor-selected { - background-color: #ffed99; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover { - background-color: #ffdb80; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area-after { - background-color: #ffed99; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area-after > div.jsoneditor-insert-area { - border-color: #e5e5e5; - background-color: #e5e5e5; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area-after.jsoneditor-selected-insert-area-before > div.jsoneditor-insert-area { - border-color: #ffdb80; - background-color: #ffdb80; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before { - background-color: inherit; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before.jsoneditor-hover { - background-color: #e5e5e5; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before.jsoneditor-hover.jsoneditor-hover-insert-area-after { - background-color: inherit; } - div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-before > div.jsoneditor-insert-area { - border-color: #ffed99; - background-color: #ffed99; } - div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover { - background-color: #ffdb80; } - div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area-after { - background-color: inherit; } - div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area-after > div.jsoneditor-insert-area { - border-color: #ffdb80; - background-color: #ffdb80; } div.jsoneditor-node-container.jsoneditor-hover { - background-color: #e5e5e5; } + background-color: #d3d3d3; } div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area-after { background-color: inherit; } div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area-after > div.jsoneditor-insert-area { - border-color: #e5e5e5; - background-color: #e5e5e5; } + border-color: #d3d3d3; + background-color: #d3d3d3; } + div.jsoneditor-node-container.jsoneditor-selected { + background-color: #ffed99; } + div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-after { + background-color: inherit; } + div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area-after > div.jsoneditor-insert-area { + border-color: #ffed99; + background-color: #ffed99; } div.jsoneditor-node-container div.jsoneditor-floating-menu { position: absolute; bottom: 100%; diff --git a/src/jsoneditor/components/jsoneditor.scss b/src/jsoneditor/components/jsoneditor.scss index 30530c4..d72e0f8 100644 --- a/src/jsoneditor/components/jsoneditor.scss +++ b/src/jsoneditor/components/jsoneditor.scss @@ -7,15 +7,16 @@ $contentsMinHeight: 150px; $theme-color: #3883fa; // TODO: create a central file with the theme colors $floating-menu-background: #4d4d4d; $floating-menu-color: #fff; -// $selectedColor: #e5e5e5; $selectedColor: #ffed99; -//$hoverColor: #f2f2f2; -$hoverColor: #e5e5e5; +$hoverColor: #d3d3d3; $hoverAndSelectedColor: #ffdb80; +$gray: #9d9d9d; +$light-gray: #c0c0c0; +$input-padding: 5px; // TODO: split this scss file into separate files per React component -$line-height: 20px; +$line-height: 18px; $insert-area-height: 6px; .jsoneditor { @@ -136,13 +137,12 @@ div.jsoneditor-list { /* no left padding for the root div element */ .jsoneditor-contents > div.jsoneditor-list { padding-left: 2px; - padding-bottom: 24px; } .jsoneditor-property, .jsoneditor-value, .jsoneditor-readonly, -.jsoneditor-separator { +.jsoneditor-delimiter { line-height: $line-height; font-family: $fontFamily; @@ -152,15 +152,19 @@ div.jsoneditor-list { .jsoneditor-property, .jsoneditor-value, .jsoneditor-readonly { - min-width: 24px; + min-width: 16px; word-break: normal; - padding: 0 5px; + padding: 0 $input-padding; color: $black; outline: none; } +.jsoneditor-readonly { + min-width: auto; +} + .jsoneditor-button-container { font-size: 0; } @@ -201,12 +205,17 @@ div.jsoneditor-list { } -.jsoneditor-separator { - color: #808080; +.jsoneditor-delimiter, +.jsoneditor-readonly { + color: $gray; } -.jsoneditor-readonly { - color: #808080; +.jsoneditor-delimiter-start { + margin-left: $input-padding; +} + +.jsoneditor-delimiter-end { + margin-left: $line-height + $input-padding + 2px; } .jsoneditor-readonly:focus, @@ -215,6 +224,18 @@ div.jsoneditor-list { background-color: inherit; } +.jsoneditor-tag { + color: white; + background: $light-gray; + border-radius: 2px; + padding: 0 4px; + margin-left: $input-padding; + margin-top: 2px; + height: 14px; + line-height: 14px; + font-size: 80%; +} + .jsoneditor-value.jsoneditor-string { color: #008000; @@ -223,7 +244,7 @@ div.jsoneditor-list { .jsoneditor-value.jsoneditor-object, .jsoneditor-value.jsoneditor-array { min-width: 16px; - color: #808080; + color: $gray; } .jsoneditor-value.jsoneditor-number { @@ -250,7 +271,7 @@ div.jsoneditor-value.jsoneditor-url { div.jsoneditor-empty { border: 1px dotted lightgray; border-radius: 2px; - padding: 0 5px; + padding: 0 $input-padding; line-height: 17px; } @@ -609,61 +630,6 @@ div.jsoneditor-node-container { } } - &.jsoneditor-selected { - background-color: $selectedColor; - - &.jsoneditor-hover { - background-color: $hoverAndSelectedColor; - - &.jsoneditor-hover-insert-area-after { - background-color: $selectedColor; - - > div.jsoneditor-insert-area { - border-color: $hoverColor; - background-color: $hoverColor; - } - - &.jsoneditor-selected-insert-area-before { - > div.jsoneditor-insert-area { - border-color: $hoverAndSelectedColor; - background-color: $hoverAndSelectedColor; - } - } - } - } - - &.jsoneditor-selected-insert-area-before { - background-color: inherit; - - &.jsoneditor-hover { - background-color: $hoverColor; - - &.jsoneditor-hover-insert-area-after { - background-color: inherit; - } - } - - > div.jsoneditor-insert-area { - border-color: $selectedColor; - background-color: $selectedColor; - } - } - - // hovering nested elements - div.jsoneditor-hover { - background-color: $hoverAndSelectedColor; - - &.jsoneditor-hover-insert-area-after { - background-color: inherit; - - > div.jsoneditor-insert-area { - border-color: $hoverAndSelectedColor; - background-color: $hoverAndSelectedColor; - } - } - } - } - &.jsoneditor-hover { background-color: $hoverColor; @@ -677,6 +643,19 @@ div.jsoneditor-node-container { } } + &.jsoneditor-selected { + background-color: $selectedColor; + + &.jsoneditor-selected-insert-area-after { + background-color: inherit; + + > div.jsoneditor-insert-area { + border-color: $selectedColor; + background-color: $selectedColor; + } + } + } + div.jsoneditor-floating-menu { position: absolute; bottom: 100%;