diff --git a/packages/editor/src/components/CodeEditor/TernEditor.tsx b/packages/editor/src/components/CodeEditor/TernEditor.tsx index 3d573ee6..4951c845 100644 --- a/packages/editor/src/components/CodeEditor/TernEditor.tsx +++ b/packages/editor/src/components/CodeEditor/TernEditor.tsx @@ -10,20 +10,28 @@ import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/ayu-mirage.css'; // tern import 'codemirror/addon/tern/tern'; +import 'codemirror/addon/selection/active-line'; +import 'codemirror/addon/comment/comment'; +import 'codemirror/addon/hint/show-hint'; +import 'codemirror/addon/dialog/dialog.css'; +import 'codemirror/addon/hint/show-hint.css'; +import 'codemirror/addon/tern/tern.css'; import 'tern/plugin/doc_comment'; import 'tern/plugin/complete_strings'; import ecma from 'tern/defs/ecmascript.json'; -import { Def } from 'tern'; +import tern, { Def } from 'tern'; + +(window as unknown as { tern: typeof tern }).tern = tern; function installTern(cm: CodeMirror.Editor) { - const tern = new CodeMirror.TernServer({ defs: [ecma as unknown as Def] }); - cm.on('cursorActivity', cm => tern.updateArgHints(cm)); + const t = new CodeMirror.TernServer({ defs: [ecma as unknown as Def] }); + cm.on('cursorActivity', cm => t.updateArgHints(cm)); cm.on('change', (_instance, change) => { if (change.text.length === 1 && change.text[0] === '.') { - tern.complete(cm); + t.complete(cm); } }); - return tern; + return t; } export const TernEditor: React.FC<{ @@ -31,7 +39,8 @@ export const TernEditor: React.FC<{ onChange?: (v: string) => void; onBlur?: (v: string) => void; lineNumbers?: boolean; -}> = ({ defaultCode, onChange, onBlur, lineNumbers = true }) => { + defs?: tern.Def[]; +}> = ({ defaultCode, onChange, onBlur, lineNumbers = true, defs }) => { const style = css` .CodeMirror { width: 100%; @@ -41,6 +50,7 @@ export const TernEditor: React.FC<{ const wrapperEl = useRef(null); const cm = useRef(null); + const tServer = useRef(null); useEffect(() => { if (!wrapperEl.current) { return; @@ -64,7 +74,8 @@ export const TernEditor: React.FC<{ theme: 'ayu-mirage', viewportMargin: Infinity, }); - installTern(cm.current); + const t = installTern(cm.current); + tServer.current = t.server; } const changeHandler = (instance: CodeMirror.Editor) => { onChange?.(instance.getValue()); @@ -79,6 +90,13 @@ export const TernEditor: React.FC<{ cm.current?.off('blur', blurHandler); }; }, [defaultCode]); + useEffect(() => { + if (defs) { + console.log(tServer.current, 'add', defs); + tServer.current?.deleteDefs('customDataTree'); + tServer.current?.addDefs(defs[0] as any, true); + } + }, [defs]); return ; }; diff --git a/packages/editor/src/components/ComponentForm/JsonSchemaForm/widgets/GeneralWidget.tsx b/packages/editor/src/components/ComponentForm/JsonSchemaForm/widgets/GeneralWidget.tsx index 63026007..f1cb5a83 100644 --- a/packages/editor/src/components/ComponentForm/JsonSchemaForm/widgets/GeneralWidget.tsx +++ b/packages/editor/src/components/ComponentForm/JsonSchemaForm/widgets/GeneralWidget.tsx @@ -1,11 +1,87 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { FieldProps } from '../fields'; import { TernEditor } from 'components/CodeEditor'; +import { stateStore } from 'setup'; +import _ from 'lodash-es'; type Props = FieldProps; +export enum Types { + URL = 'URL', + STRING = 'STRING', + NUMBER = 'NUMBER', + BOOLEAN = 'BOOLEAN', + OBJECT = 'OBJECT', + ARRAY = 'ARRAY', + FUNCTION = 'FUNCTION', + UNDEFINED = 'UNDEFINED', + NULL = 'NULL', + UNKNOWN = 'UNKNOWN', +} + +export const getType = (value: unknown) => { + if (_.isString(value)) return Types.STRING; + if (_.isNumber(value)) return Types.NUMBER; + if (_.isBoolean(value)) return Types.BOOLEAN; + if (Array.isArray(value)) return Types.ARRAY; + if (_.isFunction(value)) return Types.FUNCTION; + if (_.isObject(value)) return Types.OBJECT; + if (_.isUndefined(value)) return Types.UNDEFINED; + if (_.isNull(value)) return Types.NULL; + return Types.UNKNOWN; +}; + +function generateTypeDef( + obj: any +): string | Record> { + const type = getType(obj); + switch (type) { + case Types.ARRAY: { + const arrayType = getType(obj[0]); + return `[${arrayType}]`; + } + case Types.OBJECT: { + const objType: Record> = {}; + Object.keys(obj).forEach(k => { + objType[k] = generateTypeDef(obj[k]); + }); + return objType; + } + case Types.STRING: + return 'string'; + case Types.NUMBER: + return 'number'; + case Types.BOOLEAN: + return 'bool'; + case Types.NULL: + case Types.UNDEFINED: + return '?'; + default: + return '?'; + } +} + +let extraDefs: any = {}; + +const customTreeTypeDefCreator = (dataTree: Record>) => { + const def: any = { + '!name': 'customDataTree', + }; + Object.keys(dataTree).forEach(entityName => { + const entity = dataTree[entityName]; + def[entityName] = generateTypeDef(entity); + }); + def['!define'] = { ...extraDefs }; + extraDefs = {}; + return { ...def }; +}; + const GeneralWidget: React.FC = props => { const { formData, onChange } = props; + const [defs, setDefs] = useState(); + useEffect(() => { + setDefs([customTreeTypeDefCreator(stateStore)]); + }, []); return ( = props => { onBlur={v => { onChange(v); }} + defs={defs} /> ); };