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:
committed by
GitHub
parent
c1d421de06
commit
6521d19238
@ -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);
|
||||
}}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const BASE_CODE_EDITOR_THEME_ID = 'baseCodeEditorTheme';
|
||||
@ -1,3 +1,3 @@
|
||||
export * from './components/CodeEditor';
|
||||
export * from './components/CodeEditorHeader';
|
||||
export * from './theme/utils/codeEditorTheme';
|
||||
export * from './theme/utils/getBaseCodeEditorTheme';
|
||||
|
||||
@ -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,
|
||||
Reference in New Issue
Block a user