JSON editor: add code editor mode (#509)

This commit is contained in:
Sidney Alcantara
2021-10-21 16:14:14 +11:00
parent 1378573237
commit 18bd483a41
3 changed files with 128 additions and 53 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import React, { useState, useEffect } from "react";
import Editor, { EditorProps, useMonaco } from "@monaco-editor/react";
import type { languages } from "monaco-editor/esm/vs/editor/editor.api";
import type { editor, languages } from "monaco-editor/esm/vs/editor/editor.api";
import githubLightTheme from "./github-light-default.json";
import githubDarkTheme from "./github-dark-default.json";
@@ -23,11 +23,15 @@ export interface ICodeEditorProps extends Partial<EditorProps> {
value: string;
minHeight?: number;
disabled?: boolean;
error?: boolean;
containerProps?: Partial<BoxProps>;
extraLibs?: string[];
onValidate?: EditorProps["onValidate"];
onValidStatusUpdate?: ({ isValid: boolean }) => void;
onValidStatusUpdate?: (result: {
isValid: boolean;
markers: editor.IMarker[];
}) => void;
diagnosticsOptions?: languages.typescript.DiagnosticsOptions;
onUnmount?: () => void;
}
@@ -36,6 +40,7 @@ export default function CodeEditor({
value,
minHeight = 100,
disabled,
error,
containerProps,
extraLibs,
@@ -60,7 +65,7 @@ export default function CodeEditor({
const onValidate_: EditorProps["onValidate"] = (markers) => {
if (onValidStatusUpdate)
onValidStatusUpdate({ isValid: markers.length <= 0 });
onValidStatusUpdate({ isValid: markers.length <= 0, markers });
else if (onValidate) onValidate(markers);
};
@@ -210,6 +215,15 @@ export default function CodeEditor({
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
...(error
? {
"&::after, &:hover::after, &:focus-within::after": {
boxShadow: `0 -2px 0 0 ${theme.palette.error.main} inset,
0 0 0 1px ${theme.palette.action.inputOutline} inset`,
},
}
: {}),
"& .editor": {
// Overwrite user-select: none that causes editor
// to not be focusable in Safari
@@ -221,7 +235,7 @@ export default function CodeEditor({
}}
>
<Editor
language="javascript"
defaultLanguage="javascript"
value={initialEditorValue}
onValidate={onValidate_}
loading={<CircularProgressOptical size={20} sx={{ m: 2 }} />}
@@ -236,6 +250,7 @@ export default function CodeEditor({
lineDecorationsWidth: 0,
automaticLayout: true,
fixedOverflowWidgets: true,
tabSize: 2,
...props.options,
}}
/>

View File

@@ -16,8 +16,10 @@ import { useProjectContext } from "@src/contexts/ProjectContext";
import { runRoutes } from "@src/constants/runRoutes";
import { WIKI_LINKS } from "constants/externalLinks";
const useAlgoliaSearchKeys = createPersistedState("_ROWY_algolia-search-keys");
const useAlgoliaAppId = createPersistedState("_ROWY_algolia-app-id");
const useAlgoliaSearchKeys = createPersistedState(
"__ROWY__ALGOLIA_SEARCH_KEYS"
);
const useAlgoliaAppId = createPersistedState("__ROWY__ALGOLIA_APP_ID");
export type ConnectTableValue = {
docPath: string;

View File

@@ -1,12 +1,20 @@
import { useState } from "react";
import { Controller } from "react-hook-form";
import createPersistedState from "use-persisted-state";
import { ISideDrawerFieldProps } from "../types";
import ReactJson from "react-json-view";
import jsonFormat from "json-format";
import CodeEditor from "components/CodeEditor";
import { useTheme } from "@mui/material";
import { useTheme, Tab, FormHelperText } from "@mui/material";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import TabPanel from "@mui/lab/TabPanel";
import { useFieldStyles } from "components/SideDrawer/Form/utils";
const useJsonEditor = createPersistedState("__ROWY__JSON_EDITOR");
const isValidJson = (val: any) => {
try {
if (typeof val === "string") JSON.parse(val);
@@ -25,11 +33,23 @@ export default function Json({
const fieldClasses = useFieldStyles();
const theme = useTheme();
const [editor, setEditor] = useJsonEditor<"tree" | "code">("tree");
const [codeValid, setCodeValid] = useState(true);
const changeEditor = (_, newValue) => {
setEditor(newValue);
setCodeValid(true);
};
return (
<Controller
control={control}
name={column.key}
render={({ field: { onChange, value } }) => {
const formattedJson = value
? jsonFormat(value, { type: "space", char: " ", size: 2 })
: "";
if (disabled)
return (
<div
@@ -41,12 +61,7 @@ export default function Json({
wordBreak: "break-word",
}}
>
{value &&
jsonFormat(value, {
type: "space",
char: " ",
size: 2,
})}
{value && formattedJson}
</div>
);
@@ -55,46 +70,89 @@ export default function Json({
};
return (
<div
className={fieldClasses.root}
style={{ overflowX: "auto", ...theme.typography.caption }}
>
<ReactJson
src={
value !== undefined && isValidJson(value)
? value
: column.config?.isArray
? []
: {}
}
onEdit={handleEdit}
onAdd={handleEdit}
onDelete={handleEdit}
theme={{
base00: "rgba(0, 0, 0, 0)",
base01: theme.palette.background.default,
base02: theme.palette.divider,
base03: "#93a1a1",
base04: theme.palette.text.disabled,
base05: theme.palette.text.secondary,
base06: "#073642",
base07: theme.palette.text.primary,
base08: "#d33682",
base09: "#cb4b16",
base0A: "#dc322f",
base0B: "#859900",
base0C: "#6c71c4",
base0D: theme.palette.text.secondary,
base0E: "#2aa198",
base0F: "#268bd2",
<TabContext value={editor}>
<TabList
onChange={changeEditor}
aria-label="JSON editor"
variant="standard"
sx={{
minHeight: 32,
mt: -32 / 8,
"& .MuiTabs-flexContainer": { justifyContent: "flex-end" },
"& .MuiTab-root": { minHeight: 32, py: 0 },
}}
iconStyle="triangle"
style={{
fontFamily: theme.typography.fontFamilyMono,
backgroundColor: "transparent",
}}
/>
</div>
>
<Tab label="Tree" value="tree" />
<Tab label="Code" value="code" />
</TabList>
<TabPanel value="tree" sx={{ p: 0 }}>
<div
className={fieldClasses.root}
style={{ overflowX: "auto", ...theme.typography.caption }}
>
<ReactJson
src={
value !== undefined && isValidJson(value)
? value
: column.config?.isArray
? []
: {}
}
onEdit={handleEdit}
onAdd={handleEdit}
onDelete={handleEdit}
theme={{
base00: "rgba(0, 0, 0, 0)",
base01: theme.palette.background.default,
base02: theme.palette.divider,
base03: "#93a1a1",
base04: theme.palette.text.disabled,
base05: theme.palette.text.secondary,
base06: "#073642",
base07: theme.palette.text.primary,
base08: "#d33682",
base09: "#cb4b16",
base0A: "#dc322f",
base0B: "#859900",
base0C: "#6c71c4",
base0D: theme.palette.text.secondary,
base0E: "#2aa198",
base0F: "#268bd2",
}}
iconStyle="triangle"
style={{
fontFamily: theme.typography.fontFamilyMono,
backgroundColor: "transparent",
minHeight: 100 - 4 - 4,
}}
/>
</div>
</TabPanel>
<TabPanel value="code" sx={{ p: 0 }}>
<CodeEditor
defaultLanguage="json"
value={formattedJson || "{\n \n}"}
onChange={(v) => {
try {
if (v) onChange(JSON.parse(v));
} catch (e) {
console.log(`Failed to parse JSON: ${e}`);
setCodeValid(false);
}
}}
onValidStatusUpdate={({ isValid }) => setCodeValid(isValid)}
error={!codeValid}
/>
{!codeValid && (
<FormHelperText error variant="filled">
Invalid JSON
</FormHelperText>
)}
</TabPanel>
</TabContext>
);
}}
/>