diff --git a/client/package.json b/client/package.json index 12f93a4aa7..8f2d98e0c5 100644 --- a/client/package.json +++ b/client/package.json @@ -73,6 +73,7 @@ }, "dependencies": { "@lottiefiles/react-lottie-player": "^3.5.3", + "@monaco-editor/react": "^4.7.0", "@remixicon/react": "^4.1.1", "@supabase/supabase-js": "^2.45.4", "@testing-library/react": "^14.1.2", @@ -81,6 +82,7 @@ "antd-mobile": "^5.34.0", "chalk": "4", "flag-icons": "^7.2.1", + "monaco-editor": "^0.52.2", "number-precision": "^1.6.0", "react-countup": "^6.5.3", "react-github-btn": "^1.4.0", diff --git a/client/packages/lowcoder-design/src/icons/index.tsx b/client/packages/lowcoder-design/src/icons/index.tsx index b033d52e92..96b75c985b 100644 --- a/client/packages/lowcoder-design/src/icons/index.tsx +++ b/client/packages/lowcoder-design/src/icons/index.tsx @@ -329,6 +329,7 @@ export { ReactComponent as RangeSliderCompIconSmall } from "./v2/range-slider-s. export { ReactComponent as RatingCompIconSmall } from "./v2/rating-s.svg"; export { ReactComponent as ResponsiveLayoutCompIconSmall } from "./v2/resposive-layout-s.svg"; // new export { ReactComponent as SplitLayoutCompIconSmall } from "./v2/split-layout-s.svg"; // new +export { ReactComponent as CodeEditorCompIconSmall } from "./v2/code-editor-s.svg"; // new export { ReactComponent as RichTextEditorCompIconSmall } from "./v2/rich-text-editor-s.svg"; // new export { ReactComponent as ScannerCompIconSmall } from "./v2/scanner-s.svg"; // new export { ReactComponent as ShapesCompIconSmall } from "./v2/shapes-s.svg"; // new @@ -437,6 +438,7 @@ export { ReactComponent as RangeSliderCompIcon } from "./v2/range-slider-m.svg"; export { ReactComponent as RatingCompIcon } from "./v2/rating-m.svg"; export { ReactComponent as ResponsiveLayoutCompIcon } from "./v2/resposive-layout-m.svg"; export { ReactComponent as SplitLayoutCompIcon } from "./v2/split-layout-m.svg"; +export { ReactComponent as CodeEditorCompIcon } from "./v2/code-editor-m.svg"; export { ReactComponent as RichTextEditorCompIcon } from "./v2/rich-text-editor-m.svg"; export { ReactComponent as ScannerCompIcon } from "./v2/scanner-m.svg"; export { ReactComponent as ShapesCompIcon } from "./v2/shapes-m.svg"; diff --git a/client/packages/lowcoder-design/src/icons/v2/code-editor-m.svg b/client/packages/lowcoder-design/src/icons/v2/code-editor-m.svg new file mode 100644 index 0000000000..95615f9ea3 --- /dev/null +++ b/client/packages/lowcoder-design/src/icons/v2/code-editor-m.svg @@ -0,0 +1,15 @@ + + + + Layer 1 + + + + + + + + + + + diff --git a/client/packages/lowcoder-design/src/icons/v2/code-editor-s.svg b/client/packages/lowcoder-design/src/icons/v2/code-editor-s.svg new file mode 100644 index 0000000000..ebbeed86d3 --- /dev/null +++ b/client/packages/lowcoder-design/src/icons/v2/code-editor-s.svg @@ -0,0 +1,15 @@ + + + + Layer 1 + + + + + + + + + + + diff --git a/client/packages/lowcoder/src/comps/comps/codeEditorComp/codeEditorComp.tsx b/client/packages/lowcoder/src/comps/comps/codeEditorComp/codeEditorComp.tsx new file mode 100644 index 0000000000..ab26c326f8 --- /dev/null +++ b/client/packages/lowcoder/src/comps/comps/codeEditorComp/codeEditorComp.tsx @@ -0,0 +1,543 @@ +import { + UICompBuilder, + NameConfig, + Section, + withDefault, + withExposingConfigs, + withMethodExposing, + eventHandlerControl, + stringExposingStateControl, + BoolControl, + LabelControl, + styleControl, + dropdownControl, + AutoHeightControl, +} from "lowcoder-sdk"; +import { CodeEditorContainerStyle, LabelStyle } from "comps/controls/styleControlConstants"; +import { useResizeDetector } from "react-resize-detector"; +import Editor from "@monaco-editor/react"; +import { styled } from "styled-components"; +import { trans } from "i18n"; +import { useRef, useCallback, useLayoutEffect } from "react"; +import debounce from "lodash/debounce"; +import * as monacoEditor from "monaco-editor"; +import { formDataChildren, FormDataPropertyView } from "../../comps/formComp/formDataConstants"; + +const CodeEditorWrapper = styled.div` + border: 1px solid #dddddd; +`; + +let CodeEditorTmpComp = (function () { + + const languages = [ + { label: trans("codeEditor.languages.yaml"), value: "yaml" }, + { label: trans("codeEditor.languages.json"), value: "json" }, + { label: trans("codeEditor.languages.xml"), value: "xml" }, + { label: trans("codeEditor.languages.html"), value: "html" }, + { label: trans("codeEditor.languages.css"), value: "css" }, + { label: trans("codeEditor.languages.ini"), value: "ini" }, + { label: trans("codeEditor.languages.sql"), value: "sql" }, + { label: trans("codeEditor.languages.php"), value: "php" }, + { label: trans("codeEditor.languages.shell"), value: "shell" }, + { label: trans("codeEditor.languages.powershell"), value: "powershell" }, + { label: trans("codeEditor.languages.handlebars"), value: "handlebars" }, + { label: trans("codeEditor.languages.dockerfile"), value: "dockerfile" }, + { label: trans("codeEditor.languages.graphql"), value: "graphql" }, + { label: trans("codeEditor.languages.markdown"), value: "markdown" }, + { label: trans("codeEditor.languages.plaintext"), value: "plaintext" }, + { label: trans("codeEditor.languages.python"), value: "python" }, + { label: trans("codeEditor.languages.ruby"), value: "ruby" }, + { label: trans("codeEditor.languages.rust"), value: "rust" }, + { label: trans("codeEditor.languages.java"), value: "java" }, + { label: trans("codeEditor.languages.c"), value: "c" }, + { label: trans("codeEditor.languages.csharp"), value: "csharp" }, + { label: trans("codeEditor.languages.cpp"), value: "cpp" }, + { label: trans("codeEditor.languages.go"), value: "go" }, + { label: trans("codeEditor.languages.javascript"), value: "javascript" }, + { label: trans("codeEditor.languages.typescript"), value: "typescript" } + ].sort((a, b) => a.label.localeCompare(b.label)) + + const defaultValues = { + value: "", + language: "yaml", + theme: "light", + lineNumbers: "on", + wordWrap: "on", + lightbulb: monacoEditor.editor.ShowLightbulbIconMode.OnCode, + enabled: true, + disabled: false, + autoHeight: "445px", + } + + const themes = [ + { label: trans("codeEditor.theme.light"), value: "light" }, + { label: trans("codeEditor.theme.dark"), value: "vs-dark" }, + ].sort((a, b) => a.label.localeCompare(b.label)) + + const lineNumbersOptions = [ + { label: trans("codeEditor.lineNumberOptions.on"), value: "on" }, + { label: trans("codeEditor.lineNumberOptions.off"), value: "off" }, + { label: trans("codeEditor.lineNumberOptions.interval"), value: "interval" }, + { label: trans("codeEditor.lineNumberOptions.relative"), value: "relative" }, + ].sort((a, b) => a.label.localeCompare(b.label)) + + const wordWrapOptions = [ + { label: trans("codeEditor.wordWrapOptions.on"), value: "on" }, + { label: trans("codeEditor.wordWrapOptions.off"), value: "off" }, + { label: trans("codeEditor.wordWrapOptions.wordWrapColumn"), value: "wordWrapColumn" }, + { label: trans("codeEditor.wordWrapOptions.bounded"), value: "bounded" }, + ].sort((a, b) => a.label.localeCompare(b.label)) + + const childrenMap = { + autoHeight: withDefault(AutoHeightControl, "auto"), + language: dropdownControl(languages, defaultValues.language), + theme: dropdownControl(themes, defaultValues.theme), + lineNumbers: dropdownControl(lineNumbersOptions, defaultValues.lineNumbers), + wordWrap: dropdownControl(wordWrapOptions, defaultValues.wordWrap), + minimap: withDefault(BoolControl, defaultValues.enabled), + stickyScroll: withDefault(BoolControl, defaultValues.enabled), + lightbulb: withDefault(BoolControl, defaultValues.enabled), + hover: withDefault(BoolControl, defaultValues.enabled), + folding: withDefault(BoolControl, defaultValues.enabled), + readOnly: withDefault(BoolControl, defaultValues.disabled), + value: stringExposingStateControl("text", defaultValues.value), + required: withDefault(BoolControl, defaultValues.disabled), + label: withDefault(LabelControl, { + text: "Code Editor", + tooltip: "", + hidden: false, + widthUnit: "%", + position: "column", + align: "left" + }), + style: styleControl(CodeEditorContainerStyle , "style"), + labelStyle: styleControl(LabelStyle , 'labelStyle'), + onEvent: eventHandlerControl([ + { + label: "onChange", + value: "change", + description: "Triggers when data changes", + }, + ] as const), + ...formDataChildren, + }; + + return new UICompBuilder(childrenMap, (props) => { + + const editorRef = useRef(null); + const lastExternalValue = useRef(props.value.value); + + const { ref: conRef } = useResizeDetector({ + onResize: () => { + if (editorRef.current) { + setTimeout(() => { + editorRef.current?.layout(); + }, 0); + } + } + }); + + const getEffectiveDimensions = () => { + if (props.autoHeight) { + return { + width: "100%", + height: defaultValues.autoHeight + }; + } + return { + width: "100%", + height: "100%" + }; + }; + + const effectiveDimensions = getEffectiveDimensions(); + + const handleEditorDidMount = ( + editor: monacoEditor.editor.IStandaloneCodeEditor + ) => { + editorRef.current = editor; + setTimeout(() => { + editor.layout(); + }, 0); + }; + + const debouncedOnChange = useCallback( + debounce((value: string | undefined) => { + if(props.value && value !== undefined) { + lastExternalValue.current = value; + props.value.onChange(value); + props.onEvent("change"); + } + }, 300), + [props.value, props.onEvent] + ); + + const handleOnChange = (value: string | undefined) => { + if (value !== undefined) { + debouncedOnChange(value); + } + }; + + useLayoutEffect(() => { + const editor = editorRef.current; + if (!editor) return; + + const currentValue = editor.getValue(); + const newValue = props.value.value; + + if (newValue !== currentValue && newValue !== lastExternalValue.current) { + const position = editor.getPosition(); + const scrollTop = editor.getScrollTop(); + + editor.setValue(newValue); + lastExternalValue.current = newValue; + + if (position) { + editor.setPosition(position); + } + editor.setScrollTop(scrollTop); + } + }, [props.value.value]); + + return props.label({ + required: props.required, + style: props.style, + children: ( + + + + ) + }) +}) +.setPropertyViewFn((children: any) => { + return ( + <> +
+ {children.value.propertyView({ label: trans("codeEditor.properties.value") })} + {children.language.propertyView({ label: trans("codeEditor.properties.language") })} + {children.theme.propertyView({ label: trans("codeEditor.properties.theme") })} + {children.lineNumbers.propertyView({ label: trans("codeEditor.properties.lineNumbers") })} + {children.wordWrap.propertyView({ label: trans("codeEditor.properties.wordWrap") })} + {children.minimap.propertyView({ label: trans("codeEditor.properties.minimap") })} + {children.stickyScroll.propertyView({ label: trans("codeEditor.properties.stickyScroll")})} + {children.lightbulb.propertyView({ label: trans("codeEditor.properties.lightbulb") })} + {children.hover.propertyView({ label: trans("codeEditor.properties.hover") })} + {children.folding.propertyView({ label: trans("codeEditor.properties.folding") })} +
+ {children.label.getPropertyView()} +
+ {children.onEvent.propertyView()} +
+
+ {children.autoHeight.getPropertyView()} +
+
+ {children.readOnly.propertyView({ label: trans("codeEditor.properties.readOnly") })} +
+
+ {children.required.propertyView({ label: trans("codeEditor.properties.required") })} +
+
+ {children.style.getPropertyView()} +
+
+ {children.labelStyle.getPropertyView()} +
+ + + ); +}) +.build(); +})(); + +CodeEditorTmpComp = class extends CodeEditorTmpComp { + autoHeight(): boolean { + return this.children.autoHeight.getView(); + } +}; + +CodeEditorTmpComp = withMethodExposing(CodeEditorTmpComp, [ + { + method: { + name: "setValue", + description: trans("codeEditor.methods.setValue"), + params: [{ + name: "value", + type: "JSON", + description: "JSON value" + }], + }, + execute: (comp: any, values: any[]) => { + let codeValue = Array.isArray(values) ? values[0] : values; + if (typeof codeValue === "object" && codeValue !== null) { + codeValue = JSON.stringify(codeValue, null, 2); + } + comp.children.value.dispatchChangeValueAction(codeValue); + } + }, + { + method: { + name: "setLanguage", + description: trans("codeEditor.methods.setLanguage"), + params: [{ + name: "language", + type: "string", + description: "string" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.language.dispatchChangeValueAction(values[0]); + } else { + comp.children.language.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "setTheme", + description: trans("codeEditor.methods.setTheme"), + params: [{ + name: "theme", + type: "string", + description: "string" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.theme.dispatchChangeValueAction(values[0]); + } else { + comp.children.theme.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "setLineNumbers", + description: trans("codeEditor.methods.setLineNumbers"), + params: [{ + name: "lineNumbers", + type: "string", + description: "string" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.lineNumbers.dispatchChangeValueAction(values[0]); + } else { + comp.children.lineNumbers.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "enableMinimap", + description: trans("codeEditor.methods.enableMinimap"), + params: [{ + name: "minimap", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.minimap.dispatchChangeValueAction(values[0]); + } else { + comp.children.minimap.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "enableStickyScroll", + description: trans("codeEditor.methods.enableStickyScroll"), + params: [{ + name: "stickyScroll", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.stickyScroll.dispatchChangeValueAction(values[0]); + } else { + comp.children.stickyScroll.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "enableLightbulb", + description: trans("codeEditor.methods.enableLightbulb"), + params: [{ + name: "lightbulb", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + const lightbulbEnum = monacoEditor.editor.ShowLightbulbIconMode; + + if(Array.isArray(values)) { + if(Boolean(values[0])) { + comp.children.lightbulb.dispatchChangeValueAction(lightbulbEnum.OnCode); + } else { + comp.children.lightbulb.dispatchChangeValueAction(lightbulbEnum.Off); + } + } else { + if(Boolean(values)) { + comp.children.lightbulb.dispatchChangeValueAction(lightbulbEnum.OnCode); + } else { + comp.children.lightbulb.dispatchChangeValueAction(lightbulbEnum.Off); + } + } + } + }, + { + method: { + name: "enableHover", + description: trans("codeEditor.methods.enableHover"), + params: [{ + name: "hover", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.hover.dispatchChangeValueAction(values[0]); + } else { + comp.children.hover.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "enableFolding", + description: trans("codeEditor.methods.enableFolding"), + params: [{ + name: "folding", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.folding.dispatchChangeValueAction(values[0]); + } else { + comp.children.folding.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "enableWordWrap", + description: trans("codeEditor.methods.enableWordWrap"), + params: [{ + name: "wordWrap", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.wordWrap.dispatchChangeValueAction(values[0]); + } else { + comp.children.wordWrap.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "setReadOnly", + description: trans("codeEditor.methods.setReadOnly"), + params: [{ + name: "readOnly", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.readOnly.dispatchChangeValueAction(values[0]); + } else { + comp.children.readOnly.dispatchChangeValueAction(values); + } + } + }, + { + method: { + name: "markAsRequired", + description: trans("codeEditor.methods.markAsRequired"), + params: [{ + name: "required", + type: "boolean", + description: "boolean" + }], + }, + execute: (comp: any, values: any[]) => { + if(Array.isArray(values)) { + comp.children.required.dispatchChangeValueAction(values[0]); + } else { + comp.children.required.dispatchChangeValueAction(values); + } + } + }, +]); + +export const CodeEditorComp = withExposingConfigs(CodeEditorTmpComp, [ + new NameConfig("value", trans("codeEditor.properties.value")), + new NameConfig("language", trans("codeEditor.properties.language")), + new NameConfig("theme", trans("codeEditor.properties.theme")), + new NameConfig("lineNumbers", trans("codeEditor.properties.lineNumbers")), + new NameConfig("wordWrap", trans("codeEditor.properties.wordWrap")), + new NameConfig("minimap", trans("codeEditor.properties.minimap")), + new NameConfig("stickyScroll", trans("codeEditor.properties.stickyScroll")), + new NameConfig("lightbulb", trans("codeEditor.properties.lightbulb")), + new NameConfig("hover", trans("codeEditor.properties.hover")), + new NameConfig("folding", trans("codeEditor.properties.folding")), + new NameConfig("readOnly", trans("codeEditor.properties.readOnly")), + new NameConfig("required", trans("codeEditor.properties.required")), +]); \ No newline at end of file diff --git a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx index 176afbbfc9..011c5a8552 100644 --- a/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx +++ b/client/packages/lowcoder/src/comps/controls/styleControlConstants.tsx @@ -1446,6 +1446,10 @@ export const SignatureContainerStyle = [ // ...STYLING_FIELDS_CONTAINER_SEQUENCE, ] as const; +export const CodeEditorContainerStyle = [ + getStaticBorder(), +] as const; + export const RatingStyle = [ { name: "checked", diff --git a/client/packages/lowcoder/src/comps/index-test.tsx b/client/packages/lowcoder/src/comps/index-test.tsx index b832d8a1b3..4c0770c2b4 100644 --- a/client/packages/lowcoder/src/comps/index-test.tsx +++ b/client/packages/lowcoder/src/comps/index-test.tsx @@ -139,6 +139,7 @@ import { AutoCompleteCompIcon, ResponsiveLayoutCompIcon, MermaidCompIcon, + CodeEditorCompIcon } from "lowcoder-design"; type Registry = { @@ -582,6 +583,21 @@ var uiCompMap: Registry = { h: 50, }, }, + codeEditor: { + name: trans("uiComp.codeEditorCompName"), + enName: "Code Editor", + categories: ["forms"], + description: trans("uiComp.codeEditorCompDesc"), + icon: CodeEditorCompIcon, + keywords: trans("uiComp.codeEditorCompKeywords"), + lazyLoad: true, + compName: "CodeEditorComp", + compPath: "comps/codeEditorComp/codeEditorComp", + layoutInfo: { + w: 12, + h: 50, + } + }, richTextEditor: { name: trans("uiComp.richTextEditorCompName"), enName: "Rich Text Editor", diff --git a/client/packages/lowcoder/src/comps/index.tsx b/client/packages/lowcoder/src/comps/index.tsx index 2395f4f290..35c6f5928e 100644 --- a/client/packages/lowcoder/src/comps/index.tsx +++ b/client/packages/lowcoder/src/comps/index.tsx @@ -117,7 +117,8 @@ import { PieChartCompIcon, BarChartCompIcon, LineChartCompIcon, - ScatterChartCompIcon + ScatterChartCompIcon, + CodeEditorCompIcon } from "lowcoder-design"; import { ModuleComp } from "./comps/moduleComp/moduleComp"; import { TableComp } from "./comps/tableComp/tableComp"; @@ -990,6 +991,21 @@ export var uiCompMap: Registry = { h: 50, }, }, + codeEditor: { + name: trans("uiComp.codeEditorCompName"), + enName: "Code Editor", + categories: ["forms"], + description: trans("uiComp.codeEditorCompDesc"), + icon: CodeEditorCompIcon, + keywords: trans("uiComp.codeEditorCompKeywords"), + lazyLoad: true, + compName: "CodeEditorComp", + compPath: "comps/codeEditorComp/codeEditorComp", + layoutInfo: { + w: 12, + h: 50, + } + }, richTextEditor: { name: trans("uiComp.richTextEditorCompName"), enName: "Rich Text Editor", diff --git a/client/packages/lowcoder/src/comps/uiCompRegistry.ts b/client/packages/lowcoder/src/comps/uiCompRegistry.ts index 4c320de479..0485dbe2e2 100644 --- a/client/packages/lowcoder/src/comps/uiCompRegistry.ts +++ b/client/packages/lowcoder/src/comps/uiCompRegistry.ts @@ -69,6 +69,7 @@ export type UICompType = | "imageEditor" | "calendar" | "password" + | "codeEditor" | "richTextEditor" | "numberInput" | "slider" diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 43bcb39868..36807303a7 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -53,7 +53,77 @@ export const en = { }, "codeEditor": { "notSupportAutoFormat": "The current code editor does not support auto-formatting.", - "fold": "Fold" + "fold": "Fold", + "properties": { + "value": "Default Value", + "language": "Language", + "theme": "Theme", + "lineNumbers": "Line Numbers", + "minimap": "Minimap", + "stickyScroll": "Sticky Scroll", + "lightbulb": "Lightbulb", + "hover": "Hover", + "folding": "Folding", + "wordWrap": "Word Wrap", + "readOnly": "Read Only", + "required": "Required", + }, + "methods": { + "setValue": "Update the editor's content.", + "setLanguage": "Change the programming language.", + "setTheme": "Switch the editor theme.", + "setLineNumbers": "Control the display of line numbers.", + "enableMinimap": "Toggle the minimap display.", + "enableStickyScroll": "Toggle sticky scroll.", + "enableLightbulb": "Toggles lightbulb feature.", + "enableHover": "Enable or disable hover.", + "enableFolding": "Enable or disable folding.", + "setReadOnly": "Enable or disable read-only mode.", + "markAsRequired": "Marks the field as required, preventing submission if left empty.", + }, + "theme": { + "light": "Light", + "dark": "Dark" + }, + "languages": { + "yaml": "YAML", + "json": "JSON", + "xml": "XML", + "html": "HTML", + "css": "CSS", + "ini": "INI", + "sql": "SQL", + "php": "PHP", + "shell": "Shell", + "powershell": "PowerShell", + "handlebars": "Handlebars", + "dockerfile": "Dockerfile", + "graphql": "GraphQL", + "markdown": "Markdown", + "plaintext": "Plaintext", + "python": "Python", + "ruby": "Ruby", + "rust": "Rust", + "java": "Java", + "c": "C", + "csharp": "C#", + "cpp": "C++", + "go": "Go", + "javascript": "JavaScript", + "typescript": "TypeScript" + }, + "lineNumberOptions": { + "on": "On", + "off": "Off", + "relative": "Relative", + "interval": "Interval" + }, + "wordWrapOptions": { + "on": "On", + "off": "Off", + "wordWrapColumn": "Column", + "bounded": "Bounded" + } }, "exportMethod": { "setDesc": "Set Property: {property}", @@ -1014,6 +1084,10 @@ export const en = { "passwordCompDesc": "A secure field for password input, masking the characters for privacy.", "passwordCompKeywords": "password, security, input, hidden", + "codeEditorCompName": "Code Editor", + "codeEditorCompDesc": "A feature-rich code editor component, offering multi-language support, syntax highlighting, and built-in validation.", + "codeEditorCompKeywords": "code, editor, programming, syntax highlighting, coding", + "richTextEditorCompName": "Rich Text Editor", "richTextEditorCompDesc": "An advanced text editor supporting rich formatting options like bold, italics, and lists.", "richTextEditorCompKeywords": "editor, text, formatting, rich content", diff --git a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx index a931455d4b..63be02fab0 100644 --- a/client/packages/lowcoder/src/pages/editor/editorConstants.tsx +++ b/client/packages/lowcoder/src/pages/editor/editorConstants.tsx @@ -104,6 +104,7 @@ import { TurnstileCaptchaCompIconSmall, PivotTableCompIconSmall, GraphChartCompIconSmall, + CodeEditorCompIconSmall, } from "lowcoder-design"; // Memoize icon components to prevent unnecessary re-renders @@ -229,6 +230,7 @@ export const CompStateIcon: { progressCircle: , qrCode: , rating: , + codeEditor: , richTextEditor: , scanner: , segmentedControl: , diff --git a/client/yarn.lock b/client/yarn.lock index b3885ff806..1d1387e555 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -3030,6 +3030,28 @@ __metadata: languageName: node linkType: hard +"@monaco-editor/loader@npm:^1.5.0": + version: 1.5.0 + resolution: "@monaco-editor/loader@npm:1.5.0" + dependencies: + state-local: ^1.0.6 + checksum: 45e5f56ea9b1e5c16e3d40b05f8c365af830627d2aa8215c86cfac57384419c1b896927408c1261a12dc182a08419d4f20a0d0949d3e76ca42ccc68f4ffec508 + languageName: node + linkType: hard + +"@monaco-editor/react@npm:^4.7.0": + version: 4.7.0 + resolution: "@monaco-editor/react@npm:4.7.0" + dependencies: + "@monaco-editor/loader": ^1.5.0 + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 8b3bd8adfcd6af70dc5f965e986932269e1e2c2a0f6beb5a3c632c8c7942c1341f6086d9664f9a949983bdf4a04a706e529a93bfec3b5884642915dfcc0354c3 + languageName: node + linkType: hard + "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -13971,6 +13993,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: "@babel/preset-env": ^7.20.2 "@babel/preset-typescript": ^7.18.6 "@lottiefiles/react-lottie-player": ^3.5.3 + "@monaco-editor/react": ^4.7.0 "@remixicon/react": ^4.1.1 "@rollup/plugin-typescript": ^12.1.0 "@supabase/supabase-js": ^2.45.4 @@ -14005,6 +14028,7 @@ coolshapes-react@lowcoder-org/coolshapes-react: jest-environment-jsdom: ^29.5.0 lint-staged: ^13.0.1 lowcoder-cli: "workspace:^" + monaco-editor: ^0.52.2 mq-polyfill: ^1.1.8 number-precision: ^1.6.0 prettier: ^3.1.0 @@ -15585,6 +15609,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"monaco-editor@npm:^0.52.2": + version: 0.52.2 + resolution: "monaco-editor@npm:0.52.2" + checksum: d5ff7b7a469afee25ac708d9ace0dcc5ef24ed328dfc526a52944a497f0d826cfb0685a778ff4b7becc0a8f7843f260c17ea6de3f6719481d53501d79ebb1260 + languageName: node + linkType: hard + "moo-color@npm:^1.0.2": version: 1.0.3 resolution: "moo-color@npm:1.0.3" @@ -19951,6 +19982,13 @@ coolshapes-react@lowcoder-org/coolshapes-react: languageName: node linkType: hard +"state-local@npm:^1.0.6": + version: 1.0.7 + resolution: "state-local@npm:1.0.7" + checksum: d1afcf1429e7e6eb08685b3a94be8797db847369316d4776fd51f3962b15b984dacc7f8e401ad20968e5798c9565b4b377afedf4e4c4d60fe7495e1cbe14a251 + languageName: node + linkType: hard + "statuses@npm:2.0.1": version: 2.0.1 resolution: "statuses@npm:2.0.1" diff --git a/docs/publish-apps/embedd-an-app/native-embed-sdk/README.md b/docs/publish-apps/embedd-an-app/native-embed-sdk/README.md index 79dfe6a08f..a2d1161672 100644 --- a/docs/publish-apps/embedd-an-app/native-embed-sdk/README.md +++ b/docs/publish-apps/embedd-an-app/native-embed-sdk/README.md @@ -212,6 +212,7 @@ Components cover a wide range of functionalities, from input and selection to di * **QRCodeComp** - Component for displaying QR codes. * **RadioComp** - Component for radio button functionality, allowing users to select a single option from a set. * **RatingComp** - Component for rating functionality, allowing users to provide a rating, typically represented by stars or similar indicators. +* **CodeEditorComp** - Component for code editing, offering multi-language support, syntax highlighting, and built-in validation. * **RichTextEditorComp** - Component for rich text editing, providing a user interface for formatting text with options like bold, italic, lists, etc. * **SliderComp** - Component for slider functionality, allowing users to select a value from a range by sliding a handle. * **SwitchComp** - Component for switch functionality, allowing users to toggle between two states, such as on/off.