diff --git a/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js b/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js
index 6e456820b..5ca8c019e 100644
--- a/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js
+++ b/packages/bruno-app/src/components/SingleLineEditor/StyledWrapper.js
@@ -24,7 +24,18 @@ const StyledWrapper = styled.div`
font-family: Inter, sans-serif !important;
font-weight: 400;
}
- }
+ }
+
+ .tooltip {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background: #fff;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ padding: 5px;
+ font-size: 12px;
+ z-index: 100;
}
.cm-variable-valid{color: green}
diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js
index fbeb8bb69..946dc1f32 100644
--- a/packages/bruno-app/src/components/SingleLineEditor/index.js
+++ b/packages/bruno-app/src/components/SingleLineEditor/index.js
@@ -24,6 +24,9 @@ class SingleLineEditor extends Component {
lineNumbers: false,
autofocus: true,
mode: "brunovariables",
+ brunoVarInfo: {
+ variables: getEnvironmentVariables(this.props.collection),
+ },
extraKeys: {
"Enter": () => {
if (this.props.onRun) {
@@ -65,8 +68,7 @@ class SingleLineEditor extends Component {
'Tab': () => {}
},
});
- this.editor.setValue(this.props.value)
-
+ this.editor.setValue(this.props.value);
this.editor.on('change', (cm) => {
this.props.onChange(cm.getValue());
});
@@ -76,6 +78,7 @@ class SingleLineEditor extends Component {
componentDidUpdate(prevProps) {
let variables = getEnvironmentVariables(this.props.collection);
if (!isEqual(variables, this.variables)) {
+ this.editor.options.brunoVarInfo.variables = variables;
this.addOverlay();
}
}
diff --git a/packages/bruno-app/src/pageComponents/Index/index.js b/packages/bruno-app/src/pageComponents/Index/index.js
index d7e5d1016..c74d60c11 100644
--- a/packages/bruno-app/src/pageComponents/Index/index.js
+++ b/packages/bruno-app/src/pageComponents/Index/index.js
@@ -31,6 +31,8 @@ if (!SERVER_RENDERED) {
require('codemirror-graphql/info');
require('codemirror-graphql/jump');
require('codemirror-graphql/mode');
+
+ require('utils/codemirror/brunoVarInfo');
}
export default function Main() {
diff --git a/packages/bruno-app/src/pages/_app.js b/packages/bruno-app/src/pages/_app.js
index f0b5bbce5..7682c2c84 100644
--- a/packages/bruno-app/src/pages/_app.js
+++ b/packages/bruno-app/src/pages/_app.js
@@ -12,6 +12,7 @@ import '../styles/globals.css';
import 'tailwindcss/dist/tailwind.min.css';
import 'codemirror/lib/codemirror.css';
import 'graphiql/graphiql.min.css';
+import 'utils/codemirror/brunoVarInfo.css';
function SafeHydrate({ children }) {
return
{typeof window === 'undefined' ? null : children}
;
diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.css b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.css
new file mode 100644
index 000000000..ce116defc
--- /dev/null
+++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.css
@@ -0,0 +1,28 @@
+.CodeMirror-brunoVarInfo {
+ background: white;
+ border-radius: 2px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
+ box-sizing: border-box;
+ font-size: 13px;
+ line-height: 16px;
+ margin: 8px -8px;
+ max-width: 800px;
+ opacity: 0;
+ overflow: hidden;
+ padding: 8px 8px;
+ position: fixed;
+ transition: opacity 0.15s;
+ z-index: 50;
+}
+
+.CodeMirror-brunoVarInfo :first-child {
+ margin-top: 0;
+}
+
+.CodeMirror-brunoVarInfo :last-child {
+ margin-bottom: 0;
+}
+
+.CodeMirror-brunoVarInfo p {
+ margin: 1em 0;
+}
diff --git a/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
new file mode 100644
index 000000000..dc0cb64ed
--- /dev/null
+++ b/packages/bruno-app/src/utils/codemirror/brunoVarInfo.js
@@ -0,0 +1,198 @@
+/**
+ * Copyright (c) 2017, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file at https://github.com/graphql/codemirror-graphql/tree/v0.8.3
+ */
+
+let CodeMirror;
+const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true;
+
+if (!SERVER_RENDERED) {
+ CodeMirror = require('codemirror');
+
+ const renderVarInfo = (token, options, cm, pos) => {
+ const str = token.string || '';
+
+ // str is of format {{variableName}}, extract variableName
+ const variableName = str.substring(2, str.length - 2);
+
+ // get the value of the variable
+ const variableValue = options.variables[variableName] || '';
+
+ const into = document.createElement('div');
+ const descriptionDiv = document.createElement('div');
+ descriptionDiv.className = 'info-description';
+
+ descriptionDiv.appendChild(document.createTextNode(variableValue));
+ into.appendChild(descriptionDiv);
+
+ return into;
+ };
+
+ CodeMirror.defineOption('brunoVarInfo', false, function(cm, options, old) {
+
+ if (old && old !== CodeMirror.Init) {
+ const oldOnMouseOver = cm.state.brunoVarInfo.onMouseOver;
+ CodeMirror.off(cm.getWrapperElement(), 'mouseover', oldOnMouseOver);
+ clearTimeout(cm.state.brunoVarInfo.hoverTimeout);
+ delete cm.state.brunoVarInfo;
+ }
+
+ if (options) {
+ const state = (cm.state.brunoVarInfo = createState(options));
+ state.onMouseOver = onMouseOver.bind(null, cm);
+ CodeMirror.on(cm.getWrapperElement(), 'mouseover', state.onMouseOver);
+ }
+ });
+
+ function createState(options) {
+ return {
+ options:
+ options instanceof Function
+ ? {render: options}
+ : options === true ? {} : options,
+ };
+ }
+
+ function getHoverTime(cm) {
+ const options = cm.state.brunoVarInfo.options;
+ return (options && options.hoverTime) || 50;
+ }
+
+ function onMouseOver(cm, e) {
+ const state = cm.state.brunoVarInfo;
+ const target = e.target || e.srcElement;
+
+ if (target.nodeName !== 'SPAN' || state.hoverTimeout !== undefined) {
+ return;
+ }
+
+ if(target.className !== 'cm-variable-valid') {
+ return;
+ }
+
+ const box = target.getBoundingClientRect();
+
+ const hoverTime = getHoverTime(cm);
+ state.hoverTimeout = setTimeout(onHover, hoverTime);
+
+ const onMouseMove = function() {
+ clearTimeout(state.hoverTimeout);
+ state.hoverTimeout = setTimeout(onHover, hoverTime);
+ };
+
+ const onMouseOut = function() {
+ CodeMirror.off(document, 'mousemove', onMouseMove);
+ CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
+ clearTimeout(state.hoverTimeout);
+ state.hoverTimeout = undefined;
+ };
+
+ const onHover = function() {
+ CodeMirror.off(document, 'mousemove', onMouseMove);
+ CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
+ state.hoverTimeout = undefined;
+ onMouseHover(cm, box);
+ };
+
+ CodeMirror.on(document, 'mousemove', onMouseMove);
+ CodeMirror.on(cm.getWrapperElement(), 'mouseout', onMouseOut);
+ }
+
+ function onMouseHover(cm, box) {
+ const pos = cm.coordsChar({
+ left: (box.left + box.right) / 2,
+ top: (box.top + box.bottom) / 2,
+ });
+
+ const state = cm.state.brunoVarInfo;
+ const options = state.options;
+ const token = cm.getTokenAt(pos, true);
+ if (token) {
+ const brunoVarInfo = renderVarInfo(token, options, cm, pos);
+ if (brunoVarInfo) {
+ showPopup(cm, box, brunoVarInfo);
+ }
+ }
+ }
+
+ function showPopup(cm, box, brunoVarInfo) {
+ const popup = document.createElement('div');
+ popup.className = 'CodeMirror-brunoVarInfo';
+ popup.appendChild(brunoVarInfo);
+ document.body.appendChild(popup);
+
+ const popupBox = popup.getBoundingClientRect();
+ const popupStyle = popup.currentStyle || window.getComputedStyle(popup);
+ const popupWidth =
+ popupBox.right -
+ popupBox.left +
+ parseFloat(popupStyle.marginLeft) +
+ parseFloat(popupStyle.marginRight);
+ const popupHeight =
+ popupBox.bottom -
+ popupBox.top +
+ parseFloat(popupStyle.marginTop) +
+ parseFloat(popupStyle.marginBottom);
+
+ let topPos = box.bottom;
+ if (
+ popupHeight > window.innerHeight - box.bottom - 15 &&
+ box.top > window.innerHeight - box.bottom
+ ) {
+ topPos = box.top - popupHeight;
+ }
+
+ if (topPos < 0) {
+ topPos = box.bottom;
+ }
+
+ // make popup appear on top of cursor
+ if (topPos > 70) {
+ topPos = topPos - 70;
+ }
+
+ let leftPos = Math.max(0, window.innerWidth - popupWidth - 15);
+ if (leftPos > box.left) {
+ leftPos = box.left;
+ }
+
+ popup.style.opacity = 1;
+ popup.style.top = topPos + 'px';
+ popup.style.left = leftPos + 'px';
+
+ let popupTimeout;
+
+ const onMouseOverPopup = function() {
+ clearTimeout(popupTimeout);
+ };
+
+ const onMouseOut = function() {
+ clearTimeout(popupTimeout);
+ popupTimeout = setTimeout(hidePopup, 200);
+ };
+
+ const hidePopup = function() {
+ CodeMirror.off(popup, 'mouseover', onMouseOverPopup);
+ CodeMirror.off(popup, 'mouseout', onMouseOut);
+ CodeMirror.off(cm.getWrapperElement(), 'mouseout', onMouseOut);
+
+ if (popup.style.opacity) {
+ popup.style.opacity = 0;
+ setTimeout(function() {
+ if (popup.parentNode) {
+ popup.parentNode.removeChild(popup);
+ }
+ }, 600);
+ } else if (popup.parentNode) {
+ popup.parentNode.removeChild(popup);
+ }
+ };
+
+ CodeMirror.on(popup, 'mouseover', onMouseOverPopup);
+ CodeMirror.on(popup, 'mouseout', onMouseOut);
+ CodeMirror.on(cm.getWrapperElement(), 'mouseout', onMouseOut);
+ }
+}