Browse Source

chore: unifiy json editor dialog (#767)

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 4 months ago
parent
commit
2bdee890ef

+ 34 - 6
client/src/pages/databases/collections/data/CollectionData.tsx

@@ -12,7 +12,7 @@ import Filter from '@/components/advancedSearch';
 import DeleteTemplate from '@/components/customDialog/DeleteDialogTemplate';
 import CustomToolBar from '@/components/grid/ToolBar';
 import InsertDialog from '@/pages/dialogs/insert/Dialog';
-import EditEntityDialog from '@/pages/dialogs/EditEntityDialog';
+import EditJSONDialog from '@/pages/dialogs/EditJSONDialog';
 import { getLabelDisplayedRows } from '@/pages/search/Utils';
 import { getQueryStyles } from './Styles';
 import {
@@ -31,6 +31,7 @@ import CollectionColHeader from '../CollectionColHeader';
 import DataView from '@/components/DataView/DataView';
 import DataListView from '@/components/DataListView/DataListView';
 import type { QueryState } from '../../types';
+import { CollectionFullObject } from '@server/types';
 
 export interface CollectionDataProps {
   queryState: QueryState;
@@ -148,7 +149,13 @@ const CollectionData = (props: CollectionDataProps) => {
     await fetchCollection(collectionName);
   };
 
-  const onEditEntity = async (id: string) => {
+  const handleEditConfirm = async (data: Record<string, any>) => {
+    const result = (await DataService.upsert(collection.collection_name, {
+      fields_data: [data],
+    })) as any;
+
+    const idField = result.IDs.id_field;
+    const id = result.IDs[idField].data;
     // deselect all
     setSelectedData([]);
     const newExpr = `${collection.schema.primaryField.name} == ${id}`;
@@ -163,6 +170,25 @@ const CollectionData = (props: CollectionDataProps) => {
     });
   };
 
+  const getEditData = (data: any, collection: CollectionFullObject) => {
+    // sort data by collection schema order
+    const schema = collection.schema;
+    let sortedData: { [key: string]: any } = {};
+    schema.fields.forEach(field => {
+      if (data[field.name] !== undefined) {
+        sortedData[field.name] = data[field.name];
+      }
+    });
+
+    // add dynamic fields if exist
+    const isDynamicSchema = collection.schema.dynamicFields.length > 0;
+    if (isDynamicSchema) {
+      sortedData = { ...sortedData, ...data[DYNAMIC_FIELD] };
+    }
+
+    return sortedData;
+  };
+
   // Toolbar settings
   const toolbarConfigs: ToolBarConfig[] = [
     {
@@ -270,10 +296,12 @@ const CollectionData = (props: CollectionDataProps) => {
           type: 'custom',
           params: {
             component: (
-              <EditEntityDialog
-                data={selectedData[0]}
-                collection={collection!}
-                cb={onEditEntity}
+              <EditJSONDialog
+                data={getEditData(selectedData[0], collection)}
+                dialogTitle={dialogTrans('editEntityTitle')}
+                dialogTip={dialogTrans('editEntityInfo')}
+                handleConfirm={handleEditConfirm}
+                handleCloseDialog={handleCloseDialog}
               />
             ),
           },

+ 0 - 174
client/src/pages/dialogs/EditEntityDialog.tsx

@@ -1,174 +0,0 @@
-import { FC, useContext, useEffect, useRef, useState } from 'react';
-import { Theme, useTheme } from '@mui/material';
-import { EditorState, Compartment } from '@codemirror/state';
-import { EditorView, keymap, ViewUpdate } from '@codemirror/view';
-import { insertTab } from '@codemirror/commands';
-import { indentUnit } from '@codemirror/language';
-import { basicSetup } from 'codemirror';
-import { json, jsonParseLinter } from '@codemirror/lang-json';
-import { linter } from '@codemirror/lint';
-import { useTranslation } from 'react-i18next';
-import { rootContext } from '@/context';
-import DialogTemplate from '@/components/customDialog/DialogTemplate';
-import { DataService } from '@/http';
-import { DYNAMIC_FIELD } from '@/consts';
-import { makeStyles } from '@mui/styles';
-import { githubLight } from '@ddietr/codemirror-themes/github-light';
-import { githubDark } from '@ddietr/codemirror-themes/github-dark';
-import type { CollectionFullObject } from '@server/types';
-
-const useStyles = makeStyles((theme: Theme) => ({
-  code: {
-    border: `1px solid ${theme.palette.divider}`,
-    overflow: 'auto',
-  },
-  tip: {
-    fontSize: 14,
-    marginBottom: 16,
-    fontWeight: 'bold',
-    color: theme.palette.warning.main,
-  },
-}));
-
-type EditEntityDialogProps = {
-  data: { [key: string]: any };
-  collection: CollectionFullObject;
-  cb?: (id: string) => void;
-};
-
-// json linter for cm
-const linterExtension = linter(jsonParseLinter());
-
-const EditEntityDialog: FC<EditEntityDialogProps> = props => {
-  const theme = useTheme();
-  const themeCompartment = new Compartment();
-
-  // props
-  const { data, collection } = props;
-  // UI states
-  const [disabled, setDisabled] = useState(true);
-  // context
-  const { handleCloseDialog } = useContext(rootContext);
-  // translations
-  const { t: btnTrans } = useTranslation('btn');
-  const { t: dialogTrans } = useTranslation('dialog');
-  // refs
-  const editorEl = useRef<HTMLDivElement>(null);
-  const editor = useRef<EditorView>();
-  // styles
-  const classes = useStyles();
-
-  // sort data by collection schema order
-  const schema = collection.schema;
-  let sortedData: { [key: string]: any } = {};
-  schema.fields.forEach(field => {
-    if (data[field.name] !== undefined) {
-      sortedData[field.name] = data[field.name];
-    }
-  });
-
-  // add dynamic fields if exist
-  const isDynamicSchema = collection.schema.dynamicFields.length > 0;
-  if (isDynamicSchema) {
-    sortedData = { ...sortedData, ...data[DYNAMIC_FIELD] };
-  }
-
-  const originalData = JSON.stringify(sortedData, null, 2);
-
-  // create editor
-  useEffect(() => {
-    if (!editor.current) {
-      const startState = EditorState.create({
-        doc: originalData,
-        extensions: [
-          basicSetup,
-          json(),
-          linterExtension,
-          keymap.of([{ key: 'Tab', run: insertTab }]), // fix tab behaviour
-          indentUnit.of('    '), // fix tab indentation
-          EditorView.theme({
-            '&.cm-editor': {
-              '&.cm-focused': {
-                outline: 'none',
-              },
-            },
-            '.cm-content': {
-              fontSize: '12px',
-            },
-            '.cm-tooltip-lint': {
-              width: '80%',
-            },
-          }),
-          themeCompartment.of(
-            theme.palette.mode === 'light' ? githubLight : githubDark
-          ),
-          EditorView.lineWrapping,
-          EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
-            if (viewUpdate.docChanged) {
-              const d = jsonParseLinter()(view);
-              if (d.length !== 0) {
-                setDisabled(true);
-                return;
-              }
-
-              const doc = viewUpdate.state.doc;
-              const value = doc.toString();
-
-              setDisabled(value === originalData);
-            }
-          }),
-        ],
-      });
-
-      const view = new EditorView({
-        state: startState,
-        parent: editorEl.current!,
-      });
-
-      editor.current = view;
-
-      return () => {
-        view.destroy();
-        editor.current = undefined;
-      };
-    }
-  }, [JSON.stringify(data)]);
-
-  // handle confirm
-  const handleConfirm = async () => {
-    const result = (await DataService.upsert(collection.collection_name, {
-      fields_data: [JSON.parse(editor.current?.state.doc.toString()!)],
-    })) as any;
-
-    const idField = result.IDs.id_field;
-    const id = result.IDs[idField].data;
-
-    props.cb && props.cb(id[0]);
-    handleCloseDialog();
-  };
-
-  return (
-    <DialogTemplate
-      title={dialogTrans('editEntityTitle')}
-      handleClose={handleCloseDialog}
-      children={
-        <>
-          <div className={classes.tip}>{dialogTrans('editEntityInfo')}</div>
-          <div
-            className={`${classes.code} cm-editor`}
-            ref={editorEl}
-            onClick={() => {
-              if (editor.current) editor.current.focus();
-            }}
-          ></div>
-        </>
-      }
-      confirmDisabled={disabled}
-      confirmLabel={btnTrans('edit')}
-      handleConfirm={handleConfirm}
-      showCancel={true}
-    />
-  );
-};
-
-export default EditEntityDialog;

+ 9 - 8
client/src/pages/dialogs/EditAnalyzerDialog.tsx → client/src/pages/dialogs/EditJSONDialog.tsx

@@ -26,17 +26,20 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-type EditAnalyzerDialogProps = {
+type EditJSONDialogProps = {
   data: { [key: string]: any };
   handleConfirm: (data: { [key: string]: any }) => void;
   handleCloseDialog: () => void;
+  dialogTitle: string;
+  dialogTip: string;
   cb?: () => void;
 };
 
 // json linter for cm
 const linterExtension = linter(jsonParseLinter());
 
-const EditAnalyzerDialog: FC<EditAnalyzerDialogProps> = props => {
+const EditJSONDialog: FC<EditJSONDialogProps> = props => {
+  // hooks
   const theme = useTheme();
   const themeCompartment = new Compartment();
 
@@ -44,17 +47,15 @@ const EditAnalyzerDialog: FC<EditAnalyzerDialogProps> = props => {
   const { data, handleCloseDialog, handleConfirm } = props;
   // UI states
   const [disabled, setDisabled] = useState(true);
-  // context
   // translations
   const { t: btnTrans } = useTranslation('btn');
-  const { t: dialogTrans } = useTranslation('dialog');
   // refs
   const editorEl = useRef<HTMLDivElement>(null);
   const editor = useRef<EditorView>();
   // styles
   const classes = useStyles();
 
-  const originalData = JSON.stringify(data, null, 4) + '\n';
+  const originalData = JSON.stringify(data, null, 2) + '\n';
 
   // create editor
   useEffect(() => {
@@ -123,14 +124,14 @@ const EditAnalyzerDialog: FC<EditAnalyzerDialogProps> = props => {
 
   return (
     <DialogTemplate
-      title={dialogTrans('editAnalyzerTitle')}
+      title={props.dialogTitle}
       handleClose={handleCloseDialog}
       children={
         <>
           <div
             className={classes.tip}
             dangerouslySetInnerHTML={{
-              __html: dialogTrans('editAnalyzerInfo'),
+              __html: props.dialogTip,
             }}
           ></div>
           <div
@@ -150,4 +151,4 @@ const EditAnalyzerDialog: FC<EditAnalyzerDialogProps> = props => {
   );
 };
 
-export default EditAnalyzerDialog;
+export default EditJSONDialog;

+ 8 - 2
client/src/pages/dialogs/create/CreateFields.tsx

@@ -37,7 +37,7 @@ import {
 } from '@/consts';
 import { makeStyles } from '@mui/styles';
 import CustomIconButton from '@/components/customButton/CustomIconButton';
-import EditAnalyzerDialog from '@/pages/dialogs/EditAnalyzerDialog';
+import EditJSONDialog from '@/pages/dialogs/EditJSONDialog';
 import type {
   CreateFieldsProps,
   CreateFieldType,
@@ -171,11 +171,15 @@ const CreateFields: FC<CreateFieldsProps> = ({
   autoID,
   setFieldsValidation,
 }) => {
+  // context
   const { setDialog2, handleCloseDialog2 } = useContext(rootContext);
 
+  // i18n
   const { t: collectionTrans } = useTranslation('collection');
   const { t: warningTrans } = useTranslation('warning');
+  const { t: dialogTrans } = useTranslation('dialog');
 
+  // styles
   const classes = useStyles();
 
   const AddIcon = icons.addOutline;
@@ -628,10 +632,12 @@ const CreateFields: FC<CreateFieldsProps> = ({
               type: 'custom',
               params: {
                 component: (
-                  <EditAnalyzerDialog
+                  <EditJSONDialog
                     data={getAnalyzerParams(
                       field.analyzer_params || 'standard'
                     )}
+                    dialogTitle={dialogTrans('editAnalyzerTitle')}
+                    dialogTip={dialogTrans('editAnalyzerInfo')}
                     handleConfirm={data => {
                       changeFields(field.id!, { analyzer_params: data });
                     }}