mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
JSON editor: add code editor mode (#509)
This commit is contained in:
@@ -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,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user