ソースを参照

[Feature][UI Next] Add monaco editor to form (#8241)

Amy0104 3 年 前
コミット
832f7352c9

+ 11 - 0
dolphinscheduler-ui-next/src/components/form/fields.ts

@@ -17,6 +17,7 @@
 
 import { h } from 'vue'
 import { NInput, NRadio, NRadioGroup, NSpace } from 'naive-ui'
+import Editor from '@/components/monaco-editor'
 import type { IFieldParams } from './types'
 
 // TODO Support other widgets later
@@ -52,3 +53,13 @@ export function renderRadio(params: IFieldParams) {
       )
   )
 }
+
+// Editor
+export function renderEditor(params: IFieldParams) {
+  const { props, fields, field } = params
+  return h(Editor, {
+    ...props,
+    value: fields[field],
+    onUpdateValue: (value) => void (fields[field] = value)
+  })
+}

+ 9 - 4
dolphinscheduler-ui-next/src/components/form/get-elements-by-json.ts

@@ -22,9 +22,7 @@ import type { IJsonItem } from './types'
 
 export default function getElementByJson(
   json: IJsonItem[],
-  fields: { [field: string]: any },
-  t: Function,
-  prefix: string
+  fields: { [field: string]: any }
 ) {
   const rules: FormRules = {}
   const initialValues: { [field: string]: any } = {}
@@ -41,6 +39,13 @@ export default function getElementByJson(
         options
       })
     }
+    if (type === 'editor') {
+      return Field.renderEditor({
+        field,
+        fields,
+        props
+      })
+    }
 
     return Field.renderInput({ field, fields, props })
   }
