import React, { useEffect, useMemo, useState } from 'react'; import { useSpring, config, animated } from 'react-spring'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import Tooltip from 'react-bootstrap/Tooltip'; import CodeMirror from '@uiw/react-codemirror'; import 'codemirror/mode/handlebars/handlebars'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/sql/sql'; import 'codemirror/addon/hint/show-hint'; import 'codemirror/addon/display/placeholder'; import 'codemirror/addon/search/match-highlighter'; import 'codemirror/addon/hint/show-hint.css'; import 'codemirror/theme/base16-light.css'; import 'codemirror/theme/duotone-light.css'; import 'codemirror/theme/monokai.css'; import { getSuggestionKeys, onBeforeChange, handleChange } from './utils'; import { resolveReferences } from '@/_helpers/utils'; import useHeight from '@/_hooks/use-height-transition'; import usePortal from '@/_hooks/use-portal'; export function CodeHinter({ initialValue, onChange, currentState, mode, theme, lineNumbers, placeholder, ignoreBraces, enablePreview, height, minHeight, lineWrapping, componentName = null, usePortalEditor = true, className = 'code-hinter', }) { const darkMode = localStorage.getItem('darkMode') === 'true'; const options = { lineNumbers: lineNumbers ?? false, lineWrapping: lineWrapping ?? true, singleLine: true, mode: mode || 'handlebars', tabSize: 2, theme: theme || 'default', readOnly: false, highlightSelectionMatches: true, placeholder, }; const [realState, setRealState] = useState(currentState); const [currentValue, setCurrentValue] = useState(initialValue); const [isFocused, setFocused] = useState(false); const [heightRef, currentHeight] = useHeight(); const slideInStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0, height: 0 }, to: { opacity: isFocused ? 1 : 0, height: isFocused ? currentHeight : 0, }, }); useEffect(() => { setRealState(currentState); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentState.components]); let suggestions = useMemo(() => { return getSuggestionKeys(realState); // eslint-disable-next-line react-hooks/exhaustive-deps }, [realState.components, realState.queries]); function valueChanged(editor, onChange, suggestions, ignoreBraces) { handleChange(editor, onChange, suggestions, ignoreBraces); setCurrentValue(editor.getValue()); } const getPreviewContent = (content, type) => { switch (type) { case 'object': return JSON.stringify(content); case 'boolean': return content.toString(); default: return content; } }; const getPreview = () => { const [preview, error] = resolveReferences(currentValue, realState, null, {}, true); const themeCls = darkMode ? 'bg-dark py-1' : 'bg-light py-1'; if (error) { return (
Error
{error.toString()}
); } const previewType = typeof preview; const content = getPreviewContent(preview, previewType); return (
{previewType}
{content}
); }; enablePreview = enablePreview ?? true; const [isOpen, setIsOpen] = React.useState(false); const handleToggle = () => { if (!isOpen) { setIsOpen(true); } return new Promise((resolve) => { const element = document.getElementsByClassName('portal-container'); if (element) { const checkPortalExits = element[0]?.classList.contains(componentName); if (checkPortalExits === false) { const parent = element[0].parentNode; parent.removeChild(element[0]); } setIsOpen(false); resolve(); } }).then(() => { setIsOpen(true); forceUpdate(); }); }; const [, forceUpdate] = React.useReducer((x) => x + 1, 0); // const defaultClassName = isScrollable ? '' : 'code-hinter'; return (
{usePortalEditor && } setFocused(true)} onBlur={(editor) => { const value = editor.getValue(); onChange(value); setFocused(false); }} onChange={(editor) => valueChanged(editor, onChange, suggestions, ignoreBraces)} onBeforeChange={(editor, change) => onBeforeChange(editor, change, ignoreBraces)} options={options} viewportMargin={Infinity} />
{enablePreview && !isOpen && getPreview()}
); } const PopupIcon = ({ callback }) => { return (
{'Pop out code editor into a new window'}} > { e.stopPropagation(); callback(); }} />
); }; const Portal = ({ children, ...restProps }) => { const renderPortal = usePortal({ children, ...restProps }); return {renderPortal}; }; CodeHinter.PopupIcon = PopupIcon; CodeHinter.Portal = Portal;