Serverless function UI (#6388)
https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=36235-120877 Did not do the file manager part. A Function is defined using one unique file at the moment Feature protected by featureFlag `IS_FUNCTION_SETTINGS_ENABLED` ## Demo https://github.com/user-attachments/assets/0acb8291-47b4-4521-a6fa-a88b9198609b
This commit is contained in:
@ -0,0 +1,80 @@
|
||||
import Editor, { Monaco, EditorProps } from '@monaco-editor/react';
|
||||
import { editor } from 'monaco-editor';
|
||||
import { codeEditorTheme } from '@/ui/input/code-editor/theme/CodeEditorTheme';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const DEFAULT_CODE = `export const handler = async (
|
||||
event: object,
|
||||
context: object
|
||||
): Promise<object> => {
|
||||
// Your code here
|
||||
return {};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEditor = styled(Editor)`
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-top: none;
|
||||
border-radius: 0 0 ${({ theme }) => theme.border.radius.sm}
|
||||
${({ theme }) => theme.border.radius.sm};
|
||||
`;
|
||||
|
||||
type CodeEditorProps = Omit<EditorProps, 'onChange'> & {
|
||||
header: React.ReactNode;
|
||||
onChange?: (value: string) => void;
|
||||
};
|
||||
|
||||
export const CodeEditor = ({
|
||||
value = DEFAULT_CODE,
|
||||
onChange,
|
||||
language = 'typescript',
|
||||
height = 500,
|
||||
options = undefined,
|
||||
header,
|
||||
}: CodeEditorProps) => {
|
||||
const theme = useTheme();
|
||||
const handleEditorDidMount = (
|
||||
editor: editor.IStandaloneCodeEditor,
|
||||
monaco: Monaco,
|
||||
) => {
|
||||
monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme));
|
||||
monaco.editor.setTheme('codeEditorTheme');
|
||||
};
|
||||
useEffect(() => {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
.monaco-editor .margin .line-numbers {
|
||||
font-weight: bold;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
return () => {
|
||||
document.head.removeChild(style);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div>
|
||||
{header}
|
||||
<StyledEditor
|
||||
height={height}
|
||||
language={language}
|
||||
value={value}
|
||||
onMount={handleEditorDidMount}
|
||||
onChange={(value?: string) => value && onChange?.(value)}
|
||||
options={{
|
||||
...options,
|
||||
overviewRulerLanes: 0,
|
||||
scrollbar: {
|
||||
vertical: 'hidden',
|
||||
horizontal: 'hidden',
|
||||
},
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledEditorHeader = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(10)};
|
||||
padding: ${({ theme }) => `0 ${theme.spacing(2)}`};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const StyledElementContainer = styled.div`
|
||||
align-content: flex-end;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export type CoreEditorHeaderProps = {
|
||||
title?: string;
|
||||
leftNodes?: React.ReactNode[];
|
||||
rightNodes?: React.ReactNode[];
|
||||
};
|
||||
|
||||
export const CoreEditorHeader = ({
|
||||
title,
|
||||
leftNodes,
|
||||
rightNodes,
|
||||
}: CoreEditorHeaderProps) => {
|
||||
return (
|
||||
<StyledEditorHeader>
|
||||
<StyledElementContainer>
|
||||
{leftNodes &&
|
||||
leftNodes.map((leftButton, index) => {
|
||||
return <div key={`left-${index}`}>{leftButton}</div>;
|
||||
})}
|
||||
{title}
|
||||
</StyledElementContainer>
|
||||
<StyledElementContainer>
|
||||
{rightNodes &&
|
||||
rightNodes.map((rightButton, index) => {
|
||||
return <div key={`right-${index}`}>{rightButton}</div>;
|
||||
})}
|
||||
</StyledElementContainer>
|
||||
</StyledEditorHeader>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,33 @@
|
||||
import { editor } from 'monaco-editor';
|
||||
import { ThemeType } from 'twenty-ui';
|
||||
|
||||
export const codeEditorTheme = (theme: ThemeType) => {
|
||||
return {
|
||||
base: 'vs' as editor.BuiltinTheme,
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
token: '',
|
||||
foreground: theme.code.text.gray,
|
||||
fontStyle: 'bold',
|
||||
},
|
||||
{ token: 'keyword', foreground: theme.code.text.sky },
|
||||
{
|
||||
token: 'delimiter',
|
||||
foreground: theme.code.text.gray,
|
||||
},
|
||||
{ token: 'string', foreground: theme.code.text.pink },
|
||||
{
|
||||
token: 'comment',
|
||||
foreground: theme.code.text.orange,
|
||||
},
|
||||
],
|
||||
colors: {
|
||||
'editor.background': theme.background.secondary,
|
||||
'editorCursor.foreground': theme.font.color.primary,
|
||||
'editorLineNumber.foreground': theme.font.color.extraLight,
|
||||
'editorLineNumber.activeForeground': theme.font.color.light,
|
||||
'editor.lineHighlightBackground': theme.background.tertiary,
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -29,7 +29,6 @@ const StyledTextArea = styled(TextareaAutosize)`
|
||||
line-height: 16px;
|
||||
overflow: auto;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(3)};
|
||||
resize: none;
|
||||
width: 100%;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user