Use JSON visualizer for JSON fields (#11428)

- Updates on the JSON field input
  - Previously, we were editing json fields in a textarea 
- Now, we display a JSON visualizer and the user can click on an Edit
button to edit the JSON in Monaco
- The JSON field input has a special behavior for workflow run output.
We want the error to be displayed first in the visualizer. Displaying
the error in red was optional but makes the output clearer in the
context of a workflow run record board.
- Made the code editor transparent in the json field input
- Ensure workflow run's output is not considered readonly in
`packages/twenty-front/src/modules/object-record/record-field/utils/isFieldValueReadOnly.ts`;
we want the json visualizer to always be displayed in this specific case

## Demo

### Failed Workflow Run


https://github.com/user-attachments/assets/7a438d11-53fb-4425-a982-25bbea4ee7a8

### Any JSON field in the record table


https://github.com/user-attachments/assets/b5591abe-3483-4473-bd87-062a45e653e3

Closes https://github.com/twentyhq/core-team-issues/issues/539
This commit is contained in:
Baptiste Devessier
2025-04-08 18:18:36 +02:00
committed by GitHub
parent c1d421de06
commit 6521d19238
13 changed files with 300 additions and 58 deletions

View File

@ -2,21 +2,28 @@ import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import Editor, { EditorProps, Monaco } from '@monaco-editor/react';
import { Loader } from '@ui/feedback/loader/components/Loader';
import { codeEditorTheme } from '@ui/input';
import { BASE_CODE_EDITOR_THEME_ID } from '@ui/input/code-editor/constants/BaseCodeEditorThemeId';
import { getBaseCodeEditorTheme } from '@ui/input/code-editor/theme/utils/getBaseCodeEditorTheme';
import { editor } from 'monaco-editor';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
type CodeEditorProps = Omit<EditorProps, 'onChange'> & {
type CodeEditorVariant = 'default' | 'with-header' | 'borderless';
type CodeEditorProps = Pick<
EditorProps,
'value' | 'language' | 'onMount' | 'onValidate' | 'height' | 'options'
> & {
onChange?: (value: string) => void;
setMarkers?: (value: string) => editor.IMarkerData[];
withHeader?: boolean;
variant?: CodeEditorVariant;
isLoading?: boolean;
transparentBackground?: boolean;
};
const StyledEditorLoader = styled.div<{
height: string | number;
withHeader?: boolean;
variant: CodeEditorVariant;
}>`
align-items: center;
display: flex;
@ -24,35 +31,66 @@ const StyledEditorLoader = styled.div<{
justify-content: center;
border: 1px solid ${({ theme }) => theme.border.color.medium};
background-color: ${({ theme }) => theme.background.transparent.lighter};
${({ withHeader, theme }) =>
withHeader
? css`
${({ variant, theme }) => {
switch (variant) {
case 'default':
return css`
border-radius: ${theme.border.radius.sm};
`;
case 'borderless':
return css`
border: none;
`;
case 'with-header':
return css`
border-radius: 0 0 ${theme.border.radius.sm} ${theme.border.radius.sm};
border-top: none;
`
: css`
border-radius: ${theme.border.radius.sm};
`}
`;
}
}}
`;
const StyledEditor = styled(Editor)<{ withHeader: boolean }>`
const StyledEditor = styled(Editor)<{
variant: CodeEditorVariant;
transparentBackground?: boolean;
}>`
.monaco-editor {
border-radius: ${({ theme }) => theme.border.radius.sm};
outline-width: 0;
${({ theme, transparentBackground }) =>
!transparentBackground &&
css`
background-color: ${theme.background.secondary};
`}
${({ variant, theme }) =>
variant !== 'borderless' &&
css`
border-radius: ${theme.border.radius.sm};
`}
}
.overflow-guard {
border: 1px solid ${({ theme }) => theme.border.color.medium};
box-sizing: border-box;
${({ withHeader, theme }) =>
withHeader
? css`
${({ variant, theme }) => {
switch (variant) {
case 'default': {
return css`
border: 1px solid ${theme.border.color.medium};
border-radius: ${theme.border.radius.sm};
`;
}
case 'with-header': {
return css`
border: 1px solid ${theme.border.color.medium};
border-radius: 0 0 ${theme.border.radius.sm}
${theme.border.radius.sm};
border-top: none;
`
: css`
border-radius: ${theme.border.radius.sm};
`}
`;
}
}
}}
}
`;
@ -64,7 +102,8 @@ export const CodeEditor = ({
setMarkers,
onValidate,
height = 450,
withHeader = false,
variant = 'default',
transparentBackground,
isLoading = false,
options,
}: CodeEditorProps) => {
@ -89,21 +128,29 @@ export const CodeEditor = ({
};
return isLoading ? (
<StyledEditorLoader height={height} withHeader={withHeader}>
<StyledEditorLoader height={height} variant={variant}>
<Loader />
</StyledEditorLoader>
) : (
<StyledEditor
height={height}
withHeader={withHeader}
variant={variant}
value={isLoading ? '' : value}
language={language}
loading=""
transparentBackground={transparentBackground}
onMount={(editor, monaco) => {
setMonaco(monaco);
setEditor(editor);
monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme));
monaco.editor.setTheme('codeEditorTheme');
monaco.editor.defineTheme(
BASE_CODE_EDITOR_THEME_ID,
getBaseCodeEditorTheme({
theme,
}),
);
monaco.editor.setTheme(BASE_CODE_EDITOR_THEME_ID);
onMount?.(editor, monaco);
setModelMarkers(editor, monaco);
}}

View File

@ -0,0 +1 @@
export const BASE_CODE_EDITOR_THEME_ID = 'baseCodeEditorTheme';

View File

@ -1,3 +1,3 @@
export * from './components/CodeEditor';
export * from './components/CodeEditorHeader';
export * from './theme/utils/codeEditorTheme';
export * from './theme/utils/getBaseCodeEditorTheme';

View File

@ -1,9 +1,13 @@
import { ThemeType } from '@ui/theme';
import { editor } from 'monaco-editor';
export const codeEditorTheme = (theme: ThemeType) => {
export const getBaseCodeEditorTheme = ({
theme,
}: {
theme: ThemeType;
}): editor.IStandaloneThemeData => {
return {
base: 'vs' as editor.BuiltinTheme,
base: 'vs',
inherit: true,
rules: [
{
@ -23,7 +27,8 @@ export const codeEditorTheme = (theme: ThemeType) => {
},
],
colors: {
'editor.background': theme.background.secondary,
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
'editor.background': '#00000000',
'editorCursor.foreground': theme.font.color.primary,
'editorLineNumber.foreground': theme.font.color.extraLight,
'editorLineNumber.activeForeground': theme.font.color.light,