ソースを参照

fix(type): type definition register timing (#809)

* feat(material): code-editor dynamic import language

* fix: variable root display fix

* fix: type sequence

* fix: remove debugger
Yiwei Mao 4 ヶ月 前
コミット
4d0d2ad16b

+ 0 - 1
apps/demo-free-layout/src/nodes/code/components/code.tsx

@@ -8,7 +8,6 @@ import { CodeEditor } from '@flowgram.ai/form-materials';
 import { Divider } from '@douyinfe/semi-ui';
 
 import { useIsSidebar, useNodeRenderContext } from '../../../hooks';
-import { FormItem } from '../../../form-components';
 
 export function Code() {
   const isSidebar = useIsSidebar();

+ 1 - 9
apps/demo-vite/src/hooks/use-editor-props.tsx

@@ -14,7 +14,6 @@ import {
   Field,
   useNodeRender,
 } from '@flowgram.ai/free-layout-editor';
-import { VariableSelector } from '@flowgram.ai/form-materials/components/variable-selector';
 
 import { nodeRegistries } from '../node-registries';
 import { initialData } from '../initial-data';
@@ -59,14 +58,7 @@ export const useEditorProps = () =>
                 <Field<string> name="title">
                   {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
                 </Field>
-                <Field<string[]> name="code">
-                  {({ field }) => (
-                    <VariableSelector
-                      value={field.value}
-                      onChange={(value) => field.onChange(value || [])}
-                    />
-                  )}
-                </Field>
+
                 <div className="demo-free-node-content">
                   <Field<string> name="content">
                     <input />

+ 7 - 10
packages/materials/form-materials/src/components/code-editor/editor.tsx

@@ -12,12 +12,11 @@ import {
   InferValues,
 } from '@flowgram.ai/coze-editor/react';
 import preset, { type EditorAPI } from '@flowgram.ai/coze-editor/preset-code';
+import { Skeleton } from '@douyinfe/semi-ui';
 import { EditorView } from '@codemirror/view';
 
 import { getSuffixByLanguageId } from './utils';
-
-import './theme';
-import './language-features';
+import { useDynamicLoadLanguage } from './language-features';
 
 const OriginCodeEditor = createRenderer(preset, [
   EditorView.theme({
@@ -52,6 +51,8 @@ export function CodeEditor({
   options,
   readonly,
 }: CodeEditorPropsType) {
+  const { loaded } = useDynamicLoadLanguage(languageId);
+
   const editorRef = useRef<EditorAPI | null>(null);
 
   useEffect(() => {
@@ -61,13 +62,9 @@ export function CodeEditor({
     }
   }, [value]);
 
-  useEffect(() => {
-    if (languageId === 'typescript') {
-      import('./init-worker').then((module) => {
-        module.initTsWorker();
-      });
-    }
-  }, [languageId]);
+  if (!loaded) {
+    return <Skeleton />;
+  }
 
   return (
     <EditorProvider>

+ 3 - 1
packages/materials/form-materials/src/components/code-editor/index.tsx

@@ -6,7 +6,9 @@
 import { lazySuspense } from '@/shared';
 
 export const CodeEditor = lazySuspense(() =>
-  import('./editor').then((module) => ({ default: module.CodeEditor }))
+  Promise.all([import('./editor'), import('./theme')]).then(([editorModule]) => ({
+    default: editorModule.CodeEditor,
+  }))
 );
 
 export type { CodeEditorPropsType } from './editor';

+ 0 - 27
packages/materials/form-materials/src/components/code-editor/init-worker.ts

@@ -1,27 +0,0 @@
-/**
- * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
- * SPDX-License-Identifier: MIT
- */
-
-import { typescript } from '@flowgram.ai/coze-editor/language-typescript';
-
-let tsWorkerInit = false;
-
-export const initTsWorker = () => {
-  if (tsWorkerInit) {
-    return;
-  }
-  tsWorkerInit = true;
-
-  const tsWorker = new Worker(
-    new URL(`@flowgram.ai/coze-editor/language-typescript/worker`, import.meta.url),
-    { type: 'module' }
-  );
-  typescript.languageService.initialize(tsWorker, {
-    compilerOptions: {
-      // eliminate Promise error
-      lib: ['es2015', 'dom'],
-      noImplicitAny: false,
-    },
-  });
-};

+ 52 - 14
packages/materials/form-materials/src/components/code-editor/language-features.ts

@@ -3,21 +3,59 @@
  * SPDX-License-Identifier: MIT
  */
 
+import { useEffect, useMemo, useState } from 'react';
+
 import { languages } from '@flowgram.ai/coze-editor/preset-code';
-import { typescript } from '@flowgram.ai/coze-editor/language-typescript';
-import { shell } from '@flowgram.ai/coze-editor/language-shell';
-import { python } from '@flowgram.ai/coze-editor/language-python';
-import { json } from '@flowgram.ai/coze-editor/language-json';
 import { mixLanguages } from '@flowgram.ai/coze-editor';
 
-languages.register('python', python);
-languages.register('shell', shell);
-languages.register('typescript', typescript);
+export const dynamicLoadLanguages: Record<string, () => Promise<void>> = {
+  python: () =>
+    import('@flowgram.ai/coze-editor/language-python').then((module) => {
+      languages.register('python', module.python);
+    }),
+  shell: () =>
+    import('@flowgram.ai/coze-editor/language-shell').then((module) => {
+      languages.register('shell', module.shell);
+    }),
+  typescript: () =>
+    import('@flowgram.ai/coze-editor/language-typescript').then((module) => {
+      languages.register('typescript', module.typescript);
+
+      // Init TypeScript language service
+      const tsWorker = new Worker(
+        new URL(`@flowgram.ai/coze-editor/language-typescript/worker`, import.meta.url),
+        { type: 'module' }
+      );
+      module.typescript.languageService.initialize(tsWorker, {
+        compilerOptions: {
+          // eliminate Promise error
+          lib: ['es2015', 'dom'],
+          noImplicitAny: false,
+        },
+      });
+    }),
+  json: () =>
+    import('@flowgram.ai/coze-editor/language-json').then((module) => {
+      languages.register('json', {
+        // mixLanguages is used to solve the problem that interpolation also uses parentheses, which causes incorrect highlighting
+        language: mixLanguages({
+          outerLanguage: module.json.language,
+        }),
+        languageService: module.json.languageService,
+      });
+    }),
+};
+
+export const useDynamicLoadLanguage = (languageId: string) => {
+  const [loaded, setLoaded] = useState(useMemo(() => !!languages.get(languageId), [languageId]));
+
+  useEffect(() => {
+    if (!loaded && dynamicLoadLanguages[languageId]) {
+      dynamicLoadLanguages[languageId]().then(() => {
+        setLoaded(true);
+      });
+    }
+  }, [languageId, loaded]);
 
-languages.register('json', {
-  // mixLanguages is used to solve the problem that interpolation also uses parentheses, which causes incorrect highlighting
-  language: mixLanguages({
-    outerLanguage: json.language,
-  }),
-  languageService: json.languageService,
-});
+  return { loaded };
+};

+ 62 - 77
packages/materials/form-materials/src/components/code-editor/theme/dark.ts

@@ -6,28 +6,45 @@
 import { createTheme, tags as t } from '@flowgram.ai/coze-editor/preset-code';
 import { type Extension } from '@codemirror/state';
 
-const colors = {
-  background: '#0D1117',
-  // syntax - 现代化暗色主题配色
-  comment: '#8B949E',
-  key: '#7DD3FC',
-  variable: '#F472B6',
-  string: '#34D399',
-  number: '#FBBF24',
-  boolean: '#A78BFA',
-  null: '#A78BFA',
-  separator: '#E6EDF3',
+export const colors = {
+  background: '#24292e',
+  foreground: '#d1d5da',
+  selection: '#3392FF44',
+  cursor: '#c8e1ff',
+  dropdownBackground: '#24292e',
+  dropdownBorder: '#1b1f23',
+  activeLine: '#4d566022',
+  matchingBracket: '#888892',
+  keyword: '#9197F1',
+  storage: '#f97583',
+  variable: '#ffab70',
+  variableName: '#D9DCFA',
+  parameter: '#e1e4e8',
+  function: '#FFCA66',
+  string: '#FF9878',
+  constant: '#79b8ff',
+  type: '#79b8ff',
+  class: '#b392f0',
+  number: '#2EC7D9',
+  comment: '#568B2A',
+  heading: '#79b8ff',
+  invalid: '#f97583',
+  regexp: '#9ecbff',
+  propertyName: '#9197F1',
+  separator: '#888892',
+  gutters: '#888892',
+  moduleKeyword: '#CC4FD4',
 };
 
 export const darkTheme: Extension = createTheme({
   variant: 'dark',
   settings: {
     background: colors.background,
-    foreground: '#E6EDF3',
-    caret: '#7DD3FC',
-    selection: '#264F7833',
+    foreground: colors.foreground,
+    caret: colors.cursor,
+    selection: colors.selection,
     gutterBackground: colors.background,
-    gutterForeground: '#6E7681',
+    gutterForeground: colors.foreground,
     gutterBorderColor: 'transparent',
     gutterBorderWidth: 0,
     lineHighlight: '#21262D',
@@ -44,7 +61,8 @@ export const darkTheme: Extension = createTheme({
       backgroundColor: '#21262D',
     },
     completionItemSelected: {
-      backgroundColor: '#1F6EEB',
+      backgroundColor: colors.selection,
+      color: colors.foreground,
     },
     completionItemIcon: {
       color: '#8B949E',
@@ -60,79 +78,46 @@ export const darkTheme: Extension = createTheme({
     },
   },
   styles: [
-    // json
+    { tag: t.keyword, color: colors.keyword },
+    { tag: t.variableName, color: colors.variableName },
     {
-      tag: t.comment,
-      color: colors.comment,
+      tag: [t.name, t.deleted, t.character, t.macroName],
+      color: colors.variable,
     },
+    { tag: [t.propertyName], color: colors.propertyName },
     {
-      tag: [t.propertyName],
-      color: colors.key,
-    },
-    {
-      tag: [t.string],
+      tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)],
       color: colors.string,
     },
     {
-      tag: [t.number],
-      color: colors.number,
+      tag: [t.function(t.variableName), t.function(t.propertyName), t.labelName],
+      color: colors.function,
     },
     {
-      tag: [t.bool],
-      color: colors.boolean,
+      tag: [t.moduleKeyword, t.controlKeyword],
+      color: colors.moduleKeyword,
     },
     {
-      tag: [t.null],
-      color: colors.null,
+      tag: [t.color, t.constant(t.name), t.standard(t.name)],
+      color: colors.constant,
     },
+    { tag: t.definition(t.name), color: colors.variable },
+    { tag: [t.className], color: colors.class },
     {
-      tag: [t.separator],
-      color: colors.separator,
-    },
-
-    // js
-    {
-      tag: [t.definitionKeyword],
-      color: '#C084FC',
-    },
-    {
-      tag: [t.modifier],
-      color: '#C084FC',
-    },
-    {
-      tag: [t.controlKeyword],
-      color: '#C084FC',
-    },
-    {
-      tag: [t.operatorKeyword],
-      color: '#C084FC',
-    },
-
-    // markdown
-    {
-      tag: [t.heading],
-      color: '#7DD3FC',
-    },
-    {
-      tag: [t.processingInstruction],
-      color: '#7DD3FC',
-    },
-
-    // shell
-    // curl
-    {
-      tag: [t.standard(t.variableName)],
-      color: '#34D399',
-    },
-    // -X
-    {
-      tag: [t.attributeName],
-      color: '#FBBF24',
-    },
-    // url in string (includes quotes), e.g. "https://..."
-    {
-      tag: [t.special(t.string)],
-      color: '#7DD3FC',
+      tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
+      color: colors.number,
     },
+    { tag: [t.typeName], color: colors.type, fontStyle: colors.type },
+    { tag: [t.operatorKeyword], color: colors.keyword },
+    { tag: [t.url, t.escape, t.regexp, t.link], color: colors.regexp },
+    { tag: [t.meta, t.comment], color: colors.comment },
+    { tag: t.strong, fontWeight: 'bold' },
+    { tag: t.emphasis, fontStyle: 'italic' },
+    { tag: t.link, textDecoration: 'underline' },
+    { tag: t.heading, fontWeight: 'bold', color: colors.heading },
+    { tag: [t.atom, t.bool, t.special(t.variableName)], color: colors.variable },
+    { tag: t.invalid, color: colors.invalid },
+    { tag: t.strikethrough, textDecoration: 'line-through' },
+    { tag: t.separator, color: colors.separator },
   ],
 });

+ 60 - 91
packages/materials/form-materials/src/components/code-editor/theme/light.ts

@@ -6,45 +6,61 @@
 import { createTheme, tags as t } from '@flowgram.ai/coze-editor/preset-code';
 import { type Extension } from '@codemirror/state';
 
-const colors = {
-  background: '#FFFFFF',
-  comment: '#6B7280',
-  key: '#2563EB',
-  variable: '#DC2626',
-  string: '#059669',
-  number: '#EA580C',
-  boolean: '#7C3AED',
-  null: '#7C3AED',
-  separator: '#374151',
+export const colors = {
+  background: '#f4f5f5',
+  foreground: '#444d56',
+  selection: '#0366d625',
+  cursor: '#044289',
+  dropdownBackground: '#fff',
+  dropdownBorder: '#e1e4e8',
+  activeLine: '#c6c6c622',
+  matchingBracket: '#34d05840',
+  keyword: '#d73a49',
+  storage: '#d73a49',
+  variable: '#e36209',
+  parameter: '#24292e',
+  function: '#005cc5',
+  string: '#032f62',
+  constant: '#005cc5',
+  type: '#005cc5',
+  class: '#6f42c1',
+  number: '#005cc5',
+  comment: '#6a737d',
+  heading: '#005cc5',
+  invalid: '#cb2431',
+  regexp: '#032f62',
 };
 
 export const lightTheme: Extension = createTheme({
   variant: 'light',
   settings: {
-    background: '#FFFFFF',
-    foreground: '#1F2937',
-    caret: '#2563EB',
-    selection: '#E5E7EB',
-    gutterBackground: '#F9FAFB',
-    gutterForeground: '#6B7280',
+    background: colors.background,
+    foreground: colors.foreground,
+    caret: colors.cursor,
+    selection: colors.selection,
+    gutterBackground: colors.background,
+    gutterForeground: colors.foreground,
     gutterBorderColor: 'transparent',
     gutterBorderWidth: 0,
-    lineHighlight: '#F3F4F680',
+    lineHighlight: colors.background,
     bracketColors: ['#F59E0B', '#8B5CF6', '#06B6D4'],
     tooltip: {
-      backgroundColor: '#FFFFFF',
-      color: '#1F2937',
-      border: '1px solid #E5E7EB',
-      boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
+      backgroundColor: colors.dropdownBackground,
+      color: colors.foreground,
+      border: 'none',
+      boxShadow: '0 0 1px rgba(0, 0, 0, .3), 0 4px 14px rgba(0, 0, 0, .1)!important',
+      maxWidth: '400px',
     },
     link: {
       color: '#2563EB',
+      caret: colors.cursor,
     },
     completionItemHover: {
       backgroundColor: '#F3F4F6',
     },
     completionItemSelected: {
-      backgroundColor: '#E5E7EB',
+      backgroundColor: colors.selection,
+      color: colors.foreground,
     },
     completionItemIcon: {
       color: '#4B5563',
@@ -60,84 +76,37 @@ export const lightTheme: Extension = createTheme({
     },
   },
   styles: [
-    // JSON
-    {
-      tag: t.comment,
-      color: colors.comment,
-    },
-    {
-      tag: [t.propertyName],
-      color: colors.key,
-    },
+    { tag: t.keyword, color: colors.keyword },
     {
-      tag: [t.variableName],
+      tag: [t.name, t.deleted, t.character, t.macroName],
       color: colors.variable,
     },
-
+    { tag: [t.propertyName], color: colors.function },
     {
-      tag: [t.string],
+      tag: [t.processingInstruction, t.string, t.inserted, t.special(t.string)],
       color: colors.string,
     },
+    { tag: [t.function(t.variableName), t.labelName], color: colors.function },
     {
-      tag: [t.number],
-      color: colors.number,
-    },
-    {
-      tag: [t.bool],
-      color: colors.boolean,
-    },
-    {
-      tag: [t.null],
-      color: colors.null,
-    },
-    {
-      tag: [t.separator],
-      color: colors.separator,
-    },
-
-    // markdown
-    {
-      tag: [t.heading],
-      color: '#2563EB',
-    },
-    {
-      tag: [t.processingInstruction],
-      color: '#2563EB',
-    },
-
-    // js
-    {
-      tag: [t.definitionKeyword],
-      color: '#9333EA',
-    },
-    {
-      tag: [t.modifier],
-      color: '#9333EA',
-    },
-    {
-      tag: [t.controlKeyword],
-      color: '#9333EA',
+      tag: [t.color, t.constant(t.name), t.standard(t.name)],
+      color: colors.constant,
     },
+    { tag: [t.definition(t.name), t.separator], color: colors.variable },
+    { tag: [t.className], color: colors.class },
     {
-      tag: [t.operatorKeyword],
-      color: '#9333EA',
-    },
-
-    // shell
-    // curl
-    {
-      tag: [t.standard(t.variableName)],
-      color: '#059669',
-    },
-    // -X
-    {
-      tag: [t.attributeName],
-      color: '#EA580C',
-    },
-    // url in string (includes quotes), e.g. "https://..."
-    {
-      tag: [t.special(t.string)],
-      color: '#2563EB',
+      tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
+      color: colors.number,
     },
+    { tag: [t.typeName], color: colors.type, fontStyle: colors.type },
+    { tag: [t.operator, t.operatorKeyword], color: colors.keyword },
+    { tag: [t.url, t.escape, t.regexp, t.link], color: colors.regexp },
+    { tag: [t.meta, t.comment], color: colors.comment },
+    { tag: t.strong, fontWeight: 'bold' },
+    { tag: t.emphasis, fontStyle: 'italic' },
+    { tag: t.link, textDecoration: 'underline' },
+    { tag: t.heading, fontWeight: 'bold', color: colors.heading },
+    { tag: [t.atom, t.bool, t.special(t.variableName)], color: colors.variable },
+    { tag: t.invalid, color: colors.invalid },
+    { tag: t.strikethrough, textDecoration: 'line-through' },
   ],
 });

+ 6 - 3
packages/materials/form-materials/src/components/coze-editor-extensions/extensions/variable-tag.tsx

@@ -63,10 +63,13 @@ class VariableTagWidget extends WidgetType {
       return;
     }
 
-    const rootField = last(v.parentFields);
+    const rootField = last(v.parentFields) || v;
+    const isRoot = v === rootField;
 
     const rootTitle = (
-      <UIRootTitle>{rootField?.meta.title ? `${rootField.meta.title} -` : ''}</UIRootTitle>
+      <UIRootTitle>
+        {rootField.meta?.title ? `${rootField.meta.title} ${isRoot ? '' : '-'} ` : ''}
+      </UIRootTitle>
     );
     const rootIcon = this.renderIcon(rootField?.meta.icon);
 
@@ -82,7 +85,7 @@ class VariableTagWidget extends WidgetType {
       >
         <UITag prefixIcon={rootIcon}>
           {rootTitle}
-          <UIVarName>{v?.key}</UIVarName>
+          {!isRoot && <UIVarName>{v?.key}</UIVarName>}
         </UITag>
       </Popover>
     );

+ 3 - 0
packages/materials/form-materials/src/components/coze-editor-extensions/extensions/variable-tree.tsx

@@ -82,6 +82,9 @@ export function VariableTree({
           <div style={{ width: 300, maxHeight: 300, overflowY: 'auto' }}>
             <Tree
               treeData={treeData}
+              onExpand={(v) => {
+                setPosKey(String(Math.random()));
+              }}
               onSelect={(v) => {
                 insert(v);
               }}

+ 4 - 1
packages/materials/form-materials/src/plugins/json-schema-preset/react.tsx

@@ -12,9 +12,12 @@ import {
   JsonSchemaTypeManager,
 } from '@flowgram.ai/json-schema';
 
-import { jsonSchemaTypePreset } from './type-definition';
+import { initRegistries, jsonSchemaTypePreset } from './type-definition';
 import { type JsonSchemaTypeRegistry } from './manager';
 
+// If you want to use new type Manager, init registries
+initRegistries();
+
 export const useTypeManager = () =>
   useOriginTypeManager() as JsonSchemaTypeManager<IJsonSchema, JsonSchemaTypeRegistry>;
 

+ 3 - 1
packages/materials/form-materials/src/plugins/json-schema-preset/type-definition/index.tsx

@@ -23,4 +23,6 @@ export const jsonSchemaTypePreset = [
   dateTimeRegistry,
 ];
 
-jsonSchemaTypePreset.forEach((_type) => jsonSchemaTypeManager.register(_type));
+export const initRegistries = () => {
+  jsonSchemaTypePreset.forEach((_type) => jsonSchemaTypeManager.register(_type));
+};

+ 2 - 2
packages/variable-engine/json-schema/src/json-schema/json-schema-type-manager.tsx

@@ -51,8 +51,8 @@ export class JsonSchemaTypeManager<
     const registries = [
       defaultTypeDefinitionRegistry,
       stringRegistryCreator,
-      numberRegistryCreator,
       integerRegistryCreator,
+      numberRegistryCreator,
       booleanRegistryCreator,
       objectRegistryCreator,
       arrayRegistryCreator,
@@ -121,7 +121,7 @@ export class JsonSchemaTypeManager<
 
   public getDisplayIcon = (type: Schema) => {
     const registry = this.getTypeBySchema(type);
-    return registry?.getDisplayIcon(type) || registry?.icon || <></>;
+    return registry?.getDisplayIcon?.(type) || registry?.icon || <></>;
   };
 
   public getTypeSchemaProperties = (type: Schema) => {