@@ -50,7 +55,7 @@ export default function getElementByJson(
     initialValues[item.field] = item.value
     if (item.validate) rules[item.field] = formatValidate(item.validate)
     elements.push({
-      label: t(prefix + '.' + item.field),
+      label: item.name,
       path: item.field,
       widget: () => getElement(item)
     })

+ 1 - 1
dolphinscheduler-ui-next/src/components/form/types.ts

@@ -24,7 +24,7 @@ import type {
   SelectOption
 } from 'naive-ui'
 
-type IType = 'input' | 'radio'
+type IType = 'input' | 'radio' | 'editor'
 
 type IOption = SelectOption
 

+ 1 - 1
dolphinscheduler-ui-next/src/components/form/utils.ts

@@ -27,7 +27,7 @@ export function formatValidate(
 ): FormItemRule {
   if (!validate) return {}
   if (Array.isArray(validate)) {
-    validate.map((item: FormItemRule) => {
+    validate.forEach((item: FormItemRule) => {
       if (!item?.message) delete item.message
       return item
     })

+ 43 - 10
dolphinscheduler-ui-next/src/components/monaco-editor/index.tsx

@@ -24,18 +24,26 @@ import {
   ref,
   watch
 } from 'vue'
+import { useFormItem } from 'naive-ui/es/_mixins'
+import { call } from 'naive-ui/es/_utils'
 import * as monaco from 'monaco-editor'
 import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
 import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
 import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
 import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
 import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
+import type { MaybeArray, OnUpdateValue, OnUpdateValueImpl } from './types'
 
 const props = {
-  modelValue: {
+  value: {
     type: String as PropType<string>,
     default: ''
   },
+  defaultValue: {
+    type: String as PropType<string>
+  },
+  'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
+  onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
   language: {
     type: String as PropType<string>,
     default: 'shell'
@@ -72,13 +80,18 @@ window.MonacoEnvironment = {
 export default defineComponent({
   name: 'MonacoEditor',
   props,
-  setup(props) {
+  emits: ['change', 'focus', 'blur'],
+  setup(props, ctx) {
     let editor = null as monaco.editor.IStandaloneCodeEditor | null
-    const content = ref()
+
+    const editorRef = ref()
+
     const getValue = () => editor?.getValue()
 
+    const formItem = useFormItem({})
+
     watch(
-      () => props.modelValue,
+      () => props.value,
       (val) => {
         if (val !== getValue()) {
           editor?.setValue(val)
@@ -87,17 +100,34 @@ export default defineComponent({
     )
 
     onMounted(async () => {
-      content.value = props.modelValue
-
       await nextTick()
-      const dom = document.getElementById('monaco-container')
+      const dom = editorRef.value
       if (dom) {
         editor = monaco.editor.create(dom, props.options, {
-          value: props.modelValue,
+          value: props.defaultValue ?? props.value,
           language: props.language,
           readOnly: props.readOnly,
           automaticLayout: true
         })
+        editor.onDidChangeModelContent(() => {
+          const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props
+          const value = editor?.getValue() || ''
+
+          if (onUpdateValue) call(onUpdateValue as OnUpdateValueImpl, value)
+          if (_onUpdateValue) call(_onUpdateValue as OnUpdateValueImpl, value)
+          ctx.emit('change', value)
+
+          formItem.nTriggerFormChange()
+          formItem.nTriggerFormInput()
+        })
+        editor.onDidBlurEditorWidget(() => {
+          ctx.emit('blur')
+          formItem.nTriggerFormBlur()
+        })
+        editor.onDidFocusEditorWidget(() => {
+          ctx.emit('focus')
+          formItem.nTriggerFormFocus()
+        })
       }
     })
 
@@ -105,14 +135,17 @@ export default defineComponent({
       editor?.dispose()
     })
 
-    return { getValue }
+    ctx.expose({ getValue })
+
+    return { editorRef }
   },
   render() {
     return (
       <div
-        id='monaco-container'
+        ref='editorRef'
         style={{
           height: '300px',
+          width: '100%',
           border: '1px solid #eee'
         }}
       ></div>

+ 22 - 0
dolphinscheduler-ui-next/src/components/monaco-editor/types.ts

@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import type { MaybeArray } from 'naive-ui/es/_utils'
+
+type OnUpdateValue = <T extends string>(value: T) => void
+type OnUpdateValueImpl = (value: string) => void
+
+export { MaybeArray, OnUpdateValue, OnUpdateValueImpl }

+ 0 - 1
dolphinscheduler-ui-next/src/views/datasource/list/use-columns.ts

@@ -91,7 +91,6 @@ export function useColumns(onCallback: Function) {
               NButton,
               {
                 circle: true,
-                class: styles['mr-10'],
                 type: 'info',
                 onClick: () => void onCallback(rowData.id, 'edit')
               },

+ 1 - 4
dolphinscheduler-ui-next/src/views/resource/file/create/index.tsx

@@ -35,7 +35,6 @@ export default defineComponent({
   setup() {
     const router: Router = useRouter()
 
-    const codeEditorRef = ref()
     const { state } = useForm()
     const { handleCreateFile } = useCreate(state)
 
@@ -46,7 +45,6 @@ export default defineComponent({
     }))
 
     const handleFile = () => {
-      state.fileForm.content = codeEditorRef.value?.getValue()
       handleCreateFile()
     }
 
@@ -57,7 +55,6 @@ export default defineComponent({
     }
 
     return {
-      codeEditorRef,
       fileSuffixOptions,
       handleFile,
       handleReturn,
@@ -105,7 +102,7 @@ export default defineComponent({
                 width: '90%'
               }}
             >
-              <MonacoEditor ref='codeEditorRef' />
+              <MonacoEditor v-model={[this.fileForm.content, 'value']} />
             </div>
           </NFormItem>
           <div class={styles['file-edit-content']}>

+ 2 - 5
dolphinscheduler-ui-next/src/views/resource/file/edit/index.tsx

@@ -33,7 +33,6 @@ export default defineComponent({
     const router: Router = useRouter()
 
     const resourceViewRef = ref()
-    const codeEditorRef = ref()
     const routeNameRef = ref(router.currentRoute.value.name)
     const idRef = ref(Number(router.currentRoute.value.params.id))
 
@@ -41,7 +40,7 @@ export default defineComponent({
     const { getResourceView, handleUpdateContent } = useEdit(state)
 
     const handleFileContent = () => {
-      state.fileForm.content = codeEditorRef.value?.getValue()
+      state.fileForm.content = resourceViewRef.value.content
       handleUpdateContent(idRef.value)
     }
 
@@ -56,7 +55,6 @@ export default defineComponent({
     return {
       idRef,
       routeNameRef,
-      codeEditorRef,
       resourceViewRef,
       handleReturn,
       handleFileContent,
@@ -84,8 +82,7 @@ export default defineComponent({
                 }}
               >
                 <MonacoEditor
-                  ref='codeEditorRef'
-                  modelValue={this.resourceViewRef?.value.content}
+                  v-model={[this.resourceViewRef?.value.content, 'value']}
                 />
               </div>
             </NFormItem>

+ 5 - 6
dolphinscheduler-ui-next/src/views/security/alarm-instance-manage/detail.tsx

@@ -83,13 +83,12 @@ const DetailModal = defineComponent({
     watch(
       () => state.json,
       () => {
+        if (state.json?.length) return
+        state.json.forEach((item) => {
+          item.name = t('security.alarm_instance' + '.' + item.field)
+        })
         const { rules: fieldsRules, elements: fieldsElements } =
-          getElementByJson(
-            state.json,
-            state.detailForm,
-            t,
-            'security.alarm_instance'
-          )
+          getElementByJson(state.json, state.detailForm)
         rules.value = fieldsRules
         elements.value = fieldsElements
       }