From 955c6c4fec9c948088c46aaa87c3cefa7872c2e0 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Wed, 27 Mar 2019 14:59:03 -0700 Subject: [PATCH 01/12] init --- lib/getStaticTypes.js | 14 ++- src/Playroom/CodeMirror-JSX.js | 205 +++++++++++++++++++++++++++++++++ src/Playroom/Playroom.js | 3 +- src/Playroom/Playroom.less | 14 +++ 4 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 src/Playroom/CodeMirror-JSX.js diff --git a/lib/getStaticTypes.js b/lib/getStaticTypes.js index 61a8a7a8..09984907 100644 --- a/lib/getStaticTypes.js +++ b/lib/getStaticTypes.js @@ -33,11 +33,15 @@ module.exports = async playroomConfig => { const files = await fastGlob(typeScriptFiles, { cwd }); const types = parse(files); const typesByDisplayName = keyBy(types, 'displayName'); - const parsedTypes = mapValues(typesByDisplayName, component => - mapValues(filterProps(component.props || {}), prop => - parsePropTypeName(prop.type.name) - ) - ); + const parsedTypes = mapValues(typesByDisplayName, component => ({ + component_description: component.description, + ...mapValues(filterProps(component.props || {}), prop => ({ + description: prop.description, + defaultValue: prop.defaultValue && prop.defaultValue.value, + required: prop.required, + values: parsePropTypeName(prop.type.name) + })) + })); return parsedTypes; } catch (err) { diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js new file mode 100644 index 00000000..4c5a4d1e --- /dev/null +++ b/src/Playroom/CodeMirror-JSX.js @@ -0,0 +1,205 @@ +/* eslint-disable */ + +import styles from './Playroom.less'; + +function matches(hint, typed, matchInMiddle) { + if (matchInMiddle) return hint.indexOf(typed) >= 0; + else return hint.lastIndexOf(typed, 0) == 0; +} + +function elt(tagname, cls /*, ... elts*/) { + var e = document.createElement(tagname); + + if (cls) e.className = cls; + + for (var i = 2; i < arguments.length; ++i) { + var elt = arguments[i]; + if (typeof elt == 'string') elt = document.createTextNode(elt); + e.appendChild(elt); + } + + return e; +} + +function makeTooltip(x, y, content) { + var node = elt('div', styles['description-tooltip'], content); + node.style.left = x + 'px'; + node.style.top = y + 'px'; + + document.body.appendChild(node); + return node; +} + +function remove(node) { + var p = node && node.parentNode; + if (p) p.removeChild(node); +} + +export default function getHints(cm, options) { + debugger; + const CodeMirror = cm.constructor; + + var tags = options && options.schemaInfo; + var quote = (options && options.quoteChar) || '"'; + var matchInMiddle = options && options.matchInMiddle; + if (!tags) return; + var cur = cm.getCursor(), + token = cm.getTokenAt(cur); + if (token.end > cur.ch) { + token.end = cur.ch; + token.string = token.string.slice(0, cur.ch - token.start); + } + var inner = CodeMirror.innerMode(cm.getMode(), token.state); + if (inner.mode.name != 'xml') return; + var result = [], + replaceToken = false, + prefix; + var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string); + var tagName = tag && /^\w/.test(token.string), + tagStart; + + if (tagName) { + var before = cm + .getLine(cur.line) + .slice(Math.max(0, token.start - 2), token.start); + var tagType = /<\/$/.test(before) + ? 'close' + : /<$/.test(before) + ? 'open' + : null; + if (tagType) tagStart = token.start - (tagType == 'close' ? 2 : 1); + } else if (tag && token.string == '<') { + tagType = 'open'; + } else if (tag && token.string == ''); + } else { + // Attribute completion + var curTag = tags[inner.state.tagName], + attrs = curTag && curTag.attrs; + var globalAttrs = tags['!attrs']; + if (!attrs && !globalAttrs) return; + if (!attrs) { + attrs = globalAttrs; + } else if (globalAttrs) { + // Combine tag-local and global attributes + var set = {}; + for (var nm in globalAttrs) + if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm]; + for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm]; + attrs = set; + } + if (token.type == 'string' || token.string == '=') { + // A value + var before = cm.getRange( + CodeMirror.Pos(cur.line, Math.max(0, cur.ch - 60)), + CodeMirror.Pos( + cur.line, + token.type == 'string' ? token.start : token.end + ) + ); + var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), + atValues; + if ( + !atName || + !attrs.hasOwnProperty(atName[1]) || + !(atValues = attrs[atName[1]].values) + ) + return; + if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget + if (token.type == 'string') { + prefix = token.string; + var n = 0; + if (/['"]/.test(token.string.charAt(0))) { + quote = token.string.charAt(0); + prefix = token.string.slice(1); + n++; + } + var len = token.string.length; + if (/['"]/.test(token.string.charAt(len - 1))) { + quote = token.string.charAt(len - 1); + prefix = token.string.substr(n, len - 2); + } + if (n) { + // an opening quote + var line = cm.getLine(cur.line); + if (line.length > token.end && line.charAt(token.end) == quote) + token.end++; // include a closing quote + } + replaceToken = true; + } + for (var i = 0; i < atValues.length; ++i) + if (!prefix || matches(atValues[i], prefix, matchInMiddle)) + result.push(quote + atValues[i] + quote); + } else { + // An attribute name + if (token.type == 'attribute') { + prefix = token.string; + replaceToken = true; + } + for (var attr in attrs) + if ( + attrs.hasOwnProperty(attr) && + (!prefix || matches(attr, prefix, matchInMiddle)) + ) + result.push({ text: attr, ...attrs[attr] }); + } + } + + const obj = { + list: result, + from: replaceToken + ? CodeMirror.Pos(cur.line, tagStart == null ? token.start : tagStart) + : cur, + to: replaceToken ? CodeMirror.Pos(cur.line, token.end) : cur + }; + + let tooltip = null; + + CodeMirror.on(obj, 'close', () => remove(tooltip)); + CodeMirror.on(obj, 'update', () => remove(tooltip)); + CodeMirror.on(obj, 'select', (cur, node) => { + remove(tooltip); + + if (cur && cur.description) { + tooltip = makeTooltip( + node.parentNode.getBoundingClientRect().right + window.pageXOffset, + node.getBoundingClientRect().top + window.pageYOffset, + cur.description + ); + tooltip.className += ' hint-doc'; + } + }); + + return obj; +} diff --git a/src/Playroom/Playroom.js b/src/Playroom/Playroom.js index 69a8099e..c439ee8e 100644 --- a/src/Playroom/Playroom.js +++ b/src/Playroom/Playroom.js @@ -14,6 +14,7 @@ import { store } from '../index'; import WindowPortal from './WindowPortal'; import UndockSvg from '../assets/icons/NewWindowSvg'; import { formatCode } from '../utils/formatting'; +import getHints from './CodeMirror-JSX'; import codeMirror from 'codemirror'; import ReactCodeMirror from 'react-codemirror'; @@ -39,7 +40,7 @@ const completeAfter = (cm, predicate) => { if (!predicate || predicate()) { setTimeout(() => { if (!cm.state.completionActive) { - cm.showHint({ completeSingle: false }); + cm.showHint({ completeSingle: false, hint: getHints }); } }, 100); } diff --git a/src/Playroom/Playroom.less b/src/Playroom/Playroom.less index 03121bf5..ed8395b2 100644 --- a/src/Playroom/Playroom.less +++ b/src/Playroom/Playroom.less @@ -176,3 +176,17 @@ } } } + +.description-tooltip { + position: absolute; + background: white; + border-radius: 3px; + font-family: monospace; + white-space: pre-wrap; + padding: 2px 5px; + z-index: 10; + box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + border: 1px solid silver; + max-width: 25em; + margin-top: -3px; +} From cb180f92f85429431240023c6636152e5a902723 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Thu, 28 Mar 2019 19:14:55 -0700 Subject: [PATCH 02/12] required types --- src/Playroom/CodeMirror-JSX.js | 18 ++++++++++++++---- src/Playroom/Playroom.less | 9 +++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index 4c5a4d1e..ac9a1028 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -21,8 +21,17 @@ function elt(tagname, cls /*, ... elts*/) { return e; } -function makeTooltip(x, y, content) { - var node = elt('div', styles['description-tooltip'], content); +function makeTooltip(x, y, data) { + const content = [ + elt('span', styles['description-tooltip-text'], data.description) + ]; + + if (data.required) { + content.unshift(elt('span', styles['description-tooltip-required'], 'ⓘ')); + } + + const node = elt('div', styles['description-tooltip'], ...content); + node.style.left = x + 'px'; node.style.top = y + 'px'; @@ -36,7 +45,6 @@ function remove(node) { } export default function getHints(cm, options) { - debugger; const CodeMirror = cm.constructor; var tags = options && options.schemaInfo; @@ -85,6 +93,7 @@ export default function getHints(cm, options) { if (!prefix || matches(childList[i], prefix, matchInMiddle)) result.push('<' + childList[i]); } else if (tagType != 'close') { + // Component Identifier names for (var name in tags) if ( tags.hasOwnProperty(name) && @@ -170,6 +179,7 @@ export default function getHints(cm, options) { for (var attr in attrs) if ( attrs.hasOwnProperty(attr) && + attr !== 'component_description' && (!prefix || matches(attr, prefix, matchInMiddle)) ) result.push({ text: attr, ...attrs[attr] }); @@ -195,7 +205,7 @@ export default function getHints(cm, options) { tooltip = makeTooltip( node.parentNode.getBoundingClientRect().right + window.pageXOffset, node.getBoundingClientRect().top + window.pageYOffset, - cur.description + cur ); tooltip.className += ' hint-doc'; } diff --git a/src/Playroom/Playroom.less b/src/Playroom/Playroom.less index ed8395b2..10416767 100644 --- a/src/Playroom/Playroom.less +++ b/src/Playroom/Playroom.less @@ -183,10 +183,15 @@ border-radius: 3px; font-family: monospace; white-space: pre-wrap; - padding: 2px 5px; + padding: 4px 8px; z-index: 10; box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); border: 1px solid silver; max-width: 25em; - margin-top: -3px; + margin-left: 5px; + + .description-tooltip-required { + color: red; + padding-right: 5px; + } } From 3b65d4fbfaea02caba338860fa7b548d5d26fedb Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Thu, 28 Mar 2019 19:40:47 -0700 Subject: [PATCH 03/12] default + type --- lib/getStaticTypes.js | 3 ++- src/Playroom/CodeMirror-JSX.js | 19 +++++++++++++++++++ src/Playroom/Playroom.less | 18 ++++++++++++++---- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/getStaticTypes.js b/lib/getStaticTypes.js index 09984907..32d7d17e 100644 --- a/lib/getStaticTypes.js +++ b/lib/getStaticTypes.js @@ -37,8 +37,9 @@ module.exports = async playroomConfig => { component_description: component.description, ...mapValues(filterProps(component.props || {}), prop => ({ description: prop.description, - defaultValue: prop.defaultValue && prop.defaultValue.value, + default: prop.defaultValue && prop.defaultValue.value, required: prop.required, + type: prop.type.name.replace(/.\| undefined$/, ''), values: parsePropTypeName(prop.type.name) })) })); diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index ac9a1028..164915fc 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -21,15 +21,34 @@ function elt(tagname, cls /*, ... elts*/) { return e; } +function makeLabeledSection(label, value) { + return elt( + 'div', + styles['description-tooltip-default'], + elt('span', styles['description-tooltip-default-label'], label), + elt('span', styles['description-tooltip-default-value'], value) + ); +} + function makeTooltip(x, y, data) { const content = [ elt('span', styles['description-tooltip-text'], data.description) ]; + console.log(data); + if (data.required) { content.unshift(elt('span', styles['description-tooltip-required'], 'ⓘ')); } + if (data.default !== null) { + content.push(makeLabeledSection('Default:', data.default)); + } + + if (data.type !== null) { + content.push(makeLabeledSection('Type:', data.type)); + } + const node = elt('div', styles['description-tooltip'], ...content); node.style.left = x + 'px'; diff --git a/src/Playroom/Playroom.less b/src/Playroom/Playroom.less index 10416767..29b1eb6c 100644 --- a/src/Playroom/Playroom.less +++ b/src/Playroom/Playroom.less @@ -189,9 +189,19 @@ border: 1px solid silver; max-width: 25em; margin-left: 5px; +} - .description-tooltip-required { - color: red; - padding-right: 5px; - } +.description-tooltip-required { + color: red; + padding-right: 5px; +} + +.description-tooltip-default { + margin-top: 10px; +} + +.description-tooltip-default-label { + margin-right: 8px; + font-weight: 500; + color: rgba(0, 0, 0, 0.7); } From ecac118ab0e1596d47e79369e2b76325fd8684b8 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Thu, 28 Mar 2019 19:53:15 -0700 Subject: [PATCH 04/12] add some pizzaz --- src/Playroom/CodeMirror-JSX.js | 47 ++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index 164915fc..b733f6c3 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -21,32 +21,53 @@ function elt(tagname, cls /*, ... elts*/) { return e; } -function makeLabeledSection(label, value) { - return elt( - 'div', - styles['description-tooltip-default'], - elt('span', styles['description-tooltip-default-label'], label), - elt('span', styles['description-tooltip-default-value'], value) - ); -} - function makeTooltip(x, y, data) { const content = [ elt('span', styles['description-tooltip-text'], data.description) ]; - console.log(data); - if (data.required) { content.unshift(elt('span', styles['description-tooltip-required'], 'ⓘ')); } if (data.default !== null) { - content.push(makeLabeledSection('Default:', data.default)); + const value = elt( + 'span', + styles['description-tooltip-default-value'], + data.default + ); + + if (data.type === 'boolean') { + value.style.color = 'rebeccapurple'; + } + + if (data.type === 'string' || data.values.length > 0) { + value.style.color = 'darkred'; + } + + if (data.type === 'number') { + value.style.color = 'steelblue'; + } + + content.push( + elt( + 'div', + styles['description-tooltip-default'], + elt('span', styles['description-tooltip-default-label'], 'Default:'), + value + ) + ); } if (data.type !== null) { - content.push(makeLabeledSection('Type:', data.type)); + content.push( + elt( + 'div', + styles['description-tooltip-default'], + elt('span', styles['description-tooltip-default-label'], 'Type:'), + elt('span', styles['description-tooltip-default-value'], data.type) + ) + ); } const node = elt('div', styles['description-tooltip'], ...content); From 8d12c3ffda25a1117d01341b38533ea24bccf8be Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Thu, 28 Mar 2019 20:14:19 -0700 Subject: [PATCH 05/12] lint --- src/Playroom/CodeMirror-JSX.js | 239 ++++++++++++++++++------------- src/Playroom/CodeMirror-JSX.less | 28 ++++ src/Playroom/Playroom.less | 29 ---- 3 files changed, 169 insertions(+), 127 deletions(-) create mode 100644 src/Playroom/CodeMirror-JSX.less diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index b733f6c3..89255173 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -1,41 +1,43 @@ -/* eslint-disable */ - -import styles from './Playroom.less'; +/* eslint-disable new-cap */ +import styles from './CodeMirror-JSX.less'; function matches(hint, typed, matchInMiddle) { - if (matchInMiddle) return hint.indexOf(typed) >= 0; - else return hint.lastIndexOf(typed, 0) == 0; + if (matchInMiddle) { + return hint.indexOf(typed) >= 0; + } + + return hint.lastIndexOf(typed, 0) === 0; } -function elt(tagname, cls /*, ... elts*/) { - var e = document.createElement(tagname); +function elt(tagname, cls, ...elts) { + const e = document.createElement(tagname); - if (cls) e.className = cls; + if (cls) { + e.className = cls; + } + + for (let i = 2; i < elts.length; ++i) { + let element = elts[i]; - for (var i = 2; i < arguments.length; ++i) { - var elt = arguments[i]; - if (typeof elt == 'string') elt = document.createTextNode(elt); - e.appendChild(elt); + if (typeof element === 'string') { + element = document.createTextNode(element); + } + + e.appendChild(element); } return e; } function makeTooltip(x, y, data) { - const content = [ - elt('span', styles['description-tooltip-text'], data.description) - ]; + const content = [elt('span', null, data.description)]; if (data.required) { - content.unshift(elt('span', styles['description-tooltip-required'], 'ⓘ')); + content.unshift(elt('span', styles.required, 'ⓘ')); } if (data.default !== null) { - const value = elt( - 'span', - styles['description-tooltip-default-value'], - data.default - ); + const value = elt('span', null, data.default); if (data.type === 'boolean') { value.style.color = 'rebeccapurple'; @@ -52,8 +54,8 @@ function makeTooltip(x, y, data) { content.push( elt( 'div', - styles['description-tooltip-default'], - elt('span', styles['description-tooltip-default-label'], 'Default:'), + styles.default, + elt('span', styles.defaultLabel, 'Default:'), value ) ); @@ -63,173 +65,213 @@ function makeTooltip(x, y, data) { content.push( elt( 'div', - styles['description-tooltip-default'], - elt('span', styles['description-tooltip-default-label'], 'Type:'), - elt('span', styles['description-tooltip-default-value'], data.type) + styles.default, + elt('span', styles.defaultLabel, 'Type:'), + elt('span', null, data.type) ) ); } - const node = elt('div', styles['description-tooltip'], ...content); + const node = elt('div', styles.tooltip, ...content); - node.style.left = x + 'px'; - node.style.top = y + 'px'; + node.style.left = `${x}px`; + node.style.top = `${y}px`; document.body.appendChild(node); return node; } function remove(node) { - var p = node && node.parentNode; - if (p) p.removeChild(node); + const p = node && node.parentNode; + + if (p) { + p.removeChild(node); + } } export default function getHints(cm, options) { const CodeMirror = cm.constructor; - var tags = options && options.schemaInfo; - var quote = (options && options.quoteChar) || '"'; - var matchInMiddle = options && options.matchInMiddle; - if (!tags) return; - var cur = cm.getCursor(), + const tags = options && options.schemaInfo; + const matchInMiddle = options && options.matchInMiddle; + let quote = (options && options.quoteChar) || '"'; + + if (!tags) { + return; + } + + const cur = cm.getCursor(), token = cm.getTokenAt(cur); + if (token.end > cur.ch) { token.end = cur.ch; token.string = token.string.slice(0, cur.ch - token.start); } - var inner = CodeMirror.innerMode(cm.getMode(), token.state); - if (inner.mode.name != 'xml') return; - var result = [], + + const inner = CodeMirror.innerMode(cm.getMode(), token.state); + + if (inner.mode.name !== 'xml') { + return; + } + + const result = []; + const tag = /\btag\b/.test(token.type) && !/>$/.test(token.string); + const tagName = tag && /^\w/.test(token.string); + + let tagStart, + tagType, replaceToken = false, prefix; - var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string); - var tagName = tag && /^\w/.test(token.string), - tagStart; if (tagName) { - var before = cm + const before = cm .getLine(cur.line) .slice(Math.max(0, token.start - 2), token.start); - var tagType = /<\/$/.test(before) - ? 'close' - : /<$/.test(before) - ? 'open' - : null; - if (tagType) tagStart = token.start - (tagType == 'close' ? 2 : 1); - } else if (tag && token.string == '<') { + tagType = + (/<\/$/.test(before) && 'close') || (/<$/.test(before) && 'open') || null; + + if (tagType) { + tagStart = token.start - (tagType === 'close' ? 2 : 1); + } + } else if (tag && token.string === '<') { tagType = 'open'; - } else if (tag && token.string == ''); + (tagType === 'close' && matches(cx.tagName, prefix, matchInMiddle))) + ) { + result.push(``); + } } else { // Attribute completion - var curTag = tags[inner.state.tagName], - attrs = curTag && curTag.attrs; - var globalAttrs = tags['!attrs']; - if (!attrs && !globalAttrs) return; + const curTag = tags[inner.state.tagName]; + let attrs = curTag && curTag.attrs; + const globalAttrs = tags['!attrs']; + if (!attrs && !globalAttrs) { + return; + } if (!attrs) { attrs = globalAttrs; } else if (globalAttrs) { // Combine tag-local and global attributes - var set = {}; - for (var nm in globalAttrs) - if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm]; - for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm]; + const set = {}; + for (const nm in globalAttrs) { + if (globalAttrs.hasOwnProperty(nm)) { + set[nm] = globalAttrs[nm]; + } + } + for (const nm in attrs) { + if (attrs.hasOwnProperty(nm)) { + set[nm] = attrs[nm]; + } + } attrs = set; } - if (token.type == 'string' || token.string == '=') { + if (token.type === 'string' || token.string === '=') { // A value - var before = cm.getRange( + const before = cm.getRange( CodeMirror.Pos(cur.line, Math.max(0, cur.ch - 60)), CodeMirror.Pos( cur.line, - token.type == 'string' ? token.start : token.end + token.type === 'string' ? token.start : token.end ) ); - var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), - atValues; + const atName = before.match(/([^\s\u00a0=<>\"\']+)=$/); + let atValues; if ( !atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]].values) - ) + ) { return; - if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget - if (token.type == 'string') { + } + if (typeof atValues === 'function') { + atValues = atValues.call(this, cm); + } // Functions can be used to supply values for autocomplete widget + if (token.type === 'string') { prefix = token.string; - var n = 0; + let n = 0; if (/['"]/.test(token.string.charAt(0))) { quote = token.string.charAt(0); prefix = token.string.slice(1); n++; } - var len = token.string.length; + const len = token.string.length; if (/['"]/.test(token.string.charAt(len - 1))) { quote = token.string.charAt(len - 1); prefix = token.string.substr(n, len - 2); } if (n) { // an opening quote - var line = cm.getLine(cur.line); - if (line.length > token.end && line.charAt(token.end) == quote) - token.end++; // include a closing quote + const line = cm.getLine(cur.line); + if (line.length > token.end && line.charAt(token.end) === quote) { + token.end++; + } // include a closing quote } replaceToken = true; } - for (var i = 0; i < atValues.length; ++i) - if (!prefix || matches(atValues[i], prefix, matchInMiddle)) + for (let i = 0; i < atValues.length; ++i) { + if (!prefix || matches(atValues[i], prefix, matchInMiddle)) { result.push(quote + atValues[i] + quote); + } + } } else { // An attribute name - if (token.type == 'attribute') { + if (token.type === 'attribute') { prefix = token.string; replaceToken = true; } - for (var attr in attrs) + for (const attr in attrs) { if ( attrs.hasOwnProperty(attr) && attr !== 'component_description' && (!prefix || matches(attr, prefix, matchInMiddle)) - ) + ) { result.push({ text: attr, ...attrs[attr] }); + } + } } } const obj = { list: result, from: replaceToken - ? CodeMirror.Pos(cur.line, tagStart == null ? token.start : tagStart) + ? CodeMirror.Pos(cur.line, tagStart === null ? token.start : tagStart) : cur, to: replaceToken ? CodeMirror.Pos(cur.line, token.end) : cur }; @@ -238,18 +280,19 @@ export default function getHints(cm, options) { CodeMirror.on(obj, 'close', () => remove(tooltip)); CodeMirror.on(obj, 'update', () => remove(tooltip)); - CodeMirror.on(obj, 'select', (cur, node) => { + CodeMirror.on(obj, 'select', (data, node) => { remove(tooltip); - if (cur && cur.description) { + if (data && data.description) { tooltip = makeTooltip( node.parentNode.getBoundingClientRect().right + window.pageXOffset, node.getBoundingClientRect().top + window.pageYOffset, - cur + data ); tooltip.className += ' hint-doc'; } }); + // eslint-disable-next-line consistent-return return obj; } diff --git a/src/Playroom/CodeMirror-JSX.less b/src/Playroom/CodeMirror-JSX.less new file mode 100644 index 00000000..ae50c930 --- /dev/null +++ b/src/Playroom/CodeMirror-JSX.less @@ -0,0 +1,28 @@ +.tooltip { + position: absolute; + background: white; + border-radius: 3px; + font-family: monospace; + white-space: pre-wrap; + padding: 4px 8px; + z-index: 10; + box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); + border: 1px solid silver; + max-width: 25em; + margin-left: 5px; +} + +.required { + color: red; + padding-right: 5px; +} + +.default { + margin-top: 10px; +} + +.defaultLabel { + margin-right: 8px; + font-weight: 500; + color: rgba(0, 0, 0, 0.7); +} diff --git a/src/Playroom/Playroom.less b/src/Playroom/Playroom.less index 29b1eb6c..03121bf5 100644 --- a/src/Playroom/Playroom.less +++ b/src/Playroom/Playroom.less @@ -176,32 +176,3 @@ } } } - -.description-tooltip { - position: absolute; - background: white; - border-radius: 3px; - font-family: monospace; - white-space: pre-wrap; - padding: 4px 8px; - z-index: 10; - box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); - border: 1px solid silver; - max-width: 25em; - margin-left: 5px; -} - -.description-tooltip-required { - color: red; - padding-right: 5px; -} - -.description-tooltip-default { - margin-top: 10px; -} - -.description-tooltip-default-label { - margin-right: 8px; - font-weight: 500; - color: rgba(0, 0, 0, 0.7); -} From e7dc3d83db2ec749e150a3d0c9cc34295a4e25d1 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Thu, 28 Mar 2019 20:35:21 -0700 Subject: [PATCH 06/12] fix it after linting --- src/Playroom/CodeMirror-JSX.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index 89255173..f1558ea9 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -1,4 +1,5 @@ /* eslint-disable new-cap */ + import styles from './CodeMirror-JSX.less'; function matches(hint, typed, matchInMiddle) { @@ -9,15 +10,15 @@ function matches(hint, typed, matchInMiddle) { return hint.lastIndexOf(typed, 0) === 0; } -function elt(tagname, cls, ...elts) { +function elt(tagname, cls) { const e = document.createElement(tagname); if (cls) { e.className = cls; } - for (let i = 2; i < elts.length; ++i) { - let element = elts[i]; + for (let i = 2; i < arguments.length; ++i) { + let element = arguments[i]; if (typeof element === 'string') { element = document.createTextNode(element); @@ -216,7 +217,9 @@ export default function getHints(cm, options) { if ( !atName || !attrs.hasOwnProperty(atName[1]) || - !(atValues = attrs[atName[1]].values) + !(atValues = Array.isArray(attrs[atName[1]]) + ? attrs[atName[1]] + : attrs[atName[1]].values) ) { return; } @@ -271,7 +274,12 @@ export default function getHints(cm, options) { const obj = { list: result, from: replaceToken - ? CodeMirror.Pos(cur.line, tagStart === null ? token.start : tagStart) + ? CodeMirror.Pos( + cur.line, + tagStart === null || typeof tagStart === 'undefined' + ? token.start + : tagStart + ) : cur, to: replaceToken ? CodeMirror.Pos(cur.line, token.end) : cur }; From b2e275b5fc54ad81a9e4044a0bc482e8308035f7 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Thu, 28 Mar 2019 23:54:08 -0700 Subject: [PATCH 07/12] get rid of the copy pasta :tada: --- src/Playroom/CodeMirror-JSX.js | 246 ++++++--------------------------- src/Playroom/Playroom.js | 3 +- 2 files changed, 47 insertions(+), 202 deletions(-) diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index f1558ea9..f4c0947e 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -2,14 +2,6 @@ import styles from './CodeMirror-JSX.less'; -function matches(hint, typed, matchInMiddle) { - if (matchInMiddle) { - return hint.indexOf(typed) >= 0; - } - - return hint.lastIndexOf(typed, 0) === 0; -} - function elt(tagname, cls) { const e = document.createElement(tagname); @@ -37,7 +29,7 @@ function makeTooltip(x, y, data) { content.unshift(elt('span', styles.required, 'ⓘ')); } - if (data.default !== null) { + if (data.default !== null && typeof data.default !== 'undefined') { const value = elt('span', null, data.default); if (data.type === 'boolean') { @@ -62,7 +54,7 @@ function makeTooltip(x, y, data) { ); } - if (data.type !== null) { + if (data.type !== null && typeof data.type !== 'undefined') { content.push( elt( 'div', @@ -90,217 +82,69 @@ function remove(node) { } } +function prepareSchema(tags) { + return Object.keys(tags).reduce((all, tag) => { + all[tag] = { + ...tags[tag], + attrs: Object.keys(tags[tag].attrs).reduce((allAttrs, attr) => { + allAttrs[attr] = tags[tag].attrs[attr].values; + return allAttrs; + }, {}) + }; + + return all; + }, {}); +} + export default function getHints(cm, options) { const CodeMirror = cm.constructor; - const tags = options && options.schemaInfo; - const matchInMiddle = options && options.matchInMiddle; - let quote = (options && options.quoteChar) || '"'; + const hint = CodeMirror.hint.xml( + cm, + Object.assign({}, options, { + schemaInfo: prepareSchema(tags) + }) + ); - if (!tags) { - return; - } - - const cur = cm.getCursor(), - token = cm.getTokenAt(cur); - - if (token.end > cur.ch) { - token.end = cur.ch; - token.string = token.string.slice(0, cur.ch - token.start); - } - - const inner = CodeMirror.innerMode(cm.getMode(), token.state); - - if (inner.mode.name !== 'xml') { - return; - } + let tooltip = null; - const result = []; - const tag = /\btag\b/.test(token.type) && !/>$/.test(token.string); - const tagName = tag && /^\w/.test(token.string); + CodeMirror.on(hint, 'close', () => remove(tooltip)); + CodeMirror.on(hint, 'update', () => remove(tooltip)); + CodeMirror.on(hint, 'select', (data, node) => { + const cur = cm.getCursor(); + const token = cm.getTokenAt(cur); - let tagStart, - tagType, - replaceToken = false, - prefix; + if (token.end > cur.ch) { + token.end = cur.ch; + token.string = token.string.slice(0, cur.ch - token.start); + } - if (tagName) { - const before = cm - .getLine(cur.line) - .slice(Math.max(0, token.start - 2), token.start); - tagType = - (/<\/$/.test(before) && 'close') || (/<$/.test(before) && 'open') || null; + const inner = CodeMirror.innerMode(cm.getMode(), token.state); + let attr; - if (tagType) { - tagStart = token.start - (tagType === 'close' ? 2 : 1); + // Attribute + if (tags[inner.state.tagName]) { + attr = tags[inner.state.tagName].attrs[data]; } - } else if (tag && token.string === '<') { - tagType = 'open'; - } else if (tag && token.string === '`); - } - } else { - // Attribute completion - const curTag = tags[inner.state.tagName]; - let attrs = curTag && curTag.attrs; - const globalAttrs = tags['!attrs']; - if (!attrs && !globalAttrs) { - return; - } - if (!attrs) { - attrs = globalAttrs; - } else if (globalAttrs) { - // Combine tag-local and global attributes - const set = {}; - for (const nm in globalAttrs) { - if (globalAttrs.hasOwnProperty(nm)) { - set[nm] = globalAttrs[nm]; - } - } - for (const nm in attrs) { - if (attrs.hasOwnProperty(nm)) { - set[nm] = attrs[nm]; - } - } - attrs = set; + // Tag + if (data.match(/<\S+/)) { + attr = { + description: tags[data.slice(1)].attrs.component_description + }; } - if (token.type === 'string' || token.string === '=') { - // A value - const before = cm.getRange( - CodeMirror.Pos(cur.line, Math.max(0, cur.ch - 60)), - CodeMirror.Pos( - cur.line, - token.type === 'string' ? token.start : token.end - ) - ); - const atName = before.match(/([^\s\u00a0=<>\"\']+)=$/); - let atValues; - if ( - !atName || - !attrs.hasOwnProperty(atName[1]) || - !(atValues = Array.isArray(attrs[atName[1]]) - ? attrs[atName[1]] - : attrs[atName[1]].values) - ) { - return; - } - if (typeof atValues === 'function') { - atValues = atValues.call(this, cm); - } // Functions can be used to supply values for autocomplete widget - if (token.type === 'string') { - prefix = token.string; - let n = 0; - if (/['"]/.test(token.string.charAt(0))) { - quote = token.string.charAt(0); - prefix = token.string.slice(1); - n++; - } - const len = token.string.length; - if (/['"]/.test(token.string.charAt(len - 1))) { - quote = token.string.charAt(len - 1); - prefix = token.string.substr(n, len - 2); - } - if (n) { - // an opening quote - const line = cm.getLine(cur.line); - if (line.length > token.end && line.charAt(token.end) === quote) { - token.end++; - } // include a closing quote - } - replaceToken = true; - } - for (let i = 0; i < atValues.length; ++i) { - if (!prefix || matches(atValues[i], prefix, matchInMiddle)) { - result.push(quote + atValues[i] + quote); - } - } - } else { - // An attribute name - if (token.type === 'attribute') { - prefix = token.string; - replaceToken = true; - } - for (const attr in attrs) { - if ( - attrs.hasOwnProperty(attr) && - attr !== 'component_description' && - (!prefix || matches(attr, prefix, matchInMiddle)) - ) { - result.push({ text: attr, ...attrs[attr] }); - } - } - } - } - - const obj = { - list: result, - from: replaceToken - ? CodeMirror.Pos( - cur.line, - tagStart === null || typeof tagStart === 'undefined' - ? token.start - : tagStart - ) - : cur, - to: replaceToken ? CodeMirror.Pos(cur.line, token.end) : cur - }; - - let tooltip = null; - CodeMirror.on(obj, 'close', () => remove(tooltip)); - CodeMirror.on(obj, 'update', () => remove(tooltip)); - CodeMirror.on(obj, 'select', (data, node) => { remove(tooltip); - if (data && data.description) { + if (attr && attr.description) { tooltip = makeTooltip( node.parentNode.getBoundingClientRect().right + window.pageXOffset, node.getBoundingClientRect().top + window.pageYOffset, - data + attr ); tooltip.className += ' hint-doc'; } }); - // eslint-disable-next-line consistent-return - return obj; + return hint; } diff --git a/src/Playroom/Playroom.js b/src/Playroom/Playroom.js index b50dd170..d514cb2a 100644 --- a/src/Playroom/Playroom.js +++ b/src/Playroom/Playroom.js @@ -14,7 +14,6 @@ import { store } from '../index'; import WindowPortal from './WindowPortal'; import UndockSvg from '../assets/icons/NewWindowSvg'; import { formatCode } from '../utils/formatting'; -import getHints from './CodeMirror-JSX'; import codeMirror from 'codemirror'; import ReactCodeMirror from 'react-codemirror'; @@ -22,7 +21,9 @@ import 'codemirror/mode/jsx/jsx'; import 'codemirror/addon/edit/closetag'; import 'codemirror/addon/edit/closebrackets'; import 'codemirror/addon/hint/show-hint'; + import 'codemirror/addon/hint/xml-hint'; +import getHints from './CodeMirror-JSX'; const themesImport = require('./themes'); const componentsImport = require('./components'); From c97b2230202868e2f743387010fcad21df37ea9e Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Fri, 29 Mar 2019 00:55:39 -0700 Subject: [PATCH 08/12] use lit-html for rendering the tooltip --- package.json | 3 +- src/Playroom/CodeMirror-JSX.js | 139 +++++++++++++------------------ src/Playroom/CodeMirror-JSX.less | 6 +- yarn.lock | 13 ++- 4 files changed, 74 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index 834a4a89..0f404822 100644 --- a/package.json +++ b/package.json @@ -73,12 +73,13 @@ "html-webpack-plugin": "^3.2.0", "less": "^3.8.1", "less-loader": "^4.1.0", + "lit-html": "^1.0.0", "localforage": "^1.7.2", "lodash": "^4.17.11", "mini-css-extract-plugin": "^0.4.3", "opn": "^5.4.0", "parse-prop-types": "^0.3.0", - "prettier": "^1.15.3", + "prettier": "^1.16.4", "prop-types": "^15.6.2", "query-string": "^6.1.0", "re-resizable": "^4.9.3", diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index f4c0947e..2ed36ff6 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -1,85 +1,52 @@ /* eslint-disable new-cap */ import styles from './CodeMirror-JSX.less'; +import { html, render } from 'lit-html'; -function elt(tagname, cls) { - const e = document.createElement(tagname); - - if (cls) { - e.className = cls; +function getTypeColor(data) { + if (data.type === 'boolean') { + return 'rebeccapurple'; } - for (let i = 2; i < arguments.length; ++i) { - let element = arguments[i]; - - if (typeof element === 'string') { - element = document.createTextNode(element); - } - - e.appendChild(element); - } - - return e; -} - -function makeTooltip(x, y, data) { - const content = [elt('span', null, data.description)]; - - if (data.required) { - content.unshift(elt('span', styles.required, 'ⓘ')); - } - - if (data.default !== null && typeof data.default !== 'undefined') { - const value = elt('span', null, data.default); - - if (data.type === 'boolean') { - value.style.color = 'rebeccapurple'; - } - - if (data.type === 'string' || data.values.length > 0) { - value.style.color = 'darkred'; - } - - if (data.type === 'number') { - value.style.color = 'steelblue'; - } - - content.push( - elt( - 'div', - styles.default, - elt('span', styles.defaultLabel, 'Default:'), - value - ) - ); + if (data.type === 'string' || data.values.length > 0) { + return 'darkred'; } - if (data.type !== null && typeof data.type !== 'undefined') { - content.push( - elt( - 'div', - styles.default, - elt('span', styles.defaultLabel, 'Type:'), - elt('span', null, data.type) - ) - ); + if (data.type === 'number') { + return 'steelblue'; } - - const node = elt('div', styles.tooltip, ...content); - - node.style.left = `${x}px`; - node.style.top = `${y}px`; - - document.body.appendChild(node); - return node; } -function remove(node) { - const p = node && node.parentNode; - - if (p) { - p.removeChild(node); - } +function makeTooltip(data) { + const required = data.required + ? html` + + ` + : ''; + const defaultValue = + data.default !== null && typeof data.default !== 'undefined' + ? html` +
+ Default: + ${data.default} +
+ ` + : ''; + const type = + data.type !== null && typeof data.type !== 'undefined' + ? html` +
+ Type: + ${data.type} +
+ ` + : ''; + + return html` +
+ ${required} ${data.description} ${defaultValue} ${type} +
+ `; } function prepareSchema(tags) { @@ -87,6 +54,10 @@ function prepareSchema(tags) { all[tag] = { ...tags[tag], attrs: Object.keys(tags[tag].attrs).reduce((allAttrs, attr) => { + if (attr === 'component_description') { + return allAttrs; + } + allAttrs[attr] = tags[tag].attrs[attr].values; return allAttrs; }, {}) @@ -106,10 +77,11 @@ export default function getHints(cm, options) { }) ); - let tooltip = null; + const container = document.createElement('div'); + document.body.appendChild(container); - CodeMirror.on(hint, 'close', () => remove(tooltip)); - CodeMirror.on(hint, 'update', () => remove(tooltip)); + CodeMirror.on(hint, 'close', () => container.remove()); + CodeMirror.on(hint, 'update', () => container.remove()); CodeMirror.on(hint, 'select', (data, node) => { const cur = cm.getCursor(); const token = cm.getTokenAt(cur); @@ -134,15 +106,20 @@ export default function getHints(cm, options) { }; } - remove(tooltip); + container.remove(); if (attr && attr.description) { - tooltip = makeTooltip( - node.parentNode.getBoundingClientRect().right + window.pageXOffset, - node.getBoundingClientRect().top + window.pageYOffset, - attr - ); - tooltip.className += ' hint-doc'; + const tooltip = makeTooltip(attr); + const x = + node.parentNode.getBoundingClientRect().right + window.pageXOffset; + const y = node.getBoundingClientRect().top + window.pageYOffset; + + container.style.left = `${x}px`; + container.style.top = `${y}px`; + container.className = styles.tooltip; + document.body.appendChild(container); + + render(tooltip, container); } }); diff --git a/src/Playroom/CodeMirror-JSX.less b/src/Playroom/CodeMirror-JSX.less index ae50c930..a7ed7c83 100644 --- a/src/Playroom/CodeMirror-JSX.less +++ b/src/Playroom/CodeMirror-JSX.less @@ -10,6 +10,7 @@ border: 1px solid silver; max-width: 25em; margin-left: 5px; + white-space: initial; } .required { @@ -17,12 +18,15 @@ padding-right: 5px; } +.default { + margin-top: 5px; +} + .default { margin-top: 10px; } .defaultLabel { - margin-right: 8px; font-weight: 500; color: rgba(0, 0, 0, 0.7); } diff --git a/yarn.lock b/yarn.lock index 2c096a13..d2d60bf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6174,6 +6174,11 @@ listr@^0.14.2: p-map "^2.0.0" rxjs "^6.3.3" +lit-html@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.0.0.tgz#3dc3781a8ca68a9b5c2ff2a61e263662b9b2267b" + integrity sha512-oeWlpLmBW3gFl7979Wol2LKITpmKTUFNn7PnFbh6YNynF61W74l6x5WhwItAwPRSATpexaX1egNnRzlN4GOtfQ== + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -7920,10 +7925,10 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@^1.15.3: - version "1.15.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a" - integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg== +prettier@^1.16.4: + version "1.16.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717" + integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g== pretty-error@^2.0.2: version "2.1.1" From 34b17327a2a7198b2f0bd5f41b2e27a8ad259007 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Fri, 29 Mar 2019 01:07:28 -0700 Subject: [PATCH 09/12] add example --- examples/typescript/components/Bar/Bar.tsx | 7 +++++++ examples/typescript/components/Foo/Foo.tsx | 13 ++++++++++++- src/Playroom/CodeMirror-JSX.js | 2 +- src/Playroom/CodeMirror-JSX.less | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/examples/typescript/components/Bar/Bar.tsx b/examples/typescript/components/Bar/Bar.tsx index df2e5c84..8944fbd2 100644 --- a/examples/typescript/components/Bar/Bar.tsx +++ b/examples/typescript/components/Bar/Bar.tsx @@ -1,9 +1,16 @@ import React, { Component } from 'react'; interface Props { + /** The color of the Bar component's text */ color: 'red' | 'blue'; + /** + * The count of schmeckles + * @default 42 + */ + count?: number; } +/** A fancy component */ export default class Bar extends Component { render() { const { color } = this.props; diff --git a/examples/typescript/components/Foo/Foo.tsx b/examples/typescript/components/Foo/Foo.tsx index 334a890f..77a8a4aa 100644 --- a/examples/typescript/components/Foo/Foo.tsx +++ b/examples/typescript/components/Foo/Foo.tsx @@ -1,10 +1,21 @@ import React, { Component } from 'react'; interface Props { - color: 'red' | 'blue'; + /** + * The color of the Foo components text + * @default 'red' + */ + color?: 'red' | 'blue'; + /** + * Do something special + * @default false + */ + active?: boolean } +/** A basic component */ export default class Foo extends Component { + render() { const { color } = this.props; diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index 2ed36ff6..67aeb629 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -35,7 +35,7 @@ function makeTooltip(data) { const type = data.type !== null && typeof data.type !== 'undefined' ? html` -
+
Type: ${data.type}
diff --git a/src/Playroom/CodeMirror-JSX.less b/src/Playroom/CodeMirror-JSX.less index a7ed7c83..7f3edd2f 100644 --- a/src/Playroom/CodeMirror-JSX.less +++ b/src/Playroom/CodeMirror-JSX.less @@ -22,7 +22,7 @@ margin-top: 5px; } -.default { +.default:first-of-type { margin-top: 10px; } From 59512489d6422f064af3955c7d55f84e0bafeb9b Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Fri, 29 Mar 2019 01:18:24 -0700 Subject: [PATCH 10/12] fix lint + test --- examples/typescript/components/Bar/Bar.tsx | 4 ++-- examples/typescript/components/Foo/Foo.tsx | 13 ++++++------- src/Playroom/CodeMirror-JSX.js | 17 +++++++++++------ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/examples/typescript/components/Bar/Bar.tsx b/examples/typescript/components/Bar/Bar.tsx index 8944fbd2..c7271532 100644 --- a/examples/typescript/components/Bar/Bar.tsx +++ b/examples/typescript/components/Bar/Bar.tsx @@ -3,8 +3,8 @@ import React, { Component } from 'react'; interface Props { /** The color of the Bar component's text */ color: 'red' | 'blue'; - /** - * The count of schmeckles + /** + * The count of schmeckles * @default 42 */ count?: number; diff --git a/examples/typescript/components/Foo/Foo.tsx b/examples/typescript/components/Foo/Foo.tsx index 77a8a4aa..7a9dc6de 100644 --- a/examples/typescript/components/Foo/Foo.tsx +++ b/examples/typescript/components/Foo/Foo.tsx @@ -1,21 +1,20 @@ import React, { Component } from 'react'; interface Props { - /** - * The color of the Foo components text - * @default 'red' + /** + * The color of the Foo components text + * @default 'red' */ color?: 'red' | 'blue'; - /** + /** * Do something special - * @default false + * @default false */ - active?: boolean + active?: boolean; } /** A basic component */ export default class Foo extends Component { - render() { const { color } = this.props; diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index 67aeb629..3a18add1 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -15,6 +15,8 @@ function getTypeColor(data) { if (data.type === 'number') { return 'steelblue'; } + + return null; } function makeTooltip(data) { @@ -50,15 +52,18 @@ function makeTooltip(data) { } function prepareSchema(tags) { - return Object.keys(tags).reduce((all, tag) => { - all[tag] = { - ...tags[tag], - attrs: Object.keys(tags[tag].attrs).reduce((allAttrs, attr) => { - if (attr === 'component_description') { + return Object.keys(tags).reduce((all, key) => { + const tag = tags[key]; + + all[key] = { + ...tag, + attrs: Object.keys(tag.attrs).reduce((allAttrs, name) => { + if (name === 'component_description') { return allAttrs; } - allAttrs[attr] = tags[tag].attrs[attr].values; + const attr = tag.attrs[name]; + allAttrs[name] = Array.isArray(attr) ? attr : attr.values; return allAttrs; }, {}) }; From f5a418611a8912d22873e1a7e59e6ce23b8f8920 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Fri, 29 Mar 2019 01:41:28 -0700 Subject: [PATCH 11/12] just use react! --- package.json | 1 - src/Playroom/CodeMirror-JSX.js | 145 +++++++++++++++---------------- src/Playroom/CodeMirror-JSX.less | 7 +- yarn.lock | 5 -- 4 files changed, 73 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index 0f404822..0603f41d 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,6 @@ "html-webpack-plugin": "^3.2.0", "less": "^3.8.1", "less-loader": "^4.1.0", - "lit-html": "^1.0.0", "localforage": "^1.7.2", "lodash": "^4.17.11", "mini-css-extract-plugin": "^0.4.3", diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index 3a18add1..49cce92b 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -1,7 +1,28 @@ -/* eslint-disable new-cap */ - +import React from 'react'; +import ReactDom from 'react-dom'; import styles from './CodeMirror-JSX.less'; -import { html, render } from 'lit-html'; + +// Convert attribute values to arrays that addon-xml can handle +function prepareSchema(tags) { + return Object.keys(tags).reduce((all, key) => { + const tag = tags[key]; + + all[key] = { + ...tag, + attrs: Object.keys(tag.attrs).reduce((allAttrs, name) => { + if (name === 'component_description') { + return allAttrs; + } + + const attr = tag.attrs[name]; + allAttrs[name] = Array.isArray(attr) ? attr : attr.values; + return allAttrs; + }, {}) + }; + + return all; + }, {}); +} function getTypeColor(data) { if (data.type === 'boolean') { @@ -19,57 +40,51 @@ function getTypeColor(data) { return null; } -function makeTooltip(data) { - const required = data.required - ? html` - - ` - : ''; - const defaultValue = - data.default !== null && typeof data.default !== 'undefined' - ? html` -
- Default: - ${data.default} -
- ` - : ''; - const type = - data.type !== null && typeof data.type !== 'undefined' - ? html` -
- Type: - ${data.type} -
- ` - : ''; - - return html` -
- ${required} ${data.description} ${defaultValue} ${type} -
- `; -} +const Tooltip = ({ data }) => ( +
+ {data.required && } + {data.description} + {data.default !== null && typeof data.default !== 'undefined' && ( +
+ Default: + {data.default} +
+ )} + {data.type !== null && typeof data.type !== 'undefined' && ( +
+ Type: + {data.type} +
+ )} +
+); + +function getAttribute(cm, tags, data) { + const CodeMirror = cm.constructor; + const cur = cm.getCursor(); + const token = cm.getTokenAt(cur); -function prepareSchema(tags) { - return Object.keys(tags).reduce((all, key) => { - const tag = tags[key]; + if (token.end > cur.ch) { + token.end = cur.ch; + token.string = token.string.slice(0, cur.ch - token.start); + } - all[key] = { - ...tag, - attrs: Object.keys(tag.attrs).reduce((allAttrs, name) => { - if (name === 'component_description') { - return allAttrs; - } + const inner = CodeMirror.innerMode(cm.getMode(), token.state); + let attr; - const attr = tag.attrs[name]; - allAttrs[name] = Array.isArray(attr) ? attr : attr.values; - return allAttrs; - }, {}) + // Attribute + if (tags[inner.state.tagName]) { + attr = tags[inner.state.tagName].attrs[data]; + } + + // Tag + if (data.match(/<\S+/)) { + attr = { + description: tags[data.slice(1)].attrs.component_description }; + } - return all; - }, {}); + return attr; } export default function getHints(cm, options) { @@ -83,48 +98,24 @@ export default function getHints(cm, options) { ); const container = document.createElement('div'); - document.body.appendChild(container); CodeMirror.on(hint, 'close', () => container.remove()); CodeMirror.on(hint, 'update', () => container.remove()); CodeMirror.on(hint, 'select', (data, node) => { - const cur = cm.getCursor(); - const token = cm.getTokenAt(cur); - - if (token.end > cur.ch) { - token.end = cur.ch; - token.string = token.string.slice(0, cur.ch - token.start); - } - - const inner = CodeMirror.innerMode(cm.getMode(), token.state); - let attr; - - // Attribute - if (tags[inner.state.tagName]) { - attr = tags[inner.state.tagName].attrs[data]; - } - - // Tag - if (data.match(/<\S+/)) { - attr = { - description: tags[data.slice(1)].attrs.component_description - }; - } - + const attr = getAttribute(cm, tags, data); container.remove(); if (attr && attr.description) { - const tooltip = makeTooltip(attr); const x = node.parentNode.getBoundingClientRect().right + window.pageXOffset; const y = node.getBoundingClientRect().top + window.pageYOffset; container.style.left = `${x}px`; container.style.top = `${y}px`; - container.className = styles.tooltip; - document.body.appendChild(container); + container.className = styles.container; - render(tooltip, container); + ReactDom.render(, container); + document.body.appendChild(container); } }); diff --git a/src/Playroom/CodeMirror-JSX.less b/src/Playroom/CodeMirror-JSX.less index 7f3edd2f..03cfde1b 100644 --- a/src/Playroom/CodeMirror-JSX.less +++ b/src/Playroom/CodeMirror-JSX.less @@ -1,11 +1,14 @@ -.tooltip { +.container { position: absolute; + z-index: 10; +} + +.tooltip { background: white; border-radius: 3px; font-family: monospace; white-space: pre-wrap; padding: 4px 8px; - z-index: 10; box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2); border: 1px solid silver; max-width: 25em; diff --git a/yarn.lock b/yarn.lock index d2d60bf9..611e7e4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6174,11 +6174,6 @@ listr@^0.14.2: p-map "^2.0.0" rxjs "^6.3.3" -lit-html@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.0.0.tgz#3dc3781a8ca68a9b5c2ff2a61e263662b9b2267b" - integrity sha512-oeWlpLmBW3gFl7979Wol2LKITpmKTUFNn7PnFbh6YNynF61W74l6x5WhwItAwPRSATpexaX1egNnRzlN4GOtfQ== - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" From d48b2e1cf26c8b320069fe0910849a37cf35a432 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Fri, 29 Mar 2019 01:51:46 -0700 Subject: [PATCH 12/12] code improvements --- lib/getStaticTypes.js | 2 +- src/Playroom/CodeMirror-JSX.js | 4 +--- src/Playroom/CodeMirror-JSX.less | 1 + 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/getStaticTypes.js b/lib/getStaticTypes.js index 32d7d17e..aff2bbdc 100644 --- a/lib/getStaticTypes.js +++ b/lib/getStaticTypes.js @@ -34,7 +34,7 @@ module.exports = async playroomConfig => { const types = parse(files); const typesByDisplayName = keyBy(types, 'displayName'); const parsedTypes = mapValues(typesByDisplayName, component => ({ - component_description: component.description, + component_description: component, ...mapValues(filterProps(component.props || {}), prop => ({ description: prop.description, default: prop.defaultValue && prop.defaultValue.value, diff --git a/src/Playroom/CodeMirror-JSX.js b/src/Playroom/CodeMirror-JSX.js index 49cce92b..958c57af 100644 --- a/src/Playroom/CodeMirror-JSX.js +++ b/src/Playroom/CodeMirror-JSX.js @@ -79,9 +79,7 @@ function getAttribute(cm, tags, data) { // Tag if (data.match(/<\S+/)) { - attr = { - description: tags[data.slice(1)].attrs.component_description - }; + attr = tags[data.slice(1)].attrs.component_description; } return attr; diff --git a/src/Playroom/CodeMirror-JSX.less b/src/Playroom/CodeMirror-JSX.less index 03cfde1b..4497ce98 100644 --- a/src/Playroom/CodeMirror-JSX.less +++ b/src/Playroom/CodeMirror-JSX.less @@ -30,6 +30,7 @@ } .defaultLabel { + margin-right: 8px; font-weight: 500; color: rgba(0, 0, 0, 0.7); }