import React, { useEffect, useState, useRef, useContext } 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 { onBeforeChange, handleChange } from './utils';
import { resolveReferences, hasCircularDependency, handleCircularStructureToJSON } from '@/_helpers/utils';
import useHeight from '@/_hooks/use-height-transition';
import usePortal from '@/_hooks/use-portal';
import { Color } from './Elements/Color';
import { Json } from './Elements/Json';
import { Select } from './Elements/Select';
import { Toggle } from './Elements/Toggle';
import { AlignButtons } from './Elements/AlignButtons';
import { TypeMapping } from './TypeMapping';
import { Number } from './Elements/Number';
import { BoxShadow } from './Elements/BoxShadow';
import FxButton from './Elements/FxButton';
import { ToolTip } from '../Inspector/Elements/Components/ToolTip';
import { toast } from 'react-hot-toast';
import { EditorContext } from '@/Editor/Context/EditorContextWrapper';
import { camelCase } from 'lodash';
import { useTranslation } from 'react-i18next';
import cx from 'classnames';
import { useCurrentState } from '@/_stores/currentStateStore';
const AllElements = {
Color,
Json,
Toggle,
Select,
AlignButtons,
Number,
BoxShadow,
};
export function CodeHinter({
initialValue,
onChange,
mode,
theme,
lineNumbers,
placeholder,
ignoreBraces,
enablePreview,
height,
minHeight,
lineWrapping,
componentName = null,
usePortalEditor = true,
className,
width = '',
paramName,
paramLabel,
type,
fieldMeta,
onFxPress,
fxActive,
component,
popOverCallback,
cyLabel = '',
callgpt = () => null,
isCopilotEnabled = false,
currentState: _currentState,
}) {
const darkMode = localStorage.getItem('darkMode') === 'true';
const options = {
lineNumbers: lineNumbers ?? false,
lineWrapping: lineWrapping ?? true,
singleLine: true,
mode: mode || 'handlebars',
tabSize: 2,
theme: theme ? theme : darkMode ? 'monokai' : 'default',
readOnly: false,
highlightSelectionMatches: true,
placeholder,
};
const currentState = useCurrentState();
const [realState, setRealState] = useState(currentState);
const [currentValue, setCurrentValue] = useState(initialValue);
const [isFocused, setFocused] = useState(false);
const [heightRef, currentHeight] = useHeight();
const isPreviewFocused = useRef(false);
const wrapperRef = useRef(null);
const slideInStyles = useSpring({
config: { ...config.stiff },
from: { opacity: 0, height: 0 },
to: {
opacity: isFocused ? 1 : 0,
height: isFocused ? currentHeight : 0,
},
});
const { t } = useTranslation();
const { variablesExposedForPreview } = useContext(EditorContext);
const prevCountRef = useRef(false);
useEffect(() => {
if (_currentState) {
setRealState(_currentState);
} else {
setRealState(currentState);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState.components, _currentState]);
useEffect(() => {
const handleClickOutside = (event) => {
if (isOpen) {
return;
}
if (wrapperRef.current && isFocused && !wrapperRef.current.contains(event.target) && prevCountRef.current) {
isPreviewFocused.current = false;
setFocused(false);
prevCountRef.current = false;
} else if (isFocused) {
prevCountRef.current = true;
} else if (!isFocused && prevCountRef.current) prevCountRef.current = false;
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [wrapperRef, isFocused, isPreviewFocused, currentValue, prevCountRef, isOpen]);
function valueChanged(editor, onChange, ignoreBraces) {
if (editor.getValue()?.trim() !== currentValue) {
handleChange(editor, onChange, ignoreBraces, realState, componentName);
setCurrentValue(editor.getValue()?.trim());
}
}
const getPreviewContent = (content, type) => {
try {
switch (type) {
case 'object':
return JSON.stringify(content);
case 'boolean':
return content.toString();
default:
return content;
}
} catch (e) {
return undefined;
}
};
const focusPreview = () => (isPreviewFocused.current = true);
const unFocusPreview = () => (isPreviewFocused.current = false);
const copyToClipboard = (text) => {
navigator.clipboard.writeText(text);
toast.success('Copied to clipboard');
};
const getCustomResolvables = () => {
if (variablesExposedForPreview.hasOwnProperty(component?.id)) {
if (component?.component?.component === 'Table' && fieldMeta?.name) {
return {
...variablesExposedForPreview[component?.id],
cellValue: variablesExposedForPreview[component?.id]?.rowData?.[fieldMeta?.name],
rowData: { ...variablesExposedForPreview[component?.id]?.rowData },
};
}
return variablesExposedForPreview[component.id];
}
return {};
};
const getPreview = () => {
if (!enablePreview) return;
const customResolvables = getCustomResolvables();
const [preview, error] = resolveReferences(currentValue, realState, null, customResolvables, true, true);
const themeCls = darkMode ? 'bg-dark py-1' : 'bg-light py-1';
if (error) {
const err = String(error);
const errorMessage = err.includes('.run()') ? `${err} in ${componentName.split('::')[0]}'s field` : err;
return (